Repository: ahlstromcj/seq66 Branch: master Commit: 19e2100fd585 Files: 992 Total size: 12.8 MB Directory structure: gitextract_2t7n7wkn/ ├── .gitignore ├── ChangeLog ├── INSTALL ├── INSTALL.seq66.on.Ubuntu.md ├── Makefile.am ├── NEWS ├── README.md ├── RELNOTES ├── ROADMAP.md ├── Seq66cli/ │ ├── Makefile.am │ ├── Seq66cli.pro │ └── seq66rtcli.cpp ├── Seq66qt5/ │ ├── Makefile.am │ ├── Seq66qt5.pro │ └── seq66qt5.cpp ├── TODO ├── VERSION ├── autogen.sh ├── bootstrap ├── bootstrap.help ├── configure ├── configure.ac ├── configure.help ├── contrib/ │ ├── DIR_COLORS │ ├── VMPK.conf │ ├── Xdefaults.rc │ ├── code/ │ │ ├── affinity.cpp │ │ ├── ametro.c │ │ ├── function_calls_gnu.c │ │ ├── function_calls_gnu.h │ │ ├── make_ametro │ │ ├── qchannelpopup.cpp │ │ ├── qchannelpopup.hpp │ │ ├── readbinaryplist.c │ │ ├── readbinaryplist.h │ │ ├── ring_buffer.hpp │ │ ├── test/ │ │ │ └── filename_split.cpp │ │ └── ttymidi.c │ ├── config.rpath │ ├── control-map.rc │ ├── dd-11/ │ │ ├── DD-11_Information.ods │ │ └── dd-11-notes.log │ ├── gdb/ │ │ ├── cgdbrc │ │ └── dot-gdbinit │ ├── git/ │ │ ├── git.odt │ │ ├── git.text │ │ ├── gitconfig │ │ └── github_checklist.ods │ ├── gvim.rc │ ├── midi/ │ │ ├── 16-blank-patterns.midi │ │ ├── 16-blank-patterns.text │ │ ├── 1Bar-1920.midi │ │ ├── 1Bar.midi │ │ ├── 1Bar_2_tracks.midi │ │ ├── 1Note.midi │ │ ├── 24stones.mid │ │ ├── 2B-test.midi │ │ ├── 2Bars-song-export.midi │ │ ├── 2Bars.midi │ │ ├── 2rock.asc │ │ ├── 2rock.hex │ │ ├── 2rock.mid │ │ ├── 2rock.midi │ │ ├── 2stones.mid │ │ ├── Carpet_of_the_Sun_karaoke_meta_text.mid │ │ ├── Carpet_of_the_Sun_karaoke_meta_text.text │ │ ├── CountryStrum.mid │ │ ├── CountryStrum.midi │ │ ├── Dixie04.mid │ │ ├── Kraftwerk-Europe-Endless-exported.midi │ │ ├── MIDI_sample-480.mid │ │ ├── NR_Route_66.midi │ │ ├── README │ │ ├── XBars.midi │ │ ├── ancestor.mid │ │ ├── bad-linking.midi │ │ ├── barrage.midi │ │ ├── buffalo.midi │ │ ├── chords000.midi │ │ ├── click_4_4.midi │ │ ├── click_7_8.midi │ │ ├── colours-qt.midi │ │ ├── controllers.midi │ │ ├── dd-11-rock-sample-8.midi │ │ ├── hanging-note-on.midi │ │ ├── issue49.midi │ │ ├── issue51-1920.midi │ │ ├── issue51.midi │ │ ├── multi-ports.midi │ │ ├── mutes-long.midi │ │ ├── mutes-test.midi │ │ ├── one-shot-recording.midi │ │ ├── patternfix.midi │ │ ├── pitchwheel.midi │ │ ├── play-pattern-1.text │ │ ├── play.midi │ │ ├── playtime.midi │ │ ├── ppqn/ │ │ │ ├── ppqn-120.midi │ │ │ ├── ppqn-192.midi │ │ │ ├── ppqn-1920.midi │ │ │ ├── ppqn-19200.midi │ │ │ ├── ppqn-24.midi │ │ │ ├── ppqn-240.midi │ │ │ ├── ppqn-2400.midi │ │ │ ├── ppqn-32.midi │ │ │ ├── ppqn-384.midi │ │ │ ├── ppqn-3840.midi │ │ │ ├── ppqn-48.midi │ │ │ ├── ppqn-480.midi │ │ │ ├── ppqn-768.midi │ │ │ ├── ppqn-7680.midi │ │ │ ├── ppqn-96.midi │ │ │ ├── ppqn-960.midi │ │ │ └── ppqn-9600.midi │ │ ├── program-test.midi │ │ ├── random.midi │ │ ├── reset.mid │ │ ├── route66.midi │ │ ├── scales-and-chords.midi │ │ ├── seq66-scales.midi │ │ ├── sets-test.midi │ │ ├── simple-dd11-beat-test-GM.midi │ │ ├── simple-dd11-beat-test.midi │ │ ├── simpleblast-ch1-8th-notes-960.midi │ │ ├── simpleblast-ch1-8th-notes.midi │ │ ├── simpleblastbeat.midi │ │ ├── songtest.midi │ │ ├── songtest.text │ │ ├── test.midi │ │ ├── text.midi │ │ ├── timesigs.midi │ │ ├── trilogy.mid │ │ ├── volume.midi │ │ └── world7.midi │ ├── midnam/ │ │ ├── README │ │ └── Roland_MT-32.midnam │ ├── mkinstalldirs-1.10 │ ├── non/ │ │ ├── NSM_API.txt │ │ ├── messagelist.txt │ │ ├── nsm-emails.txt │ │ ├── nsm-proxy-h2.sh │ │ ├── nsm.h │ │ ├── nsm.sh │ │ └── nsm_tendrils.txt │ ├── notes/ │ │ ├── INSTALL4Ubuntu.md │ │ ├── NEWS-template │ │ ├── RELNOTES-0_99_0.md │ │ ├── RPN.text │ │ ├── bluez-alsa-notes.text │ │ ├── bvi.text │ │ ├── clang-macros-freebsd.text │ │ ├── controls.ods │ │ ├── drum_notes_gm_2.text │ │ ├── event-from-bytes.text │ │ ├── freebsd.text │ │ ├── gcc-version.text │ │ ├── gridlines.ods │ │ ├── install-directories.text │ │ ├── key-maps-dump.text │ │ ├── key-names.text │ │ ├── launchpad.text │ │ ├── midi-control-in.text │ │ ├── midi-messages.text │ │ ├── midi-override-options.text │ │ ├── msys2-packages.text │ │ ├── pattern-fix-tests.text │ │ ├── perf-callbacks.text │ │ ├── performance.text │ │ ├── pipewire.text │ │ ├── ppqn-and-grids.ods │ │ ├── program-banks.text │ │ ├── q-hierarchy.text │ │ ├── qw-az-keys.text │ │ ├── rearrange-test.text │ │ ├── scales-key-chord-handling.text │ │ ├── session-mgrs.text │ │ ├── snapshots.text │ │ ├── styling.text │ │ ├── win-virtual-keys.text │ │ ├── windows-port-midi.text │ │ └── zoom.text │ ├── scripts/ │ │ ├── Jack │ │ ├── README │ │ ├── alsa.m4 │ │ ├── audio │ │ ├── binfuncs │ │ ├── bluejack │ │ ├── build_debug_code.bat │ │ ├── compositor │ │ ├── configure-clang │ │ ├── conk │ │ ├── debug │ │ ├── dot-xbindkeysrc │ │ ├── gdarkseq66 │ │ ├── grayscale.sh │ │ ├── gvo │ │ ├── htmldoc │ │ ├── make-checkout │ │ ├── make-qt5-links │ │ ├── mutetest │ │ ├── naming │ │ ├── notemapgen.py │ │ ├── ordercp │ │ ├── qbuild-bash │ │ ├── qbuild.sh │ │ ├── qbuildwin.sh │ │ ├── qtctrun │ │ ├── qtests │ │ ├── reconf │ │ ├── recordpa │ │ ├── release │ │ ├── seq66-nsm-proxy │ │ ├── seq66.sed │ │ ├── session │ │ ├── strap_functions │ │ ├── timid │ │ ├── vd │ │ ├── vo │ │ ├── windows/ │ │ │ └── VMS_fixes.reg │ │ └── ystart │ ├── tests/ │ │ ├── 4x4/ │ │ │ ├── README │ │ │ ├── buff.midi │ │ │ ├── darkfix.qss │ │ │ ├── qseq66-lp-mini-4x4.ctrl │ │ │ ├── qseq66.ctrl │ │ │ ├── qseq66.drums │ │ │ ├── qseq66.mutes │ │ │ ├── qseq66.playlist │ │ │ ├── qseq66.rc │ │ │ ├── qseq66.usr │ │ │ └── synthstart │ │ └── test_numbers.ods │ ├── valgrind/ │ │ ├── fontconfig.supp │ │ ├── glibc.supp │ │ ├── helgrind-test.sh │ │ ├── kde.supp │ │ └── valgrind-leaks.sh │ ├── vim-syntax/ │ │ ├── c.vim │ │ ├── cpp.vim │ │ ├── meson.vim │ │ └── syncolor.vim │ └── vim.rc ├── data/ │ ├── Makefile.am │ ├── license.text │ ├── linux/ │ │ ├── alsa_ports.rc │ │ ├── ca_ports.rc │ │ ├── jack/ │ │ │ ├── README │ │ │ ├── jack_portmaps.rc │ │ │ ├── jackctl │ │ │ ├── pulseaudio/ │ │ │ │ ├── jack-post-start.sh │ │ │ │ ├── jack-post-stop.sh │ │ │ │ ├── jack-pre-start.sh │ │ │ │ ├── jack-pre-stop.sh │ │ │ │ └── repulse │ │ │ ├── startjack │ │ │ └── startqjack │ │ ├── jack_ports.rc │ │ ├── macros-APC40-mk2.ctrl │ │ ├── macros-MMC.ctrl │ │ ├── macros-launchpad-mini.ctrl │ │ ├── macros-launchpad-pro-mk3.ctrl │ │ ├── qseq66-alt-gray.palette │ │ ├── qseq66-azerty-fr.keymap │ │ ├── qseq66-azerty.ctrl │ │ ├── qseq66-default.palette │ │ ├── qseq66-gray.palette │ │ ├── qseq66-lp-mini-8x8.ctrl │ │ ├── qseq66-lp-mini-alt.ctrl │ │ ├── qseq66-lp-mini-swapped.ctrl │ │ ├── qseq66-lp-mini.ctrl │ │ ├── qseq66-qwerty-us.keymap │ │ ├── qseq66-swapped.ctrl │ │ ├── qseq66.ctrl │ │ ├── qseq66.drums │ │ ├── qseq66.mutes │ │ ├── qseq66.palette │ │ ├── qseq66.playlist │ │ ├── qseq66.rc │ │ ├── qseq66.rc.legacy │ │ ├── qseq66.usr │ │ └── yoshimi-b4uacuse-gm.state │ ├── midi/ │ │ ├── 16-blank-patterns.midi │ │ ├── Carpet_of_the_Sun.text │ │ ├── Chameleon-HHancock-Ov.midi │ │ ├── EE-qsynth-presets.conf │ │ ├── FM/ │ │ │ ├── README │ │ │ ├── brecluse.mid │ │ │ ├── carptsun.mid │ │ │ ├── cbflitfm.mid │ │ │ ├── dasmodel.mid │ │ │ ├── grntamb.mid │ │ │ ├── hapwandr.mid │ │ │ ├── judyblue.mid │ │ │ ├── k_seq11.mid │ │ │ ├── longhair.mid │ │ │ ├── marraksh.mid │ │ │ ├── oxyg4bfm.mid │ │ │ ├── pirates.mid │ │ │ ├── pss680.mid │ │ │ ├── qufrency.mid │ │ │ ├── stdemo3.mid │ │ │ ├── viceuk.mid │ │ │ └── wallstsm.mid │ │ ├── If_You_Could_Read_My_Mind.mid │ │ ├── Kraftwerk-Europe_Endless-reconstructed.midi │ │ ├── Kraftwerk-Europe_Endless.asc │ │ ├── Kraftwerk-Europe_Endless.text │ │ ├── PSS-790/ │ │ │ ├── ancestor.mid │ │ │ ├── carptsun.mid │ │ │ ├── cbflite.mid │ │ │ └── old_love.mid │ │ ├── Peter_Gunn-reconstructed.midi │ │ ├── Peter_Gunn.text │ │ ├── README │ │ ├── b4uacufm.mid │ │ ├── b4uacuse-gm-patchless.midi │ │ ├── carptsun-4.midi │ │ ├── carptsun.midi │ │ ├── colours.midi │ │ └── metro.midi │ ├── readme.text │ ├── readme.windows │ ├── samples/ │ │ ├── GM.patches │ │ ├── GM_DD-11.drums │ │ ├── GM_PSS-790.drums │ │ ├── GM_PSS-790_Multi.ini │ │ ├── PSS-790.patches │ │ ├── ca_midi.playlist │ │ ├── dark-gradient.qss │ │ ├── flat-rounded.qss │ │ ├── green.palette │ │ ├── green.qss │ │ ├── grey-ghost.qss │ │ ├── incrypt-66.palette │ │ ├── incrypt-66.qss │ │ ├── monogreen.palette │ │ ├── monogreen.qss │ │ ├── nanomap.ctrl │ │ ├── perstfic-66.palette │ │ ├── perstfic-66.qss │ │ ├── qseq66-sample.palette │ │ ├── qseq66.qss │ │ ├── sample.playlist │ │ ├── sample.usr │ │ ├── sessions.rc │ │ └── textfix.qss │ ├── seq66cli/ │ │ ├── seq66cli.ctrl │ │ ├── seq66cli.drums │ │ ├── seq66cli.mutes │ │ ├── seq66cli.playlist │ │ ├── seq66cli.rc │ │ └── seq66cli.usr │ ├── share/ │ │ ├── applications/ │ │ │ └── seq66.desktop │ │ ├── doc/ │ │ │ ├── Mini_Play_Info.ods │ │ │ ├── akai-mini-play-mk3.ods │ │ │ ├── control_keys.ods │ │ │ ├── info/ │ │ │ │ ├── automation_keys.html │ │ │ │ ├── common_keys.html │ │ │ │ ├── mute_group_keys.html │ │ │ │ ├── pattern_hotkeys.html │ │ │ │ ├── seqroll_keys.html │ │ │ │ └── songroll_keys.html │ │ │ ├── launchpad-mini.ods │ │ │ └── tutorial/ │ │ │ ├── configuration.html │ │ │ ├── css/ │ │ │ │ ├── dark-slide.css │ │ │ │ ├── emac-slide.css │ │ │ │ ├── light-slide.css │ │ │ │ └── slide.css │ │ │ ├── faq.html │ │ │ ├── home.html │ │ │ ├── images/ │ │ │ │ └── README │ │ │ ├── index.html │ │ │ ├── introduction.html │ │ │ ├── left-tree.html │ │ │ ├── main_window.html │ │ │ ├── main_window_patterns.html │ │ │ ├── mutes_manager.html │ │ │ ├── pagenotready.html │ │ │ ├── pattern_editor.html │ │ │ ├── pattern_tools.html │ │ │ ├── playlist_manager.html │ │ │ ├── sets_manager.html │ │ │ ├── song_editor.html │ │ │ ├── tutorial_first_startup.html │ │ │ ├── tutorial_live_play.html │ │ │ ├── tutorial_main.html │ │ │ ├── tutorial_new_patterns.html │ │ │ ├── tutorial_new_song.html │ │ │ ├── tutorial_other_features.html │ │ │ └── tutorial_song_performance.html │ │ └── metainfo/ │ │ └── seq66.appdata.xml │ ├── testing/ │ │ ├── mapping-snippet.rc │ │ ├── simple-test.notemap │ │ └── sixteen-ports-snippet.rc │ ├── win/ │ │ ├── dark-theme.qss │ │ ├── qpseq66.ctrl │ │ ├── qpseq66.drums │ │ ├── qpseq66.mutes │ │ ├── qpseq66.palette │ │ ├── qpseq66.playlist │ │ ├── qpseq66.rc │ │ ├── qpseq66.usr │ │ └── win_midi.playlist │ └── wrk/ │ ├── longhair.midi │ ├── longhair.wrk │ └── oxygen4b.wrk ├── desktop/ │ └── seq66.xpm ├── distros/ │ ├── README │ ├── arch/ │ │ ├── README │ │ └── package/ │ │ ├── PKGBUILD │ │ └── PKGBUILD-alt │ ├── fedora/ │ │ ├── README │ │ └── seq66.spec │ └── nixos/ │ ├── README │ └── default.nix ├── doc/ │ ├── Makefile.am │ ├── README │ ├── dia/ │ │ ├── libseq66-headers.dia │ │ ├── rtbusses.dia │ │ └── rtjack_init.dia │ ├── dox/ │ │ ├── Makefile.am │ │ ├── doxy-common.cfg │ │ ├── libseq66/ │ │ │ ├── libseq66.cfg │ │ │ └── mainpage.dox │ │ ├── libsessions/ │ │ │ ├── libsessions.cfg │ │ │ └── mainpage.dox │ │ ├── make-helper │ │ ├── make_dox │ │ ├── optimize │ │ ├── seq_portmidi/ │ │ │ ├── mainpage.dox │ │ │ └── seq_portmidi.cfg │ │ └── seq_rtmidi/ │ │ ├── mainpage.dox │ │ └── seq_rtmidi.cfg │ └── latex/ │ ├── Makefile.am │ ├── README │ ├── images/ │ │ └── README │ └── tex/ │ ├── Makefile.am │ ├── alsa.tex │ ├── concepts.tex │ ├── configuration.tex │ ├── defaultkeys.tex │ ├── docs-structure.tex │ ├── event_editor.tex │ ├── first_start.tex │ ├── headless.tex │ ├── jack.tex │ ├── kbd_mouse.tex │ ├── kudos.tex │ ├── launchpad_mini.tex │ ├── live_grid.tex │ ├── menu.tex │ ├── meta_events.tex │ ├── midi_export.tex │ ├── midi_formats.tex │ ├── mutes.tex │ ├── palettes.tex │ ├── pattern_editor.tex │ ├── patterns_panel.tex │ ├── playlist.tex │ ├── port_mapping.tex │ ├── preferences.tex │ ├── recording.tex │ ├── references.tex │ ├── seq66-user-manual.tex │ ├── sessions.tex │ ├── setmaster.tex │ ├── song_editor.tex │ └── windows.tex ├── include/ │ ├── cli/ │ │ └── seq66-config.h │ ├── config.h.in │ └── qt/ │ ├── portmidi/ │ │ └── seq66-config.h │ └── rtmidi/ │ └── seq66-config.h ├── libseq66/ │ ├── Makefile.am │ ├── README │ ├── include/ │ │ ├── Makefile.am │ │ ├── base64_images.hpp │ │ ├── cfg/ │ │ │ ├── basesettings.hpp │ │ │ ├── cmdlineopts.hpp │ │ │ ├── comments.hpp │ │ │ ├── configfile.hpp │ │ │ ├── midicontrolfile.hpp │ │ │ ├── mutegroupsfile.hpp │ │ │ ├── notemapfile.hpp │ │ │ ├── patchesfile.hpp │ │ │ ├── playlistfile.hpp │ │ │ ├── rcfile.hpp │ │ │ ├── rcsettings.hpp │ │ │ ├── recent.hpp │ │ │ ├── scales.hpp │ │ │ ├── sessionfile.hpp │ │ │ ├── settings.hpp │ │ │ ├── userinstrument.hpp │ │ │ ├── usermidibus.hpp │ │ │ ├── usrfile.hpp │ │ │ ├── usrsettings.hpp │ │ │ └── zoomer.hpp │ │ ├── ctrl/ │ │ │ ├── automation.hpp │ │ │ ├── keycontainer.hpp │ │ │ ├── keycontrol.hpp │ │ │ ├── keymap.hpp │ │ │ ├── keystroke.hpp │ │ │ ├── midicontrol.hpp │ │ │ ├── midicontrolbase.hpp │ │ │ ├── midicontrolin.hpp │ │ │ ├── midicontrolout.hpp │ │ │ ├── midimacro.hpp │ │ │ ├── midimacros.hpp │ │ │ ├── midioperation.hpp │ │ │ ├── opcontainer.hpp │ │ │ └── opcontrol.hpp │ │ ├── midi/ │ │ │ ├── businfo.hpp │ │ │ ├── calculations.hpp │ │ │ ├── controllers.hpp │ │ │ ├── drums.hpp │ │ │ ├── editable_event.hpp │ │ │ ├── editable_events.hpp │ │ │ ├── event.hpp │ │ │ ├── eventlist.hpp │ │ │ ├── jack_assistant.hpp │ │ │ ├── mastermidibase.hpp │ │ │ ├── mastermidibus.hpp │ │ │ ├── midi_splitter.hpp │ │ │ ├── midi_vector.hpp │ │ │ ├── midi_vector_base.hpp │ │ │ ├── midibase.hpp │ │ │ ├── midibus.hpp │ │ │ ├── midibus_common.hpp │ │ │ ├── midibytes.hpp │ │ │ ├── midifile.hpp │ │ │ ├── patches.hpp │ │ │ └── wrkfile.hpp │ │ ├── os/ │ │ │ ├── daemonize.hpp │ │ │ ├── shellexecute.hpp │ │ │ └── timing.hpp │ │ ├── play/ │ │ │ ├── clockslist.hpp │ │ │ ├── inputslist.hpp │ │ │ ├── metro.hpp │ │ │ ├── mutegroup.hpp │ │ │ ├── mutegroups.hpp │ │ │ ├── notemapper.hpp │ │ │ ├── performer.hpp │ │ │ ├── playlist.hpp │ │ │ ├── portslist.hpp │ │ │ ├── screenset.hpp │ │ │ ├── seq.hpp │ │ │ ├── sequence.hpp │ │ │ ├── setmapper.hpp │ │ │ ├── setmaster.hpp │ │ │ ├── songsummary.hpp │ │ │ └── triggers.hpp │ │ ├── seq66_features.h │ │ ├── seq66_features.hpp │ │ ├── seq66_platform_macros.h │ │ ├── sessions/ │ │ │ ├── clinsmanager.hpp │ │ │ └── smanager.hpp │ │ └── util/ │ │ ├── automutex.hpp │ │ ├── basic_macros.h │ │ ├── basic_macros.hpp │ │ ├── condition.hpp │ │ ├── filefunctions.hpp │ │ ├── named_bools.hpp │ │ ├── palette.hpp │ │ ├── recmutex.hpp │ │ ├── rect.hpp │ │ ├── ring_buffer.hpp │ │ └── strfunctions.hpp │ ├── libseq66.pro │ └── src/ │ ├── Makefile.am │ ├── cfg/ │ │ ├── basesettings.cpp │ │ ├── cmdlineopts.cpp │ │ ├── comments.cpp │ │ ├── configfile.cpp │ │ ├── midicontrolfile.cpp │ │ ├── mutegroupsfile.cpp │ │ ├── notemapfile.cpp │ │ ├── patchesfile.cpp │ │ ├── playlistfile.cpp │ │ ├── rcfile.cpp │ │ ├── rcsettings.cpp │ │ ├── recent.cpp │ │ ├── scales.cpp │ │ ├── sessionfile.cpp │ │ ├── settings.cpp │ │ ├── userinstrument.cpp │ │ ├── usermidibus.cpp │ │ ├── usrfile.cpp │ │ ├── usrsettings.cpp │ │ └── zoomer.cpp │ ├── ctrl/ │ │ ├── automation.cpp │ │ ├── keycontainer.cpp │ │ ├── keycontrol.cpp │ │ ├── keymap.cpp │ │ ├── keystroke.cpp │ │ ├── midicontrol.cpp │ │ ├── midicontrolbase.cpp │ │ ├── midicontrolin.cpp │ │ ├── midicontrolout.cpp │ │ ├── midimacro.cpp │ │ ├── midimacros.cpp │ │ ├── midioperation.cpp │ │ ├── opcontainer.cpp │ │ ├── opcontrol.cpp │ │ └── winkeys.hpp │ ├── midi/ │ │ ├── businfo.cpp │ │ ├── calculations.cpp │ │ ├── controllers.cpp │ │ ├── drums.cpp │ │ ├── editable_event.cpp │ │ ├── editable_events.cpp │ │ ├── event.cpp │ │ ├── eventlist.cpp │ │ ├── jack_assistant.cpp │ │ ├── mastermidibase.cpp │ │ ├── midi_splitter.cpp │ │ ├── midi_vector.cpp │ │ ├── midi_vector_base.cpp │ │ ├── midibase.cpp │ │ ├── midibytes.cpp │ │ ├── midifile.cpp │ │ ├── patches.cpp │ │ └── wrkfile.cpp │ ├── os/ │ │ ├── daemonize.cpp │ │ ├── shellexecute.cpp │ │ └── timing.cpp │ ├── play/ │ │ ├── clockslist.cpp │ │ ├── inputslist.cpp │ │ ├── metro.cpp │ │ ├── mutegroup.cpp │ │ ├── mutegroups.cpp │ │ ├── notemapper.cpp │ │ ├── performer.cpp │ │ ├── playlist.cpp │ │ ├── portslist.cpp │ │ ├── screenset.cpp │ │ ├── seq.cpp │ │ ├── sequence.cpp │ │ ├── setmapper.cpp │ │ ├── setmaster.cpp │ │ ├── songsummary.cpp │ │ └── triggers.cpp │ ├── seq66_features.cpp │ ├── sessions/ │ │ ├── clinsmanager.cpp │ │ └── smanager.cpp │ └── util/ │ ├── automutex.cpp │ ├── basic_macros.cpp │ ├── condition.cpp │ ├── filefunctions.cpp │ ├── named_bools.cpp │ ├── palette.cpp │ ├── recmutex.cpp │ ├── rect.cpp │ ├── ring_buffer.cpp │ └── strfunctions.cpp ├── libsessions/ │ ├── Makefile.am │ ├── include/ │ │ ├── Makefile.am │ │ └── nsm/ │ │ ├── nsmbase.hpp │ │ ├── nsmclient.hpp │ │ ├── nsmmessagesex.hpp │ │ └── nsmserver.hpp │ ├── libsessions.pro │ └── src/ │ ├── Makefile.am │ └── nsm/ │ ├── nsmbase.cpp │ ├── nsmclient.cpp │ ├── nsmmessagesex.cpp │ └── nsmserver.cpp ├── licenses/ │ ├── LICENSE.FDL │ ├── LICENSE.GPL │ └── LICENSE.LGPL ├── m4/ │ ├── Makefile.am │ ├── alsa.m4 │ ├── ax_cxx_compile_stdcxx.m4 │ ├── ax_cxx_compile_stdcxx_11.m4 │ ├── ax_have_qt.m4 │ ├── ax_have_qt_clang.m4 │ ├── ax_have_qt_ex.m4 │ ├── ax_have_qt_min.m4 │ ├── ax_prefix_config_h.m4 │ ├── ax_prog_flex.m4 │ ├── ax_pthread.m4 │ ├── ax_require_defined.m4 │ ├── gcc-version.m4 │ ├── inttypes.m4 │ ├── isc-posix.m4 │ ├── mm-common.m4 │ ├── mm-warnings.m4 │ ├── pkg.m4 │ ├── seq64_mingw_dll.m4 │ ├── threadlib.m4 │ ├── win32msc.m4 │ ├── xpc_debug.m4 │ ├── xpc_doxygen.m4 │ ├── xpc_errorlog.m4 │ ├── xpc_mingw.m4 │ ├── xpc_nullptr.m4 │ └── xpc_thisptr.m4 ├── man/ │ ├── Makefile.am │ ├── seq66.1 │ ├── seq66cli.1 │ └── sequencer66.1 ├── nsis/ │ ├── README │ ├── Seq66Constants.nsh │ ├── Seq66Setup.nsi │ ├── build_release_package.bat │ └── x64.nsh ├── pack ├── packages ├── resources/ │ ├── icons/ │ │ └── route66.xpm │ ├── pixmaps/ │ │ ├── Makefile.am │ │ ├── bus.xpm │ │ ├── chord.xpm │ │ ├── chord3-inv.xpm │ │ ├── chord3.xpm │ │ ├── collapse.xpm │ │ ├── copy.xpm │ │ ├── del.xpm │ │ ├── down.xpm │ │ ├── drum.xpm │ │ ├── exp_rec_on.xpm │ │ ├── expand.xpm │ │ ├── expandgrid.xpm │ │ ├── filter_off.xpm │ │ ├── filter_on.xpm │ │ ├── finger.xpm │ │ ├── follow.xpm │ │ ├── fruity.xpm │ │ ├── hex_off.xpm │ │ ├── hex_on.xpm │ │ ├── hide.xpm │ │ ├── ins.xpm │ │ ├── jack.xpm │ │ ├── jack_black.xpm │ │ ├── key.xpm │ │ ├── learn.xpm │ │ ├── learn2.xpm │ │ ├── length.xpm │ │ ├── length_red.xpm │ │ ├── length_short.xpm │ │ ├── length_short_inv.xpm │ │ ├── live_mode.xpm │ │ ├── logo.xpm │ │ ├── loop.xpm │ │ ├── menu.xpm │ │ ├── menu_empty.xpm │ │ ├── menu_empty_inv.xpm │ │ ├── menu_full.xpm │ │ ├── menu_full_inv.xpm │ │ ├── metro.xpm │ │ ├── metro_on.xpm │ │ ├── midi.xpm │ │ ├── muting.xpm │ │ ├── n_rec_on.xpm │ │ ├── note_length.xpm │ │ ├── note_length_inv.xpm │ │ ├── numbers_off.xpm │ │ ├── numbers_on.xpm │ │ ├── panic.xpm │ │ ├── panic2.xpm │ │ ├── pause.xpm │ │ ├── perfedit.xpm │ │ ├── play.xpm │ │ ├── play2.xpm │ │ ├── play_on.xpm │ │ ├── q_rec.xpm │ │ ├── q_rec_on.xpm │ │ ├── quantize.xpm │ │ ├── quantize_inv.xpm │ │ ├── rec.xpm │ │ ├── rec_ex_buss.xpm │ │ ├── rec_ex_channel.xpm │ │ ├── rec_ex_normal.xpm │ │ ├── rec_on.xpm │ │ ├── redo.xpm │ │ ├── right.xpm │ │ ├── route66.xpm │ │ ├── route66rwb-32x32.xpm │ │ ├── route66rwb-64x64.xpm │ │ ├── scale.xpm │ │ ├── seq-editor.xpm │ │ ├── seq.xpm │ │ ├── seq66.xpm │ │ ├── seq66_32.xpm │ │ ├── sequences.xpm │ │ ├── show.xpm │ │ ├── show_bars_off.xpm │ │ ├── show_bars_on.xpm │ │ ├── snap.xpm │ │ ├── song-editor.xpm │ │ ├── song-snap.xpm │ │ ├── song_mode.xpm │ │ ├── song_rec.xpm │ │ ├── song_rec_no_snap.xpm │ │ ├── song_rec_off.xpm │ │ ├── song_rec_on.xpm │ │ ├── song_record.xpm │ │ ├── stop.xpm │ │ ├── t_rec_on.xpm │ │ ├── thru.xpm │ │ ├── thru_on.xpm │ │ ├── tools.xpm │ │ ├── transport_follow.xpm │ │ ├── transpose.xpm │ │ ├── tux.xpm │ │ ├── undo.xpm │ │ ├── up.xpm │ │ ├── up_inv.xpm │ │ ├── zoom.xpm │ │ ├── zoom_in.xpm │ │ └── zoom_out.xpm │ └── seq66_win.rc ├── seq66.pro ├── seq_portmidi/ │ ├── Makefile.am │ ├── README │ ├── include/ │ │ ├── Makefile.am │ │ ├── mastermidibus_pm.hpp │ │ ├── midibus_pm.hpp │ │ ├── pmerrmm.h │ │ ├── pminternal.h │ │ ├── pmlinux.h │ │ ├── pmlinuxalsa.h │ │ ├── pmmac.h │ │ ├── pmutil.h │ │ ├── pmwinmm.h │ │ ├── portmidi.h │ │ └── porttime.h │ ├── seq_portmidi.pro │ └── src/ │ ├── Makefile.am │ ├── mastermidibus.cpp │ ├── midibus.cpp │ ├── pmerrmm.c │ ├── pmlinux.c │ ├── pmlinuxalsa.c │ ├── pmmac.c │ ├── pmmacosxcm.c │ ├── pmutil.c │ ├── pmwin.c │ ├── pmwinmm.c │ ├── portmidi.c │ ├── porttime.c │ ├── ptlinux.c │ ├── ptmacosx_cf.c │ ├── ptmacosx_mach.c │ └── ptwinmm.c ├── seq_qt5/ │ ├── Makefile.am │ ├── README │ ├── forms/ │ │ ├── Makefile.am │ │ ├── qlfoframe.ui │ │ ├── qliveframeex.ui │ │ ├── qmutemaster.ui │ │ ├── qpatternfix.ui │ │ ├── qperfeditex.ui │ │ ├── qperfeditframe64.ui │ │ ├── qplaylistframe.ui │ │ ├── qsabout.ui │ │ ├── qsappinfo.ui │ │ ├── qsbuildinfo.ui │ │ ├── qseditoptions.ui │ │ ├── qseqeditex.ui │ │ ├── qseqeditframe64.ui │ │ ├── qseqeventframe.ui │ │ ├── qsessionframe.ui │ │ ├── qsetmaster.ui │ │ ├── qslivegrid.ui │ │ ├── qslogview.ui │ │ └── qsmainwnd.ui │ ├── include/ │ │ ├── Makefile.am │ │ ├── gui_palette_qt5.hpp │ │ ├── palettefile.hpp │ │ ├── qbase.hpp │ │ ├── qclocklayout.hpp │ │ ├── qeditbase.hpp │ │ ├── qinputcheckbox.hpp │ │ ├── qlfoframe.hpp │ │ ├── qliveframeex.hpp │ │ ├── qloopbutton.hpp │ │ ├── qmutemaster.hpp │ │ ├── qpatternfix.hpp │ │ ├── qperfbase.hpp │ │ ├── qperfeditex.hpp │ │ ├── qperfeditframe64.hpp │ │ ├── qperfnames.hpp │ │ ├── qperfroll.hpp │ │ ├── qperftime.hpp │ │ ├── qplaylistframe.hpp │ │ ├── qportwidget.hpp │ │ ├── qsabout.hpp │ │ ├── qsappinfo.hpp │ │ ├── qsbuildinfo.hpp │ │ ├── qscrollmaster.h │ │ ├── qscrollslave.h │ │ ├── qseditoptions.hpp │ │ ├── qseqbase.hpp │ │ ├── qseqdata.hpp │ │ ├── qseqeditex.hpp │ │ ├── qseqeditframe64.hpp │ │ ├── qseqeventframe.hpp │ │ ├── qseqframe.hpp │ │ ├── qseqkeys.hpp │ │ ├── qseqroll.hpp │ │ ├── qseqtime.hpp │ │ ├── qsessionframe.hpp │ │ ├── qsetmaster.hpp │ │ ├── qseventslots.hpp │ │ ├── qslivebase.hpp │ │ ├── qslivegrid.hpp │ │ ├── qslogview.hpp │ │ ├── qslotbutton.hpp │ │ ├── qsmaintime.hpp │ │ ├── qsmainwnd.hpp │ │ ├── qstriggereditor.hpp │ │ ├── qt5_helper.h │ │ ├── qt5_helpers.hpp │ │ └── qt5nsmanager.hpp │ ├── seq_qt5.pro │ └── src/ │ ├── Makefile.am │ ├── gui_palette_qt5.cpp │ ├── palettefile.cpp │ ├── qbase.cpp │ ├── qclocklayout.cpp │ ├── qeditbase.cpp │ ├── qinputcheckbox.cpp │ ├── qlfoframe.cpp │ ├── qliveframeex.cpp │ ├── qloopbutton.cpp │ ├── qmutemaster.cpp │ ├── qpatternfix.cpp │ ├── qperfbase.cpp │ ├── qperfeditex.cpp │ ├── qperfeditframe64.cpp │ ├── qperfnames.cpp │ ├── qperfroll.cpp │ ├── qperftime.cpp │ ├── qplaylistframe.cpp │ ├── qportwidget.cpp │ ├── qsabout.cpp │ ├── qsappinfo.cpp │ ├── qsbuildinfo.cpp │ ├── qscrollmaster.cpp │ ├── qscrollslave.cpp │ ├── qseditoptions.cpp │ ├── qseqbase.cpp │ ├── qseqdata.cpp │ ├── qseqeditex.cpp │ ├── qseqeditframe64.cpp │ ├── qseqeventframe.cpp │ ├── qseqframe.cpp │ ├── qseqkeys.cpp │ ├── qseqroll.cpp │ ├── qseqtime.cpp │ ├── qsessionframe.cpp │ ├── qsetmaster.cpp │ ├── qseventslots.cpp │ ├── qslivebase.cpp │ ├── qslivegrid.cpp │ ├── qslogview.cpp │ ├── qslotbutton.cpp │ ├── qsmaintime.cpp │ ├── qsmainwnd.cpp │ ├── qstriggereditor.cpp │ ├── qt5_helpers.cpp │ └── qt5nsmanager.cpp └── seq_rtmidi/ ├── Makefile.am ├── include/ │ ├── Makefile.am │ ├── mastermidibus_rm.hpp │ ├── midi_alsa.hpp │ ├── midi_alsa_info.hpp │ ├── midi_api.hpp │ ├── midi_info.hpp │ ├── midi_jack.hpp │ ├── midi_jack_data.hpp │ ├── midi_jack_info.hpp │ ├── midi_probe.hpp │ ├── midibus_rm.hpp │ ├── rterror.hpp │ ├── rtmidi.hpp │ ├── rtmidi_info.hpp │ ├── rtmidi_types.hpp │ └── seq66_rtmidi_features.h ├── seq_rtmidi.pro └── src/ ├── Makefile.am ├── mastermidibus.cpp ├── midi_alsa.cpp ├── midi_alsa_info.cpp ├── midi_api.cpp ├── midi_info.cpp ├── midi_jack.cpp ├── midi_jack_data.cpp ├── midi_jack_info.cpp ├── midi_probe.cpp ├── midibus.cpp ├── rtmidi.cpp ├── rtmidi_info.cpp └── rtmidi_types.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # .gitignore for Seq66 # # Chris Ahlstrom # Updates: 2015-09-12 to 2021-12-12 ~$* .* *~ *.a \# ABOUT-NLS aclocal.m4 *.aps am--include-marker /autom4te.cache autom4te.cache/ *.aux aux-files Backup*/ [Bb]in *.bak *.bbl *.blg blib/ bootstrap.stamp _build/ .build/ Build Build.bat .builds build-stamp configure-stamp *.Cache ClientBin /compile config config.h config.status core .coverage cover_db/ *.dbmdl [Dd]ebug/ debian/debhelper-build-stamp debian/files debian/*.log debian/ .deps/ Desktop.ini develop-eggs .directory dist DocProject/buildhelp/ DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/html DocProject/Help/Html2 DocProject/Help/*.HxC DocProject/Help/*.HxT dox-stamp doxygen_sqlite3.db *.dvi [Ee]xpress *.egg *.egg-info eggs *.fdb_latexmk *.fls *.gcda *.gcno *.gcov Generated_Code # added for RIA/Silverlight projects !.gitignore *.glg *.glo *.gls html/ i18n/ *_i.c *.idb *.idx *.ilg *.ilk include/seq66-config.h inc/ *.ind .installed.cfg ipch/ *.ist *.la *.lai .last_cover_stats .libs/ libtool *.lo *.lof *.lot m4/libtool.m4 m4/lt~obsolete.m4 m4/ltoptions.m4 m4/ltsugar.m4 m4/ltversion.m4 *.maf Makefile Makefile.in Makefile.in.in Makefile.old MANIFEST.bak *.meta META.yml moc_*.cpp *.mo *.moc.cpp .mr.developer.cfg *.mtc *.mtc0 MYMETA.yml *.nav *.ncb *.nlo nytprof.out *.o *.obj [Oo]bj *.opensdf *.out parts *_p.c *.pch *.pdb *.pdfsync *.pgc *.pgd pip-log.txt pm_to_blib *.ps *.psess publish *.py[co] *.qrc.cpp _ReSharper* [Rr]elease/ *.rsp safety/ # hmmm save/ *.sbr scratch/ *.sdf sdist Seq66cli/seq66cli Seq66qt5/qseq66 Seq66rtmidi/seq66 Session.vim *.sln.docstates *.slo *.snm *.so sql stamp-h1 *.status stylecop.* *.suo .svn/ .*.sw[a-z] *.synctex.gz *.t tags TAGS TestResults tex-stamp Thumbs.db *.tlb *.tlh *.tli *.tmp tmp/ # hmmm *.toc .tox *.trs ui_*.h *.un~ UpgradeLog*.XML _UpgradeReport_Files/ *.ui.h *.user var vgcore *.vrb *.vsp *.vspscc # Ignore programmer's reference manuals doc/dox/libseq66/latex/ doc/dox/libseq66/seq66_lib_libseq66.pdf doc/dox/libsessions/latex/ doc/dox/libsessions/seq66_lib_libsessions.pdf doc/dox/seq_portmidi/latex/ doc/dox/seq_portmidi/seq66_lib_seq_portmidi.pdf doc/dox/seq_rtmidi/latex/ doc/dox/seq_rtmidi/seq66_lib_seq_rtmidi.pdf ================================================ FILE: ChangeLog ================================================ 2026-05-01 Chris * NEWS, RELNOTES, TODO, VERSION, aux-files/ltmain.sh, configure, configure.ac, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/mastermidibus_rm.hpp, seq_rtmidi/include/midibus_rm.hpp: Worked on issues #144 and #144, mitigated segfaults. 2026-04-22 Chris * INSTALL, Install.seq66.on.Ubuntu.md => INSTALL.seq66.on.Ubuntu.md, NEWS, README.md, TODO, INSTALL4Ubuntu.md => contrib/notes/INSTALL4Ubuntu.md, contrib/notes/bvi.text, doc/latex/tex/midi_formats.tex, seq_qt5/forms/qlfoframe.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp: Changed INSTALL for Ub Studio, fixed 2nd restart segfault, doc updates. 2026-04-21 Chris * NEWS, TODO, doc/latex/tex/palettes.tex, doc/latex/tex/pattern_editor.tex, resources/pixmaps/hex_off.xpm, resources/pixmaps/hex_on.xpm, resources/pixmaps/numbers_off.xpm, resources/pixmaps/numbers_on.xpm, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp: Added numbers button in pattern editor to show/hide some data pane numbers. * NEWS, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qliveframeex.ui, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qpatternfix.ui, seq_qt5/forms/qperfeditex.ui, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qsabout.ui, seq_qt5/forms/qsappinfo.ui, seq_qt5/forms/qsbuildinfo.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditex.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qslogview.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp: Added hex button in pattern editor, touched all ui files to get new Qt6 namespaces. 2026-04-20 Chris * NEWS, TODO, contrib/midi/README, contrib/notes/RPN.text, data/win/qpseq66.mutes, libseq66/include/midi/controllers.hpp, libseq66/src/midi/controllers.cpp, seq_qt5/include/palettefile.hpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp: A couple of NRPN MIDI files added, hex display of controller event numbers. 2026-04-18 Chris * NEWS, TODO, aux-files/ltmain.sh, configure, data/linux/qseq66.rc, doc/latex/tex/pattern_editor.tex, include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/drums.hpp, libseq66/include/seq66_features.h, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/drums.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Mitigated issue #144, fixed potential remap crash, display of drum note names, auto-save-rc no longer defaults to true. 2026-04-16 Chris * NEWS, README.md, RELNOTES, TODO, VERSION, configure.ac, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Version increment to wip 0.99.24. 2026-04-15 Chris * : Merged qt6-->master-->wip. * NEWS, RELNOTES, Seq66qt5/seq66qt5.cpp, TODO, contrib/scripts/gvo, contrib/scripts/vd, contrib/scripts/vo, data/readme.text, data/readme.windows, data/samples/incrypt-66.palette, data/win/qpseq66.palette, doc/latex/tex/configuration.tex, doc/latex/tex/first_start.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/palettes.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/src/seq66_features.cpp: Documentation, new vim scripts, path to executable shown. 2026-04-14 Chris Ahlstrom * NEWS, README.md, RELNOTES, TODO, VERSION, configure, configure.ac, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/event_editor.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Updating to version 0.99.22 in some files. 2026-03-14 Chris * Install.seq66.on.Ubuntu.md, seq_qt5/src/qperfeditframe64.cpp: Update Ubuntu Studio instructions and fixed code to use QPalette::Window. * seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp: Event editor now shows controllers from 'usr' and programs from 'patches' file. * Install.seq66.on.Ubuntu.md, NEWS, aux-files/ltmain.sh, configure, LICENSE.FDL => licenses/LICENSE.FDL, LICENSE.GPL => licenses/LICENSE.GPL, LICENSE.LGPL => licenses/LICENSE.LGPL, qt6-make.log, seq_qt5/forms/qseqeventframe.ui, seq_qt5/src/qseqeventframe.cpp: Moved license files, event editor displays controllers as specified in the usr file. 2026-03-13 Chris * NEWS, TODO, aux-files/ltmain.sh, configure, {seq_qt5/src => contrib/code}/qchannelpopup.cpp, {seq_qt5/include => contrib/code}/qchannelpopup.hpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qt5_helpers.cpp: Moved qchannelpopup to contrib, added populate_midich_combo(), used it in event and pattern editors. 2026-03-12 Chris * : commit aeab54af9c83b7a582033c9e0f848f7759a8766d Author: Chris Date: Thu Mar 12 15:19:54 2026 -0400 2026-02-16 Chris Ahlstrom * aux-files/ltmain.sh, configure: Updated ltmain.sh and configure. * aux-files/ltmain.sh, configure, data/samples/sample.usr, libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/midi/drums.hpp, libseq66/include/midi/patches.hpp, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/midi/controllers.cpp, libseq66/src/midi/drums.cpp, libseq66/src/midi/patches.cpp, seq_qt5/src/qperfroll.cpp: Added drums module and some fixes to controllers and patches. 2026-01-23 Chris * seq_qt5/include/qt5_helper.h, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qscrollslave.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5_helpers.cpp: First fixes of qt6 deprecations. 2026-01-22 Chris * seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qt5_helpers.cpp: Interim qt6 check-in. 2026-01-22 Chris * qt6-make.log: Added qt6-make log to analyze for fixes. 2025-10-26 ahlstrom * ChangeLog: Release Notes for Seq66 v. 0.99.22 2025-10-26 "Itsa Been Awhile" This release contains fixes and updates for issue #141 as well as a number of other issues discovered. Some new features to support creating harmony more easily added. - Issue #141, mostly implemented. See "Pattern editor" below. - Fixed: a segfault at startup on some builds on Arch Linux. - Main window and grid. - When dragging the mouse, the slot under it is drawn "flat". - Pressing the Menu key on a selected slot shows the popup menu. - Fixed: loading a file with a different PPQN set "modify". - Fixed: dragging a pattern set "modify" before the drag was completed. Could not move another pattern into the empty slot. - Pattern editor. Extensive updates. - Showing note bars for scales and chords: - Added: a new brush called "chord" to the palette file. - If a chord is set, notes _not_ in the chord are grayed. - Added: buttons to toggle the scale/chord bars and filtering of off-scale/off-chord painted notes. - Painting notes: - Changed to down-snap to make note insertion nicer. - The selected key, scale, and chord determine the actual chord represented on the pattern grid. - Fixed the snap-interval setting for drawing notes. - The selected chord for a pattern, if any, is stored as a new track-specific "c\_musicchord" SeqSpec. - Removed display of time signatures in the data pane. - Fixed: background sequence not loaded into the piano roll. - Added: an option to select notes in a range of pitches. Either note numbers or note names can be used. - midifile. - Added: storage for the first BPM value, needed by performer. - Fixed: Tempo event in track 0 does not override c\_bpmtag SeqSpec. - performer. - Fixed: File PPQN was set when reading the file, but was not informing the master bus of a PPQN change (in ALSA). - Fixed: the slow playback of high-PPQN files. - JACK. - Fixed detect\_jack() and added a missing call to jack\_free(). See NEWS for more details. 2025-10-26 ahlstrom * ChangeLog, NEWS, README.md, RELNOTES, VERSION, configure.ac, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/configuration.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Script and manual updates for next release. 2025-10-25 ahlstrom * NEWS, aux-files/ltmain.sh, configure, data/linux/qseq66-alt-gray.palette, data/linux/qseq66-default.palette, data/linux/qseq66-gray.palette, data/linux/qseq66.palette, data/samples/green.palette, data/samples/incrypt-66.palette, data/samples/monogreen.palette, data/samples/perstfic-66.palette, data/samples/qseq66-sample.palette, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/seq66_features.cpp: Updated palettes, fixed odd Arch startup seqfault. 2025-10-24 Chris * NEWS, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/seq66_features.h: Added git release checklist, reenabled bolding the main note of a key. * NEWS, README.md, TODO, aux-files/ltmain.sh, configure, configure.ac, contrib/vim-syntax/c.vim, doc/latex/tex/pattern_editor.tex, libseq66/include/cfg/scales.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Finish pitch-range selection, fixed note painting. 2025-10-21 Chris * NEWS, TODO, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp: Tweaked sequence::partial_assign(), fixed pattern drag-n-drop issues. 2025-10-17 Chris * NEWS, TODO, contrib/midi/README, libseq66/src/play/sequence.cpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed a few bugs found in the pattern editor. * NEWS, TODO, doc/latex/tex/pattern_editor.tex, libseq66/src/midi/midi_vector_base.cpp, seq_qt5/src/qseqeditframe64.cpp: Fixed the saving of the selected chord of a pattern. 2025-10-16 Chris * NEWS, TODO, libseq66/include/cfg/scales.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/songsummary.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Added c_musicchord seqspec, needs to be finished. * aux-files/ltmain.sh, configure, doc/latex/tex/pattern_editor.tex, resources/pixmaps/Makefile.in, resources/pixmaps/show_bars_off.xpm, resources/pixmaps/show_bars_on.xpm: Added documentation and updated build scripts. 2025-10-15 Chris * resources/pixmaps/Makefile.am, resources/pixmaps/filter_off.xpm, resources/pixmaps/filter_on.xpm, resources/pixmaps/show_bars_off.xpm, resources/pixmaps/show_bars_on.xpm, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Toggling scale/chord bars works, toggling scale/chord filtering works, more testing needed. 2025-10-14 Chris * NEWS, TODO, contrib/notes/scales-key-chord-handling.text, libseq66/include/cfg/scales.hpp, libseq66/include/seq66_features.h, libseq66/src/cfg/scales.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp: Added drawing of the selected chord to the grid. 2025-10-12 Chris * NEWS, TODO, contrib/notes/RPN.text, contrib/notes/event-from-bytes.text, contrib/notes/midi-messages.text, contrib/notes/program-banks.text, contrib/notes/scales-key-chord-handling.text, contrib/notes/zoom.text: Adding notes about scales and chords and more. 2025-09-27 Chris * NEWS, TODO, data/linux/jack/jackctl, libseq66/include/ctrl/keymap.hpp, libseq66/include/ctrl/keystroke.hpp, libseq66/include/midi/midifile.hpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/midi/midifile.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Popup menu character and some clean-up. 2025-09-15 Chris * NEWS, TODO, contrib/midi/README, libseq66/src/midi/calculations.cpp, libseq66/src/play/performer.cpp: Tentative fix of high-PPQN files playing slowly. 2025-08-20 Chris * TODO, libseq66/include/midi/midibus_common.hpp, libseq66/include/midi/midifile.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/portslist.cpp, seq_portmidi/src/midibus.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midibus.cpp: Minor refactoring, e_clock value changed. 2025-07-29 ahlstrom * configure, include/config.h.in, libseq66/src/midi/calculations.cpp: More version bump, fixed double-declaration in calculations module. 2025-07-28 Chris * ChangeLog, NEWS, README.md, VERSION, configure.ac, data/license.text, data/readme.text, data/readme.windows, include/cli/seq66-config.h, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version bump for next release 0.99.22. * NEWS, README.md, configure.ac: Release Notes for Seq66 v. 0.99.21 2025-07-28 "Karl Kleanup" This release contains fixes and updates for issues #68, #137, #138, * seq_qt5/src/qseditoptions.cpp: New manual, tweak fix to showing MIDI control/display status in Preferences. 2025-07-27 Chris * NEWS, RELNOTES, TODO, doc/latex/tex/alsa.tex, doc/latex/tex/configuration.tex, doc/latex/tex/preferences.tex, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Disabled the use of ALSA Midi Through ports for both control and display at the same time. 2025-07-26 ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, TODO, aux-files/compile, aux-files/depcomp, aux-files/missing, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, doc/latex/tex/alsa.tex, doc/latex/tex/configuration.tex, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/play/performer.hpp, libseq66/src/Makefile.in, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/play/performer.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/Makefile.in, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsessionframe.cpp, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: More issues found, MIDI Through feedback loop when set for control and display. 2025-07-25 Chris * TODO, doc/latex/tex/configuration.tex, doc/latex/tex/first_start.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/preferences.tex, doc/latex/tex/seq66-user-manual.tex, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp: Oops, updates to the manual and the insert-macro tool. * seq_qt5/forms/qlfoframe.ui: Fixed duplicate identifier in qlfoframe ui. * INSTALL, NEWS, README.md, RELNOTES, TODO, configure.ac, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/pattern_editor.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/seq66_features.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, nsis/winddeploybruteforce.bat, seq_qt5/src/qseqdata.cpp: Readying for testing 0.99.21 in Windows. 2025-07-24 Chris * NEWS, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/calculations.hpp, libseq66/include/midi/event.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/src/qlfoframe.cpp: Added a DC-only waveform to the LFO, tweaked pitch-bend fixes. 2025-07-23 Chris * NEWS, TODO, libseq66/include/cfg/zoomer.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/midi/midibytes.hpp, libseq66/src/cfg/zoomer.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qperfbase.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: LFO pitchbend generation, seq/perf zoom, wave_func(), phase fixed. 2025-07-21 Chris * NEWS, TODO, doc/latex/tex/live_grid.tex, include/qt/rtmidi/seq66-config.h, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/include/qeditbase.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Fixed snap usage when painting notes, added 480 PPQN, restored 32 PPQN. 2025-07-19 Chris * NEWS, TODO, libseq66/include/cfg/usrsettings.hpp, libseq66/include/cfg/zoomer.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/cfg/zoomer.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qbase.hpp, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qplaylistframe.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed PPQN handling and zoom reset. 2025-07-17 ahlstrom * NEWS, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/preferences.tex, libseq66/include/cfg/settings.hpp, libseq66/include/cfg/zoomer.hpp, libseq66/include/midi/calculations.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/cfg/zoomer.cpp, libseq66/src/midi/calculations.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qseqdata.cpp: Changed the low PPQN to 24, documented. 2025-07-15 ahlstrom * NEWS, TODO, doc/latex/tex/live_grid.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/cfg/zoomer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/zoomer.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqroll.cpp: Working on the appearance/handling of other PPQNs. 2025-07-14 Chris * NEWS, TODO, libseq66/include/midi/event.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Fixed note-drawing at non-standard PPQN, added lots of ppqn test files, removed the old ones. 2025-07-12 Chris * NEWS, data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/configuration.tex, doc/latex/tex/pattern_editor.tex, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midimacro.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/ctrl/midimacro.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/event.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp: Finished upgrading the handling of macros in sending or insertion. 2025-07-09 Chris * NEWS, TODO, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midimacro.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/midi/wrkfile.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/ctrl/midimacro.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midibytes.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/sequence.cpp, seq_portmidi/src/mastermidibus.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsessionframe.cpp: Adding background for macro-insertion in patterns, converted midistring usage to midibytes. 2025-07-07 Chris * doc/latex/tex/pattern_editor.tex, libseq66/include/midi/calculations.hpp, libseq66/include/util/rect.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqroll.cpp: Fixing the implementation of ghost notes in the seqroll when moving or pasting notes, in progress. 2025-07-05 Chris * TODO, doc/latex/tex/concepts.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/recording.tex, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqtime.cpp: Can set left tick as the starting point for auto-step recording, works best using mouse. 2025-07-03 ahlstrom * NEWS, README.md, TODO, configure, configure.ac, contrib/notes/NEWS-template, contrib/notes/rearrange-test.text, doc/latex/tex/patterns_panel.tex, doc/latex/tex/recording.tex, libseq66/include/midi/eventlist.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp: Require C++17, fixes to event sorting for issue #138. 2025-07-02 ahlstrom * NEWS, TODO, libseq66/src/midi/controllers.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/play/sequence.cpp: Enabled recording non-note events while not playing, needs more testing. 2025-07-01 ahlstrom * NEWS, doc/latex/tex/pattern_editor.tex, doc/latex/tex/seq66-user-manual.tex: Updating the user manual for recent features/fixes. 2025-06-30 Chris * NEWS, TODO, libseq66/include/midi/controllers.hpp, libseq66/include/midi/midibytes.hpp, libseq66/src/midi/controllers.cpp, libseq66/src/midi/eventlist.cpp, seq_qt5/include/qseqdata.hpp, seq_qt5/src/qseqdata.cpp: Issue #140 basically fixed. 2025-06-29 ahlstrom * TODO, libseq66/src/play/sequence.cpp, seq_portmidi/include/mastermidibus_pm.hpp, seq_portmidi/include/midibus_pm.hpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qclocklayout.hpp, seq_qt5/include/qinputcheckbox.hpp, seq_qt5/include/qperfeditex.hpp, seq_qt5/include/qportwidget.hpp, seq_qt5/include/qseqdata.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_rtmidi/include/mastermidibus_rm.hpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/midi_probe.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midi_probe.cpp, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp, seq_rtmidi/src/rtmidi_types.cpp: Work on issue #140 in progess, doxygen note removed. 2025-06-28 Chris * NEWS, TODO, libseq66/include/midi/calculations.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qseqdata.cpp: Fixes made to pitchbend calculation and display. 2025-06-27 Chris * NEWS, TODO, libseq66/include/cfg/usrsettings.hpp, libseq66/include/cfg/zoomer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/include/qlfoframe.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qseqeditframe64.cpp: Upgrades for issue #139 LFO process, in progress. 2025-06-26 Chris * NEWS, README.md, TODO, libseq66/include/midi/calculations.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Many fixes to time-signature handling. 2025-06-20 Chris * NEWS, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, libseq66/include/midi/calculations.hpp, libseq66/include/midi/midi_splitter.hpp, libseq66/include/midi/midibytes.hpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp: Improved SMF 0 import and fixed adding time-sig events from a MIDI file. 2025-06-19 Chris * NEWS, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/first_start.tex, doc/latex/tex/jack.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/port_mapping.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qseditoptions.cpp: Work on issue #137 time-sig handling in progress. 2025-06-16 Chris * TODO, libseq66/include/base64_images.hpp, libseq66/include/cfg/basesettings.hpp, libseq66/include/cfg/comments.hpp, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/cfg/mutegroupsfile.hpp, libseq66/include/cfg/notemapfile.hpp, libseq66/include/cfg/patchesfile.hpp, libseq66/include/cfg/playlistfile.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/recent.hpp, libseq66/include/cfg/scales.hpp, libseq66/include/cfg/sessionfile.hpp, libseq66/include/cfg/settings.hpp, libseq66/include/cfg/userinstrument.hpp, libseq66/include/cfg/usermidibus.hpp, libseq66/include/cfg/usrfile.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/keycontainer.hpp, libseq66/include/ctrl/keycontrol.hpp, libseq66/include/ctrl/keymap.hpp, libseq66/include/ctrl/keystroke.hpp, libseq66/include/ctrl/midicontrol.hpp, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midimacro.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/include/ctrl/midioperation.hpp, libseq66/include/ctrl/opcontainer.hpp, libseq66/include/ctrl/opcontrol.hpp, libseq66/include/midi/businfo.hpp, libseq66/include/midi/controllers.hpp, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/editable_events.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midi_splitter.hpp, libseq66/include/midi/midi_vector.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/midi/midibus_common.hpp, libseq66/include/midi/wrkfile.hpp, libseq66/include/os/daemonize.hpp, libseq66/include/os/shellexecute.hpp, libseq66/include/os/timing.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/metro.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/include/play/portslist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/songsummary.hpp, libseq66/include/play/triggers.hpp, libseq66/include/sessions/smanager.hpp, libseq66/include/util/automutex.hpp, libseq66/include/util/basic_macros.hpp, libseq66/include/util/condition.hpp, libseq66/include/util/filefunctions.hpp, libseq66/include/util/named_bools.hpp, libseq66/include/util/palette.hpp, libseq66/include/util/recmutex.hpp, libseq66/include/util/rect.hpp, libseq66/src/cfg/basesettings.cpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/comments.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/patchesfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/recent.cpp, libseq66/src/cfg/scales.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/userinstrument.cpp, libseq66/src/cfg/usermidibus.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/cfg/zoomer.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/keycontrol.cpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/ctrl/keystroke.cpp, libseq66/src/ctrl/midicontrol.cpp, libseq66/src/ctrl/midicontrolbase.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/ctrl/midimacro.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/ctrl/midioperation.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/controllers.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midi_vector.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midibytes.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/os/shellexecute.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/play/songsummary.cpp, libseq66/src/play/triggers.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/automutex.cpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/condition.cpp, libseq66/src/util/filefunctions.cpp, libseq66/src/util/named_bools.cpp, libseq66/src/util/palette.cpp, libseq66/src/util/recmutex.cpp, libseq66/src/util/rect.cpp, libseq66/src/util/strfunctions.cpp, libsessions/include/nsm/nsmbase.hpp, libsessions/include/nsm/nsmclient.hpp, libsessions/include/nsm/nsmmessagesex.hpp, libsessions/include/nsm/nsmserver.hpp, libsessions/src/nsm/nsmbase.cpp, libsessions/src/nsm/nsmclient.cpp, libsessions/src/nsm/nsmmessagesex.cpp, libsessions/src/nsm/nsmserver.cpp, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/midibus.cpp, seq_qt5/include/palettefile.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qsabout.hpp, seq_qt5/include/qseqbase.hpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qseqtime.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/include/qsmaintime.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qportwidget.cpp, seq_qt5/src/qsabout.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmaintime.cpp, seq_qt5/src/qstriggereditor.cpp, seq_rtmidi/include/midi_alsa.hpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midibus.cpp: Removed 'do not document a namespace comment, as annoying. 2025-06-16 ahlstrom * NEWS, TODO, libseq66/include/play/playlist.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp: Fixed auto-arm playlist bug and note-pitch randomization. 2025-06-15 Chris * NEWS, TODO, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp: Moved QLinearGradient objects from auto scope to class scope. * doc/latex/tex/pattern_editor.tex, doc/latex/tex/preferences.tex, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseqroll.cpp: Interim check-in, working on gradient optimization. 2025-06-14 Chris * NEWS, TODO, libseq66/include/cfg/scales.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: The basic pattern-fix note pitch randomization works. 2025-06-13 Chris * NEWS, libseq66/include/cfg/scales.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Interim check-in, groundwork for randomizing note pitches. 2025-06-12 Chris * TODO, doc/latex/tex/sessions.tex, libseq66/src/play/performer.cpp, seq_qt5/src/qsessionframe.cpp: Minor tweaks, removed 16-in-busses.midi. * Makefile.in, NEWS, README.md, RELNOTES, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, TODO, VERSION, aux-files/compile, aux-files/depcomp, aux-files/ltmain.sh, aux-files/missing, configure, configure.ac, contrib/notes/midi-override-options.text, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Version bumped to 0.99.21 and BBT:HMS tooltip fixed. 2025-06-11 Chris * NEWS, TODO, contrib/midi/16-blank-patterns.text, doc/latex/tex/port_mapping.tex, doc/latex/tex/windows.tex, libseq66/include/play/portslist.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/portslist.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Improving/documenting port-mapping. 2025-06-10 ahlstrom * NEWS, TODO, data/samples/GM_PSS-790.patches, doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, doc/latex/tex/jack.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/palettes.tex, doc/latex/tex/playlist.tex, doc/latex/tex/recording.tex, doc/latex/tex/sessions.tex, doc/latex/tex/setmaster.tex, doc/latex/tex/song_editor.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qslogview.ui, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_rtmidi/src/midi_jack_info.cpp: A number of fixes, recent-files, playlists, and more. 2025-06-07 Chris * TODO, data/linux/jack/README, doc/latex/tex/event_editor.tex, doc/latex/tex/sessions.tex, doc/latex/tex/song_editor.tex, seq_rtmidi/src/midi_jack_info.cpp: Tweaked JACK detection, more manual updates. 2025-06-06 Chris * NEWS, TODO, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/song_editor.tex, libseq66/src/play/performer.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsmainwnd.cpp: More user-manual updates and code tweaks based on them. 2025-06-04 Chris * NEWS, TODO, data/linux/qseq66.ctrl, data/linux/qseq66.rc, doc/latex/tex/docs-structure.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/preferences.tex, libseq66/include/cfg/settings.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/os/shellexecute.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in of corrections based on fixing user manual. 2025-05-30 ahlstrom * doc/latex/tex/preferences.tex: Forgot to add preferences.tex. * doc/latex/tex/configuration.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/recording.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/windows.tex: Split Edit / Preferences into its own manual section. 2025-05-29 Chris * Makefile.in, NEWS, TODO, contrib/scripts/ystart, doc/latex/tex/menu.tex, libseq66/include/play/performer.hpp, libseq66/src/play/sequence.cpp: Updated ystart script, fixed setting and applying global (song) transposition. 2025-05-28 Chris * NEWS, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/recording.tex, libseq66/src/play/performer.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes to documentation, resetting trigger transposition, Mute button. 2025-05-27 Chris * NEWS, TODO, doc/latex/tex/first_start.tex, doc/latex/tex/live_grid.tex, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Added Seq66-click to close external editors, button size tweaks, more doc updates. 2025-05-26 Chris * Makefile.in, NEWS, TODO, aux-files/ltmain.sh, configure, data/seq66cli/seq66cli.ctrl, data/seq66cli/seq66cli.drums, data/seq66cli/seq66cli.mutes, data/seq66cli/seq66cli.playlist, data/seq66cli/seq66cli.rc, data/seq66cli/seq66cli.usr, doc/latex/tex/first_start.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/recording.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/rcfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/recent.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/recent.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midifile.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes to recent files, auto-save-rc, seq66cli configuration samples, and documentation. 2025-05-25 ahlstrom * TODO, VERSION, aux-files/ltmain.sh, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Merely catching up with the date. 2025-05-23 Chris * NEWS, TODO, doc/latex/tex/configuration.tex, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Updated sequence modification and recounting, nested live slot popup menu, consolidate new seqedit creation code in qsmainwnd. 2025-05-20 Chris * TODO, contrib/vim-syntax/c.vim, doc/latex/tex/alsa.tex, libseq66/include/midi/mastermidibase.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/palettefile.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Tweaks made while researching the input FIFO overrun issue. 2025-05-19 Chris * NEWS, TODO, doc/latex/tex/midi_export.tex, doc/latex/tex/recording.tex, libseq66/src/play/performer.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Fixed input buss index error, added OK/Cancel prompt to Save As and Exports. 2025-05-18 Chris * NEWS, TODO, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp: Fixed export to SMF 0 for live-mode songs. 2025-05-16 Chris * NEWS, data/samples/flat-rounded.qss, data/samples/grey-ghost.qss, doc/latex/tex/menu.tex, doc/latex/tex/palettes.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/song_editor.tex, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp: Cleanup of user manual, style-sheets, and song editor. 2025-05-15 Chris * INSTALL, NEWS, doc/latex/tex/concepts.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/seq66_features.h, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qslivegrid.cpp: Beefing up the user manual in progress. 2025-05-14 Chris * NEWS, TODO, libseq66/include/midi/midifile.hpp, libseq66/include/play/performer.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qslivegrid.cpp: Fixed a paste bug, go single-track export to work, must document. * libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp: Refactored qslivebase/qslivegrid re flatten and export functionality. 2025-05-13 Chris * NEWS, TODO, libseq66/include/midi/midifile.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp: Added flattening triggers to pattern slot popup menu. 2025-05-09 Chris * NEWS, TODO, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Added display of buss in event editor, more to come. 2025-05-08 Chris * libseq66/include/midi/midifile.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/midi_vector.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp: Interim check-in of some cleanup and additional experimental code in sequence. 2025-05-07 ahlstrom * NEWS, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/recording.tex, libseq66/src/play/sequence.cpp, resources/pixmaps/pause.xpm, seq_qt5/include/qloopbutton.hpp, seq_qt5/src/qloopbutton.cpp: Documentation on record-by and adding gradient support to elliptical progress box. 2025-05-06 Chris * TODO, seq_qt5/src/qseqroll.cpp: Tweak to mouse handling in seqroll. 2025-05-05 Chris * NEWS, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Fixed the painting of notes on mouse movement. 2025-05-04 ahlstrom * TODO, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/src/qslivebase.cpp: Fixed delete-pattern modify bug and can now save SMF 0 versus export. * NEWS, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/performer.cpp: Beefed up the auto-save of new versions of config files. 2025-05-03 Chris * NEWS, TODO, contrib/midi/README, libseq66/include/midi/midifile.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: Solved some issues with working with SMF 0 files. 2025-05-01 Chris * NEWS, TODO, libseq66/include/midi/calculations.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qseventslots.cpp: Added display of pitchbend semitones in the event editor. 2025-04-30 Chris * NEWS, TODO, doc/latex/tex/palettes.tex, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qperfbase.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp: Palette doc update, thick set-separator in song editor. * NEWS, TODO, libseq66/src/sessions/smanager.cpp, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Pen style selection working, needs cleanup. 2025-04-29 Chris * TODO, data/linux/qseq66-alt-gray.palette, data/linux/qseq66-default.palette, data/linux/qseq66-gray.palette, data/linux/qseq66.palette, data/linux/qseq66.rc, data/linux/qseq66.usr, data/samples/green.palette, data/samples/incrypt-66.palette, data/samples/monogreen.palette, data/samples/perstfic-66.palette, data/samples/qseq66-sample.palette, data/samples/sample.usr, data/win/qpseq66.palette, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qseqroll.cpp: Initial update to add pen-styles to palette file. 2025-04-28 Chris * NEWS, TODO, aux-files/ltmain.sh, configure, doc/latex/tex/alsa.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qstriggereditor.cpp: Fixups for zoom and grid drawing. 2025-04-26 Chris * : commit e254ff55adcd2fe2877a1dd8672d379139869145 Author: Chris Date: Sat Apr 26 11:19:52 2025 -0400 2025-04-16 ahlstrom * NEWS, TODO, m4/pkg.m4: pkg.m4 update plus to-dos. * doc/latex/tex/sessions.tex, libseq66/src/sessions/clinsmanager.cpp: Minor doc update for NSM. 2025-04-12 Chris * ChangeLog, README.md, VERSION, aux-files/ltmain.sh, configure, configure.ac, data/license.text, data/readme.text, data/readme.windows, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Updating dates for local install/testing. 2025-04-10 Chris * NEWS, contrib/vim-syntax/cpp.vim, contrib/vim.rc, data/linux/jack/jackctl, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Added a timed error message-box for use with NSM. 2025-03-19 Chris * seq_qt5/src/qclocklayout.cpp: Merely added 8 characters to label size limit in qclocklayout. 2025-03-02 ahlstrom * NEWS, README.md, RELNOTES, TODO, VERSION, configure, configure.ac, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Version bump to 0.99.20. 2025-03-02 Chris * doc/latex/tex/pattern_editor.tex, doc/latex/tex/seq66-user-manual.tex: Release Notes for Seq66 v. 0.99.19 2025-03-02 This release contains fixes for controller/patch handling, and other updates. Fixes for reported issues: - Issue #136. - Filtered out Program events from the drum-mode piano roll. - Can now drag Program events up and down in the data pane. - The saving of Program events now calculates the proper time-stamp. - Added display of the names of Control and Program Change numbers in the event editor and the data pane of the pattern editor. Other fixes: - Pressing the finger button in the pattern editor now also enters paint mode in the event pane. Updates: - Seq66 now shows the GM patch names in the pattern editor data pane. - Added a new configuration file, '.patches' to show non-GM device program names. See NEWS for more details. * libseq66/libseq66.pro: Added patches and patchesfile to the Qt build. * NEWS, README.md, RELNOTES, VERSION, configure.ac, data/license.text, data/readme.text, data/readme.windows, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Date-stamp updates. 2025-02-23 Chris * NEWS, RELNOTES, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Just some notes. 2025-02-20 Chris * NEWS, TODO, libseq66/include/midi/editable_event.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp: Finishes showing control and program names in event editor. 2025-02-19 Chris * NEWS, TODO, data/samples/GM.patches, data/samples/PSS-790.patches, doc/latex/tex/configuration.tex, libseq66/include/cfg/patchesfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/controllers.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/patchesfile.cpp, libseq66/src/midi/patches.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeventframe.cpp: Patches finished, working on D0 text in event editor in progress. 2025-02-18 Chris * data/samples/GM.patches, libseq66/include/cfg/patchesfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/patches.hpp, libseq66/src/cfg/patchesfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/patches.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qseditoptions.cpp: Patches file support nearly done. * data/samples/PSS-790.patches, libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/cfg/patchesfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/cfg/patchesfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/patches.cpp, seq_qt5/forms/qseditoptions.ui: Adding GUI for the patches file. 2025-02-17 Chris * TODO, data/samples/GM_DD-11.drums, data/samples/GM_PSS-790.drums, data/samples/GM_PSS-790.patches, data/samples/GM_PSS-790_Multi.ini, libseq66/include/cfg/patchesfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/patches.hpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/patchesfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/patches.cpp, seq_qt5/src/qseqdata.cpp: Adding patches file in progress. * aux-files/ltmain.sh, configure, data/samples/GM_PSS-790.patches, libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/midi/calculations.hpp, libseq66/include/midi/controllers.hpp, libseq66/include/midi/patches.hpp, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/midi/calculations.cpp, libseq66/src/midi/controllers.cpp, libseq66/src/midi/patches.cpp, seq_qt5/src/qseqdata.cpp: Added further adjustments to data-pane Program editing. 2025-02-16 Chris * NEWS, TODO, seq_qt5/src/qseqdata.cpp: Added adjustment for high-numbered Program events in data pane. * INSTALL, NEWS, TODO, contrib/DIR_COLORS, data/samples/GM_PSS-790.patches, libseq66/include/seq66_features.h, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp: Can insert event-pane items, patch names show. 2025-02-15 Chris * NEWS, TODO, aux-files/ltmain.sh, configure, libseq66/src/midi/calculations.cpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqroll.cpp: More work on issue #136, including side issues. * TODO, aux-files/ltmain.sh, configure, contrib/vim-syntax/cpp.vim, libseq66/include/midi/calculations.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/sequence.cpp: Fixed bug in inserting events in event editor re #136. 2025-02-04 Chris * NEWS, README.md, RELNOTES, VERSION, aux-files/ltmain.sh, configure, configure.ac, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/util/filefunctions.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/util/filefunctions.cpp, libseq66/src/util/strfunctions.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Version bump and file/strfunctions enhancements. 2025-02-03 ahlstrom * TODO: Release Notes for Seq66 v. 0.99.18 2025-02-02 This release contains fixes for dark themes, fixes to project import/export, a new Help feature, and other updates. Fixes for reported issues: - Issue #135. - Added an inverse pattern-length icon for dark themes. - Fixed the vertical lines in the time panel to obscure measure numbers and to avoid invisible vertical lines. Other fixes: - Fixed a seqfault when pressing an empty button on a set other than the first set. - Fixed the JACK-to-ALSA fallback process when running the jackdbus daemon. - Fixed an error in the --home command-line option that left "home" as "~/.config/seq66". - Refactored and fixed the Import and ExportProject Configuration menu entries. - Disabled the various "Remap"/"Restart" buttons when running under the NSM protocol. Updates: - Added support for raysession and agordejo to the jackctl script. Beefed up this script to facilitate testing and port-naming. - Added the dark-theme and dark-ui options to the user file for handling GUI elements that are otherwise difficult to see. - Added an option to dump the current palette while automatically inverting to create the --invert palette. - Refactored the copy-configuration and delete-configuration functions to make them more robust. - Updated the show_folder_dialog() function to show hidden directories. - Added a Help / View Log function to bring up the latest log text. See NEWS for more details. * : Merge conflicts. * NEWS, README.md, RELNOTES, TODO, VERSION, configure.ac, contrib/scripts/Jack, contrib/scripts/conk, contrib/scripts/release, data/license.text, {contrib/scripts => data/linux/jack}/jackctl, data/linux/jack/startjack, data/linux/jack/startqjack, data/readme.text, data/readme.windows, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/seq66_platform_macros.h, nsis/README, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_rtmidi/src/rtmidi.cpp: Version and date bump for 0.99.18 2025-02-01 ahlstromcj * NEWS, libseq66/include/seq66_platform_macros.h, libseq66/include/util/filefunctions.hpp, libseq66/src/util/filefunctions.cpp: Fixed Windows build of get_wildcard(), it just returns false. 2025-01-31 Chris * contrib/vim-syntax/cpp.vim: Tweaked git doc and cpp.vim syntax file. 2025-01-29 Chris * libseq66/include/seq66_platform_macros.h, libseq66/include/util/filefunctions.hpp, libseq66/src/play/performer.cpp, libseq66/src/util/filefunctions.cpp: Minor updates to globbing. 2025-01-27 Chris * NEWS, libseq66/include/util/filefunctions.hpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp: Added wild-card copying of image files. 2025-01-26 Chris * NEWS, RELNOTES, contrib/notes/session-mgrs.text, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: Disabled Remap and Restart when NSM is in control. * TODO, include/config.h.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_qt5/src/qslogview.cpp: Updated some Makefile.in files. 2025-01-25 Chris * NEWS, README.md, RELNOTES, TODO, VERSION, configure.ac, contrib/notes/session-mgrs.text, contrib/scripts/jackctl, data/readme.text, data/readme.windows, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/calculations.hpp, nsis/Seq66Constants.nsh, nsis/build_release_package.bat, seq_qt5/forms/Makefile.am, seq_qt5/forms/qpatternfix.ui, seq_qt5/forms/qslogview.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/Makefile.am, seq_qt5/include/qslogview.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/palettefile.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qsappinfo.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslogview.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Upgrades to show-folder function, new View Log help, import/export. 2025-01-23 Chris * NEWS, libseq66/include/os/shellexecute.hpp, libseq66/include/sessions/smanager.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/os/shellexecute.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Refactored and fixed the Import Project command. 2025-01-22 Chris * NEWS, TODO, contrib/scripts/jackctl, doc/latex/tex/menu.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/seq66_features.h, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/util/filefunctions.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midibus.cpp: Update to jackctl, configuration-file list, JACK detection, still need to fix configuration import. 2025-01-20 Chris * INSTALL, TODO, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midibus.cpp, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp: Fixing JACK-to-ALSA fallback in progress. 2025-01-19 Chris * TODO, contrib/notes/session-mgrs.text, contrib/scripts/jackctl, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/seq66_features.h, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in of refactored project export. 2025-01-17 Chris * TODO, contrib/notes/session-mgrs.text, contrib/scripts/jackctl, libseq66/src/midi/eventlist.cpp, seq_qt5/forms/qseditoptions.ui: Work for retesting session management, one bug found. 2025-01-15 Chris * INSTALL, TODO, data/linux/qseq66-alt-gray.palette, data/linux/qseq66-default.palette, data/linux/qseq66-gray.palette, data/linux/qseq66.palette, data/samples/green.palette, data/samples/incrypt-66.palette, data/samples/monogreen.palette, data/samples/perstfic-66.palette, data/samples/qseq66-sample.palette, libseq66/include/seq66_features.h, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qseditoptions.cpp: Updating palettes with dark-theme and dark-ui options. * data/samples/monogreen.palette, data/samples/qseq66.qss, libseq66/include/cfg/usrsettings.hpp, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qseqtime.cpp: Tentatively fixed items 2 and 3 of issue #135. 2025-01-14 Chris * data/linux/qseq66.palette, libseq66/include/util/palette.hpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qseditoptions.cpp: Working on inverting colors automatically. * TODO, data/samples/monogreen.palette, doc/latex/tex/sessions.tex, resources/pixmaps/length_short_inv.xpm, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qseqeditframe64.cpp: Fixed item 1 of issue #135, prepped for more. 2025-01-13 ahlstrom * NEWS, TODO, data/readme.text, libseq66/src/play/performer.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp: Fixed route66 bitmap, refresh of pattern editor port/channel, set 1+ segfault. 2025-01-12 Chris * NEWS, README.md, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qslivegrid.cpp: Rev bump to 0.99.17.1 to fix a segfault in Live grid. * NEWS, README.md, RELNOTES, TODO, VERSION, configure, configure.ac, distros/debian/copyright, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/os/daemonize.hpp, libseq66/include/os/timing.hpp, libseq66/include/seq66_platform_macros.h, libseq66/src/os/daemonize.cpp, seq_qt5/src/qseqeditframe64.cpp: Version/date updates. 2025-01-11 ahlstrom * ChangeLog: Release Notes for Seq66 v. 0.99.17 2025-01-12 This release contains a large number of updates and fixes spurred by testing and user-reports. Fixes for reported issues: - Issue #128. Expanded recording and related recording issues have been greatly tightened, plus some follow-on issues. (See NEWS). - Issue #133. Pattern length change issues and issues found while working this. See TODO for a list of things fixed. - Issue #134. Added dates to the release-name line from 0.99.14 on. Other fixes: - Fixed a segfault when clicking the Restart button with external live frame(s) or external song editor open. - Fixed a seqfault when opening a new tune or a recent-file with a pattern-editor already up for the current tune. - Main window. Fixed updating the PPQN when a file of different PPQN is loaded. Updates: - Majorly revamped, fixed, and tightened the "Pattern Fix" dialog. - Added a little more control over the lines and palette of the various panes in the pattern and song editors. Also improved the appearance of time signatures other than 4/4. - Refactored the event/buss dropdowns to be modified only when the user changes setting or tries to bring up the dropdowns - Added ghost notes to the pattern-editor selection box and other means of note selection. - Some minor tweaking (field size, font) of various dialogs. - See NEWS and ChangeLog for full details. 2025-01-11 Chris * README.md, TODO, contrib/scripts/qbuild.sh, doc/latex/tex/seq66-user-manual.tex: Notes and spelling errors. * include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Updated qt include files for version/date. * INSTALL, NEWS, README.md, RELNOTES, TODO, VERSION, configure.ac, data/readme.text, data/readme.windows, include/config.h.in, nsis/Seq66Constants.nsh, nsis/build_release_package.bat: Prep for upcoming release. 2025-01-10 Chris * NEWS, TODO, contrib/scripts/notemapgen.py, data/testing/simple-test.notemap, doc/latex/tex/pattern_editor.tex, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/notemapper.cpp, seq_qt5/src/qpatternfix.cpp: Finished the pattern-fix work, we hope. 2025-01-09 Chris * TODO, contrib/notes/pattern-fix-tests.text, libseq66/include/play/notemapper.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/play/notemapper.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qpatternfix.cpp: Implemented notemapper pattern-fix, needs tests. 2025-01-08 Chris * NEWS, TODO, contrib/notes/pattern-fix-tests.text, data/linux/qseq66-alt-gray.palette, data/linux/qseq66-default.palette, data/linux/qseq66-gray.palette, data/linux/qseq66.palette, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/calculations.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/automutex.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qt5_helpers.cpp: Added align right to pattern fix, nearly done. 2025-01-05 Chris * TODO, contrib/notes/pattern-fix-tests.text, contrib/scripts/grayscale.sh, doc/latex/tex/pattern_editor.tex, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp: More fixes to fix-pattern. 2025-01-04 Chris * TODO, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Got time-sig fix-pattern Set and Reset to work for 3/4. 2025-01-02 Chris * NEWS, README.md, TODO, data/license.text, libseq66/include/midi/calculations.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp: Fixing time-signature pattern-fix still in progress. 2024-12-31 Chris * resources/pixmaps/note_length_inv.xpm, resources/pixmaps/quantize_inv.xpm, resources/pixmaps/tools.xpm, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp: Added dark-theme icons, pattern-fix change. * libseq66/include/util/strfunctions.hpp, libseq66/src/util/strfunctions.cpp: Added test file, float-number detection. 2024-12-30 Chris * contrib/notes/pattern-fix-tests.text, data/samples/monogreen.palette, data/samples/monogreen.qss, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/calculations.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qpatternfix.cpp: Working on fixing the pattern-fix feature. 2024-12-29 Chris * TODO, data/samples/incrypt-66.palette, data/samples/perstfic-66.palette, data/samples/perstfic-66.qss, data/samples/qseq66-sample.palette, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qstriggereditor.cpp: Improving appearance of grids, tables, and style-sheets. * NEWS, TODO, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Fixed segfault during Restart with external grid and song windows open. 2024-12-28 Chris * NEWS, TODO, data/samples/perstfic-66.palette, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Making grid-line settings a little more orderly. 2024-12-27 Chris * TODO, data/midi/FM/README, data/samples/monogreen.qss, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseqroll.cpp: Cleanup of verify/link, monogreen.qss. * NEWS, TODO, data/linux/qseq66.rc, data/linux/qseq66.usr, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/calculations.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/playlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, resources/pixmaps/up_inv.xpm, seq_portmidi/src/midibus.cpp, seq_portmidi/src/pmwinmm.c, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qscrollmaster.h, seq_qt5/include/qscrollslave.h, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5nsmanager.cpp: Interim cleanup check-in, added up_inv icon. 2024-12-24 Chris * NEWS, TODO, doc/latex/tex/pattern_editor.tex, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qt5_helpers.cpp: Interim check-in, minor fixes like 'usr' file saving. 2024-12-23 Chris * TODO, contrib/scripts/audio, contrib/scripts/dot-xbindkeysrc, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditframe64.cpp: Added a couple scripts, got one-shot during playback working. 2024-12-21 Chris * NEWS, TODO, contrib/notes/perf-callbacks.text, libseq66/include/cfg/zoomer.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp: Fixed zoomer, wrestling with performer callbacks. 2024-12-19 Chris * NEWS, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/cfg/zoomer.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp: Fixes to performer callbacks, grid-drawing, and reading time signatures. 2024-12-17 ahlstrom * TODO, libseq66/include/cfg/zoomer.hpp, libseq66/include/midi/calculations.hpp, libseq66/src/cfg/zoomer.cpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp: Refactoring of zoomer complete at this time. 2024-12-16 Chris * NEWS, libseq66/include/cfg/zoomer.hpp, libseq66/include/midi/calculations.hpp, libseq66/src/cfg/zoomer.cpp, libseq66/src/midi/calculations.cpp, seq_qt5/include/qeditbase.hpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qstriggereditor.cpp: Refactoring zoomer and editor grid-drawing in progress. 2024-12-14 Chris * NEWS, TODO, doc/latex/tex/midi_formats.tex, libseq66/include/midi/calculations.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qbase.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed seqfault as noted in NEWS, added tests of handling non-192-divisible PPQNs. 2024-12-12 ahlstrom * TODO, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, resources/pixmaps/up.xpm, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp: Added up icon, fixes to seqedit time-sig changes. 2024-12-11 Chris * NEWS, TODO, libseq66/include/midi/calculations.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Issues #133 and #134 fixes in progress, GUI tweaks. 2024-12-08 Chris * TODO, contrib/DIR_COLORS, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/calculations.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Interim check-in of upgrading qpatternfix. 2024-12-04 Chris * NEWS, TODO, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Reworked linking and zero-correction of recorded notes, fixed removal of truncated events in patterns. * TODO, configure, include/config.h.in, libseq66/include/midi/eventlist.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp: Working on recording issues with linking notes. 2024-12-03 Chris * NEWS, README.md, RELNOTES, VERSION, configure.ac, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/seq66-user-manual.tex, include/cli/seq66-config.h, include/qt/portmidi/seq66-config.h, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Prep for work on version 0.99.17. 2024-12-03 ahlstrom * ChangeLog, NEWS, RELNOTES: Release Notes for Seq66 v. 0.99.16 2024-12-03 Fixes for NSM (session manager), build-file updates, and better PPQN and recording handling. A lot of little issues found and fixed, too. - Fixed issue #128 with expanded recording not working. The expansion is now continual, not waiting for a MIDI key to be struck. - Merged a fix from a pull request (issue #130) to update the "*.desktop" files. - Fixed issue #131 re faulty NSM interactions introduced in version 0.99.11, plus other related issues: - NSM (agordejo or nsm-legacy-gui) would show two clients: "qseq66" and "seq66" when adding only the "qseq66" client. - Saving via a remote NSM Save command or by the File / Save menu would not clear the modified flag. - Closing the session would not remove any external editor windows. - The main window now reflects the current record-loop style and new-pattern option as read from the 'usr' file. - Fixed the pattern editor so it reflects buss and channel settings made from the grid slot popup menu. - Fixed the display of tunes with various PPQNs such as 120 in the pattern editor. - Fixed zero-length notes caused by quantized recording. - Some automation actions need to work whether the action is "on" or "toggle". Fixed these 'ctrl' actions: - Save session (under NSM) or the MIDI file. - Record style select. - Quit. - Added "Clear events" to the grid slot popup menu. - Enhancements to pattern-editor note copy/paste. - Added 120 PPQN to the list of supported PPQNs. - Fixed File / New plus File Save overwriting the previous loaded file. - The main window now reflects the current record-loop style and new-pattern option as read from the 'usr' file. But note: - Renamed [new-pattern-editor] to [pattern-editor] in the 'usr' file. - The Quantized Record button in the pattern editor steps through None, Tighten, Quantize, Notemap, None.... Prettied-up the icons, too. - Added CONFIG\_DIR\_NAME and cleaned up configure.ac. This macro differentiates between client name and config directory name. Updated the Makefile sources. Do "./bootstrap --full-clean". - Updated the PDF documentation re the Import/Export functionality etc. - Upgraded the color palette code. - See NEWS and ChangeLog for full details. 2024-12-03 ahlstrom * : Final document update. * TODO, doc/latex/tex/event_editor.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex: More documentation updates. * aux-files/ltmain.sh, configure, contrib/scripts/compositor, contrib/scripts/jackctl, doc/latex/tex/menu.tex, include/config.h.in, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qpatternfix.ui, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsetmaster.ui: Still more tweaks to scripts, the GUI, and docs. 2024-12-02 Chris * NEWS, README.md, TODO, VERSION, configure.ac, contrib/scripts/compositor, data/license.text, data/readme.text, doc/latex/tex/configuration.tex, doc/latex/tex/first_start.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qsmainwnd.cpp: Mostly caught up with documentation. * NEWS, RELNOTES, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Added options for progress-bar and grid-lines changes. 2024-12-01 ahlstrom * NEWS, TODO, contrib/scripts/ystart, data/samples/monogreen.palette, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/util/palette.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqroll.cpp: Updated palette, ystart script, and song editor grid drawing. 2024-11-30 ahlstrom * NEWS, TODO, VERSION, configure.ac, data/license.text, data/readme.text, data/readme.windows, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/cfg/settings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, nsis/build_release_package.bat, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp: Provisionally finished issue #128, updated version info. 2024-11-29 Chris * NEWS, RELNOTES, TODO, data/samples/monogreen.qss, doc/latex/tex/configuration.tex, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp: Updated seqedit, might have Qt6 in it, and fixed bus/channel settings from live grid popup menu. * NEWS, TODO, libseq66/include/midi/eventlist.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp: Added zero-length note correction and check for note value when recording. 2024-11-27 Chris * NEWS, TODO, libseq66/include/midi/eventlist.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseventslots.cpp: Added provisional feature to streamline the recording and linking of new notes from a MIDI keyboard. * TODO, data/samples/monogreen.qss, doc/latex/tex/live_grid.tex, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qseventslots.cpp: Undefined SEQ66_PRIORITIZE_NOTE_OFF to fix too-long note recording. 2024-11-26 Chris * libseq66/src/play/sequence.cpp: Enabled handling expansion properly in sequence. * NEWS, RELNOTES, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseqroll.cpp: Paste-selection box works properly now. 2024-11-25 Chris * NEWS, data/samples/monogreen.qss, libseq66/include/play/sequence.hpp, libseq66/include/util/rect.hpp, libseq66/src/play/sequence.cpp, libseq66/src/util/rect.cpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseqroll.cpp: Fixed bug that offset painted notes down 2 notes, still hacking at ghost notes when pasting. 2024-11-23 Chris * INSTALL, TODO, bootstrap, data/samples/monogreen.qss, libseq66/include/midi/calculations.hpp, libseq66/include/play/performer.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Tweaked monoscreen.qss, added snapped template function, fixed note-drawing of non-192 PPQN. 2024-11-20 Chris * TODO, data/samples/monogreen.palette, data/samples/monogreen.qss, data/samples/perstfic-66.qss, libseq66/src/midi/eventlist.cpp, seq_qt5/src/qseqroll.cpp: Added monogreen palette/qss file pair. * TODO, data/samples/green.palette, data/samples/green.qss: Added a green palette and style sheet. 2024-11-19 Chris * TODO, doc/latex/tex/patterns_panel.tex, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqtime.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqtime.cpp: Simplified the record-style handling, add END> to mark expanded recording. 2024-11-18 Chris * TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Added padding to seqroll when expand-recording. 2024-11-17 Chris * NEWS, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp: Added better handling of empty patterns in sets. * NEWS, RELNOTES, TODO, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qseqtime.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmaintime.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp: More progress on expand-recording, issue #128. 2024-11-15 ahlstrom * TODO, doc/latex/tex/pattern_editor.tex, resources/pixmaps/length_red.xpm, resources/pixmaps/length_short.xpm, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qseqeditframe64.cpp: Cleanup in pattern editor, added red length icon. 2024-11-14 Chris * NEWS, RELNOTES, TODO, contrib/tests/4x4/qseq66.usr, data/linux/qseq66.usr, data/samples/sample.usr, data/seq66cli/seq66cli.usr, data/win/qpseq66.usr, doc/latex/tex/alsa.tex, doc/latex/tex/configuration.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/song_editor.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, resources/pixmaps/n_rec_on.xpm, resources/pixmaps/q_rec.xpm, resources/pixmaps/q_rec_on.xpm, resources/pixmaps/t_rec_on.xpm, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp: Close to finishing off issue #128, expand-record. 2024-11-11 Chris * libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp: Removing redundant grid-record-style code. 2024-11-10 Chris * TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, resources/pixmaps/exp_rec_on.xpm, seq_qt5/include/qseqtime.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qslivegrid.cpp: Still working on issue #128, many problems uncovered. * NEWS, RELNOTES, TODO, contrib/scripts/jackctl, doc/latex/tex/configuration.tex, libseq66/src/cfg/usrfile.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: The main window now shows the record mode/style settings fread from 'usr'. 2024-11-08 Chris * NEWS, README.md, RELNOTES, TODO, doc/latex/tex/configuration.tex, libseq66/src/ctrl/automation.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes for using a control keystroke to Save. 2024-11-07 Chris * NEWS, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: More follow-up fixes for issue #131. * : commit fbe4d2fdf5b690a4b1f4b19a97528f139ac07b0b Merge: e529bf65 9ac12b15 Author: C. Ahlstrom Date: Thu Nov 7 11:42:15 2024 -0500 * Seq66qt5/seq66qt5.cpp, libseq66/include/seq66_features.hpp, libseq66/src/os/daemonize.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp: Cleanup for issue #131. 2024-11-06 Chris * Seq66qt5/seq66qt5.cpp, libseq66/include/seq66_features.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qt5nsmanager.cpp: Safety check-in for issue #131 fix. * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, Seq66qt5/seq66qt5.cpp, aux-files/compile, aux-files/depcomp, aux-files/ltmain.sh, aux-files/missing, configure, contrib/non/nsm_tendrils.txt, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/seq66_features.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/Makefile.in, libseq66/src/os/daemonize.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/include/nsm/nsmbase.hpp, libsessions/src/Makefile.in, libsessions/src/nsm/nsmbase.cpp, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: NSM troubleshooting and more modern makefiles. 2024-11-04 Chris * INSTALL, Makefile.in, NEWS, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, aux-files/ltmain.sh, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/cli/seq66-config.h, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/seq66_features.hpp, libseq66/src/Makefile.in, libseq66/src/cfg/rcsettings.cpp, libseq66/src/seq66_features.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Differentiate client name from configuration directory name. 2024-11-03 g * data/share/applications/seq66.desktop, distros/debian/seq66.desktop: seq66.desktop: fix typo; add X-NSM-Exec=qseq66 2024-11-02 ahlstrom * NEWS, RELNOTES, TODO, libseq66/src/midi/calculations.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed PPQN handling. 2024-11-01 Chris * libseq66/include/cfg/settings.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/midi/calculations.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: More tinkering with handling various PPQNs. 2024-10-31 Chris * NEWS, TODO, contrib/DIR_COLORS, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, libseq66/src/cfg/settings.cpp, seq_qt5/src/qseqtime.cpp: Import/Export documentation, tinkering with drawing code for 120 PPQN. 2024-10-29 Chris * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Updated Makefile.in etc for 0.99.16. 2024-10-29 ahlstrom * NEWS, README.md, RELNOTES, VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Version bump for 0.99.16. 2024-10-28 Chris * ChangeLog: Version 0.99.15 pending. * configure.ac, m4/ax_have_qt.m4, nsis/build_release_package.bat: Still working on MSYS2 build, conflicting Qt locations between Win Qt install and Msys2 Qt install. 2024-10-27 ahlstrom * README.md, TODO, bootstrap, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp: Fixed modify flag when loading MIDI file with sequence having an input buss specified. * NEWS, README.md, TODO, VERSION, bootstrap, configure.ac, data/share/doc/tutorial/introduction.html, doc/latex/tex/configuration.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/recording.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, seq_qt5/src/qslivegrid.cpp: Moving pending new work to non-master branch for safety. 2024-10-25 Chris Ahlstrom * INSTALL, bootstrap, contrib/notes/msys2-packages.text: More work on getting MSYS2 build to work. * nsis/build_release_package.bat: Tweaking errorlevel checks in Windows batch build. 2024-10-23 ahlstromcj * nsis/build_release_package.bat: Got Windows batch file mostly working. * data/readme.text, data/readme.windows, nsis/README, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Setting up for Windows 11 test. 2024-10-21 Chris * : Merge conflict fixed. 2024-10-19 ahlstrom * Makefile.in, NEWS, RELNOTES, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, TODO, VERSION, aux-files/compile, aux-files/depcomp, aux-files/ltmain.sh, aux-files/missing, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/cli/seq66-config.h, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/seq66_platform_macros.h, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_qt5/src/qsabout.cpp, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Rearranged configure.ac to avoid enabling JACK and liblo (NSM) if not installed. * configure.ac, contrib/scripts/qbuildwin.sh: Tweaking to try to get an msys2 build. 2024-10-16 Chris * doc/latex/tex/menu.tex, doc/latex/tex/palettes.tex, seq_qt5/forms/qseditoptions.ui: Completely updated the Edit / Preference screenshots. 2024-10-15 Chris * NEWS, TODO, contrib/gdb/cgdbrc, contrib/gdb/dot-gdbinit, doc/latex/tex/menu.tex, doc/latex/tex/palettes.tex, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qt5_helpers.cpp: Added browser/pdf settings buttons, fixed more GUI ugliness. 2024-10-14 Chris * NEWS, README.md, RELNOTES, TODO, contrib/DIR_COLORS, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Added a Preference / Session setting to specify / select the browser and PDF viewer for Help. 2024-10-13 Chris * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, TODO, aux-files/compile, aux-files/depcomp, aux-files/ltmain.sh, aux-files/missing, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/pattern_editor.tex, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/forms/qliveframeex.ui, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: New makefiles, and documentation/GUI updates. 2024-09-28 ahlstrom * NEWS, README.md, RELNOTES, TODO, VERSION, configure.ac, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/zoomer.hpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Prep for 0.99.15 wip. 2024-08-24 Chris Ahlstrom * nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Updated Windows build files. 2024-08-24 Chris Ahlstrom * ChangeLog, NEWS, README.md, RELNOTES, ROADMAP.md, TODO, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/pattern_editor.tex, doc/latex/tex/recording.tex, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qsessionframe.ui: Prepping for v. 0.99.14. 2024-08-22 Chris Ahlstrom * NEWS, TODO, libseq66/src/midi/event.cpp: Changed event::get_rank() to give Note Offs a higher priority to fix potential playback issues. * doc/latex/tex/pattern_editor.tex, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseqroll.cpp: Add palette coloring to note tooltips. 2024-08-21 Chris Ahlstrom * NEWS, TODO, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qt5_helpers.cpp: Added note-info tooltips. 2024-08-18 Chris Ahlstrom * NEWS, doc/latex/tex/live_grid.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/recording.tex, doc/latex/tex/song_editor.tex, libseq66/src/play/performer.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Tightened record-button handling in live-grid. 2024-08-17 Chris Ahlstrom * : commit a5e0dd7193e37e0625c7237a75f054fda47b510e Author: Chris Ahlstrom Date: Sat Aug 17 08:57:52 2024 -0400 2024-08-16 ahlstrom * TODO, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qseqeditframe64.cpp: Provisional fix to issue #129 where quantization will not set if recording is already set. 2024-08-15 Chris Ahlstrom * NEWS, doc/latex/tex/pattern_editor.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp: Interim check-in issue #128, one-shot reset. 2024-08-08 ahlstrom * NEWS, libseq66/src/cfg/cmdlineopts.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp: Upgraded support for elliptical progress boxes. 2024-08-07 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, Seq66qt5/Seq66qt5.pro, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Makefiles for v. 0.99.14. * TODO, doc/latex/tex/configuration.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, libseq66/include/midi/calculations.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/metro.cpp, seq_qt5/src/qseqroll.cpp: Fixes and documentation concerning PPQN. 2024-08-06 Chris Ahlstrom * NEWS, TODO, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qperfroll.cpp: Add Esc-exit to external song editor. * NEWS, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/cfg/zoomer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/zoomer.cpp, seq_qt5/forms/qseditoptions.ui: Fixed naughty compiler warning in zoomer class. 2024-08-05 Chris Ahlstrom * NEWS, README.md, RELNOTES, TODO, VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version bump in portfix to 0.99.14. * ChangeLog, Makefile.in, RELNOTES, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, configure.ac, contrib/scripts/{qbuild => qbuild-bash}, contrib/scripts/qbuild.sh, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Release Notes for Seq66 v. 0.99.13 2024-08-05 - Added a 'usr' option for elliptical progress boxes. - Fixed the writing and byte-counting of the end-of track event. - Updated the licensing files for GitHub detectability. - See NEWS and ChangeLog for more details. This is an quick-release since other projects have taken our time. * NEWS, README.md, RELNOTES: News updates. 2024-06-06 Chris Ahlstrom * LICENSE.LGPL: Dang forgot the LGPL license. * GPL.txt, LGPL.txt, LICENSE, FDL.txt => LICENSE.FDL, NEWS, README.md, RELNOTES: More upgrading of the licensing files. * LICENSE.GPL, doc/latex/tex/alsa.tex, libseq66/include/midi/midibase.hpp, libseq66/src/cfg/rcfile.cpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa.cpp: Fixed build issue, added LICENSE.GPL. * TODO, doc/latex/tex/configuration.tex, libseq66/src/play/performer.cpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa.cpp: Documenting virtual ports and port-mapping better. * TODO, doc/latex/tex/configuration.tex, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qclocklayout.cpp: Tweaks to make it obvious about port-mapping/naming. 2024-06-04 Chris Ahlstrom * TODO, libseq66/include/midi/midibase.hpp, libseq66/src/midi/midibase.cpp, seq_rtmidi/src/mastermidibus.cpp: Beefed up verbose port display, should remove client_id() as bus_id() is right. 2024-05-25 ahlstrom * : Updated the simpleblast MIDI files. 2024-05-22 Chris Ahlstrom * NEWS, TODO, libseq66/src/play/sequence.cpp: Fixed some data/midi files, added check to output sysex, in progress. * NEWS, doc/latex/tex/midi_formats.tex, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/midifile.cpp: Added handling of Seq66 SeqSpec track when another app saved it as a counted track. 2024-05-21 Chris Ahlstrom * : Added special test file 1Bar_2_tracks.midi. 2024-05-20 Chris Ahlstrom * NEWS, README.md, RELNOTES, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midifile.hpp: Fixed track-length calculation, updated Kraftwerk tune. * libseq66/include/midi/midifile.hpp, libseq66/src/midi/midifile.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseditoptions.cpp: Fixed tempo-track change notification, fixed 1Bar tune. * libseq66/src/midi/midifile.cpp, seq_qt5/forms/qseditoptions.ui: Tentative fix of error writing SeqSpec end-of-track. 2024-05-16 ahlstrom * : Updated 1Bar.midi file. 2024-05-03 Chris Ahlstrom * libseq66/include/play/performer.hpp: Interim. 2024-04-29 ahlstrom * Seq66cli/Seq66cli.pro, Seq66qt5/Seq66qt5.pro, TODO: Adding qmake install support in progress. 2024-04-26 Chris Ahlstrom * contrib/notes/freebsd.text, doc/latex/tex/references.tex: Documentation updates. 2024-03-24 ahlstrom * contrib/DIR_COLORS, contrib/vim-syntax/cpp.vim: More etc and vim coloring updates. 2024-03-23 ahlstrom * contrib/vim-syntax/c.vim: Added word and byte to c.vim. 2024-03-18 Chris Ahlstrom * configure, contrib/DIR_COLORS, include/config.h.in: Minor configure and DIR_COLORS update. 2024-03-12 ahlstrom * configure.ac, libseq66/src/util/filefunctions.cpp, libsessions/include/nsm/nsmbase.hpp, libsessions/src/nsm/nsmbase.cpp: Fixed liblo warnings, clang 17 errors/warnings. 2024-03-11 ahlstrom * libsessions/src/nsm/nsmbase.cpp: Fixed OSC liblo call error in Arch Linux, needs testing. * libseq66/include/seq66_platform_macros.h: Fixed SEQ66_PLATFORM_DEBUG definition. 2024-03-05 Chris Ahlstrom * contrib/vim-syntax/c.vim, contrib/vim-syntax/cpp.vim, contrib/vim-syntax/syncolor.vim: Updated cpp.vim and added syncolor.vim. 2024-03-03 Chris Ahlstrom * contrib/vim-syntax/cpp.vim: cpp.vim and git.odt changes. 2024-02-23 ahlstrom * NEWS, README.md, RELNOTES, VERSION, doc/latex/tex/configuration.tex, libseq66/src/cfg/usrfile.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qloopbutton.cpp: Refined and documented elliptical progress box. 2024-02-22 ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/src/qloopbutton.cpp: Added progress-box-elliptical option. * TODO, contrib/vim-syntax/c.vim, contrib/vim-syntax/cpp.vim: Added keywords to C/C++ Vim syntax. 2024-01-17 ahlstrom * LICENSE, TODO, data/license.text, distros/debian/copyright, libseq66/include/os/daemonize.hpp, libseq66/include/os/timing.hpp, libseq66/include/seq66_platform_macros.h, libseq66/include/util/condition.hpp, libseq66/include/util/recmutex.hpp, libseq66/src/midi/event.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/util/condition.cpp, libseq66/src/util/recmutex.cpp: Backporting some clang-related fixes. 2024-01-14 ahlstrom * README.md, RELNOTES, VERSION, configure.ac, contrib/notes/freebsd.text, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp: Version bump and additional freebsd notes. 2024-01-13 Chris Ahlstrom * configure, configure.ac, include/config.h.in, libseq66/include/seq66_platform_macros.h, libseq66/src/util/filefunctions.cpp, m4/ax_have_qt_clang.m4, seq_portmidi/src/ptlinux.c: More clang-related work and configure updates. * INSTALL, NEWS, README.md, RELNOTES, TODO, autogen.sh, bootstrap, configure.ac, contrib/notes/freebsd.text: Added autogen.sh and freebsd notes. 2024-01-10 Chris Ahlstrom * INSTALL, TODO, data/license.text, data/readme.text, data/readme.windows, libseq66/src/seq66_features.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/seq66_rtmidi_features.h, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp: Date bumps and updates for FreeBSD, in progress. 2024-01-09 ahlstrom * RELNOTES, TODO, doc/latex/tex/defaultkeys.tex, doc/latex/tex/patterns_panel.tex, libseq66/src/ctrl/keycontainer.cpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Disabled Solo dropdown entry, changed Solo key default. 2024-01-07 Chris Ahlstrom * seq_portmidi/seq_portmidi.pro, seq_portmidi/src/Makefile.am, seq_portmidi/src/Makefile.in: Disabled gcc warning about function cast in portmidi code. 2024-01-06 ahlstrom * seq_portmidi/include/pminternal.h, seq_portmidi/src/portmidi.c: Added notes about gcc warning for cast of Pt_Timer() function. 2024-01-05 ahlstrom * seq_portmidi/include/portmidi.h, seq_portmidi/include/porttime.h, seq_portmidi/src/pmlinuxalsa.c, seq_portmidi/src/ptlinux.c, seq_portmidi/src/ptmacosx_cf.c, seq_portmidi/src/ptmacosx_mach.c, seq_portmidi/src/ptwinmm.c: Fixed some errors and warning in portmidi under clang. * Makefile.in: Makefile.in redux. 2024-01-04 Chris Ahlstrom * Makefile.in: Solo makefile. * doc/latex/tex/patterns_panel.tex, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp: The ctrlstatus operations now work, at last. 2024-01-03 ahlstrom * contrib/code/ring_buffer.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqroll.cpp: Still more tweaking of replace/solo/queue/keep-queue mode. 2024-01-02 Chris Ahlstrom * contrib/tests/4x4/qseq66-lp-mini-4x4.ctrl, contrib/tests/4x4/qseq66.ctrl, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66-lp-mini-swapped.ctrl, data/linux/qseq66-lp-mini.ctrl, data/linux/qseq66-swapped.ctrl, data/linux/qseq66.ctrl, data/samples/nanomap.ctrl, data/seq66cli/seq66cli.ctrl, data/win/qpseq66.ctrl, doc/latex/tex/patterns_panel.tex, include/config.h.in, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/Makefile.in: More work on solo, ctrl keystrokes. 2023-12-30 ahlstrom * VERSION, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/src/qslivegrid.cpp: Solo is queued-replace, ctrl statuses displayed in main window. 2023-12-29 Chris Ahlstrom * RELNOTES, TODO, contrib/midi/README, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseventslots.cpp: Updated sysex/text display and MIDI file error messages. 2023-12-28 Chris Ahlstrom * RELNOTES, TODO, libseq66/include/ctrl/automation.hpp, libseq66/src/play/performer.cpp: Fixed bug in keep-queue, tightening related code. 2023-12-26 ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp: Interim check-in against new solo branch. 2023-12-24 ahlstrom * INSTALL, RELNOTES, TODO, contrib/scripts/qbuild, contrib/scripts/qbuild.sh, libseq66/src/play/performer.cpp, seq_portmidi/src/portmidi.c: Fixed portmidi.c buffer overflow. 2023-12-23 ahlstrom * RELNOTES, Seq66qt5/Seq66qt5.pro, contrib/scripts/qbuild, contrib/scripts/qbuild.sh, contrib/scripts/qtests, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, libsessions/libsessions.pro, seq_qt5/include/Makefile.am, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Giving up on automake for FreeBSD, added new qbuild.sh script to build using qmake, which works, but qseq66 has a Qt xcb plugin issue. 2023-12-22 ahlstrom * doc/latex/tex/patterns_panel.tex, libseq66/src/os/shellexecute.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp: Change some LINUX macroing to UNIX macroing for FreeBSD. 2023-12-21 ahlstrom * INSTALL, TODO, libseq66/src/util/recmutex.cpp: Fixed recmutex build in FreeBSD, now QBrush is not found. 2023-12-20 Chris Ahlstrom * INSTALL, configure, configure.ac, contrib/notes/clang-macros-freebsd.text, include/config.h.in, libseq66/include/play/triggers.hpp, libseq66/include/util/recmutex.hpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/recmutex.cpp, seq_qt5/src/qslivegrid.cpp: More progress on issue #124 FreeBSD. 2023-12-19 Chris Ahlstrom * INSTALL, configure, configure.ac, configure.help, include/config.h.in, libseq66/include/seq66_platform_macros.h, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, seq_qt5/src/qslivegrid.cpp: Added better detection of FreeBSD for issue #124. 2023-12-17 ahlstrom * TODO, libseq66/include/midi/midifile.hpp, libseq66/src/midi/midifile.cpp: Fixed nagging little errors in parsing MIDI files. 2023-12-16 Chris Ahlstrom * INSTALL, Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, TODO, configure, configure.ac, contrib/scripts/configure-clang, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libseq66/src/midi/midifile.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Still more fixes to the configure script. 2023-12-15 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.am, Seq66qt5/Makefile.in, TODO, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, doc/latex/tex/midi_formats.tex, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/midi/midifile.hpp, libseq66/src/Makefile.in, libseq66/src/midi/midifile.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Updated configure.ac to handle linker differences between gcc/g++ and clang/clang++ for issue #124. 2023-12-14 Chris Ahlstrom * README.md, Seq66cli/Makefile.am, Seq66cli/Makefile.in, Seq66qt5/Makefile.am, Seq66qt5/Makefile.in, TODO, configure, configure.ac, contrib/midi/README, doc/latex/tex/midi_formats.tex, doc/latex/tex/references.tex, include/config.h.in, libseq66/include/midi/event.hpp, libseq66/include/midi/midifile.hpp, libseq66/src/midi/midifile.cpp: Wrestling with oddities in Dixie04.mid. 2023-12-13 Chris Ahlstrom * RELNOTES, configure, configure.ac, include/config.h.in, libseq66/include/cfg/cmdlineopts.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/src/qsmainwnd.cpp: Configure.ac to support LLVM/clang, fixed a couple command-line option bugs. 2023-12-12 Chris Ahlstrom * RELNOTES, TODO, doc/latex/tex/midi_formats.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/midi/event.hpp, libseq66/include/midi/midifile.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midi_vector.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp: More fixes to SysEx handling/documentation. 2023-12-11 Chris Ahlstrom * INSTALL, RELNOTES, Seq66cli/Makefile.am, Seq66cli/Makefile.in, TODO, doc/dox/doxy-common.cfg, doc/latex/tex/meta_events.tex, doc/latex/tex/midi_formats.tex, libseq66/include/midi/midifile.hpp, libseq66/include/seq66_features.h, libseq66/src/midi/midifile.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Fixing some SysEx misunderstandings. * RELNOTES, TODO, contrib/midi/README, data/linux/qseq66.rc, doc/latex/tex/configuration.tex, libseq66/include/midi/midifile.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp: Added running-status and SysEx fixes. 2023-12-10 ahlstrom * RELNOTES, TODO, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/midifile.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Added option to recover from running-status errors. 2023-12-08 Chris Ahlstrom * Seq66qt5/Makefile.in, TODO, contrib/scripts/configure-clang, libseq66/src/midi/midibase.cpp, libseq66/src/util/ring_buffer.cpp, libsessions/src/nsm/nsmbase.cpp: More fixing warnings, also liblo clang tweak. * Seq66qt5/Makefile.am, TODO, libseq66/src/util/strfunctions.cpp, libsessions/src/nsm/nsmbase.cpp, m4/ax_have_qt_clang.m4, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack_info.cpp: Notes and warning fixes for clang-16 build. 2023-12-07 Chris Ahlstrom * TODO, libseq66/include/midi/midifile.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midifile.cpp, libsessions/include/nsm/nsmclient.hpp, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixing sysex in progress, clang-12 found more warnings re issue #124. * TODO, bootstrap, contrib/midi/README, contrib/scripts/configure-clang, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midifile.cpp, seq_portmidi/include/midibus_pm.hpp: Fixed more errors/warnings uncovered by clang-16 for ussue #124. 2023-12-06 Chris Ahlstrom * : commit cebd4eac4e22cbd8c5e96d5f2f741bf8512382dc Author: Chris Ahlstrom Date: Wed Dec 6 11:30:34 2023 -0500 2023-12-06 ahlstrom * ChangeLog, TODO, doc/latex/tex/patterns_panel.tex, libseq66/src/cfg/usrsettings.cpp: Safety check-in of minor changes. * LICENSE, NEWS, README.md, RELNOTES, VERSION, configure, configure.ac, data/license.text, data/readme.text, data/readme.windows, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Set up for next version. 2023-12-05 Chris Ahlstrom * README.md: Release Notes for Seq66 v. 0.99.11 2023-12-05 - Added 8 more ui-palette entries, total of 32. - Added display of the pattern input bus (if present) in the grid slot. - Moved style-sheet options from 'usr' to the 'rc' file. - Fixed errors setting style-sheet, palette, and mutes file in Preferences / Session. Now indicates when exit is needed. - Added mute-group label ("MG") to main window. - Fixing various playlist errors: - PPQN setting issue causing slow/fast playback. Cannot display 120 PPQN well, fix too intrusive at this time. - Segfaults due to not stopping playback before loading the next song, or basing calculations on missing values. - Embarassing fixes: - The massive botch of the Set Master tab. - More Mutes tab fixes, including raising modify flag. - App exiting unceremoniously if "quiet" is set. - Issue in Song zoom with 1920 PPQN. - Odd bug breaking MIDI-control-out (display). - Preventing long redundant start-up error messages. - Solo feature. Unsolo before starting another solo. - Queue and keep-queue. - Not saving record-by-channel. Fixed record-by-channel. - Not modifying the song when pattern measures is changed. - Breakage of stopping song play at the end of song. - Bug in event-editor initialization. - Not applying note-length setting to step-edit. - Added a pre-made MIDI file to use with record-by-channel. - Added ways to toggle recording of multiple patterns. - Added a record-by-buss feature. - Can now paste a pattern into a new or another loaded MIDI file. - When loading a MIDI file, the file dialog defaults to the last-used directory. Fixes made to this feature. - Improved copy/paste for screen-sets. - Added optional parameter to --priority option. - Focus is now set immediately to seqroll and perfroll. - --verbose now shows playlist actions on the console and prevents daemonization and logging to a file. - Replaced the --inspect option with --session-tag for easy selection of another setup specified in sessions.rc. Also added the SEQ66_SESSION_TAG environment variable. - Added showing program changes in slot button. - Added showing text events in the data pane and all text events in the main Session tab. Fixed its Save Info button. - Implemented "menu-mode" automation. It duplicates the function of the hide/show button, to toggle between hiding some main window controls and the main menu, and showing them. See NEWS for more details. * FDL.txt, GPL.txt, LGPL.txt, LICENSE, README.md, RELNOTES, TODO, VERSION, configure.ac, data/readme.text, data/readme.windows, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/cfg/playlistfile.cpp, libseq66/src/sessions/smanager.cpp, nsis/Seq66Constants.nsh, nsis/build_release_package.bat, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui: Ready for next version, more for issue #123. * Seq66qt5/seq66qt5.cpp, TODO: Trying setuid root code, no go. 2023-12-04 Chris Ahlstrom * LICENSE, NEWS, README.md, contrib/git/gitconfig, doc/latex/tex/first_start.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/references.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui: Fixes to issue #122 and issue #123 and tweaks to Preferences labels and documentation. * TODO, doc/latex/tex/recording.tex, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/rtmidi_types.cpp: Refinements to JACK latency checks and record-by handling. 2023-12-02 Chris Ahlstrom * RELNOTES, TODO, VERSION, configure.ac, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/recording.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qsmainwnd.cpp: Getting near to the next release. 2023-12-01 Chris Ahlstrom * README.md, TODO, doc/latex/tex/sessions.tex, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, man/seq66.1, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Add SEQ66_SESSION_TAG env variable, enhanced inbus processing and display. 2023-11-30 Chris Ahlstrom * README.md, Seq66qt5/seq66qt5.cpp, TODO, doc/latex/tex/alsa.tex, doc/latex/tex/configuration.tex, doc/latex/tex/sessions.tex, libseq66/include/play/performer.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed last-used-dir, in-bus recording, more to fix though. 2023-11-29 Chris Ahlstrom * README.md, TODO, contrib/scripts/jackctl, libseq66/include/play/sequence.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp: Fixed event editor init bug, applying note length in step edit. 2023-11-28 Chris Ahlstrom * README.md, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/recording.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qslivegrid.cpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/rtmidi_types.cpp: Record-by-buss/channel fixed, documented, and re-enabled some code for JACK latency testing. 2023-11-27 ahlstrom * data/linux/qseq66.rc, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.h, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Added setting for record-by-buss. * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsmainwnd.cpp: Multi-pattern record toggling works. * README.md, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/first_start.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/recording.tex, doc/latex/tex/setmaster.tex, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, resources/pixmaps/rec_ex_buss.xpm, resources/pixmaps/rec_ex_channel.xpm, resources/pixmaps/rec_ex_normal.xpm, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Added menu-mode automation and start of multi-pattern record toggling. 2023-11-25 ahlstrom * README.md, TODO, contrib/midi/README, doc/latex/tex/pattern_editor.tex, doc/latex/tex/sessions.tex, libseq66/include/cfg/scales.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_portmidi/src/portmidi.c, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/palettefile.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsessionframe.cpp: Fallout from an attempt to display files with 120 PPQN. 2023-11-23 ahlstrom * README.md, TODO, libseq66/include/midi/event.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/event.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_portmidi/src/portmidi.c, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qslivegrid.cpp: Added showing all meta text events in the data pane. 2023-11-22 Chris Ahlstrom * README.md, TODO, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqroll.cpp: Added Mini Play spreadsheet and showing Program Change events in the grid slot. * : Updated 16-patterns MIDI file. * README.md, TODO, data/midi/README, doc/latex/tex/configuration.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/play/sequence.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qsappinfo.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Display input bus in slot, fixed record-by-channel, fixed measure-count modify, open-file defaults to last-used folder. 2023-11-18 ahlstrom * README.md, TODO, libseq66/include/ctrl/midicontrolin.hpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp: Keep queue now works. 2023-11-16 Chris Ahlstrom * README.md, TODO, contrib/gvim.rc, contrib/notes/snapshots.text, data/samples/perstfic-66.palette, data/samples/perstfic-66.qss, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qloopbutton.cpp: More progress on grid Solo and on plain Replace. 2023-11-14 Chris Ahlstrom * TODO, contrib/notes/snapshots.text, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed grid-solo partly, need to restore patterns and fix one-shot solo/replace. 2023-11-13 Chris Ahlstrom * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmaster.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsmainwnd.cpp: Tweak display of active set count, fixed clear/double mod bugs, redundant port msgs, mutes mod bug, set mod bug. 2023-11-12 Chris Ahlstrom * README.md, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp: Beefing up the priority option. * README.md, TODO, doc/latex/tex/menu.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qsmainwnd.cpp: Enhanced copy/paste of patterns and screensets. 2023-11-11 ahlstrom * TODO, doc/latex/tex/setmaster.tex, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsetmaster.ui, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qsetmaster.cpp: Set Master almost complete and documented. * TODO, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/forms/qsetmaster.ui, seq_qt5/src/qsetmaster.cpp: Got Set Master working with different approach, more refinements needed. 2023-11-10 Chris Ahlstrom * TODO, libseq66/include/play/screenset.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/src/qsetmaster.cpp: Nope, they now crash, Up/Down enabled only with --investigate option. * README.md, TODO, libseq66/include/play/screenset.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/seq.cpp, libseq66/src/play/setmaster.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qsetmaster.cpp: Fixed set-master up/down movement. 2023-11-08 Chris Ahlstrom * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsmainwnd.cpp: Renamed a couple performer accessors, removed code for external set-master, fixed bug in reading MIDI display-output controls. 2023-11-07 Chris Ahlstrom * TODO, libseq66/src/play/performer.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsetmaster.cpp: Fixed incorrect modifying of tune by selecting a mute group and fixed uneditable setmaster table. 2023-11-06 Chris Ahlstrom * README.md, TODO, configure, configure.ac, doc/latex/tex/live_grid.tex, doc/latex/tex/mutes.tex, include/config.h.in, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed NSM/OSC handling in configure.ac, added display of mute-group to main window. 2023-11-05 ahlstrom * TODO, contrib/tests/4x4/qseq66.rc, contrib/tests/4x4/qseq66.usr, data/samples/sessions.rc, doc/latex/tex/sessions.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/src/qt5nsmanager.cpp: Sessions.rc testing part 2. 2023-11-04 ahlstrom * TODO, data/samples/{session.rc => sessions.rc}, doc/latex/tex/configuration.tex, doc/latex/tex/sessions.tex, libseq66/src/cfg/cmdlineopts.cpp, man/seq66.1: Documented --session-tag, some testing. * TODO, libseq66/include/play/performer.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp: Sessions.rc testing part 1. 2023-11-03 Chris Ahlstrom * README.md, data/samples/session.rc, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/sessions/smanager.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1: Added -S/--session-tag option to command-line. 2023-11-02 Chris Ahlstrom * README.md, doc/latex/tex/configuration.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qt5nsmanager.cpp: Moved style-sheet options to 'rc' file. 2023-11-01 Chris Ahlstrom * README.md, TODO, libseq66/include/cfg/zoomer.hpp, libseq66/src/cfg/zoomer.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed playlist seqfault and zoom/ppqn issues. 2023-10-31 Chris Ahlstrom * README.md, Seq66cli/seq66rtcli.cpp, doc/latex/tex/configuration.tex, doc/latex/tex/headless.tex, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/os/daemonize.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/sessions/smanager.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Added support for showing playlist actions in the CLI using --verbose. * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp: Fixed seqfault in select next playlist song for both GUI and CLI. 2023-10-30 Chris Ahlstrom * README.md, Seq66cli/seq66rtcli.cpp, TODO, include/config.h.in, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qt5nsmanager.cpp: Autoplay working in GUI and CLI. 2023-10-29 ahlstrom * TODO, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseditoptions.cpp: Some fixes to playlist handling. 2023-10-28 ahlstrom * Seq66cli/seq66rtcli.cpp, Seq66qt5/seq66qt5.cpp, TODO, data/samples/GM_DD-11.drums, data/samples/ca_midi.playlist, data/seq66cli/seq66cli.ctrl, data/seq66cli/seq66cli.drums, data/seq66cli/seq66cli.mutes, data/seq66cli/seq66cli.playlist, data/seq66cli/seq66cli.rc, data/seq66cli/seq66cli.usr, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/smanager.cpp: Fixes for seq66cli and preparation for playlist testing of it. 2023-10-27 Chris Ahlstrom * TODO, data/linux/qseq66-alt-gray.palette, data/linux/qseq66-default.palette, data/linux/qseq66-gray.palette, data/linux/qseq66.palette, data/linux/qseq66.usr, data/samples/incrypt-66.palette, data/samples/perstfic-66.palette, data/samples/perstfic-66.qss, data/samples/qseq66-sample.palette, data/win/qpseq66.palette, doc/latex/tex/configuration.tex, doc/latex/tex/palettes.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/util/palette.hpp, libseq66/src/cfg/usrfile.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qstriggereditor.cpp: Many palette and palette-usage improvements. * README.md, RELNOTES, libseq66/include/cfg/rcsettings.hpp, libseq66/include/util/palette.hpp, libseq66/src/cfg/rcsettings.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseditoptions.cpp: Fixed and enhanced the session preferences tab. 2023-10-26 Chris Ahlstrom * TODO, configure, data/samples/incrypt-66.palette, data/samples/perstfic-66.palette, data/samples/perstfic-66.qss, libseq66/include/util/palette.hpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp: Expanded the palette by 8 more, may need to rename a couple for better usage. * NEWS, README.md, VERSION, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/util/palette.hpp, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/src/gui_palette_qt5.cpp: Version bump and palette expansion redux. 2023-10-25 Chris Ahlstrom * README.md: Minor README update. * README.md: Release Notes for Seq66 v. 0.99.10 2023-10-25 - The usual raft of humiliating bug-fixes: - Issue #117 Option to close pattern windows with esc key if enabled via a 'usr' option. - Issue #118 Made virtual ports ports enabled via an option. - Issue #119 "Quantized Record Active does not work" fixed. Live note/drum-mapping also fixed. - Fixed an egregious error in drawing notes in drum mode. - Fixed error in moving notes at PPQN != 192. - Fixed bug preventing a song with triggers stopping playback at end of song. Fixes related to playlist-handling. - MANY other undocumented minor fixes. - Implemented drag-and-drop of one MIDI file onto the Live grid. - Added the export of most project configuration files to another directory. - Multiple tempo events can be drawn in a line in the data pane and can be dragged up and down to change the tempo value. - If double-click enabled, can open/create a pattern in the song editor. - Many improvements and fixes to the Mutes tab. - Added a "grid mode" to toggle mutes by clicking in the Live Grid. The new default mute-group-selection mode keystroke is "_". - Opening the pattern editor reflects the recording mode and live pattern alteration selection (e.g. quantization). - The main time display works better with high PPQN. - Now scrolls automatically in time and note value to show the first notes in a pattern. - Live-grid record-mode and alteration are applied when active pattern is opened for edit. - Added more Qt style-sheets and UI palette items for more color control. - Tightened the file-name handling in Session preferences. Also see the NEWS, README.md, and TODO files. * ChangeLog, doc/latex/tex/midi_export.tex: Version 0.99.10 pending. * README.md, RELNOTES, TODO, VERSION, configure.ac, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/song_editor.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Version updating in progress. * contrib/DIR_COLORS, data/readme.text, data/readme.windows, doc/latex/tex/alsa.tex, doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, doc/latex/tex/first_start.tex, doc/latex/tex/headless.tex, doc/latex/tex/jack.tex, doc/latex/tex/kudos.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/mutes.tex, doc/latex/tex/palettes.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/playlist.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/references.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, doc/latex/tex/windows.tex, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Lotsa documentation fixes. 2023-10-24 Chris Ahlstrom * TODO, VERSION, configure.ac, data/linux/qseq66.palette, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp: Gearing up for 0.99.10. * TODO, doc/latex/tex/configuration.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qloopbutton.cpp: Grid slot font enlargement and doc updates. * README.md, RELNOTES, TODO, data/samples/perstfic-66.palette, data/samples/perstfic-66.qss, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qt5_helpers.cpp: Massively update Preferences / Session. 2023-10-22 ahlstrom * TODO, data/linux/qseq66-alt-gray.palette, data/linux/qseq66-default.palette, data/linux/qseq66-gray.palette, data/linux/qseq66.palette, data/samples/perstfic-66.palette, data/samples/perstfic-66.qss, data/samples/qseq66-sample.palette, data/win/qpseq66.palette, libseq66/include/util/palette.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qt5_helpers.cpp: Updates for even better palette support. 2023-10-21 ahlstrom * TODO, data/samples/incrypt-66.palette, data/samples/incrypt-66.qss, doc/latex/tex/configuration.tex, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp: Upgraded palette and qss handling, documentation. 2023-10-20 Chris Ahlstrom * TODO, contrib/tests/4x4/qseq66.palette, libseq66/include/cfg/rcsettings.hpp, libseq66/include/util/palette.hpp, libseq66/src/cfg/rcsettings.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqroll.cpp: Fixes to palette to draw gridlines properly. * README.md, RELNOTES, TODO, data/samples/incrypt-66.palette, doc/latex/tex/palettes.tex, libseq66/include/util/palette.hpp, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qstriggereditor.cpp: Expanded the UI palette to 24 colors. 2023-10-19 Chris Ahlstrom * README.md, RELNOTES, TODO, data/samples/incrypt-66.palette, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp: Fixed note movement issues at non-192 PPQN. * data/samples/incrypt-66.palette: Forgot the incrypt-66 palette file. * TODO, data/samples/incrypt-66.qss, data/samples/perstfic-66.qss, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qt5nsmanager.cpp: The palette and qss files are now also exported. 2023-10-18 Chris Ahlstrom * data/samples/incrypt-66.qss, data/samples/perstfic-66.qss, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui: Initial versions of incrypt- and perstfix-derived Qt style sheets. * README.md, RELNOTES, libseq66/include/cfg/cmdlineopts.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Finish project export option. 2023-10-17 Chris Ahlstrom * libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in, adding configuration export option. * TODO, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/windows.tex, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qmutemaster.cpp: Updated documentation and loop buttons. 2023-10-16 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.usr, doc/latex/tex/first_start.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/windows.tex, libseq66/src/cfg/usrfile.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes to Mute and Song/Live main button, new-pattern UI, and doc updates. 2023-10-15 ahlstrom * README.md, RELNOTES, TODO, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/include/qperfeditex.hpp, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qperfnames.hpp, seq_qt5/src/qperfeditex.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Tightening perf double-click, tighten recording toggling. 2023-10-14 ahlstrom * TODO, doc/latex/tex/patterns_panel.tex, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp: Added record loop mode and alteration to popup menu entry for record-toggle. 2023-10-13 Chris Ahlstrom * TODO, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp: Working on improving altered recording handling. 2023-10-11 Chris Ahlstrom * TODO, doc/latex/tex/menu.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/settings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, resources/pixmaps/n_rec_on.xpm, resources/pixmaps/t_rec_on.xpm, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qslivegrid.cpp: Fixed strncompare(), live-note-mapping, recording, added new-pattern options. 2023-10-10 Chris Ahlstrom * : Merge fix. 2023-10-09 ahlstrom * README.md, RELNOTES, TODO, doc/latex/tex/patterns_panel.tex, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Initial implementation of MIDI file drag-n-drop onto the Live grid. * README.md, TODO, doc/latex/tex/pattern_editor.tex, libseq66/src/midi/eventlist.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqeditframe64.cpp: Fixed auto-scrolling to the first notes in pattern editor. 2023-10-08 Chris Ahlstrom * doc/latex/tex/menu.tex, doc/latex/tex/mutes.tex, doc/latex/tex/seq66-user-manual.tex: Documenting virtual-port auto-enable. * TODO, data/linux/qseq66.rc, doc/latex/tex/menu.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/midibase.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/src/mastermidibus.cpp: For issue #118, added rc option to auto-enable virtual ports. 2023-10-07 ahlstrom * README.md, TODO, data/linux/qseq66.usr, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp: For issue #117, added 'usr' option to enable Esc key to close the pattern editor if play is stopped and not in paint mode. 2023-10-06 ahlstrom * TODO, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, resources/pixmaps/hide.xpm, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed issue #119 and some minor issues. * doc/latex/tex/mutes.tex, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed inadvertent MIDI modification altering mutes, add saving::none value. 2023-10-05 ahlstrom * README.md, TODO, doc/latex/tex/mutes.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/mutegroups.cpp, resources/pixmaps/hide.xpm, resources/pixmaps/show.xpm, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: More mutes fixes, show/hide icon fix, file-name robustness. 2023-10-03 Chris Ahlstrom * TODO, configure, configure.ac, doc/latex/tex/mutes.tex, include/config.h.in, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/play/mutegroups.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: Working on better mutemaster workflow in progress. 2023-10-02 ahlstrom * README.md, TODO, doc/latex/tex/mutes.tex, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp: Removed dead wood from mute-master. 2023-10-01 Chris Ahlstrom * : Updated mute-master tab screenshot. 2023-09-30 ahlstrom * README.md, doc/latex/tex/configuration.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qslivegrid.cpp: Added a 'mutes' option to the grid modes. * README.md, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp: Fixed song auto-stop feature, fixed mute-master trigger mode. 2023-09-29 ahlstrom * README.md, TODO, data/linux/qseq66.palette, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/song_editor.tex, doc/latex/tex/windows.tex, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/include/qperfeditex.hpp, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qperfeditex.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qsmainwnd.cpp: Tweaked palette, docs, finish song-track double-click feature. 2023-09-28 Chris Ahlstrom * README.md, TODO, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Adding spawning pattern editors from song editor by double-click in progress. * README.md, libseq66/src/midi/eventlist.cpp, seq_qt5/include/qscrollmaster.h, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqeditframe64.cpp: Scroll to first note feature seems to work. 2023-09-27 Chris Ahlstrom * TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qscrollmaster.h, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp: Pattern editor scroll-to-note, vertical works, horizontal goes to end of pattern, in progress. * README.md, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqdata.cpp: Finalized and documented tempo drawing in the data pane. 2023-09-26 Chris Ahlstrom * README.md, TODO, libseq66/include/play/sequence.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/event.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqdata.hpp, seq_qt5/src/qseqdata.cpp: Added potential feature to draw tempos in data pane. * TODO, doc/latex/tex/pattern_editor.tex, libseq66/src/midi/event.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qt5nsmanager.cpp: Got tempo drag to work, but needs modify status set. 2023-09-25 Chris Ahlstrom * ChangeLog, NEWS, README.md, RELNOTES, TODO, VERSION, configure, configure.ac, data/readme.text, data/readme.windows, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/midi/calculations.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Updated version info, tweaked main time display. 2023-09-24 Chris Ahlstrom * README.md, RELNOTES, TODO, VERSION, configure.ac, data/readme.text, data/readme.windows, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/sessions/smanager.cpp, nsis/build_release_package.bat: Release Notes for Seq66 v. 0.99.9 2023-09-24 - Added an "Input Bus Routing" feature, where each pattern can be set to receive events from a given input buss. Selectable from - Refactored and extended zoom support in the song/pattern editors. - Can now select a line in the data pane and grab a handle to change its value. - Implemented automation for BBT/HMS toggling, FF/Rewind, Undo/Redo, Play-set Copy/Paste. - Added more seqroll keystokes. Enabled Esc to exit paint mode if not playing. Added keystroke zoom handling to additional panes. - Added a show-hide build option to allow for a very small window. - Added HTML help files to data/share/doc/info for installation and viewing. Help for hard-wired panel keystrokes. - The usual raft of humiliating bug-fixes: - Fixed nasty segfault opening new file with Editor tab open. - Fixed port-mapping Remap and Restart not restarting Seq66. - Fixed years-long bug in detecting Note-related events. - Fixed error in "quiet" startup that would cause immediate exit. - Fixed error in copying info files in the NSIS build. - Fixed startup error in portmidi with missing ports; however, still broken in the Windows build. See TODO. Read the NEWS, README.md, and TODO files. 2023-09-24 Chris Ahlstrom * RELNOTES: Updated RELNOTES. 2023-09-23 ahlstrom * README.md, libseq66/include/midi/calculations.hpp, libseq66/include/midi/event.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, seq_qt5/src/qseqdata.cpp: Fixed bugs in note detection, attempted to allow tempo dragging. 2023-09-22 Chris Ahlstrom * TODO, data/linux/qseq66.usr, doc/latex/images/.gitignore, doc/latex/images/main-menu/edit/.gitignore, doc/latex/images/main-menu/file/.gitignore, doc/latex/images/main-menu/help/.gitignore, doc/latex/images/main-window/.gitignore, doc/latex/images/slot-menu/.gitignore, doc/latex/images/tabs/edit/.gitignore, doc/latex/images/tabs/events/.gitignore, doc/latex/images/tabs/live/.gitignore, doc/latex/images/tabs/mutes/.gitignore, doc/latex/images/tabs/playlist/.gitignore, doc/latex/images/tabs/sets/.gitignore, doc/latex/images/tabs/song/.gitignore, doc/latex/tex/configuration.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qsmainwnd.cpp: Doc update, time-display refinements/fixes, removed redundant BBT/HMS button. * README.md, TODO, doc/latex/tex/alsa.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: Added configurable time-display colors. 2023-09-21 Chris Ahlstrom * INSTALL, doc/latex/tex/menu.tex, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.h, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp: Updated show/hide button, session log-file handling. * TODO, doc/latex/tex/event_editor.tex, doc/latex/tex/sessions.tex, resources/pixmaps/hide.xpm, resources/pixmaps/show.xpm, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Added optional show/hide button to compress main window. 2023-09-20 Chris Ahlstrom * README.md, TODO, doc/latex/tex/event_editor.tex, doc/latex/tex/first_start.tex, doc/latex/tex/menu.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/references.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/pmlinuxalsa.c, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/rtmidi_info.cpp: Finished input routing for ALSA/JACK/Portmidi, major user manual update. 2023-09-19 Chris Ahlstrom * README.md, TODO, libseq66/include/ctrl/automation.hpp, libseq66/include/play/performer.hpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qslivegrid.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Added record toggle for hot-keys, disabled input routing in ALSA and in Windows. 2023-09-18 Chris Ahlstrom * libseq66/include/play/sequence.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed issues with saving in-bus settings, added bussoverride display to Preferences. 2023-09-17 ahlstrom * libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/include/qslivebase.hpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp: Event buss routing enabled, needs testing. * data/share/doc/info/seqroll_keys.html, libseq66/include/midi/event.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/songsummary.cpp, seq_qt5/src/qperfroll.cpp: Added macroed-out code for routing input by buss number. 2023-09-15 Chris Ahlstrom * TODO, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qseditoptions.cpp: Interim check-in, mute-master errors fixed, spurious bg recording notes from Launchpad. 2023-09-14 Chris Ahlstrom * libseq66/src/midi/midifile.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp: Still tweaking weak background recording. 2023-09-13 Chris Ahlstrom * TODO, doc/latex/tex/menu.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/play/performer.hpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, resources/pixmaps/metro_on.xpm, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp: Fixing broken background recording in progress. * TODO, data/share/doc/info/common_keys.html, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qt5nsmanager.cpp: Minor fixes to startup messaging and docs. 2023-09-12 Chris Ahlstrom * TODO, data/share/doc/info/automation_keys.html, data/share/doc/info/common_keys.html, data/share/doc/info/mute_group_keys.html, data/share/doc/info/pattern_hotkeys.html, data/share/doc/info/seqroll_keys.html, data/share/doc/info/songroll_keys.html, doc/latex/tex/defaultkeys.tex, doc/latex/tex/kbd_mouse.tex, include/config.h.in, libseq66/include/cfg/zoomer.hpp, libseq66/src/cfg/zoomer.cpp, seq_qt5/forms/qsappinfo.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qsappinfo.hpp, seq_qt5/include/qscrollmaster.h, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qsappinfo.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qstriggereditor.cpp: Tweaked zoom scrolling, update keys HTML documentation. * README.md, TODO, configure.ac, data/share/doc/info/common_keys.html, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qt5nsmanager.cpp: Fixes to edit options and error reporting. 2023-09-11 Chris Ahlstrom * NEWS, README.md, RELNOTES, TODO, seq_qt5/include/qseqbase.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed removing modified marker from seqedit after saving. * TODO, libseq66/src/cfg/zoomer.cpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qstriggereditor.cpp: Zoom and alignment fixes in place. 2023-09-10 ahlstrom * README.md, TODO, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Zoom refactoring works except for 0 reset in perfedit. 2023-09-09 ahlstrom * contrib/code/ring_buffer.hpp, libseq66/include/cfg/zoomer.hpp, libseq66/include/play/mutegroups.hpp, libseq66/src/cfg/settings.cpp, seq_qt5/include/qbase.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/src/qbase.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Seq horizontal zoom works, needs cleanup, Perf zoom hangs. 2023-09-08 Chris Ahlstrom * libseq66/include/Makefile.in, libseq66/src/Makefile.in: Added zoomer to makefiles. * libseq66/include/Makefile.am, libseq66/include/cfg/zoomer.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/cfg/zoomer.cpp: Built zoomer class. * libseq66/include/cfg/settings.hpp, libseq66/include/cfg/zoomer.hpp, libseq66/include/midi/calculations.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/zoomer.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/midi_vector_base.cpp, seq_qt5/include/qeditbase.hpp: Added a not-yet-used zoom calculation module. * seq_qt5/include/qeditbase.hpp, seq_qt5/src/qbase.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqtime.cpp: Disabled zoom-expansion hack pending refactoring of zoom. 2023-09-07 Chris Ahlstrom * TODO, libseq66/include/cfg/usrsettings.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qseqdata.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseqdata.cpp: Working on expanded zoom-in hack. * README.md, RELNOTES, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp: Fixed Editor tab segfault, grab-handles solidified. 2023-09-06 Chris Ahlstrom * README.md, TODO, libseq66/include/midi/event.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqdata.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp: In regard to issue #115, got a grab handle implemented in the data pane, needs more testing. 2023-09-05 Chris Ahlstrom * TODO, doc/latex/tex/references.tex, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqdata.hpp, seq_qt5/src/qseqdata.cpp: Laying groundwork for data event grab handles. * README.md, data/linux/qseq66.ctrl, data/samples/nanomap.ctrl, doc/latex/tex/configuration.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/song_editor.tex, libseq66/include/ctrl/opcontrol.hpp, libseq66/include/midi/calculations.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Updated keystroke automation and naming. 2023-09-04 ahlstrom * libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/src/qt5nsmanager.cpp: Fixed another bug in port-map restart. * README.md, Seq66qt5/seq66qt5.cpp, TODO, data/samples/nanomap.ctrl, data/share/doc/info/common_keys.html, doc/latex/tex/pattern_editor.tex, doc/latex/tex/song_editor.tex, libseq66/include/os/daemonize.hpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qt5nsmanager.cpp: Fixed port-map restart and added Esc of paint mode. 2023-09-03 ahlstrom * README.md, TODO, data/samples/nanomap.ctrl, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/play/performer.hpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseqeditframe64.cpp: Added most of the rest of automation controls, much testing needed. 2023-09-02 ahlstrom * README.md, TODO, data/samples/nanomap.ctrl, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Updated nanomap.ctrl, add L/R automation. * contrib/notes/key-maps-dump.text, contrib/notes/midi-control-in.text, doc/latex/tex/configuration.tex, libseq66/src/ctrl/keymap.cpp: Documentation and debug code inspired by a poor ctrl-file edit. 2023-09-01 Chris Ahlstrom * data/linux/qseq66-lp-mini-alt.ctrl, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qsmainwnd.cpp: Added BBT/HMS automation and live repitch, to test. * data/share/doc/info/common_keys.html, data/share/doc/info/seqroll_keys.html, libseq66/include/play/notemapper.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/metro.cpp, libseq66/src/play/notemapper.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp: Seqroll keystroke upgrades, note-mapper upgrade. 2023-08-31 Chris Ahlstrom * TODO, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qbase.hpp, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qseqtime.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp: Recording refactoring basically done. 2023-08-30 Chris Ahlstrom * data/Makefile.in, libseq66/include/midi/calculations.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.hpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/seq66_features.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp: Refactoring recording type and style, needs rigorous testing. 2023-08-29 ahlstrom * data/share/doc/info/common_keys.html, data/share/doc/info/seqroll_keys.html, data/share/doc/info/songroll_keys.html, libseq66/src/seq66_features.cpp, libseq66/src/sessions/clinsmanager.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp: Update keys htmls and added reading of /etc/issue. 2023-08-28 Chris Ahlstrom * README.md, RELNOTES, TODO, configure, data/Makefile.am, data/readme.text, data/readme.windows, data/share/doc/info/automation_keys.html, data/share/doc/info/common_keys.html, data/share/doc/info/seqroll_keys.html, data/share/doc/info/songroll_keys.html, doc/latex/tex/menu.tex, include/config.h.in, libseq66/include/cfg/settings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/play/performer.cpp, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_qt5/forms/qsappinfo.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qsappinfo.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsappinfo.cpp: Version bump, added some HTML help for keys with installation script updates. 2023-08-27 Chris Ahlstrom * NEWS, README.md, VERSION, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Prep for 0.99.9. 2023-08-27 Chris Ahlstrom * ChangeLog, README.md, RELNOTES, VERSION, configure.ac, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/configuration.tex, doc/latex/tex/pattern_editor.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Release Notes for Seq66 v. 0.99.8 2023-08-27 - Issue #112: A new pattern now displays in the MIDI controller. - Issue #114: Adding display of shortcut keys to tool tips. - Added a Pattern tab to Edit / Preferences for more settings. - Added automation for the main window Loop L/R button. - Fixed seqroll drawing errors introduced in adding time-sig support. - Fixed incomplete data-pane refresh in scrolling with arrow-keys. - Fixed not setting up SIGINT, which prevented a proper shutdown. - Fixed a couple corrupted sample *.mid files. - Changing playlist setting enables Session Restart button. - Removed coloring of record-style and -mode buttons. Added coloring of event-editor "Store" button to denote saving is needed. - Refactoring quantization alterations for future upgrades. Added an option to jitter the notes in the seqroll. - Enforced that configuration files are stored in the "home" directory. - The usual raft of humiliating bug-fixes. See README.md. Read the NEWS, README.md, and TODO files. 2023-08-26 ahlstrom * TODO, seq_qt5/forms/qsappinfo.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsappinfo.cpp, seq_qt5/src/qsmainwnd.cpp: Added basic Help / App Keys command. * TODO, doc/latex/tex/menu.tex, doc/latex/tex/seq66-user-manual.tex: Updating some diagrams, noting a PPQN issue to be fixed. 2023-08-25 ahlstrom * doc/latex/tex/menu.tex, libseq66/src/midi/calculations.cpp, libseq66/src/play/sequence.cpp: Fixed randomization, documented it. * TODO, libseq66/include/midi/calculations.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/event.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Tried uniform_int_distribution, macroed out because it works no better than rand(). * README.md, RELNOTES, TODO, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Added Edit / Preferences / Pattern tab. 2023-08-24 Chris Ahlstrom * RELNOTES, TODO, include/config.h.in, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Fixed playlist file-name issue and laid ground for new Pattern preferences tab. * README.md, RELNOTES, TODO, VERSION, configure.ac, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseditoptions.cpp: Enforced HOME directory for configuration files. 2023-08-23 Chris Ahlstrom * TODO, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qt5_helpers.cpp: Added tooltips to filenames in Preference / Session. 2023-08-22 ahlstrom * README.md, libseq66/src/play/performer.cpp, seq_qt5/src/qseqeditframe64.cpp: Saving rc file on a restart when remapping ports, reloading the event menus when recording stops. * libseq66/include/cfg/configfile.hpp, libseq66/include/play/portslist.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qseqeditframe64.cpp: Fixed bugs in record button, port saving, and appearance. * seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in: Checking in modified Makefile.in files. * libseq66/include/cfg/settings.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/Makefile.am, seq_qt5/forms/qsappinfo.ui, seq_qt5/include/Makefile.am, seq_qt5/include/qsappinfo.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/qsappinfo.cpp, seq_qt5/src/qsbuildinfo.cpp: Added qsappinfo and file_read() for the future. 2023-08-21 Chris Ahlstrom * README.md, TODO, libseq66/include/seq66_features.hpp, libseq66/src/seq66_features.cpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Pane focus, Store button coloring, and QMenu refinement. * TODO, contrib/code/qsliveframe.cpp, contrib/code/qsliveframe.hpp, contrib/code/qsliveframe.ui, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qt5_helpers.cpp: Refining new QAction creation. 2023-08-20 ahlstrom * README.md, TODO, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qt5_helpers.cpp: Tightening the seqedit note-alteration code, still some bugs. 2023-08-19 ahlstrom * README.md, TODO, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_rtmidi/src/midi_jack_data.cpp: Got jitter working, added usr setting, but see TODO. 2023-08-18 ahlstrom * libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qloopbutton.cpp: Refining the event-randomization functions. 2023-08-17 ahlstrom * libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Fixed not quantizing Note Off and making Note Off at least a snap away from quantized Note On. * INSTALL, README.md, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qslivegrid.cpp: Refactored quantization/tighten, but bug prevents quantizing Note Offs. 2023-08-16 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp: Refactoring quantized recording, beware. * README.md, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/src/play/performer.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qslivegrid.cpp: Enabled restart when playlist changes. 2023-08-15 Chris Ahlstrom * README.md, TODO, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Fixed some secondary errors found working on other issues. * TODO, libseq66/src/play/performer.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qseqeditframe64.cpp: Work on issues #112 and #114. 2023-08-14 Chris Ahlstrom * TODO, doc/latex/tex/patterns_panel.tex, libseq66/include/ctrl/automation.hpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: More tool-tip upgrades for issue #114, next do the pattern editor. 2023-08-13 ahlstrom * README.md, TODO, libseq66/include/ctrl/automation.hpp, libseq66/include/play/performer.hpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Add loop L/R automation, more work on issue #114. 2023-08-12 ahlstrom * libseq66/include/ctrl/keycontainer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontainer.cpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Work for issue #114, adding automation key-name lookup to add key-name to tool-tips. 2023-08-09 Chris Ahlstrom * README.md, TODO, libseq66/include/util/filefunctions.hpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5nsmanager.cpp: Fixed seqroll drawing errors and SIGINT setup. 2023-08-06 ahlstrom * TODO, data/linux/qseq66-azerty-fr.keymap, data/linux/qseq66-qwerty-us.keymap, data/linux/qseq66.playlist, data/linux/qseq66.usr, data/samples/ca_midi.playlist: Sample config file updates. 2023-07-31 Chris Ahlstrom * libseq66/include/util/strfunctions.hpp, libseq66/src/util/strfunctions.cpp: Backported string_to_float() function. 2023-07-28 ahlstrom * libseq66/include/util/strfunctions.hpp, libseq66/src/util/strfunctions.cpp: Backported hanging_word_wrap(). 2023-07-27 Chris Ahlstrom * libseq66/include/util/strfunctions.hpp, libseq66/src/util/strfunctions.cpp: Backported the strfunction word_wrap() function. 2023-07-23 Chris Ahlstrom * contrib/vim-syntax/cpp.vim, libseq66/include/os/daemonize.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/os/daemonize.cpp, libseq66/src/util/filefunctions.cpp: Backport a filefunction function. 2023-07-19 Chris Ahlstrom * configure, configure.ac, distros/README, {arch => distros/arch}/README, {arch => distros/arch}/package/PKGBUILD, {arch => distros/arch}/package/PKGBUILD-alt, {debian => distros/debian}/README, {debian => distros/debian}/bash.rc, {debian => distros/debian}/changelog, {debian => distros/debian}/compat, {debian => distros/debian}/control, {debian => distros/debian}/copyright, {debian => distros/debian}/gbp.conf, {debian => distros/debian}/install, {debian => distros/debian}/libseq66-dev.install, {debian => distros/debian}/libseq66.install, {debian => distros/debian}/menu, {debian => distros/debian}/rules, {debian => distros/debian}/seq-rtmidi-dev.install, {debian => distros/debian}/seq-rtmidi.install, {debian => distros/debian}/seq66.desktop, {debian => distros/debian}/seq66.install, {debian => distros/debian}/seq66.xpm, {debian => distros/debian}/source/format, {debian => distros/debian}/watch, distros/fedora/README, distros/fedora/seq66.spec, distros/nixos/README, distros/nixos/default.nix, include/config.h.in, libseq66/src/seq66_features.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/src/qloopbutton.cpp: Prep for 0.99.8, added more distro packages. * ChangeLog, RELNOTES, VERSION, configure.ac, contrib/notes/install-directories.text, data/share/doc/tutorial/home.html, data/share/doc/tutorial/tutorial_first_startup.html, data/win/qpseq66.rc, doc/README, doc/latex/tex/first_start.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/windows.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/cfg/settings.cpp, nsis/README, nsis/Seq66Constants.nsh, nsis/build_release_package.bat: Release Notes for Seq66 v. 0.99.7 2023-07-19 This file lists __major__ changes in version 0.99.7 - Issue #110 follow-ons: - Fixed saving tempo (BPM) in Windows when changed from main window. Caused by mixing a long and size_t; messed up in Windows builds. - Issue #111 follow-ons: - Fixed initial time-signature drawing in data pane. - Fixed errors in inserting a time-signature. - Added a pulse (tick) calculator to iterate through time-signatures. - Fixed an important port-translation bug in output port-mapping. - Revamped the Playlist tab, as it was confusing and very buggy. - Added auto-play and auto-advance to play-lists. - Issue #102: Added Windows key-mapping to fix processing "native virtual" keys, such as the arrow keys. Read the NEWS, README.md, and TODO files. * README.md, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/windows.tex, libseq66/src/ctrl/keymap.cpp, libseq66/src/ctrl/winkeys.hpp: Doc updates and Windows/AZERTY keymap tweaks. 2023-07-18 Chris Ahlstrom * README.md, RELNOTES, contrib/notes/win-virtual-keys.text, libseq66/src/ctrl/keymap.cpp, libseq66/src/ctrl/winkeys.hpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qt5_helpers.cpp: Added Windows key-mapping module. 2023-07-17 Chris Ahlstrom * data/readme.text, data/readme.windows, data/win/win_midi.playlist, nsis/README, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_qt5/src/qt5_helpers.cpp: Windows installer updates and fixes. * README.md, TODO, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qsmainwnd.cpp: Minor playlist fixes. 2023-07-16 Chris Ahlstrom * INSTALL, TODO, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.h, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, resources/pixmaps/panic2.xpm, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp: Auto-advance seems to be perfected, let us pray. 2023-07-14 Chris Ahlstrom * README.md, RELNOTES, TODO, doc/latex/tex/playlist.tex, libseq66/include/play/playlist.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qplaylistframe.cpp: Auto-advance almost working. 2023-07-13 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/src/qplaylistframe.cpp: Auto-play improved, added support for play-list auto-advance. 2023-07-12 Chris Ahlstrom * README.md, RELNOTES, configure.ac, doc/latex/tex/playlist.tex, include/config.h.in, libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp: Added auto-play support to playlists, needs some tinkering. * README.md, RELNOTES, TODO, libseq66/src/play/performer.cpp: Fixed a nasty bug in output port-mapping lookup. 2023-07-11 Chris Ahlstrom * TODO, data/samples/ca_midi.playlist, doc/latex/tex/configuration.tex, doc/latex/tex/playlist.tex, libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed handling MIDI file paths in playlists. 2023-07-10 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/src/qplaylistframe.cpp: Playlist UI tweaks, added list activation function. 2023-07-09 ahlstrom * TODO, libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: More playlist revamping, nearly done. 2023-07-08 ahlstrom * README.md, TODO, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: More playlist revamping, much more to come. 2023-07-07 Chris Ahlstrom * TODO, doc/latex/tex/first_start.tex, doc/latex/tex/windows.tex, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp: Starting improvement of Playlists tab. 2023-07-06 Chris Ahlstrom * README.md, RELNOTES, TODO, libseq66/include/midi/midifile.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed BPM saving error in Windows, issue #110. 2023-07-05 Chris Ahlstrom * README.md, libseq66/include/midi/calculations.hpp, libseq66/include/midi/editable_events.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/editable_events.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qstriggereditor.cpp: Issue #111 time-sig insertion solved. 2023-07-03 Chris Ahlstrom * README.md, TODO, libseq66/include/midi/calculations.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqtime.cpp: Some more fixes to time-signature analysis. 2023-07-01 Chris Ahlstrom * INSTALL, README.md, TODO, VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.h, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqtime.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qstriggereditor.cpp: Cleanup for next version of Seq66. * ChangeLog, RELNOTES, contrib/git/git.text, {nsis => contrib/scripts}/build_debug_code.bat, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/port_mapping.tex, nsis/README, nsis/build_release_package.bat, nsis/winddeploybruteforce.bat: Release Notes for Seq66 v. 0.99.6 2023-07-01 This file lists __major__ changes from version 0.99.6 - Issue #3 follow-ons: - Pattern editor panes stay in sync with the piano roll using the hjkl, arrow, and page keys. Scroll wheel works in the piano roll. - Issue #110 follow-ons: - Addition of Start menu entries for Windows. - Fixed access to the tutorial and manual. - data/readme & doc/tutorial files fixed for NSIS installer. - Fixed the saving of modified tempo changes. - Fixed changing note velocities in the pattern editor data pane. Improved velocity-change undo. - Fixed error preventing changing the "background" pattern. - Remaining issue: Building 32-bit (Windows XP) version on 64-bit Windows. - Issue #111: - Added support for editing, storing, and displaying time signatures in the pattern and event editors. - The first time-signature in a pattern becomes the main time signature of the pattern. - The data pane shows a time-signature as a simple fraction. - Changing the time signature if at time 0 is automatic. - Time signatures at later times are logged by setting the current time with a click in the top half of the time line, changing the beats and beat width, then clicking a time-sig log button. - Non-MIDI-standard beat-widths are supported as a Seq66-specific "event". - Fixed event filtering in the event (qstriggereditor) pane. - As time-signatures change, Seq66 adjusts the piano roll, time line (with measure counts), and event pane vertical lines. - Port-mapping prompts about port issues and allows an immediate remap-and-restart. - 'o' keystroke in seqroll toggles recording ('r' already taken). - Added a "quiet" option to not show startup message prompts. - A log-file is now created by default & kept under a megabyte. - Added the pattern port number to the Song Summary output. - A large number of fixes of unrelated issues. Read the NEWS, README.md, and TODO files. Working our assoff! // vim: sw=4 ts=4 wm=15 et ft=sh 2023-07-01 Chris Ahlstrom * README.md, doc/latex/tex/references.tex, seq_portmidi/src/pmwinmm.c: Minor doc updates. 2023-06-29 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, contrib/scripts/alsa.m4, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, m4/alsa.m4, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Updated alsa.m4 to avoid AC_TRY_COMPILE warnings on Arch Linux. * README.md, RELNOTES, VERSION, configure.ac, contrib/git/git.text, data/readme.text, data/readme.windows, data/testing/mapping-snippet.rc, data/testing/sixteen-ports-snippet.rc, doc/latex/tex/port_mapping.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/midi/businfo.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseqtime.cpp: Portmap fixes, date/doc updates, seqtime markers fixed. 2023-06-28 Chris Ahlstrom * README.md, TODO, data/readme.windows, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/references.tex, doc/latex/tex/windows.tex, libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibase.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, seq_portmidi/include/midibus_pm.hpp, seq_portmidi/include/pminternal.h, seq_portmidi/src/midibus.cpp, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_qt5/forms/qseditoptions.ui: Solidified Windows MIDI Mapper handling for issue #110. 2023-06-27 Chris Ahlstrom * TODO, data/linux/qseq66.rc, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/windows.tex, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseqdata.cpp: Minor tweaks and clean-up. 2023-06-26 Chris Ahlstrom * README.md, TODO, data/readme.text, doc/latex/tex/menu.tex, doc/latex/tex/port_mapping.tex, libseq66/include/midi/midibus_common.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/portslist.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp, libseq66/src/play/songsummary.cpp, nsis/build_release_package.bat, seq_portmidi/src/midibus.cpp, seq_portmidi/src/portmidi.c, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qseqtime.cpp: Added unavailable flag to port handling and mapping. 2023-06-24 ahlstrom * README.md, RELNOTES, TODO, doc/latex/tex/midi_formats.tex, doc/latex/tex/pattern_editor.tex, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Added time-sig display to timeline. 2023-06-23 Chris Ahlstrom * README.md, TODO, doc/latex/tex/menu.tex, libseq66/include/cfg/usrsettings.hpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqbase.hpp, seq_qt5/include/qseqeditex.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Improved modification management, added grid space setting to UI. 2023-06-22 Chris Ahlstrom * INSTALL, README.md, RELNOTES, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/calculations.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.h, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqtime.cpp: Enabled time-sig drawing in seq66_features, upgraded measure calculation. 2023-06-21 Chris Ahlstrom * README.md, TODO, libseq66/include/midi/calculations.hpp, libseq66/include/midi/editable_events.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqtime.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5_helpers.cpp: Added time-sig drawing to qseqtime and qstriggereditor, disabled by SEQ66_TIME_SIG_DRAWING in sequence.hpp, time-sig fixes galore. 2023-06-18 ahlstrom * README.md, RELNOTES, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp: Fixed setting up the time-sig log button. 2023-06-17 Chris Ahlstrom * README.md, TODO, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Refactored meta and time-sig detection code. 2023-06-16 Chris Ahlstrom * NEWS, README.md, TODO, VERSION, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/midi/calculations.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Fixes to editing time-signature in event editor. 2023-06-15 Chris Ahlstrom * TODO, doc/latex/tex/pattern_editor.tex, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqtime.cpp: More work on adding time signatures. 2023-06-14 Chris Ahlstrom * Seq66qt5/Seq66qt5.pro, TODO, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, nsis/winddeploybruteforce.bat, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp: Wash hands of 32-bit Windows for now, fixed and improvd time-sig handling. 2023-06-12 Chris Ahlstrom * Seq66qt5/Seq66qt5.pro, include/config.h.in, nsis/Seq66Constants.nsh, nsis/build_release_package.bat, nsis/winddeploybruteforce.bat, seq_qt5/src/qseqroll.cpp: Seemingly futile attempt at Win32 build on a Win64 machine. 2023-06-09 Chris Ahlstrom * README.md, TODO, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Support for setting pattern editor to beginning time-signature in place. 2023-06-08 Chris Ahlstrom * README.md, TODO, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_vector.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5nsmanager.cpp: Work on issue #111 improving time-signature support, in progress. 2023-06-07 ahlstrom * TODO, libseq66/src/os/shellexecute.cpp, libseq66/src/play/performer.cpp, nsis/Seq66Constants.nsh, nsis/build_release_package.bat, nsis/winddeploybruteforce.bat: Trying to get a Windows 32-bit version to deploy. 2023-06-04 Chris Ahlstrom * README.md, TODO, doc/latex/tex/menu.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qt5nsmanager.cpp: Added quiet option, improved control/display options. 2023-06-03 ahlstrom * libseq66/include/seq66_features.h, seq_portmidi/include/portmidi.h, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/pmlinux.c, seq_portmidi/src/pmmac.c, seq_portmidi/src/pmmacosxcm.c, seq_portmidi/src/pmutil.c, seq_portmidi/src/pmwin.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_portmidi/src/ptmacosx_mach.c: Investigated Windows portmidi free error, disabled incomplete sysex processing. 2023-06-02 Chris Ahlstrom * README.md, TODO, libseq66/src/play/performer.cpp: Tweaks for remote work. 2023-06-01 ahlstrom * README.md, TODO, VERSION, configure.ac, doc/latex/tex/alsa.tex, doc/latex/tex/event_editor.tex, doc/latex/tex/jack.tex, doc/latex/tex/menu.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qsmainwnd.cpp: Documentation of port-map prompts. 2023-06-01 Chris Ahlstrom * : commit d5070779858645f0ca8db0fab4a45dcdbf1f413e Author: Chris Ahlstrom Date: Thu Jun 1 12:27:45 2023 -0400 2023-05-31 ahlstrom * libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp: Improved reporting of MIDI driver errors. * README.md, TODO, doc/latex/tex/first_start.tex, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Made port-map inconsistencies raise a prompt for a potential remapping and restart. 2023-05-30 Chris Ahlstrom * TODO, data/share/doc/tutorial/faq.html, data/share/doc/tutorial/left-tree.html: Added a couple of FAQs to the tutorial. * README.md, TODO, data/license.text, data/readme.text, libseq66/include/os/shellexecute.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/os/shellexecute.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qseqdata.cpp: Fixed tutorial/manual access for issue #110. 2023-05-28 ahlstrom * README.md, Seq66qt5/Seq66qt5.pro, TODO, libseq66/include/util/filefunctions.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, nsis/Seq66Setup.nsi, seq66.pro: Added code to delete gigantic log file. 2023-05-27 Chris Ahlstrom * contrib/notes/install-directories.text, libseq66/src/os/daemonize.cpp: Fixed stdio rerouting and added Windows icons. 2023-05-26 Chris Ahlstrom * seq_qt5/src/qsmainwnd.cpp: Minor tweak to qsmainwnd. * Seq66qt5/Seq66qt5.pro, contrib/notes/install-directories.text, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, resources/icons/route66.xpm, resources/seq66_win.rc, seq66.pro, seq_qt5/src/qsmainwnd.cpp: Better app icon support in progress. 2023-05-26 ahlstrom * README.md, Seq66qt5/seq66qt5.cpp, contrib/notes/install-directories.text, data/linux/qseq66.usr, resources/seq66_win.rc, seq66.pro, seq_qt5/src/qsmainwnd.cpp: Interim check-in for Windows icon handling. * README.md, libseq66/include/seq66_features.h, libseq66/include/seq66_features.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/smanager.cpp: Making log-file usage more automatic, need to debug under Windows. * data/linux/qseq66.usr, doc/latex/tex/menu.tex, libseq66/include/cfg/settings.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/os/shellexecute.cpp, seq_qt5/src/qsmainwnd.cpp: Refactored the handling of the tutorial and manual. 2023-05-24 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, TODO, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/os/shellexecute.cpp, libseq66/src/play/sequence.cpp, libseq66/src/seq66_features.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Fixed issue #110 to handle changing background sequence. * TODO, seq_qt5/include/qscrollslave.h, seq_qt5/src/qscrollslave.cpp, seq_qt5/src/qseqeditframe64.cpp: Forwarding direction events from qscrollslave to qscrollmaster. 2023-05-23 Chris Ahlstrom * TODO, doc/latex/tex/pattern_editor.tex, seq_qt5/include/qscrollslave.h, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qscrollslave.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: More issue #3 work and tentative fix for main tempo change. * README.md, RELNOTES, TODO, VERSION, configure, configure.ac, contrib/git/git.text, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, nsis/README, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5_helpers.cpp: Added qscrollslave class, keeps seqedit panes in sync now, issue #3. 2023-05-20 Chris Ahlstrom * ChangeLog, README.md, RELNOTES, TODO, contrib/git/git.text, data/readme.text, data/readme.windows, doc/latex/tex/first_start.tex, include/config.h.in, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, nsis/README, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_debug_code.bat, nsis/build_release_package.bat: Release Notes for Seq66 v. 0.99.5 2023-05-20 This file lists __major__ changes from version 0.99.5 - Issue #110 Windows: Fixed many errors, added installer to this release, no more going to the sequencer64 repository. Can build NSIS installer in Windows now. - Fixed portmidi bugs in Linux and Windows, enhanced the device naming. - Greatly enhanced the event editor tab and added more events that can be view and modified. - Made port-mapping the default. At first startup the map exactly matches the existing ports; can be changed in the 'rc' file or Preferences dialog. Used the Edit / Preferences / MIDI Clock / Make Maps button to refresh the port setup when devices are added or removed. - Eliminated "missing ctrl" message at first startup. - Fixed port ID setting in midibus, and adding output flag for ALSA MIDI info. - Internal refactoring to regularize handling of the session/config directory between Linux and Windows. - Shows disabled/unavailable MIDI devices as grayed in various dropdowns. - Rearranged the Seq66 man pages more sensibly. Read the NEWS, README.md, and TODO files. Never-ending! 2023-05-20 Chris Ahlstrom * data/license.text, data/readme.text, data/readme.windows, nsis/README, nsis/Seq66Setup.nsi: Got 64-bit build/installer working. 2023-05-19 Chris Ahlstrom * VERSION, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Issue #110 is tentatively done, portfix branch. * TODO, doc/latex/tex/alsa.tex, doc/latex/tex/configuration.tex, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_portmidi/src/pmlinux.c, seq_portmidi/src/pmwin.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_portmidi/src/ptmacosx_cf.c, seq_portmidi/src/ptmacosx_mach.c: Eliminated calls to update_midi_buttons when just recording events, and nullified portmidi pointers after free(). 2023-05-18 Chris Ahlstrom * README.md, RELNOTES, doc/latex/tex/menu.tex, libseq66/include/play/performer.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_portmidi/src/pmwinmm.c, seq_qt5/include/qclocklayout.hpp, seq_qt5/include/qinputcheckbox.hpp, seq_qt5/include/qportwidget.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp: USB MIDI control/display works in Windows, issue with recording in Qt debugger. 2023-05-17 ahlstrom * README.md, TODO, data/readme.text, data/readme.windows, doc/latex/tex/first_start.tex, libseq66/include/play/performer.hpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Improved performer error reporting, and added ghosting of ports in dropdowns when they are no longer present. * TODO, data/readme.windows, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp: Finally got port handling almost airtight and playing tunes on Windows re issue #110. 2023-05-16 Chris Ahlstrom * NEWS, README.md, TODO, libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/portslist.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_rtmidi/include/midi_info.hpp: Big fix to synch the masterbus ports and port-maps when saving the 'rc' file. 2023-05-15 Chris Ahlstrom * libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/performer.cpp, seq_portmidi/src/pmlinuxalsa.c, seq_portmidi/src/pmutil.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c: More fixes for portmidi and port-mapping, still fails with Qsynth, Portmidi, with mapping off. 2023-05-14 Chris Ahlstrom * TODO, libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/play/performer.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, seq_portmidi/src/pmwinmm.c, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qseditoptions.cpp: Added code to display present-but-unavailable ports. 2023-05-13 ahlstrom * README.md, libseq66/include/util/basic_macros.h, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, seq_portmidi/include/midibus_pm.hpp, seq_portmidi/seq_portmidi.pro, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/midibus.cpp, seq_portmidi/src/pmlinuxalsa.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c: Enhancement and fixes to borken portmidi code re issue #110. 2023-05-11 Chris Ahlstrom * INSTALL, configure.ac, include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp: Working out config-file issues with normal and NSM usage. 2023-05-10 Chris Ahlstrom * README.md, TODO, contrib/git/git.text, doc/latex/tex/sessions.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, nsis/build_debug_code.bat, nsis/build_release_package.bat: Some successful tinkering for pathnames re issue #110. 2023-05-09 Chris Ahlstrom * TODO, data/license.text, data/readme.text, libseq66/include/seq66_features.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, libsessions/src/nsm/nsmclient.cpp, nsis/build_release_package.bat: Refactoring config/session directories for consistency, expect breakage for now. 2023-05-08 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.rc, libseq66/include/os/daemonize.hpp, libseq66/src/os/daemonize.cpp, libseq66/src/sessions/smanager.cpp, nsis/README, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_debug_code.bat, nsis/build_release_package.bat, nsis/x64.nsh, seq_qt5/forms/qseqeventframe.ui, seq_rtmidi/src/midi_alsa_info.cpp: Work on issue #110 in progress. 2023-05-07 ahlstrom * README.md, TODO, contrib/git/git.text, doc/latex/tex/configuration.tex, libseq66/include/midi/mastermidibase.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/sessions/smanager.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midibus.cpp: Fixed subtle bugs creating midi ports. 2023-05-06 ahlstrom * INSTALL, README.md, TODO, doc/latex/tex/port_mapping.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseditoptions.cpp: Enabled default port mapping, testing needed. 2023-05-05 Chris Ahlstrom * libseq66/src/midi/editable_event.cpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qsmainwnd.cpp: More progress on inserting meta text events. 2023-05-04 Chris Ahlstrom * README.md, doc/latex/tex/event_editor.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/scales.hpp, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midifile.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qslivegrid.cpp: Now able to insert Meta Text events in event editor. 2023-05-03 Chris Ahlstrom * TODO, contrib/git/git.text, doc/latex/tex/event_editor.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/sessions.tex, libseq66/include/cfg/scales.hpp, libseq66/include/midi/editable_event.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qseventslots.cpp: Event editing progress, added key-signature conversion functions. 2023-05-02 Chris Ahlstrom * doc/latex/tex/meta_events.tex, doc/latex/tex/midi_formats.tex, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qt5_helpers.cpp: Now can populate event combo based on event category. 2023-05-01 Chris Ahlstrom * configure, include/config.h.in, libseq66/include/midi/editable_event.hpp, libseq66/src/midi/editable_event.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qseventslots.cpp: Continuing work to add meta/text handling to event editor. 2023-04-30 Chris Ahlstrom * VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Initial version bump. * ChangeLog: Forget to update 0.99.4 Changelog. * README.md, RELNOTES, doc/latex/tex/sessions.tex, include/config.h.in: Version 0.99.4 issues fixed for #3, #48, #108, #109, and discovered issues. * libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qsessionframe.hpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp: Hidden work to support show meta text, in progress. 2023-04-29 ahlstrom * VERSION, configure.ac, contrib/git/git.text, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qsessionframe.cpp: Date bump, find-event code, fixes to qsessionframe form. * README.md, TODO, libseq66/include/midi/eventlist.hpp, libseq66/include/play/performer.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed mutes/session refresh, 256-char text limit, experimental meta-text enhancements started. 2023-04-28 Chris Ahlstrom * TODO, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qsessionframe.cpp: Future spinbox for song-info, need to fix 256-char limit. * libseq66/include/midi/midi_vector_base.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midi_vector.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qsessionframe.cpp: Fixed error writing meta text length to file. 2023-04-27 Chris Ahlstrom * README.md, TODO, contrib/midi/Carpet_of_the_Sun_karaoke_meta_text.text, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qslivegrid.cpp: Work in progress on adding song-info feature. 2023-04-26 Chris Ahlstrom * data/linux/qseq66.ctrl, libseq66/include/play/performer.hpp, libseq66/src/ctrl/keycontainer.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp: Modified default ctrl keystrokes. * README.md, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqroll.cpp: Fixed issues with note wrap-around and linking. 2023-04-25 Chris Ahlstrom * README.md, TODO, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qseqroll.cpp: Fixed issue #109 where exports lost event channels. * README.md, TODO, doc/latex/tex/event_editor.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/song_editor.tex, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp: Fixes to linking and drawing recorded notes. 2023-04-24 Chris Ahlstrom * README.md, seq_qt5/src/qsmainwnd.cpp: A potential fix to issue #108, was removing a widget after deleting. 2023-04-22 ahlstrom * README.md, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/pattern_editor.tex, libseq66/include/play/performer.hpp, libseq66/src/play/metro.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qscrollmaster.h, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqtime.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qstriggereditor.cpp: Mitigated issue #3 so that only the piano rolls can use the scroll wheel. 2023-04-20 Chris Ahlstrom * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qsbuildinfo.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/rtmidi.cpp: Revisited issue #48, fixed some minor issues, see README.md. 2023-04-19 ahlstrom * INSTALL, README.md, RELNOTES, contrib/scripts/qtctrun, libseq66/src/play/performer.cpp: Fixed showing port errors re MIDI control undefined. * contrib/git/git.text: Updated Git notes. * RELNOTES, RELNOTES.md, configure: Release Notes for Seq66 v. 0.99.4 Commit message This file lists __major__ changes from version 0.99.1 to 0.99.4. These notes are not complete, just trying to get it to work. * Version 0.99.4: * Issue #xyz. Expand-pattern functionality. * Previous changes: * Issue #40. Enhanced NSM handling and debugging. * Issue #44. Revisited to fix related additional issues. * Issue #93. Revisited to fix related open pattern-editor issues. * Issue #100. Partly mitigated. Added a custom JACK ringbuffer. * Issue #103. Some improvements to pattern loop-count. * Pull request #106. User phuel added checkmarks for active buss. * Issue #107. Expand-pattern functionality. * A raft of MIDI automation/display fixes. * Added reading/writing/displaying Meta textual events. * Improvements to playlist handling. * Fixes to mute-group handling. * Fixed the daemonization and log-file functionality. * Fixed broken "recent-files" feature. * Improved error reporting. * Fixed background sequence not displaying with linear-gradient brush. * Fixes to brushes; made the linear gradient the default. * Other minor fixes and documentation updates, including the manual. * Fixed partial breakage of pattern-merge function. * Fixed odd breakage of ALSA playback in release mode. * Fixed Stop button when another Master has started playback. * Shift-click on Stop button rewinds JACK transport when running as JACK Slave. * Display of some JACK server settings in Edit / Preferences. * Fixed handling of Ctrl vs non-Ctrl zoom keys in perfroll. * Event-dump now prompts for a text-file name. * Added linear-gradient compile-time option for displaying notes and triggers. Read the NEWS, README.md, and TODO files. Never-ending! * NEWS, README.md, RELNOTES.md, VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Stamping for version 0.99.4. Also need to figure out why the last multi-line commit message is a single line. * ChangeLog: Updated Changelog. * doc/latex/tex/configuration.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex: Merged portfix and updated documentation for version 0.99.3 * README.md, RELNOTES.md, TODO, VERSION, configure.ac, contrib/scripts/gdarkseq66, contrib/scripts/qtctrun, doc/latex/tex/port_mapping.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseditoptions.ui: Time to put version 0.99.3 to bed. 2023-04-18 Chris Ahlstrom * README.md, TODO, contrib/notes/launchpad.text, contrib/scripts/timid, data/linux/alsa_ports.rc, data/linux/jack_ports.rc, data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/launchpad_mini.tex, libseq66/include/midi/event.hpp, libseq66/src/midi/event.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseditoptions.cpp: Tweak mutes-test and added LaunchPad Mini macros. 2023-04-17 ahlstrom * data/linux/qseq66.usr, doc/latex/tex/configuration.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/src/qsmainwnd.cpp: Added usr option to disable learn-complete prompt. * TODO, contrib/midi/README, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qt5nsmanager.cpp: Refactoring mutes internals in progress, yeesh. 2023-04-16 ahlstrom * README.md, libseq66/include/midi/midifile.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qmutemaster.cpp: Fixed big bug in MIDI-only mute-groups. 2023-04-15 ahlstrom * README.md, doc/latex/tex/mutes.tex, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/rtmidi_info.cpp: Better handling of failure to open ALSA client. 2023-04-14 Chris Ahlstrom * README.md, data/linux/qseq66.mutes, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/songsummary.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: More work improving mute-group handling. 2023-04-12 Chris Ahlstrom * README.md, contrib/scripts/jackctl, data/linux/qseq66-lp-mini-alt.ctrl, libseq66/include/ctrl/keycontainer.hpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/play/performer.cpp: Working on improving mute-group handling. 2023-04-11 Chris Ahlstrom * README.md, data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/menu.tex, libseq66/include/play/performer.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Updated/documented LaunchPad Mini handling. 2023-04-10 Chris Ahlstrom * README.md, TODO, contrib/git/git.text, contrib/scripts/jackctl, data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/patterns_panel.tex, include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp: Improving launchpad display, but play causes file change, so be aware. 2023-04-08 ahlstrom * README.md, RELNOTES.md, TODO, VERSION, configure.ac, doc/dox/doxy-common.cfg, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/seq66_features.h, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/songsummary.cpp, seq_qt5/src/qsmainwnd.cpp: Date bump, fixed activating imported playlist. 2023-04-07 ahlstrom * libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/gui_palette_qt5.cpp: Fixed persistence of BPM in playlist via a static boolean. * README.md, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/performer.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Fixed CLI vs playlists and added Meta text handling. 2023-04-06 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp: Fixed unintended file change when BPM changes during playlist usage. 2023-04-05 ahlstrom * TODO, data/samples/ca_midi.playlist, doc/latex/tex/configuration.tex, libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/src/qplaylistframe.cpp: Still more tweaks to playlist handling. * data/Makefile.in, data/samples/ca_midi.playlist: Removed a couple of Yamaha demo tunes. 2023-04-04 ahlstrom * data/Makefile.am, data/Makefile.in, data/samples/ca_midi.playlist, libseq66/include/util/basic_macros.hpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp: Added playlist material to the installer. 2023-04-03 Chris Ahlstrom * INSTALL, doc/latex/tex/configuration.tex, doc/latex/tex/headless.tex, doc/latex/tex/menu.tex, include/config.h.in, libseq66/src/cfg/rcsettings.cpp: Fixed the --home option. * README.md, TODO, VERSION, configure.ac, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Fixed setting of config subdirectory. 2023-04-01 ahlstrom * TODO, libseq66/include/sessions/smanager.hpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp: Working on reading config for NSM and --home in progress. 2023-03-31 Chris Ahlstrom * libseq66/include/cfg/rcsettings.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Still trying to simplify config files with nsm. 2023-03-30 Chris Ahlstrom * configure.ac, include/config.h.in, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qsessionframe.ui: Added nsm debugging code. * Seq66cli/seq66rtcli.cpp, Seq66qt5/seq66qt5.cpp, doc/latex/tex/sessions.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: More progress on issue #40. 2023-03-29 Chris Ahlstrom * Makefile.in, README.md, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, TODO, aux-files/compile, aux-files/depcomp, aux-files/ltmain.sh, aux-files/missing, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/os/daemonize.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/Makefile.in, libseq66/src/os/daemonize.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/include/nsm/nsmbase.hpp, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/forms/qsabout.ui, seq_qt5/include/Makefile.in, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/Makefile.in, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Work on improving issue #40 for NSM support, still in progress. 2023-03-28 ahlstrom * seq_qt5/forms/qseditoptions.ui, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/qperfeditframe64.cpp: Adding Qt 5.15 disabling checks. 2023-03-27 ahlstrom * : commit 72bb4f9b782eb075261c34efca6d564f3a0d03c2 Author: ahlstrom Date: Mon Mar 27 17:23:14 2023 -0400 * README.md, RELNOTES.md, TODO, VERSION, configure.ac, doc/latex/tex/sessions.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Date bump, log-file name now editable. * README.md, Seq66cli/seq66rtcli.cpp, doc/latex/tex/configuration.tex, doc/latex/tex/headless.tex, doc/latex/tex/jack.tex, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/os/daemonize.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/os/timing.cpp: Fixed handling of log files. 2023-03-26 ahlstrom * README.md, Seq66cli/seq66rtcli.cpp, contrib/VMPK.conf, contrib/scripts/README, contrib/scripts/qtctrun, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/include/os/daemonize.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp: Fixed daemonization, still need fix to reroute_stdio(). 2023-03-24 Chris Ahlstrom * Seq66cli/seq66rtcli.cpp, libseq66/include/os/daemonize.hpp, libseq66/src/os/daemonize.cpp: Daemoniztion works, functional testing needed. 2023-03-23 ahlstrom * Seq66cli/seq66rtcli.cpp, arch/package/PKGBUILD, contrib/gvim.rc, doc/latex/tex/palettes.tex, libseq66/include/os/daemonize.hpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/scales.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/os/daemonize.cpp, seq_qt5/src/qseqeditframe64.cpp: More dicking with daemonization. 2023-03-22 Chris Ahlstrom * Seq66cli/seq66rtcli.cpp, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/usrfile.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/os/daemonize.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/os/daemonize.cpp: Improvements to daemonization/logs, more debugging needed. 2023-03-19 Chris Ahlstrom * README.md, Seq66cli/seq66rtcli.cpp, configure, contrib/vim-syntax/c.vim, data/seq66cli/seq66cli.usr, libseq66/include/os/daemonize.hpp, libseq66/src/os/daemonize.cpp, seq_qt5/src/qloopbutton.cpp: Work on daemonization and log files in progress. 2023-03-18 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.ctrl, libseq66/include/ctrl/automation.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qslivegrid.cpp: Many fixes related to issue #107. 2023-03-17 Chris Ahlstrom * INSTALL, README.md, TODO, VERSION, configure, configure.ac, contrib/vim-syntax/cpp.vim, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/util/recmutex.hpp, libseq66/src/util/recmutex.cpp, seq_qt5/src/qseqeditframe64.cpp: Work on issue #107 and documentation, versioning. 2023-03-04 Chris Ahlstrom * : Updated main-windows image for GitHub. * README.md, VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version 0.99.2 pending. 2023-03-03 Chris Ahlstrom * doc/latex/tex/alsa.tex, doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, doc/latex/tex/event_editor.tex, doc/latex/tex/jack.tex, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/kudos.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/references.tex, doc/latex/tex/sessions.tex, doc/latex/tex/song_editor.tex: Still more documentation fixups. * doc/latex/tex/alsa.tex, doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, doc/latex/tex/event_editor.tex, doc/latex/tex/first_start.tex, doc/latex/tex/headless.tex, doc/latex/tex/jack.tex, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/meta_events.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/palettes.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/playlist.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, doc/latex/tex/setmaster.tex, doc/latex/tex/song_editor.tex, doc/latex/tex/windows.tex, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qseqroll.cpp: Optimizing latex documentation in progress. 2023-03-02 Chris Ahlstrom * INSTALL, README.md, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/docs-structure.tex, doc/latex/tex/first_start.tex, doc/latex/tex/kudos.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/mutes.tex, doc/latex/tex/seq66-user-manual.tex: Significant modification to user manual layout. 2023-03-01 Chris Ahlstrom * TODO, doc/dox/doxy-common.cfg, libseq66/include/midi/jack_assistant.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp: More TODO cleanup. * INSTALL, TODO, doc/latex/tex/midi_formats.tex, libseq66/include/play/songsummary.hpp, libseq66/include/seq66_features.h, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/songsummary.cpp, seq_portmidi/include/portmidi.h, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseqbase.cpp, seq_rtmidi/src/midi_alsa.cpp: Interim check-in, investigating macros. 2023-02-28 Chris Ahlstrom * data/linux/qseq66.palette, seq_qt5/src/gui_palette_qt5.cpp: Fixed some Qt gradient warnings. 2023-02-27 Chris Ahlstrom * : commit 7206c497d2e533ab42a02ee3f55653e6a0ca7f84 Author: Chris Ahlstrom Date: Mon Feb 27 19:47:10 2023 -0500 * : commit d66be924a37902246978a79d4f662b33f9fba62d Author: Chris Ahlstrom Date: Mon Feb 27 07:49:58 2023 -0500 * README.md, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp: Fixed palette file brush settings getting reset. 2023-02-26 Chris Ahlstrom * : commit 204ab0622fabc59c409f45061811f410025f6372 Merge: b1967d12 0047f5eb Author: Chris Ahlstrom Date: Sun Feb 26 08:52:25 2023 -0500 * seq_qt5/src/qslivegrid.cpp: Added credit for phuel fix #106. * TODO, doc/latex/tex/references.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/setmapper.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp: Minor docu tweaks and starting making linear gradient an option. * seq_qt5/src/qslivegrid.cpp: Mark the selected MIDI bus and channel in the pattern dropdown menu. 2023-01-27 Chris Ahlstrom * README.md, seq_qt5/src/qseqroll.cpp: Fixed background seq display with linear gradients. 2023-01-17 Chris Ahlstrom * README.md, VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/play/sequence.cpp: Work on issue #103 under JACK slave transport. 2023-01-16 Chris Ahlstrom * contrib/DIR_COLORS, contrib/vim-syntax/c.vim, contrib/vim-syntax/cpp.vim, contrib/vim.rc, include/config.h.in, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/comments.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/util/filefunctions.cpp, libsessions/src/nsm/nsmbase.cpp: Minor tweaks to contrib and source files. 2022-11-27 Chris Ahlstrom * ChangeLog, README.md, VERSION, configure.ac, contrib/code/ttymidi.c, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version 0.99.1 pending. * README.md, contrib/scripts/jackctl, doc/latex/tex/jack.tex, libseq66/include/cfg/rcsettings.hpp, seq_qt5/forms/qseditoptions.ui, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Readying for version 0.99.1. 2022-11-22 Chris Ahlstrom * README.md, libseq66/include/midi/event.hpp, libseq66/src/midi/midifile.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp: Tweaked the frame offset calculation in midi_jack_data to more closely match the ttymidi.c module. 2022-11-16 Chris Ahlstrom * : Added contrib/tests/testnumbers.ods. 2022-11-04 Chris Ahlstrom * INSTALL, doc/latex/tex/song_editor.tex, libseq66/include/play/performer.hpp, libseq66/include/util/ring_buffer.hpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp: Add gradient styling to grid progress boxes. 2022-10-29 Chris Ahlstrom * doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/song_editor.tex, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp: Very minor changes, mostly documentation. 2022-10-21 Chris Ahlstrom * README.md, TODO, doc/latex/tex/song_editor.tex, libseq66/src/midi/midibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/triggers.cpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qt5_helpers.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/rtmidi_types.cpp: Safety check-in, various minor fixes/tweaks. 2022-10-14 Chris Ahlstrom * libseq66/include/midi/midibytes.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/midi_jack_info.cpp: Interim safety check-in related to issue #100. 2022-10-11 Chris Ahlstrom * README.md, contrib/scripts/jackctl, include/config.h.in, libseq66/include/seq66_features.h, libseq66/src/midi/jack_assistant.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp, seq_rtmidi/src/midi_jack_info.cpp: Added lineargradient look to the piano rolls, to take a break from troubleshooting. 2022-10-10 Chris Ahlstrom * README.md, TODO, VERSION, configure.ac, contrib/midi/songtest.text, doc/latex/tex/song_editor.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/calculations.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_rtmidi/src/midi_jack_data.cpp: Issue #44 almost worked out. 2022-10-06 Chris Ahlstrom * README.md, TODO, contrib/midi/songtest.text, contrib/scripts/jackctl, doc/latex/tex/song_editor.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/settings.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, resources/pixmaps/song_rec_no_snap.xpm, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/midi_jack_info.cpp: Hacking at issues #44 and #100, issues still. 2022-09-30 Chris Ahlstrom * libseq66/include/util/ring_buffer.hpp, libseq66/src/midi/jack_assistant.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi_types.cpp: For issue #100, fixed a calculation error and playback at 4096 is reasonable, not yet perfect. 2022-09-27 Chris Ahlstrom * README.md, TODO, contrib/scripts/jackctl, contrib/scripts/recordpa, contrib/scripts/ystart, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.h, libseq66/include/util/ring_buffer.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/rtmidi_types.cpp: Still whacking at issue #100, calculations look correct but still glitches at 4096 frames per cycle. 2022-09-22 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, contrib/code/ring_buffer.hpp, libseq66/include/seq66_features.h, libseq66/include/util/ring_buffer.hpp, libseq66/src/util/ring_buffer.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/rtmidi_types.cpp: Initial smoke test of midi_message ring_buffer works. 2022-09-19 Chris Ahlstrom * contrib/code/ring_buffer.hpp, contrib/vim-syntax/cpp.vim, libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/seq66_features.h, libseq66/include/util/ring_buffer.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/midi/jack_assistant.cpp, libseq66/src/util/ring_buffer.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi_types.cpp: Adding ring_buffer to support midi_message directly. 2022-09-14 Chris Ahlstrom * README.md, contrib/scripts/recordpa, libseq66/src/play/performer.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp: Made frame offset values static, fixed Stop/Master bug. 2022-09-13 Chris Ahlstrom * seq_rtmidi/src/midi_jack_data.cpp: Forgot to add new midi_jack_data cpp file. * README.md, TODO, contrib/notes/RELNOTES-0_99_0.md, libseq66/src/play/performer.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/seq_rtmidi.pro, seq_rtmidi/src/Makefile.am, seq_rtmidi/src/Makefile.in, seq_rtmidi/src/midi_jack.cpp: Moved jack_frame_offset() code to midi_jack_data, added default JACK frame-related data members, still need to adjust to changes when transport is running. 2022-09-11 Chris Ahlstrom * contrib/vim-syntax/c.vim, contrib/vim-syntax/cpp.vim, libseq66/include/midi/event.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.h, libseq66/src/midi/event.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/src/qseqtime.cpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/rtmidi_types.cpp: Added macros for using midi_message timestamps and 8-byte timestamps, have issue of JACK buffer overruns if active. 2022-09-07 Chris Ahlstrom * configure, libseq66/include/play/sequence.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/sequence.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi_types.cpp: For issue #100, added current tick value to each output event and to the ringbuffer data, seems to work, need further verification. 2022-09-04 Chris Ahlstrom * NEWS, README.md, RELNOTES.md, TODO, VERSION, configure.ac, contrib/git/git.text, data/share/metainfo/seq66.appdata.xml, doc/dox/doxy-common.cfg, doc/latex/tex/seq66-user-manual.tex, include/cli/seq66-config.h, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_debug_code.bat, nsis/build_release_package.bat, seq_qt5/include/qslivebase.hpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Revisited issue #93 and fixed pattern pasting and merging. 2022-09-03 Chris Ahlstrom * ChangeLog, VERSION, configure, configure.ac, contrib/git/git.text, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Updating for version 0.99.0 release. * README.md, RELNOTES.md, contrib/git/git.text: Added some notes on releases. * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Implemented clear and double grid modes, tweaked song recording. 2022-09-02 Chris Ahlstrom * README.md, TODO, doc/latex/tex/patterns_panel.tex, libseq66/include/play/triggers.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, resources/pixmaps/song_rec_no_snap.xpm, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: For issue #44, updates to song recording and snapping. 2022-09-01 Chris Ahlstrom * README.md, TODO, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed handling/saving measure changes. 2022-08-30 Chris Ahlstrom * README.md, TODO, libseq66/include/midi/calculations.hpp, libseq66/src/midi/calculations.cpp, seq_qt5/include/qbase.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes to PPQN handling in seqroll, perfroll, perftime, and mainwnd. 2022-08-29 Chris Ahlstrom * README.md, TODO, contrib/code/ametro.c, contrib/git/git.text, doc/latex/tex/seq66-user-manual.tex, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed detecting color change, MIDI Continue. * README.md, TODO, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qslivebase.cpp: Fixed sets option parsing and improved grid text sizing. 2022-08-26 Chris Ahlstrom * README.md, RELNOTES.md, TODO, doc/latex/tex/seq66-user-manual.tex, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolbase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, nsis/build_release_package.bat, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed note Ctrl-Z, 'ctrl' options, other to-dos. 2022-08-24 Chris Ahlstrom * README.md, TODO, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Partial fixes for issue #82 horizontal scaling. 2022-08-23 Chris Ahlstrom * TODO, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, libseq66/src/play/sequence.cpp: Finished issue #97 so that note entry behaves like Seq24. 2022-08-22 Chris Ahlstrom * README.md, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, libseq66/src/play/metro.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseditoptions.cpp: Fix for issue #54 Qt detection, doc update. * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, m4/ax_have_qt.m4, m4/ax_have_qt_min.m4, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Incorporated ax_have_qt serial 19 updates to fix issue #54. 2022-08-21 Chris Ahlstrom * README.md, RELNOTES.md, TODO, VERSION, bootstrap, bootstrap.help, configure.ac, contrib/git/gitconfig, data/share/doc/tutorial/home.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/metainfo/seq66.appdata.xml, doc/dox/doxy-common.cfg, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, include/cli/seq66-config.h, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/metro.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_debug_code.bat, nsis/build_release_package.bat, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Version bump to 0.99, fixes to background recording. 2022-08-17 Chris Ahlstrom * README.md, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqtime.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqtime.cpp: Fixed L/R marker handling, background recording follow Grid and Quan modes. 2022-08-16 Chris Ahlstrom * doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/play/metro.hpp, libseq66/include/play/performer.hpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp: Background recording essentially works for issue #98, some tweaks might be needed. 2022-08-15 Chris Ahlstrom * libseq66/include/play/metro.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Groundwork for issue #98 background recording laid. 2022-08-14 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Interim check-in for auto-recording, in its infancy. 2022-08-13 Chris Ahlstrom * README.md, doc/latex/tex/midi_formats.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp: Metronome count-in essentially works. 2022-08-12 Chris Ahlstrom * libseq66/include/play/metro.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp: Provisional macroed code for metro count-in. 2022-08-10 Chris Ahlstrom * TODO, contrib/vim-syntax/meson.vim, data/linux/qseq66.rc, doc/latex/tex/menu.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed some bugs in the metronome code, added more documentation. 2022-08-09 Chris Ahlstrom * : Added image for new Metronome tab, issue #98. * doc/latex/tex/menu.tex, libseq66/include/play/performer.hpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Metronome feature and configuration essentially working now. 2022-08-08 Chris Ahlstrom * README.md, RELNOTES.md, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsessionframe.cpp: Metro GUI config in progress, weird debug seqfault fixed. 2022-08-08 Chris Ahlstrom * README.md, data/linux/qseq66.rc, doc/latex/tex/configuration.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/metro.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp: Added metronome configuration to 'rc' file. 2022-08-07 Chris Ahlstrom * libseq66/include/midi/midibytes.hpp, libseq66/include/play/metro.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseqeditframe64.cpp: Added configuration class for metronome support, need to add code for the rc file. * README.md, TODO, libseq66/include/play/metro.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qslivegrid.cpp: More metronome work, niggling issues and configuration needed. 2022-08-06 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qsmainwnd.cpp: Metronome works, need to get it going in Song mode still. * libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, resources/pixmaps/metro.xpm, seq_qt5/forms/qslivegrid.ui, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: More progress on a metronome.... 2022-08-05 Chris Ahlstrom * libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/midi/calculations.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/metro.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, resources/pixmaps/metro.xpm, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qslivegrid.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp: For issue #98, initial metronome code, still in progress. 2022-08-03 Chris Ahlstrom * README.md, RELNOTES.md, TODO, contrib/git/git.text, libseq66/include/util/rect.hpp, libseq66/src/os/shellexecute.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Some fixes to issue #97, added paste box and progress-bar Ctrl-arrow movement to pattern editor. 2022-08-01 Chris Ahlstrom * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/include/qperfroll.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp: L/R keys now work to move triggers, and Pattern Fix now modifies. 2022-07-31 Chris Ahlstrom * README.md, doc/latex/tex/song_editor.tex, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/triggers.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/triggers.cpp, resources/pixmaps/expandgrid.xpm, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp: Added an expand-grid button to the song editor for issue #94. 2022-07-30 Chris Ahlstrom * README.md, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/include/qperfroll.hpp, seq_qt5/src/qperfroll.cpp: More work on issue #90 for song-editor improvements, still in progress. 2022-07-29 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp: Issue #90 for the song editor triggers, still in flux. * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/seq.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Tightened up display message and playback control in regard to issue #89. 2022-07-28 Chris Ahlstrom * ChangeLog, README.md, TODO, libseq66/include/cfg/usrsettings.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qportwidget.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Good progress on issues #89, #90, and #94, more testing needed. * README.md, TODO, libseq66/src/play/performer.cpp: Interim safety check-in for the road. 2022-07-27 Chris Ahlstrom * README.md, TODO, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed issue #93, pattern editor open after pattern cut/delete. * TODO, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qsmainwnd.cpp: Good progress on issues #89 and #90, but need to resolve grid-flicker for #89. * README.md, Seq66qt5/seq66qt5.cpp, TODO, contrib/vim-syntax/cpp.vim, doc/latex/tex/configuration.tex, libseq66/include/cfg/cmdlineopts.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/sessions/smanager.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1: Revisited issues #78 and #91 and added a locale setting option. 2022-07-23 Chris Ahlstrom * README.md, TODO, libseq66/src/play/performer.cpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qloopbutton.cpp: Minor tweaks to the slot-button display. 2022-07-22 Chris Ahlstrom * data/linux/qseq66-lp-mini-alt.ctrl, libseq66/include/play/performer.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp: More work on issue #89, nearly complete. 2022-07-22 Chris Ahlstrom * libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Interim check-in of light refactoring of control-file I/O. 2022-07-21 Chris Ahlstrom * data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/ctrl/midicontrolout.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qloopbutton.cpp: Interim check-in. 2022-07-21 Chris Ahlstrom * README.md, configure, include/config.h.in, libseq66/include/cfg/usrsettings.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Upgrades for issue #78. 2022-07-18 Chris Ahlstrom * README.md, VERSION, configure.ac, contrib/git/git.text, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Prep for version 0.98.11. 2022-07-18 Chris Ahlstrom * VERSION, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version 0.98.10 pending. 2022-07-18 Chris Ahlstrom * README.md, RELNOTES.md, contrib/vim-syntax/c.vim, contrib/vim-syntax/cpp.vim, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/midi/midifile.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack.cpp: Revisited issue #83 re automation/display controls. 2022-07-05 Chris Ahlstrom * README.md, RELNOTES.md, contrib/git/git.text, contrib/vim-syntax/c.vim, doc/latex/tex/seq66-user-manual.tex, libseq66/src/cfg/configfile.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_rtmidi/include/rtmidi_types.hpp: Fixed issue #88, updated RELNOTES for next version. 2022-06-28 Chris Ahlstrom * README.md, TODO, VERSION, configure.ac, contrib/git/git.text, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/mutegroupsfile.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/mutegroup.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/shellexecute.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qmutemaster.cpp: Fixed issue #87, more testing needed. 2022-06-27 Chris Ahlstrom * contrib/scripts/make-checkout, libseq66/include/cfg/usrsettings.hpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/src/qseditoptions.cpp: Improved layout of qmutemaster, fixed non-changing usr options as per issue #87. 2022-06-27 Chris Ahlstrom * ROADMAP.md, contrib/vim-syntax/c.vim, contrib/vim-syntax/cpp.vim, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/mutegroupsfile.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/mutegroup.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/midibytes.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmaster.cpp, libseq66/src/play/songsummary.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/rtmidi_info.cpp: Many tweaks, work on issue #87 well underway. 2022-06-18 Chris Ahlstrom * contrib/vim-syntax/c.vim, contrib/vim-syntax/cpp.vim: Updated vim syntax files. 2022-06-03 Chris Ahlstrom * ROADMAP.md, contrib/scripts/seq66.sed, libseq66/include/midi/midibytes.hpp, libseq66/src/midi/midibytes.cpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Removed unused midi_booleans class, minor tweaks. 2022-06-03 Chris Ahlstrom * NEWS, README.md, TODO, VERSION, configure, configure.ac, configure.help, doc/latex/tex/jack.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, include/config.h.in, libseq66/src/play/portslist.cpp, libseq66/src/util/strfunctions.cpp, seq_rtmidi/include/midi_alsa.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack.cpp: Version increment, doc updates, port-naming fix, alsa experiments. 2022-06-01 Chris Ahlstrom * configure, include/config.h.in: Version bump to 0.98.9.1. 2022-06-01 Chris Ahlstrom * : Fixed config.h.in merge conflict. 2022-05-31 Chris Ahlstrom * INSTALL, TODO, configure, doc/latex/tex/alsa.tex, include/config.h.in: Minor config updates, added VMPK documentation to user manual. 2022-05-30 Chris Ahlstrom * TODO, VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, seq_rtmidi/src/midi_alsa_info.cpp: Bumped the version and added note about vmpk input weirdness. 2022-05-29 Chris Ahlstrom * ChangeLog, include/config.h.in: Updating to quick release 0.98.9. 2022-05-29 Chris Ahlstrom * README.md, TODO, VERSION, configure.ac, data/license.text, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/pattern_tools.html, doc/dox/doxy-common.cfg, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed issue #85 seqfault and some minor bugs, tutorial updates. 2022-05-27 Chris Ahlstrom * data/share/doc/tutorial/css/dark-slide.css, data/share/doc/tutorial/css/emac-slide.css, data/share/doc/tutorial/css/light-slide.css, data/share/doc/tutorial/css/slide.css, data/share/doc/tutorial/home.html, data/share/doc/tutorial/left-tree.html: Added CSS color variables to style sheets. 2022-05-26 Chris Ahlstrom * data/share/doc/tutorial/css/dark-slide.css, data/share/doc/tutorial/css/emac-slide.css, data/share/doc/tutorial/css/light-slide.css, data/share/doc/tutorial/css/slide.css, data/share/doc/tutorial/faq.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/introduction.html: Add emac-slide.css, updated HTML. 2022-05-25 Chris Ahlstrom * TODO, contrib/git/git.text, doc/latex/tex/configuration.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/settings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/os/shellexecute.cpp, seq_qt5/src/qsmainwnd.cpp: Add PDF viewer/browser options for the Help functions. 2022-05-24 Chris Ahlstrom * TODO, libseq66/include/cfg/settings.hpp, libseq66/src/cfg/settings.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_debug_code.bat, nsis/build_release_package.bat, seq_qt5/src/qsmainwnd.cpp: Added fall back to github.io to find user manual, needs testing. 2022-05-24 Chris Ahlstrom * contrib/git/git.text, data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/css/dark-slide.css, data/share/doc/tutorial/css/light-slide.css, data/share/doc/tutorial/css/slide.css, data/share/doc/tutorial/faq.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/images/README, data/share/doc/tutorial/index.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/mutes_manager.html, data/share/doc/tutorial/navibar-saved.html, data/share/doc/tutorial/navibar.html, data/share/doc/tutorial/pagenotready.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/pattern_tools.html, data/share/doc/tutorial/playlist_manager.html, data/share/doc/tutorial/sets_manager.html, data/share/doc/tutorial/song_editor.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_live_play.html, data/share/doc/tutorial/tutorial_main.html, data/share/doc/tutorial/tutorial_new_patterns.html, data/share/doc/tutorial/tutorial_new_song.html, data/share/doc/tutorial/tutorial_other_features.html, data/share/doc/tutorial/tutorial_song_performance.html: Perfected navigation buttons, trimmed PNGs and HTMLs. 2022-05-23 Chris Ahlstrom * data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/faq.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/mutes_manager.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/pattern_tools.html, data/share/doc/tutorial/playlist_manager.html, data/share/doc/tutorial/sets_manager.html, data/share/doc/tutorial/song_editor.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_live_play.html, data/share/doc/tutorial/tutorial_main.html, data/share/doc/tutorial/tutorial_new_patterns.html, data/share/doc/tutorial/tutorial_new_song.html, data/share/doc/tutorial/tutorial_other_features.html, data/share/doc/tutorial/tutorial_song_performance.html, seq_qt5/src/qloopbutton.cpp: Shortened Prev/Home/Next link, fixed bug where queued/one-shot did not gray the progress box. 2022-05-23 Chris Ahlstrom * VERSION, configure, configure.ac, data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/faq.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/index.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/mutes_manager.html, data/share/doc/tutorial/pagenotready.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/pattern_tools.html, data/share/doc/tutorial/playlist_manager.html, data/share/doc/tutorial/sets_manager.html, data/share/doc/tutorial/song_editor.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_live_play.html, data/share/doc/tutorial/tutorial_main.html, data/share/doc/tutorial/tutorial_new_patterns.html, data/share/doc/tutorial/tutorial_new_song.html, data/share/doc/tutorial/tutorial_other_features.html, data/share/doc/tutorial/tutorial_song_performance.html, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version numbers, navigate row for tutorial. 2022-05-23 Chris Ahlstrom * ChangeLog, INSTALL, VERSION, bootstrap, configure.ac, data/Makefile.am, data/Makefile.in, include/config.h.in, seq_qt5/src/qsmainwnd.cpp: Minor fixes to make uninstall for 0.98.8. 2022-05-23 Chris Ahlstrom * README.md, TODO, data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/css/dark-slide.css, data/share/doc/tutorial/css/light-slide.css, data/share/doc/tutorial/css/slide.css, data/share/doc/tutorial/home.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/mutes_manager.html, data/share/doc/tutorial/navibar-saved.html, data/share/doc/tutorial/navibar.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/playlist_manager.html, data/share/doc/tutorial/sets_manager.html, data/share/doc/tutorial/song_editor.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_live_play.html, data/share/doc/tutorial/tutorial_main.html, data/share/doc/tutorial/tutorial_new_patterns.html, data/share/doc/tutorial/tutorial_new_song.html, data/share/doc/tutorial/tutorial_other_features.html, data/share/doc/tutorial/tutorial_song_performance.html: Finished first draft of tutorial, needs prev/next and testing in Windows. 2022-05-22 Chris Ahlstrom * data/share/doc/tutorial/css/dark-slide.css, data/share/doc/tutorial/css/light-slide.css, data/share/doc/tutorial/css/slide.css, data/share/doc/tutorial/home.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/song_editor.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_main.html: Still more tutorial updates, added a dark-mode css file. 2022-05-21 Chris Ahlstrom * TODO, data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/faq.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/song_editor.html, data/share/doc/tutorial/tutorial_first_startup.html: More tutorial updates. 2022-05-21 Chris Ahlstrom * data/Makefile.am, data/Makefile.in, data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/pattern_tools.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_main.html: Fixed tutorial install, more tutorial updates. 2022-05-20 Chris Ahlstrom * README.md, TODO, doc/latex/tex/menu.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/play/sequence.hpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Various fixes, UI tweaks, doc updates, added END to perfroll. 2022-05-19 Chris Ahlstrom * data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/pattern_tools.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_main.html: Add tutorial section, broken. 2022-05-19 Chris Ahlstrom * Makefile.in, README.md, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, TODO, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/Makefile.in, libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/os/shellexecute.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/cfg/settings.cpp, libseq66/src/os/shellexecute.cpp, libseq66/src/seq66_features.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.am, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.am, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.am, seq_rtmidi/src/Makefile.in: Added installation path search and shellexecute module. 2022-05-18 Chris Ahlstrom * configure.ac, libseq66/include/cfg/settings.hpp, libseq66/include/seq66_features.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/src/qsmainwnd.cpp: Added HTML/PDF lookup, PDF local file access needs work. 2022-05-18 Chris Ahlstrom * README.md, TODO, contrib/mutes-map.rc, contrib/vim.rc, data/Makefile.am, data/Makefile.in, data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/pagenotready.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/pattern_tools.html, data/share/doc/tutorial/song_editor.html, libseq66/include/util/filefunctions.hpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Initial working tutorial files, lookup is next. 2022-05-17 Chris Ahlstrom * data/readme.text, data/share/doc/tutorial/css/slide.css, data/share/doc/tutorial/home.html, data/share/doc/tutorial/images/README, data/share/doc/tutorial/index.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/navibar.html, data/share/doc/tutorial/pagenotready.html, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Fixed Windows installer, added initial HTML tutorial documentation. 2022-05-16 Chris Ahlstrom * doc/dia/libseq66-headers.dia, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/settings.hpp, libseq66/include/ctrl/midicontrol.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/util/basic_macros.hpp, libseq66/include/util/condition.hpp, libseq66/include/util/filefunctions.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/scales.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/filefunctions.cpp, libsessions/include/nsm/nsmbase.hpp, seq_qt5/src/palettefile.cpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midibus.cpp: More header refactoring including seq_rtmidi. 2022-05-15 Chris Ahlstrom * libseq66/include/cfg/configfile.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/portslist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qpatternfix.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack_info.cpp: Rearranged/removed palette and calculations headers. 2022-05-14 Chris Ahlstrom * doc/dia/libseq66-headers.dia, libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/{util => midi}/calculations.hpp, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/midi/wrkfile.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/{midi => play}/songsummary.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/{util => midi}/calculations.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/portslist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/{midi => play}/songsummary.cpp, libseq66/src/play/triggers.cpp, libseq66/src/util/filefunctions.cpp, libsessions/include/nsm/nsmdummy.hpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack_info.cpp: Duty now for the future, major header/headache refactorying. 2022-05-13 Chris Ahlstrom * README.md, ROADMAP.md, doc/dia/libseq66-headers.dia, libseq66/include/ctrl/keycontrol.hpp, libseq66/include/ctrl/keymap.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midimacro.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/include/midi/controllers.hpp, libseq66/include/midi/editable_events.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/mutegroup.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/ctrl/keycontrol.cpp, libseq66/src/ctrl/midimacro.cpp, libseq66/src/midi/controllers.cpp, libseq66/src/midi/midibytes.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qseditoptions.cpp: Started header-file refactoring. 2022-05-11 Chris Ahlstrom * ROADMAP.md, contrib/code/function_calls_gnu.c, contrib/code/function_calls_gnu.h, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack.hpp: Added ROADMAP, removed gnu module. 2022-05-11 Chris Ahlstrom * INSTALL, Makefile.in, README.md, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, TODO, bootstrap, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/function_calls_gnu.h, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/function_calls_gnu.c, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, m4/xpc_debug.m4, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/Makefile.in, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Fixed out-of-source builds, removed func call code, streamline bootstrap script. 2022-05-10 Chris Ahlstrom * INSTALL, README.md, Seq66cli/seq66rtcli.cpp, VERSION, bootstrap, configure, configure.ac, include/cli/seq66-config.h, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/seq66_features.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: For issue #84, added an option to build and install both the Qt and CLI apps. 2022-05-08 Chris Ahlstrom * ChangeLog: Version 0.98.7. 2022-05-08 Chris Ahlstrom * TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qpatternfix.cpp: Measure-detection updates, fixed pattern reversal feature. 2022-05-08 Chris Ahlstrom * libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp: Improving speed of measure-change detection. 2022-05-06 Chris Ahlstrom * seq_qt5/src/qt5_helpers.cpp: Fixed fill_combobox, patternfix.midi. 2022-05-06 Chris Ahlstrom * README.md, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/portslist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseditoptions.cpp: Add client:port display option, pattern-fix reversal option. 2022-05-04 Chris Ahlstrom * Makefile.am, Makefile.in, README.md, Seq66qt5/Makefile.am, Seq66qt5/Makefile.in, bootstrap, contrib/git/git.text, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/portslist.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Refactoring portslist and adding eventual support for client/port pair showing. 2022-05-03 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qbase.hpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes for slot flickering, slight refactoring of modification detection. 2022-05-01 Chris Ahlstrom * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes to modify status of time-signature in main window. 2022-05-01 Chris Ahlstrom * : commit f813290f2a03db33f498c86aab4ad391959806c3 Author: Chris Ahlstrom Date: Sun May 1 08:22:22 2022 -0400 2022-04-29 Chris Ahlstrom * : Fix merge conflict in sequence module. 2022-04-29 Chris Ahlstrom * README.md, libseq66/include/play/sequence.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/play/sequence.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qt5_helpers.cpp: Fixed issue #81 by adding stdexcept header. 2022-04-28 Chris Ahlstrom * README.md, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqbase.hpp, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditex.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qseqtime.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp: Refactored seqedit to use seq::ref instead of pointers. 2022-04-27 Chris Ahlstrom * libseq66/include/cfg/settings.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Refactoring fill_combobox() function. 2022-04-26 Chris Ahlstrom * README.md, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Added jitter to pattern-fix, GUI fixes. 2022-04-25 Chris Ahlstrom * README.md, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/mastermidibase.hpp, libseq66/include/util/calculations.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, resources/pixmaps/play_on.xpm, resources/pixmaps/q_rec_on.xpm, resources/pixmaps/rec_on.xpm, resources/pixmaps/thru_on.xpm, seq_qt5/include/qeditbase.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp: Added 'on' icons for seqedit, non-power-of-2 detection, improved modification detection. 2022-04-23 Chris Ahlstrom * README.md, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp: Fixes to pattern-fix, ongoing. 2022-04-22 Chris Ahlstrom * README.md, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/cfg/settings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Improved perf-modified handling, adding settings lists. 2022-04-20 Chris Ahlstrom * README.md, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp: Added QIcon theme-name retrieval, seqedit tweakage. 2022-04-19 Chris Ahlstrom * libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp: More refinements to qpatternfix processing. 2022-04-16 Chris Ahlstrom * libseq66/include/play/sequence.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qpatternfix.cpp: Added rudimentary time-signature adjustment to qpatternfix, still fixing issue. 2022-04-15 Chris Ahlstrom * TODO, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qpatternfix.cpp: Augmenting qpatternfix with note-length preservation. 2022-04-14 Chris Ahlstrom * README.md, TODO, libseq66/include/cfg/settings.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqstyle.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfeditex.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qportwidget.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqstyle.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5_helpers.cpp: More work on settings, qpatternfix, time signatures. 2022-04-13 Chris Ahlstrom * README.md, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/settings.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Tightened string-to-number calls, more combolist updates and usages. 2022-04-13 Chris Ahlstrom * libseq66/src/cfg/settings.cpp: Minor settings module update. 2022-04-13 Chris Ahlstrom * README.md, TODO, libseq66/include/cfg/settings.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/util/calculations.cpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Refactoring combo-box handling into settings module. 2022-04-12 Chris Ahlstrom * README.md, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qpatternfix.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qsmainwnd.cpp: qpatternfix fixes, tab ordering, measure calculation improvments. 2022-04-11 Chris Ahlstrom * doc/latex/tex/palettes.tex, libseq66/include/util/calculations.hpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp: qpatternfix fixes, more progress. 2022-04-11 Chris Ahlstrom * README.md, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qstriggereditor.cpp: qpatternfix dialog now in the debugging stage. 2022-04-10 Chris Ahlstrom * VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qt5_helper.h, seq_qt5/include/qt5_helpers.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp: qpatternfix dialog fleshed out, implementation not yet in place. 2022-04-09 Chris Ahlstrom * seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp: Added qpatternfix dialog, not yet functional. 2022-04-09 Chris Ahlstrom * ChangeLog, doc/latex/tex/menu.tex: Minor user-manual fix, change-log. 2022-04-08 Chris Ahlstrom * README.md, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Fixed stupid seqedit bug setting beats/bar. 2022-04-08 Chris Ahlstrom * README.md, TODO, VERSION, bootstrap, configure, configure.ac, doc/dox/doxy-common.cfg, doc/latex/tex/menu.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.h, libseq66/include/seq66_features.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/screenset.cpp, seq_portmidi/src/portmidi.c, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_rtmidi/src/midi_jack.cpp: Code cleanup of macros, unused UI items. 2022-04-06 Chris Ahlstrom * libseq66/include/cfg/basesettings.hpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Added Apply/Reset buttons to Preferences dialog. 2022-04-05 Chris Ahlstrom * README.md, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Clear global seq features from last tune, even more detection of modification. 2022-04-04 Chris Ahlstrom * README.md, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Better pattern modification detection, SeqSpec reading, restart handling. 2022-04-03 Chris Ahlstrom * seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Global-seq-feature work, may be complete. 2022-04-03 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Getting global-seq-feature working right, in progress. 2022-04-02 Chris Ahlstrom * README.md, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/src/midi_jack.cpp: Working on a potential segfault when adding a new track while playing. 2022-03-31 Chris Ahlstrom * README.md, TODO, libseq66/src/util/filefunctions.cpp, nsis/Seq66Constants.nsh, nsis/build_debug_code.bat, nsis/build_release_package.bat, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qslivegrid.cpp: Removed dead code, minor GUI tweaks. 2022-03-29 Chris Ahlstrom * NEWS, README.md, RELNOTES.md, TODO, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed issues with Live/Song mode, Preferences updates. 2022-03-28 Chris Ahlstrom * README.md, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Fixed setting last-used-directory and modify status with painted notes. 2022-03-28 Chris Ahlstrom * README.md, Seq66qt5/seq66qt5.cpp, contrib/code/test/filename_split.cpp, doc/dia/rtbusses.dia, doc/dia/rtjack_init.dia, libseq66/include/util/filefunctions.hpp, libseq66/src/util/filefunctions.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/src/mastermidibus.cpp: Fixed filename splitting/building, updated diagrams. 2022-03-26 Chris Ahlstrom * README.md, TODO, bootstrap.help, doc/dia/rtbusses.dia, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp: Fixed Dia files, minor bugs, cleanup. 2022-03-23 Chris Ahlstrom * arch/package/PKGBUILD, doc/dia/rtbusses.dia, libseq66/src/os/daemonize.cpp: Added initial Dia JACK sequence diagram, updated Arch PKGBUILS. 2022-03-22 Chris Ahlstrom * README.md, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, doc/latex/tex/song_editor.tex: Updated the user manual. 2022-03-21 Chris Ahlstrom * README.md, bootstrap, configure, configure.ac, doc/latex/tex/sessions.tex, include/config.h.in, libseq66/include/util/basic_macros.hpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Revisited issue #41, added another reload button. 2022-03-14 Chris Ahlstrom * INSTALL, README.md, doc/latex/tex/jack.tex, doc/latex/tex/pattern_editor.tex, libseq66/src/midi/businfo.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midi_probe.cpp, seq_rtmidi/src/rtmidi_types.cpp: Add UI for click-to-edit, removed JACK callback code. 2022-03-10 Chris Ahlstrom * README.md, libseq66/include/midi/midibase.hpp, libseq66/src/midi/midibase.cpp, seq_rtmidi/src/midibus.cpp: JACK port enable/disable fixed, very minor optimizing. 2022-03-08 Chris Ahlstrom * README.md, RELNOTES.md, VERSION, configure, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/opcontrol.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/util/palette.hpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/mutegroups.cpp, seq_qt5/include/gui_palette_qt5.hpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Setup for 0.98.6, tweaking some enum classes. 2022-03-07 Chris Ahlstrom * ChangeLog, doc/latex/tex/seq66-user-manual.tex: Version 0.98.5. 2022-03-06 Chris Ahlstrom * VERSION, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version date-stamp. 2022-03-06 Chris Ahlstrom * libseq66/src/midi/midibase.cpp: Minor businfo tweak for debugging. 2022-03-06 Chris Ahlstrom * TODO, libseq66/include/midi/midibase.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/midibase.cpp, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/portmidi.c, seq_rtmidi/src/midibus.cpp: Portmidi fixes and businfo optimizing. 2022-03-06 Chris Ahlstrom * README.md, TODO, contrib/notes/q-hierarchy.text, libseq66/include/midi/event.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/recmutex.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/sequence.cpp, seq_rtmidi/src/midibus.cpp: Tightened draw_locking(). 2022-03-03 Chris Ahlstrom * README.md, TODO, doc/latex/tex/seq66-user-manual.tex, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/rtmidi_types.cpp: Fixed another subtle segfault, added an underrun indicator. 2022-03-02 Chris Ahlstrom * README.md, contrib/code/qsliveframe.cpp, libseq66/include/play/sequence.hpp, libseq66/src/os/timing.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Added draw-lock-unlock functions to sequence and use them with most GUI get_next_() functions. 2022-03-01 Chris Ahlstrom * libseq66/include/midi/eventlist.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midibase.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Working on unpredictable crash recording from two inputs. 2022-02-28 Chris Ahlstrom * README.md, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/midibase.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/util/strfunctions.cpp, seq_portmidi/src/midibus.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/rtmidi_types.cpp: Added experimental/problematic feature to allow disabled ports to still be inited. 2022-02-27 Chris Ahlstrom * README.md, TODO, configure.ac, configure.help, doc/latex/tex/configuration.tex, doc/latex/tex/jack.tex, doc/latex/tex/port_mapping.tex, include/config.h.in, libseq66/include/midi/businfo.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/midi/midibus_common.hpp, libseq66/include/play/portslist.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/portslist.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midibus.cpp: Port enable/disable working for JACK, partially for ALSA. 2022-02-24 Chris Ahlstrom * INSTALL, README.md, TODO, bootstrap, configure.ac, doc/latex/tex/configuration.tex, include/config.h.in, libseq66/include/midi/midibase.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Improved no-JACK build and handling of bad command-line arguments. 2022-02-23 Chris Ahlstrom * README.md, libseq66/include/midi/businfo.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/midibase.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Moved input initing to busarray initialization, to match output initing. 2022-02-22 Chris Ahlstrom * Makefile.in, README.md, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, include/qt/rtmidi/seq66-config.h, libseq66/Makefile.in, libseq66/include/Makefile.am, libseq66/include/Makefile.in, {seq_rtmidi => libseq66}/include/base64_images.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/midibase.hpp, libseq66/src/Makefile.in, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/util/basic_macros.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_portmidi/src/midibus.cpp, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.am, seq_rtmidi/include/Makefile.in, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/seq_rtmidi.pro, seq_rtmidi/src/Makefile.in, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Moved base64_images, fixed rtmidi pro file, interim check-in. 2022-02-19 Chris Ahlstrom * doc/dia/rtbusses.dia, libseq66/include/midi/midibytes.hpp, libseq66/src/play/performer.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Minor tweaks will updating rtbusses diagram. 2022-02-17 Chris Ahlstrom * README.md, VERSION, configure, configure.ac, contrib/git/git.text, doc/dia/rtbusses.dia, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/src/qslivebase.cpp, seq_rtmidi/include/mastermidibus_rm.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midibus.cpp, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp, seq_rtmidi/src/rtmidi_types.cpp: Prep 0.98.5, add rtmidi accessors, diagram updates, more. 2022-02-12 Chris Ahlstrom * : commit f8bcfa37d53302044ab8f1d6a571e09d8f0ac052 Author: Chris Ahlstrom Date: Sat Feb 12 09:13:31 2022 -0500 2022-02-12 Chris Ahlstrom * doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qloopbutton.cpp, seq_rtmidi/src/midi_jack.cpp: Some tweaks and documentation for looming 0.98.4. 2022-02-11 Chris Ahlstrom * README.md, libseq66/include/midi/jack_assistant.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Fixed seq24 bug with tick-to-time calculations using beat width. 2022-02-11 Chris Ahlstrom * : Merged portfix branch. 2022-02-08 Chris Ahlstrom * bootstrap, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/util/basic_macros.cpp, seq_qt5/src/qt5_helpers.cpp: Made JACK metadata true by default, more improvements to investigate output. 2022-02-08 Chris Ahlstrom * include/qt/rtmidi/seq66-config.h, libseq66/include/midi/jack_assistant.hpp, libseq66/include/util/basic_macros.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/util/basic_macros.cpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: More work on issue #75, metadata for icons. 2022-02-06 Chris Ahlstrom * README.md, libseq66/src/util/basic_macros.cpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi_info.cpp: Added detection of ports being owned by Seq66. 2022-02-03 Chris Ahlstrom * libseq66/include/util/basic_macros.hpp, libseq66/src/util/basic_macros.cpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi_info.cpp: Enabled port-register callback and added another async print function. 2022-02-01 Chris Ahlstrom * doc/dia/rtbusses.dia, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp: Changed rterror kind to an enum class. 2022-01-31 Chris Ahlstrom * : Minor merge conflicts in 0_98_0 and portfix branches. 2022-01-31 Chris Ahlstrom * README.md, configure, include/config.h.in, seq_qt5/src/qseqeditframe64.cpp: Fixed indexing bug in seqedit record-style selector. 2022-01-30 Chris Ahlstrom * libseq66/include/util/basic_macros.hpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/filefunctions.cpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Layed groundwork for future detection of JACK port connection/registration. 2022-01-29 Chris Ahlstrom * libseq66/include/cfg/settings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/calculations.cpp, seq_portmidi/include/mastermidibus_pm.hpp, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/midibus.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa.cpp: The portmidi version builds, other minor refactoring. 2022-01-27 Chris Ahlstrom * configure, include/config.h.in, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibase.hpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/performer.cpp, seq_rtmidi/include/mastermidibus_rm.hpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midi_probe.cpp, seq_rtmidi/src/midibus.cpp, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp: Interim portfix check-in, not yet tested. 2022-01-26 Chris Ahlstrom * configure.ac, seq_rtmidi/include/mastermidibus_rm.hpp, seq_rtmidi/src/mastermidibus.cpp: Started refactoring port creation. 2022-01-25 Chris Ahlstrom * contrib/git/git.text: Updated git.text to discuss removing old branches locally and from GitHub. 2022-01-24 Chris Ahlstrom * configure.ac: Back to 0.98.4. 2022-01-24 Chris Ahlstrom * README.md, configure, configure.ac, include/config.h.in: Version 0.98.3.1 to fix make-files. 2022-01-24 Chris Ahlstrom * Makefile.am, Makefile.in, data/Makefile.am, data/Makefile.in, doc/Makefile.am, doc/Makefile.in, doc/dox/Makefile.am, doc/latex/Makefile.am, doc/latex/Makefile.in, doc/latex/tex/Makefile.am, doc/latex/tex/Makefile.in, libseq66/Makefile.am, libseq66/Makefile.in, libsessions/Makefile.am, libsessions/Makefile.in, seq_portmidi/Makefile.am, seq_portmidi/Makefile.in, seq_qt5/Makefile.am, seq_qt5/Makefile.in, seq_rtmidi/Makefile.am, seq_rtmidi/Makefile.in: Revisited issue #45, cleaned and fixed other Makefiles. 2022-01-23 Chris Ahlstrom * VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Stamped for next version, 0.98.4. 2022-01-23 Chris Ahlstrom * include/config.h.in: Updated include/config.h.in. 2022-01-23 Chris Ahlstrom * README.md, TODO, VERSION, configure.ac, doc/dox/doxy-common.cfg, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version 0.98.3. 2022-01-23 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, bootstrap, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/function_calls_gnu.h, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/function_calls_gnu.c, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, m4/xpc_debug.m4, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.am, seq_rtmidi/src/Makefile.in: GN instrument-functions option available, but too problematic at this time. 2022-01-22 Chris Ahlstrom * bootstrap, configure.ac, libseq66/include/Makefile.am, libseq66/include/function_calls_gnu.h, libseq66/src/Makefile.am, libseq66/src/function_calls_gnu.c, m4/xpc_debug.m4: Added an attempt at GNU C option instrument-functions. 2022-01-20 Chris Ahlstrom * README.md, TODO, libseq66/src/midi/midibase.cpp, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/qclocklayout.hpp, seq_qt5/include/qinputcheckbox.hpp, seq_qt5/include/qportwidget.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qportwidget.cpp, seq_rtmidi/src/midi_alsa.cpp: Created base class qportwidget, looking at virtual ALSA input port issues. 2022-01-19 Chris Ahlstrom * README.md, TODO, data/linux/macros-MMC.ctrl, libseq66/include/midi/event.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_rtmidi/include/midi_alsa.hpp, seq_rtmidi/src/midi_alsa.cpp: Improving tempo handling, ALSA port-naming fixes in progress. 2022-01-18 Chris Ahlstrom * README.md, TODO, contrib/code/ametro.c, contrib/code/make_ametro, seq_rtmidi/src/rtmidi.cpp: Got ametro to generate MIDI clock for testing. 2022-01-17 Chris Ahlstrom * contrib/code/ametro.c, contrib/code/make_ametro: Adding ametro command for testing MIDI clock commands, in progress. 2022-01-16 Chris Ahlstrom * README.md, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/sessions.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/playlist.hpp, libseq66/include/seq66_features.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, seq_portmidi/src/pmlinuxalsa.c, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Work on issue #76, fixing imports of project/playlist in progress. 2022-01-13 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: More progress on importing playlists. 2022-01-11 Chris Ahlstrom * README.md, doc/latex/tex/event_editor.tex, doc/latex/tex/jack.tex, doc/latex/tex/references.tex, doc/latex/tex/sessions.tex, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp: Work on a File / Import Project command in progress. 2022-01-09 Chris Ahlstrom * libseq66/include/cfg/configfile.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/sessions/smanager.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, libsessions/include/nsm/nsmbase.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Initial prep for import Seq66 configurations. 2022-01-08 Chris Ahlstrom * README.md, doc/latex/tex/sessions.tex, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/smanager.cpp: Minor updates, icon and prep for resurfacing an NSM issue. 2022-01-07 Chris Ahlstrom * bootstrap, contrib/scripts/reconf, data/share/applications/seq66.desktop, debian/seq66.desktop, libseq66/include/cfg/configfile.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/sessions/smanager.cpp: Work on fixing issue #64, preserving visibility in the 'usr' file. 2022-01-06 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/midi/jack_assistant.hpp, libseq66/include/seq66_features.hpp, libseq66/src/Makefile.in, libseq66/src/midi/jack_assistant.cpp, libseq66/src/seq66_features.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: More tinkering with JACK metadata, one must now enable it in configure. 2022-01-06 Chris Ahlstrom * contrib/scripts/make-checkout, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/jack_assistant.hpp, libseq66/src/midi/jack_assistant.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Added metadata support for issue #75, does not work as expected with jack 1.9.12 dated 2017 on ubuntu. 2022-01-04 Chris Ahlstrom * : Added qseq66.png to resources. 2022-01-04 Chris Ahlstrom * README.md, configure, configure.ac, include/config.h.in, libseq66/include/midi/jack_assistant.hpp, libseq66/include/os/daemonize.hpp, libseq66/include/os/timing.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/timing.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeventframe.cpp, seq_rtmidi/include/Makefile.am, seq_rtmidi/include/Makefile.in, seq_rtmidi/include/base64_images.hpp, seq_rtmidi/seq_rtmidi.pro, seq_rtmidi/src/midi_jack_info.cpp: Added functions to set JACK metadata re issue #75, but they do not work properly yet. 2022-01-04 Chris Ahlstrom * Makefile.am, Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, configure.ac, data/Makefile.am, data/Makefile.in, data/README, data/{license.txt => license.text}, data/{readme.txt => readme.text}, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.am, m4/Makefile.in, man/Makefile.in, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, resources/pixmaps/Makefile.am, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: More make-file work for issue #75. 2022-01-03 Chris Ahlstrom * data/Makefile.am, data/Makefile.in, data/linux/seq66.desktop.in, data/share/applications/seq66.desktop, {desktop => data/share}/metainfo/seq66.appdata.xml, debian/seq66.desktop, doc/README, doc/latex/tex/Makefile.am, doc/latex/tex/Makefile.in, libseq66/include/Makefile.am, libseq66/include/Makefile.in, resources/pixmaps/Makefile.am, resources/pixmaps/Makefile.in, resources/pixmaps/SEQ66_24x24.xpm, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_rtmidi/include/Makefile.am, seq_rtmidi/include/Makefile.in: Refactoring icons installation for #issue #75 in progress. 2022-01-02 Chris Ahlstrom * NEWS, README.md, VERSION, configure, configure.ac, contrib/tests/4x4/README, contrib/tests/4x4/darkfix.qss, contrib/tests/4x4/qseq66-lp-mini-4x4.ctrl, contrib/tests/4x4/qseq66.ctrl, contrib/tests/4x4/qseq66.mutes, contrib/tests/4x4/qseq66.rc, contrib/tests/4x4/qseq66.usr, contrib/tests/4x4/synthstart, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqdata.cpp: Version bump, note-data display fix, style-sheet test. 2022-01-01 Chris Ahlstrom * VERSION, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version 0.98.2 to fix issue #74. 2022-01-01 Chris Ahlstrom * README.md, libseq66/src/util/strfunctions.cpp: Fixed issue #74 where string conversion of -1 resulted in 0. 2021-12-31 Chris Ahlstrom * contrib/tests/4x4/qseq66.ctrl, contrib/tests/4x4/qseq66.mutes, contrib/tests/4x4/qseq66.rc, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/util/filefunctions.cpp: Fixing handling of log file. 2021-12-31 Chris Ahlstrom * contrib/tests/4x4/README, contrib/tests/4x4/qseq66.ctrl, contrib/tests/4x4/qseq66.rc, data/samples/session.rc, doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, libseq66/include/ctrl/keycontrol.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontrol.cpp: Added blank keystroke for placeholders. 2021-12-30 Chris Ahlstrom * contrib/midnam/README, contrib/tests/4x4/qseq66.rc, data/samples/session.rc, libseq66/include/Makefile.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/sessionfile.hpp, libseq66/src/Makefile.in, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/sessions/smanager.cpp: Preparations for heavy 4x4 testing. 2021-12-29 Chris Ahlstrom * libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/sessions/smanager.cpp: Interim check-in. 2021-12-29 Chris Ahlstrom * data/samples/session.rc, doc/latex/tex/configuration.tex, libseq66/include/Makefile.am, libseq66/include/cfg/rcfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/sessionfile.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/src/qseditoptions.cpp: Added a test facility, session.rc. 2021-12-28 Chris Ahlstrom * README.md, TODO, contrib/tests/4x4/qseq66.ctrl, data/linux/qseq66-azerty.ctrl, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66-lp-mini-swapped.ctrl, data/linux/qseq66-lp-mini.ctrl, data/linux/qseq66-swapped.ctrl, data/linux/qseq66.ctrl, data/samples/nanomap.ctrl, data/win/qpseq66.ctrl, doc/latex/tex/configuration.tex, doc/latex/tex/headless.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/performer.cpp: Removed key/MIDI control-loading flags, fixed the applying of session mutes. 2021-12-27 Chris Ahlstrom * README.md, TODO, VERSION, configure, configure.ac, data/win/dark-theme.qss, doc/dox/doxy-common.cfg, doc/latex/tex/configuration.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/include/play/portslist.hpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, nsis/x64.nsh, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/src/qperfeditframe64.cpp: Version bump, added detection of missing system ports in port-mapping. 2021-12-26 Chris Ahlstrom * include/config.h.in, seq_qt5/src/qperfeditframe64.cpp: Very minor config misses. 2021-12-26 Chris Ahlstrom * ChangeLog, VERSION, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version 0.98.1 in place. 2021-12-26 Chris Ahlstrom * README.md, TODO, contrib/scripts/qtests, doc/latex/tex/concepts.tex, doc/latex/tex/menu.tex, doc/latex/tex/port_mapping.tex, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/qskeymaps.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qskeymaps.cpp: Fixed horizontal piano rolls alignment, song editor name issue. 2021-12-24 Chris Ahlstrom * doc/latex/tex/port_mapping.tex: Updated port-mapping documentation. 2021-12-23 Chris Ahlstrom * libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/strfunctions.cpp: Perhaps port-mapping is whipped into shape now :-D. 2021-12-21 Chris Ahlstrom * README.md, TODO, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/portslist.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/portslist.cpp, libseq66/src/util/filefunctions.cpp, libseq66/src/util/strfunctions.cpp: Port mapping basically done, some minor cleanup needed. 2021-12-20 Chris Ahlstrom * libseq66/include/play/portslist.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp: More progress in port-mapping, tough stuff. 2021-12-19 Chris Ahlstrom * libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/{listsbase.hpp => portslist.hpp}, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/{listsbase.cpp => portslist.cpp}, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Changed listsbase to portslist for clarity. 2021-12-18 Chris Ahlstrom * libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/listsbase.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: More refactoring of port and mapping configuration. 2021-12-16 Chris Ahlstrom * libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/play/listsbase.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/src/midi_jack_info.cpp: Still working on port-mapping robustness. 2021-12-15 Chris Ahlstrom * README.md, contrib/tests/4x4/qseq66.ctrl, contrib/tests/4x4/qseq66.mutes, contrib/tests/4x4/qseq66.rc, data/linux/jack/jack_portmaps.rc, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/listsbase.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp, seq_rtmidi/src/midi_jack_info.cpp: Updating port-naming/mapping in progress. 2021-12-14 Chris Ahlstrom * contrib/tests/4x4/README, contrib/tests/4x4/qseq66.rc: Just some changes re 4x4 test. 2021-12-13 Chris Ahlstrom * README.md, contrib/midi/README, {data => contrib}/tests/4x4/qseq66.ctrl, {data => contrib}/tests/4x4/qseq66.drums, {data => contrib}/tests/4x4/qseq66.mutes, {data => contrib}/tests/4x4/qseq66.palette, {data => contrib}/tests/4x4/qseq66.playlist, {data => contrib}/tests/4x4/qseq66.rc, {data => contrib}/tests/4x4/qseq66.usr, data/linux/qseq66.ctrl, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qslivegrid.cpp: Fixes to pattern access in sets. 2021-12-12 Chris Ahlstrom * .gitignore, README.md, TODO, configure, data/tests/4x4/qseq66.ctrl, data/tests/4x4/qseq66.drums, data/tests/4x4/qseq66.mutes, data/tests/4x4/qseq66.palette, data/tests/4x4/qseq66.playlist, data/tests/4x4/qseq66.rc, data/tests/4x4/qseq66.usr, include/config.h.in, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qt5nsmanager.cpp: Added initial 4x4 test configs, fixed help segfault. 2021-12-12 Chris Ahlstrom * INSTALL, NEWS, README.md, VERSION, configure.ac, contrib/DIR_COLORS, contrib/notes/{gcc-version.txt => gcc-version.text}, contrib/notes/get_midi_event.txt, contrib/notes/{key-names.txt => key-names.text}, contrib/notes/keycontainer.dump, contrib/notes/keymap.dump, contrib/notes/{launchpad.txt => launchpad.text}, contrib/notes/{performance.txt => performance.text}, contrib/notes/qt5-azerty-codes.txt, contrib/notes/qw-az-keys.text, contrib/notes/slots.txt, contrib/notes/styling.text, contrib/notes/{windows-midi.txt => windows-port-midi.text}, contrib/notes/windows-portmidi.txt, data/readme.txt, data/readme.windows, doc/dox/doxy-common.cfg, doc/latex/tex/configuration.tex, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/README, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, nsis/x64.nsh: Version bump and NSIS research notes. 2021-12-11 Chris Ahlstrom * TODO, nsis/Seq66Constants.nsh: Version 0.98.0 release to master. 2021-12-10 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qslivegrid.cpp: Tweaking coloring getting record/quantize button coloring to work. 2021-12-10 Chris Ahlstrom * NEWS, README.md, TODO, VERSION, configure.ac, data/README, data/license.txt, data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66.ctrl, data/linux/qseq66.rc, data/linux/qseq66.usr, data/readme.txt, data/readme.windows, doc/dox/doxy-common.cfg, doc/latex/tex/configuration.tex, doc/latex/tex/launchpad_mini.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/configfile.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/play/performer.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, seq_qt5/src/palettefile.cpp, seq_qt5/src/qslivegrid.cpp: Build date updates and interim check-in re automation. 2021-12-10 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/util/condition.hpp, libseq66/src/play/performer.cpp, libseq66/src/util/condition.cpp, nsis/build_debug_code.bat: Fixed Windows condition-wait CPU issue with new synchronization class, applies to Linux too. 2021-12-09 Chris Ahlstrom * doc/latex/tex/defaultkeys.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/util/condition.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/condition.cpp, libseq66/src/util/recmutex.cpp, seq_portmidi/include/portmidi.h, seq_portmidi/src/midibus.cpp, seq_portmidi/src/pmwin.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_qt5/src/qslivegrid.cpp: Still working on Windows CPU usage, dang. 2021-12-08 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/src/qslivegrid.cpp: Provisional implementations of most grid-mode functions. 2021-12-08 Chris Ahlstrom * README.md, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Added grid-mode combobox to the live grid. 2021-12-07 Chris Ahlstrom * TODO, data/linux/qseq66.ctrl, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/midioperation.hpp, libseq66/include/ctrl/opcontrol.hpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/ctrl/opcontrol.cpp: Fixed issue with slot-names shown in 'ctrl' file, oops. 2021-12-07 Chris Ahlstrom * README.md, data/samples/textfix.qss, doc/latex/tex/configuration.tex, libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/src/mastermidibus.cpp: The JACK port alias feature basically works. 2021-12-06 Chris Ahlstrom * contrib/code/jack_impl.cpp, contrib/code/qseqeditframe.cpp, contrib/code/qseqeditframe.hpp, contrib/code/qseqeditframe.ui, contrib/code/victor.hpp, doc/latex/tex/alsa.tex, doc/latex/tex/configuration.tex, doc/latex/tex/jack.tex, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/play/listsbase.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/strfunctions.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midibus.cpp: Adding support to show JACK port aliases. 2021-12-05 Chris Ahlstrom * doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, doc/latex/tex/mutes.tex, doc/latex/tex/references.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, doc/latex/tex/setmaster.tex: Getting documentation up to spec for version 0.98.0. 2021-12-04 Chris Ahlstrom * doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, doc/latex/tex/sessions.tex, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/keycontainer.hpp, libseq66/include/ctrl/keycontrol.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/keycontrol.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp: Interim check-in, latex table of keys is still broken. 2021-12-04 Chris Ahlstrom * doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, libseq66/include/ctrl/midimacro.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/util/calculations.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/midi/midibytes.cpp: More work on new automation slots, tightening headers. 2021-12-03 Chris Ahlstrom * doc/latex/tex/configuration.tex, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/include/ctrl/automation.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/play/performer.cpp: Working on automatic ctrl file upgrade. 2021-12-02 Chris Ahlstrom * contrib/notes/slots.txt, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/keycontainer.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/util/filefunctions.cpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp: Much refactoring for additional automation slots. 2021-11-30 Chris Ahlstrom * INSTALL, NEWS, README.md, TODO, configure, configure.ac, data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/concepts.tex, doc/latex/tex/references.tex, doc/latex/tex/sessions.tex, include/config.h.in, libseq66/include/ctrl/automation.hpp, libseq66/include/seq66_features.hpp, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, libsessions/include/nsm/nsmbase.hpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/src/rtmidi.cpp: Important work on issues #41 and #73. 2021-11-29 Chris Ahlstrom * INSTALL, README.md, data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66.ctrl, data/linux/seq66.desktop.in, debian/seq66.desktop, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libsessions/include/nsm/nsmbase.hpp, libsessions/include/nsm/nsmclient.hpp, libsessions/src/nsm/nsmbase.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/src/rtmidi.cpp: Work on NSM show/hide issues in progress. 2021-11-28 Chris Ahlstrom * data/linux/macros-APC40-mk2.ctrl, data/linux/macros-launchpad-pro-mk3.ctrl, data/samples/textfix.qss, include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/midicontrol.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/seq66_features.hpp, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/midicontrol.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/midi/event.cpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/basic_macros.cpp: Minor fixes to MIDI control and seq_client_tag(), qss update. 2021-11-26 Chris Ahlstrom * : Fix merge conflicts from optimize/master. 2021-11-26 Chris Ahlstrom * configure, doc/latex/tex/configuration.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex: Version 0.97.3 pending. 2021-11-25 Chris Ahlstrom * seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseditoptions.cpp: Fixed UI for setting MIDI I/O control ports. 2021-11-24 Chris Ahlstrom * README.md, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midibytes.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midibus.cpp: Macros now work, sysex sending works, added UI for MIDI I/O control. 2021-11-24 Chris Ahlstrom * libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/include/midi/midibytes.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/midi/midibytes.cpp, libseq66/src/util/strfunctions.cpp: More progress on macro support, interim check-in 2. 2021-11-23 Chris Ahlstrom * INSTALL, README.md, configure, contrib/scripts/make-qt5-links, data/linux/macros-launchpad-mini.ctrl, data/linux/macros-launchpad-pro-mk3.ctrl, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midimacro.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/ctrl/midimacro.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qsessionframe.hpp, seq_qt5/src/qsessionframe.cpp: More progress on macro support, interim check-in. 2021-11-22 Chris Ahlstrom * libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midimacro.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midimacro.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/util/strfunctions.cpp: More progess on MIDI macros. 2021-11-22 Chris Ahlstrom * : commit 3ff41681995c17bfd320bfcd1463e1e7e6aa922e Merge: 3525ddf1 4088954b Author: Chris Ahlstrom Date: Mon Nov 22 16:48:56 2021 -0500 2021-11-22 Chris Ahlstrom * configure.ac: Tweak of configure.ac. 2021-11-22 C. Ahlstrom * : Merge pull request #71 from Fi3/FixFedoraBuild Fix fedora 34 build 2021-11-22 fi3 * INSTALL, configure.ac, m4/ax_have_qt.m4, m4/ax_have_qt_ex.m4, m4/ax_have_qt_min.m4: Fix fedora 34 build 2021-11-22 Chris Ahlstrom * TODO, seq_qt5/forms/qslivegrid.ui, seq_qt5/src/qslivegrid.cpp: Tweaks to loop/quantize main buttons. 2021-11-20 Chris Ahlstrom * TODO, libseq66/include/seq66_features.hpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/include/qslivebase.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qt5_helpers.cpp: External grid fixes done, coloring the record/loop-mode buttons. 2021-11-19 Chris Ahlstrom * README.md, TODO, libseq66/include/os/timing.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, seq_portmidi/src/midibus.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/include/qliveframeex.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midi_probe.cpp: Fixing Windows CPU usage, external live frame. 2021-11-18 Chris Ahlstrom * README.md, TODO, libseq66/include/cfg/configfile.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/seq.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/pmlinuxalsa.c, seq_portmidi/src/pmutil.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_portmidi/src/ptlinux.c, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp: Portmidi updates, added qt_timer() function. 2021-11-18 Chris Ahlstrom * TODO, doc/latex/tex/concepts.tex, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midicontrolout.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/src/qseqeditframe64.cpp: Turned off the show/toggle NSM hack. 2021-11-17 Chris Ahlstrom * TODO, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.hpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/util/basic_macros.cpp, libsessions/src/nsm/nsmbase.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/src/qt5nsmanager.cpp: Progress on issues #41, #64, and #67. 2021-11-17 Chris Ahlstrom * libseq66/src/sessions/clinsmanager.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/src/qt5nsmanager.cpp: Improved robustness of filename_concatenate, untested in most scenarios. 2021-11-16 Chris Ahlstrom * README.md, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Added record-mode for record-style to select normal, quantize, and tighten functions. 2021-11-15 Chris Ahlstrom * README.md, doc/latex/tex/meta_events.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslotbutton.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in, UI tweaks, loop-mode debugging. 2021-11-13 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp: Work in progress, support for loop-control-mode. 2021-11-12 Chris Ahlstrom * README.md, TODO, include/config.h.in, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/midicontrol.hpp, libseq66/include/util/basic_macros.h, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/midicontrol.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qloopbutton.cpp: Basic MIDI control refactoring and fixes in place. 2021-11-12 Chris Ahlstrom * : Merge conflicts twixt control and optimizing bug-fix. 2021-11-12 Chris Ahlstrom * README.md, VERSION, configure.ac, include/config.h.in, libseq66/src/play/performer.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qslivegrid.cpp: Version 0.97.2.1 bug-fix pending. 2021-11-11 Chris Ahlstrom * NEWS, README.md, TODO, VERSION, configure, configure.ac, data/readme.txt, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/configfile.hpp, libseq66/include/ctrl/midioperation.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontrol.cpp, libseq66/src/ctrl/midicontrol.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qsetmaster.cpp: Added d1 parameter to MIDI control, currently BROKEN. 2021-11-11 Chris Ahlstrom * : Additional notes for 0.9.7.2. 2021-11-11 Chris Ahlstrom * : Version 0.97.2 pending. 2021-11-11 Chris Ahlstrom * README.md, VERSION, configure.ac, doc/latex/tex/configuration.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/ctrl/midioperation.hpp, libseq66/src/cfg/midicontrolfile.cpp: Prep for 0.97.2 release. 2021-11-10 Chris Ahlstrom * contrib/{notes/git.txt => git/git.text}, contrib/git/gitconfig, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/play/setmapper.hpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp: Retweaking external live grid, activate button. 2021-11-09 Chris Ahlstrom * README.md, data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/launchpad_mini.tex, libseq66/include/seq66_features.hpp, libseq66/include/util/basic_macros.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/screenset.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivebase.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: External live grid no longer changes active play-screen. 2021-11-08 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/forms/qliveframeex.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in, minor refactoring for external grid support in progress. 2021-11-07 Chris Ahlstrom * README.md, TODO, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/song_editor.tex, libseq66/include/midi/editable_event.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in, work on external live grid. 2021-11-07 Chris Ahlstrom * TODO, VERSION, configure.ac, doc/latex/tex/event_editor.tex, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp: Tweaked and documented the event-editor, new screenshots. 2021-11-06 Chris Ahlstrom * README.md, doc/latex/tex/midi_formats.tex, libseq66/include/midi/midifile.hpp, libseq66/include/play/mutegroup.hpp, libseq66/include/play/mutegroups.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/util/calculations.cpp: Fixed botched handling of mute-groups in the MIDI file, updated MIDI format documentation. 2021-11-05 Chris Ahlstrom * README.md, data/samples/textfix.qss, libseq66/include/play/performer.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/calculations.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsmainwnd.cpp: Song editor GUI tweaks, mute-modification fixes. 2021-11-04 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsmainwnd.cpp: Working on enabling MIDI file save on mute-group modifications. 2021-11-04 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.ctrl, data/linux/qseq66.drums, data/linux/qseq66.mutes, data/linux/qseq66.palette, data/linux/qseq66.playlist, data/linux/qseq66.rc, data/linux/qseq66.usr, doc/latex/tex/mutes.tex, libseq66/include/cfg/configfile.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qt5_helpers.cpp: More streamlining of configuration writing. 2021-11-03 Chris Ahlstrom * libseq66/include/play/mutegroups.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qmutemaster.cpp: Fixed mutegroups parsing bug introduced by new feature. 2021-11-02 Chris Ahlstrom * contrib/code/qsliveframe.cpp, data/linux/qseq66.mutes, doc/latex/tex/patterns_panel.tex, libseq66/include/midi/songsummary.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed set-name editing in table. 2021-11-01 Chris Ahlstrom * README.md, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp: Work on editing/storing/reading mute-group names. 2021-10-31 Chris Ahlstrom * README.md, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsetmaster.cpp: Safety check-in for set/mutes swapping, no progress bar in grid slots if muted. 2021-10-30 Chris Ahlstrom * libseq66/include/play/mutegroup.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsetmaster.cpp: Safety check-in for sets/mutes swapping. 2021-10-28 Chris Ahlstrom * README.md, data/linux/jack/pulseaudio/jack-post-start.sh, data/linux/jack/pulseaudio/jack-pre-stop.sh, libseq66/include/midi/jack_assistant.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/seq.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/util/calculations.cpp, libsessions/include/nsm/nsmserver.hpp, libsessions/src/nsm/nsmbase.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_alsa.cpp: Work on issue #64, #57, other fixes. 2021-10-27 Chris Ahlstrom * README.md, libseq66/src/cfg/usrfile.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp: Added Edit / Preferences / Bold Grid Slot. 2021-10-27 Chris Ahlstrom * README.md, Seq66qt5/seq66qt5.cpp, data/linux/qseq66-lp-mini-swapped.ctrl, doc/latex/tex/configuration.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/os/daemonize.hpp, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.hpp, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qt5nsmanager.cpp: Config reload/restart works, upgraded Edit / Preferences. 2021-10-27 Chris Ahlstrom * data/linux/qseq66-lp-mini-swapped.ctrl, data/linux/qseq66-swapped.ctrl, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qt5nsmanager.cpp: Added swapped ctrl files, quit(). 2021-10-26 Chris Ahlstrom * README.md, Seq66qt5/seq66qt5.cpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Got issue #63 working for the live grid, also work on app reload. 2021-10-24 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/screenset.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qslivegrid.cpp: Interim check-in experimenting with row/column swap fro screensets. 2021-10-23 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/os/daemonize.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/src/qt5_helpers.cpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_info.cpp: Console message clean-up. 2021-10-22 Chris Ahlstrom * README.md, TODO, doc/latex/tex/alsa.tex, doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/sessions/smanager.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeventframe.ui: Added jack-connect options for disabling automatic JACK connection from command-line. 2021-10-21 Chris Ahlstrom * configure, contrib/scripts/make-checkout, include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, seq_qt5/forms/qsabout.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qsabout.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_jack_info.cpp: Interim check-in for issue #60 etc. 2021-10-20 Chris Ahlstrom * VERSION, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Prep for 0.97.2. 2021-10-20 Chris Ahlstrom * ChangeLog: Version 0.97.1 pending. 2021-10-20 Chris Ahlstrom * README.md, VERSION, configure.ac, contrib/code/qseqeditframe.cpp, contrib/code/qsliveframe.cpp, data/samples/textfix.qss, doc/latex/tex/configuration.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/usrsettings.hpp, libseq66/include/seq66_features.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsbuildinfo.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp: Work on issue #57, issue #58, issue #59, and issue #61. 2021-10-19 Chris Ahlstrom * README.md, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/event.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/event.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed regression in note display in data/event panels, some minor doc and code updates. 2021-10-18 Chris Ahlstrom * README.md, contrib/code/qsliveframe.cpp, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Exponential LFO basically done. 2021-10-17 Chris Ahlstrom * libseq66/include/util/calculations.hpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qseqeditex.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp: Added exponential LFO, still needs work. 2021-10-16 Chris Ahlstrom * doc/latex/tex/pattern_editor.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qperfnames.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp: Interim check-in, very minor doc and code updates. 2021-10-15 Chris Ahlstrom * README.md, doc/latex/tex/patterns_panel.tex, seq_qt5/forms/qliveframeex.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmaintime.cpp, seq_qt5/src/qsmainwnd.cpp: PNG optimization, set fixes, more tweaks. 2021-10-14 Chris Ahlstrom * README.md, contrib/scripts/make-checkout, debian/README, doc/latex/tex/configuration.tex, doc/latex/tex/event_editor.tex, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/references.tex, doc/latex/tex/song_editor.tex, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in, notable doc upgrade, many little fixes. 2021-10-13 Chris Ahlstrom * INSTALL, Makefile.in, README.md, VERSION, aux-files/ltmain.sh, bootstrap, configure, configure.ac, contrib/scripts/strap_functions, data/README, data/linux/qseq66.ctrl, data/linux/qseq66.drums, data/linux/qseq66.mutes, data/linux/qseq66.palette, data/linux/qseq66.playlist, data/linux/qseq66.rc, data/linux/qseq66.usr, data/readme.txt, data/readme.windows, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/seq66_features.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/seq66_features.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/palettefile.cpp, seq_qt5/src/qseditoptions.cpp: Minor tweaks to version, icons, UI, preferences dialog, and documentation. 2021-10-12 Chris Ahlstrom * Makefile.in, VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Setup for 0.97.1. 2021-10-12 Chris Ahlstrom * ChangeLog, seq_qt5/forms/qsmainwnd.ui: Version 0.97.0 pending. 2021-10-12 Chris Ahlstrom * INSTALL, Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, VERSION, configure, configure.ac, contrib/scripts/QjackCtl.conf, contrib/scripts/README, contrib/scripts/htmldoc, data/Makefile.in, data/linux/jack/README, data/linux/jack/pulseaudio/jack-post-start.sh, data/linux/jack/pulseaudio/jack-post-stop.sh, data/linux/jack/pulseaudio/jack-pre-start.sh, data/linux/jack/pulseaudio/jack-pre-stop.sh, data/linux/jack/pulseaudio/repulse, data/linux/{ => jack}/startjack, data/linux/{ => jack}/startqjack, doc/Makefile.in, doc/README, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, doc/latex/tex/jack.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Makefile and documentation updates. 2021-10-11 Chris Ahlstrom * contrib/scripts/timid, contrib/scripts/ystart: Minor script updates. 2021-10-08 Chris Ahlstrom * libseq66/include/cfg/rcsettings.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: More modified-handling improvements. 2021-10-07 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qsmainwnd.cpp: Improved modified handling in the main window. 2021-10-06 Chris Ahlstrom * README.md, doc/latex/tex/configuration.tex, doc/latex/tex/midi_export.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, man/sequencer66.1, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Implemented convert-to-smf-0 menu item, convert-to-smf-1 usr flag, modified file visibility. 2021-10-05 Chris Ahlstrom * include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/seq66_features.cpp, seq_qt5/include/qperfnames.hpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Interim check-in, Windows build fixes and SMF 0 fixes. 2021-10-04 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/midi/midifile.hpp, libseq66/src/Makefile.in, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Mostly makefile-in updates. 2021-10-04 Chris Ahlstrom * README.md, configure.ac, data/README, data/linux/qseq66.usr, data/readme.txt, data/readme.windows, debian/seq66.desktop, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/midi_vector.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midi_vector.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/seq.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, resources/pixmaps/Makefile.am, resources/pixmaps/{route66rwb-66x66.xpm => route66rwb-64x64.xpm}, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qsmainwnd.cpp: Date bump, support for SMF 0 conversion continued. 2021-10-02 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in for experimental conversion to SMF 0. 2021-10-01 Chris Ahlstrom * README.md, doc/latex/tex/midi_export.tex, doc/latex/tex/midi_formats.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Prep for experimental conversion to SMF 0. 2021-09-29 Chris Ahlstrom * contrib/code/qseqeditframe.cpp, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/event.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qstriggereditor.cpp: Now trying to tighten up event-status and channel handling. 2021-09-28 Chris Ahlstrom * libseq66/include/midi/event.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qstriggereditor.cpp, seq_rtmidi/src/midi_jack.cpp: Interim check-in, event channel/status fixes. 2021-09-27 Chris Ahlstrom * doc/latex/tex/configuration.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/song_editor.tex, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Fixing LFO for tempo events, GUI update fixes. 2021-09-26 Chris Ahlstrom * README.md, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/midi/event.hpp, libseq66/src/util/calculations.cpp, seq_qt5/include/qseqdata.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqroll.cpp: Interim check-in, still more tempo improvements. 2021-09-25 Chris Ahlstrom * libseq66/include/midi/event.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Still more improvements to display/edit tempo. 2021-09-24 Chris Ahlstrom * README.md, TODO, contrib/code/qsliveframe.cpp, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qstriggereditor.cpp, seq_rtmidi/src/midi_alsa.cpp: More improvements to tempo editing, more to come. 2021-09-23 Chris Ahlstrom * libseq66/include/midi/event.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qstriggereditor.cpp: Interim check-in of upgraded tempo support. 2021-09-22 Chris Ahlstrom * README.md, include/config.h.in, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Added some primitive tempo display. 2021-09-21 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.usr, doc/latex/tex/configuration.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: Added more settings like lock-main-window. 2021-09-20 Chris Ahlstrom * NEWS, README.md, TODO, contrib/scripts/bluejack, data/linux/qseq66.rc, data/seq66cli/seq66cli.rc, data/win/qpseq66.rc, doc/dox/doxy-common.cfg, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, seq_qt5/forms/qsabout.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qsabout.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Work on issue #21, qss configuration, font scaling. 2021-09-16 Chris Ahlstrom * README.md, doc/latex/tex/configuration.tex, doc/latex/tex/pattern_editor.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp: Improved wrapped-note handling and drawing of slot buttons. 2021-09-15 Chris Ahlstrom * doc/latex/tex/pattern_editor.tex, libseq66/src/seq66_features.cpp, seq_rtmidi/src/rtmidi.cpp: Updating the handling of version information items. 2021-09-15 Chris Ahlstrom * contrib/midnam/Roland_MT-32.midnam: Added a sample midnam file for future research. 2021-09-15 Chris Ahlstrom * INSTALL, README.md, doc/latex/tex/pattern_editor.tex, doc/latex/tex/references.tex, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/sequence.cpp, libseq66/src/seq66_features.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qt5nsmanager.cpp: Added build settings, fix for unlinked notes. 2021-09-14 Chris Ahlstrom * README.md, include/config.h.in, libseq66/src/cfg/rcsettings.cpp, seq_qt5/forms/qseditoptions.ui: Updated config.h.in, made rc-save the default again. 2021-09-14 Chris Ahlstrom * README.md, data/linux/qseq66.rc, doc/latex/tex/playlist.tex, include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Can now set config files from the UI. 2021-09-13 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: All Makefile.in files modified in our base distro, Ubuntu. 2021-09-13 Chris Ahlstrom * libseq66/include/util/named_bools.hpp, libseq66/src/util/named_bools.cpp: Forgot to add the named_bools class code. 2021-09-13 Chris Ahlstrom * README.md, doc/latex/tex/pattern_editor.tex, doc/latex/tex/song_editor.tex, libseq66/include/Makefile.am, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/palette.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseditoptions.cpp: Making configuration file UI settings and tightening configuration handling in progress. 2021-09-12 Chris Ahlstrom * INSTALL, NEWS, README.md, TODO, VERSION, configure.ac, doc/latex/tex/port_mapping.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, m4/ax_pthread.m4, man/sequencer66.1, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseditoptions.cpp: Interim check-in, adding vertical zoom in song editor. 2021-09-10 Chris Ahlstrom * ChangeLog: Version 0.96.3 2021-09-10 Chris Ahlstrom * configure.ac, doc/latex/tex/sessions.tex, include/config.h.in, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/forms/qsbuildinfo.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/src/midi_jack_info.cpp: Pretty much done with JACK session management. 2021-09-08 Chris Ahlstrom * README.md, contrib/key-map.rc, contrib/scripts/QjackCtl.conf, contrib/scripts/q-make, data/samples/dark-gradient.qss, data/samples/flat-rounded.qss, data/samples/grey-ghost.qss, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, include/config.h.in, libseq66/libseq66.pro, seq66.pro, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qt5nsmanager.cpp: Documentation and minor tweaks. 2021-09-08 Chris Ahlstrom * : commit 0d63bf24182c229d2b95415071c8b4bdef48a0a6 Author: Chris Ahlstrom Date: Wed Sep 8 06:02:03 2021 -0400 2021-09-03 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Updated Makefile.in files for removal of lash. 2021-09-03 Chris Ahlstrom * INSTALL, README.md, Seq66cli/Makefile.am, Seq66qt5/Makefile.am, bootstrap, configure.ac, configure.help, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/sessions.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/seq66_features.h, libseq66/src/Makefile.am, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/include/Makefile.am, libsessions/include/lash/lash.hpp, libsessions/libsessions.pro, libsessions/src/Makefile.am, libsessions/src/lash/lash.cpp, man/sequencer66.1, seq_portmidi/src/Makefile.am, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/Makefile.am, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/include/Makefile.am, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack_info.cpp: Making session usage configurable. 2021-09-03 Chris Ahlstrom * data/linux/startjack, data/linux/startqjack, doc/latex/tex/configuration.tex, doc/latex/tex/jack.tex, doc/latex/tex/references.tex, doc/latex/tex/sessions.tex, include/config.h.in, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/midi/jack_assistant.cpp, man/sequencer66.1: Interim check-in of JACK session updates. 2021-08-31 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/cfg/rcsettings.hpp, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: More configure fixes, compiler error on Ubuntu. 2021-08-31 Chris Ahlstrom * README.md, VERSION, configure.ac, configure.help, doc/latex/tex/jack.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/os/daemonize.hpp, libseq66/include/play/performer.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, m4/ax_pthread.m4, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Configure upgrades and refactoring for JACK session in progress. 2021-08-28 Chris Ahlstrom * data/linux/qseq66.rc, doc/latex/tex/configuration.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/sessions.tex, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp: Beefed up event editing and port-mapping. 2021-08-22 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.rc, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/listsbase.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp: Fixed lookup for the Qsynth/FluidSynth port in ALSA. 2021-08-21 Chris Ahlstrom * README.md, libseq66/src/midi/event.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Can now modify Note Off/On at same time in event editor. 2021-08-19 Chris Ahlstrom * configure, libseq66/include/midi/editable_events.hpp, libseq66/src/midi/editable_events.cpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Added event table reload to show Note pair changes, very krufty. 2021-08-19 Chris Ahlstrom * libseq66/include/midi/editable_event.hpp, libseq66/include/midi/editable_events.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Interim check-in, getting note on/off editing to work smoothly. 2021-08-17 Chris Ahlstrom * libseq66/include/midi/editable_event.hpp, libseq66/include/midi/editable_events.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/editable_events.cpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Interim check-in, work on note-event editing. 2021-08-16 Chris Ahlstrom * NEWS, README.md, TODO, VERSION, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/src/midi/midi_vector_base.cpp, seq_qt5/src/qseventslots.cpp: Version bump. 2021-08-15 Chris Ahlstrom * ChangeLog, seq_qt5/src/qsmainwnd.cpp: Version 0.96.2 pending. 2021-08-15 Chris Ahlstrom * README.md, VERSION, configure.ac, doc/latex/tex/pattern_editor.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqroll.cpp: Added Ctrl_N/E for selecting events by channel. 2021-08-13 Chris Ahlstrom * README.md, contrib/code/qseqeditframe.cpp, doc/dox/doxy-common.cfg, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/eventlist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_platform_macros.h, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp: Interim check-in of clean-up and recording handling. 2021-08-12 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, resources/pixmaps/menu_empty_inv.xpm, resources/pixmaps/menu_full_inv.xpm, seq_qt5/src/qseqeditframe64.cpp: Added 'usr' option for adapting to dark themes. 2021-08-12 Chris Ahlstrom * INSTALL, README.md, doc/dox/doxy-common.cfg, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qstriggereditor.cpp: Still more channel fixes, added sets-mode config UI. 2021-08-11 Chris Ahlstrom * libseq66/include/midi/editable_events.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/sequence.cpp, seq_portmidi/src/midibus.cpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qstriggereditor.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack.cpp: Almost done with the new channel handling. 2021-08-10 Chris Ahlstrom * libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, seq_portmidi/src/mastermidibus.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qstriggereditor.cpp: More channel-handling improvements. 2021-08-09 Chris Ahlstrom * doc/latex/tex/midi_formats.tex, libseq66/include/midi/event.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp: Tightening event/pattern channel handling, in progress. 2021-08-07 Chris Ahlstrom * configure, seq_portmidi/src/mastermidibus.cpp: Updated configure script, removed disabled portmidi code. 2021-08-07 Chris Ahlstrom * README.md, TODO, contrib/code/pthread_performer.cpp, contrib/code/qrollframe.cpp, contrib/code/qrollframe.hpp, {seq_portmidi/src => contrib/code}/readbinaryplist.c, {seq_portmidi/include => contrib/code}/readbinaryplist.h, contrib/notes/bluez-alsa-notes.text, contrib/scripts/bluejack, data/linux/startjack, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/midi/event.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/event.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, seq_portmidi/seq_portmidi.pro, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/pmmacosxcm.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_portmidi/src/ptmacosx_cf.c, seq_rtmidi/src/midi_jack.cpp: Fixes to incoming note handling while fixing issue #55. 2021-08-04 Chris Ahlstrom * README.md, TODO, contrib/notes/bluez-alsa-notes.text, libseq66/include/midi/mastermidibase.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_rtmidi/src/midi_alsa.cpp: Updated handling of preview keys in virtual keyboard. 2021-08-03 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.rc, data/seq66cli/seq66cli.rc, data/win/qpseq66.rc, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, seq_qt5/forms/qsabout.ui, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: rc fix, buss-override improvements. 2021-08-02 Chris Ahlstrom * README.md, TODO, VERSION, configure.ac, doc/latex/tex/menu.tex, doc/latex/tex/song_editor.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/jack_assistant.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qsmainwnd.cpp: New version info, JACK pause improvements. 2021-08-01 Chris Ahlstrom * ChangeLog: Version 0.96.1 pending. 2021-08-01 Chris Ahlstrom * libseq66/include/Makefile.in, seq_qt5/include/Makefile.in: Official makefile updates for 0.96.1. 2021-08-01 Chris Ahlstrom * README.md, TODO, VERSION, configure.ac, contrib/code/qsliveframe.cpp, doc/dox/libseq66/libseq66.cfg, doc/latex/tex/launchpad_mini.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/Makefile.am, libseq66/include/qt/qsmacros.hpp, libseq66/libseq66.pro, libseq66/src/cfg/usrsettings.cpp, seq_qt5/include/Makefile.am, seq_qt5/include/qsmacros.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Updated version date, removed obsolete qsmacros header. 2021-08-01 Chris Ahlstrom * README.md, TODO, doc/latex/tex/launchpad_mini.tex, libseq66/include/cfg/playlistfile.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/play/triggers.hpp, libseq66/include/util/calculations.hpp, libseq66/include/util/recmutex.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/play/triggers.cpp, libseq66/src/util/recmutex.cpp, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/midibus.cpp, seq_portmidi/src/pmlinuxalsa.c, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_probe.cpp: Cleanup of macros and attempting to fix a panic/Launchpad bug. 2021-07-31 Chris Ahlstrom * libseq66/include/cfg/configfile.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, seq_qt5/src/qperfnames.cpp: Config-file streamlining, ongoing set-handling imporovements. 2021-07-29 Chris Ahlstrom * libseq66/include/play/screenset.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qsmainwnd.cpp: Working on improving set handling. 2021-07-28 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.usr, doc/latex/tex/menu.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqkeys.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Added feature to copy/paste all patterns in a screenset. 2021-07-27 Chris Ahlstrom * doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Working on sets improvements. 2021-07-26 Chris Ahlstrom * README.md, TODO, data/midi/Kraftwerk-Europe_Endless.text, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/basesettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/seq.hpp, libseq66/src/cfg/basesettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp: Added progress-box scaling, drag-n-drop of patterns. 2021-07-25 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.rc, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: Beefed up recent file configuration. 2021-07-24 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Finished Display tab in qseditoptions. 2021-07-24 Chris Ahlstrom * README.md, TODO, doc/latex/tex/event_editor.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/editable_event.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/editable_event.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeventframe.cpp: Adding some config options to qseditoptions in progress. 2021-07-22 Chris Ahlstrom * seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp: Setting up program/control combo box programatically, has weird issues. 2021-07-22 Chris Ahlstrom * TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/controllers.hpp, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/editable_events.hpp, libseq66/src/midi/controllers.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp: Improved controller-name access, added patch/program array. 2021-07-20 Chris Ahlstrom * libseq66/include/Makefile.in: Updated Makefile.in re app_limits.h header. 2021-07-20 Chris Ahlstrom * contrib/code/qseqeditframe.cpp, doc/dox/libseq66/libseq66.cfg, doc/dox/libsessions/libsessions.cfg, libseq66/include/Makefile.am, libseq66/include/app_limits.h, libseq66/include/cfg/settings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/midi/wrkfile.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.h, libseq66/include/util/calculations.hpp, libseq66/libseq66.pro, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/calculations.cpp, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/midibus.cpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qseqbase.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qbase.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/mastermidibus.cpp: Eliminated the app_limits.h header file. 2021-07-19 Chris Ahlstrom * libseq66/include/app_limits.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usermidibus.hpp, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/mutegroup.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/usermidibus.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/calculations.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qstriggereditor.hpp, seq_rtmidi/include/midi_info.hpp: Moved still more constants from app_limits.h. 2021-07-19 Chris Ahlstrom * TODO, doc/latex/tex/midi_formats.tex, libseq66/include/app_limits.h, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_portmidi/include/mastermidibus_pm.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmaintime.cpp, seq_rtmidi/include/mastermidibus_rm.hpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/src/midi_probe.cpp: Moving more manifest constants into usrsettings. 2023-07-19 ahlstrom * README.md, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/windows.tex, libseq66/src/ctrl/keymap.cpp, libseq66/src/ctrl/winkeys.hpp: Doc updates and Windows/AZERTY keymap tweaks. 2023-07-18 Chris Ahlstrom * README.md, RELNOTES, contrib/notes/win-virtual-keys.text, libseq66/src/ctrl/keymap.cpp, libseq66/src/ctrl/winkeys.hpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qt5_helpers.cpp: Added Windows key-mapping module. 2023-07-17 Chris Ahlstrom * data/readme.text, data/readme.windows, data/win/win_midi.playlist, nsis/README, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_qt5/src/qt5_helpers.cpp: Windows installer updates and fixes. * README.md, TODO, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qsmainwnd.cpp: Minor playlist fixes. 2023-07-16 Chris Ahlstrom * INSTALL, TODO, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.h, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, resources/pixmaps/panic2.xpm, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp: Auto-advance seems to be perfected, let us pray. 2023-07-14 Chris Ahlstrom * README.md, RELNOTES, TODO, doc/latex/tex/playlist.tex, libseq66/include/play/playlist.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qplaylistframe.cpp: Auto-advance almost working. 2023-07-13 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/src/qplaylistframe.cpp: Auto-play improved, added support for play-list auto-advance. 2023-07-12 Chris Ahlstrom * README.md, RELNOTES, configure.ac, doc/latex/tex/playlist.tex, include/config.h.in, libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp: Added auto-play support to playlists, needs some tinkering. * README.md, RELNOTES, TODO, libseq66/src/play/performer.cpp: Fixed a nasty bug in output port-mapping lookup. 2023-07-11 Chris Ahlstrom * TODO, data/samples/ca_midi.playlist, doc/latex/tex/configuration.tex, doc/latex/tex/playlist.tex, libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed handling MIDI file paths in playlists. 2023-07-10 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/src/qplaylistframe.cpp: Playlist UI tweaks, added list activation function. 2023-07-09 ahlstrom * TODO, libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: More playlist revamping, nearly done. 2023-07-08 ahlstrom * README.md, TODO, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: More playlist revamping, much more to come. 2023-07-07 Chris Ahlstrom * TODO, doc/latex/tex/first_start.tex, doc/latex/tex/windows.tex, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp: Starting improvement of Playlists tab. 2023-07-06 Chris Ahlstrom * README.md, RELNOTES, TODO, libseq66/include/midi/midifile.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed BPM saving error in Windows, issue #110. 2023-07-05 Chris Ahlstrom * README.md, libseq66/include/midi/calculations.hpp, libseq66/include/midi/editable_events.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/editable_events.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qstriggereditor.cpp: Issue #111 time-sig insertion solved. 2023-07-03 Chris Ahlstrom * README.md, TODO, libseq66/include/midi/calculations.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqtime.cpp: Some more fixes to time-signature analysis. 2023-07-01 Chris Ahlstrom * INSTALL, README.md, TODO, VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.h, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqtime.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qstriggereditor.cpp: Cleanup for next version of Seq66. * ChangeLog, RELNOTES, contrib/git/git.text, {nsis => contrib/scripts}/build_debug_code.bat, data/license.text, data/readme.text, data/readme.windows, doc/latex/tex/port_mapping.tex, nsis/README, nsis/build_release_package.bat, nsis/winddeploybruteforce.bat: Release Notes for Seq66 v. 0.99.6 2023-07-01 * README.md, doc/latex/tex/references.tex, seq_portmidi/src/pmwinmm.c: Minor doc updates. 2023-06-29 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, contrib/scripts/alsa.m4, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, m4/alsa.m4, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Updated alsa.m4 to avoid AC_TRY_COMPILE warnings on Arch Linux. * README.md, RELNOTES, VERSION, configure.ac, contrib/git/git.text, data/readme.text, data/readme.windows, data/testing/mapping-snippet.rc, data/testing/sixteen-ports-snippet.rc, doc/latex/tex/port_mapping.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/midi/businfo.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseqtime.cpp: Portmap fixes, date/doc updates, seqtime markers fixed. 2023-06-28 Chris Ahlstrom * README.md, TODO, data/readme.windows, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/references.tex, doc/latex/tex/windows.tex, libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibase.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, seq_portmidi/include/midibus_pm.hpp, seq_portmidi/include/pminternal.h, seq_portmidi/src/midibus.cpp, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_qt5/forms/qseditoptions.ui: Solidified Windows MIDI Mapper handling for issue #110. 2023-06-27 Chris Ahlstrom * TODO, data/linux/qseq66.rc, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/windows.tex, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseqdata.cpp: Minor tweaks and clean-up. 2023-06-26 Chris Ahlstrom * README.md, TODO, data/readme.text, doc/latex/tex/menu.tex, doc/latex/tex/port_mapping.tex, libseq66/include/midi/midibus_common.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/portslist.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp, libseq66/src/play/songsummary.cpp, nsis/build_release_package.bat, seq_portmidi/src/midibus.cpp, seq_portmidi/src/portmidi.c, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qseqtime.cpp: Added unavailable flag to port handling and mapping. 2023-06-24 ahlstrom * README.md, RELNOTES, TODO, doc/latex/tex/midi_formats.tex, doc/latex/tex/pattern_editor.tex, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Added time-sig display to timeline. 2023-06-23 Chris Ahlstrom * README.md, TODO, doc/latex/tex/menu.tex, libseq66/include/cfg/usrsettings.hpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqbase.hpp, seq_qt5/include/qseqeditex.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Improved modification management, added grid space setting to UI. 2023-06-22 Chris Ahlstrom * INSTALL, README.md, RELNOTES, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/calculations.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.h, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqtime.cpp: Enabled time-sig drawing in seq66_features, upgraded measure calculation. 2023-06-21 Chris Ahlstrom * README.md, TODO, libseq66/include/midi/calculations.hpp, libseq66/include/midi/editable_events.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqtime.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5_helpers.cpp: Added time-sig drawing to qseqtime and qstriggereditor, disabled by SEQ66_TIME_SIG_DRAWING in sequence.hpp, time-sig fixes galore. 2023-06-18 ahlstrom * README.md, RELNOTES, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp: Fixed setting up the time-sig log button. 2023-06-17 Chris Ahlstrom * README.md, TODO, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Refactored meta and time-sig detection code. 2023-06-16 Chris Ahlstrom * NEWS, README.md, TODO, VERSION, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/midi/calculations.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Fixes to editing time-signature in event editor. 2023-06-15 Chris Ahlstrom * TODO, doc/latex/tex/pattern_editor.tex, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqtime.cpp: More work on adding time signatures. 2023-06-14 Chris Ahlstrom * Seq66qt5/Seq66qt5.pro, TODO, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, nsis/winddeploybruteforce.bat, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp: Wash hands of 32-bit Windows for now, fixed and improvd time-sig handling. 2023-06-12 Chris Ahlstrom * Seq66qt5/Seq66qt5.pro, include/config.h.in, nsis/Seq66Constants.nsh, nsis/build_release_package.bat, nsis/winddeploybruteforce.bat, seq_qt5/src/qseqroll.cpp: Seemingly futile attempt at Win32 build on a Win64 machine. 2023-06-09 Chris Ahlstrom * README.md, TODO, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Support for setting pattern editor to beginning time-signature in place. 2023-06-08 Chris Ahlstrom * README.md, TODO, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_vector.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5nsmanager.cpp: Work on issue #111 improving time-signature support, in progress. 2023-06-07 ahlstrom * TODO, libseq66/src/os/shellexecute.cpp, libseq66/src/play/performer.cpp, nsis/Seq66Constants.nsh, nsis/build_release_package.bat, nsis/winddeploybruteforce.bat: Trying to get a Windows 32-bit version to deploy. 2023-06-04 Chris Ahlstrom * README.md, TODO, doc/latex/tex/menu.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qt5nsmanager.cpp: Added quiet option, improved control/display options. 2023-06-03 ahlstrom * libseq66/include/seq66_features.h, seq_portmidi/include/portmidi.h, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/pmlinux.c, seq_portmidi/src/pmmac.c, seq_portmidi/src/pmmacosxcm.c, seq_portmidi/src/pmutil.c, seq_portmidi/src/pmwin.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_portmidi/src/ptmacosx_mach.c: Investigated Windows portmidi free error, disabled incomplete sysex processing. 2023-06-02 Chris Ahlstrom * README.md, TODO, libseq66/src/play/performer.cpp: Tweaks for remote work. 2023-06-01 ahlstrom * README.md, TODO, VERSION, configure.ac, doc/latex/tex/alsa.tex, doc/latex/tex/event_editor.tex, doc/latex/tex/jack.tex, doc/latex/tex/menu.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qsmainwnd.cpp: Documentation of port-map prompts. 2023-05-31 ahlstrom * libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp: Improved reporting of MIDI driver errors. * README.md, TODO, doc/latex/tex/first_start.tex, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Made port-map inconsistencies raise a prompt for a potential remapping and restart. 2023-05-30 Chris Ahlstrom * TODO, data/share/doc/tutorial/faq.html, data/share/doc/tutorial/left-tree.html: Added a couple of FAQs to the tutorial. * README.md, TODO, data/license.text, data/readme.text, libseq66/include/os/shellexecute.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/os/shellexecute.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qseqdata.cpp: Fixed tutorial/manual access for issue #110. 2023-05-28 ahlstrom * README.md, Seq66qt5/Seq66qt5.pro, TODO, libseq66/include/util/filefunctions.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, nsis/Seq66Setup.nsi, seq66.pro: Added code to delete gigantic log file. 2023-05-27 Chris Ahlstrom * contrib/notes/install-directories.text, libseq66/src/os/daemonize.cpp: Fixed stdio rerouting and added Windows icons. 2023-05-26 Chris Ahlstrom * seq_qt5/src/qsmainwnd.cpp: Minor tweak to qsmainwnd. * Seq66qt5/Seq66qt5.pro, contrib/notes/install-directories.text, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, resources/icons/route66.xpm, resources/seq66_win.rc, seq66.pro, seq_qt5/src/qsmainwnd.cpp: Better app icon support in progress. * README.md, Seq66qt5/seq66qt5.cpp, contrib/notes/install-directories.text, data/linux/qseq66.usr, resources/seq66_win.rc, seq66.pro, seq_qt5/src/qsmainwnd.cpp: Interim check-in for Windows icon handling. 2023-05-25 Chris Ahlstrom * README.md, libseq66/include/seq66_features.h, libseq66/include/seq66_features.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/smanager.cpp: Making log-file usage more automatic, need to debug under Windows. * data/linux/qseq66.usr, doc/latex/tex/menu.tex, libseq66/include/cfg/settings.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/os/shellexecute.cpp, seq_qt5/src/qsmainwnd.cpp: Refactored the handling of the tutorial and manual. 2023-05-24 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, TODO, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/os/shellexecute.cpp, libseq66/src/play/sequence.cpp, libseq66/src/seq66_features.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Fixed issue #110 to handle changing background sequence. * TODO, seq_qt5/include/qscrollslave.h, seq_qt5/src/qscrollslave.cpp, seq_qt5/src/qseqeditframe64.cpp: Forwarding direction events from qscrollslave to qscrollmaster. 2023-05-23 Chris Ahlstrom * TODO, doc/latex/tex/pattern_editor.tex, seq_qt5/include/qscrollslave.h, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qscrollslave.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: More issue #3 work and tentative fix for main tempo change. * README.md, RELNOTES, TODO, VERSION, configure, configure.ac, contrib/git/git.text, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, nsis/README, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5_helpers.cpp: Added qscrollslave class, keeps seqedit panes in sync now, issue #3. 2023-05-20 Chris Ahlstrom * ChangeLog, README.md, RELNOTES, TODO, contrib/git/git.text, data/readme.text, data/readme.windows, doc/latex/tex/first_start.tex, include/config.h.in, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, nsis/README, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_debug_code.bat, nsis/build_release_package.bat: Release Notes for Seq66 v. 0.99.5 2023-05-20 This file lists __major__ changes... * data/license.text, data/readme.text, data/readme.windows, nsis/README, nsis/Seq66Setup.nsi: Got 64-bit build/installer working. 2023-05-19 Chris Ahlstrom * VERSION, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Issue #110 is tentatively done, portfix branch. * TODO, doc/latex/tex/alsa.tex, doc/latex/tex/configuration.tex, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_portmidi/src/pmlinux.c, seq_portmidi/src/pmwin.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_portmidi/src/ptmacosx_cf.c, seq_portmidi/src/ptmacosx_mach.c: Eliminated calls to update_midi_buttons when just recording events, and nullified portmidi pointers after free(). 2023-05-18 Chris Ahlstrom * README.md, RELNOTES, doc/latex/tex/menu.tex, libseq66/include/play/performer.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_portmidi/src/pmwinmm.c, seq_qt5/include/qclocklayout.hpp, seq_qt5/include/qinputcheckbox.hpp, seq_qt5/include/qportwidget.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp: USB MIDI control/display works in Windows, issue with recording in Qt debugger. 2023-05-17 ahlstrom * README.md, TODO, data/readme.text, data/readme.windows, doc/latex/tex/first_start.tex, libseq66/include/play/performer.hpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Improved performer error reporting, and added ghosting of ports in dropdowns when they are no longer present. * TODO, data/readme.windows, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp: Finally got port handling almost airtight and playing tunes on Windows re issue #110. 2023-05-16 Chris Ahlstrom * NEWS, README.md, TODO, libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/portslist.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_rtmidi/include/midi_info.hpp: Big fix to synch the masterbus ports and port-maps when saving the 'rc' file. 2023-05-15 Chris Ahlstrom * libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/performer.cpp, seq_portmidi/src/pmlinuxalsa.c, seq_portmidi/src/pmutil.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c: More fixes for portmidi and port-mapping, still fails with Qsynth, Portmidi, with mapping off. 2023-05-14 Chris Ahlstrom * TODO, libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/play/performer.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, seq_portmidi/src/pmwinmm.c, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qseditoptions.cpp: Added code to display present-but-unavailable ports. 2023-05-13 ahlstrom * README.md, libseq66/include/util/basic_macros.h, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, seq_portmidi/include/midibus_pm.hpp, seq_portmidi/seq_portmidi.pro, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/midibus.cpp, seq_portmidi/src/pmlinuxalsa.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c: Enhancement and fixes to borken portmidi code re issue #110. 2023-05-11 Chris Ahlstrom * INSTALL, configure.ac, include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp: Working out config-file issues with normal and NSM usage. 2023-05-10 Chris Ahlstrom * README.md, TODO, contrib/git/git.text, doc/latex/tex/sessions.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, nsis/build_debug_code.bat, nsis/build_release_package.bat: Some successful tinkering for pathnames re issue #110. 2023-05-09 Chris Ahlstrom * TODO, data/license.text, data/readme.text, libseq66/include/seq66_features.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, libsessions/src/nsm/nsmclient.cpp, nsis/build_release_package.bat: Refactoring config/session directories for consistency, expect breakage for now. 2023-05-08 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.rc, libseq66/include/os/daemonize.hpp, libseq66/src/os/daemonize.cpp, libseq66/src/sessions/smanager.cpp, nsis/README, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_debug_code.bat, nsis/build_release_package.bat, nsis/x64.nsh, seq_qt5/forms/qseqeventframe.ui, seq_rtmidi/src/midi_alsa_info.cpp: Work on issue #110 in progress. 2023-05-07 ahlstrom * README.md, TODO, contrib/git/git.text, doc/latex/tex/configuration.tex, libseq66/include/midi/mastermidibase.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/sessions/smanager.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midibus.cpp: Fixed subtle bugs creating midi ports. 2023-05-06 ahlstrom * INSTALL, README.md, TODO, doc/latex/tex/port_mapping.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseditoptions.cpp: Enabled default port mapping, testing needed. 2023-05-05 Chris Ahlstrom * libseq66/src/midi/editable_event.cpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qsmainwnd.cpp: More progress on inserting meta text events. 2023-05-04 Chris Ahlstrom * README.md, doc/latex/tex/event_editor.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/scales.hpp, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midifile.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qslivegrid.cpp: Now able to insert Meta Text events in event editor. 2023-05-03 Chris Ahlstrom * TODO, contrib/git/git.text, doc/latex/tex/event_editor.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/sessions.tex, libseq66/include/cfg/scales.hpp, libseq66/include/midi/editable_event.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qseventslots.cpp: Event editing progress, added key-signature conversion functions. 2023-05-02 Chris Ahlstrom * doc/latex/tex/meta_events.tex, doc/latex/tex/midi_formats.tex, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qt5_helpers.cpp: Now can populate event combo based on event category. 2023-05-01 Chris Ahlstrom * configure, include/config.h.in, libseq66/include/midi/editable_event.hpp, libseq66/src/midi/editable_event.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qseventslots.cpp: Continuing work to add meta/text handling to event editor. 2023-04-30 Chris Ahlstrom * VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Initial version bump. * ChangeLog: Forget to update 0.99.4 Changelog. * README.md, RELNOTES, doc/latex/tex/sessions.tex, include/config.h.in: Version 0.99.4 issues fixed for #3, #48, #108, #109, and discovered issues. * libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qsessionframe.hpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp: Hidden work to support show meta text, in progress. 2023-04-29 ahlstrom * VERSION, configure.ac, contrib/git/git.text, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qsessionframe.cpp: Date bump, find-event code, fixes to qsessionframe form. * README.md, TODO, libseq66/include/midi/eventlist.hpp, libseq66/include/play/performer.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed mutes/session refresh, 256-char text limit, experimental meta-text enhancements started. 2023-04-28 Chris Ahlstrom * TODO, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qsessionframe.cpp: Future spinbox for song-info, need to fix 256-char limit. * libseq66/include/midi/midi_vector_base.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midi_vector.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qsessionframe.cpp: Fixed error writing meta text length to file. 2023-04-27 Chris Ahlstrom * README.md, TODO, contrib/midi/Carpet_of_the_Sun_karaoke_meta_text.text, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qslivegrid.cpp: Work in progress on adding song-info feature. 2023-04-26 Chris Ahlstrom * data/linux/qseq66.ctrl, libseq66/include/play/performer.hpp, libseq66/src/ctrl/keycontainer.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp: Modified default ctrl keystrokes. * README.md, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqroll.cpp: Fixed issues with note wrap-around and linking. 2023-04-25 Chris Ahlstrom * README.md, TODO, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qseqroll.cpp: Fixed issue #109 where exports lost event channels. * README.md, TODO, doc/latex/tex/event_editor.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/song_editor.tex, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp: Fixes to linking and drawing recorded notes. 2023-04-24 Chris Ahlstrom * README.md, seq_qt5/src/qsmainwnd.cpp: A potential fix to issue #108, was removing a widget after deleting. 2023-04-22 ahlstrom * README.md, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/pattern_editor.tex, libseq66/include/play/performer.hpp, libseq66/src/play/metro.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qscrollmaster.h, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqtime.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qstriggereditor.cpp: Mitigated issue #3 so that only the piano rolls can use the scroll wheel. 2023-04-20 Chris Ahlstrom * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qsbuildinfo.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/rtmidi.cpp: Revisited issue #48, fixed some minor issues, see README.md. 2023-04-19 ahlstrom * INSTALL, README.md, RELNOTES, contrib/scripts/qtctrun, libseq66/src/play/performer.cpp: Fixed showing port errors re MIDI control undefined. * contrib/git/git.text: Updated Git notes. * RELNOTES, RELNOTES.md, configure: Release Notes for Seq66 v. 0.99.4 (RELNOTES snipped for brevity here). * NEWS, README.md, RELNOTES.md, VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Stamping for version 0.99.4. Also need to figure out why the last multi-line commit message is a single line. * ChangeLog: Updated Changelog. * doc/latex/tex/configuration.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex: Merged portfix and updated documentation for version 0.99.3 * README.md, RELNOTES.md, TODO, VERSION, configure.ac, contrib/scripts/gdarkseq66, contrib/scripts/qtctrun, doc/latex/tex/port_mapping.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseditoptions.ui: Time to put version 0.99.3 to bed. 2023-04-18 Chris Ahlstrom * README.md, TODO, contrib/notes/launchpad.text, contrib/scripts/timid, data/linux/alsa_ports.rc, data/linux/jack_ports.rc, data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/launchpad_mini.tex, libseq66/include/midi/event.hpp, libseq66/src/midi/event.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseditoptions.cpp: Tweak mutes-test and added LaunchPad Mini macros. 2023-04-17 ahlstrom * data/linux/qseq66.usr, doc/latex/tex/configuration.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/src/qsmainwnd.cpp: Added usr option to disable learn-complete prompt. * TODO, contrib/midi/README, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qt5nsmanager.cpp: Refactoring mutes internals in progress, yeesh. 2023-04-16 ahlstrom * README.md, libseq66/include/midi/midifile.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qmutemaster.cpp: Fixed big bug in MIDI-only mute-groups. 2023-04-15 ahlstrom * README.md, doc/latex/tex/mutes.tex, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/rtmidi_info.cpp: Better handling of failure to open ALSA client. 2023-04-14 Chris Ahlstrom * README.md, data/linux/qseq66.mutes, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/songsummary.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: More work improving mute-group handling. 2023-04-12 Chris Ahlstrom * README.md, contrib/scripts/jackctl, data/linux/qseq66-lp-mini-alt.ctrl, libseq66/include/ctrl/keycontainer.hpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/play/performer.cpp: Working on improving mute-group handling. 2023-04-11 Chris Ahlstrom * README.md, data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/menu.tex, libseq66/include/play/performer.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Updated/documented LaunchPad Mini handling. 2023-04-10 Chris Ahlstrom * README.md, TODO, contrib/git/git.text, contrib/scripts/jackctl, data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/patterns_panel.tex, include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp: Improving launchpad display, but play causes file change, so be aware. 2023-04-08 ahlstrom * README.md, RELNOTES.md, TODO, VERSION, configure.ac, doc/dox/doxy-common.cfg, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/seq66_features.h, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/songsummary.cpp, seq_qt5/src/qsmainwnd.cpp: Date bump, fixed activating imported playlist. 2023-04-07 ahlstrom * libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/gui_palette_qt5.cpp: Fixed persistence of BPM in playlist via a static boolean. * README.md, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/performer.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Fixed CLI vs playlists and added Meta text handling. 2023-04-06 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp: Fixed unintended file change when BPM changes during playlist usage. 2023-04-05 ahlstrom * TODO, data/samples/ca_midi.playlist, doc/latex/tex/configuration.tex, libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/src/qplaylistframe.cpp: Still more tweaks to playlist handling. * data/Makefile.in, data/samples/ca_midi.playlist: Removed a couple of Yamaha demo tunes. 2023-04-04 ahlstrom * data/Makefile.am, data/Makefile.in, data/samples/ca_midi.playlist, libseq66/include/util/basic_macros.hpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp: Added playlist material to the installer. 2023-04-03 Chris Ahlstrom * INSTALL, doc/latex/tex/configuration.tex, doc/latex/tex/headless.tex, doc/latex/tex/menu.tex, include/config.h.in, libseq66/src/cfg/rcsettings.cpp: Fixed the --home option. * README.md, TODO, VERSION, configure.ac, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Fixed setting of config subdirectory. 2023-04-01 ahlstrom * TODO, libseq66/include/sessions/smanager.hpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp: Working on reading config for NSM and --home in progress. 2023-03-31 Chris Ahlstrom * libseq66/include/cfg/rcsettings.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Still trying to simplify config files with nsm. 2023-03-30 Chris Ahlstrom * configure.ac, include/config.h.in, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qsessionframe.ui: Added nsm debugging code. * Seq66cli/seq66rtcli.cpp, Seq66qt5/seq66qt5.cpp, doc/latex/tex/sessions.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: More progress on issue #40. 2023-03-29 Chris Ahlstrom * Makefile.in, README.md, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, TODO, aux-files/compile, aux-files/depcomp, aux-files/ltmain.sh, aux-files/missing, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/os/daemonize.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/Makefile.in, libseq66/src/os/daemonize.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/include/nsm/nsmbase.hpp, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/forms/qsabout.ui, seq_qt5/include/Makefile.in, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/Makefile.in, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Work on improving issue #40 for NSM support, still in progress. 2023-03-28 ahlstrom * seq_qt5/forms/qseditoptions.ui, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/qperfeditframe64.cpp: Adding Qt 5.15 disabling checks. 2023-03-27 ahlstrom * : commit 72bb4f9b782eb075261c34efca6d564f3a0d03c2 Author: ahlstrom Date: Mon Mar 27 17:23:14 2023 -0400 2023-03-27 Chris Ahlstrom * README.md, RELNOTES.md, TODO, VERSION, configure.ac, doc/latex/tex/sessions.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Date bump, log-file name now editable. * README.md, Seq66cli/seq66rtcli.cpp, doc/latex/tex/configuration.tex, doc/latex/tex/headless.tex, doc/latex/tex/jack.tex, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/os/daemonize.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/os/timing.cpp: Fixed handling of log files. 2023-03-26 ahlstrom * README.md, Seq66cli/seq66rtcli.cpp, contrib/VMPK.conf, contrib/scripts/README, contrib/scripts/qtctrun, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/include/os/daemonize.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp: Fixed daemonization, still need fix to reroute_stdio(). 2023-03-24 Chris Ahlstrom * Seq66cli/seq66rtcli.cpp, libseq66/include/os/daemonize.hpp, libseq66/src/os/daemonize.cpp: Daemoniztion works, functional testing needed. 2023-03-23 ahlstrom * Seq66cli/seq66rtcli.cpp, arch/package/PKGBUILD, contrib/gvim.rc, doc/latex/tex/palettes.tex, libseq66/include/os/daemonize.hpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/scales.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/os/daemonize.cpp, seq_qt5/src/qseqeditframe64.cpp: More dicking with daemonization. 2023-03-22 Chris Ahlstrom * Seq66cli/seq66rtcli.cpp, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/usrfile.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/os/daemonize.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/os/daemonize.cpp: Improvements to daemonization/logs, more debugging needed. 2023-03-19 Chris Ahlstrom * README.md, Seq66cli/seq66rtcli.cpp, configure, contrib/vim-syntax/c.vim, data/seq66cli/seq66cli.usr, libseq66/include/os/daemonize.hpp, libseq66/src/os/daemonize.cpp, seq_qt5/src/qloopbutton.cpp: Work on daemonization and log files in progress. 2023-03-18 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.ctrl, libseq66/include/ctrl/automation.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qslivegrid.cpp: Many fixes related to issue #107. 2023-03-17 Chris Ahlstrom * INSTALL, README.md, TODO, VERSION, configure, configure.ac, contrib/vim-syntax/cpp.vim, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/util/recmutex.hpp, libseq66/src/util/recmutex.cpp, seq_qt5/src/qseqeditframe64.cpp: Work on issue #107 and documentation, versioning. 2023-03-04 Chris Ahlstrom * : Updated main-windows image for GitHub. * README.md, VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version 0.99.2 pending. 2023-03-03 Chris Ahlstrom * doc/latex/tex/alsa.tex, doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, doc/latex/tex/event_editor.tex, doc/latex/tex/jack.tex, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/kudos.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/references.tex, doc/latex/tex/sessions.tex, doc/latex/tex/song_editor.tex: Still more documentation fixups. * doc/latex/tex/alsa.tex, doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, doc/latex/tex/event_editor.tex, doc/latex/tex/first_start.tex, doc/latex/tex/headless.tex, doc/latex/tex/jack.tex, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/meta_events.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/palettes.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/playlist.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, doc/latex/tex/setmaster.tex, doc/latex/tex/song_editor.tex, doc/latex/tex/windows.tex, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qseqroll.cpp: Optimizing latex documentation in progress. 2023-03-02 Chris Ahlstrom * INSTALL, README.md, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/docs-structure.tex, doc/latex/tex/first_start.tex, doc/latex/tex/kudos.tex, doc/latex/tex/live_grid.tex, doc/latex/tex/menu.tex, doc/latex/tex/mutes.tex, doc/latex/tex/seq66-user-manual.tex: Significant modification to user manual layout. 2023-03-01 Chris Ahlstrom * TODO, doc/dox/doxy-common.cfg, libseq66/include/midi/jack_assistant.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp: More TODO cleanup. 2023-03-01 Chris Ahlstrom * INSTALL, TODO, doc/latex/tex/midi_formats.tex, libseq66/include/play/songsummary.hpp, libseq66/include/seq66_features.h, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/songsummary.cpp, seq_portmidi/include/portmidi.h, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseqbase.cpp, seq_rtmidi/src/midi_alsa.cpp: Interim check-in, investigating macros. 2023-02-28 Chris Ahlstrom * data/linux/qseq66.palette, seq_qt5/src/gui_palette_qt5.cpp: Fixed some Qt gradient warnings. 2023-02-27 Chris Ahlstrom * : commit 7206c497d2e533ab42a02ee3f55653e6a0ca7f84 Author: Chris Ahlstrom Date: Mon Feb 27 19:47:10 2023 -0500 * : commit d66be924a37902246978a79d4f662b33f9fba62d Author: Chris Ahlstrom Date: Mon Feb 27 07:49:58 2023 -0500 * README.md, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp: Fixed palette file brush settings getting reset. 2023-02-26 Chris Ahlstrom * : commit 204ab0622fabc59c409f45061811f410025f6372 Merge: b1967d12 0047f5eb Author: Chris Ahlstrom Date: Sun Feb 26 08:52:25 2023 -0500 * seq_qt5/src/qslivegrid.cpp: Added credit for phuel fix #106. * TODO, doc/latex/tex/references.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/setmapper.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp: Minor docu tweaks and starting making linear gradient an option. * seq_qt5/src/qslivegrid.cpp: Mark the selected MIDI bus and channel in the pattern dropdown menu. 2023-01-27 Chris Ahlstrom * README.md, seq_qt5/src/qseqroll.cpp: Fixed background seq display with linear gradients. 2023-01-17 Chris Ahlstrom * README.md, VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/play/sequence.cpp: Work on issue #103 under JACK slave transport. 2023-01-16 Chris Ahlstrom * contrib/DIR_COLORS, contrib/vim-syntax/c.vim, contrib/vim-syntax/cpp.vim, contrib/vim.rc, include/config.h.in, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/comments.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/util/filefunctions.cpp, libsessions/src/nsm/nsmbase.cpp: Minor tweaks to contrib and source files. 2022-11-27 Chris Ahlstrom * ChangeLog, README.md, VERSION, configure.ac, contrib/code/ttymidi.c, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version 0.99.1 pending. * README.md, contrib/scripts/jackctl, doc/latex/tex/jack.tex, libseq66/include/cfg/rcsettings.hpp, seq_qt5/forms/qseditoptions.ui, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Readying for version 0.99.1. 2022-11-22 Chris Ahlstrom * README.md, libseq66/include/midi/event.hpp, libseq66/src/midi/midifile.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp: Tweaked the frame offset calculation in midi_jack_data to more closely match the ttymidi.c module. 2022-11-16 Chris Ahlstrom * : Added contrib/tests/testnumbers.ods. 2022-11-04 Chris Ahlstrom * INSTALL, doc/latex/tex/song_editor.tex, libseq66/include/play/performer.hpp, libseq66/include/util/ring_buffer.hpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp: Add gradient styling to grid progress boxes. 2022-10-29 Chris Ahlstrom * doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/song_editor.tex, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp: Very minor changes, mostly documentation. 2022-10-21 Chris Ahlstrom * README.md, TODO, doc/latex/tex/song_editor.tex, libseq66/src/midi/midibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/triggers.cpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qt5_helpers.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/rtmidi_types.cpp: Safety check-in, various minor fixes/tweaks. 2022-10-14 Chris Ahlstrom * libseq66/include/midi/midibytes.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/midi_jack_info.cpp: Interim safety check-in related to issue #100. 2022-10-11 Chris Ahlstrom * README.md, contrib/scripts/jackctl, include/config.h.in, libseq66/include/seq66_features.h, libseq66/src/midi/jack_assistant.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp, seq_rtmidi/src/midi_jack_info.cpp: Added lineargradient look to the piano rolls, to take a break from troubleshooting. 2022-10-10 Chris Ahlstrom * README.md, TODO, VERSION, configure.ac, contrib/midi/songtest.text, doc/latex/tex/song_editor.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/calculations.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/calculations.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_rtmidi/src/midi_jack_data.cpp: Issue #44 almost worked out. 2022-10-06 Chris Ahlstrom * README.md, TODO, contrib/midi/songtest.text, contrib/scripts/jackctl, doc/latex/tex/song_editor.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/settings.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, resources/pixmaps/song_rec_no_snap.xpm, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/midi_jack_info.cpp: Hacking at issues #44 and #100, issues still. 2022-09-30 Chris Ahlstrom * libseq66/include/util/ring_buffer.hpp, libseq66/src/midi/jack_assistant.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi_types.cpp: For issue #100, fixed a calculation error and playback at 4096 is reasonable, not yet perfect. 2022-09-27 Chris Ahlstrom * README.md, TODO, contrib/scripts/jackctl, contrib/scripts/recordpa, contrib/scripts/ystart, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.h, libseq66/include/util/ring_buffer.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/rtmidi_types.cpp: Still whacking at issue #100, calculations look correct but still glitches at 4096 frames per cycle. 2022-09-22 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, contrib/code/ring_buffer.hpp, libseq66/include/seq66_features.h, libseq66/include/util/ring_buffer.hpp, libseq66/src/util/ring_buffer.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/rtmidi_types.cpp: Initial smoke test of midi_message ring_buffer works. 2022-09-19 Chris Ahlstrom * contrib/code/ring_buffer.hpp, contrib/vim-syntax/cpp.vim, libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/seq66_features.h, libseq66/include/util/ring_buffer.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/midi/jack_assistant.cpp, libseq66/src/util/ring_buffer.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi_types.cpp: Adding ring_buffer to support midi_message directly. 2022-09-14 Chris Ahlstrom * README.md, contrib/scripts/recordpa, libseq66/src/play/performer.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_data.cpp: Made frame offset values static, fixed Stop/Master bug. 2022-09-13 Chris Ahlstrom * seq_rtmidi/src/midi_jack_data.cpp: Forgot to add new midi_jack_data cpp file. * README.md, TODO, contrib/notes/RELNOTES-0_99_0.md, libseq66/src/play/performer.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/seq_rtmidi.pro, seq_rtmidi/src/Makefile.am, seq_rtmidi/src/Makefile.in, seq_rtmidi/src/midi_jack.cpp: Moved jack_frame_offset() code to midi_jack_data, added default JACK frame-related data members, still need to adjust to changes when transport is running. 2022-09-11 Chris Ahlstrom * contrib/vim-syntax/c.vim, contrib/vim-syntax/cpp.vim, libseq66/include/midi/event.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.h, libseq66/src/midi/event.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/src/qseqtime.cpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/rtmidi_types.cpp: Added macros for using midi_message timestamps and 8-byte timestamps, have issue of JACK buffer overruns if active. 2022-09-07 Chris Ahlstrom * configure, libseq66/include/play/sequence.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/sequence.cpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi_types.cpp: For issue #100, added current tick value to each output event and to the ringbuffer data, seems to work, need further verification. 2022-09-04 Chris Ahlstrom * NEWS, README.md, RELNOTES.md, TODO, VERSION, configure.ac, contrib/git/git.text, data/share/metainfo/seq66.appdata.xml, doc/dox/doxy-common.cfg, doc/latex/tex/seq66-user-manual.tex, include/cli/seq66-config.h, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_debug_code.bat, nsis/build_release_package.bat, seq_qt5/include/qslivebase.hpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Revisited issue #93 and fixed pattern pasting and merging. 2022-09-03 Chris Ahlstrom * ChangeLog, VERSION, configure, configure.ac, contrib/git/git.text, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Updating for version 0.99.0 release. * README.md, RELNOTES.md, contrib/git/git.text: Added some notes on releases. * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Implemented clear and double grid modes, tweaked song recording. 2022-09-02 Chris Ahlstrom * README.md, TODO, doc/latex/tex/patterns_panel.tex, libseq66/include/play/triggers.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, resources/pixmaps/song_rec_no_snap.xpm, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: For issue #44, updates to song recording and snapping. 2022-09-01 Chris Ahlstrom * README.md, TODO, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed handling/saving measure changes. 2022-08-30 Chris Ahlstrom * README.md, TODO, libseq66/include/midi/calculations.hpp, libseq66/src/midi/calculations.cpp, seq_qt5/include/qbase.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes to PPQN handling in seqroll, perfroll, perftime, and mainwnd. 2022-08-29 Chris Ahlstrom * README.md, TODO, contrib/code/ametro.c, contrib/git/git.text, doc/latex/tex/seq66-user-manual.tex, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed detecting color change, MIDI Continue. * README.md, TODO, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qslivebase.cpp: Fixed sets option parsing and improved grid text sizing. 2022-08-26 Chris Ahlstrom * README.md, RELNOTES.md, TODO, doc/latex/tex/seq66-user-manual.tex, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolbase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, nsis/build_release_package.bat, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed note Ctrl-Z, 'ctrl' options, other to-dos. 2022-08-24 Chris Ahlstrom * README.md, TODO, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Partial fixes for issue #82 horizontal scaling. 2022-08-23 Chris Ahlstrom * TODO, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, libseq66/src/play/sequence.cpp: Finished issue #97 so that note entry behaves like Seq24. 2022-08-22 Chris Ahlstrom * README.md, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, libseq66/src/play/metro.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseditoptions.cpp: Fix for issue #54 Qt detection, doc update. * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, m4/ax_have_qt.m4, m4/ax_have_qt_min.m4, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Incorporated ax_have_qt serial 19 updates to fix issue #54. 2022-08-21 Chris Ahlstrom * README.md, RELNOTES.md, TODO, VERSION, bootstrap, bootstrap.help, configure.ac, contrib/git/gitconfig, data/share/doc/tutorial/home.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/metainfo/seq66.appdata.xml, doc/dox/doxy-common.cfg, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, include/cli/seq66-config.h, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/metro.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_debug_code.bat, nsis/build_release_package.bat, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Version bump to 0.99, fixes to background recording. 2022-08-17 Chris Ahlstrom * README.md, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqtime.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqtime.cpp: Fixed L/R marker handling, background recording follow Grid and Quan modes. 2022-08-16 Chris Ahlstrom * doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/play/metro.hpp, libseq66/include/play/performer.hpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp: Background recording essentially works for issue #98, some tweaks might be needed. 2022-08-15 Chris Ahlstrom * libseq66/include/play/metro.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Groundwork for issue #98 background recording laid. 2022-08-14 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Interim check-in for auto-recording, in its infancy. 2022-08-13 Chris Ahlstrom * README.md, doc/latex/tex/midi_formats.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp: Metronome count-in essentially works. 2022-08-12 Chris Ahlstrom * libseq66/include/play/metro.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp: Provisional macroed code for metro count-in. 2022-08-10 Chris Ahlstrom * TODO, contrib/vim-syntax/meson.vim, data/linux/qseq66.rc, doc/latex/tex/menu.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed some bugs in the metronome code, added more documentation. 2022-08-09 Chris Ahlstrom * : Added image for new Metronome tab, issue #98. * doc/latex/tex/menu.tex, libseq66/include/play/performer.hpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Metronome feature and configuration essentially working now. 2022-08-08 Chris Ahlstrom * README.md, RELNOTES.md, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsessionframe.cpp: Metro GUI config in progress, weird debug seqfault fixed. * README.md, data/linux/qseq66.rc, doc/latex/tex/configuration.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/metro.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp: Added metronome configuration to 'rc' file. 2022-08-07 Chris Ahlstrom * libseq66/include/midi/midibytes.hpp, libseq66/include/play/metro.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseqeditframe64.cpp: Added configuration class for metronome support, need to add code for the rc file. * README.md, TODO, libseq66/include/play/metro.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qslivegrid.cpp: More metronome work, niggling issues and configuration needed. 2022-08-06 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qsmainwnd.cpp: Metronome works, need to get it going in Song mode still. * libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, resources/pixmaps/metro.xpm, seq_qt5/forms/qslivegrid.ui, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: More progress on a metronome.... 2022-08-05 Chris Ahlstrom * libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/midi/calculations.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/metro.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/play/metro.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, resources/pixmaps/metro.xpm, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qslivegrid.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp: For issue #98, initial metronome code, still in progress. 2022-08-03 Chris Ahlstrom * README.md, RELNOTES.md, TODO, contrib/git/git.text, libseq66/include/util/rect.hpp, libseq66/src/os/shellexecute.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Some fixes to issue #97, added paste box and progress-bar Ctrl-arrow movement to pattern editor. 2022-08-01 Chris Ahlstrom * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/include/qperfroll.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp: L/R keys now work to move triggers, and Pattern Fix now modifies. 2022-07-31 Chris Ahlstrom * README.md, doc/latex/tex/song_editor.tex, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/triggers.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/triggers.cpp, resources/pixmaps/expandgrid.xpm, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp: Added an expand-grid button to the song editor for issue #94. 2022-07-30 Chris Ahlstrom * README.md, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/include/qperfroll.hpp, seq_qt5/src/qperfroll.cpp: More work on issue #90 for song-editor improvements, still in progress. 2022-07-29 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp: Issue #90 for the song editor triggers, still in flux. * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/seq.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Tightened up display message and playback control in regard to issue #89. * ChangeLog, README.md, TODO, libseq66/include/cfg/usrsettings.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qportwidget.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Good progress on issues #89, #90, and #94, more testing needed. * README.md, TODO, libseq66/src/play/performer.cpp: Interim safety check-in for the road. 2022-07-27 Chris Ahlstrom * README.md, TODO, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed issue #93, pattern editor open after pattern cut/delete. * TODO, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qsmainwnd.cpp: Good progress on issues #89 and #90, but need to resolve grid-flicker for #89. * README.md, Seq66qt5/seq66qt5.cpp, TODO, contrib/vim-syntax/cpp.vim, doc/latex/tex/configuration.tex, libseq66/include/cfg/cmdlineopts.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/sessions/smanager.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1: Revisited issues #78 and #91 and added a locale setting option. 2022-07-23 Chris Ahlstrom * README.md, TODO, libseq66/src/play/performer.cpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qloopbutton.cpp: Minor tweaks to the slot-button display. 2022-07-22 Chris Ahlstrom * data/linux/qseq66-lp-mini-alt.ctrl, libseq66/include/play/performer.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp: More work on issue #89, nearly complete. * libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Interim check-in of light refactoring of control-file I/O. 2022-07-21 Chris Ahlstrom * data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/ctrl/midicontrolout.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qloopbutton.cpp: Interim check-in. * README.md, configure, include/config.h.in, libseq66/include/cfg/usrsettings.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Upgrades for issue #78. 2022-07-18 Chris Ahlstrom * README.md, VERSION, configure.ac, contrib/git/git.text, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Prep for version 0.98.11. * VERSION, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version 0.98.10 pending. * README.md, RELNOTES.md, contrib/vim-syntax/c.vim, contrib/vim-syntax/cpp.vim, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/midi/midifile.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack.cpp: Revisited issue #83 re automation/display controls. * README.md, RELNOTES.md, contrib/git/git.text, contrib/vim-syntax/c.vim, doc/latex/tex/seq66-user-manual.tex, libseq66/src/cfg/configfile.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_rtmidi/include/rtmidi_types.hpp: Fixed issue #88, updated RELNOTES for next version. 2022-06-28 Chris Ahlstrom * README.md, TODO, VERSION, configure.ac, contrib/git/git.text, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/mutegroupsfile.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/mutegroup.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/shellexecute.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qmutemaster.cpp: Fixed issue #87, more testing needed. 2022-06-27 Chris Ahlstrom * contrib/scripts/make-checkout, libseq66/include/cfg/usrsettings.hpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/src/qseditoptions.cpp: Improved layout of qmutemaster, fixed non-changing usr options as per issue #87. * ROADMAP.md, contrib/vim-syntax/c.vim, contrib/vim-syntax/cpp.vim, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/mutegroupsfile.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/mutegroup.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/midibytes.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmaster.cpp, libseq66/src/play/songsummary.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/rtmidi_info.cpp: Many tweaks, work on issue #87 well underway. 2022-06-18 Chris Ahlstrom * contrib/vim-syntax/c.vim, contrib/vim-syntax/cpp.vim: Updated vim syntax files. 2022-06-03 Chris Ahlstrom * ROADMAP.md, contrib/scripts/seq66.sed, libseq66/include/midi/midibytes.hpp, libseq66/src/midi/midibytes.cpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Removed unused midi_booleans class, minor tweaks. * NEWS, README.md, TODO, VERSION, configure, configure.ac, configure.help, doc/latex/tex/jack.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, include/config.h.in, libseq66/src/play/portslist.cpp, libseq66/src/util/strfunctions.cpp, seq_rtmidi/include/midi_alsa.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack.cpp: Version increment, doc updates, port-naming fix, alsa experiments. 2022-06-01 Chris Ahlstrom * configure, include/config.h.in: Version bump to 0.98.9.1. * : Fixed config.h.in merge conflict. 2022-05-31 Chris Ahlstrom * INSTALL, TODO, configure, doc/latex/tex/alsa.tex, include/config.h.in: Minor config updates, added VMPK documentation to user manual. 2022-05-30 Chris Ahlstrom * TODO, VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, seq_rtmidi/src/midi_alsa_info.cpp: Bumped the version and added note about vmpk input weirdness. 2022-05-29 Chris Ahlstrom * ChangeLog, include/config.h.in: Updating to quick release 0.98.9. * README.md, TODO, VERSION, configure.ac, data/license.text, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/pattern_tools.html, doc/dox/doxy-common.cfg, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed issue #85 seqfault and some minor bugs, tutorial updates. * README.md, TODO, VERSION, configure.ac, data/license.text, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/pattern_tools.html, doc/dox/doxy-common.cfg, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed issue #85 seqfault and some minor bugs, tutorial updates. 2022-05-27 Chris Ahlstrom * data/share/doc/tutorial/css/dark-slide.css, data/share/doc/tutorial/css/emac-slide.css, data/share/doc/tutorial/css/light-slide.css, data/share/doc/tutorial/css/slide.css, data/share/doc/tutorial/home.html, data/share/doc/tutorial/left-tree.html: Added CSS color variables to style sheets. 2022-05-26 Chris Ahlstrom * data/share/doc/tutorial/css/dark-slide.css, data/share/doc/tutorial/css/emac-slide.css, data/share/doc/tutorial/css/light-slide.css, data/share/doc/tutorial/css/slide.css, data/share/doc/tutorial/faq.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/introduction.html: Add emac-slide.css, updated HTML. 2022-05-25 Chris Ahlstrom * TODO, contrib/git/git.text, doc/latex/tex/configuration.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/settings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/os/shellexecute.cpp, seq_qt5/src/qsmainwnd.cpp: Add PDF viewer/browser options for the Help functions. 2022-05-24 Chris Ahlstrom * TODO, libseq66/include/cfg/settings.hpp, libseq66/src/cfg/settings.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_debug_code.bat, nsis/build_release_package.bat, seq_qt5/src/qsmainwnd.cpp: Added fall back to github.io to find user manual, needs testing. * contrib/git/git.text, data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/css/dark-slide.css, data/share/doc/tutorial/css/light-slide.css, data/share/doc/tutorial/css/slide.css, data/share/doc/tutorial/faq.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/images/README, data/share/doc/tutorial/index.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/mutes_manager.html, data/share/doc/tutorial/navibar-saved.html, data/share/doc/tutorial/navibar.html, data/share/doc/tutorial/pagenotready.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/pattern_tools.html, data/share/doc/tutorial/playlist_manager.html, data/share/doc/tutorial/sets_manager.html, data/share/doc/tutorial/song_editor.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_live_play.html, data/share/doc/tutorial/tutorial_main.html, data/share/doc/tutorial/tutorial_new_patterns.html, data/share/doc/tutorial/tutorial_new_song.html, data/share/doc/tutorial/tutorial_other_features.html, data/share/doc/tutorial/tutorial_song_performance.html: Perfected navigation buttons, trimmed PNGs and HTMLs. 2022-05-23 Chris Ahlstrom * data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/faq.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/mutes_manager.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/pattern_tools.html, data/share/doc/tutorial/playlist_manager.html, data/share/doc/tutorial/sets_manager.html, data/share/doc/tutorial/song_editor.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_live_play.html, data/share/doc/tutorial/tutorial_main.html, data/share/doc/tutorial/tutorial_new_patterns.html, data/share/doc/tutorial/tutorial_new_song.html, data/share/doc/tutorial/tutorial_other_features.html, data/share/doc/tutorial/tutorial_song_performance.html, seq_qt5/src/qloopbutton.cpp: Shortened Prev/Home/Next link, fixed bug where queued/one-shot did not gray the progress box. * VERSION, configure, configure.ac, data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/faq.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/index.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/mutes_manager.html, data/share/doc/tutorial/pagenotready.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/pattern_tools.html, data/share/doc/tutorial/playlist_manager.html, data/share/doc/tutorial/sets_manager.html, data/share/doc/tutorial/song_editor.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_live_play.html, data/share/doc/tutorial/tutorial_main.html, data/share/doc/tutorial/tutorial_new_patterns.html, data/share/doc/tutorial/tutorial_new_song.html, data/share/doc/tutorial/tutorial_other_features.html, data/share/doc/tutorial/tutorial_song_performance.html, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version numbers, navigate row for tutorial. * ChangeLog, INSTALL, VERSION, bootstrap, configure.ac, data/Makefile.am, data/Makefile.in, include/config.h.in, seq_qt5/src/qsmainwnd.cpp: Minor fixes to make uninstall for 0.98.8. * README.md, TODO, data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/css/dark-slide.css, data/share/doc/tutorial/css/light-slide.css, data/share/doc/tutorial/css/slide.css, data/share/doc/tutorial/home.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/mutes_manager.html, data/share/doc/tutorial/navibar-saved.html, data/share/doc/tutorial/navibar.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/playlist_manager.html, data/share/doc/tutorial/sets_manager.html, data/share/doc/tutorial/song_editor.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_live_play.html, data/share/doc/tutorial/tutorial_main.html, data/share/doc/tutorial/tutorial_new_patterns.html, data/share/doc/tutorial/tutorial_new_song.html, data/share/doc/tutorial/tutorial_other_features.html, data/share/doc/tutorial/tutorial_song_performance.html: Finished first draft of tutorial, needs prev/next and testing in Windows. 2022-05-22 Chris Ahlstrom * data/share/doc/tutorial/css/dark-slide.css, data/share/doc/tutorial/css/light-slide.css, data/share/doc/tutorial/css/slide.css, data/share/doc/tutorial/home.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/song_editor.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_main.html: Still more tutorial updates, added a dark-mode css file. 2022-05-21 Chris Ahlstrom * TODO, data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/faq.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/song_editor.html, data/share/doc/tutorial/tutorial_first_startup.html: More tutorial updates. * data/Makefile.am, data/Makefile.in, data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/pattern_tools.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_main.html: Fixed tutorial install, more tutorial updates. 2022-05-20 Chris Ahlstrom * README.md, TODO, doc/latex/tex/menu.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/play/sequence.hpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Various fixes, UI tweaks, doc updates, added END to perfroll. 2022-05-19 Chris Ahlstrom * data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/pattern_tools.html, data/share/doc/tutorial/tutorial_first_startup.html, data/share/doc/tutorial/tutorial_main.html: Add tutorial section, broken. * Makefile.in, README.md, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, TODO, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/Makefile.in, libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/os/shellexecute.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/cfg/settings.cpp, libseq66/src/os/shellexecute.cpp, libseq66/src/seq66_features.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.am, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.am, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.am, seq_rtmidi/src/Makefile.in: Added installation path search and shellexecute module. 2022-05-18 Chris Ahlstrom * configure.ac, libseq66/include/cfg/settings.hpp, libseq66/include/seq66_features.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/src/qsmainwnd.cpp: Added HTML/PDF lookup, PDF local file access needs work. * README.md, TODO, contrib/mutes-map.rc, contrib/vim.rc, data/Makefile.am, data/Makefile.in, data/share/doc/tutorial/configuration.html, data/share/doc/tutorial/home.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/pagenotready.html, data/share/doc/tutorial/pattern_editor.html, data/share/doc/tutorial/pattern_tools.html, data/share/doc/tutorial/song_editor.html, libseq66/include/util/filefunctions.hpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Initial working tutorial files, lookup is next. 2022-05-17 Chris Ahlstrom * data/readme.text, data/share/doc/tutorial/css/slide.css, data/share/doc/tutorial/home.html, data/share/doc/tutorial/images/README, data/share/doc/tutorial/index.html, data/share/doc/tutorial/introduction.html, data/share/doc/tutorial/left-tree.html, data/share/doc/tutorial/main_window.html, data/share/doc/tutorial/main_window_patterns.html, data/share/doc/tutorial/navibar.html, data/share/doc/tutorial/pagenotready.html, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Fixed Windows installer, added initial HTML tutorial documentation. 2022-05-16 Chris Ahlstrom * doc/dia/libseq66-headers.dia, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/settings.hpp, libseq66/include/ctrl/midicontrol.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/util/basic_macros.hpp, libseq66/include/util/condition.hpp, libseq66/include/util/filefunctions.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/scales.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/filefunctions.cpp, libsessions/include/nsm/nsmbase.hpp, seq_qt5/src/palettefile.cpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midibus.cpp: More header refactoring including seq_rtmidi. 2022-05-15 Chris Ahlstrom * libseq66/include/cfg/configfile.hpp, libseq66/include/midi/calculations.hpp, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/calculations.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/portslist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qpatternfix.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack_info.cpp: Rearranged/removed palette and calculations headers. 2022-05-14 Chris Ahlstrom * doc/dia/libseq66-headers.dia, libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/{util => midi}/calculations.hpp, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/midi/wrkfile.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/{midi => play}/songsummary.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/{util => midi}/calculations.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/portslist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/{midi => play}/songsummary.cpp, libseq66/src/play/triggers.cpp, libseq66/src/util/filefunctions.cpp, libsessions/include/nsm/nsmdummy.hpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack_info.cpp: Duty now for the future, major header/headache refactorying. 2022-05-13 Chris Ahlstrom * README.md, ROADMAP.md, doc/dia/libseq66-headers.dia, libseq66/include/ctrl/keycontrol.hpp, libseq66/include/ctrl/keymap.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midimacro.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/include/midi/controllers.hpp, libseq66/include/midi/editable_events.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/mutegroup.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/ctrl/keycontrol.cpp, libseq66/src/ctrl/midimacro.cpp, libseq66/src/midi/controllers.cpp, libseq66/src/midi/midibytes.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qseditoptions.cpp: Started header-file refactoring. 2022-05-11 Chris Ahlstrom * ROADMAP.md, contrib/code/function_calls_gnu.c, contrib/code/function_calls_gnu.h, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack.hpp: Added ROADMAP, removed gnu module. * INSTALL, Makefile.in, README.md, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, TODO, bootstrap, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/function_calls_gnu.h, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/function_calls_gnu.c, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, m4/xpc_debug.m4, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/Makefile.in, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Fixed out-of-source builds, removed func call code, streamline bootstrap script. 2022-05-10 Chris Ahlstrom * INSTALL, README.md, Seq66cli/seq66rtcli.cpp, VERSION, bootstrap, configure, configure.ac, include/cli/seq66-config.h, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/seq66_features.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: For issue #84, added an option to build and install both the Qt and CLI apps. 2022-05-08 Chris Ahlstrom * ChangeLog: Version 0.98.7. * TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qpatternfix.cpp: Measure-detection updates, fixed pattern reversal feature. * libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp: Improving speed of measure-change detection. 2022-05-06 Chris Ahlstrom * seq_qt5/src/qt5_helpers.cpp: Fixed fill_combobox, patternfix.midi. * README.md, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/portslist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseditoptions.cpp: Add client:port display option, pattern-fix reversal option. 2022-05-04 Chris Ahlstrom * Makefile.am, Makefile.in, README.md, Seq66qt5/Makefile.am, Seq66qt5/Makefile.in, bootstrap, contrib/git/git.text, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/portslist.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Refactoring portslist and adding eventual support for client/port pair showing. 2022-05-03 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qbase.hpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes for slot flickering, slight refactoring of modification detection. 2022-05-01 Chris Ahlstrom * README.md, TODO, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes to modify status of time-signature in main window. * : commit f813290f2a03db33f498c86aab4ad391959806c3 Author: Chris Ahlstrom Date: Sun May 1 08:22:22 2022 -0400 2022-04-29 Chris Ahlstrom * : Fix merge conflict in sequence module. * README.md, libseq66/include/play/sequence.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/play/sequence.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qt5_helpers.cpp: Fixed issue #81 by adding stdexcept header. 2022-04-28 Chris Ahlstrom * README.md, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqbase.hpp, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditex.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qseqtime.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp: Refactored seqedit to use seq::ref instead of pointers. 2022-04-27 Chris Ahlstrom * libseq66/include/cfg/settings.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Refactoring fill_combobox() function. 2022-04-26 Chris Ahlstrom * README.md, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Added jitter to pattern-fix, GUI fixes. 2022-04-25 Chris Ahlstrom * README.md, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/mastermidibase.hpp, libseq66/include/util/calculations.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, resources/pixmaps/play_on.xpm, resources/pixmaps/q_rec_on.xpm, resources/pixmaps/rec_on.xpm, resources/pixmaps/thru_on.xpm, seq_qt5/include/qeditbase.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp: Added 'on' icons for seqedit, non-power-of-2 detection, improved modification detection. 2022-04-23 Chris Ahlstrom * README.md, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp: Fixes to pattern-fix, ongoing. 2022-04-22 Chris Ahlstrom * README.md, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/cfg/settings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Improved perf-modified handling, adding settings lists. 2022-04-20 Chris Ahlstrom * README.md, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp: Added QIcon theme-name retrieval, seqedit tweakage. 2022-04-19 Chris Ahlstrom * libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp: More refinements to qpatternfix processing. 2022-04-16 Chris Ahlstrom * libseq66/include/play/sequence.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qpatternfix.cpp: Added rudimentary time-signature adjustment to qpatternfix, still fixing issue. 2022-04-15 Chris Ahlstrom * TODO, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qpatternfix.cpp: Augmenting qpatternfix with note-length preservation. 2022-04-14 Chris Ahlstrom * README.md, TODO, libseq66/include/cfg/settings.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqstyle.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfeditex.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qportwidget.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqstyle.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5_helpers.cpp: More work on settings, qpatternfix, time signatures. 2022-04-13 Chris Ahlstrom * README.md, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/settings.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Tightened string-to-number calls, more combolist updates and usages. * libseq66/src/cfg/settings.cpp: Minor settings module update. * README.md, TODO, libseq66/include/cfg/settings.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/util/calculations.cpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Refactoring combo-box handling into settings module. 2022-04-12 Chris Ahlstrom * README.md, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qpatternfix.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qsmainwnd.cpp: qpatternfix fixes, tab ordering, measure calculation improvments. 2022-04-11 Chris Ahlstrom * doc/latex/tex/palettes.tex, libseq66/include/util/calculations.hpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp: qpatternfix fixes, more progress. * README.md, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qstriggereditor.cpp: qpatternfix dialog now in the debugging stage. 2022-04-10 Chris Ahlstrom * VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qt5_helper.h, seq_qt5/include/qt5_helpers.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp: qpatternfix dialog fleshed out, implementation not yet in place. 2022-04-09 Chris Ahlstrom * seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qpatternfix.ui, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qpatternfix.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/qpatternfix.cpp, seq_qt5/src/qseqeditframe64.cpp: Added qpatternfix dialog, not yet functional. * ChangeLog, doc/latex/tex/menu.tex: Minor user-manual fix, change-log. 2022-04-08 Chris Ahlstrom * README.md, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Fixed stupid seqedit bug setting beats/bar. * README.md, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Fixed stupid seqedit bug setting beats/bar. * README.md, TODO, VERSION, bootstrap, configure, configure.ac, doc/dox/doxy-common.cfg, doc/latex/tex/menu.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.h, libseq66/include/seq66_features.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/screenset.cpp, seq_portmidi/src/portmidi.c, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_rtmidi/src/midi_jack.cpp: Code cleanup of macros, unused UI items. 2022-04-06 Chris Ahlstrom * libseq66/include/cfg/basesettings.hpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Added Apply/Reset buttons to Preferences dialog. 2022-04-05 Chris Ahlstrom * README.md, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Clear global seq features from last tune, even more detection of modification. 2022-04-04 Chris Ahlstrom * README.md, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Better pattern modification detection, SeqSpec reading, restart handling. 2022-04-03 Chris Ahlstrom * seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Global-seq-feature work, may be complete. * libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Getting global-seq-feature working right, in progress. 2022-04-02 Chris Ahlstrom * README.md, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/src/midi_jack.cpp: Working on a potential segfault when adding a new track while playing. 2022-03-31 Chris Ahlstrom * README.md, TODO, libseq66/src/util/filefunctions.cpp, nsis/Seq66Constants.nsh, nsis/build_debug_code.bat, nsis/build_release_package.bat, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qslivegrid.cpp: Removed dead code, minor GUI tweaks. 2022-03-29 Chris Ahlstrom * NEWS, README.md, RELNOTES.md, TODO, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed issues with Live/Song mode, Preferences updates. 2022-03-28 Chris Ahlstrom * README.md, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Fixed setting last-used-directory and modify status with painted notes. * README.md, Seq66qt5/seq66qt5.cpp, contrib/code/test/filename_split.cpp, doc/dia/rtbusses.dia, doc/dia/rtjack_init.dia, libseq66/include/util/filefunctions.hpp, libseq66/src/util/filefunctions.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/src/mastermidibus.cpp: Fixed filename splitting/building, updated diagrams. 2022-03-26 Chris Ahlstrom * README.md, TODO, bootstrap.help, doc/dia/rtbusses.dia, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp: Fixed Dia files, minor bugs, cleanup. 2022-03-23 Chris Ahlstrom * arch/package/PKGBUILD, doc/dia/rtbusses.dia, libseq66/src/os/daemonize.cpp: Added initial Dia JACK sequence diagram, updated Arch PKGBUILS. 2022-03-22 Chris Ahlstrom * README.md, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, doc/latex/tex/song_editor.tex: Updated the user manual. 2022-03-21 Chris Ahlstrom * README.md, bootstrap, configure, configure.ac, doc/latex/tex/sessions.tex, include/config.h.in, libseq66/include/util/basic_macros.hpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Revisited issue #41, added another reload button. 2022-03-14 Chris Ahlstrom * INSTALL, README.md, doc/latex/tex/jack.tex, doc/latex/tex/pattern_editor.tex, libseq66/src/midi/businfo.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midi_probe.cpp, seq_rtmidi/src/rtmidi_types.cpp: Add UI for click-to-edit, removed JACK callback code. 2022-03-10 Chris Ahlstrom * README.md, libseq66/include/midi/midibase.hpp, libseq66/src/midi/midibase.cpp, seq_rtmidi/src/midibus.cpp: JACK port enable/disable fixed, very minor optimizing. 2022-03-08 Chris Ahlstrom * README.md, RELNOTES.md, VERSION, configure, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/opcontrol.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/util/palette.hpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/mutegroups.cpp, seq_qt5/include/gui_palette_qt5.hpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Setup for 0.98.6, tweaking some enum classes. 2022-03-07 Chris Ahlstrom * ChangeLog, doc/latex/tex/seq66-user-manual.tex: Version 0.98.5. 2022-03-06 Chris Ahlstrom * VERSION, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version date-stamp. * libseq66/src/midi/midibase.cpp: Minor businfo tweak for debugging. * TODO, libseq66/include/midi/midibase.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/midibase.cpp, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/portmidi.c, seq_rtmidi/src/midibus.cpp: Portmidi fixes and businfo optimizing. * README.md, TODO, contrib/notes/q-hierarchy.text, libseq66/include/midi/event.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/recmutex.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/sequence.cpp, seq_rtmidi/src/midibus.cpp: Tightened draw_locking(). 2022-03-03 Chris Ahlstrom * README.md, TODO, doc/latex/tex/seq66-user-manual.tex, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/rtmidi_types.cpp: Fixed another subtle segfault, added an underrun indicator. 2022-03-02 Chris Ahlstrom * README.md, contrib/code/qsliveframe.cpp, libseq66/include/play/sequence.hpp, libseq66/src/os/timing.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Added draw-lock-unlock functions to sequence and use them with most GUI get_next_() functions. 2022-03-01 Chris Ahlstrom * libseq66/include/midi/eventlist.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midibase.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Working on unpredictable crash recording from two inputs. 2022-02-28 Chris Ahlstrom * README.md, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/midibase.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/util/strfunctions.cpp, seq_portmidi/src/midibus.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/rtmidi_types.cpp: Added experimental/problematic feature to allow disabled ports to still be inited. 2022-02-27 Chris Ahlstrom * README.md, TODO, configure.ac, configure.help, doc/latex/tex/configuration.tex, doc/latex/tex/jack.tex, doc/latex/tex/port_mapping.tex, include/config.h.in, libseq66/include/midi/businfo.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/midi/midibus_common.hpp, libseq66/include/play/portslist.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/portslist.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midibus.cpp: Port enable/disable working for JACK, partially for ALSA. 2022-02-24 Chris Ahlstrom * INSTALL, README.md, TODO, bootstrap, configure.ac, doc/latex/tex/configuration.tex, include/config.h.in, libseq66/include/midi/midibase.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Improved no-JACK build and handling of bad command-line arguments. 2022-02-23 Chris Ahlstrom * README.md, libseq66/include/midi/businfo.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/midibase.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Moved input initing to busarray initialization, to match output initing. 2022-02-22 Chris Ahlstrom * Makefile.in, README.md, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, include/qt/rtmidi/seq66-config.h, libseq66/Makefile.in, libseq66/include/Makefile.am, libseq66/include/Makefile.in, {seq_rtmidi => libseq66}/include/base64_images.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/midibase.hpp, libseq66/src/Makefile.in, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/util/basic_macros.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_portmidi/src/midibus.cpp, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivegrid.cpp, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.am, seq_rtmidi/include/Makefile.in, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/seq_rtmidi.pro, seq_rtmidi/src/Makefile.in, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Moved base64_images, fixed rtmidi pro file, interim check-in. 2022-02-19 Chris Ahlstrom * doc/dia/rtbusses.dia, libseq66/include/midi/midibytes.hpp, libseq66/src/play/performer.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Minor tweaks will updating rtbusses diagram. 2022-02-17 Chris Ahlstrom * README.md, VERSION, configure, configure.ac, contrib/git/git.text, doc/dia/rtbusses.dia, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/src/qslivebase.cpp, seq_rtmidi/include/mastermidibus_rm.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midibus.cpp, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp, seq_rtmidi/src/rtmidi_types.cpp: Prep 0.98.5, add rtmidi accessors, diagram updates, more. 2022-02-12 Chris Ahlstrom * : commit f8bcfa37d53302044ab8f1d6a571e09d8f0ac052 Author: Chris Ahlstrom Date: Sat Feb 12 09:13:31 2022 -0500 * doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qloopbutton.cpp, seq_rtmidi/src/midi_jack.cpp: Some tweaks and documentation for looming 0.98.4. 2022-02-11 Chris Ahlstrom * README.md, libseq66/include/midi/jack_assistant.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Fixed seq24 bug with tick-to-time calculations using beat width. * : Merged portfix branch. 2022-02-08 Chris Ahlstrom * bootstrap, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/util/basic_macros.cpp, seq_qt5/src/qt5_helpers.cpp: Made JACK metadata true by default, more improvements to investigate output. * include/qt/rtmidi/seq66-config.h, libseq66/include/midi/jack_assistant.hpp, libseq66/include/util/basic_macros.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/util/basic_macros.cpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: More work on issue #75, metadata for icons. 2022-02-06 Chris Ahlstrom * README.md, libseq66/src/util/basic_macros.cpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi_info.cpp: Added detection of ports being owned by Seq66. 2022-02-03 Chris Ahlstrom * libseq66/include/util/basic_macros.hpp, libseq66/src/util/basic_macros.cpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi_info.cpp: Enabled port-register callback and added another async print function. 2022-02-01 Chris Ahlstrom * doc/dia/rtbusses.dia, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp: Changed rterror kind to an enum class. 2022-01-31 Chris Ahlstrom * : Minor merge conflicts in 0_98_0 and portfix branches. 2022-01-31 Chris Ahlstrom * README.md, configure, include/config.h.in, seq_qt5/src/qseqeditframe64.cpp: Fixed indexing bug in seqedit record-style selector. 2022-01-30 Chris Ahlstrom * libseq66/include/util/basic_macros.hpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/filefunctions.cpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Layed groundwork for future detection of JACK port connection/registration. 2022-01-29 Chris Ahlstrom * libseq66/include/cfg/settings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/calculations.cpp, seq_portmidi/include/mastermidibus_pm.hpp, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/midibus.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa.cpp: The portmidi version builds, other minor refactoring. 2022-01-27 Chris Ahlstrom * configure, include/config.h.in, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibase.hpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/performer.cpp, seq_rtmidi/include/mastermidibus_rm.hpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midi_probe.cpp, seq_rtmidi/src/midibus.cpp, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp: Interim portfix check-in, not yet tested. 2022-01-26 Chris Ahlstrom * configure.ac, seq_rtmidi/include/mastermidibus_rm.hpp, seq_rtmidi/src/mastermidibus.cpp: Started refactoring port creation. 2022-01-25 Chris Ahlstrom * contrib/git/git.text: Updated git.text to discuss removing old branches locally and from GitHub. 2022-01-24 Chris Ahlstrom * configure.ac: Back to 0.98.4. * README.md, configure, configure.ac, include/config.h.in: Version 0.98.3.1 to fix make-files. * Makefile.am, Makefile.in, data/Makefile.am, data/Makefile.in, doc/Makefile.am, doc/Makefile.in, doc/dox/Makefile.am, doc/latex/Makefile.am, doc/latex/Makefile.in, doc/latex/tex/Makefile.am, doc/latex/tex/Makefile.in, libseq66/Makefile.am, libseq66/Makefile.in, libsessions/Makefile.am, libsessions/Makefile.in, seq_portmidi/Makefile.am, seq_portmidi/Makefile.in, seq_qt5/Makefile.am, seq_qt5/Makefile.in, seq_rtmidi/Makefile.am, seq_rtmidi/Makefile.in: Revisited issue #45, cleaned and fixed other Makefiles. 2022-01-23 Chris Ahlstrom * VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Stamped for next version, 0.98.4. * include/config.h.in: Updated include/config.h.in. * README.md, TODO, VERSION, configure.ac, doc/dox/doxy-common.cfg, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version 0.98.3. * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, bootstrap, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/function_calls_gnu.h, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/function_calls_gnu.c, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, m4/xpc_debug.m4, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.am, seq_rtmidi/src/Makefile.in: GN instrument-functions option available, but too problematic at this time. 2022-01-22 Chris Ahlstrom * bootstrap, configure.ac, libseq66/include/Makefile.am, libseq66/include/function_calls_gnu.h, libseq66/src/Makefile.am, libseq66/src/function_calls_gnu.c, m4/xpc_debug.m4: Added an attempt at GNU C option instrument-functions. 2022-01-20 Chris Ahlstrom * README.md, TODO, libseq66/src/midi/midibase.cpp, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/qclocklayout.hpp, seq_qt5/include/qinputcheckbox.hpp, seq_qt5/include/qportwidget.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qportwidget.cpp, seq_rtmidi/src/midi_alsa.cpp: Created base class qportwidget, looking at virtual ALSA input port issues. 2022-01-19 Chris Ahlstrom * README.md, TODO, data/linux/macros-MMC.ctrl, libseq66/include/midi/event.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_rtmidi/include/midi_alsa.hpp, seq_rtmidi/src/midi_alsa.cpp: Improving tempo handling, ALSA port-naming fixes in progress. 2022-01-18 Chris Ahlstrom * README.md, TODO, contrib/code/ametro.c, contrib/code/make_ametro, seq_rtmidi/src/rtmidi.cpp: Got ametro to generate MIDI clock for testing. 2022-01-17 Chris Ahlstrom * contrib/code/ametro.c, contrib/code/make_ametro: Adding ametro command for testing MIDI clock commands, in progress. 2022-01-16 Chris Ahlstrom * README.md, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/sessions.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/playlist.hpp, libseq66/include/seq66_features.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, seq_portmidi/src/pmlinuxalsa.c, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Work on issue #76, fixing imports of project/playlist in progress. 2022-01-13 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: More progress on importing playlists. 2022-01-11 Chris Ahlstrom * README.md, doc/latex/tex/event_editor.tex, doc/latex/tex/jack.tex, doc/latex/tex/references.tex, doc/latex/tex/sessions.tex, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp: Work on a File / Import Project command in progress. 2022-01-09 Chris Ahlstrom * libseq66/include/cfg/configfile.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/sessions/smanager.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, libsessions/include/nsm/nsmbase.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Initial prep for import Seq66 configurations. 2022-01-08 Chris Ahlstrom * README.md, doc/latex/tex/sessions.tex, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/smanager.cpp: Minor updates, icon and prep for resurfacing an NSM issue. 2022-01-07 Chris Ahlstrom * bootstrap, contrib/scripts/reconf, data/share/applications/seq66.desktop, debian/seq66.desktop, libseq66/include/cfg/configfile.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/sessions/smanager.cpp: Work on fixing issue #64, preserving visibility in the 'usr' file. 2022-01-06 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/midi/jack_assistant.hpp, libseq66/include/seq66_features.hpp, libseq66/src/Makefile.in, libseq66/src/midi/jack_assistant.cpp, libseq66/src/seq66_features.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: More tinkering with JACK metadata, one must now enable it in configure. * contrib/scripts/make-checkout, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/jack_assistant.hpp, libseq66/src/midi/jack_assistant.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Added metadata support for issue #75, does not work as expected with jack 1.9.12 dated 2017 on ubuntu. 2022-01-04 Chris Ahlstrom * : Added qseq66.png to resources. * README.md, configure, configure.ac, include/config.h.in, libseq66/include/midi/jack_assistant.hpp, libseq66/include/os/daemonize.hpp, libseq66/include/os/timing.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/timing.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeventframe.cpp, seq_rtmidi/include/Makefile.am, seq_rtmidi/include/Makefile.in, seq_rtmidi/include/base64_images.hpp, seq_rtmidi/seq_rtmidi.pro, seq_rtmidi/src/midi_jack_info.cpp: Added functions to set JACK metadata re issue #75, but they do not work properly yet. * Makefile.am, Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, configure.ac, data/Makefile.am, data/Makefile.in, data/README, data/{license.txt => license.text}, data/{readme.txt => readme.text}, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.am, m4/Makefile.in, man/Makefile.in, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, resources/pixmaps/Makefile.am, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: More make-file work for issue #75. 2022-01-03 Chris Ahlstrom * data/Makefile.am, data/Makefile.in, data/linux/seq66.desktop.in, data/share/applications/seq66.desktop, {desktop => data/share}/metainfo/seq66.appdata.xml, debian/seq66.desktop, doc/README, doc/latex/tex/Makefile.am, doc/latex/tex/Makefile.in, libseq66/include/Makefile.am, libseq66/include/Makefile.in, resources/pixmaps/Makefile.am, resources/pixmaps/Makefile.in, resources/pixmaps/SEQ66_24x24.xpm, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_rtmidi/include/Makefile.am, seq_rtmidi/include/Makefile.in: Refactoring icons installation for #issue #75 in progress. 2022-01-02 Chris Ahlstrom * NEWS, README.md, VERSION, configure, configure.ac, contrib/tests/4x4/README, contrib/tests/4x4/darkfix.qss, contrib/tests/4x4/qseq66-lp-mini-4x4.ctrl, contrib/tests/4x4/qseq66.ctrl, contrib/tests/4x4/qseq66.mutes, contrib/tests/4x4/qseq66.rc, contrib/tests/4x4/qseq66.usr, contrib/tests/4x4/synthstart, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqdata.cpp: Version bump, note-data display fix, style-sheet test. 2022-01-01 Chris Ahlstrom * VERSION, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version 0.98.2 to fix issue #74. * README.md, libseq66/src/util/strfunctions.cpp: Fixed issue #74 where string conversion of -1 resulted in 0. 2021-12-31 Chris Ahlstrom * contrib/tests/4x4/qseq66.ctrl, contrib/tests/4x4/qseq66.mutes, contrib/tests/4x4/qseq66.rc, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/util/filefunctions.cpp: Fixing handling of log file. * contrib/tests/4x4/README, contrib/tests/4x4/qseq66.ctrl, contrib/tests/4x4/qseq66.rc, data/samples/session.rc, doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, libseq66/include/ctrl/keycontrol.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontrol.cpp: Added blank keystroke for placeholders. 2021-12-30 Chris Ahlstrom * contrib/midnam/README, contrib/tests/4x4/qseq66.rc, data/samples/session.rc, libseq66/include/Makefile.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/sessionfile.hpp, libseq66/src/Makefile.in, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/sessions/smanager.cpp: Preparations for heavy 4x4 testing. 2021-12-29 Chris Ahlstrom * libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/sessions/smanager.cpp: Interim check-in. * data/samples/session.rc, doc/latex/tex/configuration.tex, libseq66/include/Makefile.am, libseq66/include/cfg/rcfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/sessionfile.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/sessionfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/src/qseditoptions.cpp: Added a test facility, session.rc. 2021-12-28 Chris Ahlstrom * README.md, TODO, contrib/tests/4x4/qseq66.ctrl, data/linux/qseq66-azerty.ctrl, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66-lp-mini-swapped.ctrl, data/linux/qseq66-lp-mini.ctrl, data/linux/qseq66-swapped.ctrl, data/linux/qseq66.ctrl, data/samples/nanomap.ctrl, data/win/qpseq66.ctrl, doc/latex/tex/configuration.tex, doc/latex/tex/headless.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/performer.cpp: Removed key/MIDI control-loading flags, fixed the applying of session mutes. 2021-12-27 Chris Ahlstrom * README.md, TODO, VERSION, configure, configure.ac, data/win/dark-theme.qss, doc/dox/doxy-common.cfg, doc/latex/tex/configuration.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/include/play/portslist.hpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, nsis/x64.nsh, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/src/qperfeditframe64.cpp: Version bump, added detection of missing system ports in port-mapping. 2021-12-26 Chris Ahlstrom * include/config.h.in, seq_qt5/src/qperfeditframe64.cpp: Very minor config misses. * ChangeLog, VERSION, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Version 0.98.1 in place. * README.md, TODO, contrib/scripts/qtests, doc/latex/tex/concepts.tex, doc/latex/tex/menu.tex, doc/latex/tex/port_mapping.tex, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/qskeymaps.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qskeymaps.cpp: Fixed horizontal piano rolls alignment, song editor name issue. 2021-12-24 Chris Ahlstrom * doc/latex/tex/port_mapping.tex: Updated port-mapping documentation. 2021-12-23 Chris Ahlstrom * libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/strfunctions.cpp: Perhaps port-mapping is whipped into shape now :-D. 2021-12-21 Chris Ahlstrom * README.md, TODO, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/portslist.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/portslist.cpp, libseq66/src/util/filefunctions.cpp, libseq66/src/util/strfunctions.cpp: Port mapping basically done, some minor cleanup needed. 2021-12-20 Chris Ahlstrom * libseq66/include/play/portslist.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/portslist.cpp: More progress in port-mapping, tough stuff. 2021-12-19 Chris Ahlstrom * libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/{listsbase.hpp => portslist.hpp}, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/{listsbase.cpp => portslist.cpp}, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Changed listsbase to portslist for clarity. 2021-12-18 Chris Ahlstrom * libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/listsbase.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: More refactoring of port and mapping configuration. 2021-12-16 Chris Ahlstrom * libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/play/listsbase.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/src/midi_jack_info.cpp: Still working on port-mapping robustness. 2021-12-15 Chris Ahlstrom * README.md, contrib/tests/4x4/qseq66.ctrl, contrib/tests/4x4/qseq66.mutes, contrib/tests/4x4/qseq66.rc, data/linux/jack/jack_portmaps.rc, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/listsbase.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp, seq_rtmidi/src/midi_jack_info.cpp: Updating port-naming/mapping in progress. 2021-12-14 Chris Ahlstrom * contrib/tests/4x4/README, contrib/tests/4x4/qseq66.rc: Just some changes re 4x4 test. 2021-12-13 Chris Ahlstrom * README.md, contrib/midi/README, {data => contrib}/tests/4x4/qseq66.ctrl, {data => contrib}/tests/4x4/qseq66.drums, {data => contrib}/tests/4x4/qseq66.mutes, {data => contrib}/tests/4x4/qseq66.palette, {data => contrib}/tests/4x4/qseq66.playlist, {data => contrib}/tests/4x4/qseq66.rc, {data => contrib}/tests/4x4/qseq66.usr, data/linux/qseq66.ctrl, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qslivegrid.cpp: Fixes to pattern access in sets. 2021-12-12 Chris Ahlstrom * .gitignore, README.md, TODO, configure, data/tests/4x4/qseq66.ctrl, data/tests/4x4/qseq66.drums, data/tests/4x4/qseq66.mutes, data/tests/4x4/qseq66.palette, data/tests/4x4/qseq66.playlist, data/tests/4x4/qseq66.rc, data/tests/4x4/qseq66.usr, include/config.h.in, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qt5nsmanager.cpp: Added initial 4x4 test configs, fixed help segfault. * INSTALL, NEWS, README.md, VERSION, configure.ac, contrib/DIR_COLORS, contrib/notes/{gcc-version.txt => gcc-version.text}, contrib/notes/get_midi_event.txt, contrib/notes/{key-names.txt => key-names.text}, contrib/notes/keycontainer.dump, contrib/notes/keymap.dump, contrib/notes/{launchpad.txt => launchpad.text}, contrib/notes/{performance.txt => performance.text}, contrib/notes/qt5-azerty-codes.txt, contrib/notes/qw-az-keys.text, contrib/notes/slots.txt, contrib/notes/styling.text, contrib/notes/{windows-midi.txt => windows-port-midi.text}, contrib/notes/windows-portmidi.txt, data/readme.txt, data/readme.windows, doc/dox/doxy-common.cfg, doc/latex/tex/configuration.tex, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/README, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, nsis/x64.nsh: Version bump and NSIS research notes. 2021-12-11 Chris Ahlstrom * TODO, nsis/Seq66Constants.nsh: Version 0.98.0 release to master. 2021-12-10 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qslivegrid.cpp: Tweaking coloring getting record/quantize button coloring to work. * NEWS, README.md, TODO, VERSION, configure.ac, data/README, data/license.txt, data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66.ctrl, data/linux/qseq66.rc, data/linux/qseq66.usr, data/readme.txt, data/readme.windows, doc/dox/doxy-common.cfg, doc/latex/tex/configuration.tex, doc/latex/tex/launchpad_mini.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/configfile.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/play/performer.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, seq_qt5/src/palettefile.cpp, seq_qt5/src/qslivegrid.cpp: Build date updates and interim check-in re automation. * libseq66/include/play/performer.hpp, libseq66/include/util/condition.hpp, libseq66/src/play/performer.cpp, libseq66/src/util/condition.cpp, nsis/build_debug_code.bat: Fixed Windows condition-wait CPU issue with new synchronization class, applies to Linux too. 2021-12-09 Chris Ahlstrom * doc/latex/tex/defaultkeys.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/util/condition.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/condition.cpp, libseq66/src/util/recmutex.cpp, seq_portmidi/include/portmidi.h, seq_portmidi/src/midibus.cpp, seq_portmidi/src/pmwin.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_qt5/src/qslivegrid.cpp: Still working on Windows CPU usage, dang. 2021-12-08 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/src/qslivegrid.cpp: Provisional implementations of most grid-mode functions. * README.md, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Added grid-mode combobox to the live grid. 2021-12-07 Chris Ahlstrom * TODO, data/linux/qseq66.ctrl, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/midioperation.hpp, libseq66/include/ctrl/opcontrol.hpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/ctrl/opcontrol.cpp: Fixed issue with slot-names shown in 'ctrl' file, oops. * README.md, data/samples/textfix.qss, doc/latex/tex/configuration.tex, libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/src/mastermidibus.cpp: The JACK port alias feature basically works. 2021-12-06 Chris Ahlstrom * contrib/code/jack_impl.cpp, contrib/code/qseqeditframe.cpp, contrib/code/qseqeditframe.hpp, contrib/code/qseqeditframe.ui, contrib/code/victor.hpp, doc/latex/tex/alsa.tex, doc/latex/tex/configuration.tex, doc/latex/tex/jack.tex, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/play/listsbase.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/strfunctions.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midibus.cpp: Adding support to show JACK port aliases. 2021-12-05 Chris Ahlstrom * doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, doc/latex/tex/mutes.tex, doc/latex/tex/references.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, doc/latex/tex/setmaster.tex: Getting documentation up to spec for version 0.98.0. 2021-12-04 Chris Ahlstrom * doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, doc/latex/tex/sessions.tex, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/keycontainer.hpp, libseq66/include/ctrl/keycontrol.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/keycontrol.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp: Interim check-in, latex table of keys is still broken. * doc/latex/tex/configuration.tex, doc/latex/tex/defaultkeys.tex, libseq66/include/ctrl/midimacro.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/util/calculations.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/midi/midibytes.cpp: More work on new automation slots, tightening headers. 2021-12-03 Chris Ahlstrom * doc/latex/tex/configuration.tex, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/include/ctrl/automation.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/play/performer.cpp: Working on automatic ctrl file upgrade. 2021-12-02 Chris Ahlstrom * contrib/notes/slots.txt, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/keycontainer.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/util/filefunctions.cpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp: Much refactoring for additional automation slots. 2021-11-30 Chris Ahlstrom * INSTALL, NEWS, README.md, TODO, configure, configure.ac, data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/concepts.tex, doc/latex/tex/references.tex, doc/latex/tex/sessions.tex, include/config.h.in, libseq66/include/ctrl/automation.hpp, libseq66/include/seq66_features.hpp, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, libsessions/include/nsm/nsmbase.hpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/src/rtmidi.cpp: Important work on issues #41 and #73. 2021-11-29 Chris Ahlstrom * INSTALL, README.md, data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66.ctrl, data/linux/seq66.desktop.in, debian/seq66.desktop, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libsessions/include/nsm/nsmbase.hpp, libsessions/include/nsm/nsmclient.hpp, libsessions/src/nsm/nsmbase.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/src/rtmidi.cpp: Work on NSM show/hide issues in progress. 2021-11-28 Chris Ahlstrom * data/linux/macros-APC40-mk2.ctrl, data/linux/macros-launchpad-pro-mk3.ctrl, data/samples/textfix.qss, include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/midicontrol.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/seq66_features.hpp, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/midicontrol.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/midi/event.cpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/basic_macros.cpp: Minor fixes to MIDI control and seq_client_tag(), qss update. 2021-11-26 Chris Ahlstrom * : Fix merge conflicts from optimize/master. * configure, doc/latex/tex/configuration.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex: Version 0.97.3 pending. 2021-11-25 Chris Ahlstrom * seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseditoptions.cpp: Fixed UI for setting MIDI I/O control ports. 2021-11-24 Chris Ahlstrom * README.md, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midibytes.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midibus.cpp: Macros now work, sysex sending works, added UI for MIDI I/O control. * libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/include/midi/midibytes.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/midi/midibytes.cpp, libseq66/src/util/strfunctions.cpp: More progress on macro support, interim check-in 2. 2021-11-23 Chris Ahlstrom * INSTALL, README.md, configure, contrib/scripts/make-qt5-links, data/linux/macros-launchpad-mini.ctrl, data/linux/macros-launchpad-pro-mk3.ctrl, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midimacro.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/ctrl/midimacro.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qsessionframe.hpp, seq_qt5/src/qsessionframe.cpp: More progress on macro support, interim check-in. 2021-11-22 Chris Ahlstrom * libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midimacro.hpp, libseq66/include/ctrl/midimacros.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midimacro.cpp, libseq66/src/ctrl/midimacros.cpp, libseq66/src/util/strfunctions.cpp: More progess on MIDI macros. * : commit 3ff41681995c17bfd320bfcd1463e1e7e6aa922e Merge: 3525ddf1 4088954b Author: Chris Ahlstrom Date: Mon Nov 22 16:48:56 2021 -0500 * configure.ac: Tweak of configure.ac. * : Merge pull request #71 from Fi3/FixFedoraBuild Fix fedora 34 build 2021-11-22 fi3 * INSTALL, configure.ac, m4/ax_have_qt.m4, m4/ax_have_qt_ex.m4, m4/ax_have_qt_min.m4: Fix fedora 34 build 2021-11-22 Chris Ahlstrom * TODO, seq_qt5/forms/qslivegrid.ui, seq_qt5/src/qslivegrid.cpp: Tweaks to loop/quantize main buttons. 2021-11-20 Chris Ahlstrom * TODO, libseq66/include/seq66_features.hpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/include/qslivebase.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qt5_helpers.cpp: External grid fixes done, coloring the record/loop-mode buttons. 2021-11-19 Chris Ahlstrom * README.md, TODO, libseq66/include/os/timing.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, seq_portmidi/src/midibus.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/include/qliveframeex.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midi_probe.cpp: Fixing Windows CPU usage, external live frame. 2021-11-18 Chris Ahlstrom * README.md, TODO, libseq66/include/cfg/configfile.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/seq.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/pmlinuxalsa.c, seq_portmidi/src/pmutil.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_portmidi/src/ptlinux.c, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp: Portmidi updates, added qt_timer() function. * TODO, doc/latex/tex/concepts.tex, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midicontrolout.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/src/qseqeditframe64.cpp: Turned off the show/toggle NSM hack. 2021-11-17 Chris Ahlstrom * TODO, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.hpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/util/basic_macros.cpp, libsessions/src/nsm/nsmbase.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/src/qt5nsmanager.cpp: Progress on issues #41, #64, and #67. * libseq66/src/sessions/clinsmanager.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/src/qt5nsmanager.cpp: Improved robustness of filename_concatenate, untested in most scenarios. 2021-11-16 Chris Ahlstrom * README.md, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Added record-mode for record-style to select normal, quantize, and tighten functions. 2021-11-15 Chris Ahlstrom * README.md, doc/latex/tex/meta_events.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslotbutton.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in, UI tweaks, loop-mode debugging. 2021-11-13 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp: Work in progress, support for loop-control-mode. 2021-11-12 Chris Ahlstrom * README.md, TODO, include/config.h.in, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/midicontrol.hpp, libseq66/include/util/basic_macros.h, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/midicontrol.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qloopbutton.cpp: Basic MIDI control refactoring and fixes in place. * : Merge conflicts twixt control and optimizing bug-fix. 2021-11-12 Chris Ahlstrom * README.md, VERSION, configure.ac, include/config.h.in, libseq66/src/play/performer.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qslivegrid.cpp: Version 0.97.2.1 bug-fix pending. 2021-11-11 Chris Ahlstrom * NEWS, README.md, TODO, VERSION, configure, configure.ac, data/readme.txt, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/configfile.hpp, libseq66/include/ctrl/midioperation.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontrol.cpp, libseq66/src/ctrl/midicontrol.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qsetmaster.cpp: Added d1 parameter to MIDI control, currently BROKEN. * : Additional notes for 0.9.7.2. * : Version 0.97.2 pending. * README.md, VERSION, configure.ac, doc/latex/tex/configuration.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/ctrl/midioperation.hpp, libseq66/src/cfg/midicontrolfile.cpp: Prep for 0.97.2 release. 2021-11-10 Chris Ahlstrom * contrib/{notes/git.txt => git/git.text}, contrib/git/gitconfig, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/play/setmapper.hpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp: Retweaking external live grid, activate button. 2021-11-09 Chris Ahlstrom * README.md, data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/launchpad_mini.tex, libseq66/include/seq66_features.hpp, libseq66/include/util/basic_macros.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/screenset.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivebase.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: External live grid no longer changes active play-screen. 2021-11-08 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/forms/qliveframeex.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in, minor refactoring for external grid support in progress. 2021-11-07 Chris Ahlstrom * README.md, TODO, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/song_editor.tex, libseq66/include/midi/editable_event.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in, work on external live grid. * TODO, VERSION, configure.ac, doc/latex/tex/event_editor.tex, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp: Tweaked and documented the event-editor, new screenshots. 2021-11-06 Chris Ahlstrom * README.md, doc/latex/tex/midi_formats.tex, libseq66/include/midi/midifile.hpp, libseq66/include/play/mutegroup.hpp, libseq66/include/play/mutegroups.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/util/calculations.cpp: Fixed botched handling of mute-groups in the MIDI file, updated MIDI format documentation. 2021-11-05 Chris Ahlstrom * README.md, data/samples/textfix.qss, libseq66/include/play/performer.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/calculations.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsmainwnd.cpp: Song editor GUI tweaks, mute-modification fixes. 2021-11-04 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsmainwnd.cpp: Working on enabling MIDI file save on mute-group modifications. * README.md, TODO, data/linux/qseq66.ctrl, data/linux/qseq66.drums, data/linux/qseq66.mutes, data/linux/qseq66.palette, data/linux/qseq66.playlist, data/linux/qseq66.rc, data/linux/qseq66.usr, doc/latex/tex/mutes.tex, libseq66/include/cfg/configfile.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qt5_helpers.cpp: More streamlining of configuration writing. 2021-11-03 Chris Ahlstrom * libseq66/include/play/mutegroups.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qmutemaster.cpp: Fixed mutegroups parsing bug introduced by new feature. 2021-11-02 Chris Ahlstrom * contrib/code/qsliveframe.cpp, data/linux/qseq66.mutes, doc/latex/tex/patterns_panel.tex, libseq66/include/midi/songsummary.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed set-name editing in table. 2021-11-01 Chris Ahlstrom * README.md, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp: Work on editing/storing/reading mute-group names. 2021-10-31 Chris Ahlstrom * README.md, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsetmaster.cpp: Safety check-in for set/mutes swapping, no progress bar in grid slots if muted. 2021-10-30 Chris Ahlstrom * libseq66/include/play/mutegroup.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsetmaster.cpp: Safety check-in for sets/mutes swapping. 2021-10-28 Chris Ahlstrom * README.md, data/linux/jack/pulseaudio/jack-post-start.sh, data/linux/jack/pulseaudio/jack-pre-stop.sh, libseq66/include/midi/jack_assistant.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/seq.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/util/calculations.cpp, libsessions/include/nsm/nsmserver.hpp, libsessions/src/nsm/nsmbase.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_alsa.cpp: Work on issue #64, #57, other fixes. 2021-10-27 Chris Ahlstrom * README.md, libseq66/src/cfg/usrfile.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp: Added Edit / Preferences / Bold Grid Slot. * README.md, Seq66qt5/seq66qt5.cpp, data/linux/qseq66-lp-mini-swapped.ctrl, doc/latex/tex/configuration.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/os/daemonize.hpp, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.hpp, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qt5nsmanager.cpp: Config reload/restart works, upgraded Edit / Preferences. * data/linux/qseq66-lp-mini-swapped.ctrl, data/linux/qseq66-swapped.ctrl, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qt5nsmanager.cpp: Added swapped ctrl files, quit(). 2021-10-26 Chris Ahlstrom * README.md, Seq66qt5/seq66qt5.cpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Got issue #63 working for the live grid, also work on app reload. 2021-10-24 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/screenset.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qslivegrid.cpp: Interim check-in experimenting with row/column swap fro screensets. 2021-10-23 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/os/daemonize.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/src/qt5_helpers.cpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_info.cpp: Console message clean-up. 2021-10-22 Chris Ahlstrom * README.md, TODO, doc/latex/tex/alsa.tex, doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/sessions/smanager.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeventframe.ui: Added jack-connect options for disabling automatic JACK connection from command-line. 2021-10-21 Chris Ahlstrom * configure, contrib/scripts/make-checkout, include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, seq_qt5/forms/qsabout.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qsabout.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_jack_info.cpp: Interim check-in for issue #60 etc. 2021-10-20 Chris Ahlstrom * VERSION, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Prep for 0.97.2. * ChangeLog: Version 0.97.1 pending. * README.md, VERSION, configure.ac, contrib/code/qseqeditframe.cpp, contrib/code/qsliveframe.cpp, data/samples/textfix.qss, doc/latex/tex/configuration.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/usrsettings.hpp, libseq66/include/seq66_features.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsbuildinfo.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp: Work on issue #57, issue #58, issue #59, and issue #61. 2021-10-19 Chris Ahlstrom * README.md, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/event.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/event.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed regression in note display in data/event panels, some minor doc and code updates. 2021-10-18 Chris Ahlstrom * README.md, contrib/code/qsliveframe.cpp, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Exponential LFO basically done. 2021-10-17 Chris Ahlstrom * libseq66/include/util/calculations.hpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, seq_qt5/forms/qlfoframe.ui, seq_qt5/forms/qseqeditex.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp: Added exponential LFO, still needs work. 2021-10-16 Chris Ahlstrom * doc/latex/tex/pattern_editor.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qperfnames.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp: Interim check-in, very minor doc and code updates. 2021-10-15 Chris Ahlstrom * README.md, doc/latex/tex/patterns_panel.tex, seq_qt5/forms/qliveframeex.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmaintime.cpp, seq_qt5/src/qsmainwnd.cpp: PNG optimization, set fixes, more tweaks. 2021-10-14 Chris Ahlstrom * README.md, contrib/scripts/make-checkout, debian/README, doc/latex/tex/configuration.tex, doc/latex/tex/event_editor.tex, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/references.tex, doc/latex/tex/song_editor.tex, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in, notable doc upgrade, many little fixes. 2021-10-13 Chris Ahlstrom * INSTALL, Makefile.in, README.md, VERSION, aux-files/ltmain.sh, bootstrap, configure, configure.ac, contrib/scripts/strap_functions, data/README, data/linux/qseq66.ctrl, data/linux/qseq66.drums, data/linux/qseq66.mutes, data/linux/qseq66.palette, data/linux/qseq66.playlist, data/linux/qseq66.rc, data/linux/qseq66.usr, data/readme.txt, data/readme.windows, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/seq66_features.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/seq66_features.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/palettefile.cpp, seq_qt5/src/qseditoptions.cpp: Minor tweaks to version, icons, UI, preferences dialog, and documentation. 2021-10-12 Chris Ahlstrom * Makefile.in, VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Setup for 0.97.1. * ChangeLog, seq_qt5/forms/qsmainwnd.ui: Version 0.97.0 pending. * INSTALL, Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, VERSION, configure, configure.ac, contrib/scripts/QjackCtl.conf, contrib/scripts/README, contrib/scripts/htmldoc, data/Makefile.in, data/linux/jack/README, data/linux/jack/pulseaudio/jack-post-start.sh, data/linux/jack/pulseaudio/jack-post-stop.sh, data/linux/jack/pulseaudio/jack-pre-start.sh, data/linux/jack/pulseaudio/jack-pre-stop.sh, data/linux/jack/pulseaudio/repulse, data/linux/{ => jack}/startjack, data/linux/{ => jack}/startqjack, doc/Makefile.in, doc/README, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, doc/latex/tex/jack.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Makefile and documentation updates. 2021-10-11 Chris Ahlstrom * contrib/scripts/timid, contrib/scripts/ystart: Minor script updates. 2021-10-08 Chris Ahlstrom * libseq66/include/cfg/rcsettings.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: More modified-handling improvements. 2021-10-07 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qsmainwnd.cpp: Improved modified handling in the main window. 2021-10-06 Chris Ahlstrom * README.md, doc/latex/tex/configuration.tex, doc/latex/tex/midi_export.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, man/sequencer66.1, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Implemented convert-to-smf-0 menu item, convert-to-smf-1 usr flag, modified file visibility. 2021-10-05 Chris Ahlstrom * include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/seq66_features.cpp, seq_qt5/include/qperfnames.hpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Interim check-in, Windows build fixes and SMF 0 fixes. 2021-10-04 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/midi/midifile.hpp, libseq66/src/Makefile.in, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Mostly makefile-in updates. * README.md, configure.ac, data/README, data/linux/qseq66.usr, data/readme.txt, data/readme.windows, debian/seq66.desktop, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/midi_vector.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midi_vector.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/seq.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, resources/pixmaps/Makefile.am, resources/pixmaps/{route66rwb-66x66.xpm => route66rwb-64x64.xpm}, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qsmainwnd.cpp: Date bump, support for SMF 0 conversion continued. 2021-10-02 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in for experimental conversion to SMF 0. 2021-10-01 Chris Ahlstrom * README.md, doc/latex/tex/midi_export.tex, doc/latex/tex/midi_formats.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Prep for experimental conversion to SMF 0. 2021-09-29 Chris Ahlstrom * contrib/code/qseqeditframe.cpp, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/event.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qstriggereditor.cpp: Now trying to tighten up event-status and channel handling. 2021-09-28 Chris Ahlstrom * libseq66/include/midi/event.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qstriggereditor.cpp, seq_rtmidi/src/midi_jack.cpp: Interim check-in, event channel/status fixes. 2021-09-27 Chris Ahlstrom * doc/latex/tex/configuration.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/song_editor.tex, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Fixing LFO for tempo events, GUI update fixes. 2021-09-26 Chris Ahlstrom * README.md, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/midi/event.hpp, libseq66/src/util/calculations.cpp, seq_qt5/include/qseqdata.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqroll.cpp: Interim check-in, still more tempo improvements. 2021-09-25 Chris Ahlstrom * libseq66/include/midi/event.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Still more improvements to display/edit tempo. 2021-09-24 Chris Ahlstrom * README.md, TODO, contrib/code/qsliveframe.cpp, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qstriggereditor.cpp, seq_rtmidi/src/midi_alsa.cpp: More improvements to tempo editing, more to come. 2021-09-23 Chris Ahlstrom * libseq66/include/midi/event.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qstriggereditor.cpp: Interim check-in of upgraded tempo support. 2021-09-22 Chris Ahlstrom * README.md, include/config.h.in, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Added some primitive tempo display. 2021-09-21 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.usr, doc/latex/tex/configuration.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: Added more settings like lock-main-window. 2021-09-20 Chris Ahlstrom * NEWS, README.md, TODO, contrib/scripts/bluejack, data/linux/qseq66.rc, data/seq66cli/seq66cli.rc, data/win/qpseq66.rc, doc/dox/doxy-common.cfg, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, seq_qt5/forms/qsabout.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qsabout.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Work on issue #21, qss configuration, font scaling. 2021-09-16 Chris Ahlstrom * README.md, doc/latex/tex/configuration.tex, doc/latex/tex/pattern_editor.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp: Improved wrapped-note handling and drawing of slot buttons. 2021-09-15 Chris Ahlstrom * doc/latex/tex/pattern_editor.tex, libseq66/src/seq66_features.cpp, seq_rtmidi/src/rtmidi.cpp: Updating the handling of version information items. 2021-09-15 Chris Ahlstrom * contrib/midnam/Roland_MT-32.midnam: Added a sample midnam file for future research. 2021-09-15 Chris Ahlstrom * INSTALL, README.md, doc/latex/tex/pattern_editor.tex, doc/latex/tex/references.tex, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/sequence.cpp, libseq66/src/seq66_features.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qt5nsmanager.cpp: Added build settings, fix for unlinked notes. 2021-09-14 Chris Ahlstrom * README.md, include/config.h.in, libseq66/src/cfg/rcsettings.cpp, seq_qt5/forms/qseditoptions.ui: Updated config.h.in, made rc-save the default again. 2021-09-14 Chris Ahlstrom * README.md, data/linux/qseq66.rc, doc/latex/tex/playlist.tex, include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Can now set config files from the UI. 2021-09-13 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: All Makefile.in files modified in our base distro, Ubuntu. 2021-09-13 Chris Ahlstrom * libseq66/include/util/named_bools.hpp, libseq66/src/util/named_bools.cpp: Forgot to add the named_bools class code. 2021-09-13 Chris Ahlstrom * README.md, doc/latex/tex/pattern_editor.tex, doc/latex/tex/song_editor.tex, libseq66/include/Makefile.am, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/palette.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseditoptions.cpp: Making configuration file UI settings and tightening configuration handling in progress. 2021-09-12 Chris Ahlstrom * INSTALL, NEWS, README.md, TODO, VERSION, configure.ac, doc/latex/tex/port_mapping.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, m4/ax_pthread.m4, man/sequencer66.1, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseditoptions.cpp: Interim check-in, adding vertical zoom in song editor. 2021-09-10 Chris Ahlstrom * ChangeLog: Version 0.96.3 2021-09-10 Chris Ahlstrom * configure.ac, doc/latex/tex/sessions.tex, include/config.h.in, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/forms/qsbuildinfo.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_rtmidi/src/midi_jack_info.cpp: Pretty much done with JACK session management. 2021-09-08 Chris Ahlstrom * README.md, contrib/key-map.rc, contrib/scripts/QjackCtl.conf, contrib/scripts/q-make, data/samples/dark-gradient.qss, data/samples/flat-rounded.qss, data/samples/grey-ghost.qss, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, include/config.h.in, libseq66/libseq66.pro, seq66.pro, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qt5nsmanager.cpp: Documentation and minor tweaks. * : commit 0d63bf24182c229d2b95415071c8b4bdef48a0a6 Author: Chris Ahlstrom Date: Wed Sep 8 06:02:03 2021 -0400 2021-09-03 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Updated Makefile.in files for removal of lash. * INSTALL, README.md, Seq66cli/Makefile.am, Seq66qt5/Makefile.am, bootstrap, configure.ac, configure.help, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/sessions.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/seq66_features.h, libseq66/src/Makefile.am, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/include/Makefile.am, libsessions/include/lash/lash.hpp, libsessions/libsessions.pro, libsessions/src/Makefile.am, libsessions/src/lash/lash.cpp, man/sequencer66.1, seq_portmidi/src/Makefile.am, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/Makefile.am, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/include/Makefile.am, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack_info.cpp: Making session usage configurable. * data/linux/startjack, data/linux/startqjack, doc/latex/tex/configuration.tex, doc/latex/tex/jack.tex, doc/latex/tex/references.tex, doc/latex/tex/sessions.tex, include/config.h.in, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/midi/jack_assistant.cpp, man/sequencer66.1: Interim check-in of JACK session updates. 2021-08-31 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, configure.ac, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/cfg/rcsettings.hpp, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: More configure fixes, compiler error on Ubuntu. * README.md, VERSION, configure.ac, configure.help, doc/latex/tex/jack.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/os/daemonize.hpp, libseq66/include/play/performer.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, m4/ax_pthread.m4, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Configure upgrades and refactoring for JACK session in progress. 2021-08-28 Chris Ahlstrom * data/linux/qseq66.rc, doc/latex/tex/configuration.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/sessions.tex, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp: Beefed up event editing and port-mapping. 2021-08-22 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.rc, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/listsbase.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp: Fixed lookup for the Qsynth/FluidSynth port in ALSA. 2021-08-21 Chris Ahlstrom * README.md, libseq66/src/midi/event.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Can now modify Note Off/On at same time in event editor. 2021-08-19 Chris Ahlstrom * configure, libseq66/include/midi/editable_events.hpp, libseq66/src/midi/editable_events.cpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Added event table reload to show Note pair changes, very krufty. * libseq66/include/midi/editable_event.hpp, libseq66/include/midi/editable_events.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Interim check-in, getting note on/off editing to work smoothly. 2021-08-17 Chris Ahlstrom * libseq66/include/midi/editable_event.hpp, libseq66/include/midi/editable_events.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/editable_events.cpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Interim check-in, work on note-event editing. 2021-08-16 Chris Ahlstrom * NEWS, README.md, TODO, VERSION, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/src/midi/midi_vector_base.cpp, seq_qt5/src/qseventslots.cpp: Version bump. 2021-08-15 Chris Ahlstrom * ChangeLog, seq_qt5/src/qsmainwnd.cpp: Version 0.96.2 pending. * README.md, VERSION, configure.ac, doc/latex/tex/pattern_editor.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqroll.cpp: Added Ctrl_N/E for selecting events by channel. 2021-08-13 Chris Ahlstrom * README.md, contrib/code/qseqeditframe.cpp, doc/dox/doxy-common.cfg, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/eventlist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_platform_macros.h, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp: Interim check-in of clean-up and recording handling. 2021-08-12 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, resources/pixmaps/menu_empty_inv.xpm, resources/pixmaps/menu_full_inv.xpm, seq_qt5/src/qseqeditframe64.cpp: Added 'usr' option for adapting to dark themes. * INSTALL, README.md, doc/dox/doxy-common.cfg, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qstriggereditor.cpp: Still more channel fixes, added sets-mode config UI. 2021-08-11 Chris Ahlstrom * libseq66/include/midi/editable_events.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/sequence.cpp, seq_portmidi/src/midibus.cpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qstriggereditor.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack.cpp: Almost done with the new channel handling. 2021-08-10 Chris Ahlstrom * libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, seq_portmidi/src/mastermidibus.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qstriggereditor.cpp: More channel-handling improvements. 2021-08-09 Chris Ahlstrom * doc/latex/tex/midi_formats.tex, libseq66/include/midi/event.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp: Tightening event/pattern channel handling, in progress. 2021-08-07 Chris Ahlstrom * configure, seq_portmidi/src/mastermidibus.cpp: Updated configure script, removed disabled portmidi code. * README.md, TODO, contrib/code/pthread_performer.cpp, contrib/code/qrollframe.cpp, contrib/code/qrollframe.hpp, {seq_portmidi/src => contrib/code}/readbinaryplist.c, {seq_portmidi/include => contrib/code}/readbinaryplist.h, contrib/notes/bluez-alsa-notes.text, contrib/scripts/bluejack, data/linux/startjack, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/midi/event.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/event.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, seq_portmidi/seq_portmidi.pro, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/pmmacosxcm.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_portmidi/src/ptmacosx_cf.c, seq_rtmidi/src/midi_jack.cpp: Fixes to incoming note handling while fixing issue #55. 2021-08-04 Chris Ahlstrom * README.md, TODO, contrib/notes/bluez-alsa-notes.text, libseq66/include/midi/mastermidibase.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_rtmidi/src/midi_alsa.cpp: Updated handling of preview keys in virtual keyboard. 2021-08-03 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.rc, data/seq66cli/seq66cli.rc, data/win/qpseq66.rc, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, seq_qt5/forms/qsabout.ui, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: rc fix, buss-override improvements. 2021-08-02 Chris Ahlstrom * README.md, TODO, VERSION, configure.ac, doc/latex/tex/menu.tex, doc/latex/tex/song_editor.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/jack_assistant.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qsmainwnd.cpp: New version info, JACK pause improvements. 2021-08-01 Chris Ahlstrom * ChangeLog: Version 0.96.1 pending. * libseq66/include/Makefile.in, seq_qt5/include/Makefile.in: Official makefile updates for 0.96.1. * README.md, TODO, VERSION, configure.ac, contrib/code/qsliveframe.cpp, doc/dox/libseq66/libseq66.cfg, doc/latex/tex/launchpad_mini.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/Makefile.am, libseq66/include/qt/qsmacros.hpp, libseq66/libseq66.pro, libseq66/src/cfg/usrsettings.cpp, seq_qt5/include/Makefile.am, seq_qt5/include/qsmacros.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Updated version date, removed obsolete qsmacros header. * README.md, TODO, doc/latex/tex/launchpad_mini.tex, libseq66/include/cfg/playlistfile.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/play/triggers.hpp, libseq66/include/util/calculations.hpp, libseq66/include/util/recmutex.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/play/triggers.cpp, libseq66/src/util/recmutex.cpp, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/midibus.cpp, seq_portmidi/src/pmlinuxalsa.c, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_probe.cpp: Cleanup of macros and attempting to fix a panic/Launchpad bug. 2021-07-31 Chris Ahlstrom * libseq66/include/cfg/configfile.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, seq_qt5/src/qperfnames.cpp: Config-file streamlining, ongoing set-handling imporovements. 2021-07-29 Chris Ahlstrom * libseq66/include/play/screenset.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qsmainwnd.cpp: Working on improving set handling. 2021-07-28 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.usr, doc/latex/tex/menu.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqkeys.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Added feature to copy/paste all patterns in a screenset. 2021-07-27 Chris Ahlstrom * doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Working on sets improvements. 2021-07-26 Chris Ahlstrom * README.md, TODO, data/midi/Kraftwerk-Europe_Endless.text, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/basesettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/seq.hpp, libseq66/src/cfg/basesettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp: Added progress-box scaling, drag-n-drop of patterns. 2021-07-25 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.rc, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: Beefed up recent file configuration. 2021-07-24 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Finished Display tab in qseditoptions. * README.md, TODO, doc/latex/tex/event_editor.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/editable_event.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/editable_event.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeventframe.cpp: Adding some config options to qseditoptions in progress. 2021-07-22 Chris Ahlstrom * seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/src/qseqeventframe.cpp: Setting up program/control combo box programatically, has weird issues. * TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/controllers.hpp, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/editable_events.hpp, libseq66/src/midi/controllers.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp: Improved controller-name access, added patch/program array. 2021-07-20 Chris Ahlstrom * libseq66/include/Makefile.in: Updated Makefile.in re app_limits.h header. * contrib/code/qseqeditframe.cpp, doc/dox/libseq66/libseq66.cfg, doc/dox/libsessions/libsessions.cfg, libseq66/include/Makefile.am, libseq66/include/app_limits.h, libseq66/include/cfg/settings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/midi/wrkfile.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_features.h, libseq66/include/util/calculations.hpp, libseq66/libseq66.pro, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/calculations.cpp, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/midibus.cpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qseqbase.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qbase.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/mastermidibus.cpp: Eliminated the app_limits.h header file. 2021-07-19 Chris Ahlstrom * libseq66/include/app_limits.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usermidibus.hpp, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/mutegroup.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/usermidibus.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/calculations.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qstriggereditor.hpp, seq_rtmidi/include/midi_info.hpp: Moved still more constants from app_limits.h. * TODO, doc/latex/tex/midi_formats.tex, libseq66/include/app_limits.h, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_portmidi/include/mastermidibus_pm.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmaintime.cpp, seq_rtmidi/include/mastermidibus_rm.hpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/src/midi_probe.cpp: Moving more manifest constants into usrsettings. 2021-07-18 Chris Ahlstrom * README.md, TODO, VERSION, configure, configure.ac, doc/latex/tex/configuration.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/util/filefunctions.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/recent.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, libseq66/src/util/filefunctions.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_jack_info.cpp: Knocked off a few of the easier TODO items. 2021-07-17 Chris Ahlstrom * data/license.txt, data/readme.txt, data/readme.windows: Updated the data readmes and license files. * Makefile.in: Version 0.96.0 finalized. * ChangeLog, Makefile.in, NEWS, README.md, TODO, VERSION, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/jack_assistant.hpp, libseq66/src/midi/jack_assistant.cpp: Version 0.96.0 pending. * doc/latex/tex/alsa.tex, doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/jack.tex, doc/latex/tex/kudos.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/midi/midi_vector_base.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Documentation and cleanup. 2021-07-16 Chris Ahlstrom * README.md, doc/latex/tex/alsa.tex, doc/latex/tex/configuration.tex, libseq66/src/play/performer.cpp, man/sequencer66.1, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivegrid.cpp: Progress on shortened pattern editor and app-exit hang. 2021-07-15 Chris Ahlstrom * libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/src/qloopbutton.cpp: Minor cleanup. * libseq66/include/midi/jack_assistant.hpp, libseq66/src/midi/jack_assistant.cpp: Playing with jack_assistant to figure out why the application hangs at exit in the QApplication destructor when JACK master. 2021-07-14 Chris Ahlstrom * libseq66/include/cfg/configfile.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/src/qloopbutton.cpp: Fixed fingerprint drawing, but progress-box and notes are drawn white in some patterns, cannot figure out why. * libseq66/include/midi/jack_assistant.hpp, libseq66/include/os/daemonize.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midioperation.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/palettefile.cpp: Interim check-in, cleanup, JACK transport tweaks, NSM improvements, still working on all this. 2021-07-13 Chris Ahlstrom * doc/latex/tex/alsa.tex, doc/latex/tex/jack.tex, doc/latex/tex/menu.tex, libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/play/performer.hpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/performer.cpp, man/sequencer66.1, seq_portmidi/include/midibus_pm.hpp, seq_portmidi/src/midibus.cpp, seq_rtmidi/include/midi_alsa.hpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midibus.cpp: Fixes/tweaks made while testing MIDI clock tx/rx in ALSA/JACK. 2021-07-12 Chris Ahlstrom * README.md, data/linux/qseq66-azerty.ctrl, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66-lp-mini.ctrl, data/linux/qseq66.ctrl, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/filefunctions.cpp, libsessions/include/nsm/nsmclient.hpp, libsessions/src/nsm/nsmclient.cpp, libsessions/src/nsm/nsmmessagesex.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp: Visibility toggle works, still some fixes needed for NSM. 2021-07-11 Chris Ahlstrom * README.md, TODO, data/linux/qseq66.ctrl, doc/latex/tex/configuration.tex, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, libsessions/include/nsm/nsmbase.hpp, libsessions/include/nsm/nsmclient.hpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qt5nsmanager.cpp: Added a visibility control for MIDI and NSM, still in the testing phase. 2021-07-10 Chris Ahlstrom * README.md, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp: Fixed fingerprint handling, working on shortened seqedit. 2021-07-09 Chris Ahlstrom * : Fixed important performer edit conflict re hang at exit. 2021-07-07 Chris Ahlstrom * Makefile.in, libseq66/src/play/performer.cpp, seq_portmidi/include/midibus_pm.hpp, seq_rtmidi/include/midi_alsa.hpp, seq_rtmidi/include/midi_jack.hpp: Updated Makefile.in, added I/O check, override qualifiers. * README.md, TODO, libseq66/include/cfg/scales.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed harmonic transpose with Nashville numbering, handling read-only MIDI files. * Happy birthday to me! 64 years old! Gasp! 2021-07-06 Chris Ahlstrom * Makefile.in, README.md, TODO, VERSION, configure, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Prep for version 0.96.0. 2021-07-06 Chris Ahlstrom * ChangeLog, configure: Version 0.95 pending. 2021-07-06 Chris Ahlstrom * INSTALL, README.md, configure.ac, include/config.h.in, m4/ax_have_qt_min.m4: Made the check for qmake for robust for issue #54. 2021-07-05 Chris Ahlstrom * README.md, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/scales.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Fixed issue #54, added harmonic-transposition, diminished scale. 2021-07-03 Chris Ahlstrom * contrib/scripts/make-checkout, doc/latex/tex/launchpad_mini.tex: Added back the user manual, accidentally deleted. 2021-07-02 Chris Ahlstrom * : commit 8a810f21f75d515496255799c8b5b37521ae46c4 Author: Chris Ahlstrom Date: Fri Jul 2 17:01:36 2021 -0400 2021-07-01 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, configure, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Updated the make-files and configure script. * libseq66/include/cfg/scales.hpp, libseq66/src/cfg/scales.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Beefing up the scale-detection function in progress. 2021-06-30 Chris Ahlstrom * .gitignore, Makefile.am, README.md, Seq66cli/Makefile.am, TODO, configure.ac, contrib/scripts/naming, contrib/scripts/ordercp, contrib/scripts/seq66.sed, data/seq66cli/seq66cli.drums, data/seq66cli/seq66cli.mutes, data/seq66cli/seq66cli.rc, data/seq66cli/seq66cli.usr, doc/latex/tex/pattern_editor.tex, include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/scales.hpp, libseq66/include/cfg/usrfile.hpp, libseq66/include/play/sequence.hpp, libseq66/include/qt/qsmacros.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, resources/pixmaps/Makefile.am, seq_portmidi/include/mastermidibus_pm.hpp, seq_portmidi/include/midibus_pm.hpp, seq_portmidi/src/mastermidibus.cpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Fixed cli linkage, added enigmatic scale, fixed bg/key/scale SeqSpecs. 2021-06-27 Chris Ahlstrom * doc/latex/tex/configuration.tex, doc/latex/tex/meta_events.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: More tweaking of smaller-screen handling. 2021-06-25 Chris Ahlstrom * README.md, Seq66qt5/Seq66qt5.pro, contrib/scripts/qbuild, doc/latex/tex/configuration.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/libseq66.pro, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, libsessions/libsessions.pro, seq_portmidi/seq_portmidi.pro, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/seq_rtmidi.pro: Improving support for a smaller GUI, enhancing the 'pro' files for Qt. * doc/latex/tex/sessions.tex, seq_portmidi/src/Makefile.in, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Adapting to smaller set sizes in progress. 2021-06-24 Chris Ahlstrom * README.md, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/sessions/clinsmanager.cpp, libsessions/src/nsm/nsmclient.cpp: Fixes to initial NSM session with playlists. 2021-06-23 Chris Ahlstrom * README.md, TODO, doc/dox/seq_portmidi/seq_portmidi.cfg, doc/latex/tex/sessions.tex, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/play/playlist.cpp, seq_portmidi/include/finddefault.h, seq_portmidi/include/pmmacosxcm.h, seq_portmidi/include/portmidi.h, seq_portmidi/seq_portmidi.pro, seq_portmidi/src/Makefile.am, seq_portmidi/src/finddefault-macosx.c, seq_portmidi/src/finddefault.c, seq_portmidi/src/pmlinux.c, seq_portmidi/src/pmmac.c, seq_portmidi/src/pmwin.c, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp: Removing unused portmidi code, fixing double-click slot handling. 2021-06-22 Chris Ahlstrom * Seq66cli/Makefile.in, Seq66qt5/Makefile.in, TODO, configure, {seq_qt5/src => contrib/code}/qrollframe.cpp, {seq_qt5/include => contrib/code}/qrollframe.hpp, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/include/Makefile.am, seq_qt5/include/Makefile.in, seq_qt5/include/palettefile.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qperfeditex.cpp, seq_qt5/src/qseqroll.cpp: Moved grollframe to contrib/code. * README.md, Seq66cli/Makefile.am, Seq66qt5/Makefile.am, TODO, configure.ac, data/readme.windows, doc/latex/tex/references.tex, doc/latex/tex/windows.tex, include/config.h.in, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, seq_portmidi/include/Makefile.am, seq_portmidi/src/Makefile.am, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/Makefile.am, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/seq66_rtmidi_features.h: Added auto option to song-start-mode, fixed issues with keystrokes and file dialogs. 2021-06-21 Chris Ahlstrom * README.md, TODO, data/win/qpseq66.drums, data/win/qpseq66.playlist, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, man/sequencer66.1, seq_qt5/src/qperfeditframe64.cpp: Added auto option for song-start-mode. 2021-06-20 Chris Ahlstrom * VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp: Fixed announce_sequence, incremented version. 2021-06-19 Chris Ahlstrom * ChangeLog, data/license.txt, data/readme.txt, data/readme.windows: Version 0.95.0 pending. * NEWS, README.md, data/win/qpseq66.ctrl, data/win/qpseq66.mutes, data/win/qpseq66.rc, data/win/qpseq66.usr, doc/latex/tex/song_editor.tex, libseq66/src/os/daemonize.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/forms/qperfeditframe64.ui: Fixes for the Windows build. 2021-06-18 Chris Ahlstrom * doc/latex/tex/configuration.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/pattern_editor.tex, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes to set handling and set announcement in progress. 2021-06-16 Chris Ahlstrom * configure, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp: Interim check-in, setting up to improve adding patterns to the performers playset. * README.md, doc/latex/tex/alsa.tex, doc/latex/tex/jack.tex, doc/latex/tex/references.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp: No progress bar for empty patterns and show the duration of the tune. 2021-06-15 Chris Ahlstrom * README.md, VERSION, bootstrap, configure.ac, data/linux/qseq66-lp-mini-alt.ctrl, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/ctrl/midicontrol.hpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/play/performer.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_qt5/src/qsabout.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Setup for 0.95, minor fixes to MIDI control. 2021-06-14 Chris Ahlstrom * README.md, TODO, data/linux/qseq66-azerty.ctrl, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66-lp-mini.ctrl, data/linux/qseq66.ctrl, data/samples/nanomap.ctrl, data/win/qpseq66.ctrl, doc/latex/tex/configuration.tex, doc/latex/tex/headless.tex, libseq66/include/ctrl/midicontrolout.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp: Many fixes and additions to MIDI Control I/O configuration and 'ctrl' files. 2021-06-13 Chris Ahlstrom * README.md, data/linux/qseq66-azerty.ctrl, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66-lp-mini.ctrl, data/linux/qseq66-portmap.rc, data/linux/qseq66.ctrl, data/linux/qseq66.rc, data/linux/qseq66.usr, data/samples/ca_midi.playlist, data/samples/nanomap.ctrl, data/samples/sample.playlist, data/samples/sample.usr, doc/latex/tex/configuration.tex, doc/latex/tex/event_editor.tex, doc/latex/tex/playlist.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/windows.tex, libseq66/include/cfg/configfile.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/usrfile.cpp: More fixing, updating, and documenting the new-style configuration files 2021-06-11 Chris Ahlstrom * include/config.h.in, libseq66/include/Makefile.in: Updating official config.h.in and libseq66/include/Makefile.in to remove victor.hpp. * NEWS, README.md, Seq66cli/seq66rtcli.cpp, Seq66qt5/seq66qt5.cpp, configure.ac, contrib/code/jack_impl.cpp, contrib/code/qsliveframe.hpp, {libseq66/include/util => contrib/code}/victor.hpp, contrib/notes/launchpad.txt, contrib/vim-syntax/c.vim, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66-lp-mini.ctrl, data/readme.windows, doc/dox/libseq66/libseq66.cfg, doc/dox/seq_portmidi/seq_portmidi.cfg, doc/dox/seq_rtmidi/mainpage.dox, doc/latex/images/README, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/windows.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/Makefile.am, libseq66/include/app_limits.h, libseq66/include/cfg/basesettings.hpp, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/comments.hpp, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/cfg/mutegroupsfile.hpp, libseq66/include/cfg/notemapfile.hpp, libseq66/include/cfg/playlistfile.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/recent.hpp, libseq66/include/cfg/scales.hpp, libseq66/include/cfg/settings.hpp, libseq66/include/cfg/userinstrument.hpp, libseq66/include/cfg/usermidibus.hpp, libseq66/include/cfg/usrfile.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/keycontainer.hpp, libseq66/include/ctrl/keycontrol.hpp, libseq66/include/ctrl/keymap.hpp, libseq66/include/ctrl/keystroke.hpp, libseq66/include/ctrl/midicontrol.hpp, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/midioperation.hpp, libseq66/include/ctrl/opcontainer.hpp, libseq66/include/ctrl/opcontrol.hpp, libseq66/include/midi/businfo.hpp, libseq66/include/midi/controllers.hpp, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/editable_events.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/mastermidibus.hpp, libseq66/include/midi/midi_splitter.hpp, libseq66/include/midi/midi_vector.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/midi/midibus.hpp, libseq66/include/midi/midibus_common.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/midi/songsummary.hpp, libseq66/include/midi/wrkfile.hpp, libseq66/include/os/daemonize.hpp, libseq66/include/os/timing.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/listsbase.hpp, libseq66/include/play/mutegroup.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/notemapper.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/include/play/triggers.hpp, libseq66/include/seq66_features.h, libseq66/include/seq66_platform_macros.h, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/sessions/smanager.hpp, libseq66/include/util/automutex.hpp, libseq66/include/util/basic_macros.h, libseq66/include/util/basic_macros.hpp, libseq66/include/util/calculations.hpp, libseq66/include/util/condition.hpp, libseq66/include/util/filefunctions.hpp, libseq66/include/util/palette.hpp, libseq66/include/util/recmutex.hpp, libseq66/include/util/rect.hpp, libseq66/include/util/strfunctions.hpp, libseq66/libseq66.pro, libseq66/src/cfg/basesettings.cpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/recent.cpp, libseq66/src/cfg/scales.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/userinstrument.cpp, libseq66/src/cfg/usermidibus.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/keycontrol.cpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/ctrl/keystroke.cpp, libseq66/src/ctrl/midicontrol.cpp, libseq66/src/ctrl/midicontrolbase.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/ctrl/midioperation.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/controllers.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midi_vector.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midibytes.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/notemapper.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/seq.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, libseq66/src/play/triggers.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/automutex.cpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/calculations.cpp, libseq66/src/util/condition.cpp, libseq66/src/util/filefunctions.cpp, libseq66/src/util/palette.cpp, libseq66/src/util/recmutex.cpp, libseq66/src/util/rect.cpp, libseq66/src/util/strfunctions.cpp, man/seq66.1, man/seq66cli.1, seq_portmidi/README, seq_portmidi/include/easy_macros.h, seq_portmidi/include/finddefault.h, seq_portmidi/include/mastermidibus_pm.hpp, seq_portmidi/include/midibus_pm.hpp, seq_portmidi/include/pmerrmm.h, seq_portmidi/include/pminternal.h, seq_portmidi/include/pmlinux.h, seq_portmidi/include/pmlinuxalsa.h, seq_portmidi/include/pmmac.h, seq_portmidi/include/pmmacosxcm.h, seq_portmidi/include/pmutil.h, seq_portmidi/include/pmwinmm.h, seq_portmidi/include/portmidi.h, seq_portmidi/include/porttime.h, seq_portmidi/include/readbinaryplist.h, seq_portmidi/src/finddefault-macosx.c, seq_portmidi/src/finddefault.c, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/midibus.cpp, seq_portmidi/src/pmerrmm.c, seq_portmidi/src/pmlinux.c, seq_portmidi/src/pmlinuxalsa.c, seq_portmidi/src/pmmac.c, seq_portmidi/src/pmmacosxcm.c, seq_portmidi/src/pmutil.c, seq_portmidi/src/pmwin.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_portmidi/src/porttime.c, seq_portmidi/src/ptlinux.c, seq_portmidi/src/ptmacosx_cf.c, seq_portmidi/src/ptmacosx_mach.c, seq_portmidi/src/ptwinmm.c, seq_portmidi/src/readbinaryplist.c, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/palettefile.hpp, seq_qt5/include/qbase.hpp, seq_qt5/include/qclocklayout.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qinputcheckbox.hpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qliveframeex.hpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qperfeditex.hpp, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qplaylistframe.hpp, seq_qt5/include/qrollframe.hpp, seq_qt5/include/qsabout.hpp, seq_qt5/include/qsbuildinfo.hpp, seq_qt5/include/qscrollmaster.h, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qseqbase.hpp, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditex.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qseqstyle.hpp, seq_qt5/include/qseqtime.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/include/qskeymaps.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/include/qsmacros.hpp, seq_qt5/include/qsmaintime.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qbase.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfeditex.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qrollframe.cpp, seq_qt5/src/qsabout.cpp, seq_qt5/src/qsbuildinfo.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqstyle.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qskeymaps.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmaintime.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5_helpers.cpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/include/mastermidibus_rm.hpp, seq_rtmidi/include/midi_alsa.hpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midi_jack_data.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/midi_probe.hpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midi_probe.cpp, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp, seq_rtmidi/src/rtmidi_types.cpp: Major header file cleanup and config file refactoring. 2021-06-08 Chris Ahlstrom * data/linux/qseq66.rc, libseq66/include/app_limits.h, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/usrfile.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/seq.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Almost done refactoring the usr configuration file. 2021-06-07 Chris Ahlstrom * libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/comments.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp: rc file write refactoring done. 2021-06-06 Chris Ahlstrom * libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/ctrl/keymap.cpp: Refactoring/fixing midicontrolfile.cpp handling by rcfile.cpp. 2021-06-05 Chris Ahlstrom * TODO, configure, contrib/code/jack_impl.cpp, include/config.h.in, libseq66/include/cfg/configfile.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qslivegrid.cpp: Refactoring boolean and file-name configuration internals. 2021-05-28 Chris Ahlstrom * README.md, VERSION, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Getting set for 0.94.1. * ChangeLog: Version 0.94.0 change-log. * libseq66/src/play/performer.cpp: Version 0.94.0 pending. * Seq66qt5/seq66qt5.cpp, configure.ac, data/samples/nanomap.ctrl, include/config.h.in, libseq66/include/cfg/settings.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/play/triggers.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midibus.cpp: Refactored playlist MIDI control to use signals in GUI mode. * README.md, Seq66cli/seq66rtcli.cpp, libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/play/performer.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Worked out MIDI control of playlists in GUI and headless modes. 2021-05-27 Chris Ahlstrom * README.md, Seq66cli/seq66rtcli.cpp, VERSION, configure.ac, doc/latex/tex/headless.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, libseq66/include/app_limits.h, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/wrkfile.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Still working on issues with MIDI control versus arrow control of playists. 2021-05-26 Chris Ahlstrom * README.md, libseq66/include/app_limits.h, libseq66/include/midi/event.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/midi/wrkfile.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/midicontrolbase.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midibus.cpp: Fixes to WRK files, playlists, buss overrides, more. 2021-05-25 Chris Ahlstrom * data/seq66cli/seq66cli.mutes, data/seq66cli/seq66cli.rc, data/seq66cli/seq66cli.usr: Forgot to add seq66cli configuration samples. * INSTALL, README.md, bootstrap, data/linux/qseq66-azerty.ctrl, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66-lp-mini.ctrl, data/linux/qseq66.ctrl, data/samples/nanomap.ctrl, data/samples/sample.playlist, data/win/qpseq66.ctrl, doc/latex/tex/headless.tex, libseq66/include/ctrl/automation.hpp, libseq66/include/os/daemonize.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Working on headless playlist/JACK-transport issues. 2021-05-24 Chris Ahlstrom * README.md, doc/latex/tex/kudos.tex, libseq66/include/cfg/scales.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp: Getting looping to work again. 2021-05-23 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qt5nsmanager.cpp: May have fixed exit-hang by making inner_start() contingent on m_io_active. * Seq66qt5/seq66qt5.cpp, contrib/valgrind/helgrind-test.sh, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qt5nsmanager.cpp: Debugging hang; two versions of the JACK transport callback in place, one plays ragged, the other hangs sometimes at exit, still playing. * TODO, contrib/scripts/helgrind-test.sh, contrib/valgrind/fontconfig.supp, contrib/valgrind/glibc.supp, contrib/valgrind/helgrind-test.sh, contrib/valgrind/kde.supp, contrib/{scripts => valgrind}/valgrind-leaks.sh, libseq66/include/midi/jack_assistant.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qslivegrid.cpp, seq_rtmidi/src/midi_jack_info.cpp: Interim check-in, probing with helgrind. 2021-05-22 Chris Ahlstrom * TODO, contrib/notes/drum_notes_gm_2.text, data/midi/Carpet_of_the_Sun.text, libseq66/include/app_limits.h, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp: Still tweaking the hang bug and roll/time displays. 2021-05-21 Chris Ahlstrom * README.md, contrib/code/qseqeditframe.cpp, data/linux/qseq66-azerty.ctrl, data/midi/Carpet_of_the_Sun.text, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/util/calculations.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp: Added a merge-pattern feature, size-hint fixes, other tweaks. 2021-05-20 Chris Ahlstrom * data/midi/README, data/samples/GM_PSS-790_Multi.ini: Added a Renaissance tune converted to GM via midicvt for future testing. * data/linux/qseq66-azerty.ctrl, libseq66/include/midi/jack_assistant.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqtime.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Notes on azerty 'ctrl' file, fixing pause function, supporting set-start-tick in qperftime and qseqtime. 2021-05-19 Chris Ahlstrom * README.md, data/midi/Kraftwerk-Europe_Endless.asc, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/references.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, libseq66/include/app_limits.h, libseq66/include/cfg/settings.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, libseq66/src/util/calculations.cpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfbase.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqtime.cpp: Interim check-in for PPQN and JACK Transport tightening. 2021-05-18 Chris Ahlstrom * README.md, VERSION, configure.ac, doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/app_limits.h, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, man/sequencer66.1, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qperfroll.hpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in, work on JACK transport and PPQN handling, more to do. 2021-05-17 Chris Ahlstrom * libseq66/include/midi/jack_assistant.hpp, libseq66/include/play/performer.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp: Interim check-in, reconciling jack positioning when running and when not to avoid issue 51. * contrib/vim-syntax/c.vim, data/linux/qseq66.rc, doc/latex/tex/configuration.tex, doc/latex/tex/jack.tex, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/include/util/basic_macros.hpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/basic_macros.cpp, seq_rtmidi/src/midi_jack.cpp: Work on JACK transport, app hang, issue #47. 2021-05-16 Chris Ahlstrom * README.md, contrib/code/jack_impl.cpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/seq66_platform_macros.h, libseq66/include/util/basic_macros.h, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Update 'rc' JACK config, tweaked jack_ass to fix a weird exit bug, removed apiprint as no longer useful. 2021-05-15 Chris Ahlstrom * configure, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/src/midi_jack.cpp: Added back break in midi_jack, new make scripts. * README.md, TODO, VERSION, bootstrap, configure.ac, {seq_qt5/src => contrib/code}/qsliveframe.cpp, {seq_qt5/include => contrib/code}/qsliveframe.hpp, {seq_qt5/forms => contrib/code}/qsliveframe.ui, contrib/scripts/strap_functions, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/keymap.cpp, seq_qt5/forms/Makefile.am, seq_qt5/include/Makefile.am, seq_qt5/include/qliveframeex.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi_types.cpp: Removed qsliveframe, tinkering with weird freeze-at-exit issue on Debian Sid, interim check-in. 2021-05-14 Chris Ahlstrom * ChangeLog: Change log for 0.93.2. * libseq66/include/Makefile.in, libsessions/include/Makefile.in: New Makefile.ins for the libs. * README.md, TODO, contrib/DIR_COLORS, contrib/midi/README, contrib/notes/git.txt, data/linux/{qseq66-azerty-fr.keys => qseq66-azerty-fr.keymap}, data/linux/{qseq66-qwerty-us.keys => qseq66-qwerty-us.keymap}, data/linux/qseq66.mutes, data/linux/qseq66.rc, data/samples/nanomap.ctrl, data/samples/rowclipsmap.ctrl, data/samples/rowclipsmap.mutes, data/samples/rowclipsmap.rc, doc/latex/tex/configuration.tex, libseq66/include/Makefile.am, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libsessions/include/Makefile.am, resources/pixmaps/route66.xpm, seq_qt5/include/qloopbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsmainwnd.cpp: More fixes to mutes, interim check-in. 2021-05-13 Chris Ahlstrom * README.md, TODO, contrib/DIR_COLORS, data/linux/qseq66-azerty-fr.keys, data/linux/qseq66-qwerty-us.keys, data/linux/qseq66.mutes, libseq66/include/cfg/notemapfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/keymap.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/src/qmutemaster.cpp: Fixes to mute groups, encoding change to save about 3K in writing them. 2021-05-11 Chris Ahlstrom * TODO, data/linux/qseq66-azerty.ctrl, data/linux/qseq66.mutes, data/linux/qseq66.usr, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/keymap.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qt5_helpers.cpp: Adjusting fingerprints, tightening mute-groups, more fixes for issue #47. 2021-05-10 Chris Ahlstrom * README.md, data/linux/qseq66.mutes, data/linux/qseq66.usr, data/samples/dark-gradient.qss, data/samples/flat-rounded.qss, data/samples/grey-ghost.qss, data/samples/qseq66.qss, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/keymap.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/keymap.cpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qslivegrid.cpp: Fixes for issue #47, progress box configuration, new style-sheets. * doc/latex/tex/configuration.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/src/cfg/cmdlineopts.cpp, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in: Updated Makefile.ins. 2021-05-09 Chris Ahlstrom * TODO, {seq_qt5/forms => contrib/code}/qseqeditframe.ui, contrib/vim-syntax/cpp.vim, data/linux/qseq66-azerty.ctrl, doc/latex/tex/midi_formats.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/triggers.cpp, seq_qt5/forms/Makefile.am, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqbase.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/mastermidibus.cpp: Finished trigger-transpose handling, fixed bug in reading 'ctrl' file for azerty keyboard. 2021-05-08 Chris Ahlstrom * README.md, TODO, {seq_qt5/src => contrib/code}/qseqeditframe.cpp, {seq_qt5/include => contrib/code}/qseqeditframe.hpp, doc/latex/tex/configuration.tex, doc/latex/tex/references.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/triggers.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/triggers.cpp, seq_qt5/include/Makefile.am, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed song-editor infinite loop, removed old qseqeditframe, fixed unneeded screenset creation in making background-seq menu. 2021-05-06 Chris Ahlstrom * README.md, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/keymap.hpp, libseq66/include/ctrl/keystroke.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/ctrl/keystroke.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Keystroke refactoring and trigger updates, but currently has issue with Song Editor causing infinite loop. 2021-05-04 Chris Ahlstrom * configure, contrib/scripts/timid, libseq66/include/ctrl/keystroke.hpp: Minor tweaks to timid script and dead code in keystroke class. * NEWS, README.md, Seqtool/Makefile.am, Seqtool/Seqtool.pro, Seqtool/forms/Makefile.am, Seqtool/forms/qtestframe.ui, Seqtool/include/Makefile.am, Seqtool/include/converter.hpp, Seqtool/include/faker.hpp, Seqtool/include/gdk_basic_keys.hpp, Seqtool/include/midi_control_helpers.hpp, Seqtool/include/midi_control_unit_test.hpp, Seqtool/include/optionsfile.hpp, Seqtool/include/qtcore_task.hpp, Seqtool/include/qtestframe.hpp, Seqtool/include/unit_tests.hpp, Seqtool/include/util_unit_test.hpp, Seqtool/src/Makefile.am, Seqtool/src/converter.cpp, Seqtool/src/faker.cpp, Seqtool/src/gdk_basic_keys.cpp, Seqtool/src/midi_control_helpers.cpp, Seqtool/src/midi_control_unit_test.cpp, Seqtool/src/optionsfile.cpp, Seqtool/src/qtcore_task.cpp, Seqtool/src/qtestframe.cpp, Seqtool/src/seqtool.cpp, Seqtool/src/unit_tests.cpp, Seqtool/src/util_unit_test.cpp, TODO, VERSION, configure.ac, configure.help, contrib/vim-syntax/c.vim, data/license.txt, data/linux/qseq66-azerty.ctrl, data/linux/qseq66.ctrl, data/readme.txt, doc/dox/doxy-common.cfg, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/ctrl/keymap.hpp, libseq66/include/ctrl/keystroke.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/play/performer.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qsliveframe.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qt5_helpers.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp: Removed Seqtool, upgraded keymap, JACK tweaks. 2021-05-03 Chris Ahlstrom * README.md, VERSION, configure.ac, data/license.txt, data/readme.txt, doc/dox/doxy-common.cfg, doc/latex/tex/event_editor.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/cfg/midicontrolfile.cpp: Version 0.93.1 AKA 'Show Stopper' merged to master. * README.md, contrib/scripts/make-checkout: Version 0.93.1 pending, fixed issue #51. 2021-05-02 Chris Ahlstrom * Makefile.in, libseq66/src/play/listsbase.cpp: Improvements to port nick-naming. * README.md, TODO, contrib/scripts/timid, data/linux/qseq66.ctrl, libseq66/include/midi/jack_assistant.hpp, libseq66/src/midi/jack_assistant.cpp, seq_rtmidi/src/midi_jack.cpp: Fixed issue #51, a JACK transport failure. 2021-05-01 Chris Ahlstrom * : commit 407779a8fabeca0568521912fc000f4966591218 Author: Chris Ahlstrom Date: Sat May 1 08:26:41 2021 -0400 2021-04-29 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, Seqtool/Makefile.in, Seqtool/forms/Makefile.in, Seqtool/include/Makefile.in, Seqtool/src/Makefile.in, configure, configure.help, data/Makefile.in, doc/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.am, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Removal of XPCCUT unit test symbols from the official build. * README.md, TODO, contrib/notes/styling.text, data/linux/qseq66-azerty.ctrl, data/linux/qseq66-default.palette, data/linux/qseq66.palette, data/samples/qseq66-sample.palette, data/samples/qseq66.qss, doc/latex/tex/seq66-user-manual.tex, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Fixed issue #50, palette upgrades, reduced length of window caption. 2021-04-28 Chris Ahlstrom * README.md, Seq66qt5/seq66qt5.cpp, TODO, data/linux/qseq66-azerty.ctrl, doc/latex/tex/configuration.tex, libseq66/include/ctrl/keycontainer.hpp, libseq66/include/ctrl/keymap.hpp, libseq66/include/ctrl/keystroke.hpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/ctrl/keystroke.cpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: More progress in working with an AZERTY (FR) keyboard. 2021-04-27 Chris Ahlstrom * README.md, TODO, contrib/scripts/Jack, contrib/scripts/seq66-nsm-proxy, data/linux/qseq66.mutes, doc/latex/tex/configuration.tex, libseq66/include/cfg/mutegroupsfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp: A reasonable fix to issue #49 plus some additiona mutes-file options and improved handling thereof. 2021-04-25 Chris Ahlstrom * .gitignore, Makefile.am, TODO, bootstrap, configure.ac, contrib/midi/README, contrib/scripts/make-checkout, data/linux/qseq66-azerty.ctrl, doc/latex/tex/palettes.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/app_limits.h, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/midicontrolin.cpp, pack, seq66.pro, seq_qt5/forms/qseqeventframe.ui: Removed Seqtool build, will remove its source files in a future release. * contrib/control-map.rc, data/linux/qseq66-azerty.ctrl, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66-lp-mini.ctrl, data/samples/nanomap.ctrl, data/samples/rowclipsmap.ctrl, data/win/qpseq66.ctrl, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontrol.cpp, libseq66/src/ctrl/midicontrol.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/play/performer.cpp: Fixed the part of issue #47 where the default ctrl configuration was saved at exit. 2021-04-24 Chris Ahlstrom * libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/ctrl/keycontainer.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/keymap.cpp, seq_qt5/include/qslotbutton.hpp: Interim check-in MIDI key control bug. 2021-04-23 Chris Ahlstrom * README.md, Seqtool/src/converter.cpp, contrib/notes/qw-az-keys.text, data/linux/qseq66-azerty.ctrl, data/linux/qseq66.ctrl, libseq66/include/ctrl/keycontainer.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/opcontrol.hpp, libseq66/include/os/timing.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp: More progress on support French AZERTY layout. * README.md, TODO, data/linux/qseq66-azerty.ctrl, doc/latex/tex/configuration.tex, libseq66/include/app_limits.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/recent.cpp, libseq66/src/ctrl/keymap.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Work on issue #47 to improve AZERTY support. 2021-04-21 Chris Ahlstrom * README.md, TODO, contrib/midi/play-pattern-1.text, contrib/scripts/valgrind-leaks.sh, doc/latex/tex/pattern_editor.tex, libseq66/include/play/sequence.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qbase.hpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qseqtime.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qt5nsmanager.cpp: Mostly small tweaks, and why is get_deleter for the smanager unique_ptr so slow in the destructor. 2021-04-20 Chris Ahlstrom * README.md, TODO, doc/latex/tex/configuration.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/playlist.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Basically finished one-shot/N-shot live pattern playback and saving to the pattern. 2021-04-19 Chris Ahlstrom * NEWS, README.md, TODO, VERSION, configure.ac, contrib/scripts/timid, data/linux/ca_ports.rc, data/midi/Kraftwerk-Europe_Endless.text, doc/latex/tex/patterns_panel.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.h, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, packages, seq_qt5/include/qseqframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Minor refactoring, clearing patterns on File / New.... 2021-04-17 Chris Ahlstrom * data/README, data/license.txt, data/readme.txt, data/readme.windows, doc/README, doc/dox/doxy-common.cfg, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/windows.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/build_release_package.bat: Updates and fixes for the Windows installer. 2021-04-16 Chris Ahlstrom * ChangeLog: Updated change-log, oops. * : Update Seq66 user-manual, version 0.93.0 pending. * configure, data/Makefile.in: Updating automake files. * : Update Seq66 user-manual, version 0.93.0 pending. * configure, data/Makefile.in: Updating automake files. * README.md, TODO, data/Makefile.am, data/linux/qseq66.rc, data/midi/Peter_Gunn.text, data/midi/README, doc/dox/Makefile.am, doc/latex/tex/pattern_editor.tex, doc/latex/tex/song_editor.tex, libseq66/src/cfg/rcfile.cpp, seq_qt5/include/qscrollmaster.h, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp: Fixed issue #45 for DESTDIR, improved horizontal zoom, minor fixes, add a Peter Gunn reconstruction. 2021-04-15 Chris Ahlstrom * README.md, VERSION, configure.ac, doc/latex/tex/song_editor.tex, include/config.h.in, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Added channel/buss menu to grid buttons, many tweaks and minor fixes. 2021-04-14 Chris Ahlstrom * README.md, data/midi/Kraftwerk-Europe_Endless.text, data/midi/README, doc/latex/tex/midi_export.tex, doc/latex/tex/song_editor.tex, libseq66/include/midi/event.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Added trigger transpose to Song Export. 2021-04-13 Chris Ahlstrom * README.md, data/linux/qseq66.rc, data/linux/qseq66.usr, data/midi/Kraftwerk-Europe_Endless.text, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, libseq66/src/util/calculations.cpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qsliveframe.cpp: Fixed Meta/SeqSpec length bug, crash in adding noteless trigger, new fingerprint and trigger-saving settings. 2021-04-11 Chris Ahlstrom * README.md, TODO, data/midi/Kraftwerk-Europe_Endless.text, doc/latex/tex/midi_formats.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/song_editor.tex, libseq66/include/midi/editable_event.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Enhanced event editing for channel-less tracks. 2021-04-10 Chris Ahlstrom * README.md, TODO, data/midi/EE-qsynth-presets.conf, data/midi/Kraftwerk-Europe_Endless.text, libseq66/include/play/sequence.hpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp: Adding row highlight and name-coloring in perfedit. 2021-04-09 Chris Ahlstrom * data/midi/Kraftwerk-Europe_Endless.asc, data/midi/Kraftwerk-Europe_Endless.text, libseq66/include/midi/midi_vector_base.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/triggers.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp: Working through more trigger-addition issues. 2021-04-08 Chris Ahlstrom * seq_qt5/src/qperfeditframe64.cpp: Forgot a minor formatting change. * README.md, libseq66/include/midi/eventlist.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqbase.cpp: Fixed perfroll snap, more options, including Seq24 behavior. 2021-04-06 Chris Ahlstrom * README.md, doc/latex/tex/pattern_editor.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/song_editor.tex, libseq66/include/midi/jack_assistant.hpp, libseq66/include/play/performer.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qperfeditex.hpp, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qperfeditex.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp: Upgraded and fixed L/R looping. 2021-04-04 Chris Ahlstrom * INSTALL, README.md, RELNOTES.md, TODO, VERSION, configure.ac, data/readme.txt, data/readme.windows, debian/changelog, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/play/sequence.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qperftime.hpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqtime.cpp: Bumping version, fixing seqkeys, working on pattern-start-tick feature in progress. 2021-04-03 Chris Ahlstrom * README.md, libseq66/include/app_limits.h, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qscrollmaster.h, seq_qt5/include/qseqkeys.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp: Fixed vertical zoom in the pattern editor. 2021-04-01 Chris Ahlstrom * doc/latex/tex/midi_formats.tex, doc/latex/tex/song_editor.tex, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp: Transposable triggers basically fully operational. 2021-03-31 Chris Ahlstrom * libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp: All trigger-transpose code except noting it in the trigger block in place. * : commit 89070eb5c0245971786f1574bf6e209025385b3c Author: Chris Ahlstrom Date: Wed Mar 31 15:05:11 2021 -0400 * libseq66/include/app_limits.h, libseq66/include/os/timing.hpp, libseq66/include/play/performer.hpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseqroll.cpp: Fixed ms/us confusion in performer::output_func(). 2021-03-30 Chris Ahlstrom * libseq66/include/os/timing.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/triggers.cpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsliveframe.cpp: Tinkering to get ultra-long patterns working properly. 2021-03-29 Chris Ahlstrom * libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqroll.cpp: Minor fixes to loop buttons and copy/paste handling in seqroll. 2021-03-28 Chris Ahlstrom * INSTALL, README.md, RELNOTES.md, TODO, VERSION, configure.ac, contrib/midi/README, contrib/scripts/qbuild, contrib/scripts/strap_functions, debian/changelog, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/sequence.hpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Set up for 0.92.3, debugging odd issues with a MIDI file. 2021-03-27 Chris Ahlstrom * : Merged optimizing into master for version 0.92.2. * README.md, VERSION, configure.ac, contrib/code/jack_impl.cpp, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/references.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Updated man/help, beat indicator, pattern fonts, JACK UUID retrieval. 2021-03-25 Chris Ahlstrom * : Forget to add an image for LaTeX to use. * INSTALL, README.md, doc/latex/tex/midi_export.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/song_editor.tex, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.h, libseq66/include/util/calculations.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/calculations.cpp, seq_portmidi/include/porttime.h, seq_portmidi/src/porttime.c, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmaintime.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmaintime.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes to the handling of modified time signatures. 2021-03-24 Chris Ahlstrom * doc/latex/tex/song_editor.tex, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp: Still tweaking song-recording.... 2021-03-23 Chris Ahlstrom * : commit 2f759571e9488fc11d22091d9dbb681412e071c5 Author: Chris Ahlstrom Date: Tue Mar 23 16:39:56 2021 -0400 * doc/latex/tex/song_editor.tex, libseq66/include/play/performer.hpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/triggers.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qsmainwnd.cpp: Tweaking song-recording and trigger editing. 2021-03-22 Chris Ahlstrom * README.md, TODO, contrib/DIR_COLORS, contrib/scripts/ystart, data/samples/qseq66.qss, doc/latex/tex/configuration.tex, doc/latex/tex/menu.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/app_limits.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/settings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Added style-sheet support, improved songsummary. 2021-03-18 Chris Ahlstrom * data/linux/qseq66.usr, doc/latex/tex/midi_formats.tex, libseq66/include/cfg/settings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp: Improving variable PPQN support, in progress. 2021-03-17 Chris Ahlstrom * data/linux/qseq66.ctrl, data/linux/qseq66.rc, data/win/qpseq66.ctrl, data/win/qpseq66.palette, data/win/qpseq66.rc, data/win/qpseq66.usr, doc/latex/tex/midi_formats.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/mutegroups.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/ctrl/midicontrolbase.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp: Tightened up creation of new configuration files at first start. 2021-03-16 Chris Ahlstrom * TODO, doc/latex/tex/configuration.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/setmaster.tex, libseq66/include/midi/midibase.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/listsbase.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qclocklayout.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/midi_alsa.cpp: Removed external setmaster, fixed most port-mapping issues. 2021-03-15 Chris Ahlstrom * libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/midi/midibus_common.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/listsbase.hpp, libseq66/include/play/performer.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/util/calculations.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qsmainwnd.cpp: Refactored clockslist and inputslist in preparation to retest port-mapping. 2021-03-14 Chris Ahlstrom * configure.ac, contrib/scripts/ystart, include/config.h.in, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/midicontrolbase.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp: Some refactoring of MIDI control In/Out handling, bugs still surviving. 2021-03-13 Chris Ahlstrom * configure, data/Makefile.in, doc/Makefile.in, doc/latex/tex/Makefile.in, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/midicontrolbase.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp, m4/Makefile.in: Fixing controls to use 'true-buss', fixing missing 'rc' file segfault due to uninitialized controls. 2021-03-12 Chris Ahlstrom * doc/latex/tex/midi_formats.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/setmaster.tex, libseq66/include/midi/midi_vector.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/play/screenset.hpp, libseq66/src/midi/midi_vector.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp: Tightening meta-event handling and sets. 2021-03-11 Chris Ahlstrom * contrib/scripts/helgrind-test.sh, contrib/scripts/htmldoc, doc/latex/tex/Makefile.am, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, libseq66/include/midi/event.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, m4/Makefile.am, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsliveframe.cpp, seq_rtmidi/src/midi_alsa.cpp: Documenting and updates to MIDI file SeqSpecs. 2021-03-08 Chris Ahlstrom * INSTALL, README.md, RELNOTES.md, VERSION, configure.ac, data/Makefile.am, debian/changelog, doc/Makefile.am, doc/latex/tex/docs-structure.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/usermidibus.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Version numbering updates, LaTeX updates, fixed bug setting Any as pattern channel. 2021-03-07 Chris Ahlstrom * README.md, debian/changelog, debian/control, debian/copyright, debian/libseq66-dev.install, debian/libseq66.install, debian/menu, debian/seq-rtmidi-dev.install, debian/seq-rtmidi.install, debian/seq66.xpm, doc/Makefile.am, doc/latex/tex/concepts.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/windows.tex, include/config.h.in, resources/pixmaps/SEQ66_24x24.xpm, resources/pixmaps/route66rwb-32x32.xpm, resources/pixmaps/route66rwb-66x66.xpm, resources/pixmaps/seq.xpm, resources/pixmaps/seq66.xpm, resources/pixmaps/seq66_32.xpm, resources/pixmaps/song-snap.xpm: Version 0.92.1 now ready. * ChangeLog, README.md, VERSION, configure.ac, doc/Makefile.am, doc/latex/tex/configuration.tex, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/mutes.tex, doc/latex/tex/seq66-user-manual.tex, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/play/performer.cpp, nsis/Seq66Setup.nsi, seq_qt5/forms/qslivegrid.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qsliveframe.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Many tweaks for pending version 0.92.1. 2021-03-06 Chris Ahlstrom * doc/latex/tex/patterns_panel.tex, libseq66/include/midi/jack_assistant.hpp, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, nsis/Seq66Setup.nsi: Added launchpad-mini.ods to doc, tightened performer a bit. 2021-03-05 Chris Ahlstrom * README.md, Seqtool/src/optionsfile.cpp, data/linux/qseq66-portmap.rc, doc/latex/tex/patterns_panel.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qseditoptions.cpp: Fixed issue #42, scrollable I/O ports, more ports. 2021-03-04 Chris Ahlstrom * libseq66/include/app_limits.h, libseq66/src/cfg/rcfile.cpp, seq_qt5/src/qclocklayout.cpp: Increased number of potential ports by a lot. 2021-03-03 Chris Ahlstrom * README.md, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/triggers.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qsmainwnd.cpp: Updating triggers, fixing song recording. 2021-03-01 Chris Ahlstrom * README.md, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/src/qsmainwnd.cpp: Quick check-in. 2021-02-28 Chris Ahlstrom * data/linux/qseq66-lp-mini-alt.ctrl, libseq66/include/ctrl/midicontrolout.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp: More refining of button status display. * data/linux/qseq66-lp-mini-alt.ctrl, data/linux/qseq66-lp-mini.ctrl, data/linux/qseq66.rc, doc/latex/tex/launchpad_mini.tex, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Enhanced and fixed automation controls and display. 2021-02-25 Chris Ahlstrom * contrib/notes/slots.txt, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini.ctrl, data/linux/qseq66.ctrl, data/samples/nanomap.ctrl, data/samples/rowclipsmap.ctrl, data/win/qpseq66.ctrl, doc/latex/tex/configuration.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/patterns_panel.tex, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/ctrl/opcontrol.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp: Fixed queue and keep-queue, some clean-up. 2021-02-23 Chris Ahlstrom * README.md, data/linux/qseq66-lp-mini.ctrl, doc/latex/tex/launchpad_mini.tex, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/play/performer.hpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Enhancements to MIDI I/O control. 2021-02-22 Chris Ahlstrom * README.md, data/linux/qseq66-lp-mini.ctrl, doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/pattern_editor.tex, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/keycontrol.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp: Made extracting port nick-names more robust, fixed bpm up and down, more automation. 2021-02-21 Chris Ahlstrom * contrib/dd-11/dd-11-notes.log, data/samples/GM_DD-11.drums, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqframe.cpp: Fixed bug parsing Qsynth ALSA port name and tested note-mapping with DD-11. * libseq66/src/midi/eventlist.cpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp: Fixed vertical coordination of qseqkeys and qseqroll. 2021-02-19 Chris Ahlstrom * README.md, data/linux/qseq66.rc, data/samples/rowclipsmap.rc, data/win/qpseq66.rc, doc/latex/tex/palettes.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qstriggereditor.cpp: More iterator cleanup for sequence cbegin/end. 2021-02-18 Chris Ahlstrom * doc/latex/tex/kbd_mouse.tex, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_portmidi/src/mastermidibus.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qstriggereditor.cpp: Working out event-iterator crashes and tentative auto-step oneshot recording. 2021-02-16 Chris Ahlstrom * configure: Updated configure script. * libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Mitigated seqfault occuring when large numbers of events accumulated in auto-step/step-edit recording. 2021-02-15 Chris Ahlstrom * : commit acefb6811d3a55c42a71ea0c438f645abba6d3c8 Author: Chris Ahlstrom Date: Mon Feb 15 15:06:53 2021 -0500 2021-02-14 Chris Ahlstrom * Makefile.in, libseq66/src/midi/midifile.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsliveframe.cpp: Tweaks to eliminate divide-by-zero. 2021-02-13 Chris Ahlstrom * ChangeLog, README.md: Updated master ChangeLog. * : Version 0.92.0 pending. 2021-02-12 Chris Ahlstrom * Seqtool/include/qtcore_task.hpp, Seqtool/include/qtestframe.hpp, Seqtool/src/faker.cpp, Seqtool/src/gdk_basic_keys.cpp, Seqtool/src/qtcore_task.cpp, doc/latex/tex/mutes.tex, libseq66/include/cfg/scales.hpp, libseq66/include/play/mutegroup.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/calculations.hpp, libseq66/src/ctrl/midicontrol.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midioperation.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midibytes.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/notemapper.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/play/seq.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/calculations.cpp, libseq66/src/util/filefunctions.cpp, libsessions/include/nsm/nsmmessagesex.hpp, libsessions/src/nsm/nsmbase.cpp, libsessions/src/nsm/nsmclient.cpp, libsessions/src/nsm/nsmserver.cpp, seq_portmidi/include/porttime.h, seq_portmidi/include/readbinaryplist.h, seq_portmidi/src/pmmac.c, seq_portmidi/src/pmmacosxcm.c, seq_portmidi/src/pmutil.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_portmidi/src/readbinaryplist.c, seq_qt5/forms/qmutemaster.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qplaylistframe.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qsabout.cpp, seq_qt5/src/qsbuildinfo.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qt5nsmanager.cpp: Lotsa code cleanup, still fixing mute-master. 2021-02-11 Chris Ahlstrom * README.md, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini.ctrl, doc/latex/tex/configuration.tex, doc/latex/tex/launchpad_mini.tex, libseq66/include/cfg/configfile.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/midicontrol.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/midicontrol.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/ctrl/opcontainer.cpp, libseq66/src/ctrl/opcontrol.cpp, libseq66/src/play/performer.cpp: Radically streamlined the 'ctrl' file and provided automatic upgrade code. 2021-02-10 Chris Ahlstrom * README, README.md, RELNOTES.md, data/license.txt, data/linux/qseq66.ctrl, data/readme.txt, data/readme.windows, data/win/qpseq66.ctrl, data/win/qpseq66.mutes, data/win/qpseq66.palette, data/win/qpseq66.rc, data/win/qpseq66.usr, data/win/qseq66-lp-mini-8x8.ctrl, data/win/qseq66-lp-mini.ctrl, libseq66/src/cfg/midicontrolfile.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_portmidi/src/midibus.cpp, seq_portmidi/src/portmidi.c, seq_qt5/forms/qmutemaster.ui, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes for Windows installer. 2021-02-09 Chris Ahlstrom * README, VERSION, configure.ac, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini.ctrl, data/linux/qseq66.ctrl, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, man/seq66.1, man/seq66cli.1, man/sequencer66.1: Updating version info and mans. 2021-02-09 Chris Ahlstrom * : commit 5020b3f5e07af84c24a929811074308fdb84f953 Author: Chris Ahlstrom Date: Tue Feb 9 15:21:31 2021 -0500 2021-02-07 Chris Ahlstrom * configure, doc/Makefile.in, doc/dox/Makefile.in: Updated configure and doc Makefile.ins on Ubuntu system. * README, bootstrap, configure.ac, {contrib/scripts => data/linux}/yoshimi-b4uacuse-gm.state, doc/Makefile.am, doc/latex/tex/headless.tex, doc/latex/tex/jack.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/playlist.tex, libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qplaylistframe.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qt5_helpers.cpp: More playlist fixes, added PDF file to doc directory. 2021-02-06 Chris Ahlstrom * doc/Makefile.in, doc/dox/Makefile.in, doc/latex/tex/Makefile.in, libseq66/src/play/performer.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qplaylistframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp: More updates to mutes, playlists, and updated some Makefile.ins. 2021-02-05 Chris Ahlstrom * libseq66/include/cfg/mutegroupsfile.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: More updates to mutes and playlists. 2021-02-04 Chris Ahlstrom * INSTALL, README, data/linux/qseq66.ctrl, data/linux/qseq66.mutes, data/linux/qseq66.rc, data/linux/qseq66.usr, doc/Makefile.am, doc/dox/Makefile.am, doc/latex/tex/Makefile.am, doc/latex/tex/configuration.tex, doc/latex/tex/docs-structure.tex, doc/latex/tex/event_editor.tex, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/playlist.tex, doc/latex/tex/sessions.tex, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/filefunctions.cpp, libseq66/src/util/strfunctions.cpp, seq_portmidi/src/pmwinmm.c, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: More playlist work, documentation updates. 2021-02-02 Chris Ahlstrom * : commit 57cbb327dd20df5de5bb06f87d78c63974c54901 Author: Chris Ahlstrom Date: Tue Feb 2 14:34:13 2021 -0500 2021-02-01 Chris Ahlstrom * : commit 573809a619fe08ee2c75fb9ad848eb728532a721 Author: Chris Ahlstrom Date: Mon Feb 1 18:58:15 2021 -0500 * README, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, seq_portmidi/src/mastermidibus.cpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Working on event editor and collateral clean-up. 2021-01-30 Chris Ahlstrom * README, doc/latex/tex/build.tex, doc/latex/tex/configuration.tex, doc/latex/tex/docs-structure.tex, doc/latex/tex/jack.tex, doc/latex/tex/manpage.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_impl_chart.tex, doc/latex/tex/palettes.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qsmainwnd.cpp: Fixing event editor, more documentation. 2021-01-29 Chris Ahlstrom * doc/Makefile.am, doc/dox/Makefile.am, doc/latex/tex/Makefile.am, doc/latex/tex/build.tex, doc/latex/tex/configuration.tex, doc/latex/tex/docs-structure.tex, doc/latex/tex/event_editor.tex, doc/latex/tex/headless.tex, doc/latex/tex/jack.tex, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/kudos.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/menu.tex, doc/latex/tex/meta_events.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/mutes.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/references.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, doc/latex/tex/song_editor.tex, seq_qt5/forms/qseditoptions.ui: Interim check-in of documentation. 2021-01-28 Chris Ahlstrom * doc/Makefile.in, doc/dox/Makefile.in, doc/latex/tex/Makefile.in, doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/meta_events.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/rc_file.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/song_editor.tex, doc/latex/tex/usr_file.tex: More user-manual updates. 2021-01-27 Chris Ahlstrom * README, VERSION, configure.ac, data/linux/qseq66-lp-mini.ctrl, {contrib/scripts => data/linux}/startjack, doc/latex/tex/build.tex, doc/latex/tex/configuration.tex, doc/latex/tex/jack.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/playlist.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qt5nsmanager.cpp: Beefed up mute-group control/status, usr definitions, and documentation, with many fixes. 2021-01-25 Chris Ahlstrom * contrib/notes/launchpad.txt, data/linux/qseq66-lp-mini.ctrl, doc/latex/tex/launchpad_mini.tex, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp: Initial adding of mute-group out-control support. 2021-01-24 Chris Ahlstrom * data/license.txt, data/readme.txt, data/readme.windows, doc/Makefile.am, doc/dox/Makefile.am, doc/latex/tex/Makefile.am, doc/latex/tex/build.tex, doc/latex/tex/configuration.tex, doc/latex/tex/headless.tex, doc/latex/tex/jack.tex, doc/latex/tex/launchpad_mini.tex, doc/latex/tex/manpage.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/palettes.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/qt_portmidi.tex, doc/latex/tex/rc_file.tex, doc/latex/tex/seq66-user-manual.tex, man/seq66cli.1, man/sequencer66.1, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi: More beefing up of the PDF documentation. * configure, doc/latex/tex/midi_formats.tex, libseq66/include/Makefile.in, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/midi/songsummary.hpp, libseq66/src/Makefile.in, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Perfected the song-summary Help option. 2021-01-23 Chris Ahlstrom * data/samples/sample.usr, libseq66/include/Makefile.am, libseq66/include/midi/songsummary.hpp, libseq66/include/play/sequence.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/controllers.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/songsummary.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Added songsummy module and made some fixes. 2021-01-21 Chris Ahlstrom * data/samples/GM_DD-11.drums, data/samples/sample.usr, doc/latex/tex/configuration.tex, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/userinstrument.hpp, libseq66/include/cfg/usermidibus.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/userinstrument.cpp, libseq66/src/cfg/usermidibus.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, seq_qt5/src/qseqeditframe64.cpp: Beefing up usr-file handling. 2021-01-20 Chris Ahlstrom * README, TODO, doc/latex/tex/configuration.tex, libseq66/include/app_limits.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/play/performer.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midibytes.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/calculations.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qsmaintime.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/src/midi_jack.cpp: Fixed issue #34 where seq66 did not seem to follow JACK tempo changes. 2021-01-19 Chris Ahlstrom * INSTALL, README, TODO, VERSION, configure.ac, doc/latex/tex/seq66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, libseq66/src/seq66_features.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Refactoring jack_assistant slightly. 2021-01-19 Chris Ahlstrom * ChangeLog, README: Version 0.91.6 pending. 2021-01-19 Chris Ahlstrom * Seqtool/src/optionsfile.cpp, doc/latex/tex/concepts.tex, doc/latex/tex/configuration.tex, doc/latex/tex/docs-structure.tex, doc/latex/tex/kudos.tex, doc/latex/tex/mutes.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/setmaster.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/rcsettings.cpp, man/sequencer66.1: Code cleanup and continued documentation. 2021-01-17 Chris Ahlstrom * README, doc/latex/tex/concepts.tex, doc/latex/tex/mutes.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/src/play/performer.cpp, seq_qt5/src/qmutemaster.cpp: Added mutemaster documentation and fixes. * libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/mutegroups.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp: More updates to mutemaster, enhanced config file creation at first startup. 2021-01-16 Chris Ahlstrom * libseq66/include/cfg/mutegroupsfile.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, seq_portmidi/src/ptlinux.c, seq_qt5/forms/qmutemaster.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qslivegrid.cpp: Refactoring the mutemaster in progress. 2021-01-15 Chris Ahlstrom * libseq66/include/play/setmapper.hpp, libseq66/src/play/setmapper.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed deleted-set handling in setmaster. 2021-01-14 Chris Ahlstrom * doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/setmaster.tex, libseq66/include/play/performer.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/forms/qsetmaster.ui, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixes to external live grid and setsmaster. * doc/latex/tex/headless.tex, doc/latex/tex/mutes.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/setmaster.tex: Added some .tex files. 2021-01-13 Chris Ahlstrom * doc/latex/tex/kbd_mouse.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/song_editor.tex, libseq66/include/play/performer.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/include/qsetmaster.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed multi-live-frame and set-up-down issues. * INSTALL, README, data/linux/qseq66.rc, doc/latex/tex/song_editor.tex, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/include/seq66_features.h, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, libseq66/src/seq66_features.cpp, seq_qt5/include/qperfroll.hpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp: Cleanup and fixes to the perfroll in progress. 2021-01-11 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, Seqtool/Makefile.in, Seqtool/forms/Makefile.in, Seqtool/include/Makefile.in, Seqtool/src/Makefile.in, configure, data/Makefile.in, doc/Makefile.in, doc/dox/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/play/triggers.hpp, libseq66/src/Makefile.in, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Restoring Makefile.in files. * INSTALL, Makefile.in, README, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, Seqtool/Makefile.in, Seqtool/forms/Makefile.in, Seqtool/include/Makefile.in, Seqtool/src/Makefile.in, TODO, VERSION, configure.ac, data/Makefile.in, doc/Makefile.in, doc/dox/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, doc/latex/tex/event_editor.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/song_editor.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/Makefile.in, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, man/seq66.1, man/seq66cli.1, man/sequencer66.1, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/forms/qlfoframe.ui, seq_qt5/include/Makefile.in, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/Makefile.in, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Fixes based on qloopbutton bug and perf bugs, more in progress. 2021-01-10 Chris Ahlstrom * VERSION, configure.ac, contrib/notes/git.txt, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Merged master, minor version updates. * contrib/notes/git.txt: Updated git notes. * ChangeLog, README: Version 0.91.5 pending. * TODO, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qstriggereditor.cpp: Disabled qstriggereditor select for note-on/off/aftertouch event. * README, configure, doc/latex/tex/pattern_editor.tex, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Interim check-in, fixing qstriggereditor. 2021-01-09 Chris Ahlstrom * TODO, doc/latex/tex/pattern_editor.tex, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp: Minor fixes including qloopbutton progress box. * doc/latex/tex/pattern_editor.tex, libseq66/include/cfg/scales.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, resources/pixmaps/scale.xpm, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslotbutton.cpp: Many ongoing fixes including enabling chords. 2021-01-08 Chris Ahlstrom * TODO, contrib/code/qseqrollpix.cpp, contrib/code/qseqrollpix.hpp, data/linux/qseq66-alt-gray.palette, data/linux/qseq66-gray.palette, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini.ctrl, data/linux/qseq66.mutes, data/linux/qseq66.palette, data/linux/qseq66.rc, data/linux/qseq66.rc.legacy, data/linux/qseq66.usr, data/samples/ca_midi.playlist, doc/latex/tex/pattern_editor.tex, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/os/timing.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsliveframe.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp: Optimizations of microsleep(), sequence, and GUI. 2021-01-06 Chris Ahlstrom * TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/cfg/scales.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/midifile.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp: Fixed more bugs in seqroll selection/drum mode. 2021-01-05 Chris Ahlstrom * README, TODO, VERSION, configure.ac, contrib/notes/valgrind.log, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/app_limits.h, libseq66/include/midi/midi_vector_base.hpp, libseq66/src/cfg/basesettings.cpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/comments.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/recent.cpp, libseq66/src/cfg/userinstrument.cpp, libseq66/src/cfg/usermidibus.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qrollframe.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qstriggereditor.cpp: Added double-quotes to cfg path-names, fixed some bugs from TODO. * doc/latex/tex/Makefile.in, resources/pixmaps/right.xpm, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp: Interim check-in of perfedit work. 2021-01-04 Chris Ahlstrom * README, TODO, doc/latex/tex/pattern_editor.tex, doc/latex/tex/song_editor.tex, libseq66/include/cfg/rcfile.hpp, libseq66/src/cfg/rcfile.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqroll.cpp: Added brush support to the palette, fixed a few perfedit bugs. 2021-01-03 Chris Ahlstrom * TODO, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Partial fixes to the Song Editor. * README, TODO, doc/latex/tex/pattern_editor.tex, libseq66/include/cfg/usrsettings.hpp, seq_qt5/forms/qseqeditframe.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqkeys.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp: Added vertical zoom to the pattern editor. 2021-01-02 Chris Ahlstrom * TODO, doc/latex/tex/pattern_editor.tex, excludes, libseq66/include/app_limits.h, libseq66/include/midi/eventlist.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, modules-to-compare.txt, seq_qt5/forms/qlfoframe.ui, seq_qt5/include/qbase.hpp, seq_qt5/include/qlfoframe.hpp, seq_qt5/include/qscrollmaster.h, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Fixing more bugs found doing documentation. 2020-12-31 Chris Ahlstrom * : Added a missing png file for the user manual. * README, TODO, doc/latex/tex/menu.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/seq66-user-manual.tex, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqeditframe.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Fixed event/data selection and added more documentation. 2020-12-30 Chris Ahlstrom * TODO, doc/latex/tex/build.tex, doc/latex/tex/headless.tex, doc/latex/tex/jack.tex, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/kudos.tex, doc/latex/tex/manpage.tex, doc/latex/tex/menu.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/playlist.tex, doc/latex/tex/qt_portmidi.tex, doc/latex/tex/rc_file.tex, doc/latex/tex/references.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/src/cfg/usrsettings.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qseditoptions.cpp: Fixed some odds-and-ends and more updates to the manual. * TODO, doc/latex/tex/Makefile.am, doc/latex/tex/build.tex, doc/latex/tex/concepts.tex, doc/latex/tex/event_editor.tex, doc/latex/tex/headless.tex, doc/latex/tex/jack.tex, doc/latex/tex/kbd_mouse.tex, doc/latex/tex/kudos.tex, doc/latex/tex/manpage.tex, doc/latex/tex/menu.tex, doc/latex/tex/meta_events.tex, doc/latex/tex/midi_export.tex, doc/latex/tex/midi_formats.tex, doc/latex/tex/midi_impl_chart.tex, doc/latex/tex/palettes.tex, doc/latex/tex/pattern_editor.tex, doc/latex/tex/patterns_panel.tex, doc/latex/tex/playlist.tex, doc/latex/tex/port_mapping.tex, doc/latex/tex/qt_portmidi.tex, doc/latex/tex/rc_file.tex, doc/latex/tex/references.tex, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/sessions.tex, doc/latex/tex/song_editor.tex, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/forms/qsliveframe.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Updating PDF, disabling port-maps for virtual ports. 2020-12-29 Chris Ahlstrom * Makefile.in, libseq66/include/play/listsbase.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Added I/O port-map activity flags to rc file. 2020-12-28 Chris Ahlstrom * libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/listsbase.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Finished basics of port-mapping and translation. * Makefile.in, README, TODO, VERSION, configure, configure.ac, data/license.txt, data/linux/qseq66-alt-gray.palette, data/linux/qseq66.palette, data/readme.txt, data/readme.windows, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/play/listsbase.cpp, man/sequencer66.1, seq_qt5/src/palettefile.cpp, seq_qt5/src/qclocklayout.cpp: Version update, output port-map work. 2020-12-27 Chris Ahlstrom * INSTALL, README, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/listsbase.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qseditoptions.cpp: Working on showing port-map in drop-downs. 2020-12-26 Chris Ahlstrom * TODO, libseq66/include/play/listsbase.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/qseqkeys.hpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in, working of port-mapping issues. 2020-12-24 Chris Ahlstrom * data/linux/qseq66-alt-gray.palette, data/linux/qseq66-gray.palette, data/linux/qseq66.palette, libseq66/include/util/palette.hpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Interim check-in, sel_paint() still needs work. * data/linux/qseq66-alt-gray.palette, data/linux/qseq66-gray.palette, data/linux/qseq66.palette, libseq66/include/util/palette.hpp, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsliveframe.cpp: Refactoring, trimming palette code, in progress. 2020-12-23 Chris Ahlstrom * TODO, libseq66/include/util/palette.hpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/palettefile.hpp, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp: Fixing weird palette issues, added ui palette. 2020-12-22 Chris Ahlstrom * seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp: Fixes to drawing drum notes and handling no palette file. * TODO, contrib/DIR_COLORS, data/linux/qseq66-alt-gray.palette, data/linux/qseq66-gray.palette, data/linux/qseq66.palette, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/mutegroupsfile.hpp, libseq66/include/cfg/notemapfile.hpp, libseq66/include/cfg/playlistfile.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrfile.hpp, libseq66/include/play/playlist.hpp, libseq66/include/util/palette.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/playlist.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/Makefile.am, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/palettefile.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/palettefile.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qt5nsmanager.cpp: More palette updates, almost complete. 2020-12-21 Chris Ahlstrom * .gitignore, contrib/notes/git.txt, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp: Safety check-in. 2020-12-20 Chris Ahlstrom * README, doc/latex/tex/rc_file.tex, libseq66/include/util/palette.hpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in for palette refactoring. 2020-12-19 Chris Ahlstrom * configure, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/listsbase.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/performer.cpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qseditoptions.cpp: Added input port-map support, mildly tested. * README, TODO, VERSION, configure.ac, data/license.txt, data/readme.txt, data/readme.windows, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/listsbase.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/listsbase.cpp, man/sequencer66.1, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/src/midi_alsa.cpp: Output port-mapping and ALSA client ID working now. 2020-12-18 Chris Ahlstrom * TODO, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/listsbase.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, libseq66/src/util/filefunctions.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsessionframe.ui, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp: Initial creation of output port map basically works. 2020-12-16 Chris Ahlstrom * ChangeLog: Version 0.91.3 pending. * INSTALL, README, TODO, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/playlist.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midi_probe.cpp, seq_rtmidi/src/rtmidi_types.cpp: Fixes to port-naming and setup, playlist fixes. 2020-12-15 Chris Ahlstrom * README, contrib/scripts/startjack, libseq66/include/midi/midibase.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/midibase.cpp, seq_portmidi/include/midibus_pm.hpp, seq_portmidi/src/midibus.cpp, seq_rtmidi/include/midi_alsa.hpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midibus.cpp, seq_rtmidi/src/rtmidi_types.cpp: Interim check-in, fixing disabled-port issues. 2020-12-13 Chris Ahlstrom * libseq66/include/midi/midifile.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed recent-files issues, still working on JACK input. 2020-12-12 Chris Ahlstrom * Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, Seqtool/Makefile.in, Seqtool/forms/Makefile.in, Seqtool/include/Makefile.in, Seqtool/src/Makefile.in, configure, data/Makefile.in, doc/Makefile.in, doc/dox/Makefile.in, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_qt5/src/qsetmaster.cpp, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Updating Makefile.ins in Ubuntu 18.04.5 LTS. 2020-12-12 Chris Ahlstrom * README, VERSION, configure.ac, contrib/scripts/startjack, data/linux/qseq66.rc, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/businfo.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midibus.cpp: Still more fixes to port naming. 2020-12-11 Chris Ahlstrom * README, libseq66/include/app_limits.h, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/performer.cpp, man/sequencer66.1, seq_qt5/forms/qseditoptions.ui, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midibus.cpp: Fixed JACK virtual inport port naming issues. * libseq66/include/Makefile.am, libseq66/include/midi/midibus_common.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/play/listsbase.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/play/clockslist.cpp, libseq66/src/play/inputslist.cpp, libseq66/src/play/listsbase.cpp: Added modules accidentally forgotten, doh. * INSTALL, VERSION, configure.ac, contrib/scripts/startjack, include/config.h.in, libseq66/include/midi/businfo.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midi_vector_base.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/util/calculations.hpp, libseq66/include/util/strfunctions.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/calculations.cpp, man/sequencer66.1, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midibus.cpp: Interim check-in, much fiddling with port naming. 2020-12-07 Chris Ahlstrom * README, Seqtool/src/optionsfile.cpp, data/samples/sample.usr, doc/latex/tex/usr_file.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/seq.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsetmaster.hpp, seq_qt5/src/qsmainwnd.cpp: Got options for set auto-arming and multiple-set playback working. 2020-12-06 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp: Refactored playset to prep for multi-set playback. 2020-12-05 Chris Ahlstrom * README, VERSION, configure, configure.ac, data/license.txt, data/readme.txt, data/readme.windows, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/app_limits.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Interim safety check in for set and mutes handling. 2020-12-04 Chris Ahlstrom * : commit 77caba631e9085b9c5eff7eaa5a7054dde5d59e8 Author: Chris Ahlstrom Date: Fri Dec 4 16:43:09 2020 -0500 2020-11-30 Chris Ahlstrom * ChangeLog, README: Version 0.91.2 pending. * data/readme.windows, doc/latex/tex/menu.tex, doc/latex/tex/playlist.tex, doc/latex/tex/seq66-user-manual.tex, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/playlist.cpp, seq_qt5/src/qt5_helpers.cpp: Fixing crash with blank ctrl/mute filenames. 2020-11-29 Chris Ahlstrom * NEWS, README, TODO, configure, data/license.txt, desktop/metainfo/seq66.appdata.xml, doc/dox/doxy-common.cfg, doc/latex/tex/seq66-user-manual.tex, libseq66/include/cfg/configfile.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, man/seq66.1, man/seq66cli.1, nsis/Seq66Constants.nsh, nsis/build_release_package.bat: Update version info for 0.91.2. * INSTALL, README, VERSION, configure.ac, data/license.txt, data/readme.txt, data/readme.windows, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp: Fixing broken playlist functionality, gosh darn it. 2020-11-28 Chris Ahlstrom * contrib/scripts/make-checkout: Updated make-checkout script to add the doc/latex/tex/Makefile.in file. * ChangeLog, README: Version 0.91.1 pending. * README, resources/pixmaps/finger.xpm, seq_qt5/forms/qperfeditframe64.ui, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp: Added insert/entry button to the perfedit. * README, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/sessions/smanager.cpp, resources/pixmaps/Makefile.am, resources/pixmaps/Makefile.in, resources/pixmaps/drum_mode.xpm, resources/pixmaps/finger.xpm, seq_qt5/forms/qseqeditframe.ui, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Added note-entry mode button to seqedit, save/restore for mute-group. 2020-11-26 Chris Ahlstrom * : commit 4c72dcadf30f01ea93b7cdcfb64361f2cfcd3310 Author: Chris Ahlstrom Date: Thu Nov 26 07:45:54 2020 -0500 2020-11-25 Chris Ahlstrom * contrib/scripts/ystart, doc/dox/Makefile.am, doc/dox/Makefile.in, doc/latex/Makefile.am, doc/latex/Makefile.in, doc/latex/tex/Makefile.in, libseq66/include/play/mutegroups.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/seq66_features.cpp, pack, seq_qt5/src/qt5nsmanager.cpp: Fixes to tarball builds and mutegroups issue #18. 2020-11-24 Chris Ahlstrom * libseq66/include/app_limits.h, libseq66/include/play/mutegroup.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/sessions/clinsmanager.cpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midibus.cpp: Working on issue #18, handling mute-group toggle. * libseq66/src/sessions/clinsmanager.cpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midibus.cpp: Safety check-in, renaming client ports more uniformly. 2020-11-23 Chris Ahlstrom * libseq66/src/sessions/clinsmanager.cpp: Merged fix for issue with over-zealous detection of NSM-compatible daemons. * : commit 4b5f951234d74e0ba04787b47b0e8cec43d35216 Merge: d1fa7bf 9516dc0 Author: C. Ahlstrom Date: Mon Nov 23 19:21:27 2020 -0500 * libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/mutegroup.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/mutegroup.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/clinsmanager.cpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/src/qmutemaster.cpp: Tentative fix to issue #28, mute groups. * libseq66/src/sessions/clinsmanager.cpp: remove nsmd running detection 2020-11-22 Chris Ahlstrom * README, VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/playlist.hpp, libseq66/src/play/playlist.cpp, libseq66/src/play/screenset.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Ongoing playlist/mutes fixes. * contrib/scripts/make-checkout, doc/latex/tex/menu.tex, doc/latex/tex/sessions.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/rcsettings.cpp: Interim check-in, need to fix mutes handling, sigh. 2020-11-21 Chris Ahlstrom * ChangeLog, README, VERSION, configure.ac, doc/latex/tex/sessions.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/util/basic_macros.h, libseq66/src/util/basic_macros.cpp: Version 0.91.0 pending. * RELNOTES.md, contrib/vim-syntax/cpp.vim, data/samples/GM_DD-11.drums, data/samples/ca_midi.playlist, data/samples/rowclipsmap.ctrl, data/samples/rowclipsmap.mutes, data/samples/rowclipsmap.rc, data/samples/sample.playlist, libseq66/include/play/playlist.hpp, libseq66/src/play/playlist.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qt5_helpers.cpp: Playlist editing basically works. 2020-11-20 Chris Ahlstrom * data/samples/ca_midi.playlist, data/samples/sample.playlist, doc/latex/tex/sessions.tex, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/include/nsm/nsmbase.hpp, libsessions/src/nsm/nsmbase.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/include/qplaylistframe.hpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsabout.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Fixed issue with retrieving NSM messages, more playlist improvements in progress. 2020-11-18 Chris Ahlstrom * data/samples/sample.playlist, libseq66/include/play/performer.hpp, libseq66/include/play/playlist.hpp, libseq66/src/play/playlist.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/include/qt5_helpers.hpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5_helpers.cpp: Interim check-in, playlist improvements. 2020-11-16 Chris Ahlstrom * README, RELNOTES.md, VERSION, arch/package/PKGBUILD, arch/package/PKGBUILD-alt, configure, configure.ac, contrib/notes/git.txt, data/license.txt, data/readme.txt, data/readme.windows, doc/latex/Makefile.in, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/forms/qplaylistframe.ui, seq_qt5/include/qplaylistframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qplaylistframe.cpp: Ongoing improvements to playlist management. 2020-11-15 Chris Ahlstrom * libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, seq_portmidi/seq_portmidi.pro, seq_qt5/src/qsmainwnd.cpp: Added feature to auto-load the most recent file at startup. 2020-11-14 Chris Ahlstrom * libseq66/include/cfg/rcsettings.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/recent.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/playlist.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Updates to recent-file management. 2020-11-13 Chris Ahlstrom * Seq66cli/seq66rtcli.cpp, TODO, contrib/DIR_COLORS, doc/latex/tex/rc_file.tex, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/playlist.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/util/filefunctions.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/forms/qsessionframe.ui, seq_qt5/include/qsessionframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsessionframe.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Provisional fix to issue #26 and configuration improvements. 2020-11-11 Chris Ahlstrom * README, Seq66cli/Makefile.am, Seq66cli/Makefile.in, Seq66cli/seq66rtcli.cpp, Seq66qt5/Makefile.am, Seq66qt5/Makefile.in, Seq66qt5/seq66qt5.cpp, VERSION, configure.ac, contrib/non/nsm-proxy-h2.sh, doc/latex/tex/menu.tex, doc/latex/tex/playlist.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Got CLI building and running, NSM testing needed. 2020-11-08 Chris Ahlstrom * README, Seq66cli/seq66rtcli.cpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/playlist.hpp, libseq66/include/util/filefunctions.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/playlist.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/filefunctions.cpp, libseq66/src/util/strfunctions.cpp, libsessions/src/lash/lash.cpp, man/sequencer66.1, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qsmainwnd.cpp: Fixing PPQN issues with playlist loading and a lot more. 2020-11-04 Chris Ahlstrom * contrib/non/nsm-emails.txt, doc/latex/tex/{seq66_build.tex => build.tex}, doc/latex/tex/{seq66_concepts.tex => concepts.tex}, doc/latex/tex/docs-structure.tex, doc/latex/tex/{seq66_event_editor.tex => event_editor.tex}, doc/latex/tex/{seq66_headless.tex => headless.tex}, doc/latex/tex/{seq66_jack.tex => jack.tex}, doc/latex/tex/{seq66_kbd_mouse.tex => kbd_mouse.tex}, doc/latex/tex/{seq66_kudos.tex => kudos.tex}, doc/latex/tex/{seq66_manpage.tex => manpage.tex}, doc/latex/tex/{seq66_menu.tex => menu.tex}, doc/latex/tex/{seq66_meta_events.tex => meta_events.tex}, doc/latex/tex/{seq66_midi_export.tex => midi_export.tex}, doc/latex/tex/{seq66_midi_formats.tex => midi_formats.tex}, doc/latex/tex/{seq66_midi_impl_chart.tex => midi_impl_chart.tex}, doc/latex/tex/{seq66_pattern_editor.tex => pattern_editor.tex}, doc/latex/tex/{seq66_patterns_panel.tex => patterns_panel.tex}, doc/latex/tex/{seq66_playlist.tex => playlist.tex}, doc/latex/tex/{seq66_qt_portmidi.tex => qt_portmidi.tex}, doc/latex/tex/{seq66_rc_file.tex => rc_file.tex}, doc/latex/tex/{seq66_references.tex => references.tex}, doc/latex/tex/seq66-user-manual.tex, doc/latex/tex/{seq66_sessions.tex => sessions.tex}, doc/latex/tex/{seq66_song_editor.tex => song_editor.tex}, doc/latex/tex/{seq66_usr_file.tex => usr_file.tex}, libseq66/include/cfg/rcsettings.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/include/nsm/nsmbase.hpp, libsessions/src/nsm/nsmbase.cpp, man/sequencer66.1, seq_qt5/src/qlfoframe.cpp: Added better NSM handling, documentation, more, in progress. 2020-11-01 Chris Ahlstrom * include/qt/rtmidi/seq66-config.h, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/include/nsm/nsmbase.hpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Trying to get Detach Session to work, weird lo_server_thread_stop() hang. 2020-10-29 Chris Ahlstrom * INSTALL, Seq66qt5/seq66qt5.cpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/forms/qsabout.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Fixes to saving, issue #21 wrong email link. 2020-10-26 Chris Ahlstrom * INSTALL, configure, configure.ac, doc/dox/libseq66/libseq66.cfg, doc/dox/seq_portmidi/seq_portmidi.cfg, doc/dox/seq_rtmidi/seq_rtmidi.cfg, doc/latex/tex/seq66_sessions.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/midibus_impl.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/sessions/clinsmanager.cpp, libsessions/include/nsm/nsmbase.hpp, libsessions/src/nsm/nsmbase.cpp, man/sequencer66.1, seq_portmidi/src/midibus_impl.cpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midibus.cpp, seq_rtmidi/src/midibus_impl.cpp: Added client-name option, remove unused impl modules. 2020-10-25 Chris Ahlstrom * libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp: Disabled debug code. 2020-10-24 Chris Ahlstrom * configure, include/config.h.in, libseq66/include/Makefile.am, libseq66/include/Makefile.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/sessions/smfunctions.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/Makefile.in, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/smfunctions.cpp, libsessions/src/nsm/nsmbase.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/forms/qsessionframe.ui: Interim check-in, untested session-specific client ID. 2020-10-22 Chris Ahlstrom * configure.ac, doc/latex/tex/seq66_sessions.tex, libseq66/include/cfg/notemapfile.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/src/qsmainwnd.cpp: Added copying of drums files to session startup. 2020-10-17 Chris Ahlstrom * libseq66/include/sessions/clinsmanager.hpp, libseq66/src/play/playlist.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/util/filefunctions.cpp, libsessions/include/nsm/nsmbase.hpp, libsessions/src/nsm/nsmbase.cpp, libsessions/src/nsm/nsmclient.cpp: Interim, fixing full-copy of playlist. 2020-10-15 Chris Ahlstrom * data/samples/sample.playlist, libseq66/src/sessions/clinsmanager.cpp: Interim check-in for playlist copy issue. 2020-10-12 Chris Ahlstrom * README, VERSION, configure.ac, doc/latex/tex/seq66_sessions.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qsliveframe.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: More documentation, fixed error in set-naming. 2020-10-10 Chris Ahlstrom * contrib/scripts/make-checkout: Added a cleanup script for Makefile.in modifications. 2020-10-10 Chris Ahlstrom * .gitignore, doc/latex/tex/seq66_sessions.tex, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/forms/qsessionframe.ui: Fixed weird lo server add-method segfault. 2020-10-07 Chris Ahlstrom * libseq66/include/cfg/playlistfile.hpp, libseq66/include/play/playlist.hpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/src/qt5nsmanager.cpp: Fixed first-start issues and error-reporting. 2020-10-04 Chris Ahlstrom * doc/latex/tex/seq66_sessions.tex, libseq66/src/cfg/rcfile.cpp: Fixed error in writing ctrl file when first running seq66. 2020-10-03 Chris Ahlstrom * doc/latex/Makefile.am, doc/latex/tex/Makefile.am: Added latex/tex automake Makefiles. * Makefile.am, Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, Seqtool/Makefile.in, Seqtool/forms/Makefile.in, Seqtool/include/Makefile.in, Seqtool/src/Makefile.in, configure, configure.ac, contrib/scripts/seq66-nsm-proxy, data/Makefile.in, doc/Makefile.am, doc/Makefile.in, doc/dox/Makefile.am, doc/dox/Makefile.in, doc/dox/doxy-common.cfg, doc/dox/libseq66/libseq66.cfg, doc/dox/{notes/notes.cfg => libsessions/libsessions.cfg}, doc/dox/libsessions/mainpage.dox, doc/dox/{libseq66 => }/make-helper, doc/dox/make_dox, doc/dox/notes/condvars.dox, doc/dox/notes/coverage_profiling.dox, doc/dox/notes/jack_modes.dox, doc/dox/notes/license.dox, doc/dox/notes/mainpage.dox, doc/dox/notes/make-helper, doc/dox/notes/midi_parsing.dox, doc/dox/notes/user_testing.dox, doc/dox/optimize, doc/dox/seq_portmidi/make-helper, doc/dox/seq_portmidi/seq_portmidi.cfg, doc/dox/seq_rtmidi/make-helper, doc/dox/seq_rtmidi/seq_rtmidi.cfg, doc/latex/Makefile-helper, doc/latex/README, doc/latex/tex/seq66-user-manual.tex, excludes, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, pack, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Beefed up the doxygen and latex/pdf documentation system. 2020-09-29 Chris Ahlstrom * .gitignore, INSTALL, Makefile.in, Seq66cli/Makefile.in, Seq66qt5/Makefile.in, Seqtool/Makefile.in, Seqtool/forms/Makefile.in, Seqtool/include/Makefile.in, Seqtool/src/Makefile.in, bootstrap, configure, configure.ac, contrib/scripts/qbuild, data/Makefile.in, include/config.h.in, libseq66/Makefile.in, libseq66/include/Makefile.in, libseq66/src/Makefile.in, libseq66/src/cfg/rcfile.cpp, libsessions/Makefile.in, libsessions/include/Makefile.in, libsessions/include/nsm/nsmbase.hpp, libsessions/src/Makefile.in, m4/Makefile.in, man/Makefile.in, pack, resources/pixmaps/Makefile.in, seq_portmidi/Makefile.in, seq_portmidi/include/Makefile.in, seq_portmidi/src/Makefile.in, seq_qt5/Makefile.in, seq_qt5/forms/Makefile.in, seq_qt5/include/Makefile.in, seq_qt5/src/Makefile.in, seq_rtmidi/Makefile.in, seq_rtmidi/include/Makefile.in, seq_rtmidi/src/Makefile.in: Fixed rc creation at first start up with NSM support not built in, added Makefile.in files to project. 2020-09-28 Chris Ahlstrom * contrib/scripts/qbuild, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/notemapper.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/notemapper.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, man/sequencer66.1: Fixing first-startup issues re normal support. 2020-09-27 Chris Ahlstrom * Seqtool/src/optionsfile.cpp, libseq66/include/sessions/clinsmanager.hpp, libseq66/include/sessions/smanager.hpp, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/playlistfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/os/daemonize.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/sessions/clinsmanager.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/filefunctions.cpp, libsessions/src/nsm/nsmbase.cpp, seq_qt5/src/qsmainwnd.cpp: Interim check-in, fixed command-line-parsing, changed a function name to be more sane. 2020-09-27 Chris Ahlstrom * libseq66/include/play/playlist.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp: Interim check-in, issue with -b parsing remains. 2020-09-26 Chris Ahlstrom * : Merged master 0.90.6 and fixed playlist null pointer checks. * NEWS, README, VERSION, bootstrap, configure, configure.ac, contrib/scripts/debug, contrib/scripts/qbuild, data/README, data/license.txt, data/readme.txt, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat: Improvements related to fixing issue #19 segfault. 2020-09-25 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qseqeditframe.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qsetmaster.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Upgrade related to issue #19, improvement to sequence-change handling to update the UI appropriately. 2020-09-24 Chris Ahlstrom * libseq66/src/play/sequence.cpp: Tentative fix for Crash when recording note #19. 2020-08-15 Chris Ahlstrom * libsessions/src/nsm/nsm.cpp, seq_portmidi/src/portmidi.c: Cleanup of qmake release builds for portmidi and rtmidi. * data/Makefile.am, data/win/qseq66-lp-mini-8x8.ctrl, data/win/qseq66-lp-mini.ctrl, include/config.h.in: Fixes for 0.90.5 version. * INSTALL, README, TODO, VERSION, configure.ac, data/license.txt, data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini.ctrl, data/linux/qseq66.mutes, data/linux/qseq66.rc, data/linux/qseq66.usr, data/readme.txt, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/src/cfg/midicontrolfile.cpp: Improved portmidi error-handling, updated versioning. 2020-08-14 Chris Ahlstrom * README, contrib/notes/git.txt, data/readme.txt, data/readme.windows, libseq66/include/cfg/configfile.hpp, libseq66/include/sessions/smanager.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/play/playlist.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/strfunctions.cpp, nsis/build_release_package.bat, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Improved error-handling at startup, detection of ctrl vs live-grid dimension mismatches. * Seq66qt5/seq66qt5.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp: Fixing MIDI control error handling in progress. 2020-08-13 Chris Ahlstrom * data/linux/qseq66-lp-mini-8x8.ctrl, data/linux/qseq66-lp-mini.ctrl, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolbase.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp: Tweaking for sizing, adding MIDI Control I/O sizes. 2020-08-12 Chris Ahlstrom * README, Seqtool/src/gdk_basic_keys.cpp, Seqtool/src/optionsfile.cpp, VERSION, configure, configure.ac, contrib/code/affinity.cpp, contrib/notes/slots.txt, data/README, data/linux/qseq66-lp-mini-8x8.ctrl, data/{ => linux}/qseq66-lp-mini.ctrl, data/{ => linux}/qseq66.ctrl, data/{ => linux}/qseq66.mutes, data/{ => linux}/qseq66.rc, data/{ => linux}/qseq66.rc.legacy, data/{ => linux}/qseq66.usr, data/{ => samples}/GM_DD-11.drums, data/{ => samples}/GM_PSS-790.drums, data/{ => samples}/rowclipsmap.ctrl, data/{ => samples}/rowclipsmap.mutes, data/{ => samples}/rowclipsmap.rc, data/{ => samples}/sample.playlist, data/win/qpseq66.ctrl, data/win/qpseq66.mutes, data/win/qpseq66.rc, data/win/qpseq66.usr, data/win/qseq66-lp-mini-8x8.ctrl, data/{qseq66-lp-mini-8x8.ctrl => win/qseq66-lp-mini.ctrl}, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/app_limits.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, libsessions/src/nsm/nsm.cpp, seq_portmidi/src/mastermidibus.cpp, seq_portmidi/src/pmlinuxalsa.c, seq_portmidi/src/pmwinmm.c, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/include/midi_jack.hpp: Got basic set-handling working plus Launchpad handling. 2020-08-11 Chris Ahlstrom * configure, contrib/notes/launchpad.txt, data/qseq66-lp-mini-8x8.ctrl, libseq66/include/play/performer.hpp, libseq66/include/play/setmapper.hpp, libseq66/libseq66.pro, libseq66/src/play/setmapper.cpp, seq_qt5/src/qslivegrid.cpp: Interim check-in, a bug in 8x8 support exists. 2020-08-10 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/src/qsetmaster.cpp: Usage of setmaster nearly complete. * configure, contrib/notes/slots.txt, data/qseq66-lp-mini-8x8.ctrl, doc/latex/Makefile, doc/latex/tex/Makefile, libseq66/include/Makefile.am, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/include/play/setmaster.hpp, libseq66/src/Makefile.am, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/setmaster.cpp, seq_qt5/include/qsetmaster.hpp: Refactoring to use new setmaster in progress. 2020-08-09 Chris Ahlstrom * Seqtool/src/optionsfile.cpp, libseq66/include/app_limits.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/mutegroups.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/seq.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Cleaning out constants for rows, columns, sets, in order to test and fix 8x8 mode. 2020-08-07 Chris Ahlstrom * data/qseq66-lp-mini.ctrl, libseq66/include/ctrl/midicontrolout.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp: Seemingly have fixed MIDI control thoroughly. * README, data/qseq66-lp-mini.ctrl, doc/latex/Makefile, doc/latex/tex/Makefile, doc/latex/tex/{sequencer66-user-manual.tex => seq66-user-manual.tex}, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp: Safety check-in, tex changes, still some minor ctrl-file issues. * contrib/notes/launchpad.txt, contrib/vim-syntax/cpp.vim, data/qseq66-lp-mini.ctrl, include/config.h.in, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/midicontrolout.cpp: Still fixing writing outs to ctrl file. 2020-08-06 Chris Ahlstrom * contrib/notes/launchpad.txt, data/qseq66-lp-mini.ctrl, data/qseq66.ctrl, data/rowclipsmap.ctrl, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/ctrl/automation.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp: Refactoring MIDI-control-out in progress, interim check-in. 2020-08-05 Chris Ahlstrom * README, VERSION, configure.ac, contrib/notes/launchpad.txt, data/license.txt, data/qseq66-lp-mini.ctrl, data/readme.txt, data/readme.windows, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/app_limits.h, libseq66/include/cfg/comments.hpp, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/ctrl/midicontrolin.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/comments.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/midicontrolbase.cpp, libseq66/src/ctrl/midicontrolin.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/strfunctions.cpp, seq_portmidi/src/mastermidibus.cpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_info.cpp: Fixes to midicontrolout processing still in progress, improving. 2020-08-03 Chris Ahlstrom * Seqtool/src/converter.cpp, Seqtool/src/optionsfile.cpp, Seqtool/src/seqtool.cpp, contrib/notes/launchpad.txt, data/qseq66-lp-mini.ctrl, libseq66/include/cfg/comments.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/include/util/basic_macros.h, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/comments.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/sessions/smanager.cpp, seq_rtmidi/src/midi_alsa_info.cpp: More MIDI control fixes, Launchpad corrections, and fixes to configuration files comments. 2020-08-02 Chris Ahlstrom * data/qseq66-lp-mini.ctrl, libseq66/include/app_limits.h, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibase.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/performer.cpp, seq_rtmidi/include/midi_alsa.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_info.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midibus.cpp, seq_rtmidi/src/midibus_impl.cpp: Fixing the handling of MIDI controls in progress. 2020-08-01 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsmainwnd.cpp: Refactoring and improving modification management. 2020-07-30 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qsmainwnd.cpp: Refactoring setmapper and screenset. * libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditframe64.cpp: Improved modification response for sequence changes. 2020-07-29 Chris Ahlstrom * data/readme.windows, libseq66/include/play/sequence.hpp, libseq66/include/seq66_platform_macros.h, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qbase.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qseqbase.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qsliveframe.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qbase.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp: More fixes for dirty status, still needs testing. 2020-07-28 Chris Ahlstrom * libseq66/include/midi/event.hpp, libseq66/src/midi/event.cpp, libseq66/src/play/sequence.cpp, nsis/build_release_package.bat, seq_portmidi/src/pmwin.c, seq_portmidi/src/pmwinmm.c, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/include/qbase.hpp, seq_qt5/include/qseqeditframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixing dirty status in progress, fixing minor issues as found. 2020-07-27 Chris Ahlstrom * TODO, VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/include/qbase.hpp, seq_qt5/include/qclocklayout.hpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qinputcheckbox.hpp, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditframe.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qbase.cpp, seq_qt5/src/qclocklayout.cpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qperfeditex.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Refactored performer callbacks for the user-interfaces. 2020-07-25 Chris Ahlstrom * TODO, configure, configure.ac, contrib/notes/performance.txt, data/license.txt, data/readme.txt, data/readme.windows, libseq66/include/cfg/usrsettings.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/play/performer.hpp, libseq66/include/util/calculations.hpp, libseq66/src/cfg/settings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_portmidi/src/porttime.c, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/src/qbase.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp, seq_qt5/src/qt5nsmanager.cpp: Can now load a non-192 PPQN file and display it properly, still more to do. 2020-07-24 Chris Ahlstrom * libseq66/include/midi/eventlist.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, m4/ax_have_qt_ex.m4, seq_qt5/src/qsmainwnd.cpp: Some tweaks to fix PPQN conversion, still in progress. 2020-07-23 Chris Ahlstrom * Seq66cli/seq66rtcli.cpp, configure, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_portmidi/src/ptlinux.c, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: PPQN almost working, global buss working. * VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qsmainwnd.cpp: Adding global PPQN and buss number support, in progress. 2020-07-22 Chris Ahlstrom * : Fixed issue #11 re follow-progress button, fixed broken playback on Windows. 2020-07-20 Chris Ahlstrom * TODO, arch/README, arch/package/PKGBUILD, debian/README, debian/bash.rc, debian/changelog, debian/compat, debian/control, debian/copyright, debian/gbp.conf, debian/install, debian/libseq66-dev.install, debian/libseq66.install, debian/menu, debian/rules, debian/seq-rtmidi-dev.install, debian/seq-rtmidi.install, debian/seq66.desktop, debian/seq66.install, debian/seq66.xpm, debian/source/format, debian/watch: Added Arch and Debian build structure, preliminary. * libseq66/include/cfg/usrsettings.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/forms/qslivegrid.ui, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp: Made the main window scalable down to 800x480. 2020-07-19 Chris Ahlstrom * TODO, contrib/code/qseqrollpix.cpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Interim check-in, minor optimizations. 2020-07-18 Chris Ahlstrom * INSTALL, README, TODO, configure, include/config.h.in, libseq66/include/midi/controllers.hpp, libseq66/src/midi/controllers.cpp, libseq66/src/play/sequence.cpp, seq66.pro, seq_qt5/include/qseqdata.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Interim check-in, progress on issues #9 and #13. 2020-07-16 Chris Ahlstrom * README, libseq66/include/util/recmutex.hpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/recmutex.cpp: Disabled locking on condition-variable signal() call for Windows, causes deadlock. 2020-07-15 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, libseq66/include/play/performer.hpp, libseq66/include/sessions/smanager.hpp, libseq66/include/util/condition.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/os/timing.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/condition.cpp, libseq66/src/util/filefunctions.cpp, seq_portmidi/src/midibus.cpp, seq_qt5/include/qt5nsmanager.hpp: Fixed bugs in disabling I/O ports, but have a mutex deadlock in playback in Windows. 2020-07-14 Chris Ahlstrom * README, VERSION, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/businfo.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/performer.cpp, seq_portmidi/src/midibus.cpp, seq_portmidi/src/pmwinmm.c, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp: Allow seq66 to keep going with bad devices, including them disabled in the rc file, still need to show error message. 2020-07-13 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, contrib/notes/windows-midi.txt, contrib/notes/windows-portmidi.txt, contrib/scripts/windows/VMS_fixes.reg, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_platform_macros.h, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, m4/win32msc.m4, seq_portmidi/include/pmerrmm.h, seq_portmidi/include/pminternal.h, seq_portmidi/include/pmlinux.h, seq_portmidi/include/pmlinuxalsa.h, seq_portmidi/include/portmidi.h, seq_portmidi/src/pmerrmm.c, seq_portmidi/src/pmlinux.c, seq_portmidi/src/pmlinuxalsa.c, seq_portmidi/src/pmmac.c, seq_portmidi/src/pmmacosxcm.c, seq_portmidi/src/pmwin.c, seq_portmidi/src/pmwinmm.c, seq_portmidi/src/portmidi.c, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Interim check-in getting Win build to work, investigating song/live issue. 2020-07-10 Chris Ahlstrom * INSTALL, Seq66qt5/seq66qt5.cpp, data/readme.txt, data/readme.windows, libseq66/include/util/basic_macros.h, libseq66/include/util/basic_macros.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/filefunctions.cpp, nsis/build_release_package.bat, seq_portmidi/include/pmerrmm.h, seq_portmidi/src/pmerrmm.c, seq_portmidi/src/pmwin.c, seq_portmidi/src/pmwinmm.c: Safety check-in, added portmidi logging for Windows testing. 2020-07-07 Chris Ahlstrom * INSTALL, configure, configure.ac, data/README, data/license.txt, data/readme.txt, data/readme.windows, include/config.h.in, include/qt/rtmidi/seq66-config.h, libseq66/include/cfg/settings.hpp, libseq66/include/play/performer.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/strfunctions.cpp, libsessions/include/lash/lash.hpp, libsessions/src/lash/lash.cpp, nsis/build_release_package.bat, seq_portmidi/src/pmwinmm.c, seq_qt5/src/qt5nsmanager.cpp: Interim check-in after getting windows 32-bit build working. * include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/util/filefunctions.cpp: Interim check-in to support OS-specific path-slashes. * Happy birthday to Chris! 2020-07-06 Chris Ahlstrom * libseq66/include/util/filefunctions.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/util/filefunctions.cpp: Starting to harden Windows/UNIX slash separators. * : Fixing conflicts, dates. 2020-07-05 Chris Ahlstrom * README, VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h: Updating version information for release. * Seq66qt5/seq66qt5.cpp, configure, libseq66/include/sessions/smanager.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/sessions/smanager.cpp, libsessions/src/nsm/nsm.cpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qt5nsmanager.cpp: Fixed the writing of the erroneous config files. 2020-07-04 Chris Ahlstrom * INSTALL, Seqtool/src/seqtool.cpp, include/qt/portmidi/seq66-config.h, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/seq66_platform_macros.h, libseq66/include/util/recmutex.hpp, libseq66/src/play/performer.cpp, libseq66/src/unix/daemonize.cpp, libseq66/src/util/filefunctions.cpp, libseq66/src/util/recmutex.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, pack, seq_qt5/forms/qsmainwnd.ui: Got seq66 to build in Windows, but it will not save erroneous.rc. 2020-07-03 Chris Ahlstrom * INSTALL: Updated INSTALL file for distro installation. * README, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qloopbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qplaylistframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed muting flicker on loop keys, expanded main playback buttons. 2020-07-02 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/seq66_platform_macros.h, libseq66/src/play/performer.cpp, libseq66/src/util/filefunctions.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp, seq_qt5/src/qsmainwnd.cpp: Got rid of deprecated screenGeometry() call, updating trigger handling. 2020-07-01 Ahlstrom <021926@bah.com> * bootstrap, libseq66/src/play/performer.cpp: Do not remove the configure script, use timeapi.h. 2020-06-30 Chris Ahlstrom * README, include/config.h.in, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/midicontrolbase.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: More fixes to issue #5 and added better midi-control-out file writing. 2020-06-29 Chris Ahlstrom * NEWS, README, VERSION, configure, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp: Finished fixing and testing issue #5 and some other minor issues. 2020-06-28 Chris Ahlstrom * configure, include/config.h.in, libseq66/include/play/sequence.hpp, seq_qt5/include/qperfroll.hpp, seq_qt5/src/qperfroll.cpp: Fixed the display of notes in qperfroll. * libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/src/play/performer.cpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixing perfroll issues related to issue #5. 2020-06-27 Chris Ahlstrom * libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qseqroll.cpp: Interim check-in, fixed error growing notes in the seq roll. 2020-06-26 Chris Ahlstrom * libseq66/src/midi/eventlist.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Working on fixing show mute status correct in button grid on playback. 2020-06-23 Chris Ahlstrom * VERSION, configure.ac, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/midi/editable_events.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_jack.cpp: Refactored event linkage to use iterators and fixed note deletion bug. 2020-06-22 Chris Ahlstrom * libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/forms/qseditoptions.ui, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed configfile bug for issue #5, working on qseqroll issues. 2020-06-20 Chris Ahlstrom * Seqtool/src/seqtool.cpp, configure.ac, include/config.h.in, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/performer.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/src/qseditoptions.cpp: Added untested fix for issue #5 by implementing resume-note-ons. 2020-06-15 Chris Ahlstrom * INSTALL, README, bootstrap, configure, configure.ac, contrib/scripts/debug, contrib/scripts/yoshimi-b4uacuse-gm.state, contrib/scripts/ystart, doc/dox/doxy-common.cfg, include/config.h.in, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/play/performer.hpp, libseq66/include/seq66_features.h, libseq66/src/Makefile.am, libseq66/src/midi/midifile.cpp, libseq66/src/seq66_features.cpp, m4/xpc_debug.m4, seq_qt5/src/Makefile.am, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed issue #4, some cleanup, new script and data. 2020-06-13 Chris Ahlstrom * configure.ac, include/config.h.in, libseq66/include/play/performer.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/src/midi_alsa_info.cpp: Minor tweaks while evaluating Start/Stop/Continue. 2020-06-10 Chris Ahlstrom * libseq66/include/midi/event.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqroll.cpp, seq_rtmidi/include/midi_alsa.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Optimizing ALSA recording and verify-and-link. 2020-06-05 Chris Ahlstrom * libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/performer.hpp, libseq66/src/midi/event.cpp, libseq66/src/play/performer.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Got MIDI Start/Continue/Stop working well enough. 2020-06-02 Chris Ahlstrom * Seq66cli/Seq66cli.pro, Seq66qt5/Makefile.am, Seq66qt5/Seq66qt5.pro, Seq66qt5/seq66qt5.cpp, Seqtool/Seqtool.pro, configure, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/libseq66.pro, libseq66/src/sessions/smanager.cpp, libsessions/include/lash/lash.hpp, libsessions/libsessions.pro, libsessions/src/lash/lash.cpp, seq66.pro, seq_portmidi/seq_portmidi.pro, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/seq_qt5.pro, seq_rtmidi/seq_rtmidi.pro: Configure pro files to support rtmidi and allowed app to come up when no config files exist. 2020-05-31 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, include/qt/portmidi/seq66-config.h, include/qt/rtmidi/seq66-config.h, libseq66/include/sessions/smanager.hpp, libseq66/src/sessions/smanager.cpp, seq66.pro, seq_portmidi/src/portmidi.c, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/seq_rtmidi.pro: Got the qmake build working for portmidi. 2020-05-30 Chris Ahlstrom * configure, seq66.pro: Need qmake to select portmidi vs rtmidi, first steps. 2020-05-27 Chris Ahlstrom * configure, configure.ac, include/config.h.in, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/play/performer.hpp, libseq66/include/unix/daemonize.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/unix/daemonize.cpp, m4/pkg.m4, seq_qt5/src/qslivegrid.cpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/midi_api.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midi_probe.cpp: Ported many fixes and refactors from Seq64. 2020-04-22 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, configure, configure.ac, data/qseq66.mutes, data/qseq66.usr, include/config.h.in, libseq66/include/Makefile.am, libseq66/include/app_limits.h, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/play/performer.hpp, libseq66/include/sessions/smanager.hpp, libseq66/include/sessions/smfunctions.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/sessions/smfunctions.cpp, libsessions/src/nsm/nsm.cpp, man/seq66.1, man/sequencer66.1, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/src/mastermidibus.cpp: Many ports from sequencer64, interim check-in. 2020-04-10 Chris Ahlstrom * INSTALL, configure, configure.ac, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/settings.cpp, libseq66/src/ctrl/keycontainer.cpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qt5nsmanager.cpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa_info.cpp, seq_rtmidi/src/rtmidi.cpp, seq_rtmidi/src/rtmidi_info.cpp: Fixed issues with smanager unique pointer and uname. 2020-04-07 Chris Ahlstrom * : commit 0ba2fafa71ff0b8c0cb4f33e45585d62750f1645 Author: Chris Ahlstrom Date: Tue Apr 7 17:43:44 2020 -0400 2020-04-06 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, configure, libseq66/include/seq66_features.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/sessions/smanager.cpp, seq_qt5/include/qt5nsmanager.hpp, seq_qt5/src/qt5nsmanager.cpp: Trying to get refactory main() to work. 2020-04-01 Chris Ahlstrom * configure, configure.ac, include/config.h.in, m4/ax_cxx_compile_stdcxx.m4, m4/ax_cxx_compile_stdcxx_11.m4, m4/ax_have_qt_min.m4, m4/ax_require_defined.m4, m4/xpc_debug.m4: configure and m4 updates. 2020-03-30 Chris Ahlstrom * Seq66cli/Seq66cli.pro, Seq66qt5/Makefile.am, Seq66qt5/Seq66qt5.pro, configure, seq_qt5/include/Makefile.am, {Seq66qt5 => seq_qt5/include}/qt5nsmanager.hpp, seq_qt5/src/Makefile.am, {Seq66qt5 => seq_qt5/src}/qt5nsmanager.cpp: Moved qt5nsmanager into seq_qt5 library. * Seq66qt5/Makefile.am, Seq66qt5/qt5nsmanager.cpp, Seq66qt5/qt5nsmanager.hpp, configure, libseq66/include/cfg/rcsettings.hpp, libseq66/include/sessions/smanager.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/sessions/smanager.cpp, libseq66/src/util/basic_macros.cpp, libsessions/src/lash/lash.cpp: Interim check-in, have a vtable present. 2020-03-27 Chris Ahlstrom * libseq66/include/util/palette.hpp: Fixed header issue in palette class. 2020-03-25 Chris Ahlstrom * libsessions/include/nsm/nsmdummy.hpp: Added initial nsmdummy class. 2020-03-24 Chris Ahlstrom * INSTALL, Seq66qt5/qt5nsmanager.cpp, bootstrap, configure, configure.ac, doc/dox/libseq66/libseq66.cfg, include/config.h.in, libseq66/include/Makefile.am, libseq66/include/cfg/rcsettings.hpp, libseq66/include/main_impl.hpp, libseq66/include/seq66_features.h, libseq66/include/sessions/smanager.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/main_impl.cpp, libseq66/src/sessions/smanager.cpp, libsessions/libsessions.pro, seq66.pro: Added a base session manager support class. 2020-03-22 Chris Ahlstrom * ChangeLog, INSTALL, NEWS, Seq66qt5/qt5nsmanager.cpp, Seq66qt5/qt5nsmanager.hpp, Seq66qt5/seq66qt5.cpp, bootstrap, configure, configure.ac, contrib/non/nsm.sh, contrib/notes/gcc-version.txt, contrib/notes/git.txt, contrib/scripts/Jack, contrib/scripts/strap_functions, data/sample.playlist, include/config.h.in, libseq66/include/Makefile.am, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/keystroke.hpp, libseq66/include/play/clockslist.hpp, libseq66/include/play/inputslist.hpp, libseq66/include/seq66_features.h, libseq66/include/seq66_platform_macros.h, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/automation.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/seq66_features.cpp, libsessions/include/Makefile.am, libsessions/include/lash/lash.hpp, libsessions/include/nsm/nsm.hpp, libsessions/src/Makefile.am, libsessions/src/lash/lash.cpp, pack, seq_qt5/include/qeditbase.hpp, seq_qt5/seq_qt5.pro, seq_qt5/src/qseditoptions.cpp: Fixed tabs, some refactoring, add LASH back into the build, optionally. 2020-03-17 Chris Ahlstrom * README, Seq66qt5/qt5nsmanager.cpp, Seq66qt5/qt5nsmanager.hpp, Seq66qt5/seq66qt5.cpp, configure, contrib/non/nsm_tendrils.txt, libseq66/include/util/basic_macros.h, libsessions/include/nsm/nsm.hpp, libsessions/src/nsm/nsm.cpp, libsessions/src/nsm/nsmclient.cpp, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqkeys.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqkeys.cpp, seq_qt5/src/qsmainwnd.cpp: More NSM support being added, in progress. 2020-03-13 Chris Ahlstrom * configure, configure.ac: Added ability to ignore gcc/g++ 9 in configure. * configure.ac, include/config.h.in, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, libsessions/include/Makefile.am, libsessions/include/nsm/nsm.hpp, libsessions/include/nsm/nsmclient.hpp, libsessions/include/nsm/nsmserver.hpp, libsessions/libsessions.pro, libsessions/src/Makefile.am, libsessions/src/nsm/nsm.cpp, libsessions/src/nsm/nsmclient.cpp, libsessions/src/nsm/nsmmessages.cpp, libsessions/src/nsm/nsmserver.cpp: Add nsmserver, fixes from seq64, interim check-in. 2020-03-10 Chris Ahlstrom * Seq66qt5/Makefile.am, configure, configure.ac, libsessions/include/nsm/nsm.hpp, libsessions/include/nsm/nsmclient.hpp, libsessions/src/nsm/nsm.cpp, libsessions/src/nsm/nsmclient.cpp: Finished nsm build process work. 2020-03-09 Chris Ahlstrom * Seq66qt5/Makefile.am, bootstrap, configure, configure.ac, libsessions/src/nsm/nsm.cpp: Still working out nsm sessions build process. * Makefile.am, Seq66qt5/Makefile.am, configure, configure.ac, contrib/non/{nsmopen.sh => nsm.sh}, contrib/scripts/mutetest, include/config.h.in, libseq66/include/Makefile.am, libseq66/include/util/filefunctions.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/util/filefunctions.cpp, libsessions/Makefile.am, libsessions/include/Makefile.am, {libseq66/include/sessions => libsessions/include/nsm}/nsm.h, libseq66/include/sessions/nsmclient.hpp => libsessions/include/nsm/nsm.hpp, libsessions/include/nsm/nsmclient.hpp, libsessions/include/nsm/nsmmessages.hpp, libsessions/libsessions.pro, libsessions/src/Makefile.am, libseq66/src/sessions/nsmclient.cpp => libsessions/src/nsm/nsm.cpp, libsessions/src/nsm/nsmclient.cpp, libsessions/src/nsm/nsmmessages.cpp, modules-to-compare.txt: Moved nsm code into libsessions, still have link error. 2020-03-05 Chris Ahlstrom * contrib/{notes => non}/NSM_API.txt: Moved the NSM_API text file into contrib/non. * contrib/non/nsmopen.sh, libseq66/src/sessions/nsmclient.cpp: Added a contrib script to start nsm. 2020-03-04 Chris Ahlstrom * contrib/notes/NSM_API.txt, libseq66/src/sessions/nsmclient.cpp: Now documenting NSM protocols. 2020-03-02 Chris Ahlstrom * configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, libseq66/include/Makefile.am, libseq66/include/play/performer.hpp, libseq66/include/play/seq.hpp, libseq66/include/sessions/nsm.h, libseq66/include/sessions/nsmclient.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/cfg/rcfile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/seq66_features.cpp, libseq66/src/sessions/nsmclient.cpp, seq_portmidi/src/pmwinmm.c, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp: Fixed config, portmidi warnings, added initial nsm support. 2020-02-18 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/seq.hpp, libseq66/src/play/performer.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp: Improving pending-seqs support ported from Seq64. 2020-02-18 Chris Ahlstrom * README, configure, configure.ac, contrib/notes/git.txt, include/config.h.in, include/qt/portmidi/seq66-config.h, libseq66/include/seq66_features.hpp, libseq66/src/seq66_features.cpp, seq_qt5/src/qsmainwnd.cpp: Updated build process. 2020-02-10 Chris Ahlstrom * Seq66cli/Makefile.am, Seq66cli/seq66rtcli.cpp: Oops, added the Seq66cli directory. * Makefile.am, README, Seq66qt5/seq66qt5.cpp, configure, configure.ac, include/config.h.in, libseq66/include/unix/daemonize.hpp, libseq66/include/util/basic_macros.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/unix/daemonize.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/src/qsmainwnd.cpp, seq_rtmidi/src/rtmidi_info.cpp: Added command-line application and signal handling. 2020-01-07 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp: Interim check-in, midi control key tweaks. 2020-01-01 Chris Ahlstrom * VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, libseq66/include/ctrl/automation.hpp, libseq66/include/play/performer.hpp, libseq66/src/play/performer.cpp, seq_qt5/include/qsliveframe.hpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp: Interim check-in while adding MIDI control of pattern/event edits. 2019-12-18 Chris Ahlstrom * libseq66/src/play/sequence.cpp: Added note about sequence::set_playing(). 2019-12-17 Chris Ahlstrom * libseq66/src/cfg/usrsettings.cpp: Fix to usersettings. 2019-12-16 Chris Ahlstrom * libseq66/src/cfg/usrfile.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp: Final tweaks to update_midi_buttons. * libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/include/qbase.hpp, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qperfnames.hpp, seq_qt5/include/qperftime.hpp, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qseqeditframe.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/include/qseqtime.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp: Regularized armed/thru/record/qrecord buttons, fixed related bugs in performer. 2019-12-14 Chris Ahlstrom * VERSION, configure, configure.ac, include/config.h.in, include/qt/portmidi/seq66-config.h, libseq66/include/play/sequence.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeditframe.ui, seq_qt5/include/qseqeditframe.hpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp: Interim check-in to simplify record statuses. 2019-12-13 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditframe64.cpp: Tweaking recording settings. * Seqtool/src/converter.cpp, contrib/vim-syntax/c.vim, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/basic_macros.hpp, libseq66/include/util/calculations.hpp, libseq66/include/util/strfunctions.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/notemapper.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, libseq66/src/util/basic_macros.cpp, libseq66/src/util/calculations.cpp, libseq66/src/util/strfunctions.cpp, seq_qt5/src/qseqeditframe64.cpp: Refactored most string functions, user-save on version change. 2019-12-12 Chris Ahlstrom * configure, libseq66/include/cfg/usrsettings.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrfile.cpp, seq_qt5/src/qseqeditframe64.cpp: Interim usrfile check-in. * libseq66/src/cfg/usrfile.cpp, libseq66/src/util/calculations.cpp: Starting to add new-pattern flags to usrfile. 2019-12-11 Chris Ahlstrom * : commit 5791e61bc2492fe095900159365c6ed7ded5d0df Author: Chris Ahlstrom Date: Wed Dec 11 20:02:53 2019 -0500 * libseq66/include/cfg/usermidibus.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/src/qseqeditframe64.cpp, seq_rtmidi/src/midi_jack.cpp: Enhancing channel-less patterns. 2019-12-09 Chris Ahlstrom * TODO, libseq66/include/midi/event.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditframe.cpp: Tweaks while researching more 2rock issues. 2019-12-08 Chris Ahlstrom * README, TODO, configure.ac, include/config.h.in, libseq66/include/midi/businfo.hpp, libseq66/include/midi/event.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/editable_event.cpp, libseq66/src/play/performer.cpp: Minor refactoring of input event handling. 2019-12-02 Chris Ahlstrom * contrib/notes/get_midi_event.txt, libseq66/include/midi/midibytes.hpp, libseq66/src/util/calculations.cpp, seq_portmidi/src/mastermidibus.cpp, seq_rtmidi/include/rtmidi_types.hpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_jack.cpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/midibus_impl.cpp, seq_rtmidi/src/rtmidi_types.cpp: Tweaking while researching how to get buss numbers in JACK and ALSA. 2019-11-30 Chris Ahlstrom * Seq66qt5/Seq66qt5.pro, Seqtool/Seqtool.pro, configure, configure.ac, include/config.h.in, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/businfo.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/sequence.cpp, seq66.pro, seq_qt5/seq_qt5.pro: Interim check-in, added event to_string() functions. 2019-11-29 Chris Ahlstrom * INSTALL, bootstrap, configure, configure.ac, include/config.h.in, m4/ax_have_qt.m4, m4/ax_have_qt_ex.m4, m4/xpc_debug.m4, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qseqtime.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qsmaintime.cpp: Trying to fix QPainter drawText error and failing. 2019-11-28 Chris Ahlstrom * seq_rtmidi/include/midi_alsa.hpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_jack.hpp: Fixed virtual function compiler errors. 2019-11-27 Chris Ahlstrom * seq_rtmidi/include/mastermidibus_rm.hpp, seq_rtmidi/include/midi_alsa.hpp, seq_rtmidi/include/midi_alsa_info.hpp, seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/include/midibus_rm.hpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_api.cpp: Tweaking overrides, does not compile yet. * seq_rtmidi/include/midi_api.hpp, seq_rtmidi/include/midi_info.hpp, seq_rtmidi/include/midi_jack.hpp, seq_rtmidi/include/rtmidi.hpp, seq_rtmidi/src/mastermidibus.cpp: Tweaks while researching midi polling. 2019-11-26 Chris Ahlstrom * configure, contrib/midi/README, libseq66/include/midi/mastermidibase.hpp, libseq66/src/midi/businfo.cpp, seq_rtmidi/include/rtmidi_info.hpp, seq_rtmidi/src/mastermidibus.cpp: Trying to refigure MIDI event polling and reading. 2019-11-25 Chris Ahlstrom * Seqtool/include/faker.hpp, Seqtool/src/midi_control_helpers.cpp, Seqtool/src/midi_control_unit_test.cpp, Seqtool/src/optionsfile.cpp, configure, doc/dox/libseq66/libseq66.cfg, libseq66/include/Makefile.am, libseq66/include/cfg/midicontrolfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/ctrl/keycontainer.hpp, libseq66/include/ctrl/midicontrolbase.hpp, libseq66/include/ctrl/{midicontainer.hpp => midicontrolin.hpp}, libseq66/include/ctrl/midicontrolout.hpp, libseq66/include/play/performer.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/ctrl/midicontrol.cpp, libseq66/src/ctrl/midicontrolbase.cpp, libseq66/src/ctrl/{midicontainer.cpp => midicontrolin.cpp}, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp: Refactor midicontainer to midicontrolin. 2019-11-24 Chris Ahlstrom * README, VERSION, configure.ac, include/config.h.in, libseq66/include/midi/editable_event.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/midifile.hpp, libseq66/include/midi/wrkfile.hpp, libseq66/include/util/basic_macros.h, libseq66/include/util/basic_macros.hpp, libseq66/include/util/calculations.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/util/calculations.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe.cpp: Finally fixed the reading of 2rock.mid, now need to backport the fix. 2019-11-22 Chris Ahlstrom * contrib/midi/2rock.hex: Redid 2rock hex file. * contrib/midi/2rock.asc, contrib/midi/2rock.hex: Added text versions of weird 2rock.mid file. 2019-11-21 Chris Ahlstrom * libseq66/include/midi/event.hpp, libseq66/include/midi/midifile.hpp, libseq66/src/midi/midifile.cpp: Fixes to running status handling. * Seq66qt5/seq66qt5.cpp, libseq66/include/midi/event.hpp, libseq66/include/midi/midifile.hpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Still wrestling with issues reading 2rock.mid. 2019-11-20 Chris Ahlstrom * libseq66/include/midi/midifile.hpp, libseq66/src/midi/midifile.cpp: Added meta skipping messages. * : commit d47f6be72841d7b0879cf346529266bf3e5359f7 Author: Chris Ahlstrom Date: Wed Nov 20 05:46:48 2019 -0500 2019-11-18 Chris Ahlstrom * libseq66/include/midi/editable_events.hpp, libseq66/src/midi/editable_events.cpp, libseq66/src/play/playlist.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp: More for-loop tweakage. * libseq66/src/cfg/midicontrolfile.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Some silly tweaks to for loops. 2019-11-17 Chris Ahlstrom * configure, configure.ac, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Added a back door to dump the editable events to stdout. 2019-11-16 Chris Ahlstrom * data/qseq66.rc: Added simple test of drum conversion. 2019-11-11 Chris Ahlstrom * data/GM_DD-11.drums, libseq66/include/play/notemapper.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/play/notemapper.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qseqframe.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqroll.cpp: Basically finished the repitch-note feature. 2019-11-10 Chris Ahlstrom * libseq66/include/cfg/notemapfile.hpp, libseq66/include/cfg/rcfile.hpp, libseq66/include/cfg/rcsettings.hpp, libseq66/include/midi/event.hpp, libseq66/include/play/notemapper.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/play/notemapper.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/playlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/forms/qseqeditframe64.ui, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp: Interim check-in, note-map UI in place. 2019-11-08 Chris Ahlstrom * configure, libseq66/include/play/notemapper.hpp, libseq66/src/play/notemapper.cpp: Note mapper builds, hurray. * configure, configure.ac, include/config.h.in, libseq66/include/Makefile.am, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/notemapfile.hpp, libseq66/include/play/notemapper.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/play/notemapper.cpp: Notemap code unfinished, does not build. 2019-11-07 Chris Ahlstrom * libseq66/include/play/notemapper.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/play/notemapper.cpp: Still working on notemapper. * data/GM_DD-11.drums, data/GM_PSS-790.drums, libseq66/include/cfg/configfile.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/notemapfile.cpp: Still working on drum config. 2019-11-06 Chris Ahlstrom * data/GM_DD-11.drums, data/GM_PSS-790.drums, libseq66/include/cfg/notemapfile.hpp, libseq66/include/play/notemapper.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/notemapfile.cpp, libseq66/src/play/notemapper.cpp: Added a notemapfile class. 2019-11-05 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, data/GM_DD-11.drums, data/GM_PSS-790.drums, data/qseq66.ctrl, data/qseq66.mutes, data/qseq66.rc, data/qseq66.usr, data/rowclipsmap.ctrl, data/rowclipsmap.mutes, libseq66/include/cfg/configfile.hpp, libseq66/include/play/notemapper.hpp, libseq66/src/cfg/configfile.cpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/play/notemapper.cpp: Adding mapping of notes, in progress. 2019-11-04 Chris Ahlstrom * Seq66qt5/seq66qt5.cpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/midicontrolfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/src/qseqeditframe64.cpp: Interim check-in, minor tweakage. 2019-11-02 Chris Ahlstrom * Seqtool/src/optionsfile.cpp, contrib/scripts/ystart, doc/latex/tex/seq66_menu.tex, libseq66/include/app_limits.h, libseq66/include/cfg/usermidibus.hpp, libseq66/include/midi/controllers.hpp, libseq66/include/midi/mastermidibase.hpp, libseq66/include/midi/midibytes.hpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/usermidibus.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/ctrl/midicontrolout.cpp, libseq66/src/midi/controllers.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qinputcheckbox.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_rtmidi/src/midi_alsa.cpp: Fixed announce-bus bug and moved a few macros to consts. 2019-10-31 Chris Ahlstrom * libseq66/include/app_limits.h, libseq66/src/cfg/usrfile.cpp, libseq66/src/cfg/usrsettings.cpp: Increased supported beats/bar in GUI. 2019-10-30 Chris Ahlstrom * libseq66/src/midi/eventlist.cpp: Minor fix to eventlist quant. 2019-10-29 Chris Ahlstrom * libseq66/include/midi/editable_events.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseventslots.cpp: Tweaking note linking and marking. 2019-10-28 Chris Ahlstrom * libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp: Still more code movement to eventlist. * libseq66/include/midi/event.hpp, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp: Added sorting to event movement. 2019-10-27 Chris Ahlstrom * configure, libseq66/include/midi/eventlist.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/eventlist.cpp, libseq66/src/play/sequence.cpp: More moving code from sequence to eventlist. * contrib/code/qseqrollpix.cpp, doc/dox/libseq66/libseq66.cfg, libseq66/include/Makefile.am, libseq66/include/cfg/scales.hpp, libseq66/include/midi/editable_events.hpp, libseq66/include/midi/event.hpp, libseq66/include/midi/{event_list.hpp => eventlist.hpp}, libseq66/include/play/sequence.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/cfg/scales.cpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/{event_list.cpp => eventlist.cpp}, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/triggers.cpp, libseq66/src/util/rect.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qperfbase.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseventslots.cpp, seq_qt5/src/qstriggereditor.cpp: Moved a lot more code from sequence to eventlist. 2019-10-25 Chris Ahlstrom * libseq66/include/midi/event.hpp, libseq66/include/midi/event_list.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event_list.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqroll.cpp: Finished randomization support. * libseq66/include/app_limits.h, libseq66/include/midi/event.hpp, libseq66/include/midi/event_list.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/event_list.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qstriggereditor.cpp: Offloaded more work to event_list. 2019-10-24 Chris Ahlstrom * libseq66/include/midi/event_list.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event_list.cpp, libseq66/src/play/sequence.cpp: Fixed quantization, enabled randomizing. * libseq66/include/midi/event_list.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/event_list.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditframe64.cpp: Refactoring quantization in progress. 2019-10-23 Chris Ahlstrom * libseq66/src/play/sequence.cpp, seq_qt5/src/qseqroll.cpp: Fixed seqroll quantization. * seq_qt5/include/qeditbase.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Fixed grid-snap, still working on quantize/tighten. 2019-10-22 Chris Ahlstrom * INSTALL, README, Seqtool/src/seqtool.cpp, Seqtool/src/unit_tests.cpp, TODO, VERSION, bootstrap, configure, configure.ac, configure.help, contrib/code/qseqrollpix.cpp, data/README, data/license.txt, data/readme.txt, data/readme.windows, doc/dox/doxy-common.cfg, doc/latex/tex/sequencer66-user-manual.tex, include/config.h.in, include/qt/portmidi/seq66-config.h, libseq66/include/midi/event_list.hpp, libseq66/include/play/sequence.hpp, libseq66/src/midi/event_list.cpp, libseq66/src/play/sequence.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, packages, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qseqbase.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Added edge-fix feature, prep for new version. 2019-10-20 Chris Ahlstrom * ChangeLog: Added change-log. * INSTALL, configure, configure.ac, data/README, data/license.txt, data/readme.txt, data/readme.windows, include/config.h.in, include/qt/portmidi/seq66-config.h, libseq66/src/play/performer.cpp: Version 0.90.1 pending. * contrib/scripts/q-make, include/qt/portmidi/seq66-config.h, libseq66/include/play/seq.hpp, libseq66/libseq66.pro, libseq66/src/play/seq.cpp, nsis/build_release_package.bat, seq66.pro: Fixes made to the qmake build process. 2019-10-18 Chris Ahlstrom * libseq66/src/midi/editable_events.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, libseq66/src/play/triggers.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qskeymaps.cpp, seq_rtmidi/include/midi_jack_info.hpp, seq_rtmidi/src/midi_jack_info.cpp, seq_rtmidi/src/rtmidi_types.cpp: More iterator cleanup. * Seq66qt5/seq66qt5.cpp, Seqtool/src/gdk_basic_keys.cpp, libseq66/include/cfg/cmdlineopts.hpp, libseq66/include/cfg/configfile.hpp, libseq66/include/cfg/settings.hpp, libseq66/include/cfg/userinstrument.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/userinstrument.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/main_impl.cpp, libseq66/src/play/performer.cpp, libseq66/src/util/basic_macros.cpp, seq_qt5/src/qseqeditframe64.cpp: Fixes to seqtool and random cleanup. 2019-10-17 Chris Ahlstrom * seq_qt5/src/qslivebase.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed bank import refresh. * README, Seqtool/src/faker.cpp, TODO, include/config.h.in, libseq66/include/app_limits.h, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/include/qskeymaps.hpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qskeymaps.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed non-deletion of closed seqedit, code cleanup. 2019-10-16 Chris Ahlstrom * libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditex.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed non-removal of the seqedit frames when closed. * configure.ac, libseq66/include/app_limits.h, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/seq66_platform_macros.h, libseq66/include/util/calculations.hpp, libseq66/include/util/palette.hpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/mastermidibase.cpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qlfoframe.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp: Forward porting seq64 fixes, in progress. 2019-10-10 Chris Ahlstrom * libseq66/include/cfg/usrsettings.hpp, libseq66/src/cfg/usrsettings.cpp: Partial clean-out of unused user settings. 2019-10-09 Chris Ahlstrom * libseq66/include/cfg/scales.hpp, libseq66/include/midi/midibytes.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/midibytes.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqkeys.cpp: Still struggling with scale analysis. 2019-10-07 Chris Ahlstrom * libseq66/include/cfg/scales.hpp, libseq66/include/midi/midibytes.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/midibytes.cpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp: Note analysis mostly in place. * libseq66/include/cfg/scales.hpp, libseq66/include/midi/midibytes.hpp, libseq66/src/cfg/scales.cpp, libseq66/src/midi/midibytes.cpp: Added midi_booleans class to midibytes. 2019-10-06 Chris Ahlstrom * Makefile.am, Seq66qt5/Makefile.am, TODO, configure, configure.ac, include/config.h.in, libseq66/include/cfg/scales.hpp, libseq66/include/midi/midibytes.hpp, libseq66/include/play/sequence.hpp, libseq66/libseq66.pro, libseq66/src/Makefile.am, libseq66/src/cfg/scales.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/performer.cpp, seq_portmidi/include/finddefault.h, seq_portmidi/include/pmmacosxcm.h, seq_portmidi/src/finddefault-macosx.c, seq_portmidi/src/finddefault.c, seq_portmidi/src/pmwin.c, seq_qt5/forms/qseqeditframe64.ui: Interim check-in, adding scale analysis. 2019-10-04 Chris Ahlstrom * seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp: Fixed Modify handling in event frame, need Close button. * seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Working on update-handling in the event frame. 2019-10-03 Chris Ahlstrom * TODO, seq_qt5/src/qseqeditframe64.cpp: Fixed display of no-scale. * : commit a104e07db9c2829239909e4b663702304179dabe Author: Chris Ahlstrom Date: Thu Oct 3 05:52:45 2019 -0400 2019-10-02 Chris Ahlstrom * libseq66/include/play/setmapper.hpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp: Added partial box trigger selection. * seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp: Fixed dirt bug in opening Edit tab. 2019-10-01 Chris Ahlstrom * TODO, libseq66/src/cfg/rcfile.cpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Fixed seqframe dirtiness. * Seq66qt5/seq66qt5.cpp, Seqtool/src/converter.cpp, Seqtool/src/optionsfile.cpp, TODO, contrib/code/qseqrollpix.cpp, seq_portmidi/src/mastermidibus.cpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqdata.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qsliveframe.cpp: Whittled down a few TODOs, added more. 2019-09-30 Chris Ahlstrom * seq_qt5/src/qseqroll.cpp: Fixed issue of seqroll undo not being shown. * libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qeditbase.hpp, seq_qt5/include/qseqroll.hpp, seq_qt5/src/qeditbase.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qstriggereditor.cpp: Fixed issue moving notes with mouse. 2019-09-28 Chris Ahlstrom * Seqtool/src/optionsfile.cpp, TODO, contrib/scripts/session, data/qseq66.rc, doc/dox/notes/user_testing.dox, doc/latex/tex/seq66_jack.tex, doc/latex/tex/seq66_manpage.tex, doc/latex/tex/seq66_menu.tex, doc/latex/tex/seq66_rc_file.tex, doc/latex/tex/seq66_usr_file.tex, libseq66/include/cfg/rcsettings.hpp, libseq66/include/cfg/usermidibus.hpp, libseq66/src/cfg/cmdlineopts.cpp, libseq66/src/cfg/rcfile.cpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrfile.cpp, libseq66/src/midi/midibase.cpp, man/sequencer66.1, seq_portmidi/src/mastermidibus.cpp, seq_rtmidi/src/mastermidibus.cpp, seq_rtmidi/src/midi_alsa.cpp, seq_rtmidi/src/midi_alsa_info.cpp: Updating session script and fixing manual/auto ports command-line switches. 2019-09-27 Chris Ahlstrom * contrib/scripts/binfuncs, contrib/scripts/session: Added binfuncs script. * contrib/scripts/Jack, contrib/scripts/session: Added some preliminary Jack scripts. 2019-09-26 Chris Ahlstrom * libseq66/include/seq66_features.h, seq_qt5/forms/qseditoptions.ui, seq_qt5/include/gui_palette_qt5.hpp, seq_qt5/include/qscrollmaster.h, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/gui_palette_qt5.cpp, seq_qt5/src/qscrollmaster.cpp, seq_qt5/src/qseditoptions.cpp, seq_qt5/src/qseqroll.cpp: Fixed song mode selection, tweaks to qscrollmaster and color. 2019-09-25 Chris Ahlstrom * libseq66/include/play/performer.hpp, seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qseditoptions.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/include/qmutemaster.hpp, seq_qt5/include/qseditoptions.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qseditoptions.cpp: Mutes and options radio buttons in progress. * INSTALL, README, Seqtool/src/seqtool.cpp, Seqtool/src/unit_tests.cpp, TODO, VERSION, bootstrap, configure, configure.ac, configure.help, data/license.txt, doc/dox/doxy-common.cfg, doc/latex/tex/sequencer66-user-manual.tex, include/config.h.in, libseq66/include/play/mutegroups.hpp, libseq66/src/cfg/mutegroupsfile.cpp, libseq66/src/play/mutegroups.cpp, libseq66/src/util/strfunctions.cpp, man/seq66.1, man/seq66cli.1, man/sequencer66.1, seq_qt5/forms/qmutemaster.ui, seq_qt5/src/qmutemaster.cpp: Mutes UI and preparation for 0.90.1. 2019-09-24 Chris Ahlstrom * seq_qt5/forms/qmutemaster.ui, seq_qt5/src/qmutemaster.cpp: Tweaking mutes master UI. 2019-09-23 Chris Ahlstrom * configure, libseq66/include/util/basic_macros.hpp, seq66.pro, seq_qt5/include/Makefile.am, seq_qt5/seq_qt5.pro, seq_qt5/src/Makefile.am, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qrollframe.cpp: Fixed qmake build process. * data/README, data/license.txt, data/readme.txt, data/readme.windows, packages, resources/pixmaps/Makefile.am, resources/pixmaps/SEQ66_24x24.xpm, resources/pixmaps/SEQUENCER-66_24x24.xpm, resources/pixmaps/route66rwb-32x32.xpm, resources/pixmaps/route66rwb-66x66.xpm, resources/pixmaps/seq66.xpm, resources/pixmaps/seq66_32.xpm, resources/pixmaps/seq66_logo.xpm, resources/pixmaps/seq66_logo_legacy.xpm, seq_qt5/seq_qt5.pro: Updated icons etc from 64 to 66. 2019-09-22 Chris Ahlstrom * TODO, libseq66/include/play/performer.hpp, libseq66/src/cfg/midicontrolfile.cpp, nsis/Seq66Constants.nsh, nsis/Seq66Setup.nsi, nsis/build_release_package.bat, seq_qt5/include/qperfbase.hpp, seq_qt5/include/qperfeditframe64.hpp, seq_qt5/include/qperfnames.hpp, seq_qt5/src/qperfeditex.cpp, seq_qt5/src/qperfeditframe64.cpp, seq_qt5/src/qperfnames.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qperftime.cpp: Added initial NSIS files and fixed perfnames update. 2019-09-20 Chris Ahlstrom * seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp: Moved start/stop/pause to qsmainwnd. * seq_qt5/include/qbase.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqroll.cpp: Added start/stop/pause to qbase and qseqroll. 2019-09-19 Chris Ahlstrom * libseq66/include/midi/event.hpp, libseq66/src/midi/event_list.cpp, libseq66/src/play/sequence.cpp: Refining verify_and_link(). * libseq66/src/play/performer.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsmainwnd.cpp, seq_qt5/src/qstriggereditor.cpp: Trying to track down note-link error when recording from nanoKey2. 2019-09-18 Chris Ahlstrom * libseq66/include/app_limits.h, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/src/cfg/rcsettings.cpp, libseq66/src/cfg/usrsettings.cpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp: Moved some static values from sequence to seq. 2019-09-18 Chris Ahlstrom * libseq66/include/app_limits.h, libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, libseq66/src/play/setmapper.cpp, seq_qt5/include/qseqeditframe.hpp, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qmutemaster.cpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsetmaster.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed seqroll expansion, seq-num refactoring. * libseq66/include/play/screenset.hpp, libseq66/include/play/seq.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/midi/wrkfile.cpp, libseq66/src/play/screenset.cpp, seq_qt5/src/qsmainwnd.cpp: Refactoring seq number functions, might not build. 2019-09-17 Chris Ahlstrom * seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qseqtime.cpp: Partially fixed chopping off some of the main-window tabs. * libseq66/src/midi/editable_event.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/src/qseqeventframe.cpp: Still messing with table sizing. * libseq66/src/midi/editable_event.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/src/qseqeventframe.cpp: Fixed linked-event timestamp, still working on event table sizing. 2019-09-16 Chris Ahlstrom * libseq66/include/midi/editable_event.hpp, libseq66/src/midi/editable_event.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Interim check-in of event-frame changes. * seq_qt5/forms/qmutemaster.ui, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsetmaster.ui, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: More tweaks to event table. * libseq66/include/midi/editable_event.hpp, libseq66/include/midi/editable_events.hpp, libseq66/include/midi/event_list.hpp, libseq66/src/midi/editable_event.cpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/event.cpp, libseq66/src/midi/event_list.cpp, libseq66/src/play/performer.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Got links to appear, but table needs rework. * libseq66/include/midi/midibytes.hpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/sequence.cpp, libseq66/src/util/calculations.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/forms/qsmainwnd.ui, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qseventslots.hpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseventslots.cpp: Trying to upgrade event-frame to show links. 2019-09-15 Chris Ahlstrom * libseq66/src/play/performer.cpp, seq_qt5/forms/qseqeventframe.ui, seq_qt5/include/qseqbase.hpp, seq_qt5/include/qseqeventframe.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixed round trip editing of sequence title. 2019-09-14 Chris Ahlstrom * Seqtool/src/util_unit_test.cpp, libseq66/include/midi/event.hpp, libseq66/include/midi/jack_assistant.hpp, libseq66/include/midi/midibase.hpp, libseq66/include/play/sequence.hpp, libseq66/include/play/triggers.hpp, libseq66/include/unix/daemonize.hpp, libseq66/include/util/palette.hpp, libseq66/src/midi/event.cpp, libseq66/src/midi/jack_assistant.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qscrollmaster.h, seq_qt5/include/qseqdata.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/include/qstriggereditor.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qskeymaps.cpp, seq_rtmidi/include/rterror.hpp, seq_rtmidi/include/rtmidi_types.hpp: Interim check-in, replacing more typedefs and refactoring event slightly. * TODO, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qseqeditframe.hpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Refactoring set_recording() and related functions. 2019-09-13 Chris Ahlstrom * libseq66/include/play/performer.hpp, libseq66/include/play/seq.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qsmainwnd.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Refactoring seq-number parameters in performer, in progress. * TODO, libseq66/src/midi/event_list.cpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qliveframeex.cpp, seq_qt5/src/qseqroll.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp: Added cross-hair cursor for pasting, other fixes. 2019-09-12 Chris Ahlstrom * TODO, libseq66/include/play/sequence.hpp, libseq66/src/midi/event_list.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqroll.cpp: Fixed paste in seqroll. * TODO, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivegrid.cpp: May have fixed copy/paste issue, data pane update on record. 2019-09-11 Chris Ahlstrom * configure, seq_qt5/src/qseqeditframe64.cpp: Tinkering with seqedit. 2019-09-10 Chris Ahlstrom * INSTALL, doc/dox/doxy-common.cfg, libseq66/include/midi/event_list.hpp, libseq66/include/seq66_features.h, libseq66/src/midi/event.cpp, libseq66/src/midi/event_list.cpp, libseq66/src/play/sequence.cpp, libseq66/src/seq66_features.cpp: Removed SEQ66_USE_EVENT_LIST macro, vector permanently replaces list. * libseq66/include/midi/editable_events.hpp, libseq66/src/midi/editable_events.cpp, libseq66/src/midi/midifile.cpp, libseq66/src/play/sequence.cpp: Some event optimization. * TODO, seq_qt5/include/qseqeditframe64.hpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsmainwnd.cpp: Edit-frame 64 MIDI tooltips update, needed for dark themes. 2019-09-09 Chris Ahlstrom * configure, libseq66/include/play/sequence.hpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qsmainwnd.cpp: Updated live-grid for short patterns. * ChangeLog, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/include/qslotbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qslotbutton.cpp: More screenshots, preparation for live grid font scaling. 2019-09-08 Chris Ahlstrom * TODO, libseq66/include/midi/event_list.hpp, libseq66/include/midi/midibase.hpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/midi/midibase.cpp, libseq66/src/play/sequence.cpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qslivegrid.cpp: Provisional fix to ragged live-grid progress-bar, fixed main set spinbox, sped up SMF 0 conversion immensely. * libseq66/include/play/performer.hpp, libseq66/include/play/screenset.hpp, libseq66/include/play/setmapper.hpp, libseq66/src/play/performer.cpp, libseq66/src/play/screenset.cpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qslivegrid.cpp: Got grid fingerprint display working, but ragged when unmuted. 2019-09-07 Chris Ahlstrom * libseq66/include/play/sequence.hpp, libseq66/src/midi/midi_vector_base.cpp, libseq66/src/play/sequence.cpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qperfroll.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qsliveframe.cpp: Currently refactoring live-grid loop-buttons for speed. 2019-09-05 Chris Ahlstrom * README, libseq66/src/play/sequence.cpp, libseq66/src/play/triggers.cpp, seq_qt5/src/qloopbutton.cpp: Fixed a bug in live-frame snap coloring, sequence. * contrib/midi/README, libseq66/include/midi/event_list.hpp, libseq66/include/midi/midi_splitter.hpp, libseq66/include/play/performer.hpp, libseq66/include/play/sequence.hpp, libseq66/include/util/palette.hpp, libseq66/src/midi/midi_splitter.cpp, libseq66/src/play/performer.cpp, libseq66/src/play/sequence.cpp: Fixed SMF 0 handling, still slow, fixed coloring. 2019-09-04 Chris Ahlstrom * seq_qt5/include/qslivebase.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeditframe64.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Fixing grid refresh and buss handling. * libseq66/src/play/screenset.cpp, seq_qt5/include/qloopbutton.hpp, seq_qt5/include/qslivebase.hpp, seq_qt5/include/qslivegrid.hpp, seq_qt5/src/qloopbutton.cpp, seq_qt5/src/qseqeventframe.cpp, seq_qt5/src/qseqframe.cpp, seq_qt5/src/qslivebase.cpp, seq_qt5/src/qsliveframe.cpp, seq_qt5/src/qslivegrid.cpp, seq_qt5/src/qsmainwnd.cpp: Improvements to grid button handling. 2019-09-03 Chris Ahlstrom * seq_qt5/src/qseqtime.cpp: Fixed too-small sprintf buffer in qseqtime. * New master. ================================================ FILE: INSTALL ================================================ INSTALL for Seq66 v. 0.99.24 and above Chris Ahlstrom 2015-09-10 to 2026-04-22 Getting Seq66 requires building the code or going to "ahlstromcj/seq66" on GitHub to get an installation package or a Windows release executable. The bootstrap setup is primarily aimed at developers, but is easy to use with the instructions below. Different versions of the program can be built: - qseq66. The "rtmidi" engine version, with a Qt5 user-interface a la Kepler34. Native ALSA, JACK MIDI and JACK transport; fall-back to ALSA; support for Meta events (Set Tempo and Time Signature). The official version of Seq66, Linux only. - qpseq66. The same as qseq66, except that the internal "portmidi" implementation is used as the MIDI engine. Linux and Windows. Built via qmake and suitable for loading in QtCreator installed with mingw32 tools. To build it in Windows, specify the Mingw library and tools when installing QtCreator. See "Qmake-based Install" below. - seq66cli. A headless version of Seq66, which can run from the console or as a daemon, controlled via MIDI and playlists. - seq66clip. The seq66cli app with "portmidi" instead of "rtmidi". We provide a Windows installer (an EXE) in the GitHub release area. It is also fairly easy to build the Windows version using QtCreator/qmake. See the DOS batch script "nsis/build_release_package.bat"; it not only builds it using qmake and mingw, but also describes how to create Windows packages. Below are sections for various ways of installing this project by building from source code: - Linux Distro Package - Normal (automake) Install - Bootstrap Install (Quick Install) - Advanced Steps - Pipe Option: - Debugging: - Qmake-based Install (Linux and Windows) - MSYS2 Install (Windows only, and NOT YET READY) Linux Distro Package (seq66): Your favorite Linux distro may have put seq66 into a distro package. For example, Arch Linux and KXStudio have packages. Here is a pointer to how one user installs it on a Raspberry Pi 3b+ running Raspian: Installed it from from KXStudio repo using Muon: https://kde.org/applications/en/system/org.kde.muon Autotools Build and Install: The normal install is meant for for those who do not want to install a lot of extra developer packages, and want to use the standard "configure" steps. These steps build the most common version of Seq66, "qseq66" with the internal "rtmidi" engine. The "configure" script for the build process means one needs to install the various "dev" dependencies. The conventional way to install applications from source code is to use the source tarball and run the normal mantra: $ contrib/scripts/reconf # if needed (see below) $ ./configure $ make $ sudo make install If "configure" or "make" emit errors, make sure libtool is installed, and then run the script "reconf", stored in "contrib/scripts/reconf". This should make soft links to /usr/share/automake files in the aux-files directory. Then try the above commands again. Another variation is to change the client/port name from the default, which is "seq66". Add an option like the following: $ ./configure --with-client=myseqclient This change will show up in the MIDI I/O tabs in Edit / Preferences. Also note that the qmake executable must exist. If it is not found, check for the existence of an alternate name (e.g. qmake-qt5) and make a soft link to it. Bootstrap Install: The quick install is meant for for those who do not want to install a lot of extra developer packages, and want to use the standard "./configure ; make ; make install" mantra. These steps build the default version of Seq66, "qseq66" with the internal "rtmidi" engine. The "configure" script for the build process means one needs to install the various "dev" dependencies. See the DEPENDENCIES section. The build commands require automake and libtool to be installed. Grab a Seq66 tarball and untar it. Go to the "seq66" directory and run these commands: $ ./bootstrap -er $ make &> make.log $ sudo make install Study the "bootstrap" script to understand how it uses autoheader, automake, libtool, and other standard Linux build tools. This build results in the "qseq66" executable. Clang Compiler: Even if one has the GNU compilers installed, one can still install Clang and use it for the build. Assuming the configure script is up-to-date, and either the base clang or a specific version (e.g. clang-16) is installed, then the configure command can be used for the base clang, or the bootstrap script for a specific version: $ CC=clang CXX=clang++ ./configure $ make &> make.log $ sudo make install One might need to make symbolic links to the desired versions of these compiler, for example clang-16 and clang++-16. The alternative is to make sure the setup is fully cleaned, and use a bootstrap option: $ ./bootstrap --full-clean $ ./bootstrap --clang [... other options] FreeBSD: While FreeBSD can build Seq66, there are some issues that need to be addressed. See contrib/notes/freebsd.text for complete information. The recommended method of building Seq66 is discussed there. Basically, after making sure all dependencies are installed, run the qbuild.sh script and set up for usage of JACK. This advice is volatile. See the discussion in contrib/notes/freebsd.text. Qt and Command-Line Merged Build: Per package-manager request, one can build and install both qseq66 and seq66cli in one pass. Either of the following setups should work. $ ./bootstrap --both or $ ./bootstrap $ ./configure --enable-both Out-of-source Build: Here is an example, assuming one just downloaded the latest code: $ ./bootstrap --full-clean $ ./bootstrap $ mkdir build $ cd build $ ../configure --enable-both (or other options) $ make -j 4 &> make.log (check it for errors/warnings) $ sudo make install (in this example, both qseq66 & seq66cli) OpenSUSE: Directions from the Seq66 GitHub sivecj/seq66 fork. First, install these packages: $ sudo zypper in libjack-devel liblo-devel alsa-devel libqt5-qtbase-devel \ libqt5-linguist-devel Next, link the binary files from Qt5 without the "-qt5" at the end. Link them to your ~/.local/ directory and add the directory to the PATH only if required: $ mkdir -p ~/.local/bin/qt $ ln -s /bin/qmake-qt5 ~/.local/bin/qmake $ ln -s /bin/rcc-qt5 ~/.local/bin/rcc $ ln -s /bin/uic-qt5 ~/.local/bin/uic $ ln -s /bin/lrelease-qt5 ~/.local/bin/lrelease $ ln -s /bin/lupdate-qt5 ~/.local/bin/lupdate $ ln -s /bin/moc-qt5 ~/.local/bin/moc $ export PATH=$PATH:~/.local/bin/qt One might need to run the "reconf" script located in contrib/scripts/reconf. Otherwise ./configure drops an error. Lastly, run ./configure and make. Also see contrib/scripts/make-qt5-links for OpenSUSE and Fedora, which encapsulates the commands shown above. Fedora: Similar to OpenSUSE above. $ sudo dnf install qt5-qtbase qt5-qtbase-devel qt5-linguist \ jack-audio-connection-kit-devel liblo-devel alsa-lib-devel MSYS2: Uses the bootstrap script noted for Linux. It requires the installation of the toolchain, libtool, qt5-base, all prefixed by either "mingw-w64-x86_64-" (preferred) or "mingw-w64-i686-". See contrib/notes/msys2-packages.text for the complete list. Also, edit the .bashrc file to add the following line: export PATH=/mingw64/bin:$PATH Close the terminal window and open a new one to set the PATH. Advanced Steps: These steps are meant for those who want to try the various versions of Seq66, and do not mind installing a lot of extra developer software. 0. Preload any DEPENDENCIES, as listed at the end of this document. However, if some are missing, the configure script will tell you, or, at worst, a build error will tell you. 1. Check-out the branch you want, if you do not want "master". Make a branch if you want to make changes. The active branches are "master", "playlist", and "qt5_reconcile". The comand "git branch -a" will show other branches not yet deleted. See GitHub. 2. The first thing to do is decide what version of Seq66 you want to build: 1. Seq66qt5/qseq66: ./bootstrap -er 2. Seq66cli/seq66cli: ./bootstrap -er -cli 3. Seq66cli/seq66clip: ./bootstrap -er -cli -pm 4. qpseq66 Linux: Qmake/qbuild.sh/Qtcreator in shadow directory 5. qpseq66 Windows: Qmake/Qtcreator/build_release_package.bat For the first two: from the top project directory, run one of the commands above (they auto-configure) or run the following commands, which set up a release build of seq66 (native JACK, native ALSA fallback). $ ./bootstrap (only if the configure script does not exist) $ ./configure [options] If you do not want to see a lot of output, the stock autotools option "--enable-silent-rules" can be added to the ./configure command. Otherwise: $ ./bootstrap --enable-release [ -cli | -qt ] $ ./bootstrap -er [ -cli | -qt ] Note that the options in brackets are optional. The default is equivalent to "-rm -qt". For debugging without libtool getting in the way, just run the following command, which will add the --enable-debug and --disable-shared options to a configure run: $ ./bootstrap --enable-debug [ -cli | -qt ] $ ./bootstrap -ed [ -cli | -qt ] There are also configure options as described below, and conditional macros in the header files. The configure options can be supplied to the ./configure command, while build macros can be defined (in the code) to even further tailor the build. To learn more, run $ ./bootstrap --help If there are build issues, try again after running: $ ./bootstrap --full-clean 3. Run the make command: $ make &> make.log This procedure no longer builds the documentation. If you do care about programmer documentation, change to the doc/dox directory and run "./make_dox reference" and "./make_dox notes". WARNING: "./make_dox reference" is currently BROKEN, even though no errors/warnings are shown in the Doxygen log files. You can add options to "make", such as "V=0" (enable silent build), "V=1" (enable the normal verbose build), and "-j n" (use n processors to speed up the build). 4. To install, become root and run: # make install Note that we have removed the developer reference manual from the automated build (see the Doxygen DEPENDENCIES below), but the existing documents will still be installed. 5. See the additional features that can be enabled, below, using build macros. 6. Also grab the Seq66 User Manual from the "doc" directory. It contains a prebuilt PDF version of the manual, as well as the LaTeX and make files needed to rebuild it. 7. If you want to generate your own source/configure tarball for distributing Seq66, use the pack script: ./pack --release rtmidi 0.94.6 where rtmidi is the intended default build, and 0.94.6 is the version of the project. The branch is included in the resulting tarball name; the usual branch would be "master". Pipe Option: The gcc -pipe option uses pipes, rather than temporary files, for communication between the various stages of compilation. This fails to work on some systems where the assembler is unable to read from a pipe; the GNU assembler can do it. $ CFLAGS="-pipe" ./bootstrap -ed $ CFLAGS="-pipe" ./configure --enable-debug --disable-shared Then do the normal make. Debugging: Normally, one can bootstrap or configure with the --enable-debug option. However, on some machines (e.g. Debian Sid running gcc 9), the debug build fails because it cannot find one of the library symbols. In that case, one can either use a qmake debug build or the following sequence of commands: $ ./bootstrap --full-clean $ ./bootstrap $ ./configure --enable-debug [note the lack of "--disable-shared"] $ make &> make.log [verify that there are no errors] $ libtool --mode=execute gdb ./Seq66qt5/qseq66 We use cgdb instead of gdb for debugging. Qmake-based Install: A build based on QtCreator and Qmake is available, to build the project on Windows, though we created this build on Linux first to work out the numerous "gotchas" with QtCreator, QMake, and the internal Seq66 "architecture". The Qmake-based build is designed to use the local PortMidi library (by default), which is necessary for running Seq66 on Windows or Mac OSX. It can be configured to build using the local RtMidi library by adding "rtmidi" to the CONFIG variable (see below). The Qmake build also uses the Kepler34-based Qt 5 user interface. This user interface is currently similar to the Gtkmm 2.4 user interface, but supports a different (better) feature set, and uses the Seq66 libraries internally. The first way to use this build is to run QtCreator and load the seq66.pro file. This method can be used if you do not care for the command-line. However, if the installer for QtCreator did not set up the default "kit" properly, create a good kit manually. If the kit is set up properly, the "seq66.pro" entry in the left project panel will show the other "pro" files as subprojects. Note that a successful build will put the generated files in a "shadow" directory parallel with the "seq66" directory, such as: build-seq66-Desktop_Qt_5_10_1_MinGW_32bit-Debug Do not forget to "Configure Project" before trying to build it! The second way to use this build is to just use qmake to do a "shadow build". Assuming you are in the "seq66" directory: $ cd .. $ mkdir shadow-debug-build $ cd shadow-debug-build $ qmake -makefile -recursive "CONFIG += debug" ../seq66/seq66.pro $ make &> make.log $ gdb ./Seq66qt/qpseq66 (for debugging) One can also use "CONFIG += release", or just leave that off entirely. We get a nice build that works under Linux or Windows. Currently, we have tried only using the "mingw" tools in Windows, not the "msvc" (Microsoft) tools. For debugging, QtCreator can be used. To create an installer package for Windows, see the instructions at the top of the "nsis/Seq66Setup.nsi" file. One can build the Seq66 application in Windows, and then build the installer for it in Windows or Linux (makensis). The Qmake setup uses "PortMidi" by default, but an "RtMidi" version can be built as well: $ qmake -makefile -recursive "CONFIG += rtmidi" ../seq66/seq66.pro $ qmake -makefile -recursive "CONFIG += debug rtmidi" ../seq66/seq66.pro Followed by the "make" operation. See the "contrib/scripts/qbuild.sh" shell script (which replaces the qbuild Bash script) for an automation of this process. Windows: To build a Windows NSIS installation package, the DOS batch script "nsis/build_release_package.bat" can be used. See the top of that file for the procedure to follow. CONFIGURE OPTIONS FOR APPLICATION FEATURES: These options define or undefine various build macros: --enable-rtmidi Defines SEQ66_RTMIDI_SUPPORT to enable our heavily modified "rtmidi" library. This option enables the usage of native JACK MIDI which will fall back to ALSA if JACK is not running. Builds Seq66rtmidi/seq66. --enable-nsm --disable-nsm Controls the SEQ66_NSM_SUPPORT macro, which is defined by default. This will allow the Non Session Manager to control Seq66. --disable-jack Undefines the SEQ66_JACK_SUPPORT macro, which is otherwise defined by default. This option is not viable for the "rtmidi" version of Seq66, which incorporates a couple of JACK modules, but is useful in the Windows/OSX builds. --enable-jack-session Defines the SEQ66_JACK_SESSION macro, which is defined if JACK session support is defined, and the jack/session.h file is found. Because JACK has deprecated its session management, and now recommends the Non Session Manager (NSM), this option is now disabled by default. Cannot be enabled in the Windows/OSX builds. --enable-portmidi --enable-rtmidi We have gotten a few alternate implementations to work. The normal build is effectively --enable-rtmidi. The --enable-portmidi flag creates a new application, seq66portmidi, that is based on using ALSA on Linux via the local "PortMidi" library. SEQ66_PORTMIDI_SUPPORT SEQ66_RTMIDI_SUPPORT MANUALLY-DEFINED MACROS IN CODE: Read the descriptions of the various macros defined in libseq66/include/seq66_features.h, and decide if you want to define or undefine them, before building the application. Only crazy or experiment-happy people will normally want to change the define-state of these macros. In addition, sprinkled throughout the code are macros like the following: USE_xxxxxx: SEQ66_xxxxxx_TMI: This category of macros are usually undefined values that let us keep old or experimental code around in case we decide it was the better code after all. Generally, you do not want to change the status of these macros unless you are very familiar with the code and willing to temporarily break stuff or add annoying console output. The "TMI" in some denotes debug code that dumps too much information (TMI) to the console, so has to be enabled by editing it on those occasions where you really want it. We have incorporated some very useful code from the Seq32 project of the user "stazed". He includes some new features and some bug fixes that we had overlooked. At present, we are not enabling this functionality, just adding it, macroed out by the following macros: SEQ66_MISSING_JACK_VIRTUAL_PORTS (undefined) SEQ66_PORTMIDI_SYSEX_PROCESSING (undefined) SEQ66_PROVIDE_AUTO_COLOR_INVERSION (experimental, investigative) SEQ66_SHOW_GM_PROGRAM_NAME (defined by default) SEQ66_USE_COLLAPSED_SLOT_POPUP_MENU SEQ66_USE_DEFAULT_PORT_MAPPING SEQ66_USE_METRONOME_FADE (undefined) SEQ66_USE_MIDI_MESSAGE_RINGBUFFER SEQ66_USE_SHOW_HIDE_BUTTON SEQ66_DRAW_PITCHBEND_AS_DOT (undefined) SEQ66_SHOW_GM_DRUM_NAME (undefined; leave undefined) REPORTING ISSUES: Should one be unfortunate enough to experience a segmentation fault (i.e. the infamous "crash"), here is the most complete way to diagnose the error, if we cannot replicate it: $ ./bootstrap --full-clean $ ./bootstrap -ed $ make &> make.log $ gdb ./Seq66qt5/seq66 (gdb) r [add your command-line arguments here if needed] [replicate the crash] (gdb) bt [does a backtrace] Then highlight, copy, and paste the stack trace and attach it to the bug report. Might have to Page Up to see the relevant parts of the stack trace. DEPENDENCIES: With luck, the following dependencies will bring in their own dependencies when installed. Code: - libasound2-dev (alsa-devel on OpenSUSE) - libjack-jackd2-dev - liblo-dev (needed for NSM/OSC support, but not optional) - libpng-dev (replaces libpng12-dev with libpng16, latest Debian Sid, and brings in libpng-tools) - libpthread (normally already included in a Linux distro) Also see the sections on OpenSUSE and Fedora below. Qt Builds: This lists comes from the m4 file, and has been abbreviated. Some of these are surely not needed and we can eliminate them from the test at some point. Left off are the initial "libQt" or "libqt" and final "5-dev" items in the library names. The obviously necessary ones (*) come first; they are specified in m4/ax_have_qt_min.m4, which provides the AX_HAVE_QT_MIN test used in the configure.ac script. The rest of them seem to be brought in as sub-dependencies, so make sure they are all installed if your attempt to build qseq66 fails. See "Build tools" below. - Core * - Gui * - Widgets * - Xml - OpenGL - PrintSupport - QuickTest - Concurrent - DBus - Qml - Network - Test - Sql Build tools: - automake and autoconf - autoconf-archive - g++ - make - libtool - Qt 5 or Qt 6. Install qtcreator only if you want it, as it installs 650 Mb worth of files. One can just install the bare minimum: - qt5-default (Important!) - qtbase5-dev (might be installed after installedin qt5-default) - qtbase5-dev-tools (ditto) - qtdeclarative-dev-tools or qtdeclarative5-dev-tools (qtdeclarative5-dev?) - qtchooser - qt5-qmake - qtdesigner; optional if you want to tweak the GUIs, not huge, provided by the qtttools5-dev-tools package in Debian. - qtcreator; also optional, but handy. Unmark/uninstall the "clang" packages that are recommended unless you want clang. To setup Qt, first run the command "qtchooser -l". If more than one version of Qt is installed, their numbers are all listed. Make sure that "5" is in the list, and then add this line to your .bashrc file: export QT_SELECT=5 This allows builds in bash to use the correct version of Qt. Also, for appearances on non-KDE systems, consider: - Installing the qt5ct package (Debian/Ubuntu) - Adding "export QT_QPA_PLATFORMTHEME=qt5ct" to .bashrc. For Ubuntu Studio 25 or thereabouts, see the document INSTALL.seq66.on.Ubuntu.md Raspberry Pi: A few notes while trying to get Seq66 built on a Raspberry Pi 3 Model B+. The Raspian on this board, installed via NOOB, could ultimately not load some Qt dependences. Ended up installing OpenSUSE, and installed Qt Creator to save time. Windows Installer: One can build the Windows installer under Linux (see the "nsis" sub-directory). To build it on Windows, install the NSIS 3 program, available at https://sourceforge.net/projects/nsis/files/NSIS%203/3.08/nsis-3.08-setup.exe/download Be sure to add the location of makensis.exe to the Windows PATH. Runtime: - libatk-adaptor (and its dependencies) - libgail-common (and its dependencies) - jack_control (optional; jack-example-tools on Arch Linux) Documentation (now optional, must be built manually): We have removed the automatic generation of the developer reference documentation. It is a pain to deal with relative to the number of times one needs to read it. To build it, change to the doc/dox directory and run "./make_dox reference". There is now a shorter, more useful "Developer Notes" document that can be built using "./make_dox notes". Be aware that the PDF files will always be present and installed, even if not necessarily up-to-date with the latest code. - Source-code documentation: - doxygen and doxygen-latex - graphviz - texlive - User manual (additional packages): - latexmk - pdf2latex To build the PDF user manual, change to the doc/latex directory and run "make". The PDF is found in data/share/doc. To build the Doxygen documentation manually, change to the doc/dox directory and run: ./make_dox reference ./make_dox clean Currently, we do not care if this works. Too much information. To install the documentation to /usr/local/doc/seq66-0.90: ./make_dox install Debian packaging: See the README file in the Debian directory. - debhelper - dh-autoreconf (Perl) - fakeroot MingW: Avoid use of AC_CYGWIN and AC_MINGW32. Specify the mingw compiler when installing qmake/qtcreator for Qmake/Qt5 builds on Windows. Other: - git - vim or emacs Testing (optional): - qjackctl - hydrogen - klick (a command-line metronome) - qtractor or other MIDI sequencers - gmidimonitor or (better) midisnoop - sendmidi - python-dbus (for contrib/scripts/startjack usage) - a2jmidid (exposes Seq24 ALSA MIDI ports to JACK) # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: INSTALL.seq66.on.Ubuntu.md ================================================ # INSTALL Seq66 for Ubuntu Studio Authors: WinkoErades and MQS-mark

Updated: 2026-04-22 Here’s a **step-by-step installation plan** to build and install **Seq66** from source on **Ubuntu Studio 25.10**. This assumes you are *installing from the GitHub repository* you linked: **https://github.com/ahlstromcj/seq66/tree/master** ([GitHub](https://github.com/ahlstromcj/seq66?utm_source=chatgpt.com "Seq66: Seq24-based live MIDI looper/editor. v. ...")) ## 1. Prepare Ubuntu Studio 25.10 Ubuntu Studio is already tailored for audio production, but ensure your system is up to date: ```bash sudo apt update && sudo apt upgrade -y ``` Reboot if needed after upgrades. ## 2. Install Required Build Dependencies Seq66 uses **GNU Autotools** and **Qt5** for the GUI version, plus OSC/MIDI support. You need development tools and libraries: ```bash sudo apt install -y \ autoconf \ automake \ build-essential \ git \ libasound2-dev \ libatopology-dev \ libc6-dev \ libjack-jackd2-dev \ liblo-dev \ libpcre2-dev \ libqt5widgets5 \ libtool \ libudev-dev pkg-config \ qt5-qmake \ qtbase5-dev \ qttools5-dev \ qttools5-dev-tools \ ``` *Explanation:* - `build-essential`, `automake`, `autoconf`, etc., provide the core build toolchain. - `qtbase5-dev` and related packages supply Qt5 headers/tools for building the Seq66 GUI. - `libasound2-dev`, `libjack-jackd2-dev`, `liblo-dev` cover ALSA, JACK/pipewire jack compatibility, and liblo (OSC), all typical MIDI/audio support libs. - `libatopology-dev` might not be needed, depending on the Ubuntu version. (Exact package names can vary slightly in Ubuntu 25.10; `apt search liblo` etc. helps confirm.) ([GitHub](https://github.com/ahlstromcj/seq66/blob/master/INSTALL?utm_source=chatgpt.com "seq66/INSTALL at master")) ## 3. Clone the Seq66 Repository Choose a location such as your home directory and clone the latest code: ```bash cd ~ git clone https://github.com/ahlstromcj/seq66.git cd seq66 ``` Make sure you’re on the **master** branch (default) unless you want a different release. ## 4. Bootstrap and Configure Some older Seq66 releases may require regenerating the build system if `configure` is missing or outdated: ```bash ./bootstrap ``` Note that 'bootstrap' has a '--help' option. If 'bootstrap' is not present or errors, use `autoreconf`: ```bash autoreconf --install --force ``` Now configure the build: ```bash ./configure ``` You can pass optional flags if needed, e.g.: - `--enable-rtmidi` (explicitly enable RtMidi backend) - `--disable-portmidi` (if portmidi isn’t installed) *(Check `./configure --help` for options.)* Note: `configure` will detect Qt5 if installed; Qt6 can also be used. ([GitHub](https://github.com/ahlstromcj/seq66?utm_source=chatgpt.com "Seq66: Seq24-based live MIDI looper/editor. v. ...")) 📌 *Common issues*: If `./configure` fails complaining about missing files like `Makefile.in`, ensure you ran `autoreconf` first. Older seq66 sources may need that. ([GitHub](https://github.com/ahlstromcj/seq66/issues/25?utm_source=chatgpt.com "Configure fails · Issue #25 · ahlstromcj/seq66")) ## 5. Build Seq66 Compile the code: ```bash make -j$(nproc) ``` `-j$(nproc)` speeds up compilation using all CPU cores. If you only want the command-line version, you can compile only that target after configure: ```bash make seq66cli ``` ## 6. Install Once built, install it system-wide: ```bash sudo make install ``` This typically installs binaries like **qseq66**, **qpseq66** (Qt GUI), and **seq66cli** into `/usr/local/bin` or similar. ## 7. Optional: Add to Desktop Menu If the GUI version built (`qseq66` or `qpseq66`), a `.desktop` file already exists in the repo’s `desktop` folder. You can install it so it appears in your application launcher: ```bash sudo cp desktop/seq66.xpm /usr/share/applications/ ``` Then update desktop DB: ```bash sudo update-desktop-database ``` ## 8. Run and Test Launch either: - **GUI**: `qseq66` or `qpseq66` - **CLI**: `seq66cli` Ensure your MIDI devices are connected and recognized. Ubuntu Studio’s audio tools (like QJackCtl or Ubuntu Studio Audio Config) may help routing JACK/ALSA. ## Troubleshooting Tips **Missing `configure` or autotools errors** - Run `autoreconf --install --force` before `./configure`. ([GitHub](https://github.com/ahlstromcj/seq66/issues/25?utm_source=chatgpt.com "Configure fails · Issue #25 · ahlstromcj/seq66")) - Ensure `automake`, `autoconf`, `libtool` are installed. **Qt not detected** - Confirm `qtbase5-dev`, `qttools5-dev`, `qttools5-dev-tools`, and `qmake` are installed. Make sure your environment sets something like "export QT\_SELECT=qt6. Forqt6, might have to set a soft-link in /usr/lib/qt6/libexec: "ln -s /usr/bin/qmake6 qmake". - Use `qtchooser -list-versions` to check available Qt versions. **Missing libs (such as liblo)** - Install `liblo-dev` (for OSC) before configure. ([Reddit](https://www.reddit.com/r/sequencers/comments/16o7b7t/seq66_debian_install/?utm_source=chatgpt.com "Seq66 Debian install : r/sequencers")) **Build fails due to ALSA/JACK** - Check `libasound2-dev` and appropriate JACK dev packages are installed. ## Useful References - Official Seq66 **README / INSTALL** in the repository (detailed build instructions). ([GitHub](https://github.com/ahlstromcj/seq66?utm_source=chatgpt.com "Seq66: Seq24-based live MIDI looper/editor. v. ...")) - User manual and tutorials on the project site (great for usage after install). ([GitHub](https://raw.githubusercontent.com/ahlstromcj/seq66/master/data/share/doc/seq66-user-manual.pdf?utm_source=chatgpt.com "Seq66 User Manual v. 0.99.22 - GitHub")) [//] vim: sw=4 ts=4 wm=2 et ft=markdown ================================================ FILE: Makefile.am ================================================ #***************************************************************************** # Makefile.am (seq66) #----------------------------------------------------------------------------- ## # \file Makefile.am # \library seq66 # \author Chris Ahlstrom # \date 2018-11-11 # \updates 2022-01-04 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This is the top-level project makefile for the seq66 project. # This makefile provides the skeleton needed to build the library # and application directories using GNU autotools. It supports a number # of sub-projects # #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Packing targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) MOSTLYCLEANFILES = *~ #***************************************************************************** # Extra files in the top-level directory #----------------------------------------------------------------------------- EXTRA_DIST = bootstrap pack README.md VERSION INSTALL NEWS ChangeLog TODO #***************************************************************************** # Packaging #----------------------------------------------------------------------------- # # This section is recommended for the top-level Makefile.am by the # reference. # #----------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ SEQ66_API_MAJOR = @SEQ66_API_MAJOR@ SEQ66_API_MINOR = @SEQ66_API_MINOR@ SEQ66_API_PATCH = @SEQ66_API_PATCH@ SEQ66_API_VERSION = @SEQ66_API_VERSION@ SEQ66_LT_CURRENT = @SEQ66_LT_CURRENT@ SEQ66_LT_REVISION = @SEQ66_LT_REVISION@ SEQ66_LT_AGE = @SEQ66_LT_AGE@ #***************************************************************************** # Installed directories #----------------------------------------------------------------------------- # # The 'libdir' define is necessary to cause the proper subdirectory to # be made during installation. 'seq66libdir' is defined in the # configure.ac script. The 'libdir' define is needed for work with # libtool. Not sure about 'pkglibdr'. # # pkglibdir=$(seq66libdir) # # Directories and macros: # # prefix = /usr/local # libdir = /usr/local/lib/seq66 # datadir = /usr/local/share # datarootdir = /usr/local/share # seq66libdir = /usr/local/lib/seq66 # seq66docdir = /usr/local/share/doc/seq66.1 # seq66includedir = /usr/local/include/seq66.1 # localedir = /usr/local/share/locale # # 'localedir' is the normal system directory for installed localization # files. # #----------------------------------------------------------------------------- prefix = @prefix@ libdir = @seq66libdir@ datadir = @datadir@ datarootdir = @datarootdir@ seq66docdir = @seq66docdir@ seq66includedir = @seq66includedir@ seq66libdir = @seq66libdir@ localedir = $(datadir)/locale DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ # # top_srcdir = ../../.. [XPC Basic directory] # builddir = /home/ahlstrom/ca/mls/git/XPC Basic-1.1/debug # #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ #****************************************************************************** # aclocal support #------------------------------------------------------------------------------ ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} #***************************************************************************** # libtool #----------------------------------------------------------------------------- # ${SHELL} $(top_srcdir)/config.status --recheck # $(top_srcdir)/config.status --recheck #----------------------------------------------------------------------------- LIBTOOL_DEPS = @LIBTOOL_DEPS@ libtool: $(LIBTOOL_DEPS) $(SHELL) ./config.status libtool #***************************************************************************** # SUBDIRS #----------------------------------------------------------------------------- # # We decided to build the Doxygen documentation only manually, and have # commented it out. # # # doc/dox # # Note the order of these tests is important to handling the mixing and # matching of MIDI engine versus user-interface. # # RTMIDI/QT5 # RTMIDI/CLI # PORTMIDI/WINDOWS: QT5 Windows build is done via qmake. # # See INSTALL. # #----------------------------------------------------------------------------- SUBDIRS = m4 libseq66 resources/pixmaps if BUILD_RTMIDI SUBDIRS += seq_rtmidi endif if BUILD_PORTMIDI SUBDIRS += seq_portmidi endif if BUILD_SESSIONS SUBDIRS += libsessions endif if BUILD_QTMIDI SUBDIRS += seq_qt5 Seq66qt5 endif if BUILD_RTCLI SUBDIRS += Seq66cli endif if BUILD_DOCS SUBDIRS += doc endif SUBDIRS += man data #***************************************************************************** # DIST_SUBDIRS #----------------------------------------------------------------------------- DIST_SUBDIRS = $(SUBDIRS) #***************************************************************************** # all-local #----------------------------------------------------------------------------- all-local: @echo "Top source-directory 'top_srcdir' is $(top_srcdir)" @echo "* * * * * All build items completed * * * * *" #****************************************************************************** # Debugging targets #------------------------------------------------------------------------------ showver: @echo "PACKAGE = $(PACKAGE)" @echo "VERSION = $(VERSION)" @echo "SEQ66_API_MAJOR = $(SEQ66_API_MAJOR)" @echo "SEQ66_API_MINOR = $(SEQ66_API_MINOR)" @echo "SEQ66_API_PATCH = $(SEQ66_API_PATCH)" @echo "SEQ66_API_VERSION = $(SEQ66_API_VERSION)" @echo "SEQ66_LT_CURRENT = $(SEQ66_LT_CURRENT)" @echo "SEQ66_LT_REVISION = $(SEQ66_LT_REVISION)" @echo "SEQ66_LT_AGE = $(SEQ66_LT_AGE)" #**************************************************************************** # Makefile.am (seq66 top-level) #---------------------------------------------------------------------------- # vim: ts=3 sw=3 ft=automake #---------------------------------------------------------------------------- ================================================ FILE: NEWS ================================================ NEWS for Seq66 0.99.24 Chris Ahlstrom 2015-07-10 to 2026-05-01 # Change Log ## [0.99.24] - 2026-05-01 Mitigates issue #144, where note and drum-note painting was not being snapped in a sensible way. ### Added - Pattern editor. - Added the display of a drum-note's name when in drum mode and hovering near a drum-note to modify it. - Improved the drums module/class for this addition. - Added the ability to show hex numbers for the standard MIDI controller events. - Added two small buttons: - One toggles showing certain data pane numbers in hexadecimal versus decimal format. - The other toggles showing the 5 horizontal line level numbers, useful when the display is cluttered near the beginning. - Event editor. - Added the ability to show hex numbers for the standard MIDI controller events. ### Changed - Backed autoconf version from 2.72 to 2.71 re issue #145. - Edited all forms in QtCreator to use Qt 6 namespaces. - Fixed some more QMouseEvent x()/y() deprecations found by Clang - qseq66.rc. - Change auto-rc-save to default to false. - Added another flag so that the 'rc' file can be saved at first run (no existing configuration files) without saving auto-rc-save=true. - Auto save enabled either by user setting in Edit / Preferences / Session or by a relevant change in configuration at run-time. ### Fixed - Main window. - When the "Remap & Restart" pops up, if a pattern editor is open, the user clicks OK, and a different tune is selected, a crash (segfault) could occur on Arch. So clicking OK now enables quiet mode for the restart, so that the remap dialogs does not come up again. Let the user be aware! - A similar segfault with the "Unavailable port" message. Instead of setting quiet, the better fix seems to be stopping the qseqeditframe64 timer before the close() call in qseqeditex. - A similar segfault with the "LFO" dialog. Fixed by no longer resetting the MIDI events when the close event occurs. Holy moly. - Pattern editor. - Mitigated issue #144 (painted notes misplaced in time) for normal notes and drum notes by added a portion of snap to the location of the mouse click. If this is still an issue, simply reduce the size of the snap. ## [0.99.23] - 2026-04-15 This version has changes to better support Qt6, better display and selection of program and controller change events, and other minor fix-ups. ### Added - Event editor. - A new button for either Control or Program changes pops up the menus for Control Changes set by the 'usr' file and the Program Changes set by the 'patches' file. - Added the qchannelpopup class for future refactoring / unification of channel menus, but it might never be ready. It is in the contrib sub-directory. - Instead, added the populate\_midich\_combo() function to the qt5\_helpers module, now used in the pattern and event editors. - Added the drums module to provide for listings of GM drum notes and to support non-GM drum notes display in the future. ### Changed - Main window and grid. - At startup, instead of setting argv[0] as app-info. set QCoreApplication::applicationFilePath(). - Now uses the populate\_midich\_combo() function. - Event editor. - Now uses the populate\_midich\_combo() function to show the control/program drop-down menus. - Palettes. - Updated the incrypt-66.palette file; some colors don't apply in the old version. - Worked on some Qt6 deprecations. - Documentation updated. Additional pictures for Event editor. - Moved the LICENSE files to "licenses" sub-directory. ### Fixed - Various fixes to controller and patch naming and handling. - Fixed Qt6 deprecations. ## [0.99.22] - 2025-10-26 ### Added - Issue #141, mostly implemented. See "Pattern editor" below. - midifile. - Added storage for the first BPM value, needed by performer. - Main window and grid. - With the mouse pressed, the current slot holding the cursor is drawn "flat". - Giving a grid slot "focus" and then pressing the Menu key brings up the grid-slot popup menu. - Pattern editor. - Showing note bars for scales and chords: - Added a new brush called "chord" to the palette file. Click Edit / Preferences / Session / Store Palette. - When a chord is active, the notes that are _not_ in the chord are grayed out with diagonal hash lines. - Added buttons to toggle showing the scale/chord bars and the filtering of off-scale/off-chord notes when painting notes. - Normally the base note of the chosen musical key is shown in the keys pane. Undefine the SEQ66\_SHOW\_SELECTED\_KEY\_OCTAVE macro to keep showing C. - Painting notes in scale/chord: - Added a button to toggle the filtering of notes based on scale/chord. - The selected key, scale, and chord determine the actual chord represented on the pattern grid. - Added an option to the "Tools" (hammer) menu to select notes in a range of pitches. Either note numbers or note names can be used. See issue #141 and the user manual. - Documentation. ### Changed - C++. Added -Wno-unnecessary-virtual-specifier to Clang builds to avoid a dumb warning about final classes and virtual functions. - Pattern editor. - Changed closest-snap to down-snap to make note insertion more intuitive. - Removed the redundant display of time signatures in the data pane. - We now store the selected chord for a pattern, if any, as a new "c\_musicchord" SeqSpec. It is track-specific only, ### Fixed - Fixed a segfault at startup on some builds on Arch Linux. It seems to have been an issue of order of initialization of a static string via a static function. - performer. - The file PPQN was set when reading the file, so later the set\_ppqn() function would not inform the master bus of a PPQN change (in ALSA). - Fixed: the slow playback of high-PPQN files. - JACK. - Fixed detect\_jack() and added a missing call to jack\_free(). - midifile. - Added storage for the first BPM value, needed by performer. - Fixed: Tempo event in track 0 does not override c\_bpmtag SeqSpec. - Main window and grid. - Fixed: an issue where loading a file with a different PPQN raised the modify flag. - Fixed: When dragging a pattern, modify was set before the drag was completed. Also could not move another pattern into the empty slot. - Pattern editor. - Fixed the snap-interval setting for drawing notes. - Fixed a bug where the background sequence was not loaded into the piano roll. ## [0.99.21] - 2025-07-28 This release contains fixes for issues #137, #138, #139, and #140. It also contains a number of smaller changes discovered while fixing these issues. See below. ### Added - Main window and grid. - Pattern editor. - Pattern-fix dialog. - Added parameters for randomizing note pitches according to the current musical scale for the pattern. - A pitch-randomization function was added to the eventlist. The notes adhere to the currently selected musical scale. - Added space above and below the data range and added top/bottom lines and value labels. See issue #140. - Adding an Insert macros function to Tools. - LFO. Added a "DC Only" option. - Song editor. - Event editor. - Preferences dialog. - Detection of using ALSA's "Midi Through" ports for both control added. - Detection of this at startup has been added. An error message is posted and MIDI Control and Display disabled, for safety. - Import/Export. - MIDI defaults. Added to midibytes.h: - c\_midi\_clocks\_per\_metronome = 24 - c\_midi\_32nds\_per\_quarter = 8 - Documentation. ### Changed - A C++17 compiler is now required. Time to move on! - Changed eventlist::sort() to use std::stable\_sort(), and event::get\_rank() to make CCs and Program Changes to have the same priority, as part of fixes for issue #138. - Main window and grid. - Pattern editor. - Optimized gradient drawing using QLinearGradient members in qseqroll instead of auto objects. - Made the drawing of pitchbend data show +- pitchbend better, i.e. above and below the midline pitchbend, 0. - Controller events are now recorded when playback is stopped. Time is not incremented. (It can be changed in the Event editor, if needed.) - Upgraded the LFO editor as per issue #139. - Added an "Insert macro at L" function in the Tools popup menu. - Upgraded the MIDI control macro feature to allow multiple events in each macro, separated by " | ". - Song editor. - Optimized gradient drawing using QLinearGradient members in qperfroll and qperfnames instead of auto objects. - Event editor. - Preferences dialog. - Contrib files. - Coalescing common code. Also removed the midistring type to use midibytes (a vector) instead. - Changed the scales\_policy() function slightly. ### Fixed - SMF 0 Import. Meta events are now stored in pattern 0. See it by loading contrib/midi/CountryStrum.mid. - Main window and grid. - The BBT/HMS button tooltip was moved from the qsmainwnd widget to the button itself. Doh! - The playlist code was auto-arming all patterns even when the playlist was not active. - Changing the main time signature now changes the time signature in all patterns (except for those with a pattern editor open). - Pattern editor. - The drawing of scales was done incorrectly. Fixed both the seqroll grid drawing and the scales\_policy() function. - Fixed seqroll 'r' command (velocity randomization) to set the modify flag only if (selected) events are actually randomized. - Fixed the drawing of "ghost notes" when moving or pasting notes. - PPQN updates. - Fixed the adding of painted notes (mostly) at PPQN other than the long-standing base default, 192. - Changed the lowest PPQN from 32 to 24, for those who experiment. Also added PPQN = 48. - Fixed File / New to reset PPQN to the default (192). It also resets the tempo to the value in the 'usr' file. - Fixed the handling of the MIDI file PPQN to set initial zoom correctly for all supported PPQNs, and when "0" is clicked, in the pattern and song editors. - Fixes made to pitchbend calculations and display. - Updated the time-signature handling as per issue #137. - LFO. - When using LFO, saving, then exiting via the main window a prompt about a needless save was removed. - Fixing the LFO for operation on pitchbend. - Fixed qeditbase::snap\_x(); removed the PPQN ratio factor. - Modified adding painted notes to the closest tick snap, also fixes adding notes while zoomed. - Song editor. - Event editor. - Preferences dialog. - Fixed the enabling/disabling of entries in the control and display combo-boxes when changing active status of control and display. - A warning is provided when using a MIDI Through port for both control and display, which can lead to FIFO overruns and weird stuff like segfaults. - Configuration files. - Classes. - midifile. - The earliest time-signature event, if present, overrides the c\_timesig SeqSpec. - SeqSpecs with no data (length == 4) were read improperly. - performer. Changed default 32nds-per-quarter-note from 0 to 8. - sequence. Used the new MIDI default values and fixed an index error in add\_timesig\_event(). - Module midifile. - Cleaned up the handling of time signature events and the c\_timesig SeqSpec, now set to 4/4 in files saved before this fix. ## [0.99.20] - 2025-06-12 ### Added - Main window and grid. - Clicking on the Seq66 logo in the main window now closes all open live grids, pattern editors, and song editor windows. The tabbed version are not affected. - Added features to the grid slot popup menu: - Flatten triggers. This uses the pattern triggers to regenerate the notes, replacing multiple triggers with one long one (may need some work yet). Similar to Song Export, but for one pattern, and no export is done. - Export pattern. Saves a single track to a new file, at pattern number 0. (May need some work yet). - Control/pitch events now show up in pattern-slot progress box. Can be disabled via 'usr' [user-ui-tweaks] progress-box-show-cc = false. - A timed error prompt (instead of OK/Remap and restart) when running via NSM; see qsmainwnd and qt5nsmanager. - Pattern editor. - Added some horizontal dotted lines to the data pane to mark the 32, 64, and 96 level (of items such as velocity). - Song editor. - Added a dark vertical border at the end of a song. - Event editor. - Added display of the pitch-bend in semitones in the event editor. It currently assumes a range of +-2 semitones, a MIDI default. - A column in the event editor shows the bus number that the event came in on. Not saved with the event, however. - Preferences dialog. - An ALSA/JACK label to JACK preferences tab, same as in mainwnd. - The button to "Clear" the port-maps has been removed. The port maps are always present, but can still be deactivated. - Import/Export - Added a 'usr' option to enable/disable automatic conversion of an SMF 0 MIDI file to SMF 1. - The export of files to SMF 0. See the "MIDI Import/Export" section of the user manual for provisos. - Documentation. - Changes, fixes, and additional features in the user manual. ### Changed - Main window and grid. - A pattern with a color shows that color even if empty. - Made the button heights uniform in all Qt styles (so far). - Pattern editor. - Nested editing and copy/paste menus into Edit and Track submenus. - Record indicator supports a gradient brush. - Song editor. - Thick line between sets in the song editor. - Thick line at the end of a song. - Event editor. No changes; see "Added". - Preferences dialog. - Enhanced the recent-file handling and auto-rc-save. - Many other minor tweaks to comments and appearance. - Contrib files. - contrib/vim-syntax/cpp.vim with added symbols like CSTR. - contrib/vim.rc, changed F8 for fixing Windows quote characters. - Coalescing common code. - qsmainwnd. - Sequence recounting after deletion or moving of patterns. - More, did not keep track. ### Fixed - Main window and grid. - The movement of patterns in the grid slot. - Fixed setting the modify flag (asterisk) in recording notes. - Fixed global transpose and it now sets "modify". - Fixes to counting sequences. - Cleaned up recent-files vice playlist support. - Pasting a pattern now sets the pattern number to the slot receiving the pattern. - Many UI/keystroke fixes, including autmoation control functions such as automation\_play\_ss. - Import/Export - The export of files to SMF 0. See the "MIDI Import/Export" section of the user manual for provisos. - Pattern editor. - Weird issue with setting measures in the pattern-editor-in-tab. - The plotting of pitchbend data has been made correct. - The broken painting of notes with the mouse. - Enhanced sequence::partial\_assign(), adding missing members. - Some style sheets have been tweaked to fix time/piano-roll alignment issues. - Song editor. - Fixed resetting trigger transposition in the Song editor. - Swapped End and Ctrl-End so that the former shows last portion of the song. - Event editor. No fixes; see "Added". - Preferences dialog. - Can turn off the auto-save-rc option and preserve that setting at exit. - Error of connecting the store-palette slot in qseditoptions twice. - Configuration files. - Some configuration files weren't setting up for saving due to old versions being present. ## [0.99.19] - 2025-03-02 ### Fixed - Issue #136. - Filtered out Program events from the drum-mode piano roll. - Can now drag Program events up and down in the data pane. - While working this issue, the following problems were found and fixed. - The saving of Program events was fixed to calculate the proper time-stamp. - Program events can be moved up and down by dragging, as well as by the line-dragging method. - Added display of the names of Control and Program Change numbers in the event editor and pattern editor.. - Pressing the finger button in the pattern editor now also enters paint mode in the event pane. The selected event category can the be entered by a click, and then moved up and down in the data pane. ### Added - This macro is defined by default: SEQ66\_SHOW\_GM\_PROGRAM\_NAME to show the GM patch names in the pattern editor data pane instead of just the patch/program number. See seq66\_features.h. - Added a new configuration file, '.patches' that can be used to show non-GM device program names in lieu of the built-in GM program name. Also specifiable in the Preferences / Session tab. ## [0.99.18] - 2025-02-03 More developer-discovered issues :-( ### Added - Added the dark-theme and dark-ui options to the user file for handling GUI elements that are otherwise difficult to see. - Added an option to dump the current palette while automatically inverting to create the --invert palette. It's activated by the SEQ66\_PROVIDE\_AUTO\_COLOR\_INVERSION macro. - Added a Help / View Log function to bring up the latest log text (if a log file is enabled). ### Changed - contrib/scripts/jackctl. - Beefed up the jackctl script to facilitate testing. - Added the "-u" option to a2jmidid in the jackctl script, to avoid undesirable changes to port names. - Added support for raysession and agordejo. - Refactored the copy-configuration and delete-configuration functions to make them more robust and less dependent on file-extension. Depends on the SEQ66\_KEEP\_RC\_FILE\_LIST macro. - Updated the show\_folder\_dialog() function to show hidden directories. - Disabled the various "Remap"/"Restart" buttons when running under an implementation of NSM. ### Fixed - Issue #135. - Added an inverse pattern-length icon for dark themes. - Fixed the vertical lines in the time panel so as not to obscure the measure numbers. - Updated the pattern-editor piano roll drawing to avoid invisible vertical lines. See the dark-theme and dark-ui options too. - Fixed a seqfault when pressing an empty button on a set other than the first set. Also fixed in tag 0.99.17.1. - Fixed the JACK-to-ALSA fallback process, broken if the computer is running the jackdbus daemon. Also tested were differences in a2jmidid between two dev computers. - Fixed an error in the --home command-line option that left "home" as "~/.config/seq66". - Refactored and fixed the Import Project Configuration menu. - Refactored and fixed the Export Project Configuration menu. It now can locate and copy any PNG files (used in some of the Seq66 style-sheets). Currently the copying of PNGs is Linux-only [It uses the glob() function]. - Fixed the route66 bitmap. ### Removed - The pattern-fix effect check-box for the "Truncate" effect, which cannot occur anymore. ## [0.99.17.1] - 2025-01-12 ### Fixed - Fixed a segfault when clicking an empty pattern on sets 1 and above. ## [0.99.17] - 2025-01-10 This release provides a rather large number of fixes, including bugs found while investigating user reports. Included are updated palettes and Qt style-sheets, and a beefed up pattern-fix dialog. ### Added - Added a "thick grid lines" checkbox to Edit / Preference / Display - Added usage of "Extra" color for line drawing. - Added "Align Right" to the Pattern Fix dialog. ### Changed - Revamped and extended the functionality of the Pattern-Fix dialog. As part of this, the alteration API of the event-list has been updated. - Refactored vertical grid-line calculations; code moved into the zoomer. For odd time-signatures (i.e. with a beat-width not a power of 2, which is not allowed in MIDI but is stored in a SeqSpec event in Seq66) we draw fewer lines in between bars to make a cleaner display. But do not expect them to be "nice". See the 4/7 pattern in the timesigs.midi file. - Pattern editor. Better support for thick vs. thin grid lines. - qseqroll. - qseqtime. - qstriggereditor. - Song editor. Better support for thick vs. thin grid lines. - qperftime. - qperfroll. - qeditbase. Macroed out unused zoom functions. Moved most to the zoomer class. ### Fixed - Fixed segfault when clicking the Restart button with external live frame(s) or external song editor open. - Issue #128 follow-on. - Fixed the removal of trailing notes after truncating a pattern to a smaller number of measures. - Upgraded one-shot recording to work during playback. Still minor issues; see the user manual. - Issue #133 and issues found while working this. - Pattern editor. - Changed the slot connection for editable combo-boxes to connect the lineEdit() accessor. - Measures (pattern length) combo-box. Also made cancelling the change work correctly. - Beats-per-bar combo-box. - Beat-width combo-box. - Fixed a seqfault when opening tune with a pattern-editor already up for the current tune. The side-effect is that armed status will not synch between the main window and the editor. - Fixed raising the modify flag when a second pattern (with a different time signature) was opened in the editor. - Fixed a potential segfault when opening a recent file with a pattern currently open for editing. Had to add polling to check for changes in arm status. - Refactored the event/buss dropdowns to be modified only when the user changes setting or tries to bring up the dropdowns (lazy dropdowns). - Song editor. Time zoom now affects horizontal scroll-bar length. - Issue #134. Added dates to the release-name line from 0.99.14 on; were left off by an oversight. - Pattern-fix editor. Somewhat revamped, many fixes made, and more testing done. - Non-standard time signature handling errors. Note that we cannot support non-power-of-2 beat-widths well in the piano-rolls. - Main window. Fixed updating the PPQN when a file of different PPQN is loaded. - Cleaned up and upgraded the linking of recorded notes. - Made ghost notes in selection box official. Also made it apply to select-all and other selection operations. - Fixing and normalizing the functionality of the Pattern-Fix dialog. - Fixed timestamp setting in opening the event editor for empty pattern. - Some minor tweaking (field size, font) of various dialogs. ### Removed - Nothing. Maybe a few unnecessary macros. ## [0.99.16] - 2024-12-03 This release provides fixes to user issues and a ton of related fixes found while investigating the user issues. Some minor new features added. ### Changed - Documentation. Refined discussion about the about the Import/Export functionality, and some of the minor additional features. Some new screenshots still needed. - Build configuration. Added CONFIG\_DIR\_NAME and cleaned up configure.ac. Updated the Makefile sources. - Merged a fix from a pull request (issue #130) to correct the \*.desktop files. - Pattern editor. - Renamed [new-pattern-editor] to [pattern-editor] in the 'usr' file. Also added the "Apply to new only" option to Edit / Preferences / Display. - The Quantized Record button in the pattern editor now goes through this sequence when clicked: None, Tighten, Quantize, Notemap, None.... Also prettied-up the icons. - Enhancements to pattern-editor note copy/paste: - The paste-selection box now shows the selected notes, ghosted, for easier placement. - A right-click cancels a paste action in the pattern editor. - Undid a change from awhile back that broke the recording of fast-keyed notes on the same MIDI key. Still need to fix the occasional zero-length note. - Added provisional code to speed up recording. Grep for the macro SEQ66\_LINK\_NEWEST\_NOTE\_ON\_RECORD. Has potential issues, so it is undefined. - Color palette. Added access functions for "scale" (new) and "extra" colors in the palette, to improve the coloring (e.g. of the bars denoting notes in the scale of the pattern editor) in certain Qt style-sheet usages. - Event editor. Enhanced writing pattern-dump files from the event editor. ### Fixed - Issue #128. - Expanded recording. - Is now working. The expansion is continual, not waiting for a MIDI key to be struck. Also see the changes to the record buttons in the pattern editor. - Click-drag drawing of notes with "expand" set now works. - Issue #130. Merged a pull request for issue #130 to fix the \*.desktop files. - Issue #131. NSM interaction errors introduced in version 0.99.11, plus other related issues: - The NSM would show two clients: "qseq66" and "seq66" when adding only the "qseq66" client. - Saving via a remote NSM Save command or by the File / Save menu would not clear the modified flag. - Closing the session would not remove any external editor windows. - Automation. Some automation actions need to work whether the action is "on" or "toggle". Fixed these 'ctrl' actions: - Save session (under NSM) or the MIDI file. - Record style select. - Quit. - Pattern editor. - Fixed the display of tunes with various PPQNs such as 120 and 240. - Fixed zero-length notes caused by quantized recording. - Fixed the pattern editor so it reflects buss and channel settings made from the grid slot popup menu. - Main window. - Fixed the following scenario: Load a song; do File / New; make a pattern; then File / Save. It overwrites the previous song. The fix now brings up a prompt for the name of the new file. - The main window now reflects the current record-loop style and new-pattern option as read from the 'usr' file. ### Added - Main window. - Added "Clear events" to the grid slot popup menu. It keeps the pattern in the slot, but removes all of its MIDI events. - Added 120 PPQN to the list of supported PPQNs. - Build configuration. Added a "config directory" macro to configure.ac and seq-feature functions to access it. Differentiates between client and directory name. - Added monogreen.palette, monogreen.qss, and a down.png icon. Copy all to ~/.config/seq66 and modify the image url in the qss file to match your setting. Will work on a qrc file for a future release. - 'usr' configuration file. Currently must edit the file to change these values. - progress-bar-thickness. Changes the width of the progress bars if progress-bar-thick = true. It mainly affects the song editor. - gridlines-thick. Paints the normal song editor grid. If false, then more lines are thinner or dotted; useful if the foreground color is bright. It mainly affects the song editor. ## [0.99.15] - 2024-10-28 ### Added - Added edit fields and buttons for selecting a non-default browser or PDF viewer to the Preference's Session tab. ### Fixed - Some font handling in the various tabs and windows. - Cleaned up the configuration dialogs. - The file-selection dialog(s) when no file-extension is used. - An error in getting the name of the mutes file in the Preference's Session tab. ### Changed - Updating configure.ac to disable, and not allow enabling, of JACK, NSM, and liblo (OSC) if libraries not installed. - Progress made getting a build to work in MSYS2. Not yet ready. - Built on Windows 11 with a update to Qt 5.15.2 and mingw 8.10. ## [0.99.14] - 2024-08-14 ### Added - Added the use of the Esc key to close the external song editor window if not playing and not in adding mode. Similar to the fix made to the pattern editor for issue #117. - Added the elliptical progress-box option to Preferences / Display. - In the pattern editor, a tiny unlabelled button toggles the showing of note information when the mouse hovers over a note. - Added a note-tooltip option. Click the unlabelled button at the left of the horizontal scroll-bar. Then hovering over a note shows some note information. ### Fixed - In the zoomer class, rearranged members to avoid a warning (error) from g++ v. 14.2.1 about zoom being uninitialized in the constructor. - Issue #128 "Recording type for loop button not working". Fixes the Issue in both directions. - Issue #129 "Quantized Record Button problem" fixed by allowing the existing sequence recording status to be modified with an alteration. - Tightened up the handling of selecting a pattern's input buss. ### Changed - Standardized on "Overdub" for the pattern-window merge functionality. See issue #128. - Updated the PDF documentation based on issue fixes and other fixes. - Replaced the --session-tag option with --session. - Changed the sort order of MIDI events so that Note Offs get priority over Note Ons with the same time-stamp. Prevents playback errors in the tune judyblue.mid. Enabled via macro SEQ66\_PRIORITIZE\_NOTE\_OFF. ## [0.99.13] - 2024-08-05 This release is minor, a couple of lesser feature and some fixes. No release files are provided, no Windows installer. We've been working on another project, and it is time to move forward here. ### Added - Added 'progress-box-elliptical' to the 'usr' file. It is a 'usr' option to show an elliptical progress box. - New license files adhering to GitHub conventions. ### Fixed - Fixed the writing and byte-counting of the end-of track event. This adds 1 byte to the size of a Se66 MIDI file. - Fixed the counting of the size of empty note strings. Empty notes alway have a two-byte (short) note length of 0. - A segfault would occur when opening a pattern from a second song while the pattern editor from the first song was open. ### Changed - Changed the Tempo Track setting fields slightly in Edit / Preferences for clarity. - Added more robust handling of the Seq66 SeqSpec track. This is the final track in an Seq66 MIDI file, and is not counted. But some applications might count it as a track when saving the MIDI file. - In sequence::play() we now put SysEx events on the output bus. ### Removed - Removed LICENSE and the licensing text files it refers to. ## [0.99.12] - 2024-01-13 ### Added - Clang compiler support. - Provisional build process for FreeBSD. - Added an autogen.sh script to reconfigure without using bash. - Added 'rc' option to skip, recover, or abort from bad running status in MIDI files (trilogy.mid). ### Fixed - Errors and warnings revealed by Clang. - The reading, processing, writing, and display of SysEx and Meta events. Now handle unterminated SysEx messages (Dixie04.mid). - Issue with building the command-line "portmidi" version. - A one-character buffer overflow in portmidi.c. - Bugs in --option log=file. - Q button not working to set keep-queue status. - Queued-solo functionality improved. ### Changed - Upgraded the Event editor Dump output. ### Removed - Disabled the grid-solo option as redundant. Will repurpose later. ## [0.99.11] - 2023-12-04 ### Added - Added 8 more ui-palette entries, total of 32. Probably enough. - Added display of a pattern input bus (if present) in the grid slot. It is shown just before the pattern length at top right. - Added mute-group label ("MG") to main window. - Added a pre-made MIDI file to use with record-by-channel. - Added a record-by-buss feature. - Added a way to toggle recording of more than pattern at once. - Can now paste a pattern into a new or another loaded MIDI file. - Added optional paramater to the --priority option. - Added showing program changes in slot button. - Added showing text events in the data pane and all text events in the main Session tab. Fixed its Save Info button. - Implemented the "menu-mode" automation. It duplicates the function of the hide/show button, to toggle between hiding some of the main window controls and the main menu, and showing them. ### Fixed - Issue #123 "Would it be possible to have NEWS structured like a changelog" - Fixed errors setting style-sheet, palette, and mutes in Preferences / Session. Enhanced this tab to indicate when exit (as opposed to internal restart) is needed. - Fixing various playlist errors: - PPQN setting issue causing slow/fast playback. Cannot display 120 PPQN well, fix too intrusive. Converted contrib/midi/Carpet_of_the_Sun_karaoke_meta_text.mid from 120 to 192 PPQN. - Segfaults due to not stopping playback before loading the next song or basing calculations on missing values. - Fixing the massive botch of the Set Master tab. - More fixes in Mutes tab, including raising the modify flag. - Fixed app exiting unceremoniously if "quiet" is set. - Fixed minor issue in Song zoom with 1920 PPQN. - Fixed odd bug breaking MIDI-control-out (display). - Prevent long redundant start-up error messages. - Fixed solo feature. Should unsolo before starting another solo. - Fixed queue and keep-queue. - Fixed not saving record-by-channel. Fixed record-by-channel. - Prevent long redundant start-up error messages. - Fixed solo feature. Should unsolo before starting another solo. - Fixed queue and keep-queue. - Fixed not saving record-by-channel. Fixed record-by-channel. - Fixed not modifying the song when pattern measures is changed. - Fixed breakage of stopping song play at the end of song. - Fixed bug in event-editor initialization. - Fixed not applying note-length setting to step-edit. ### Changed - Moved style-sheet options from 'usr' to the 'rc' file. Upgrade is automatic. - Improved copy/paste for screen-sets in the same way. - When loading a MIDI file, the file dialog defaults to the last-used directory. Fixes made to this feature. - Focus is now set immediately to the seqroll and perfroll. - Replaced some verbose() checks with investigate() checks to cut down on console output. In the CLI, --verbose now shows playlist actions on the console and prevents daemonization and logging to a file. - Replaced the --inspect option with --session-tag to allow easy changing to another setup specified in sessions.rc. Also added the SEQ66_SESSION_TAG environment variable. ### Removed - No removals. ## [0.99.10] - 2023-10-25 ### Added - Added drag-and-drop of one MIDI file onto the Live grid. - Added the export of most project configuration files to another directory. - Multiple tempo events can be drawn in a line in the data pane. They can be dragged up and down in the data pane. - If configured for double-click, can now open or create a pattern by double-clicking in the song editor(s). - Added a new "grid mode" to allow toggling mutes by clicking in the Live Grid. The default group-selection keystroke is "\_". - Added feature to scroll automatically in time and note value to show the first notes in a pattern. - Added more UI palette items for more non-Qt color control. Modified the inverse color palette slightly. Added checkmark for the active pattern color in its popup menu. ### Fixed - Issue #117 Option to close pattern windows with esc key. Must be enabled via a 'usr' option first. - Issue #118 Make virtual ports ports enabled by default. - Issue #119 "Quantized Record Active does not work" fixed. Note-mapping also fixed with this issue. - Fixed an egregious error in drawing notes in drum mode. - Fixed error in moving notes at PPQN != 192. - Restored missing table header in Mutes tab. ### Changed - Improved modification detection in the data pane. - Many improvements and fixes to the Mutes tab. - Made 'rc' file-name handling more robust. - Tweaked the main time display to work better with high PPQN. - The live-grid record-mode and alteration statuses are now applied when the pattern editor is opened. The "Record toggle" popup menu entry and MIDI control also support this functionality. - Tightened the file-name/activity handling in Session preferences. ### Removed ## [0.99.9] - 2023-09-24 - Added an "Input Bus Routing" feature, where each pattern can be set to receive events from a given input buss. Selectable from the grid-slot popup menu. - Fixed nasty segfault opening new file while Editor tab open. - Fixed bug: port-mapping Remap and Restart did not work due to timing. - Fixed bug in detecting Note-related messages. - Fixed error in "quiet" startup that would cause immediate exit. - Related to issue #115: Added ability to select a line in the data pane and grab a handle to change its value. - Refactored and extended zoom support, added it to event and data panes. - Adding more seqroll keystokes (and HTML help). Enabled Esc to exit paint mode if not playing. - Added live-note mapping (needs testing!), refactoring set-record code. - Implemented automation for BBT/HMS toggling, FF/Rewind, Undo/Redo, Play-set Copy/Paste, and Record Toggle. - Added a build-time option to add a show/hide button in the main window to allow making the window to take up much less space. - Added HTML help files to data/share/doc/info, other documentation upgrades. ## [0.99.8] - 2023-08-27 - Issue #112: A new pattern now displays in the MIDI controller. - Issue #114: Adding display of shortcut keys to tool tips. - Added a Pattern tab to Edit / Preferences for new-pattern settings and jitter/randomization. - Added automation for the main window Loop L/R button. - Fixed seqroll drawing errors introduced in adding time-sig support. - Fixed incomplete data-pane refresh in scrolling with arrow-keys. - Fixed not setting up SIGINT, which prevented a proper shutdown. - Fixed a couple corrupted data/midi/FM/*.mid files. - Changing playlist setting enables Session Restart button. - Removed coloring of record-style and -mode buttons. Added coloring of event-editor "Store" button to denote saving is needed. - Refactoring quantization alterations for future upgrades. Added an option to jitter the notes in the seqroll. - Enforced that configuration files are stored in the "home" directory. - The usual raft of humiliating bug-fixes. A small sample: Updating the event list when recording stops; fixing record button in pattern editor; fixing note-selection refresh; and about a dozen more. ## [0.99.7] - 2023-07-19 - Issue #110 follow-ons: Cannot save tempo (BPM) in Windows when changed from main window. Caused by mixing a long and size_t, which messed up in the Windows build. - Issue #111 follow-ons: - Fixed initial time-signature drawing in data pane. - Fixed errors in inserting a time-signature. Added a pulse calculator that iterates through time-signatures. - Fixed an important port-translation bug in output port-mapping. - Recent Files is disabled if there are none. - Revamped the Playlist tab, as it was confusing and very buggy. - Added Windows key-mapping to fix processing "native virtual" keys, such as the arrow keys. Also fixes issue #102. - Added auto-play and auto-advance to play-lists. - Fixed bug in rcsettings::make_config_filespec(). ## [0.99.6] - 2023-07-01 - Issue #3 follow-ons: - Added a qscrollslave to allow QScrollArea to allow the pattern editor panes to remain in sync with the seqroll when using the hjkl, arrow, and page keys. - Issue #110 follow-ons: - Addition of Start menu entries for Windows. - Fixed access to the tutorial and manual. Refactored access to manual and tutorial for robustness. - Added data/readme files and doc/tutorial files accidentally left out of NSIS installer. - Fixed the saving of modified tempo changes. - Fixed event::is_desired(), which affected changing note velocities in the pattern editor's data pane. Improved velocity-change undo. - Fixed an error preventing changing the "background" pattern. - Fixed issues with port-mapping and the Windows MIDI Mapper. - Issue: Building 32-bit (Windows XP) version on 64-bit Windows. On 64-bit Windows, this seems to require building a 32-bit version of the Qt toolset. Ugh. - Issue #111: Adding support, as much as possible, for editing, storing, and displaying time signature in the pattern and event editors. - The first time-signature in a pattern becomes the main time signature of the pattern. (Also stored as a c_timesig SeqSpec). - The data pane shows a time-signature as a simple fraction. - Changing the time signature if at time 0 is automatic. - Time signatures at later times can be logged by setting the current time with a click in the top half of the time line, changing the beats and beat width, then clicking a time-sig log button. - Time-signatures with a beat width that are not a power of 2 do not add an event, but are saved as the c_timesig SeqSpec value. - Provisional feature: we properly draw the piano roll, time line, event pane vertical lines as time-signature changes. Currently compiled in via a macro (see INSTALL). - Fixed event filtering in the event (qstriggereditor) pane. - Fixed time-signature editing in the event editor, and the B:B:T to pulses calculation. - Enhanced port-mapping to prompt the user about issues and allow for an immediate remap-and-restart. A ton of fixes! - Added 'o' keystroke to seqroll to toggle recording ('r' already used to randomize notes). - Added a "quiet" option to not show startup message prompts. - Enhanced the Edit / Preferences dialog. - At first start, a log-file is now automatically created. If it gets larger than a megabyte, then it is deleted to start over. - Fixed bug writing pattern-dump files from event editor. - Improved modification detection and display in the pattern editor. - Added the pattern port number to the song summary output. - Updated alsa.m4 to avoid obsolete AC_TRY_COMPILE warning. Old version stored in contrib/scripts. ## [0.99.5] - 2023-05-20 - Greatly enhanced the event editor tab and the events that can be view and modified. - Made port-mapping the default. At first startup the map exactly matches the existing ports; the user can edit this setup in the 'rc' file or the Preferences dialog. - Eliminated "missing ctrl" message at first startup. - Fixed port ID setting in midibus, and adding output flag in midi_alsa_info. - Issue #110 Windows: Fixed compiler errors and added scripting to build NSIS-based install without leaving Windows, if desired. - Internal refactoring to regularize handling of the session/config directory between Linux and Windows. - Fixed portmidi bugs in Linux and Windows, enhanced device naming. - Showing disabled/unavailable MIDI devices as grayed in various dropdowns. - Rearranged the Seq66 man pages more sensibly. ## [0.99.4] - 2023-04-30 - Issue #3: The scroll wheel is enabled in the piano rolls (only). - Issue #48: For a new NSM session configuration, disable "JACK" port auto-connect. - Issue #108. Fixed trying to remove Event tab after deleting it. - Issue #109. Fixed the application of channels to the various export operations. - Fixed minor-but-annoying bug in reporting trying map a "null" buss. - Improved state-appearance of Stop, Pause, and Play buttons. - Fixed issue opening a non-standard-length pattern in its window. - Fixed note events not getting linked after recording. - Fixed drawing of wrap-around notes with linear gradient, and fixed handling of note wrap-around when set to false. - Fixed refresh of Mute and Session tabs when loading a MIDI file. - Added seqmenu entry for toggling recording of a pattern. - Added the ability to use the first Text message as "song info". - Seq66 now prevents opening the event editor if recording is in progress. Cannot update event editor live with new events. ## [0.99.3] - 2023-04-19 - Issue #107. The basic fix is made. Still need to rehabilitate the Expand-pattern functionality. - Issue #40. Improved NSM handling: - Seq66 detects nsmd as the parent process early in startup. - Close ("X") button disabled and hidden. Xcfe4 "close window" action works, though, as does response to a SIGTERM from nsmd. - Handling "config" subdirectory improved. - The --nsm option is now for debugging only, simulating running under NSM. - Automation fixes: - Fixed processing grid keystrokes twice. - Renamed record-mode and grid-model control labels. - Fixed accident disabling of grid-mode controls. - Making MIDI control-out handling more reasonable. - Added some LaunchPad Mini macros. - Added reading/writing/displaying Meta text events such as Text and Cue Point. - Fixed broken "recent-files" features (by forcing 'rc' save). - Improvements made to playlist handling. It wasn't displaying the BPM of the next tune. Still have issue with the BPM spinbox not causing a file modification. Editing the BPM works. - Improvements and important fixes to mute-group and its MIDI control still in progress. - Fixed the daemonization and log-file functionality. - Revisited the recmutex implementation. - Weird error where ALSA not found! We now avoid a crash, but qseq66 currently exits with only console messages. ## [0.99.2] - 2023-03-04 - Issue #103. Some improvements to pattern loop-count. - Pull request #106. User phuel added checkmarks for active buss and channel in grid-slot menu. - Fixed background sequence not displaying when running with linear-gradient brush. - Fixes to brushes and making the linear gradient (notes and triggers) a default run-time option. See the 'palette' file. - Other minor fixes and documentation updates, including the manual, as per issue #104. ## [0.99.1] - 2022-11-27 - Issue #44. Revisited to fix related additional issues. Can now toggle a pattern's song record in perfnames. Record button: Ctrl disables snap, Shift enables record at playback start. Still minor issues. - Issue #93. Revisited to fix related open pattern-editor issues. - Issue #100. Partly mitigated. Added a custom ringbuffer for MIDI message objects to replace JACK's ringbuffer. Adapted work from the ttymidi.c module. Also added configuration to calculate the sample offset. - Various fixes: - Fixed partial breakage of pattern-merge function. - Fixed odd breakage of ALSA playback in release mode. - Fixed Stop button when another Master has started playback. - Shift-click on Stop button rewinds JACK transport when running as JACK Slave. - Display of some JACK server settings in Edit / Preferences. - Fixed handling of Ctrl vs non-Ctrl zoom keys in perfroll. - The pernames panel now matches the layout of a grid button better. Other cosmetic changes. - Event-dump now prompts for a text-file name. - Added linear-gradient compile-time option (seq_features.h) for displaying notes and triggers. Enabled by default, edit to disable. Does not seem to add noticeable overhead. ## [0.99.0] - 2022-09-03 - Issue #44. Record live sequence changes functionality beefed up to handle recording without snapping. - Issue #54. Updated the ax_have_qt_min.m4 file to detect qmake-qt5, etc. - Issue #78 revisited. Pattern-box sizes would become 0 and the progress boxes disappear. Now the 'usr' show option is boolean ("pattern-boxes-shown", default = true), and the sizes are kept within reasonable limits. Also added a "--locale" option so that the user can, for example, set the Seq66 global locale to "en_US.UTF-8". - Issue #82 allows buttons and fields to expand better. Fixed for main window and the song & pattern editors only. - Issue #89 fixed. The MIDI control display not quite reflecting the status of each pattern, especially during queuing. - Issue #90 improvements. Save was not always enabled. Surely some issues remain. Also, in some cases there is no way to "unmodify". - Issue #93 fixed. The window of a deleted pattern now closes. - Issue #94. Long song in song editor could not be scrolled to the right. Added more padding and a button to expand the grid when when desired. For the pattern editor, the workaround is to increase the "length" in the pattern editor. - Issue #97. Investigate/resolve differences from Seq24. - Pattern editor fixes. - Added paste box when pasting notes, an oversight from the Seq24 reboot. - Added Ctrl-Left/Right to move the progress bar in the pattern editor. (Left/Right scrolls the piano roll.) - Issue #98. Feature requests (metronome and background recording). - Added an initial metronome facility and 'rc' configuration. - Metronome count-in added. - Background automatic recording added. - Various fixes: - Muted pattern slots show a short progress bar, to aid in the timing of queuing. - Improved the handling of the MIDI 'ctrl' file and control states upon a restart. - Tightened up pattern arming/disarming processing. - Implemented left/right arrow keys to move the selected trigger in the song editor. Ctrl moves multiple triggers. Moving a trigger past END moves END. - Fixed error in painting tempo events in triggers (perfroll). - Improved keystroke movement of "L"/"R" markers in song and pattern editor time bars. - The global time signature is now applied to new patterns. - Added a try-catch to showing the locale. - Ctrl-Z removes all mouse-painted notes at once (like Seq24). Single-note removal is macroed out. - Error in parsing "--option sets=RxC" fixed. - Improved the handling of grid font sizes re window size. - Fixed the response to incoming MIDI Continue. - PPQN != 192 : handle snapping when adding notes; bugs in perfroll and perftime. - Implement clear-events and double-length grid modes. ## [0.98.10] - 2022-07-18 - Revisited issue #83, improved GUI editing of control/display automation. - Fixes for issue #87: segfault due to mute-group on larger set-sizes, inability to modify some usr options in Edit / Preferences, and related bugs found during these fixes. Made performer the owner of mutegroups. - Fixes for issue #88: 4/16 pattern not shown/played properly until opened in editor. - Fixed minor issue with port-naming, port-lists. - Many tweaks to documentation, vim files, midibytes.... ## [0.98.9.1] - 2022-06-01 - Added files needed for ./configure. - Documentation and tutorial updates. ## [0.98.9] - 2022-05-29 - Fixed nasty issue #85 which was recreating the slot buttons, and in a different thread, leading to a seqfault. - Fixed issue: Preference / MIDI Input port check did not change Apply state. - Fixed mute/record/thru state display between live grid and a pattern editor. - Updated/cleaned the tutorial. ## [0.98.8] - 2022-05-23 - Fixed issue #84, now able to build Qt and CLI version in one pass. Also fixed out-of-source builds and removed function call tracing. Streamlined the bootstrap script; it was always "configuring". - Changed the Apply button from Edit / Preferences to a Restart button. Further tightening of change detection. - Moved midibyte/midiboolean functions from strfunctions to midibytes and mutegroup. Much header-file cleanup. Do a --full-clean! - Added Ctrl-Home and Ctrl-End support to the song editor. - Added an initial HTML tutorial and commands to access it. Add shellexecute module to replace the ill-performing QDesktopServices. - Important configure.ac/Makefile.am upgrades. ## [0.98.7] - 2022-05-08 - Fixed issue #80 where some MIDI controls were getting recorded. - Fixed issue #81, adding to code catching std::invalid_argument. - Fixed issue #83 where parsing 'rc' port lines failed with a port name having a trailing space. Also fixed short-port-name detection. - Added a "Pattern Fix" dialog to allow a whole pattern to be shifted, quantized, and changed in length all at once. Useful for fixing a badly played pattern or scaling the duration. - Fixed the issue of leftover child windows of qseqeditframe64. - Removed odd beat-widths from time signature dropdowns. Can still enter odd values manually, but unsupported by MIDI format. - Refactored drop-down lists to use the settings module. - Tightend string_to_xxx() functions and replaced the std::stoxxx() functions to avoid throwing exceptions. - Trying to get rolls, time, data, and event panes to line up no matter what the Qt theme is. Difficult. - Tightening the setting/clearing of performer modification re sequence changes to reduce unnecessary prompts to save, flag modification via the asterisk change marker, and enable/disable the File / Save option in the correct way. - Tightened the saving of WRK and MIDI files. - Major refactoring to replace seq::pointers with seq::refs in many places. - Updated the bootstrap script to make 'release' the default, and fixed the portmidi automake build process for Linux. ## [0.98.6] - 2022-04-09 - Revisited issue #41 to make sure "Quit" is "Hide" under NSM. Also fixed issue with the "newtune.midi" default name. - Fixed a minor bug involving setting input aliases. - Fixed bug with setting last-used-directory to "". - Fixed handling of Live/Song mode in performer and in 'rc' saving. - Fixed stupid seqedit bug for selecting beats/bar. - Disabling/enabling JACK input and output on the fly in Preferences works, as long as they had been enabled at startup. - Added Preferences options to toggle the double-click edit feature and select the Live/Song/Auto mode. Added Apply/Reset buttons. - Removed long-unused "rtmidi callback" code and other disabled code. - Got user-manual screenshots up-to-date. - Upgraded and fixed file-name splitting and rebuilding. - Modify flag now set when painting notes in seqroll. - Improved appearance of Loop/Record/Quantize buttons in main grid. - Improved modification detection of sequences and main window. - Refactored global SeqSpecs for better robustness, fewer surprises. - Added a file-changed check before restarting the application. - More code/macro cleanup. ## [0.98.5] - 2022-03-07 - Added locking for the event-drawing loops to prevent segfaults. Active only when recording; prevents iterator invalidation. - Added an underrun indicator to the main window. - The client name ("seq66") is no longer shown, nor saved in the 'rc' file. Easier to use multiple Seq66's with the --client-name option. - Updating Dia diagram, finding and fixing issues and dead code. - Added global internal check for both portmaps being active. - Fixed bad slot connection for import_midi_into_session(). - Bad command-line options now cause an exit. This includes JACK options when Seq66 is built without JACK support. - Improve the coherence of JACK-less builds. Some related NSM tweaks. - Adding JACK access functions for the future. Improved internal port initialization code. Removed some non-useful rtmidi code. - Adding the ability to disable/enable input and output on the fly in the MIDI Clock and Input tabs. Still in progress, though. ## [0.98.4] - 2022-02-12 - Fixed bug in recording-type selector in seqedit. Added a one-shot reset option. - Fixed some metadata problems as per issue #75. - Fixed an issue with the H:M:S display being changed by changing the beat-width, a bug going back to Seq24's JACK transport support. - MIDI API refactoring for the future; detecting JACK port registration. ## [0.98.3.1] - 2022-01-24 - Revisited issue #45. Doh! - Cleaned up the Makefiles, fixed minor issues. ## [0.98.3] - 2022-01-23 - Fixed issue #76. Fixed broken MIDI Start handling. Added setting the tempo via a tempo event. Still thinking about MMC. - Fixed old bug in showing note events; they were filtered by the pattern-configured channel! - Tweaks to style-sheet handling. - Fixed misuses of msgprintf(). Other minor bug fixes. - In a new NSM session, do not load the most-recent MIDI file, even if specified in the imported configuration. Also, no longer do an automatic import of the home configuration to the NSM configuration. Instead, use the "File / Import / Import Project" menu entry. See the user manual for how this works. - Added a "File / Import / Import Playlist" command. See the user manual for how this works. - Made virtual port names in ALSA more consistent. Also create the ports (in JACK, too) even if disabled. Still experimental. - Added Preferences item for the BPM Precision setting. - Added contrib/code/ametro.c to provide ALSA test for MIDI clocking. - Added GNU -finstrument-functions support, but too problematic. ## [0.98.2] - 2022-01-01 - Fixed issue #74, where -1 for "no buss-override" was being converted to 0. - Added detection of missing system ports when mapping ports. - Song duration label is now a button to select time versus measures. - Removed useless flags for loading keystroke and MIDI controls. - Avoid applying mute-group 0 if song has triggers and song-mode is 'auto'. - Fixes to log-file handling. - Added "Blank" for disabling keystrokes in the 'ctrl' file. ## [0.98.1] - 2021-12-26 - Work on creating an NSIS installer for a 64-bit Windows build. - Fixed a stupid segfault bug with the --help option. Doh! - Changed some extended automation keys (for grid modes). - Minor fixes: (1) Set to set 0 when opening a MIDI file; (2) Expand a pattern when merging a longer one into it; (3) fixed pattern access in sets > 0; (4) fixed an error in string tokenization. - Fixes for port-mapping and naming; if present, the user sets the status of the port-map, which is copied to the matching port. - Fixes in song-editor set-names display. - Removed unused module qskeymaps. ## [0.98.0] - 2021-12-11 - Fixed issue #41 "Hide Seq66 on closing window" via a "visibility" automation command and by fixing the response to "hide/show" messages from NSM. Note that the NSM API permits the "Quit" command to exit the application. Also see the comments at issue #64 "NSM: UI show up after restarting the app". - Fixed issue #73 "Compile error because of jack_get_version_string" by detecting the presence of this function in configure.ac. - Truly fixed high CPU usage in Windows version of the condition variable "wait". Replaced pthread implementation with C++'s condition_variable. - Added "MIDI macros" to the 'ctrl' file. Can send SysEx or other messages from a drop-down list; automatic startup and exit messages. More to come. - Added a feature to extract JACK port aliases and show them to the user so that the system port can be associated with a named device (e.g. "Launchpad-Mini"). - Added api_sysex() overrides, at last. - Added Preference items for MIDI control I/O. - Improved the display of the MIDI file-name in title and live grid. - Improving grid-mode functionality, adding more automation controls, in progress. ## [0.97.3] - 2021-11-26 - Added pattern-recording indicator to live-grid slots. - Replace useless "Record" automation command with a "Loop Mode" command to move between normal loop mode in the Live grid to various record-toggling modes: Overdub (merge), Overwrite, Expand, and One-shot. - Also refactored the quantize control to move through states of - normal, quantize, and tighten. - Refactored MIDI control for possible future usage of the D1 event value. - Fixes to external live grid handling. - Fixed the display of loop status changed via MIDI control. - Fixed and updated the Windows build. Mitigated high CPU usage when not (!) playing; fixed a bug in microsleep() for Windows. ## [0.97.2.1] - 2021-11-12 - Fixed odd breakage of loop-control hot-keys. Doh! ## [0.97.2] - 2021-11-11 - Issue #57: Increased width of option fields for wider fonts. - Issue #58: Indicate if NSM is running in Preferences / Sessions and disable other session selections at that point. - Issue #59: Spelling error(s) fixed. - Issue #60: Added 'rc' option to disable JACK port auto connect in normal mode. Also present in Preferences / JACK. - Issue #61: Rearranged console output to show the app name, in color. Also turns off color if redirected to a file. - Issue #63: Initial work on rotating numbering of patterns, sets, and mutes, so that numbers vary faster by column than by row. - Issue #64: Fix a bug in optional GUI, testing with Agordejo (NSM). - Added textfix.qss to make disabled text easier to read in themes. - Improved the saving/restoring of Edit / Preferences. - Added option "Bold Grid Slots" to make the progress bar and progress border thick, and the slot font bold. - Added the ability to reload the configuration via a button press that restarts the application. - More streamlining of configuration writing. - No longer show the grid slot progress bar moving in muted tracks. - Mute-group names now stored in the 'mutes' file. Also able to edit them in the Mutes tab. Also fixed botched mutes-in-MIDI-file handling. - Removed dead-code from the event-editor frame. - Changing behavior of external live frames for more flexibility. ## [0.97.1] - 2021-10-20 - Fixed a bad bug in displaying Notes in the data & event panels in the pattern editor, caused by premature ... optimization. per issue #61. - Added working tempo-track code to Edit / Preferences / MIDI Clock. - Added exponential ramping of Event Panel events to the pattern LFO dialog. - Added the ability to move selected Event Panel events using the Left/Right arrow keys. - Improved the handling of sets and external live grids. - Updated documentation extensively. Optimized some images. - Stopped bootstrap --full-clean from removing Makefile.in files. ## [0.97.0] - 2021-10-12 - Added limited vertical zoom to the song editor (performance roll). Still has vertical scroll issues. - Added more Preferences settings, enabled some that were not yet implemented. Upgraded the handling of the configuration files. By default, the 'rc' file is always saved, in case ports change. - Added option "wrap-around" (for notes) to the 'usr' file. - Added option "lock-main-window" to the 'usr' file to prevent resizing the window. - Added code to show unlinked note events on the seqroll, the "u" key to remove them, and the "=" key to relink them. - Added more build information to --version and Help / Build Info. - Fixed drawing slot-button borders, drawn with no pattern color now. - Got auto-scaling of the slot button font working. - Implemented the Help / About links as per issue #21. - Added OpenSUSE INSTALL notes from the sivecj/Seq66 fork. - Improved the display and editing of tempo events, especially in the pattern editor. Also tightened handling of status values. - Can now copy/paste a pattern from one MIDI file to another. - Fixed minor bug in Import dialog handling. - Refactoring for SMF 0 reading and export. - Minor fix to flag changed tune (an asterisk). ## [0.96.3] - 2021-09-10 - Added ability to modify Note On and Note Off at the same time in the event editor. Fixed and updated event::get_rank(). - Fixed output port-map issue with lookup of "FLUID Synth". Removed the random ID number; search the port name via containment, not equality. - Usage of the JACK Session API restored, though "deprecated". Removed all traces of LASH support. Added a configuration tab for allowing session management via JACK or NSM. - Added an automation control for "save session". ## [0.96.2] - 2021-08-15 - Fixed issue #55, where the MIDI control channel was being stripped when using JACK. Many other channel-handling improvements. - Fixed a related issue: null (status = 0) control events were sent. - Fixed a bug in "portmidi" MIDI input handling where only the last device events would be processed. - Improved pause/continue support with JACK transport. - Fixed 'rc' allow-click-edit feature. - Made the buss-override settings act more consistently. - Fixed the drawing of preview keys in virtual keyboard. - Fixed setting the modify flag when opening existing pattern. - Added settings interface for the "set mode" of playback. - Added selection of notes and event by channel. ## [0.96.1] - 2021-08-01 - Restored drag and drop of patterns in the Live frame. - Added the ability to copy all patterns in one set to another set. - Fixed song-start-mode handling when "auto" is specified. - Added "Control" and "Program" (GM) drop-downs in the event editor. - Now display full path names in the recent file list to distinguish files better. An 'rc' config item. - Added a few configuration options to Edit / Preferences. - Added 'usr' options to change the vertical offset of notes in the progress-box of each pattern button, and to provide default labeling of the piano keys. - Tweaked the drawing of notes and drum notes. - More work on set-handling. - Fixed panic-button disabling Launchpad display output. However, if pressed before exit, the lights remain on and there is a slit delay in exiting the session (in ALSA; works fine in JACK). ## [0.96.0] - 2021-07-17 - Adding code to control the visibility of the main window; added a "Visibility" MIDI control option, and support for the "optional-gui" flag of the Non Session Manager. - Fixed harmonic transposition settings. - Fixed bug that drew fingerprint at top of button; and another bug that disabled showing the pattern if fingerprinting was disabled. - Can now read MIDI files that are readable, but not writable, and disable File / Save as needed. - Added hjkl arrow keys to seqroll and perfroll. Why? We have a Microsoft Arc keyboard with all the arrows on one small button. - Fixed the Mixolydian mode scale. Oops. - The pattern editor time, roll, data, and event frames line up. - Fixed error reading mute-groups; small fixes to configuration files. - Work on fitting pattern-editor in tab. - Work on mitigating weird hang in QApplication destructor. ## [0.95.1] - 2021-07-06 - Added 'auto' option to song-start-mode setting. - Added missing harmonic-transposition option to the pattern editor. - Fixed issues with some of the file-dialogs. - Fixed issues with editing keystrokes being passed from the tabbed editors to the main window (and the live frame). - Improved arrow-key handling in seqroll. - Removed PortMidi Java configuration parsing code. - Important fixes to NSM sessions. - Added 'one-shot' option the the 'usr' file record-style setting. - Improved support for making the user-interface smaller; 4x4 sets. - Added the "enigmatic" scale. - Fixes to handling of musical key, scale, and background pattern. - Finalized the scale-finder code (Ctrl-K in the pattern editor piano roll.) - Updated libtool support, now seq66cli is under 200K in size. - Removed QtWidget directory from qclocklayout #includes for issue #54. Also added error-message and about to the Qt m4 file. ## [0.95.0] - 2021-06-19 - Updated almost all settings to use "name = variable" convention to improve readability. This will change the format of config-files slightly upon exit. - Fixes made to the sample Launchpad Mini 'ctrl' files. Also added a "Quit" command, especially useful in a headless run. - Added 8 "Alt" MIDI Control Out values to support special cases not covered by the concrete set of control-out values. - Major cleanup and neatening of header files. - Minor bugs and regressions fixed along the way. - Loop-edit key will create a new sequence if slot is empty. - Patterns empty of playable events no longer show unnecessary progress bars. - The estimated duration of a tune is shown in the Song tab, based on pattern lengths and song triggers. ## [0.94.0] - 2021-05-28 - Major improvements to the stability of MIDI control of playlists. - Added Phrygian scale thanks to user WinkoErades. - Update the 'rc' file for a simpler specification of JACK MIDI and JACK transport. - Removed the old-style live frame; use themes and style-sheets to create the old look if you like it. - Removed a lot of debugging material of no use now. - Fixed (mostly) a weird bug causing the application to hang on exit when set as JACK Master. Pretty damn weird. - Improving support for non-default PPQNs. - Patterns can now be copied and merged into a new pattern to combine (for example) separate percussion tracks. - Fixed error in playlist::verify() adding an empty list. - Improving the handling of headless seq66cli; added a "quit" automation function for MIDI-only use. - Fixed some issues with reading Cakewalk WRK files. ## [0.93.2] - 2021-05-14 - Changed the mute-group format inside the MIDI file to use a much less disk space (about 3K less). Can be set in the 'rc' file to write the old format. - Minor tweaks to the transposed-trigger functionality. Can be set in the 'rc' file to write the old format. - Removed the code for the essentially unused Seqtool application. - Removed the now unnecessary old-style qseqeditframe. - Add 'usr' tweaks options to change the size of the live-grid progress boxes, or eliminate them completely. - Refactored keymaps for better progress on issue #47, using native key-codes. Added a spreadsheet listing the supported names of the keys. Laid the groundwork for a 'keys' mapping file. - Removed excess spurious events when Seq66 shuts down under JACK. - Implemented the control-toggling of "follow JACK transport". - Fixed a bug in the pattern editor that created unnecessary empty screen-sets. - Further fixes and flexibility for handling mute-groups. - Fixed the installation of libseq66 and libsession library header files in "make install" to preserve their base subdirectories. (The other libraries currently do not use base subdirectories.) ## [0.93.1] - 2021-05-03 - Work in progress on issue #47; added a keyboard-layout option to the 'ctrl' file to disable auto-shift and tweak the internal key-map for some keyboards. Added an "AZERTY" 'ctrl' file to the installation. - Improved group-learn key control. - Fixed issue #49, mute-group issues, plus a bug in saving mutes to the MIDI file. Added a couple more flags to the 'mutes' file. - Fixed issue #50, made the slot text color the same as the label color, and provided a "secret" default color that will cause the text to match the theme. This can be overridden by a palette file. - Fixed issue #51 (show-stopper!), where playback with JACK transport enable was extremely erratic on some platforms. - Add clearing of the performer "play-set" for "File / New" to prevent the previous song from being playable. :-D - Activate usage of (larger) seqedit frame in the "Edit" tab via a 'usr' option. Adapted the size to better fit, but the user still needs to increase vertical dimension slightly to see the bottom buttons. Will eventually eliminate the old version of the tabbed Edit frame. - Fixed a bug in drawing notes as a new one is recorded; needed to "verify-and-link". - Added a loop-count for live playback of a pattern. 0 means normal infinite looping; 1 means "one-shot"; and higher numbers will work. Stored with the pattern in the MIDI file. ## [0.93.0] - 2021-04-16 - Added a transpose value to song editor triggers to support shifting patterns automatically during Song play. Added an 'rc' option to save MIDI files using the original triggers. - Improved the song editor vertical scrollbar, it adjusts to the number of actual sets. - Added the L/R marker feature to the external pattern editor, and Song mode is no longer required to use it. This makes it easier to explore a long pattern for extracting to a short pattern loop. - Added more perfroll snap options, including one to force Seq24 behavior. - Make the perf-names now also show the pattern color. They highlight as the mouse moves over the pattern rows, useful for long songs. - Fixed playback of ultra-long patterns due to not sleeping in the output loop (forgot to convert milliseconds to microseconds). - Fixed vertical zoom in the pattern editor. - Fixed a bad bug in writing Meta and SeqSpec data-lengths greater than 127. - Enhanced the event editor to work with channel-less tracks. - Added channel and bus menus to the grid-button popup menu. ## [0.92.2] - 2021-03-27 - Added a Qt "style-sheet" configuration it to the 'usr' file. It can be used to alter the appearance of the application beyond what a palette can do. A sample 'qss' file is provided. - Fixed PPQN modification, added user-interface and 'usr' configuration to change the default PPQN from 192. - Fixed many issues with changing the time signature. - Fixed creation of new configuration files. - Fixed port-mapping for MIDI output, control, and status display. - Removed the external set-master; use the set-master tab. - Tightened meta-events and set-handling. - More fixes to Song recording; added a Snap button for it. - Fixed the rendering of the beat indicator and pattern fonts. - Updated the man pages and the documentation. ## [0.92.1] - 2021-03-07 - Fixed issue #42 by adding scrollbars to I/O lists in Preferences when there are many ports on the system; also increased port limits to 48 ports. - Fixed issues with iterating through MIDI events in the user-interfaces. - Big fixes to Song recording. More to do? - Added an experimental one-shot auto-step step-edit feature for recording (for example) drumbeats from MIDI devices. - Fixed bug in parsing Fluidsynth port names. - Improved handling of MIDI I/O control of Seq66. Added more automation-display events, more testing. - Updated documentation, including a ODF spreadsheet for the MIDI control configuration of the Launchpad Mini. ## [0.92.0] - 2021-02-13 - Fixed issue #34: "seq66 does not follow jack_transport tempo changes" - Fixed issues with applying 'usr' buss and instrument names to the pattern-editor menus. - Fixing serious issues with the event editor. Now deletes both linked notes. - Added mute-group-out to show mutegroups on Launchpad Mini buttons. - Tightened up mute-group handling, configuration file handling, play-list handling, and MIDI display device handling. - Stream-line the format of the 'ctrl' file by removing columns for the values of "enabled" and "channel". Will detect older formats and fix them. - A PDF user manual is now provided in the doc directory, installed to /usr/local/share/doc/seq66-0.92 with "make install". ## [0.91.6] - unknown - Massively updated the Mutes tab. - More documentation. - More fixes to the song editor. - Added more files to the creation setup at the first run of Seq66. ## [0.91.5] - 2021-01-10 - Added vertical zoom to the pattern editor (V keys and buttons). - Added more control over the coloring of notes. - Still improving the port-mapper feature. - Added quotes to file-paths in the 'rc' configuration file. - Many fixes to seqedit, perfedit. Way too many to mention them all. Changed the 4/4 and length selections to be editable. - Getting serious about rewriting the user manual in this project. ## [0.91.4] - unknown - Improved port naming and provide an option for short or long port names. - Improved safety in NSM sessions. - Major refactoring the color handling. Colors have changed!!! ## [0.91.3] - 2020-12-16 - Added check to not apply last mute-group if in Song mode. - Provisional support for playing multiple sets at once and for auto-arming the selected set when loaded. - Added a configurable number for virtual MIDI input/output ports. - Provides an option to for multiple-set playback and auto-arming of a newly-selected set. - Fixed bug in string-to-int conversion uncovered by automatic mute-group restore. - Refactoring port naming and I/O lists. - Minor play-list fixes. ## [0.91.2] - 2020-11-30 - Fix developer bug causing playlists to not load properly. - Fix crash when 'rc' file specifies empty mutes and ctrl files. ## [0.91.1] - 2020-11-28 - More fixes for mute-group (mutes) handling. - Ability to save the last-active mute-group for restoring on startup. - Added a button to toggle insert mode in pattern and song editors. - Robustness enhancement to NSM support. ## [0.91.0] - 2020-11-21 - Non Session Manager support essentially complete. The refactoring to do this was massive. - All too many bug fixes and minor improvements. - Added --copy-dt-needed-entries to qseq66 Makefile.am to fix linkage errors that cropped up in debug builds. - Got the CLI version building, needs a lot of testing. - Playlist editing from the user-interface much improved. ## [0.90.6] - 2020-09-26 - Fixed issue #19 where recording notes caused a segfault. ## [0.90.5] - 2020-08-15 - More refactoring to improve Windows builds and fix deadlocks and playback on a default Windows 10 install. - Added code to implement Song/Live and JACK Transport key toggles. - MIDI control improved for control and for status display by devices like the Launchpad Mini, using new "ctrl" files. - Fixes: - Fixed a bug using millitime() instead of microtime(). - Issue #13, data and event/trigger panes not reflecting event changes. Change handling improved in general with more performer callbacks. - The data pane was some pixels off in the tabbed edit-frame. - Fix a lot of issue with using sets and an 8x8 Seq66 grid. - Improved error-handling and reporting. ## [0.90.4] - unknown - Some refactoring to improve Windows builds. ## [0.90.3] - 2020-07-05 - Added signal handling (e.g. for nsm-proxy) from user falkTX. Used in the CLI and Qt versions to trigger file save and application exit. - Added code to define the engine used and the exact "uname a-" of the operating system on which the build was done, for the --version option. - Fixed issue #5 to save/restore note-on resumes and get them working in Live mode and Song mode. - Fixed issues with perfoll and grid-button rendering, synch between button and seqedit mute status, growing notes in the seqroll, and more. - Added writing of blank [midi-control-out] section. - Fixed flickering when muting/unmuting patterns via keystrokes. ## [0.90.2] - unknown - Lots of code cleanup. - Added truncation of BPM to precision in "usr" file. - Fixed at bug in handling running status; backported to Seq64. ## [0.90.1] - 2019-10-20 - A raft of bug fixes and user-interface improvements. - Many tweaks to try to make the performer and the user-interface even faster. ## [0.90.0] - 2019-09-10 - The application and documentation are now (barely) suitable for public consumption, after residing on a server (a Toshiba laptop) at home for months. # vim: sw=4 ts=4 wm=15 et ft=markdown ================================================ FILE: README.md ================================================ # README for Seq66 0.99.24 2026-04-22 __Seq66__ MIDI sequencer/live-looper with a hardware-sampler grid interface; pattern banks, triggers, and playlists for song management; scale and chord aware piano-roll; song layout for creative composition; control/status via MIDI automation, and mute-groups to enable/disable sets of patterns. Tools for live performance and for composing great-sounding MIDI tracks. Supports the Non/New Session Manager; can run headless and on a small computer like the Pi. It does not support audio samples, just MIDI. __Seq66__ A major refactoring of Sequencer64/Kepler34/Seq24 with modern C++ and new features. Linux and Windows users can build this application from source code. See the extensive INSTALL file. Includes a comprehensive PDF user-manual. The current development-in-progress branch is "wip". The release includes an installer for the 64-bit Windows version of Seq66. Initial work has been done on getting Seq66 to build and run in FreeBSD using the Clang compiler. A C++17 capable-compiler is needed. See NEWS for updates and RELNOTES for the latest highlights. The figure below shows Seq66 with modified palette and a style-sheet in force. Otherwise the application uses the current Qt theme. ![Alt text](doc/latex/images/main-window/main-windows-perstfic.png?raw=true "Seq66") # Major Features ## User interface * Qt 5 or Qt 6 (cross-platform). Loop-button gird. Qt style-sheet support. * Colorable pattern slots; the color palette can be saved and modified. * Drag-and-drop a MIDI file onto the main grid to load it. * Tabs and external windows for patterns, sets, mute-groups, song layout, event-editing, play-lists, and session information. * Low-frequency oscillator (LFO) to modify continuous controller and velocity values. * A "fixer" for expansion/compression/alignment of note patterns. * Horizontal and vertical zoom in the pattern and song editors. * Extremely resizable. * A headless/daemon version can be built to use with a MIDI grid controller. ## Configuration files * Supports configuration files: '.rc', '.usr', '.ctrl', '.mutes', '.playlist', '.drums' ('.notemap'), '.palette', and Qt '.qss'. * Separates MIDI control and mute-group setting into their own files. * Unified keystroke and MIDI controls in the '.ctrl' file; defines MIDI controls for automation/display of Seq66 status in grid controllers (e.g. LaunchPad). Sample '.ctrl' files provided for Launchpad Mini. ## Non/New Session Manager * Support for NSM/New Session Manager, RaySession, Agordejo.... * Handles starting, stopping, hiding, and session saving. * Displays details about the session. ## Multiple Builds * ALSA/JACK: `qseq66` using an rtmidi-based library * Command-line/headless: `seq66cli` * PortMidi: `qpseq66` * Windows: `qpseq66.exe` ## More Features * Supports configurable PPQN from 32 to 19200 (default is 192). * Transposable triggers to re-use patterns more comprehensively. * Song import/export from/to stock MIDI (SMF 0 or 1). * Highly configurable MIDI-based metronome. * Management of scales, keys, and chords. * Improved non-U.S. keyboard support. * Many demonstration and test MIDI files. ## Internal * More consistent use of modern C++, auto, and lambda functions. * Additional performer callbacks to reduce polling. * A ton of clean-up and refactoring. Seq66 has a user-interface based on Kepler34 and the Seq66 *rtmidi* (Linux) and *portmidi* (Windows) engines. MIDI devices are detected, inaccessible devices are ignored, with playback (e.g. to the Windows wavetable synth). It is built easily via *GNU Autotools*, *Qt Creator* or *qmake*, using *MingW*. See the INSTALL file for build-from-source instructions for Linux or Windows, and using a conventional source tarball. Support sites (still in progress): * https://ahlstromcj.github.io/ * https://github.com/ahlstromcj/ahlstromcj.github.io/wiki ## Recent Changes * See the NEWS file. // vim: sw=4 ts=4 wm=2 et ft=markdown ================================================ FILE: RELNOTES ================================================ Release Notes for Seq66 v. 0.99.24 2026-05-01 "Mitt Igation" This release contains some minor feature updates and fixes. - Pattern editor. - Added the display of a drum-note's name when in drum mode and hovering near a drum-note to modify it. - Added the ability to show hex numbers for the standard MIDI controller events. - Added two buttons, one that toggles showing certain numbers between hexadecimal and decimal format, and the other toggles showing level numbers to reduce clutter. - Event editor. - Added the ability to show hex numbers for standard MIDI controller events. - Backed autoconf version from 2.72 to 2.71 re issue #145. - Fixed some more QMouseEvent x()/y() deprecations found by Clang. - qseq66.rc. - Change auto-rc-save to default to false. - Added a flag so that the 'rc' file can be saved at first run (no existing configuration files) without setting auto-rc-save. - Main window. - Fixed a random segfault when the "Remap & Restart" pops up if a pattern editor is open, the user clicks OK, selects a different tune is selected. - A similar segfault with the "Unavailable port" message. The fix is to stop the qseqeditframe64 timer before the close() call. - A similar segfault with the "LFO" dialog. Fixed by no longer resetting the MIDI events when the close event occurs. - Pattern editor. - Mitigated issue #144 (painted notes misplaced in time) for normal notes and drum notes by adjusting the snap point. - Fixed more Qt6 deprecations. QtCreator to use Qt 6 namespaces. - Added a Windows installer for this release. A user request! - Documentation updated. - Moved the LICENSE files to "licenses" sub-directory. See NEWS for many more details. # vim: sw=4 ts=4 wm=15 et ft=rc ================================================ FILE: ROADMAP.md ================================================ # ROADMAP for a Possible Seq66 v. 2 Chris Ahlstrom 2022-05-11 to 2022-08-24 This file is simply some thoughts about the future of __Seq66__ and a version 2... if we decide to keep updating this project until Ahlstrom croaks. :-D Also see the bottom of the TODO file. # Topics ## Qt Upgrade It should be straightforward to upgrade from Qt 5 to Qt 6. We will see if it is better to leverage The Qt5Compat library at first. ## Configuration files Currently, we see no need to change the format of the Seq66 configuration files greatly. The INI format is simpler and easier to read and edit than XML, and covers our needs well. ## Non Session Manager Will continue to be supported. Will remove the unused nsmserver module, which was mostly meant for "just in cases" already covered by other NSM-derived projects. ## Builds * We might transition to Cmake, based on its cross-platform support. Or not, as there are also a lot of complaints about it, even lately. It might be nice to stick with Qmake, but it's been deprecated by Qt. There should be no need for both the build systems we use at present: Autotools and Qmake. * The libraries libseq66, sessions, seq\_portmidi, and seq\_rtmidi will be split into separate projects for better re-use. * A new namespace would be in order: seqx or somesuch. ## Engines * Remove the Mac and Windows support from our derivative "portmidi" library. * Move the support to our derivative "rtmidi" library. ## Executables The following executables will still be supported, though with new names and features. * ALSA/JACK: `qseq66` * Command-line/headless: `seq66cli` * Windows: `qpseq66.exe` ## Code Improvements * Untangle some of the header-file dependencies. * Create independent libraries for strings, files, midi, etc. * Add comprehensive Qt string translation. ## Additional Features * Limited support for audio patterns. Clips that could compress or expand to fit the BPM and measures and be included in the MIDI file in a new SeqSpec section. However, editing would be left to far better audio applications. Perhaps best supported as a soundfont? * A comprehensive and improved system for undo/redo support for configuration and for all song changes. * Use "rtmidi" for Windows/Mac; removed "portmidi". ## Requested Features / Suggestions These items are requests. Not sure if all are worth doing in version 2. * Replace GNU Autotools. Candidates: meson and cmake. Meson seems preferable at this time. * Use a true RtMidi-compatible library; dump the portmidi implementation. * Add Pipewire support. * Full OSC support for automation. * Break libraries into Git submodules. * MIDI clips launched by a note. * MIDINAM. See issue #1 and the TODO file for this request. * Ableton Live transport support. See issue #16 and the TODO file for this request. * Support RELNOTEs.md better, as per issue #24 and the TODO. * Beef up recording live sequence changes as per issue #44 and the TODO. * Weak JACK. // vim: sw=4 ts=4 wm=2 et ft=markdown ================================================ FILE: Seq66cli/Makefile.am ================================================ #****************************************************************************** # Makefile.am (seq66cli) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library seq66cli application # \author Chris Ahlstrom # \date 2017-04-07 # \update 2023-12-13 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the seq66cli C/C++ # application. # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #****************************************************************************** # CLEANFILES #------------------------------------------------------------------------------ CLEANFILES = *.gc* #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ # EXTRA_DIST = dl_leaks.supp make-tests README #****************************************************************************** # Items from configure.ac #------------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ # # Not needed, yet, since we won't be installing the app for awhile. # #------------------------------------------------------------------------------ prefix = @prefix@ datadir = @datadir@ datarootdir = @datarootdir@ seq66includedir = @seq66includedir@ seq66libdir = @seq66libdir@ #****************************************************************************** # localedir #------------------------------------------------------------------------------ # # 'localedir' is the normal system directory for installed localization # files. # #------------------------------------------------------------------------------ localedir = $(datadir)/locale DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ libseq66dir = $(builddir)/libseq66/src/.libs libsessionsdir = $(builddir)/libsessions/src/.libs libseq_rtmididir = $(builddir)/seq_rtmidi/src/.libs libseq_portmididir = $(builddir)/seq_portmidi/src/.libs #****************************************************************************** # AM_CPPFLAGS [formerly "INCLUDES"] #------------------------------------------------------------------------------ # # 'AM_CPPFLAGS' is the set of directories needed to access all of the # library header files used in this project. # # -I$(top_srcdir)/seq_gtkmm2/include \ # #------------------------------------------------------------------------------ if BUILD_RTMIDI MIDIHDR = $(top_srcdir)/seq_rtmidi/include MIDILIB = -L$(libseq_rtmididir) -lseq_rtmidi MIDIDEP = $(libseq_rtmididir)/libseq_rtmidi.la MIDIAPP = seq66cli else MIDIHDR = $(top_srcdir)/seq_portmidi/include MIDILIB = -L$(libseq_portmididir) -lseq_portmidi MIDIDEP = $(libseq_portmididir)/libseq_portmidi.la MIDIAPP = seq66clip endif AM_CXXFLAGS = \ -I$(top_srcdir)/libseq66/include \ -I$(top_srcdir)/libsessions/include \ -I$(MIDIHDR) \ $(JACK_CFLAGS) \ $(LIBLO_CFLAGS) \ $(NSM_CFLAGS) #****************************************************************************** # libmath #------------------------------------------------------------------------------ # # One day, we got errors about sqrt() undefined, which we fixed by # adding -lm. Then one day we got errors about various items in # sys/stat.h being multiply-defined, and it turned out to be the -lm. # # We make it (an empty) define for how to handle it more easily. # #------------------------------------------------------------------------------ libmath = -lm #**************************************************************************** # Project-specific library files #---------------------------------------------------------------------------- # # These files are the ones built in the source tree, not the installed # ones. # # Sometimes one has to change the order of the libraries in this list. # # $(libmath) # -L$(libseq_gtkmm2dir) -lseq_gtkmm2 \ # #---------------------------------------------------------------------------- libraries = \ -L$(libsessionsdir) -lsessions \ -L$(libseq66dir) -lseq66 \ -L$(libsessionsdir) -lsessions \ $(MIDILIB) #**************************************************************************** # Project-specific dependency files #---------------------------------------------------------------------------- # # Provdies the specific list of dependencies, to assure that the make # detects all changes, if they are available. # # $(libseq_gtkmm2dir)/libseq_gtkmm2.la \ #---------------------------------------------------------------------------- dependencies = \ $(MIDIDEP) \ $(libsessionsdir)/libsessions.la \ $(libseq66dir)/libseq66.la #****************************************************************************** # The programs to build #------------------------------------------------------------------------------ bin_PROGRAMS = $(MIDIAPP) #****************************************************************************** # seq66cli # # seq66cli_LDFLAGS = -Wl,--copy-dt-needed-entries # #---------------------------------------------------------------------------- seq66cli_SOURCES = seq66rtcli.cpp seq66cli_DEPENDENCIES = $(NSM_DEPS) $(dependencies) seq66cli_LDFLAGS = $(LINKER_FLAGS_ADD) seq66cli_LDADD = $(NSM_LIBS) $(libraries) $(LIBLO_LIBS) $(ALSA_LIBS) $(JACK_LIBS) $(AM_LDFLAGS) seq66clip_SOURCES = seq66rtcli.cpp seq66clip_DEPENDENCIES = $(NSM_DEPS) $(dependencies) seq66clip_LDFLAGS = $(LINKER_FLAGS_ADD) seq66clip_LDADD = $(NSM_LIBS) $(libraries) $(LIBLO_LIBS) $(ALSA_LIBS) $(JACK_LIBS) $(AM_LDFLAGS) #****************************************************************************** # distclean #------------------------------------------------------------------------------ distclean-local: -rm -rf $(testsubdir) #****************************************************************************** # Makefile.am (seq66cli) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: Seq66cli/Seq66cli.pro ================================================ #****************************************************************************** # Seq66cli.pro (Seq66cli) #------------------------------------------------------------------------------ ## # \file Seq66cli.pro # \library seq66qt5 application # \author Chris Ahlstrom # \date 2018-04-08 # \update 2024-04-29 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Created by and for Qt Creator This file was created for editing the project # sources only. You may attempt to use it for building too, by modifying this # file here. # # Important: This project file is designed only for Qt 5 (and above?). # #------------------------------------------------------------------------------ message($$_PRO_FILE_PWD_) QT += core gui widgets TARGET = seq66cli TEMPLATE += app CONFIG += static qtc_runnable c++14 # These are needed to set up seq66_platform_macros: CONFIG(debug, debug|release) { DEFINES += DEBUG } else { DEFINES += NDEBUG } SOURCES += seq66cli.cpp INCLUDEPATH = \ ../include/qt/portmidi \ ../libseq66/include \ ../libsessions/include \ ../seq_portmidi/include \ ../seq_qt5/include # Sometimes some midifile and rect member functions cannot be found at link # time, and this is worse with static linkage of our internal libraries. # So we add the linker options --start-group and --end-group, as discussed # in this interesting article. # # https://eli.thegreenplace.net/2013/07/09/library-order-in-static-linking # win32:CONFIG(release, debug|release) { LIBS += \ -Wl,--start-group \ -L$$OUT_PWD/../libseq66/release -lseq66 \ -L$$OUT_PWD/../seq_portmidi/release -lseq_portmidi \ -L$$OUT_PWD/../seq_qt5/release -lseq_qt5 \ -Wl,--end-group } else:win32:CONFIG(debug, debug|release) { LIBS += \ -Wl,--start-group \ -L$$OUT_PWD/../libseq66/debug -lseq66 \ -L$$OUT_PWD/../seq_portmidi/debug -lseq_portmidi \ -L$$OUT_PWD/../seq_qt5/debug -lseq_qt5 \ -Wl,--end-group } else:unix { LIBS += \ -Wl,--start-group \ -L$$OUT_PWD/../libseq66 -lseq66 \ -L$$OUT_PWD/../seq_portmidi -lseq_portmidi \ -L$$OUT_PWD/../seq_qt5 -lseq_qt5 \ -Wl,--end-group } DEPENDPATH += \ $$PWD/../libseq66 \ $$PWD/../seq_portmidi \ $$PWD/../seq_qt5 # Works in Linux with "CONFIG += debug". unix { isEmpty(PREFIX) { PREFIX = /usr/local } isEmpty(BINDIR) { BINDIR = $${PREFIX}/bin } isEmpty(DATADIR) { DATADIR = $${PREFIX}/share } #DEFINES += DATADIR=\"$${DATADIR}\" # make install INSTALLS += target # desktop icon appdata icon_scalable mimeinfo mimetypes mimetypes_scalable PRE_TARGETDEPS += \ $$OUT_PWD/../libseq66/libseq66.a \ $$OUT_PWD/../seq_portmidi/libseq_portmidi.a \ $$OUT_PWD/../seq_qt5/libseq_qt5.a } # May consider adding: /usr/include/lash-1.0 and -llash unix:!macx: LIBS += \ -lasound \ -ljack \ -lrt windows: LIBS += -lwinmm #****************************************************************************** # Seq66cli.pro (Seq66cli) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: Seq66cli/seq66rtcli.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file seq66rtcli.cpp * * This module declares/defines the main module for the application. * * \library seq66rtcli application * \author Seq24 team; modifications by Chris Ahlstrom * \date 2020-02-09 * \updates 2023-10-31 * \license GNU GPLv2 or above * * This application is seq66 without a GUI, control must be done via MIDI. * There are a few kinds of life-cycles for this application: * * -# On the command-line, "--option daemonize" is provided (the * option "--option log=filename.log" can also be provided.). * This causes the daemonize (and log) options to be immediately * written to the default 'usr' file (seq66cli.usr). Then * the application simply exits. * -# No daemonization option provided on the command-line. * - If step 1 has not been done, then the 'usr' daemonization * is false, and the application runs as a normal console * application. To keep the user from seeing console output, * the application can be associated with a window manager * application icon, menu entry, or hot-key. * - If the 'usr' file specifies daemonization, then the * application forks itself and the child runs in the background * with no terminal. */ #include "cfg/cmdlineopts.hpp" /* command-line functions */ #include "cfg/settings.hpp" /* seq66::usr() and seq66::rc() */ #include "os/daemonize.hpp" /* seq66::daemonize() */ #include "play/performer.hpp" /* seq66::perform, the main object */ #include "sessions/clinsmanager.hpp" /* an seq66::smanager for CLI use */ /** * The standard C/C++ entry point to this application. This first thing * this function does is scan the argument vector and strip off all * parameters known to GTK+. * * The next thing is to set the various settings defaults, and then try to * read the "user" and "rc" configuration files, in that order. There are * currently no options to change the names of those files. If we add * that code, we'll move the parsing code to where the configuration * file-names are changed from the command-line. * * The last thing is to override any other settings via the command-line * parameters. * * If --verbose is use on the command line, then this application will * ignore daemonization and the log file, and show everything to the console. * Useful in seeing a lot of stuff, live. * * Daemon support: * * Apart from the usual daemon stuff, we need to handle the following issues: * * -# Detecting the need for daemonizing and doing it before all the * normal configuration work is performed. * - Read "--option daemonize" from the command line. * - Scan the 'usr' file for just the daemonize option. * -# Read the configuratiopn options. * -# Loading the initial MIDI file. Does this filename need to be * grabbed before forking? No, local variables are passed to the new * process. * -# Setting the current-working directory. Should it be grabbed from * the 'rc' file? * * We moved the daemonize() call to up here so that the configuration files * will be reread. Note that currently the 'usr' option is not read. We * will need to make a special function to do that. * * \param argc * The number of command-line parameters, including the name of the * application as parameter 0. * * \param argv * The array of pointers to the command-line parameters. * * \return * Returns EXIT_SUCCESS (0) or EXIT_FAILURE, depending on the status of * the run. */ int main (int argc, char * argv []) { int exit_status = EXIT_SUCCESS; /* EXIT_FAILURE might occur */ bool ishelp = seq66::cmdlineopts::help_check(argc, argv); bool isverbose = seq66::cmdlineopts::verbose_check(argc, argv); #if defined SEQ66_PLATFORM_LINUX mode_t usermask = 0; /* used in un-daemonization */ #endif #if defined USE_NEW_CODE seq66::smanager::app_info(argv[0], true); #else /* * No longer used: seq66::usr().app_is_headless(true); */ seq66::set_app_cli(true); /* used in smanager */ seq66::set_app_name("seq66cli"); /* also done in smanager!! */ #endif if (! ishelp) { (void) seq66::cmdlineopts::parse_o_options(argc, argv); if (! seq66::usr().save_daemonize()) { #if defined SEQ66_PLATFORM_LINUX bool startdaemon = false; std::string logfile; std::string appname = seq66::seq_app_name(); seq66::rc().set_config_files(appname); if (! isverbose) { (void) seq66::cmdlineopts::parse_daemonization ( startdaemon, logfile /* two side-effects */ ); if (startdaemon) { int flags = d_flags_seq66cli; /* see daemonize.hpp */ seq66::set_app_type("daemon"); seq66::set_app_name("seq66daemon"); warnprint("Forking to background..."); daemonization rc = seq66::daemonize ( usermask, appname, flags, "." ); if (rc == daemonization::parent) { seq66::session_message("Parent succeeds, exits..."); exit(EXIT_SUCCESS); } else if (rc == daemonization::failure) { seq66::session_message("Parent fails, exits..."); exit(EXIT_FAILURE); } seq66::session_message("Child continues normally..."); } } #endif if (isverbose) { /* * If not falsified here, done in smanager::main_settings(). */ seq66::usr().option_use_logfile(false); } } } seq66::clinsmanager sm; bool success = sm.create(argc, argv); if (success) { if (seq66::usr().save_daemonize()) { if (seq66::cmdlineopts::write_usr_file()) { seq66::session_message ( "Daemon setup: saved 'usr' settings, exiting..." ); } } else { std::string msg; bool ok = sm.run(); exit_status = ok ? EXIT_SUCCESS : EXIT_FAILURE ; (void) sm.close_session(msg, ok); seq66::session_message(msg); } } else exit_status = EXIT_FAILURE; #if defined SEQ66_PLATFORM_LINUX if (seq66::usr().option_daemonize() && ! seq66::usr().save_daemonize()) { seq66::undaemonize(usermask); if (exit_status == EXIT_FAILURE) seq66::session_message("Child fails..."); else seq66::session_message("Child normal exit..."); } #endif return exit_status; } /* * seq66rtcli.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: Seq66qt5/Makefile.am ================================================ #****************************************************************************** # Makefile.am (seq66qt5) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library seq66qt5 application # \author Chris Ahlstrom # \date 2017-09-05 # \update 2023-12-15 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the seq66qt5 C/C++ # application. # # To do: Account for usage of CLANG/LLVM in the linker flags as noted below. # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #****************************************************************************** # CLEANFILES #------------------------------------------------------------------------------ CLEANFILES = *.gc* #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ # EXTRA_DIST = dl_leaks.supp make-tests README #****************************************************************************** # Items from configure.ac #------------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ # LINKER_FLAGS = @ LINKER_FLAGS @ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ # # Not needed, yet, since we won't be installing the app for awhile. # #------------------------------------------------------------------------------ prefix = @prefix@ datadir = @datadir@ datarootdir = @datarootdir@ seq66includedir = @seq66includedir@ seq66libdir = @seq66libdir@ #****************************************************************************** # localedir #------------------------------------------------------------------------------ # # 'localedir' is the normal system directory for installed localization # files. # #------------------------------------------------------------------------------ localedir = $(datadir)/locale DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ libseq66dir = $(builddir)/libseq66/src/.libs libsessionsdir = $(builddir)/libsessions/src/.libs libseq_rtmididir = $(builddir)/seq_rtmidi/src/.libs libseq_portmididir = $(builddir)/seq_portmidi/src/.libs libseq_qt5dir = $(builddir)/seq_qt5/src/.libs #****************************************************************************** # AM_CPPFLAGS [formerly "INCLUDES"] #------------------------------------------------------------------------------ # # 'AM_CPPFLAGS' is the set of directories needed to access all of the # library header files used in this project. # # Note that we include both the portmidi and rtmidi include directories. # There are no conflicts in names of header files, and we don't want to # try to test SEQ66_RTMIDI_SUPPORT and SEQ66_PORTMIDI_SUPPORT until we have # to. # # -I$(top_srcdir)/libsessions/include # #------------------------------------------------------------------------------ if BUILD_RTMIDI MIDIHDR = $(top_srcdir)/seq_rtmidi/include MIDILIB = -L$(libseq_rtmididir) -lseq_rtmidi MIDIDEP = $(libseq_rtmididir)/libseq_rtmidi.la MIDIAPP = qseq66 else MIDIHDR = $(top_srcdir)/seq_portmidi/include MIDILIB = -L$(libseq_portmididir) -lseq_portmidi MIDIDEP = $(libseq_portmididir)/libseq_portmidi.la MIDIAPP = qpseq66 endif AM_CXXFLAGS = \ -I$(top_srcdir)/libseq66/include \ -I$(top_srcdir)/libsessions/include \ -I$(MIDIHDR) \ -I$(top_srcdir)/seq_qt5/include \ $(QT_CXXFLAGS) \ $(JACK_CFLAGS) \ $(LIBLO_CFLAGS) \ $(NSM_CFLAGS) #****************************************************************************** # libmath #------------------------------------------------------------------------------ # # One day, we got errors about sqrt() undefined, which we fixed by # adding -lm. Then one day we got errors about various items in # sys/stat.h being multiply-defined, and it turned out to be the -lm. # # We make it (an empty) define for how to handle it more easily. # #------------------------------------------------------------------------------ libmath = -lm #**************************************************************************** # Project-specific library files #---------------------------------------------------------------------------- # # These files are the ones built in the source tree, not the installed # ones. # # Sometimes one has to change the order of the libraries in this list. # # $(libmath) # # The libsessions library is handled via the NSM_LIBS macro, since some users # might not want to build in Non Session Manager support. # #---------------------------------------------------------------------------- libraries = \ -L$(libseq_qt5dir) -lseq_qt5 \ -L$(libsessionsdir) -lsessions \ -L$(libseq66dir) -lseq66 \ -L$(libsessionsdir) -lsessions \ $(MIDILIB) #**************************************************************************** # Project-specific dependency files #---------------------------------------------------------------------------- # # Provides the specific list of dependencies, to assure that the make # detects all changes, if they are available. # #---------------------------------------------------------------------------- dependencies = \ $(MIDIDEP) \ $(libseq_qt5dir)/libseq_qt5.la \ $(libsessionsdir)/libsessions.la \ $(libseq66dir)/libseq66.la #****************************************************************************** # The programs to build #------------------------------------------------------------------------------ bin_PROGRAMS = $(MIDIAPP) #****************************************************************************** # seq66 #---------------------------------------------------------------------------- # # Added a LDFLAGS line to avoid a "DSO missing" error on some systems when # building in debug mode: # # As a workaround it's possible to switch back to the more permissive # view of what symbols are available by using the option # # -Wl,--copy-dt-needed-entries # # See this article: # # https://flameeyes.blog/2010/11/26/ # it-s-not-all-gold-that-shines-why-underlinking-is-a-bad-thing/ # # Clang-16 and LLVM: # # For the build to work (with libpng), we had to use # # qseq66_LDFLAGS = # qpseq66_LDFLAGS = # #---------------------------------------------------------------------------- qseq66_SOURCES = seq66qt5.cpp qseq66_DEPENDENCIES = $(NSM_DEPS) $(dependencies) qseq66_LDFLAGS = $(LINKER_FLAGS) qseq66_LDADD = $(NSM_LIBS) $(libraries) $(LIBLO_LIBS) $(QT_LIBS) $(ALSA_LIBS) $(JACK_LIBS) $(AM_LDFLAGS) qpseq66_SOURCES = seq66qt5.cpp qpseq66_DEPENDENCIES = $(NSM_DEPS) $(dependencies) qpseq66_LDFLAGS = $(LINKER_FLAGS) qpseq66_LDADD = $(NSM_LIBS) $(libraries) $(LIBLO_LIBS) $(QT_LIBS) $(ALSA_LIBS) $(JACK_LIBS) $(AM_LDFLAGS) #****************************************************************************** # distclean #------------------------------------------------------------------------------ distclean-local: -rm -rf $(testsubdir) #****************************************************************************** # Makefile.am (seq66qt5) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: Seq66qt5/Seq66qt5.pro ================================================ #****************************************************************************** # Seq66qt5.pro (Seq66qt5) #------------------------------------------------------------------------------ ## # \file Seq66qt5.pro # \library seq66qt5 application # \author Chris Ahlstrom # \date 2018-04-08 # \update 2024-08-07 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Created by and for Qt Creator This file was created for editing the project # sources only. You may attempt to use it for building too, by modifying this # file here. # # Important: This project file is designed only for Qt 5 (and above?). # #------------------------------------------------------------------------------ message($$_PRO_FILE_PWD_) QT += widgets gui core TEMPLATE += app CONFIG += static qtc_runnable c++14 # These are needed to set up seq66_platform_macros: CONFIG(debug, debug|release) { DEFINES += DEBUG } else { DEFINES += NDEBUG DEFINES += QT_NO_DEBUG } contains (CONFIG, rtmidi) { TARGET = qseq66 MIDILIB = rtmidi DEFINES += "SEQ66_MIDILIB=rtmidi" DEFINES += "SEQ66_RTMIDI_SUPPORT=1" } else { TARGET = qpseq66 MIDILIB = portmidi DEFINES += "SEQ66_MIDILIB=portmidi" DEFINES += "SEQ66_PORTMIDI_SUPPORT=1" DEFINES += "MINGW_HAS_SECURE_API=1" } windows { DEFINES += "UNICODE" DEFINES += "_UNICODE" } win32 { DEFINES += "WIN32" DEFINES += "QT_NEEDS_QMAIN" } message($${DEFINES}) SOURCES += seq66qt5.cpp INCLUDEPATH = \ ../include/qt/$${MIDILIB} \ ../libseq66/include \ ../libsessions/include \ ../seq_$${MIDILIB}/include \ ../seq_qt5/include # Sometimes some midifile and rect member functions cannot be found at link # time, and this is worse with static linkage of our internal libraries. # So we add the linker options --start-group and --end-group, as discussed # in this interesting article. # # https://eli.thegreenplace.net/2013/07/09/library-order-in-static-linking # win32:CONFIG(release, debug|release) { LIBS += \ -Wl,--start-group \ -L$$OUT_PWD/../libseq66/release -lseq66 \ -L$$OUT_PWD/../seq_$${MIDILIB}/release -lseq_$${MIDILIB} \ -L$$OUT_PWD/../seq_qt5/release -lseq_qt5 \ -lmingw32 -lshell32 \ -Wl,--end-group } else:windows:CONFIG(debug, debug|release) { LIBS += \ -Wl,--start-group \ -L$$OUT_PWD/../libseq66/debug -lseq66 \ -L$$OUT_PWD/../seq_$${MIDILIB}/debug -lseq_$${MIDILIB} \ -L$$OUT_PWD/../seq_qt5/debug -lseq_qt5 \ -Wl,--end-group } else:unix { contains (CONFIG, rtmidi) { LIBS += \ -Wl,--start-group \ -L$$OUT_PWD/../libsessions -lsessions \ -L$$OUT_PWD/../libseq66 -lseq66 \ -L$$OUT_PWD/../seq_$${MIDILIB} -lseq_$${MIDILIB} \ -L$$OUT_PWD/../seq_qt5 -lseq_qt5 \ -Wl,--end-group } else { LIBS += \ -Wl,--start-group \ -L$$OUT_PWD/../libseq66 -lseq66 \ -L$$OUT_PWD/../seq_$${MIDILIB} -lseq_$${MIDILIB} \ -L$$OUT_PWD/../seq_qt5 -lseq_qt5 \ -Wl,--end-group } } contains (CONFIG, rtmidi) { DEPENDPATH += $$PWD/../libsessions } DEPENDPATH += \ $$PWD/../libseq66 \ $$PWD/../seq_$${MIDILIB} \ $$PWD/../seq_qt5 # Works in Linux with "CONFIG += debug". contains (CONFIG, rtmidi) { PRE_TARGETDEPS += $$OUT_PWD/../libsessions/libsessions.a \ } unix { isEmpty(PREFIX) { PREFIX = /usr/local } isEmpty(BINDIR) { BINDIR = $${PREFIX}/bin } isEmpty(DATADIR) { DATADIR = $${PREFIX}/share } #DEFINES += DATADIR=\"$${DATADIR}\" # make install # target.path = /usr/local/bin/ # target.files += xxxxx target.path = $${BINDIR} target.CONFIG = no_check_exist executable # desktop icon appdata icon_scalable mimeinfo mimetypes mimetypes_scalable ## TO DO. ## desktop.path = $${DATADIR}/applications desktop.files += ../data/share/applications/seq66.desktop # icon.path = $${DATADIR}/icons/hicolor/32x32/apps # icon.files += images/$${NAME}.png # icon_scalable.path = $${DATADIR}/icons/hicolor/scalable/apps # icon_scalable.files += images/$${NAME}.svg # appdata.path = $${DATADIR}/metainfo # appdata.files += appdata/$${NAME}.appdata.xml # mimeinfo.path = $${DATADIR}/mime/packages # mimeinfo.files += mimetypes/$${NAME}.xml # mimetypes.path = $${DATADIR}/icons/hicolor/32x32/mimetypes # mimetypes.files += \ # mimetypes/application-x-$${NAME}-session.png \ # mimetypes/application-x-$${NAME}-template.png \ # mimetypes/application-x-$${NAME}-archive.png # mimetypes_scalable.path = $${DATADIR}/icons/hicolor/scalable/mimetypes # mimetypes_scalable.files += \ # mimetypes/application-x-$${NAME}-session.svg \ # mimetypes/application-x-$${NAME}-template.svg \ # mimetypes/application-x-$${NAME}-archive.svg PRE_TARGETDEPS += \ $$OUT_PWD/../libseq66/libseq66.a \ $$OUT_PWD/../seq_$${MIDILIB}/libseq_$${MIDILIB}.a \ $$OUT_PWD/../seq_qt5/libseq_qt5.a } INSTALLS += target # Note the inclusion of liblo (OSC library). # We may consider adding: /usr/include/lash-1.0 and -llash contains (CONFIG, rtmidi) { LIBS += -lasound -ljack -llo -lrt } else { unix:!macx: LIBS += -lasound -ljack -lrt } windows: LIBS += -lwinmm # Install an application icon for Windows to use. win32:RC_ICONS += ../resources/icons/route66.ico \ ../resources/icons/route66-16x16.ico \ ../resources/icons/route66-32x32.ico \ ../resources/icons/route66-48x48.ico \ ../resources/icons/route66-64x64.ico \ ../resources/icons/route66-256x256.ico windows:RC_ICONS += ../resources/icons/route66.ico \ ../resources/icons/route66-16x16.ico \ ../resources/icons/route66-32x32.ico \ ../resources/icons/route66-48x48.ico \ ../resources/icons/route66-64x64.ico \ ../resources/icons/route66-256x256.ico #****************************************************************************** # Seq66qt5.pro (Seq66qt5) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: Seq66qt5/seq66qt5.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file seq66qt5.cpp * * This module declares/defines the main module for the JACK/ALSA "qt5" * implementation of this application. * * \library seq66qt5 application * \author Chris Ahlstrom * \date 2017-09-05 * \updates 2026-04-15 * \license GNU GPLv2 or above * * This is an attempt to change from the hoary old (or, as H.P. Lovecraft * would style it, "eldritch") gtkmm-2.4 implementation of Seq66. */ #include /* QApplication etc. */ #include "seq66_features.hpp" /* seq66::set_app_path() */ #include "qt5nsmanager.hpp" /* an seq66::smanager for Qt 5 */ #include "os/daemonize.hpp" /* seq66::session_close(), etc. */ #include "os/timing.hpp" /* seq66::millisleep() */ /* * More macros. */ #undef SEQ66_LOCALE_SUPPORT /* using --locale instead */ #undef SEQ66_TRANSLATOR_SUPPORT /* we're not ready for this at all */ #undef USE_RING_BUFFER_TEST /* enable to check ring_buffer ops */ /* * Without the QCoreApplication::setSetuidAllow() call, the application dumps * core if setuid root in the install. However, This yields this message many * many times when run as setuid root; "QCommonStyle::drawComplexControl: * Control 1 not handled" Not sure what is up with that. The result is * that the user interface is flat and cramped! In any case, Qt warns not * to use setuid root because Qt has "a large attack surface" :-D. */ #undef SEQ66_SETUID_SUPPORT /* Qt aborts with setuid otherwise */ #if defined USE_RING_BUFFER_TEST /* requires a debug build */ #include "util/ring_buffer.hpp" #endif /** * Let's give time for the existing connections to go away, since it * seems sometimes new port settings do not work. */ static const int sc_sleep_time_ms = 250; /** * The standard C/C++ entry point to this application. The first thing is to * set the various settings defaults, and then try to read the "user" and * "rc" configuration files, in that order. There are currently no options * to change the names of those files. If we add that code, we'll move the * parsing code to where the configuration file-names are changed from the * command-line. The last thing is to override any other settings via the * command-line parameters. * * We check for any "fatal" PortMidi errors, so we can display them. But we * still want to keep going, in order to at least generate the log-files and * "erroneous" configuration files to C:/Users/me/AppData/Local/seq66 or * $HOME/.config/seq66. * * \param argc * The number of command-line parameters, including the name of the * application as parameter 0. * * \param argv * The array of pointers to the command-line parameters. * * \return * Returns EXIT_SUCCESS (0) or EXIT_FAILURE, depending on the status of * the run. */ int main (int argc, char * argv []) { #if defined SEQ66_SETUID_SUPPORT QApplication::setSetuidAllowed(true); #endif QApplication app(argc, argv); /* main application object */ QString qapppath { QCoreApplication::applicationFilePath() }; std::string apppath { qapppath.toStdString() }; if (apppath.empty()) apppath = argv[0]; seq66::smanager::app_info(apppath); /* instead of set_app_path() */ #if defined USE_RING_BUFFER_TEST && defined SEQ66_PLATFORM_DEBUG if (! seq66::run_ring_test()) { printf("ring_buffer test FAILED\n"); exit(1); } else exit(0); #endif #if defined SEQ66_LOCALE_SUPPORT /* * No longer used, it conflicts with getopt processing. Instead, see * the --locale option in the cmdlineopts module. */ Q_FOREACH(QString a, app.arguments()) { const static QString s_locale_arg = "--locale:"; if (a.startsWith(s_locale_arg)) { QLocale::setDefault(QLocale(a.mid(sizeof(s_locale_arg) - 1))); break; } } #endif #if defined SEQ66_TRANSLATOR_SUPPORT QTranslator app_translator; if (! app_translator.load ("seq66_" + QLocale().name (), app_tr_dir)) { qWarning ( "Can't load app translator file for locale %s from %s", qPrintable(QLocale().name()), app_tr_dir.toLocal8Bit().data() ); } else app.installTranslator(&app_translator); #endif int exit_status = EXIT_SUCCESS; /* versus EXIT_FAILURE */ for (;;) { seq66::qt5nsmanager sm(app); seq66::millisleep(sc_sleep_time_ms); /* TRIAL CODE 2025-06-11 */ bool result = sm.create(argc, argv); if (result) { std::string msg; bool result = sm.run(); exit_status = result ? EXIT_SUCCESS : EXIT_FAILURE ; (void) sm.close_session(msg, result); if (! result) break; /* stop infinite loop */ if (seq66::session_close()) { seq66::session_message("Closing Seq66 session"); break; /* stop infinite loop */ } if (seq66::session_restart()) { seq66::millisleep(sc_sleep_time_ms); seq66::session_message("Reloading Seq66 session"); seq66::signal_end_restart(); } else break; /* stop infinite loop */ } else { exit_status = EXIT_FAILURE; /* --help or error */ break; } } return exit_status; } /* * seq66qt5.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: TODO ================================================ TO DO for Seq66 0.99.24 Chris Ahlstrom 2019-04-13 to 2026-04-29 - Change the build system to Meson. - Port more Qt6-deprecation fixes from the (not yet public) Meson build once that works fully. - Fix positioning of data pane text in the various modes. - Make controller values movable in the data pane just like patch values. Show the number in addition to the name. - Can we alter a selected value by forwarding the arrow key to the data pane for non-note events? - Segfault: Open b4uacuse patchless midi, then open the pattern editor for guitar. Then open 1Bar-1920.midi when channel 0 pops up as being unavailable. Similar to the segfault when prompted to remap and restart. "Unavailable". Partly fixed: can still occur with multiple pattern editors plus a song editor window; fix the external song editor. Test this with pattern-fix, LFO, and other dialogs! - On Arch, with many pattern editors and one song editor open, loading a new file leaves the song editor open. - Issues #102, #137, #138 Features: - MIDNAM? Incorporate the xml66 and midiname projects if practical. NSM: - Seq66 may be delaying responding/announcing to NSM. - When creating a session, make sure playlist is copied if active. - How to keep Seq66 up-to-date when ports are added back in a session. - Should NSM really require JACK? - See the Recording items below (*). - Make the NSM start-up prompt time configurable or dependent on the number of words. - See the ongoing nsmd66 and nsm66 projects. Backlog: - Takes up too much space: - The buss name could be shown as a field in the event editor. - Also could show channel names if provided in 'usr' file. - Why do we have auto-save for the .drums file? We do not have that for patches. Seq66 offers no ways to modify them in-app at this time. - Note drawing and event drawing screwed up in time when scrolled completely to the right. - In midicvt (separate project), the c_mutegroups SeqSpec is not shown (24 24 00 09). - Automation: verify/document Solo, Reset Sets, and Song Position. - Make sure sessions.rc works as advertised; fix documentation. - Event editor: - Weird error cropped up; a Qt update issue? When clicking in the event-editors event table, a message like this appears: "qt.accessibility.core: Cannot create accessible child interface for object: QTableWidget(0x55eaaaf6b880, name = 'eventTableWidget') index: 8". This does not happen in the table-widgets in other tabs. Nor does it happen on another computer. - #115 Accessing Non-Registered Parameter Numbers (NRPNs) possible? At present, RPN and NRPN can be selected from a list of control changes in the events editor. This fills in D0, but D1 has to be entered manually. - Recording. - Turning on record for a pattern makes the bottom record button red, but turning off the record does not make it grey again. - One-shot recording during playback now works. But one-shot reset then makes it act like overdub thereafter (because note-count == 1 is the flag). - There seems to be a very slight chance of too-longth notes in a chord if quantization (1/16) is on. Needs a better keyboard player (not me) to test it. - Test overwrite etc. in the scenario where a recording mode can cause incomplete notes if one holds the note and releases it in the next iteration, leaving a partially-drawn note behind. - Consider adding time-bg/fg-color to the palette file, using the 'usr' version otherwise. Not sure we like this idea. - Add integration with JACK dbus. Low priority. - Finish getting Windows builds in MSYS2 to work. Low priority. - Oddity: drag a pattern to a new slot, and when the mouse hovers over the new slot, the slot number appears under it. Playlists: - Consider adding a configurable play-list auto-advance delay value? - Set all contrib/midi and data/midi MIDI files to use port 0? Ongoing efforts: - Verify setmapper/setmaster for odd set sizes. - Why does velocity change in data pane not work when starting from the left in barrage.midi? The Kepler34 "relative change" feature. This feature is macroed out for now. Perhaps should make it a Ctrl-Click or something to start it in addition to "would select". - Make sure metronome buttons etc. still work. Recorded measures not saved. - Make sure meta events are never sent via MIDI. What we see is that is_ex_data() events are skipped. We now allow SysEx to be sent, but we need a test file for it. Can improve this slightly. - Add "make install" support to qbuild.sh. In progress. - When zooming, try to keep the same viewport in view. Got this working for horizontal seqroll and perfroll, but not for vertical (refactoring needed). Try to center vertically on notes in the viewport. - Make it center on notes if not visible. - Can we distinguish note-insertion from movement snap, to avoid the "L"-then-snap-then-move samba? Cannot duplicate or will not do: - When I try to record events using the Midi Controller UC-16, seq66 is refusing to work and is telling me that the output bus is not available. The same hardware configuration is working perfectly when using seq64, the synth is receiving the CC signals and these events are recorded by seq64. [We do not have this unit] - Event editor. Open the b4uacuse patchless file and the first (0) pattern in the pattern editor; a segfault occurs. Try it on Arch. - Tempo event in track 0 does not override c_tempo_map SeqSpec. Actually, c_tempo_map is not really used. USE_SEQ32_SEQSPECS /* stazed feature not implemented */ - Implement showing drum note names, similar to patch name, in the various edit panes. However, this gets cramped, so disable via the macro SEQ66_SHOW_GM_DRUM_NAME. But will be shown if note is manipulated in the data pane. - Have a click/selection bring the pattern-editor to the front. Sort of fixed; only effective for the last-opened pattern editor. ISSUES: #44 "Record live sequence changes" functionality Currently the "record live sequence changes" only adds patterns into the song timeline as whole loops, starting at the last pattern loop and ending at the next, and ignoring queueing completely (queueing a pattern just adds it to the song immediately). It seems to me that a live-oriented sequencer like Seq66 should have the ability to record a live performance as accurately as possible, so that a user can simply hit the record enable and do their live performance, knowing that can then go back and replay it exactly (if they want to do a recording session for example, or perform the same track while focussing entirely on knob tweaking etc.) The song timeline already supports arbitrary start/end points for pattern "chunks", so it seems like this should already be possible. Thoughts? 1. When recording live sequence changes in song mode, turning "snap" mode off makes the pattern block start exactly when you press the button on the Launchpad, like you expect, but the end point always extends out to the next multiple of the pattern length when you mute it again. 2. With "snap" mode enabled, live sequence changes always snap to pattern length regardless of the snap size setting in the drop-down box. The snap size does work when dragging and resizing pattern blocks in the timeline - except. 3. ...off-by-one error in the snap size drop-down. 1/1 and 1/2 are both 1/1, 1/3 is actually 1/2, 1/4 is actually 1/3 etc. FIXED. STATUS: Partially fixed. One can now trigger manually in the perfnames pane, but there are still issues to resolve. #52 Community / discussion place? STATUS: Partly supported now by using github.io, but much more to be done. #63 Option to rotate pattern numbers? STATUS: It works for the live grid. New issue: Verify that it works in other settings. #66 Toggle MIDI Recording via CC message I think modes generally should be triggerable individually. Especially if there will be more to come in the future, it will get messy when you only have a single button for them. I think that a next candidate would be for copy and clear mode. In copy mode, the first grid button press would highlight a pattern and the second one would paste it to the appropriate slot. On clear mode, the patterns could be emptied or removed via the grid buttons on the controller. We could end up with a lot of modes, perhaps more than keystrokes could support. If a MIDI controller can emit specific D2 data programmably from 0 to 127 (for Notes, D1 is the note number and D2 is the velocity; for Controllers, D1 is the CC number and D2 is the value), then we could consolidate modes in one command (and preserve the stepping through the modes as an alternative for less flexible controllers). Ideally there would be an automation control for cycling through the modes and one for each mode separately. Then people could chose if they want to set up a control that cycles through the options or set up a control that instantly activates a specific mode. Can we leverage continuous controllers (CC) to support modifying synth parameters through input automation? See the new "macro" facility. STATUS: Partly fulfilled. #68 MIDI controller initialization step STATUS: Closed, some follow-on suggestions: Actually I have been thinking about the mode transition logic and it would be nice to have support for temporary transition. Here is my original suggested flow 1. Controller button pressed 2. MIDI CC message with `on value` sent 3. Seq66 reacts to the message and changes modes 4. Controller button released 5. MIDI CC message with `off value` sent 6. Seq66 does nothing 7. ... 8. Same controller button pressed 9. MIDI CC message with `on value` sent 10. Seq66 reacts to the message and changes to neutral mode 11. Controller button released 12. MIDI CC message with `off value` sent 13. Seq66 does nothing A temporary transition would be engaged by holding a button down on the MIDI controller and then releasing it later. While the button is held down, Seq66 switches to the configured mode, the grid buttons can then be used to engage with the mode. When the button is released,Seq66 switches back to the previous mode and not to the neutral one. This could be implemented via a timer that starts when a CC from a button press is released. If the button is released under a threshold amount of time then the mode is toggled permamently. If no `button released` CC message is received within a threshold amount of time then the transition becomes temporary and when the `button released` CC message is finally received then Seq66 switches back to the previous mode. The idea is that with a MIDI controller you could assign different functions to a single button. It would also fit nicely with a Launchpad where apart from the grid buttons, there are "mode" buttons on the side. #100 Seq66's MIDI timing completely falls apart at JACK buffer sizer larger than 128. STATUS: Added a timestamp to each message in the JACK ringbuffer (enabled by a C macro in libseq66/include/seq_features.h). This addes a notable burden on process and we can get buffer overflow errors. Even with message timestamps disabled, the b4uacuse-stress.midi file can cause failure, especially when the next set is chosen. At minimum we should stop, clear out JACK, and panic. We found that there seems to be no way (using methods culled from other apps such as Ardour) to completely eliminate hiccups in the synchronization of note input and playback, due to the unavoidable lag between putting the note in the ringbuffer and taking it out in the JACK process callback. #115 Accessing Non-Registered Parameter Numbers (NRPNs) possible? STATUS: It works for this user. As a side issue, he would like to be able to set data values more easily (e.g. exact number) than by dragging the data line. Also found a bug: Open brecluse.mid and then pattern #1. Then attempt to modify the velocity in the data pane. We find that many of the events were not drawn in the data pane when the roll was moved by the arrow keys! Clicking in the event pane forces redrawing. Using the horizontal scrollbar refreshes properly. Added some tricky code [flag_dirty()], as already done in qstriggereditor. #128 Recording type for loop button not working The button on the main page for recording styles Overdub, Overwrite, Expand and One Shot does not change the property of the pattern. The drop down menu in the pattern window works fine, although Overdub is called Merge here, which is not ideal. So, it is not quite as I thought. I tested it using step input and it is a bit hit and miss whether it turns off the record arm at the end of the loop. Mostly miss and it just overdubs. It also sometimes turns the record arm off instantly. This may or may not be related to a separate problem(s) I have found with step input where it does not loop properly on any mode. Step input seems to read the loop as one more step than it actually is. So on overdub the extra step will appear at the beginning of the loop and the subsequent loops second step will appear over the a step behind. On overwrite, it will not overwrite till the second step of the loop and again the step will one behind. Expand overdubs rather than expands on step input. Update: Been testing the WIP branch. I am getting an issue with the expand when recording live midi into it. When it gets to the ninth bar, it wipes all the midi recorded before it except the first bar. The same then happens at bars,16, 17 and 24. Always all bars before except the first. I did not go longer than that but you get the idea. With step entry, there is a pop up which warns that events will be dropped when you reach those bars. Separately, The looping with step entry is better now. STATUS: All issues seem to be fixed, but need to be retested. #136 Program change shows as note in drum mode The Program change is the only CC I have noticed that does not have a way to drag up and down via a lollipop. I do not whether this is connected. Anyway, to reproduce 1. Switch to drum mode 2. Create a program change cc 3. You can see a drum note on the piano roll that goes up and down with the value change, STATUS: Verified. Also another issue: use the event editor to add Program changes at various time-stamps. They all end up at 0! #137 multisignature fake bug + UX suggestion OK so initially I thought it was a bug, but it turns out it is just a UX thing. Reproduce steps 0. (optional) press play 1. create pattern 1 2. press play 3. create pattern 2 4. set signature to 3/4 > pattern 2 will stay in 4/4 eventhough the dropdown boxes will display > `3` and `4` When pressing stop, and re-opening the editor, the dropdown boxes mysteriously were reset to `4` and `4` :/ This 'bug' made me bang my head against the wall..read the docs again..try various things to make the loops play with different signatures. Cause It turns out that signatures cannot be changed when playing. However, the GUI allows changing the dropdowns (but the controls do not update the data). Solution While playing, the signature dropdowns should be grayed out (and enabled once pattern is no longer playing) `<---- preferred`. NOTE: the time signatures work, but the time signature event is not added until the time-signature logging button to the left is clicked. This work whether the L position (progress bar) is moving or is set with a click in the time pane. Other (probably more complicated/annoying) solutions are: * Stop the transport when changing the signature. * Refactor things to allow updating signature in realtime [see NOTE above. STATUS: Fixed. EXTRA WORK (STATUS: Fixed, needs more testing perhaps): 1. Follow up on issue #111, Time signature changes does not get saved on .midi file. 2. Changing the Global Time Signature shows the changes in the grid slots and the seqroll, but not in the pattern-editor combo boxes. 3. Re 2, changing to 3/8, saving, and re-opening shows 4/8! The actual time-signature event is 4/4! 4. Hmmmm, generally only the first track has a time signature event. Do we need to go back to that? #138 workflow regarding multitimbral program change + MSB + LSB When the transport is stopped..sending midi-notes to a pattern does record it to the pattern. However, it does not capture programchange+MSB+LSB or CC events (send from my MT device) I really like the concept of being able to export a midi-file which includes `program-change + MSB + LSB bank select` for each midi-channel (in the beginning of the midi-file). That way I can import the midi-file to my multitimbral synth (MT) for perfect playback with the right instruments. The problem is that I don't know a convenient way of doing this. Right now I'm doing: * MT: transmits notes on midichannel 1 to seq66 * MT: I select the previous instrument * seq66: I press 'play' and 'record' on a pattern * MT: I select the next instrument (so it triggers a `program-change + MSB + LSB bank select` of the desired instrument to seq66) * seq66: profit! the values have been captured in the pattern (not ideal though, the values never end up exactly in the beginning of the pattern) * seq66: I have to manually move the events to beginning of the pattern * Rinse and repeat: I do this for every (16) midichannels Question: is there an easier way to do this? The good news is that recording notes during stopmode, increases the pattern-position for each note. Via this exact same mechanism, NRPN data would become possible (which re-uses CC 6 to send a value`). Many midi-devices transmit NRPN data for device-specific settings, so recording it (during stopstate) starting from a specific patternposition would really allow exciting control without external dependencies/drivers/definition-files. Brainstorm Seq66 is unique in that it treats the `.mid` file as a first-class citizen for project-metadata too. In other words, the midi-file is completely selfcontained. Here is an idea to allow creating easy selfcontained multitimbral midifiles for soundfonts/hardware midi-devices: How about each seq66 pattern would always update/save the last received `program change + MSB + LSB`-values at the beginning. Maybe this could be a default feature-toggle? This would make the above mentioned manual steps (4x16=64) redundant. Imagine the the speedy workflow. All other (older) midi editors either lack this, or require you to create (very timeconsuming) instrument-bank-definition files (which is an anti-pattern imho, as they usually never exist for your preferred device/soundfont/software combination). Can we maybe allow recording midi-events in the pattern editor (during stop-state?). This would make the above mentioned ~64 manual steps redundant. One could hit record (without pressing play), receive some midi data, which would (over)write the values to the current patternposition. This more modern workflow, allows hardware to augment the seq66 pattern-editor cursor. bigger picture: synths/keyboards/grooveboxes emit program change + LSB + MSB-events when switching patterns or instruments. Many of them also send midi CC snapshots (reverb/chorus/delay send values) when switching instruments. For example, here's my CASIO CTS-1000v synth when switching an instrument: Sends CC 0 11 + CC 32 64 + PC 96 + CC 5 20 + CC 65 0 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = To close as pushed off to version 2: 1 JACK Metadata MIDINAM support 46 JACK port groups and order. 62 PipeWire compatibility 68 MIDI controller initialization step 77 For building without JACK 82 Tabs do not scale to window 90 More consistent support for undo / redo / unmodify. 96 MIDI Learn/Wizard as separate (?) app MIDI version 2 considerations:: ALSA: - When toggling a virtual input port, another port with the same number gets created. Looks like this bug has been around for awhile. It also yields an ALSA unsubscribe error when disabling an input. - Use const char * snd_asoundlib_version () to get the ALSA version. JACK: - Pulseaudio gone, so now the USB devices (without a2jmidid running) show up as "0:n system:midi_playback_n+1 'Device name'" (similar for 'playback') and qsynth as "1:4 qsynth:midi_00". True even if short port-naming. Version 2 features: - #100 Seq66's MIDI timing completely falls apart at JACK buffer sizes larger than 128. Added a timestamp to each message in the JACK, more to refactor - Round-robin support for MIDI input. - Use Meson/Ninja as the build system. - Support for multiple languages in user-messages. - Allow for building without JACK dev files installed. Weak JACK support. Seq66 dependence on the JACK ringbuffer is an issue here. - Port refresh in ALSA and JACK at a minimum. - #46 JACK port groups and order. - Add option to start JACK if not running. - #96 MIDI learn (and wizard) for creating controller maps. Separate app? - Pipewire support. - Live note mapping? - Abletone Link support? - Support for audio-clip patterns? - Add keyboard configuration to MIDI Learn/Wizard. - Move the Events, Playlist, Sets, and Mutes to a separate window (the Preferences editor?). - Make main menu hide-able. - Allow multiple instances of seq66. - Allow multiple tunes to be loaded in seq66. - #90 More consistent support for undo / redo / unmodify. - Add console output for every user action. - Add scripting language? - Clean up kruft in setmapper/screenset. - Support more than one tune? - A way to lay out a pattern from one track into another tracks playback, to re-use the pattern on a differnt buss and channel. - Support manual reconnecting, including of the control/display busses. - Implement solo, FF, rewind, etc. - Use std::numeric_limits instead of macros. - LV2 MIDINAM support. - MIDI 2.0 support? - Live note-mapping. - Windows build and installer. 32-bit installer cannot work! - Perfect the "background recording" feature. # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: VERSION ================================================ 2026-05-01 0.99.24 ================================================ FILE: autogen.sh ================================================ #!/bin/sh # #****************************************************************************** # autogen.sh (Seq66) #------------------------------------------------------------------------------ ## # \file autogen.sh # \library Seq66 # \author Chris Ahlstrom # \date 2024-01-12 # \update 2024-01-13 # \version $Revision$ # \license Whatever # # Use this script in any manner whatsoever. You don't even need to give # me any credit. However, keep in mind the value of the GPL in keeping # software and its descendant modifications available to the community for # all time. Uses basic shell commands instead of bashisms. # #------------------------------------------------------------------------------ aclocal -I m4 --install if test $? = 0 ; then echo "aclocal finished" autoconf if test $? = 0 ; then echo "autoconf finished" autoheader if test $? = 0 ; then echo "autoheader finished" libtoolize --automake --force --copy if test $? = 0 ; then echo "libtoolize finished" automake --foreign --add-missing --copy if test $? = 0 ; then echo "automake finished" else echo "automake failed; is it installed?" fi else echo "libtoolize failed; is it installed?" fi else echo "autoheader failed" fi else echo "autoconf failed; is autoconf installed?" fi else echo "aclocal failed; are autoconf and autoconf-archive installed?" fi echo "If all steps succeeded, run ./configure with desired parameters." #****************************************************************************** # autogen.sh (Seq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: bootstrap ================================================ #!/bin/bash # #****************************************************************************** # bootstrap (Seq66) #------------------------------------------------------------------------------ ## # \file bootstrap # \library Seq66 # \author Chris Ahlstrom # \date 2018-11-09 # \update 2024-11-23 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # The above is modified by the following to remove even the mild GPL # restrictions: # # Use this script in any manner whatsoever. You don't even need to give # me any credit. However, keep in mind the value of the GPL in keeping # software and its descendant modifications available to the community for # all time. Runs the configure script by default. # # This file provides the functionality we expect to find in an autogen.sh # script. Also see contrib/scripts/reconf, which is less comprehensive # but more standard. # # Also see the helper functions in contrib/scripts/strap_functions. # #------------------------------------------------------------------------------ #****************************************************************************** # Provide a sane environment, just in case it is needed. #------------------------------------------------------------------------------ LANG=C export LANG CYGWIN=binmode export CYGWIN export SEQ66_BOOTSTRAP_EDIT_DATE="2024-11-23" export SEQ66_LIBRARY_API_VERSION="0.99" export SEQ66_LIBRARY_VERSION="$SEQ66_LIBRARY_API_VERSION.15" #****************************************************************************** # Version info #------------------------------------------------------------------------------ if [ "$1" == "--version" ] || [ "$1" == "-v" ] ; then echo "Seq66 version $SEQ66_LIBRARY_VERSION $SEQ66_BOOTSTRAP_EDIT_DATE" exit 0 fi #****************************************************************************** # Set up for local bootstrapping #------------------------------------------------------------------------------ source contrib/scripts/strap_functions if [ $? != 0 ] ; then echo "'source strap_functions' failed. Must abort." exit 255 fi checkdir libseq66 #****************************************************************************** # Option settings #------------------------------------------------------------------------------ DOAUTOCONF_ONLY="no" DOBOOTSTRAP="yes" DOCLEAN="no" DOCONFIGURE="no" DODEBUG="no" DOFULLCLEAN="no" DOJACK="yes" DOMINGW="no" DONSM="yes" DOPORTMIDI="no" DOPORTREFRESH="no" DOPROFILING="no" DORELEASE="yes" DORTCLI="no" DORTMIDI="no" DOSTATIC="no" DOTESTING="no" DOVERSION="no" CLANGSET="" LOGFILENAME="" M4DIR="m4" #****************************************************************************** # Default boostrap build when not using Windows/MSYS2. Note that MSYS2 # autotools build support is still in progress. #------------------------------------------------------------------------------ if [ "$MSYS2_PATH" == "" ] ; then echo "Assuming a Linux environment..." DORTMIDI="yes" DOJACK="yes" EXTRAFLAGS="" else echo "Assuming an MSYS2 environment..." DOPORTMIDI="yes" DOCONFIGURE="yes" EXTRAFLAGS=" --enable-portmidi --disable-rtmidi" fi #****************************************************************************** # Help #------------------------------------------------------------------------------ if [ "$1" == "--help" ] ; then cat << E_O_F Usage: ./bootstrap [options] ($SEQ66_LIBRARY_VERSION-$SEQ66_BOOTSTRAP_EDIT_DATE) 'bootstrap' sets up GNU automake and libtool support for the Seq66 project and creates the 'configure' script. It can super-clean the project, removing all generated files and directories. It provides options to save trouble passing long options to the configure script for some canned setups. --autoconf Just create the autoconf/automake setup and exit. --clean Delete the usual derived files from the project. --full-clean Delete all derived and configure-related files. The bootstrap script will need to be run again. --debug, -ed Configure for debugging and disable shared libraries for easier debugging. Also: --enable-debug=gdb --clang Use the clang++ compiler when bootstrapping the configuration. --release, -er Configure for release. Sets up a 'silent' build as well. Equivalent to --enable-release; defaults to 'release'. --static Do a static release build. --portmidi, -pm Configure for qpseq66 (--enable-portmidi). --rtmidi, -rm Configure for qseq66 (ALSA/JACK version, the default). Implies --enable-rtmidi. The official GUI is Qt 5. --port-refresh, -pr Enables the port-refresh feature for JACK. Will be the default once it works. --disable-jack, -dj Disables JACK support, enables rtmidi. --no-metadata Disable JACK metadata support. --cli, -cli Configure for seq66cli (command-line rtmidi version). --both Build qseq66 and seq66cli in one pass. --no-nsm Disable Non Session Manager support. Saves some code. --profile, -prof Enable profiling and debugging. --help Show this help text. There are other options too rare to document here. Read the bootstrap script. The default (no options) bootstraps the project (e.g. runs 'autoheader'.) Then one must run 'configure', which sets up for a normal release build. A common option for 'bootstrap' is --enable-debug (-ed), which adds --disable-shared and sets up the configure script for debugging without needing "libtool --mode=execute gdb qseq66". The most common command sequence would be: $ ./bootstrap -er $ make &> make.log E_O_F exit 1 fi #****************************************************************************** # Brute-force options loop #------------------------------------------------------------------------------ if [ $# -ge 1 ] ; then while [ "$1" != "" ] ; do case "$1" in --autoconf) DOAUTOCONF_ONLY="yes" DOCONFIGURE="no" ;; --no-bootstrap | -nb) DOBOOTSTRAP="no" DOCONFIGURE="no" ;; --clean | clean) DOBOOTSTRAP="no" DOCONFIGURE="no" DOCLEAN="yes" ;; --full-clean | --super-clean) DOBOOTSTRAP="no" DOCONFIGURE="no" DOFULLCLEAN="yes" DOCLEAN="yes" ;; --debug | -ed | --enable-debug) DODEBUG="yes" DOCONFIGURE="yes" EXTRAFLAGS+=" --disable-shared --enable-static" ;; --clang) DOCONFIGURE="yes" CLANGSET="CC=clang CXX=clang++" ;; --profile | -prof | --prof) DODEBUG="yes" DOPROFILING="yes" DOCONFIGURE="yes" EXTRAFLAGS+=" --enable-profile --disable-shared" ;; --release | -er | --enable-release) DORELEASE="yes" DOCONFIGURE="yes" ;; --static) DORELEASE="yes" DOCONFIGURE="yes" EXTRAFLAGS+=" --disable-shared --enable-static" ;; --no-nsm) DONSM="no" DOCONFIGURE="yes" EXTRAFLAGS+=" --disable-nsm" ;; --portmidi | -pm | --pm) DOPORTMIDI="yes" DOCONFIGURE="yes" EXTRAFLAGS+=" --enable-portmidi --disable-rtmidi" ;; --rtmidi | -rm | --rm) DORTMIDI="yes" DOCONFIGURE="yes" EXTRAFLAGS+=" --enable-rtmidi" ;; --port-refresh | -pr | --pr) DOPORTREFRESH="yes" DOCONFIGURE="yes" EXTRAFLAGS+=" --enable-port-refresh" ;; --disable-jack | -dj) DOJACK="no" DOCONFIGURE="yes" EXTRAFLAGS+=" --disable-jack --enable-rtmidi" ;; --no-metadata | --disable-jack-metadata) DOCONFIGURE="yes" EXTRAFLAGS+=" --disable-jack-metadata" ;; --cli | -cli) DORTCLI="yes" DORELEASE="yes" DOCONFIGURE="yes" EXTRAFLAGS+=" --enable-cli" ;; --both | -both) DORTCLI="yes" DORELEASE="yes" DOCONFIGURE="yes" EXTRAFLAGS+=" --enable-rtmidi --enable-both" ;; --testing | -stt) DOTESTING="yes" DOCONFIGURE="yes" EXTRAFLAGS+=" --enable-testing" ;; --version) DOVERSION="yes" DOBOOTSTRAP="no" ;; *) echo "? Unsupported bootstrap option $1; --help for more information" exit $EXIT_ERROR_NO_SUCH_OPTION ;; esac shift done fi if [ "$EXTRAFLAGS" == "" ] ; then EXTRAFLAGS="--enable-rtmidi" fi echo "EXTRAFLAGS = $EXTRAFLAGS" #****************************************************************************** # Implement the clean option. #------------------------------------------------------------------------------ # # This goes well beyond "make clean" and "make distclean". It cleans # out /everything/ that gets created by bootstrapping and building the # library and test application. # #------------------------------------------------------------------------------ if [ $DOCLEAN == "yes" ] ; then make clean clean_gnufiles clean_tempfiles clean_m4 clean_qt5files rm -f include/seq66-config.h echo " Config, GNU, temp, m4, and Qt 5 generated files removed." fi if [ $DOFULLCLEAN == "yes" ] ; then rm -rf config rm -f include/config.h rm -f include/stamp-h1 rm -f include/seq66-config.h rm -rf po clean_debfiles echo " All junk files removed." fi if [ $DOBOOTSTRAP == "yes" ] ; then #************************************************************************ # Set up GNU Autotools #------------------------------------------------------------------------ AUTOMAKE_BAD=no INSTALL_BAD=no ACVERSION= ACLOCAL=aclocal${ACVERSION} AUTOCONF=autoconf AUTOHEADER=autoheader AUTOMAKE=automake LIBTOOLIZE=libtoolize # Exit if a simple command exits with a non-zero status. set -e # After expanding a simple command, display PS4 and the command with its # expanded arguments. This setting makes any error messages too # difficult to read: # # set -x # Check Autoconf version and perform clean ups if all is well. if [ -x `which autoconf` ] ; then AC_VER=`autoconf --version | head -1 | sed 's/^[^0-9]*//'` AC_VER_MAJOR=`echo $AC_VER | cut -f1 -d'.'` AC_VER_MINOR=`echo $AC_VER | cut -f2 -d'.' | sed 's/[^0-9]*$//'` if [ "$AC_VER_MAJOR" -lt "2" ] ; then echo echo "Autoconf 2.13 or greater may be needed to build configure." echo "Edit the bootstrap file to ignore this test, if desired." echo exit $EXIT_ERROR_AUTOCONF_VERSION fi if [ "$AC_VER_MINOR" -lt "13" ] ; then echo echo "Autoconf 2.13 or greater may be needed to build configure." echo "Edit the bootstrap file to ignore this test, if desired." echo exit $EXIT_ERROR_AUTOCONF_VERSION_2 fi if [ "$AC_VER_MINOR" -lt "50" ] ; then if [ -e configure.ac ] ; then if [ ! -e configure.in ] ; then ln -s configure.ac configure.in fi fi echo "Some warnings about cross-compiling are normal." else rm -f configure.in if [ $DOCONFIGURE == "yes" ] ; then if [ -x configure ] ; then echo The Seq66 configure script already exists. Replacing it. fi fi fi else cat << E_O_F The GNU autoconf application was not found. This project requires GNU autoconf (and automake, and ac-autoconf-archive) in order to bootstrap itself. E_O_F exit $EXIT_ERROR_AUTOCONF_VERSION_3 fi # Check for automake. amvers="none" if automake-1.8 --version >/dev/null 2>&1; then amvers="-1.8" # If we also have 1.6 (>> 1.6.1), use it instead because it is faster if automake-1.6 --version >/dev/null 2>&1; then if expr "`automake-1.6 --version | sed -e '1s/[^0-9]*//' -e q`" ">" "1.6.1" > /dev/null 2>&1; then amvers="-1.6" fi fi elif automake-1.7 --version >/dev/null 2>&1; then amvers="-1.7" # If we also have 1.6 (>> 1.6.1), use it instead because it is faster if automake-1.6 --version >/dev/null 2>&1; then if expr "`automake-1.6 --version | sed -e '1s/[^0-9]*//' -e q`" ">" "1.6.1" > /dev/null 2>&1; then amvers="-1.6" fi fi elif automake-1.6 --version >/dev/null 2>&1; then amvers="-1.6" if expr "`automake-1.6 --version | sed -e '1s/[^0-9]*//' -e q`" "<=" "1.6.1" > /dev/null 2>&1; then AUTOMAKE_BAD=yes fi elif automake-1.5 --version >/dev/null 2>&1; then INSTALL_BAD=yes amvers="-1.5" elif automake --version > /dev/null 2>&1; then amvers="" case "`automake --version | sed -e '1s/[^0-9]*//' -e q`" in 0|0.*|1|1.[01234]|1.[01234][-.]*) amvers="none" ;; 1.5|1.5.*) INSTALL_BAD=yes ;; 1.6|1.6.0|1.6.1) AUTOMAKE_BAD=yes ;; esac fi #****************************************************************************** # Check for the installation of the GNU gettext facility. # Autopoint is available from 0.11.3, but we need at least 0.11.5 #------------------------------------------------------------------------------ # Check for pkg-config if pkg-config --version >/dev/null 2>&1; then PKGCONFIG=yes else PKGCONFIG=no fi #************************************************************************ # Create config and m4 directories. Note that they might be empty for # this project. Also create an include directory, mainly for "config.h" # stuff. #------------------------------------------------------------------------ mkdir -p aux-files mkdir -p config mkdir -p m4 mkdir -p po mkdir -p include #************************************************************************ # Call a number of "auto" programs in the strict order shown below. Note: # Earlier versions of auto-tools don't ignore duplicate definitions of # macros. (The system normally provides m4 macros in /usr/share/aclocal, # but the project also provides them in the project's m4 directory.) #------------------------------------------------------------------------ # We still need to make aux-files/config.rpath and some other files # available, since they are listed in configure.ac, and not provided by # autoconf: cp contrib/config.rpath aux-files run_cmd ${ACLOCAL} -I ${M4DIR} --install run_cmd ${AUTOCONF} run_cmd ${AUTOHEADER} # The LT_INIT macro of libtool 2.0 (formerly called AC_PROG_LIBTOOL) # would do this, but Debian ships with version 1.5 libtool, so we have # to do things the old-fashioned way. run_cmd ${LIBTOOLIZE} --automake --force --copy run_cmd ${AUTOMAKE} --foreign --add-missing --copy # Automake seems to need this one, but doesn't provide it! cp contrib/mkinstalldirs-1.10 aux-files/mkinstalldirs if [ $DOAUTOCONF_ONLY == "yes" ] ; then echo "Autoconf is set up, exiting..." exit 0 fi # At this point, remove files which always need to be regenerated. # Right now, this is done with the --clean option. case "$PKGCONFIG" in yes) ;; no) cat << E_O_F ============================================================================= NOTE: The "pkg-config" utility is not installed on your system; detection of the gtk-2.0 and GNOME 2.0 libraries will not be reliable. E_O_F ;; esac case "$AUTOMAKE_BAD" in no) ;; yes) cat << E_O_F ============================================================================= NOTE: Your version of automake has a bug which prevents proper plugin compilation. Either compile Seq66 with the --disable-plugins flag, or use a version of automake newer than 1.6.1 (1.6.2 is OK, and so are the 1.5 series). E_O_F ;; esac case "$INSTALL_BAD" in no) ;; yes) cat << E_O_F ============================================================================= NOTE: Your version of automake has a bug which prevents proper installation. Do not use "make install" with this version of automake, or use a version of automake newer than 1.5 (such as 1.6 or 1.7). E_O_F ;; esac if [ -x /usr/bin/dot ] ; then echo "Graphviz package found, can build diagrams in Doxygen." else echo "! To build the documentation, you need to install graphviz." fi if [ -x /usr/bin/doxygen ] ; then echo "Doxygen package found, can build the reference manual." else echo "! To build the documentation, you need to install doxygen." fi echo "Bootstrap complete at `date`" >> bootstrap.stamp #************************************************************************ # --configure #------------------------------------------------------------------------ if [ "$DOBOOTSTRAP" == "yes" ] ; then echo "Bootstrapping enabled...." if [ "$DOCONFIGURE" == "yes" ] ; then if [ "$DODEBUG" == "yes" ] ; then echo "Running '$CLANGSET ./configure --enable-debug=gdb $EXTRAFLAGS'" env $CLANGSET ./configure --enable-debug=gdb $EXTRAFLAGS elif [ "$DORELEASE" == "yes" ] ; then echo "Running '$CLANGSET ./configure --enable-silent-rules $EXTRAFLAGS'" env $CLANGSET ./configure --enable-silent-rules $EXTRAFLAGS echo "Running 'make V=1' will turn on full command output." fi else cat << E_O_F Run './configure' to configure Seq66 for compilation, or './configure --help' for configuration options. Useful options: --enable-debug=gdb --enable-calls --disable-shared --enable-silent-rules --enable-rtmidi (default) --enable-cli (no GUI) --enable-portmidi --enable-both (both Qt and CLI versions) E_O_F fi fi fi #****************************************************************************** # bootstrap (Seq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: bootstrap.help ================================================ Usage: ./bootstrap [options] (0.98.9-2022-05-23) 'bootstrap' sets up GNU automake and libtool support for the Seq66 project and creates the 'configure' script. It can super-clean the project, removing all generated files and directories. It provides options to save trouble passing long options to the configure script for some canned setups. --clean Delete the usual derived files from the project. --full-clean Delete all derived and configure-related files. The bootstrap script will need to be run again. --debug, -ed Configure for debugging and disable shared libraries for easier debugging. Also: --enable-debug=gdb --release, -er Configure for release. Sets up a 'silent' build as well. Equivalent to --enable-release; defaults to 'release'. --static Do a static release build. --portmidi, -pm Configure for qpseq66 (--enable-portmidi). --rtmidi, -rm Configure for qseq66 (ALSA/JACK version, the default). Implies --enable-rtmidi. The official GUI is Qt 5. --port-refresh, -pr Enables the port-refresh feature for JACK. Will be the default once it works. --disable-jack, -dj Disables JACK support, enables rtmidi. --no-metadata Disable JACK metadata support. --cli, -cli Configure for seq66cli (command-line rtmidi version). --both Build qseq66 and seq66cli in one pass. --no-nsm Disable Non Session Manager support. Saves some code. --profile, -prof Enable profiling and debugging. --help Show this help text. There are other options too rare to document here. Read the bootstrap script. The default (no options) bootstraps the project (e.g. runs 'autoheader'.) Then one must run 'configure', which sets up for a normal release build. A common option for 'bootstrap' is --enable-debug (-ed), which adds --disable-shared and sets up the configure script for debugging without needing "libtool --mode=execute gdb qseq66". The most common command sequence would be: $ ./bootstrap -er $ make &> make.log ================================================ FILE: configure ================================================ #! /bin/sh # From configure.ac Revision: 0.99. # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.72 for Seq66 0.99.24. # # Report bugs to . # # # Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation, # Inc. # # # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case e in #( e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then _as_can_reexec=no; export _as_can_reexec; # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed 'exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST else case e in #( e) case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi " as_required="as_fn_return () { (exit \$1); } as_fn_success () { as_fn_return 0; } as_fn_failure () { as_fn_return 1; } as_fn_ret_success () { return 0; } as_fn_ret_failure () { return 1; } exitcode=0 as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } if ( set x; as_fn_ret_success y && test x = \"\$1\" ) then : else case e in #( e) exitcode=1; echo positional parameters were not saved. ;; esac fi test x\$exitcode = x0 || exit 1 blah=\$(echo \$(echo blah)) test x\"\$blah\" = xblah || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 test -n \"\${ZSH_VERSION+set}\${BASH_VERSION+set}\" || ( ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO PATH=/empty FPATH=/empty; export PATH FPATH test \"X\`printf %s \$ECHO\`\" = \"X\$ECHO\" \\ || test \"X\`print -r -- \$ECHO\`\" = \"X\$ECHO\" ) || exit 1 test \$(( 1 + 1 )) = 2 || exit 1" if (eval "$as_required") 2>/dev/null then : as_have_required=yes else case e in #( e) as_have_required=no ;; esac fi if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null then : else case e in #( e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. as_shell=$as_dir$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$as_shell as_have_required=yes if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null then : break 2 fi fi done;; esac as_found=false done IFS=$as_save_IFS if $as_found then : else case e in #( e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null then : CONFIG_SHELL=$SHELL as_have_required=yes fi ;; esac fi if test "x$CONFIG_SHELL" != x then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also # works around shells that cannot unset nonexistent variables. # Preserve -v and -x to the replacement shell. BASH_ENV=/dev/null ENV=/dev/null (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV case $- in # (((( *v*x* | *x*v* ) as_opts=-vx ;; *v* ) as_opts=-v ;; *x* ) as_opts=-x ;; * ) as_opts= ;; esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed 'exec'. printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi if test x$as_have_required = xno then : printf "%s\n" "$0: This script requires a shell more modern than all" printf "%s\n" "$0: the shells that I found on your system." if test ${ZSH_VERSION+y} ; then printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should" printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." else printf "%s\n" "$0: Please tell bug-autoconf@gnu.org and $0: ahlstromcj@gmail.com about your system, including any $0: error possibly output before this message. Then install $0: a modern shell, or manually run the script under such a $0: shell if you do have one." fi exit 1 fi ;; esac fi fi SHELL=${CONFIG_SHELL-/bin/sh} export SHELL # Unset more variables known to interfere with behavior of common tools. CLICOLOR_FORCE= GREP_OPTIONS= unset CLICOLOR_FORCE GREP_OPTIONS ## --------------------- ## ## M4sh Shell Functions. ## ## --------------------- ## # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else case e in #( e) as_fn_append () { eval $1=\$$1\$2 } ;; esac fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else case e in #( e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } ;; esac fi # as_fn_arith # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits as_lineno_1=$LINENO as_lineno_1a=$LINENO as_lineno_2=$LINENO as_lineno_2a=$LINENO eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' t clear :clear s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall # in an infinite loop. This has already happened in practice. _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac # For backward compatibility with old third-party macros, we provide # the shell variables $as_echo and $as_echo_n. New code should use # AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. as_echo='printf %s\n' as_echo_n='printf %s' rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" as_tr_sh="eval sed '$as_sed_sh'" # deprecated SHELL=${CONFIG_SHELL-/bin/sh} test -n "$DJDIR" || exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= # Identity of this package. PACKAGE_NAME='Seq66' PACKAGE_TARNAME='seq66' PACKAGE_VERSION='0.99.24' PACKAGE_STRING='Seq66 0.99.24' PACKAGE_BUGREPORT='ahlstromcj@gmail.com' PACKAGE_URL='' # Factoring default headers for most tests. ac_includes_default="\ #include #ifdef HAVE_STDIO_H # include #endif #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif" ac_header_c_list= ac_subst_vars='am__EXEEXT_FALSE am__EXEEXT_TRUE LTLIBOBJS LIBOBJS GNU_WIN_FALSE GNU_WIN_TRUE PTHREAD_CFLAGS PTHREAD_LIBS PTHREAD_CXX PTHREAD_CC ax_pthread_config DBGFLAGS DODEBUG_FALSE DODEBUG_TRUE PROFLAGS DOPROFILE_FALSE DOPROFILE_TRUE COVFLAGS DOCOVERAGE_FALSE DOCOVERAGE_TRUE BUILD_SESSIONS_FALSE BUILD_SESSIONS_TRUE BUILD_RTMIDI_FALSE BUILD_RTMIDI_TRUE BUILD_RTCLI_FALSE BUILD_RTCLI_TRUE BUILD_QTMIDI_FALSE BUILD_QTMIDI_TRUE BUILD_PORTMIDI_FALSE BUILD_PORTMIDI_TRUE BUILD_DOCS_FALSE BUILD_DOCS_TRUE ICON_NAME CONFIG_DIR_NAME CONFIG_NAME CLIENT_NAME APP_BUILD_OS APP_BUILD_ISSUE APP_ENGINE APP_TYPE APP_NAME am_have_qt_qmexe QT_LUPDATE QT_LRELEASE QT_RCC QT_MOC QT_UIC QT_LIBS QT_DIR QT_CXXFLAGS ac_ct_QMAKE QMAKE X_EXTRA_LIBS X_LIBS X_PRE_LIBS X_CFLAGS XMKMF ALSA_TOPOLOGY_LIBS ALSA_LIBS ALSA_CFLAGS NSM_DEPS NSM_LIBS NSM_CFLAGS LIBLO_LIBS LIBLO_CFLAGS MIDI_PORT_REFRESH JACK_LIBS JACK_CFLAGS PKG_CONFIG_LIBDIR PKG_CONFIG_PATH PKG_CONFIG LATEX DOXYGEN seq66pixdir seq66datadir seq66docdir seq66libdir seq66includedir LT_AGE LT_REVISION LT_CURRENT LT_RELEASE LIBTOOL_DEPS LT_SYS_LIBRARY_PATH OTOOL64 OTOOL LIPO NMEDIT DSYMUTIL MANIFEST_TOOL RANLIB ac_ct_AR AR DLLTOOL OBJDUMP FILECMD LN_S NM ac_ct_DUMPBIN DUMPBIN LD FGREP EGREP GREP SED LIBTOOL SEQ66_PROJECT_NAME API_VERSION SEQ66_LIBTOOL_VERSION SEQ66_LT_AGE SEQ66_LT_REVISION SEQ66_LT_CURRENT SEQ66_API_PATCH SEQ66_API_MINOR SEQ66_API_MAJOR SEQ66_SUITE_NAME LINKER_FLAGS CXXCPP CPP am__fastdepCXX_FALSE am__fastdepCXX_TRUE CXXDEPMODE ac_ct_CXX CXXFLAGS CXX am__fastdepCC_FALSE am__fastdepCC_TRUE CCDEPMODE am__nodep AMDEPBACKSLASH AMDEP_FALSE AMDEP_TRUE am__include DEPDIR OBJEXT EXEEXT ac_ct_CC CPPFLAGS LDFLAGS CFLAGS CC am__xargs_n am__rm_f_notfound AM_BACKSLASH AM_DEFAULT_VERBOSITY AM_DEFAULT_V AM_V CSCOPE ETAGS CTAGS am__untar am__tar AMTAR am__leading_dot SET_MAKE AWK mkdir_p MKDIR_P INSTALL_STRIP_PROGRAM STRIP install_sh MAKEINFO AUTOHEADER AUTOMAKE AUTOCONF ACLOCAL VERSION PACKAGE CYGPATH_W am__isrc INSTALL_DATA INSTALL_SCRIPT INSTALL_PROGRAM host_os host_vendor host_cpu host build_os build_vendor build_cpu build target_alias host_alias build_alias LIBS ECHO_T ECHO_N ECHO_C DEFS mandir localedir libdir psdir pdfdir dvidir htmldir infodir docdir oldincludedir includedir runstatedir localstatedir sharedstatedir sysconfdir datadir datarootdir libexecdir sbindir bindir program_transform_name prefix exec_prefix PACKAGE_URL PACKAGE_BUGREPORT PACKAGE_STRING PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR SHELL am__quote' ac_subst_files='' ac_user_opts=' enable_option_checking enable_silent_rules enable_dependency_tracking enable_shared enable_static enable_pic with_pic enable_fast_install enable_aix_soname with_aix_soname with_gnu_ld with_sysroot enable_libtool_lock with_client enable_docs enable_jack enable_jack_session enable_jack_metadata enable_port_refresh enable_nsm enable_both with_alsa_prefix with_alsa_inc_prefix enable_alsa_topology enable_alsatest enable_rtmidi enable_cli enable_qt with_x enable_portmidi enable_coverage enable_profile enable_debug enable_calls ' ac_precious_vars='build_alias host_alias target_alias CC CFLAGS LDFLAGS LIBS CPPFLAGS CXX CXXFLAGS CCC CPP CXXCPP LT_SYS_LIBRARY_PATH PKG_CONFIG PKG_CONFIG_PATH PKG_CONFIG_LIBDIR JACK_CFLAGS JACK_LIBS LIBLO_CFLAGS LIBLO_LIBS XMKMF QMAKE' # Initialize some variables set by options. ac_init_help= ac_init_version=false ac_unrecognized_opts= ac_unrecognized_sep= # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *=) ac_optarg= ;; *) ac_optarg=yes ;; esac case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval enable_$ac_useropt=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -runstatedir | --runstatedir | --runstatedi | --runstated \ | --runstate | --runstat | --runsta | --runst | --runs \ | --run | --ru | --r) ac_prev=runstatedir ;; -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ | --run=* | --ru=* | --r=*) runstatedir=$ac_optarg ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=\$ac_optarg ;; -without-* | --without-*) ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" "*) ;; *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" ac_unrecognized_sep=', ';; esac eval with_$ac_useropt=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) as_fn_error $? "unrecognized option: '$ac_option' Try '$0 --help' for more information" ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) as_fn_error $? "invalid variable name: '$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` as_fn_error $? "missing argument to $ac_option" fi if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi # Check all directory arguments for consistency. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. case $ac_val in */ ) ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` eval $ac_var=\$ac_val;; esac # Be sure to have absolute directory names. case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done # There might be people who depend on the old broken behavior: '$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || as_fn_error $? "working directory cannot be determined" test "X$ac_ls_di" = "X$ac_pwd_ls_di" || as_fn_error $? "pwd does not report name of working directory" # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$as_myself" || $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF 'configure' configures Seq66 0.99.24 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print 'checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for '--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or '..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, 'make install' will install all the files in '$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify an installation prefix other than '$ac_default_prefix' using '--prefix', for instance '--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/seq66] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF Program names: --program-prefix=PREFIX prepend PREFIX to installed program names --program-suffix=SUFFIX append SUFFIX to installed program names --program-transform-name=PROGRAM run sed PROGRAM on installed program names X features: --x-includes=DIR X include files are in DIR --x-libraries=DIR X library files are in DIR System types: --build=BUILD configure for building on BUILD [guessed] --host=HOST cross-compile to build programs to run on HOST [BUILD] _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of Seq66 0.99.24:";; esac cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-silent-rules less verbose build output (undo: "make V=1") --disable-silent-rules verbose build output (undo: "make V=0") --enable-dependency-tracking do not reject slow dependency extractors --disable-dependency-tracking speeds up one-time build --enable-shared[=PKGS] build shared libraries [default=yes] --enable-static[=PKGS] build static libraries [default=yes] --enable-pic[=PKGS] try to use only PIC/non-PIC objects [default=use both] --enable-fast-install[=PKGS] optimize for fast installation [default=yes] --enable-aix-soname=aix|svr4|both shared library versioning (aka "SONAME") variant to provide on AIX, [default=aix]. --disable-libtool-lock avoid locking (might break parallel builds) --enable-docs Enable developer document support --disable-jack Disable JACK support --disable-jack-session Disable JACK session --disable-jack-metadata Disable JACK metadata --enable-port-refresh Enable JACK port refresh support --disable-nsm Disable NSM support --enable-both Enable Qt and command-line builds --enable-alsatopology Force to use the Alsa topology library --disable-alsatest Do not try to compile and run a test Alsa program --disable-rtmidi Disable rtmidi MIDI engine --enable-cli Enable rtmidi command-line build --disable-qt Disable Qt5 user-interface --enable-portmidi Enable portmidi build) --enable-coverage=(no/yes) Turn on a test-coverage build (default=no) --enable-profile=(no/yes/gprof/prof) Turn on profiling builds (default=no, yes=gprof) --enable-debug=(no/yes/db/gdb) Turn on debug builds (default=no, yes=gdb) --enable-debug=(no/yes) Turn on call instrumentation (default=no) Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-gnu-ld assume the C compiler uses GNU ld [default=no] --with-sysroot[=DIR] Search for dependent libraries within DIR (or the compiler's sysroot if not specified). --with-client Change name of client/port from default --with-alsa-prefix=PFX Prefix where Alsa library is installed(optional) --with-alsa-inc-prefix=PFX Prefix where include libraries are (optional) --with-x use the X Window System Some influential environment variables: CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory CXX C++ compiler command CXXFLAGS C++ compiler flags CPP C preprocessor CXXCPP C++ preprocessor LT_SYS_LIBRARY_PATH User-defined run-time library search path. PKG_CONFIG path to pkg-config utility PKG_CONFIG_PATH directories to add to pkg-config's search path PKG_CONFIG_LIBDIR path overriding pkg-config's built-in search path JACK_CFLAGS C compiler flags for JACK, overriding pkg-config JACK_LIBS linker flags for JACK, overriding pkg-config LIBLO_CFLAGS C compiler flags for LIBLO, overriding pkg-config LIBLO_LIBS linker flags for LIBLO, overriding pkg-config XMKMF Path to xmkmf, Makefile generator for X Window System QMAKE "Qt make tool" Use these variables to override the choices made by 'configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to . _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for configure.gnu first; this name is used for a wrapper for # Metaconfig's "Configure" on case-insensitive file systems. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF Seq66 configure 0.99.24 generated by GNU Autoconf 2.72 Copyright (C) 2023 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## # ac_fn_c_try_compile LINENO # -------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_compile # ac_fn_cxx_try_compile LINENO # ---------------------------- # Try to compile conftest.$ac_ext, and return whether this succeeded. ac_fn_cxx_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_cxx_try_compile # ac_fn_c_try_cpp LINENO # ---------------------- # Try to preprocess conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_cpp () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } > conftest.i && { test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || test ! -s conftest.err } then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_cpp # ac_fn_cxx_try_cpp LINENO # ------------------------ # Try to preprocess conftest.$ac_ext, and return whether this succeeded. ac_fn_cxx_try_cpp () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack if { { ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } > conftest.i && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err } then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_cxx_try_cpp # ac_fn_c_try_link LINENO # ----------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. ac_fn_c_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_c_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext } then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_c_try_link # ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES # ------------------------------------------------------- # Tests whether HEADER exists and can be compiled using the include files in # INCLUDES, setting the cache variable VAR accordingly. ac_fn_c_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF if ac_fn_c_try_compile "$LINENO" then : eval "$3=yes" else case e in #( e) eval "$3=no" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_header_compile # ac_fn_c_check_func LINENO FUNC VAR # ---------------------------------- # Tests whether FUNC exists, setting the cache variable VAR accordingly ac_fn_c_check_func () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, which can conflict with char $2 (void); below. */ #include #undef $2 /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char $2 (void); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined __stub_$2 || defined __stub___$2 choke me #endif int main (void) { return $2 (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : eval "$3=yes" else case e in #( e) eval "$3=no" ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ;; esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_func # ac_fn_cxx_try_link LINENO # ------------------------- # Try to link conftest.$ac_ext, and return whether this succeeded. ac_fn_cxx_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then grep -v '^ *+' conftest.err >conftest.er1 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext } then : ac_retval=0 else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_retval=1 ;; esac fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would # interfere with the next link command; also delete a directory that is # left behind by Apple's compiler. We do this before executing the actions. rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval } # ac_fn_cxx_try_link ac_configure_args_raw= for ac_arg do case $ac_arg in *\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append ac_configure_args_raw " '$ac_arg'" done case $ac_configure_args_raw in *$as_nl*) ac_safe_unquote= ;; *) ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. ac_unsafe_a="$ac_unsafe_z#~" ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; esac cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by Seq66 $as_me 0.99.24, which was generated by GNU Autoconf 2.72. Invocation command line was $ $0$ac_configure_args_raw _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac printf "%s\n" "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; 2) as_fn_append ac_configure_args1 " '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi as_fn_append ac_configure_args " '$ac_arg'" ;; esac done done { ac_configure_args0=; unset ac_configure_args0;} { ac_configure_args1=; unset ac_configure_args1;} # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Sanitize IFS. IFS=" "" $as_nl" # Save into config.log some information that might help in debugging. { echo printf "%s\n" "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo printf "%s\n" "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then printf "%s\n" "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then printf "%s\n" "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo cat confdefs.h echo fi test "$ac_signal" != 0 && printf "%s\n" "$as_me: caught signal $ac_signal" printf "%s\n" "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h printf "%s\n" "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. if test -n "$CONFIG_SITE"; then ac_site_files="$CONFIG_SITE" elif test "x$prefix" != xNONE; then ac_site_files="$prefix/share/config.site $prefix/etc/config.site" else ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" fi for ac_site_file in $ac_site_files do case $ac_site_file in #( */*) : ;; #( *) : ac_site_file=./$ac_site_file ;; esac if test -f "$ac_site_file" && test -r "$ac_site_file"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See 'config.log' for more details" "$LINENO" 5; } fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 printf "%s\n" "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 printf "%s\n" "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Test code for whether the C compiler supports C89 (global declarations) ac_c_conftest_c89_globals=' /* Does the compiler advertise C89 conformance? Do not test the value of __STDC__, because some compilers set it to 0 while being otherwise adequately conformant. */ #if !defined __STDC__ # error "Compiler does not advertise C89 conformance" #endif #include #include struct stat; /* Most of the following tests are stolen from RCS 5.7 src/conf.sh. */ struct buf { int x; }; struct buf * (*rcsopen) (struct buf *, struct stat *, int); static char *e (char **p, int i) { return p[i]; } static char *f (char * (*g) (char **, int), char **p, ...) { char *s; va_list v; va_start (v,p); s = g (p, va_arg (v,int)); va_end (v); return s; } /* C89 style stringification. */ #define noexpand_stringify(a) #a const char *stringified = noexpand_stringify(arbitrary+token=sequence); /* C89 style token pasting. Exercises some of the corner cases that e.g. old MSVC gets wrong, but not very hard. */ #define noexpand_concat(a,b) a##b #define expand_concat(a,b) noexpand_concat(a,b) extern int vA; extern int vbee; #define aye A #define bee B int *pvA = &expand_concat(v,aye); int *pvbee = &noexpand_concat(v,bee); /* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has function prototypes and stuff, but not \xHH hex character constants. These do not provoke an error unfortunately, instead are silently treated as an "x". The following induces an error, until -std is added to get proper ANSI mode. Curiously \x00 != x always comes out true, for an array size at least. It is necessary to write \x00 == 0 to get something that is true only with -std. */ int osf4_cc_array ['\''\x00'\'' == 0 ? 1 : -1]; /* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters inside strings and character constants. */ #define FOO(x) '\''x'\'' int xlc6_cc_array[FOO(a) == '\''x'\'' ? 1 : -1]; int test (int i, double x); struct s1 {int (*f) (int a);}; struct s2 {int (*f) (double a);}; int pairnames (int, char **, int *(*)(struct buf *, struct stat *, int), int, int);' # Test code for whether the C compiler supports C89 (body of main). ac_c_conftest_c89_main=' ok |= (argc == 0 || f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]); ' # Test code for whether the C compiler supports C99 (global declarations) ac_c_conftest_c99_globals=' /* Does the compiler advertise C99 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L # error "Compiler does not advertise C99 conformance" #endif // See if C++-style comments work. #include extern int puts (const char *); extern int printf (const char *, ...); extern int dprintf (int, const char *, ...); extern void *malloc (size_t); extern void free (void *); // Check varargs macros. These examples are taken from C99 6.10.3.5. // dprintf is used instead of fprintf to avoid needing to declare // FILE and stderr. #define debug(...) dprintf (2, __VA_ARGS__) #define showlist(...) puts (#__VA_ARGS__) #define report(test,...) ((test) ? puts (#test) : printf (__VA_ARGS__)) static void test_varargs_macros (void) { int x = 1234; int y = 5678; debug ("Flag"); debug ("X = %d\n", x); showlist (The first, second, and third items.); report (x>y, "x is %d but y is %d", x, y); } // Check long long types. #define BIG64 18446744073709551615ull #define BIG32 4294967295ul #define BIG_OK (BIG64 / BIG32 == 4294967297ull && BIG64 % BIG32 == 0) #if !BIG_OK #error "your preprocessor is broken" #endif #if BIG_OK #else #error "your preprocessor is broken" #endif static long long int bignum = -9223372036854775807LL; static unsigned long long int ubignum = BIG64; struct incomplete_array { int datasize; double data[]; }; struct named_init { int number; const wchar_t *name; double average; }; typedef const char *ccp; static inline int test_restrict (ccp restrict text) { // Iterate through items via the restricted pointer. // Also check for declarations in for loops. for (unsigned int i = 0; *(text+i) != '\''\0'\''; ++i) continue; return 0; } // Check varargs and va_copy. static bool test_varargs (const char *format, ...) { va_list args; va_start (args, format); va_list args_copy; va_copy (args_copy, args); const char *str = ""; int number = 0; float fnumber = 0; while (*format) { switch (*format++) { case '\''s'\'': // string str = va_arg (args_copy, const char *); break; case '\''d'\'': // int number = va_arg (args_copy, int); break; case '\''f'\'': // float fnumber = va_arg (args_copy, double); break; default: break; } } va_end (args_copy); va_end (args); return *str && number && fnumber; } ' # Test code for whether the C compiler supports C99 (body of main). ac_c_conftest_c99_main=' // Check bool. _Bool success = false; success |= (argc != 0); // Check restrict. if (test_restrict ("String literal") == 0) success = true; char *restrict newvar = "Another string"; // Check varargs. success &= test_varargs ("s, d'\'' f .", "string", 65, 34.234); test_varargs_macros (); // Check flexible array members. struct incomplete_array *ia = malloc (sizeof (struct incomplete_array) + (sizeof (double) * 10)); ia->datasize = 10; for (int i = 0; i < ia->datasize; ++i) ia->data[i] = i * 1.234; // Work around memory leak warnings. free (ia); // Check named initializers. struct named_init ni = { .number = 34, .name = L"Test wide string", .average = 543.34343, }; ni.number = 58; int dynamic_array[ni.number]; dynamic_array[0] = argv[0][0]; dynamic_array[ni.number - 1] = 543; // work around unused variable warnings ok |= (!success || bignum == 0LL || ubignum == 0uLL || newvar[0] == '\''x'\'' || dynamic_array[ni.number - 1] != 543); ' # Test code for whether the C compiler supports C11 (global declarations) ac_c_conftest_c11_globals=' /* Does the compiler advertise C11 conformance? */ #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L # error "Compiler does not advertise C11 conformance" #endif // Check _Alignas. char _Alignas (double) aligned_as_double; char _Alignas (0) no_special_alignment; extern char aligned_as_int; char _Alignas (0) _Alignas (int) aligned_as_int; // Check _Alignof. enum { int_alignment = _Alignof (int), int_array_alignment = _Alignof (int[100]), char_alignment = _Alignof (char) }; _Static_assert (0 < -_Alignof (int), "_Alignof is signed"); // Check _Noreturn. int _Noreturn does_not_return (void) { for (;;) continue; } // Check _Static_assert. struct test_static_assert { int x; _Static_assert (sizeof (int) <= sizeof (long int), "_Static_assert does not work in struct"); long int y; }; // Check UTF-8 literals. #define u8 syntax error! char const utf8_literal[] = u8"happens to be ASCII" "another string"; // Check duplicate typedefs. typedef long *long_ptr; typedef long int *long_ptr; typedef long_ptr long_ptr; // Anonymous structures and unions -- taken from C11 6.7.2.1 Example 1. struct anonymous { union { struct { int i; int j; }; struct { int k; long int l; } w; }; int m; } v1; ' # Test code for whether the C compiler supports C11 (body of main). ac_c_conftest_c11_main=' _Static_assert ((offsetof (struct anonymous, i) == offsetof (struct anonymous, w.k)), "Anonymous union alignment botch"); v1.i = 2; v1.w.k = 5; ok |= v1.i != 5; ' # Test code for whether the C compiler supports C11 (complete). ac_c_conftest_c11_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} ${ac_c_conftest_c11_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} ${ac_c_conftest_c11_main} return ok; } " # Test code for whether the C compiler supports C99 (complete). ac_c_conftest_c99_program="${ac_c_conftest_c89_globals} ${ac_c_conftest_c99_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} ${ac_c_conftest_c99_main} return ok; } " # Test code for whether the C compiler supports C89 (complete). ac_c_conftest_c89_program="${ac_c_conftest_c89_globals} int main (int argc, char **argv) { int ok = 0; ${ac_c_conftest_c89_main} return ok; } " # Test code for whether the C++ compiler supports C++98 (global declarations) ac_cxx_conftest_cxx98_globals=' // Does the compiler advertise C++98 conformance? #if !defined __cplusplus || __cplusplus < 199711L # error "Compiler does not advertise C++98 conformance" #endif // These inclusions are to reject old compilers that // lack the unsuffixed header files. #include #include // and are *not* freestanding headers in C++98. extern void assert (int); namespace std { extern int strcmp (const char *, const char *); } // Namespaces, exceptions, and templates were all added after "C++ 2.0". using std::exception; using std::strcmp; namespace { void test_exception_syntax() { try { throw "test"; } catch (const char *s) { // Extra parentheses suppress a warning when building autoconf itself, // due to lint rules shared with more typical C programs. assert (!(strcmp) (s, "test")); } } template struct test_template { T const val; explicit test_template(T t) : val(t) {} template T add(U u) { return static_cast(u) + val; } }; } // anonymous namespace ' # Test code for whether the C++ compiler supports C++98 (body of main) ac_cxx_conftest_cxx98_main=' assert (argc); assert (! argv[0]); { test_exception_syntax (); test_template tt (2.0); assert (tt.add (4) == 6.0); assert (true && !false); } ' # Test code for whether the C++ compiler supports C++11 (global declarations) ac_cxx_conftest_cxx11_globals=' // Does the compiler advertise C++ 2011 conformance? #if !defined __cplusplus || __cplusplus < 201103L # error "Compiler does not advertise C++11 conformance" #endif namespace cxx11test { constexpr int get_val() { return 20; } struct testinit { int i; double d; }; class delegate { public: delegate(int n) : n(n) {} delegate(): delegate(2354) {} virtual int getval() { return this->n; }; protected: int n; }; class overridden : public delegate { public: overridden(int n): delegate(n) {} virtual int getval() override final { return this->n * 2; } }; class nocopy { public: nocopy(int i): i(i) {} nocopy() = default; nocopy(const nocopy&) = delete; nocopy & operator=(const nocopy&) = delete; private: int i; }; // for testing lambda expressions template Ret eval(Fn f, Ret v) { return f(v); } // for testing variadic templates and trailing return types template auto sum(V first) -> V { return first; } template auto sum(V first, Args... rest) -> V { return first + sum(rest...); } } ' # Test code for whether the C++ compiler supports C++11 (body of main) ac_cxx_conftest_cxx11_main=' { // Test auto and decltype auto a1 = 6538; auto a2 = 48573953.4; auto a3 = "String literal"; int total = 0; for (auto i = a3; *i; ++i) { total += *i; } decltype(a2) a4 = 34895.034; } { // Test constexpr short sa[cxx11test::get_val()] = { 0 }; } { // Test initializer lists cxx11test::testinit il = { 4323, 435234.23544 }; } { // Test range-based for int array[] = {9, 7, 13, 15, 4, 18, 12, 10, 5, 3, 14, 19, 17, 8, 6, 20, 16, 2, 11, 1}; for (auto &x : array) { x += 23; } } { // Test lambda expressions using cxx11test::eval; assert (eval ([](int x) { return x*2; }, 21) == 42); double d = 2.0; assert (eval ([&](double x) { return d += x; }, 3.0) == 5.0); assert (d == 5.0); assert (eval ([=](double x) mutable { return d += x; }, 4.0) == 9.0); assert (d == 5.0); } { // Test use of variadic templates using cxx11test::sum; auto a = sum(1); auto b = sum(1, 2); auto c = sum(1.0, 2.0, 3.0); } { // Test constructor delegation cxx11test::delegate d1; cxx11test::delegate d2(); cxx11test::delegate d3(45); } { // Test override and final cxx11test::overridden o1(55464); } { // Test nullptr char *c = nullptr; } { // Test template brackets test_template<::test_template> v(test_template(12)); } { // Unicode literals char const *utf8 = u8"UTF-8 string \u2500"; char16_t const *utf16 = u"UTF-8 string \u2500"; char32_t const *utf32 = U"UTF-32 string \u2500"; } ' # Test code for whether the C compiler supports C++11 (complete). ac_cxx_conftest_cxx11_program="${ac_cxx_conftest_cxx98_globals} ${ac_cxx_conftest_cxx11_globals} int main (int argc, char **argv) { int ok = 0; ${ac_cxx_conftest_cxx98_main} ${ac_cxx_conftest_cxx11_main} return ok; } " # Test code for whether the C compiler supports C++98 (complete). ac_cxx_conftest_cxx98_program="${ac_cxx_conftest_cxx98_globals} int main (int argc, char **argv) { int ok = 0; ${ac_cxx_conftest_cxx98_main} return ok; } " as_fn_append ac_header_c_list " stdio.h stdio_h HAVE_STDIO_H" as_fn_append ac_header_c_list " stdlib.h stdlib_h HAVE_STDLIB_H" as_fn_append ac_header_c_list " string.h string_h HAVE_STRING_H" as_fn_append ac_header_c_list " inttypes.h inttypes_h HAVE_INTTYPES_H" as_fn_append ac_header_c_list " stdint.h stdint_h HAVE_STDINT_H" as_fn_append ac_header_c_list " strings.h strings_h HAVE_STRINGS_H" as_fn_append ac_header_c_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H" as_fn_append ac_header_c_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H" as_fn_append ac_header_c_list " unistd.h unistd_h HAVE_UNISTD_H" # Auxiliary files required by this configure script. ac_aux_files="ltmain.sh compile missing install-sh config.guess config.sub" # Locations in which to look for auxiliary files. ac_aux_dir_candidates="${srcdir}/aux-files" # Search for a directory containing all of the required auxiliary files, # $ac_aux_files, from the $PATH-style list $ac_aux_dir_candidates. # If we don't find one directory that contains all the files we need, # we report the set of missing files from the *first* directory in # $ac_aux_dir_candidates and give up. ac_missing_aux_files="" ac_first_candidate=: printf "%s\n" "$as_me:${as_lineno-$LINENO}: looking for aux files: $ac_aux_files" >&5 as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in $ac_aux_dir_candidates do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac as_found=: printf "%s\n" "$as_me:${as_lineno-$LINENO}: trying $as_dir" >&5 ac_aux_dir_found=yes ac_install_sh= for ac_aux in $ac_aux_files do # As a special case, if "install-sh" is required, that requirement # can be satisfied by any of "install-sh", "install.sh", or "shtool", # and $ac_install_sh is set appropriately for whichever one is found. if test x"$ac_aux" = x"install-sh" then if test -f "${as_dir}install-sh"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install-sh found" >&5 ac_install_sh="${as_dir}install-sh -c" elif test -f "${as_dir}install.sh"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}install.sh found" >&5 ac_install_sh="${as_dir}install.sh -c" elif test -f "${as_dir}shtool"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}shtool found" >&5 ac_install_sh="${as_dir}shtool install -c" else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} install-sh" else break fi fi else if test -f "${as_dir}${ac_aux}"; then printf "%s\n" "$as_me:${as_lineno-$LINENO}: ${as_dir}${ac_aux} found" >&5 else ac_aux_dir_found=no if $ac_first_candidate; then ac_missing_aux_files="${ac_missing_aux_files} ${ac_aux}" else break fi fi fi done if test "$ac_aux_dir_found" = yes; then ac_aux_dir="$as_dir" break fi ac_first_candidate=false as_found=false done IFS=$as_save_IFS if $as_found then : else case e in #( e) as_fn_error $? "cannot find required auxiliary files:$ac_missing_aux_files" "$LINENO" 5 ;; esac fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. if test -f "${ac_aux_dir}config.guess"; then ac_config_guess="$SHELL ${ac_aux_dir}config.guess" fi if test -f "${ac_aux_dir}config.sub"; then ac_config_sub="$SHELL ${ac_aux_dir}config.sub" fi if test -f "$ac_aux_dir/configure"; then ac_configure="$SHELL ${ac_aux_dir}configure" fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 printf "%s\n" "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then # differences in whitespace do not lead to failure. ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 printf "%s\n" "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 printf "%s\n" "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 printf "%s\n" "$as_me: former value: '$ac_old_val'" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) as_fn_append ac_configure_args " '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## ## -------------------- ## ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu # Make sure we can run config.sub. $SHELL "${ac_aux_dir}config.sub" sun4 >/dev/null 2>&1 || as_fn_error $? "cannot run $SHELL ${ac_aux_dir}config.sub" "$LINENO" 5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 printf %s "checking build system type... " >&6; } if test ${ac_cv_build+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_build_alias=$build_alias test "x$ac_build_alias" = x && ac_build_alias=`$SHELL "${ac_aux_dir}config.guess"` test "x$ac_build_alias" = x && as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 ac_cv_build=`$SHELL "${ac_aux_dir}config.sub" $ac_build_alias` || as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $ac_build_alias failed" "$LINENO" 5 ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 printf "%s\n" "$ac_cv_build" >&6; } case $ac_cv_build in *-*-*) ;; *) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; esac build=$ac_cv_build ac_save_IFS=$IFS; IFS='-' set x $ac_cv_build shift build_cpu=$1 build_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: build_os=$* IFS=$ac_save_IFS case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 printf %s "checking host system type... " >&6; } if test ${ac_cv_host+y} then : printf %s "(cached) " >&6 else case e in #( e) if test "x$host_alias" = x; then ac_cv_host=$ac_cv_build else ac_cv_host=`$SHELL "${ac_aux_dir}config.sub" $host_alias` || as_fn_error $? "$SHELL ${ac_aux_dir}config.sub $host_alias failed" "$LINENO" 5 fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 printf "%s\n" "$ac_cv_host" >&6; } case $ac_cv_host in *-*-*) ;; *) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; esac host=$ac_cv_host ac_save_IFS=$IFS; IFS='-' set x $ac_cv_host shift host_cpu=$1 host_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: host_os=$* IFS=$ac_save_IFS case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac am__api_version='1.18' # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. # Reject install programs that cannot install multiple files. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 printf %s "checking for a BSD-compatible install... " >&6; } if test -z "$INSTALL"; then if test ${ac_cv_path_install+y} then : printf %s "(cached) " >&6 else case e in #( e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac # Account for fact that we put trailing slashes in our PATH walk. case $as_dir in #(( ./ | /[cC]/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ /usr/ucb/* ) ;; *) # OSF1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext"; then if test $ac_prog = install && grep dspmsg "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else rm -rf conftest.one conftest.two conftest.dir echo one > conftest.one echo two > conftest.two mkdir conftest.dir if "$as_dir$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir/" && test -s conftest.one && test -s conftest.two && test -s conftest.dir/conftest.one && test -s conftest.dir/conftest.two then ac_cv_path_install="$as_dir$ac_prog$ac_exec_ext -c" break 3 fi fi fi done done ;; esac done IFS=$as_save_IFS rm -rf conftest.one conftest.two conftest.dir ;; esac fi if test ${ac_cv_path_install+y}; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 printf "%s\n" "$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether sleep supports fractional seconds" >&5 printf %s "checking whether sleep supports fractional seconds... " >&6; } if test ${am_cv_sleep_fractional_seconds+y} then : printf %s "(cached) " >&6 else case e in #( e) if sleep 0.001 2>/dev/null then : am_cv_sleep_fractional_seconds=yes else case e in #( e) am_cv_sleep_fractional_seconds=no ;; esac fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_sleep_fractional_seconds" >&5 printf "%s\n" "$am_cv_sleep_fractional_seconds" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking filesystem timestamp resolution" >&5 printf %s "checking filesystem timestamp resolution... " >&6; } if test ${am_cv_filesystem_timestamp_resolution+y} then : printf %s "(cached) " >&6 else case e in #( e) # Default to the worst case. am_cv_filesystem_timestamp_resolution=2 # Only try to go finer than 1 sec if sleep can do it. # Don't try 1 sec, because if 0.01 sec and 0.1 sec don't work, # - 1 sec is not much of a win compared to 2 sec, and # - it takes 2 seconds to perform the test whether 1 sec works. # # Instead, just use the default 2s on platforms that have 1s resolution, # accept the extra 1s delay when using $sleep in the Automake tests, in # exchange for not incurring the 2s delay for running the test for all # packages. # am_try_resolutions= if test "$am_cv_sleep_fractional_seconds" = yes; then # Even a millisecond often causes a bunch of false positives, # so just try a hundredth of a second. The time saved between .001 and # .01 is not terribly consequential. am_try_resolutions="0.01 0.1 $am_try_resolutions" fi # In order to catch current-generation FAT out, we must *modify* files # that already exist; the *creation* timestamp is finer. Use names # that make ls -t sort them differently when they have equal # timestamps than when they have distinct timestamps, keeping # in mind that ls -t prints the *newest* file first. rm -f conftest.ts? : > conftest.ts1 : > conftest.ts2 : > conftest.ts3 # Make sure ls -t actually works. Do 'set' in a subshell so we don't # clobber the current shell's arguments. (Outer-level square brackets # are removed by m4; they're present so that m4 does not expand # ; be careful, easy to get confused.) if ( set X `ls -t conftest.ts[12]` && { test "$*" != "X conftest.ts1 conftest.ts2" || test "$*" != "X conftest.ts2 conftest.ts1"; } ); then :; else # If neither matched, then we have a broken ls. This can happen # if, for instance, CONFIG_SHELL is bash and it inherits a # broken ls alias from the environment. This has actually # happened. Such a system could not be considered "sane". printf "%s\n" ""Bad output from ls -t: \"`ls -t conftest.ts[12]`\""" >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "ls -t produces unexpected output. Make sure there is not a broken ls alias in your environment. See 'config.log' for more details" "$LINENO" 5; } fi for am_try_res in $am_try_resolutions; do # Any one fine-grained sleep might happen to cross the boundary # between two values of a coarser actual resolution, but if we do # two fine-grained sleeps in a row, at least one of them will fall # entirely within a coarse interval. echo alpha > conftest.ts1 sleep $am_try_res echo beta > conftest.ts2 sleep $am_try_res echo gamma > conftest.ts3 # We assume that 'ls -t' will make use of high-resolution # timestamps if the operating system supports them at all. if (set X `ls -t conftest.ts?` && test "$2" = conftest.ts3 && test "$3" = conftest.ts2 && test "$4" = conftest.ts1); then # # Ok, ls -t worked. If we're at a resolution of 1 second, we're done, # because we don't need to test make. make_ok=true if test $am_try_res != 1; then # But if we've succeeded so far with a subsecond resolution, we # have one more thing to check: make. It can happen that # everything else supports the subsecond mtimes, but make doesn't; # notably on macOS, which ships make 3.81 from 2006 (the last one # released under GPLv2). https://bugs.gnu.org/68808 # # We test $MAKE if it is defined in the environment, else "make". # It might get overridden later, but our hope is that in practice # it does not matter: it is the system "make" which is (by far) # the most likely to be broken, whereas if the user overrides it, # probably they did so with a better, or at least not worse, make. # https://lists.gnu.org/archive/html/automake/2024-06/msg00051.html # # Create a Makefile (real tab character here): rm -f conftest.mk echo 'conftest.ts1: conftest.ts2' >conftest.mk echo ' touch conftest.ts2' >>conftest.mk # # Now, running # touch conftest.ts1; touch conftest.ts2; make # should touch ts1 because ts2 is newer. This could happen by luck, # but most often, it will fail if make's support is insufficient. So # test for several consecutive successes. # # (We reuse conftest.ts[12] because we still want to modify existing # files, not create new ones, per above.) n=0 make=${MAKE-make} until test $n -eq 3; do echo one > conftest.ts1 sleep $am_try_res echo two > conftest.ts2 # ts2 should now be newer than ts1 if $make -f conftest.mk | grep 'up to date' >/dev/null; then make_ok=false break # out of $n loop fi n=`expr $n + 1` done fi # if $make_ok; then # Everything we know to check worked out, so call this resolution good. am_cv_filesystem_timestamp_resolution=$am_try_res break # out of $am_try_res loop fi # Otherwise, we'll go on to check the next resolution. fi done rm -f conftest.ts? # (end _am_filesystem_timestamp_resolution) ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_filesystem_timestamp_resolution" >&5 printf "%s\n" "$am_cv_filesystem_timestamp_resolution" >&6; } # This check should not be cached, as it may vary across builds of # different projects. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5 printf %s "checking whether build environment is sane... " >&6; } # Reject unsafe characters in $srcdir or the absolute working directory # name. Accept space and tab only in the latter. am_lf=' ' case `pwd` in *[\\\"\#\$\&\'\`$am_lf]*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;; esac case $srcdir in *[\\\"\#\$\&\'\`$am_lf\ \ ]*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } as_fn_error $? "unsafe srcdir value: '$srcdir'" "$LINENO" 5;; esac # Do 'set' in a subshell so we don't clobber the current shell's # arguments. Must try -L first in case configure is actually a # symlink; some systems play weird games with the mod time of symlinks # (eg FreeBSD returns the mod time of the symlink's containing # directory). am_build_env_is_sane=no am_has_slept=no rm -f conftest.file for am_try in 1 2; do echo "timestamp, slept: $am_has_slept" > conftest.file if ( set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` if test "$*" = "X"; then # -L didn't work. set X `ls -t "$srcdir/configure" conftest.file` fi test "$2" = conftest.file ); then am_build_env_is_sane=yes break fi # Just in case. sleep "$am_cv_filesystem_timestamp_resolution" am_has_slept=yes done { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_build_env_is_sane" >&5 printf "%s\n" "$am_build_env_is_sane" >&6; } if test "$am_build_env_is_sane" = no; then as_fn_error $? "newly created file is older than distributed files! Check your system clock" "$LINENO" 5 fi # If we didn't sleep, we still need to ensure time stamps of config.status and # generated files are strictly newer. am_sleep_pid= if test -e conftest.file || grep 'slept: no' conftest.file >/dev/null 2>&1 then : else case e in #( e) ( sleep "$am_cv_filesystem_timestamp_resolution" ) & am_sleep_pid=$! ;; esac fi rm -f conftest.file test "$program_prefix" != NONE && program_transform_name="s&^&$program_prefix&;$program_transform_name" # Use a double $ so make ignores it. test "$program_suffix" != NONE && program_transform_name="s&\$&$program_suffix&;$program_transform_name" # Double any \ or $. # By default was 's,x,x', remove it if useless. ac_script='s/[\\$]/&&/g;s/;s,x,x,$//' program_transform_name=`printf "%s\n" "$program_transform_name" | sed "$ac_script"` # Expand $ac_aux_dir to an absolute path. am_aux_dir=`cd "$ac_aux_dir" && pwd` if test x"${MISSING+set}" != xset; then MISSING="\${SHELL} '$am_aux_dir/missing'" fi # Use eval to expand $SHELL if eval "$MISSING --is-lightweight"; then am_missing_run="$MISSING " else am_missing_run= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5 printf "%s\n" "$as_me: WARNING: 'missing' script is too old or missing" >&2;} fi if test x"${install_sh+set}" != xset; then case $am_aux_dir in *\ * | *\ *) install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; *) install_sh="\${SHELL} $am_aux_dir/install-sh" esac fi # Installed binaries are usually stripped using 'strip' when the user # run "make install-strip". However 'strip' might not be the right # tool to use in cross-compilation environments, therefore Automake # will honor the 'STRIP' environment variable to overrule this program. if test "$cross_compiling" != no; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. set dummy ${ac_tool_prefix}strip; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_STRIP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$STRIP"; then ac_cv_prog_STRIP="$STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_STRIP="${ac_tool_prefix}strip" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi STRIP=$ac_cv_prog_STRIP if test -n "$STRIP"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 printf "%s\n" "$STRIP" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_STRIP"; then ac_ct_STRIP=$STRIP # Extract the first word of "strip", so it can be a program name with args. set dummy strip; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_STRIP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_STRIP"; then ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_STRIP="strip" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP if test -n "$ac_ct_STRIP"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 printf "%s\n" "$ac_ct_STRIP" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_STRIP" = x; then STRIP=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac STRIP=$ac_ct_STRIP fi else STRIP="$ac_cv_prog_STRIP" fi fi INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a race-free mkdir -p" >&5 printf %s "checking for a race-free mkdir -p... " >&6; } if test -z "$MKDIR_P"; then if test ${ac_cv_path_mkdir+y} then : printf %s "(cached) " >&6 else case e in #( e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in mkdir gmkdir; do for ac_exec_ext in '' $ac_executable_extensions; do as_fn_executable_p "$as_dir$ac_prog$ac_exec_ext" || continue case `"$as_dir$ac_prog$ac_exec_ext" --version 2>&1` in #( 'mkdir ('*'coreutils) '* | \ *'BusyBox '* | \ 'mkdir (fileutils) '4.1*) ac_cv_path_mkdir=$as_dir$ac_prog$ac_exec_ext break 3;; esac done done done IFS=$as_save_IFS ;; esac fi test -d ./--version && rmdir ./--version if test ${ac_cv_path_mkdir+y}; then MKDIR_P="$ac_cv_path_mkdir -p" else # As a last resort, use plain mkdir -p, # in the hope it doesn't have the bugs of ancient mkdir. MKDIR_P='mkdir -p' fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5 printf "%s\n" "$MKDIR_P" >&6; } for ac_prog in gawk mawk nawk awk do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_AWK+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$AWK"; then ac_cv_prog_AWK="$AWK" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_AWK="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi AWK=$ac_cv_prog_AWK if test -n "$AWK"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 printf "%s\n" "$AWK" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$AWK" && break done { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 printf %s "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } set x ${MAKE-make} ac_make=`printf "%s\n" "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` if eval test \${ac_cv_prog_make_${ac_make}_set+y} then : printf %s "(cached) " >&6 else case e in #( e) cat >conftest.make <<\_ACEOF SHELL = /bin/sh all: @echo '@@@%%%=$(MAKE)=@@@%%%' _ACEOF # GNU make sometimes prints "make[1]: Entering ...", which would confuse us. case `${MAKE-make} -f conftest.make 2>/dev/null` in *@@@%%%=?*=@@@%%%*) eval ac_cv_prog_make_${ac_make}_set=yes;; *) eval ac_cv_prog_make_${ac_make}_set=no;; esac rm -f conftest.make ;; esac fi if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } SET_MAKE= else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } SET_MAKE="MAKE=${MAKE-make}" fi rm -rf .tst 2>/dev/null mkdir .tst 2>/dev/null if test -d .tst; then am__leading_dot=. else am__leading_dot=_ fi rmdir .tst 2>/dev/null AM_DEFAULT_VERBOSITY=1 # Check whether --enable-silent-rules was given. if test ${enable_silent_rules+y} then : enableval=$enable_silent_rules; fi am_make=${MAKE-make} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5 printf %s "checking whether $am_make supports nested variables... " >&6; } if test ${am_cv_make_support_nested_variables+y} then : printf %s "(cached) " >&6 else case e in #( e) if printf "%s\n" 'TRUE=$(BAR$(V)) BAR0=false BAR1=true V=1 am__doit: @$(TRUE) .PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then am_cv_make_support_nested_variables=yes else am_cv_make_support_nested_variables=no fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5 printf "%s\n" "$am_cv_make_support_nested_variables" >&6; } AM_BACKSLASH='\' am__rm_f_notfound= if (rm -f && rm -fr && rm -rf) 2>/dev/null then : else case e in #( e) am__rm_f_notfound='""' ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking xargs -n works" >&5 printf %s "checking xargs -n works... " >&6; } if test ${am_cv_xargs_n_works+y} then : printf %s "(cached) " >&6 else case e in #( e) if test "`echo 1 2 3 | xargs -n2 echo`" = "1 2 3" then : am_cv_xargs_n_works=yes else case e in #( e) am_cv_xargs_n_works=no ;; esac fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_xargs_n_works" >&5 printf "%s\n" "$am_cv_xargs_n_works" >&6; } if test "$am_cv_xargs_n_works" = yes then : am__xargs_n='xargs -n' else case e in #( e) am__xargs_n='am__xargs_n () { shift; sed "s/ /\\n/g" | while read am__xargs_n_arg; do "" "$am__xargs_n_arg"; done; }' ;; esac fi if test "`cd $srcdir && pwd`" != "`pwd`"; then # Use -I$(srcdir) only when $(srcdir) != ., so that make's output # is not polluted with repeated "-I." am__isrc=' -I$(srcdir)' # test to see if srcdir already configured if test -f $srcdir/config.status; then as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5 fi fi # test whether we have cygpath if test -z "$CYGPATH_W"; then if (cygpath --version) >/dev/null 2>/dev/null; then CYGPATH_W='cygpath -w' else CYGPATH_W=echo fi fi # Define the identity of the package. PACKAGE='seq66' VERSION='0.99.24' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h printf "%s\n" "#define VERSION \"$VERSION\"" >>confdefs.h # Some tools Automake needs. ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} # For better backward compatibility. To be removed once Automake 1.9.x # dies out for good. For more background, see: # # mkdir_p='$(MKDIR_P)' # We need awk for the "check" target (and possibly the TAP driver). The # system "awk" is bad on some platforms. # Always define AMTAR for backward compatibility. Yes, it's still used # in the wild :-( We should find a proper way to deprecate it ... AMTAR='$${TAR-tar}' # We'll loop over all known methods to create a tar archive until one works. _am_tools='gnutar plaintar pax cpio none' # The POSIX 1988 'ustar' format is defined with fixed-size fields. # There is notably a 21 bits limit for the UID and the GID. In fact, # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343 # and bug#13588). am_max_uid=2097151 # 2^21 - 1 am_max_gid=$am_max_uid # The $UID and $GID variables are not portable, so we need to resort # to the POSIX-mandated id(1) utility. Errors in the 'id' calls # below are definitely unexpected, so allow the users to see them # (that is, avoid stderr redirection). am_uid=`id -u || echo unknown` am_gid=`id -g || echo unknown` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether UID '$am_uid' is supported by ustar format" >&5 printf %s "checking whether UID '$am_uid' is supported by ustar format... " >&6; } if test x$am_uid = xunknown; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: ancient id detected; assuming current UID is ok, but dist-ustar might not work" >&5 printf "%s\n" "$as_me: WARNING: ancient id detected; assuming current UID is ok, but dist-ustar might not work" >&2;} elif test $am_uid -le $am_max_uid; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } _am_tools=none fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether GID '$am_gid' is supported by ustar format" >&5 printf %s "checking whether GID '$am_gid' is supported by ustar format... " >&6; } if test x$gm_gid = xunknown; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: ancient id detected; assuming current GID is ok, but dist-ustar might not work" >&5 printf "%s\n" "$as_me: WARNING: ancient id detected; assuming current GID is ok, but dist-ustar might not work" >&2;} elif test $am_gid -le $am_max_gid; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } _am_tools=none fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to create a ustar tar archive" >&5 printf %s "checking how to create a ustar tar archive... " >&6; } # Go ahead even if we have the value already cached. We do so because we # need to set the values for the 'am__tar' and 'am__untar' variables. _am_tools=${am_cv_prog_tar_ustar-$_am_tools} for _am_tool in $_am_tools; do case $_am_tool in gnutar) for _am_tar in tar gnutar gtar; do { echo "$as_me:$LINENO: $_am_tar --version" >&5 ($_am_tar --version) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && break done am__tar="$_am_tar --format=ustar -chf - "'"$$tardir"' am__tar_="$_am_tar --format=ustar -chf - "'"$tardir"' am__untar="$_am_tar -xf -" ;; plaintar) # Must skip GNU tar: if it does not support --format= it doesn't create # ustar tarball either. (tar --version) >/dev/null 2>&1 && continue am__tar='tar chf - "$$tardir"' am__tar_='tar chf - "$tardir"' am__untar='tar xf -' ;; pax) am__tar='pax -L -x ustar -w "$$tardir"' am__tar_='pax -L -x ustar -w "$tardir"' am__untar='pax -r' ;; cpio) am__tar='find "$$tardir" -print | cpio -o -H ustar -L' am__tar_='find "$tardir" -print | cpio -o -H ustar -L' am__untar='cpio -i -H ustar -d' ;; none) am__tar=false am__tar_=false am__untar=false ;; esac # If the value was cached, stop now. We just wanted to have am__tar # and am__untar set. test -n "${am_cv_prog_tar_ustar}" && break # tar/untar a dummy directory, and stop if the command works. rm -rf conftest.dir mkdir conftest.dir echo GrepMe > conftest.dir/file { echo "$as_me:$LINENO: tardir=conftest.dir && eval $am__tar_ >conftest.tar" >&5 (tardir=conftest.dir && eval $am__tar_ >conftest.tar) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } rm -rf conftest.dir if test -s conftest.tar; then { echo "$as_me:$LINENO: $am__untar &5 ($am__untar &5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } { echo "$as_me:$LINENO: cat conftest.dir/file" >&5 (cat conftest.dir/file) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } grep GrepMe conftest.dir/file >/dev/null 2>&1 && break fi done rm -rf conftest.dir if test ${am_cv_prog_tar_ustar+y} then : printf %s "(cached) " >&6 else case e in #( e) am_cv_prog_tar_ustar=$_am_tool ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_tar_ustar" >&5 printf "%s\n" "$am_cv_prog_tar_ustar" >&6; } # Variables for tags utilities; see am/tags.am if test -z "$CTAGS"; then CTAGS=ctags fi if test -z "$ETAGS"; then ETAGS=etags fi if test -z "$CSCOPE"; then CSCOPE=cscope fi printf "%s\n" "#define _GNU_SOURCE 1" >>confdefs.h ac_config_headers="$ac_config_headers include/config.h" export PKG_CONFIG=$(which pkg-config) ac_config_commands="$ac_config_commands include/seq66-config.h" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu if test -n "$ac_tool_prefix"; then for ac_prog in gcc clang llvm-gcc do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CC"; then ac_cv_prog_CC="$CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CC=$ac_cv_prog_CC if test -n "$CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 printf "%s\n" "$CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$CC" && break done fi if test -z "$CC"; then ac_ct_CC=$CC for ac_prog in gcc clang llvm-gcc do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CC"; then ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CC=$ac_cv_prog_ac_ct_CC if test -n "$ac_ct_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 printf "%s\n" "$ac_ct_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$ac_ct_CC" && break done if test "x$ac_ct_CC" = x; then CC="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CC=$ac_ct_CC fi fi test -z "$CC" && { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "no acceptable C compiler found in \$PATH See 'config.log' for more details" "$LINENO" 5; } # Provide some information about the compiler. printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion -version; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 printf %s "checking whether the C compiler works... " >&6; } ac_link_default=`printf "%s\n" "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { { ac_try="$ac_link_default" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. # So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an '-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else case e in #( e) ac_file='' ;; esac fi if test -z "$ac_file" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "C compiler cannot create executables See 'config.log' for more details" "$LINENO" 5; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 printf %s "checking for C compiler default output file name... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 printf "%s\n" "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 printf %s "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) # catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will # work properly (i.e., refer to 'conftest.exe'), while it won't with # 'rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else case e in #( e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link See 'config.log' for more details" "$LINENO" 5; } ;; esac fi rm -f conftest conftest$ac_cv_exeext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 printf "%s\n" "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { FILE *f = fopen ("conftest.out", "w"); if (!f) return 1; return ferror (f) || fclose (f) != 0; ; return 0; } _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 printf %s "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "cannot run C compiled programs. If you meant to cross compile, use '--host'. See 'config.log' for more details" "$LINENO" 5; } fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 printf "%s\n" "$cross_compiling" >&6; } rm -f conftest.$ac_ext conftest$ac_cv_exeext \ conftest.o conftest.obj conftest.out ac_clean_files=$ac_clean_files_save { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 printf %s "checking for suffix of object files... " >&6; } if test ${ac_cv_objext+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else case e in #( e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile See 'config.log' for more details" "$LINENO" 5; } ;; esac fi rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 printf "%s\n" "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C" >&5 printf %s "checking whether the compiler supports GNU C... " >&6; } if test ${ac_cv_c_compiler_gnu+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_compiler_gnu=yes else case e in #( e) ac_compiler_gnu=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_c_compiler_gnu=$ac_compiler_gnu ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 printf "%s\n" "$ac_cv_c_compiler_gnu" >&6; } ac_compiler_gnu=$ac_cv_c_compiler_gnu if test $ac_compiler_gnu = yes; then GCC=yes else GCC= fi ac_test_CFLAGS=${CFLAGS+y} ac_save_CFLAGS=$CFLAGS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 printf %s "checking whether $CC accepts -g... " >&6; } if test ${ac_cv_prog_cc_g+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_save_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes ac_cv_prog_cc_g=no CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes else case e in #( e) CFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : else case e in #( e) ac_c_werror_flag=$ac_save_c_werror_flag CFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_c_werror_flag=$ac_save_c_werror_flag ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 printf "%s\n" "$ac_cv_prog_cc_g" >&6; } if test $ac_test_CFLAGS; then CFLAGS=$ac_save_CFLAGS elif test $ac_cv_prog_cc_g = yes; then if test "$GCC" = yes; then CFLAGS="-g -O2" else CFLAGS="-g" fi else if test "$GCC" = yes; then CFLAGS="-O2" else CFLAGS= fi fi ac_prog_cc_stdc=no if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C11 features" >&5 printf %s "checking for $CC option to enable C11 features... " >&6; } if test ${ac_cv_prog_cc_c11+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c11=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c11_program _ACEOF for ac_arg in '' -std=gnu11 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c11=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c11" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c11" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c11" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c11" >&5 printf "%s\n" "$ac_cv_prog_cc_c11" >&6; } CC="$CC $ac_cv_prog_cc_c11" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c11 ac_prog_cc_stdc=c11 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C99 features" >&5 printf %s "checking for $CC option to enable C99 features... " >&6; } if test ${ac_cv_prog_cc_c99+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c99=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c99_program _ACEOF for ac_arg in '' -std=gnu99 -std=c99 -c99 -qlanglvl=extc1x -qlanglvl=extc99 -AC99 -D_STDC_C99= do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c99=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c99" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c99" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c99" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c99" >&5 printf "%s\n" "$ac_cv_prog_cc_c99" >&6; } CC="$CC $ac_cv_prog_cc_c99" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 ac_prog_cc_stdc=c99 ;; esac fi fi if test x$ac_prog_cc_stdc = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CC option to enable C89 features" >&5 printf %s "checking for $CC option to enable C89 features... " >&6; } if test ${ac_cv_prog_cc_c89+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cc_c89=no ac_save_CC=$CC cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_c_conftest_c89_program _ACEOF for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO" then : ac_cv_prog_cc_c89=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cc_c89" != "xno" && break done rm -f conftest.$ac_ext CC=$ac_save_CC ;; esac fi if test "x$ac_cv_prog_cc_c89" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cc_c89" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 printf "%s\n" "$ac_cv_prog_cc_c89" >&6; } CC="$CC $ac_cv_prog_cc_c89" ;; esac fi ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 ac_prog_cc_stdc=c89 ;; esac fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5 printf %s "checking whether $CC understands -c and -o together... " >&6; } if test ${am_cv_prog_cc_c_o+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF # Make sure it works both with $CC and with simple cc. # Following AC_PROG_CC_C_O, we do the test twice because some # compilers refuse to overwrite an existing .o file with -o, # though they will create one. am_cv_prog_cc_c_o=yes for am_i in 1 2; do if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5 ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } \ && test -f conftest2.$ac_objext; then : OK else am_cv_prog_cc_c_o=no break fi done # aligned with autoconf, so not including core; see bug#72225. rm -f -r a.out a.exe b.out conftest.$ac_ext conftest.$ac_objext \ conftest.dSYM conftest1.$ac_ext conftest1.$ac_objext conftest1.dSYM \ conftest2.$ac_ext conftest2.$ac_objext conftest2.dSYM unset am_i ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5 printf "%s\n" "$am_cv_prog_cc_c_o" >&6; } if test "$am_cv_prog_cc_c_o" != yes; then # Losing compiler, so override with the script. # FIXME: It is wrong to rewrite CC. # But if we don't then we get into trouble of one sort or another. # A longer-term fix would be to have automake use am__CC in this case, # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" CC="$am_aux_dir/compile $CC" fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu DEPDIR="${am__leading_dot}deps" ac_config_commands="$ac_config_commands depfiles" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} supports the include directive" >&5 printf %s "checking whether ${MAKE-make} supports the include directive... " >&6; } cat > confinc.mk << 'END' am__doit: @echo this is the am__doit target >confinc.out .PHONY: am__doit END am__include="#" am__quote= # BSD make does it like this. echo '.include "confinc.mk" # ignored' > confmf.BSD # Other make implementations (GNU, Solaris 10, AIX) do it like this. echo 'include confinc.mk # ignored' > confmf.GNU _am_result=no for s in GNU BSD; do { echo "$as_me:$LINENO: ${MAKE-make} -f confmf.$s && cat confinc.out" >&5 (${MAKE-make} -f confmf.$s && cat confinc.out) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } case $?:`cat confinc.out 2>/dev/null` in #( '0:this is the am__doit target') : case $s in #( BSD) : am__include='.include' am__quote='"' ;; #( *) : am__include='include' am__quote='' ;; esac ;; #( *) : ;; esac if test "$am__include" != "#"; then _am_result="yes ($s style)" break fi done rm -f confinc.* confmf.* { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${_am_result}" >&5 printf "%s\n" "${_am_result}" >&6; } # Check whether --enable-dependency-tracking was given. if test ${enable_dependency_tracking+y} then : enableval=$enable_dependency_tracking; fi if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' am__nodep='_no' fi if test "x$enable_dependency_tracking" != xno; then AMDEP_TRUE= AMDEP_FALSE='#' else AMDEP_TRUE='#' AMDEP_FALSE= fi depcc="$CC" am_compiler_list= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 printf %s "checking dependency style of $depcc... " >&6; } if test ${am_cv_CC_dependencies_compiler_type+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named 'D' -- because '-MD' means "put the output # in D". rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_CC_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` fi am__universal=false case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with # Solaris 10 /bin/sh. echo '/* dummy */' > sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf # We check with '-c' and '-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle '-M -o', and we need to detect this. Also, some Intel # versions had trouble with output in subdirs. am__obj=sub/conftest.${OBJEXT-o} am__minus_obj="-o $am__obj" case $depmode in gcc) # This depmode causes a compiler race in universal mode. test "$am__universal" = false || continue ;; nosideeffect) # After this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested. if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok '-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. am__obj=conftest.${OBJEXT-o} am__minus_obj= ;; none) break ;; esac if depmode=$depmode \ source=sub/conftest.c object=$am__obj \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep $am__obj sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thus: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_CC_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_CC_dependencies_compiler_type=none fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 printf "%s\n" "$am_cv_CC_dependencies_compiler_type" >&6; } CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type if test "x$enable_dependency_tracking" != xno \ && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then am__fastdepCC_TRUE= am__fastdepCC_FALSE='#' else am__fastdepCC_TRUE='#' am__fastdepCC_FALSE= fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu if test -z "$CXX"; then if test -n "$CCC"; then CXX=$CCC else if test -n "$ac_tool_prefix"; then for ac_prog in g++ clang++ llvm-g++ do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$CXX"; then ac_cv_prog_CXX="$CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi CXX=$ac_cv_prog_CXX if test -n "$CXX"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5 printf "%s\n" "$CXX" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$CXX" && break done fi if test -z "$CXX"; then ac_ct_CXX=$CXX for ac_prog in g++ clang++ llvm-g++ do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_CXX"; then ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CXX="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_CXX=$ac_cv_prog_ac_ct_CXX if test -n "$ac_ct_CXX"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5 printf "%s\n" "$ac_ct_CXX" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$ac_ct_CXX" && break done if test "x$ac_ct_CXX" = x; then CXX="g++" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CXX=$ac_ct_CXX fi fi fi fi # Provide some information about the compiler. printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion; do { { ac_try="$ac_compiler $ac_option >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then sed '10a\ ... rest of stderr output deleted ... 10q' conftest.err >conftest.er1 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C++" >&5 printf %s "checking whether the compiler supports GNU C++... " >&6; } if test ${ac_cv_cxx_compiler_gnu+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : ac_compiler_gnu=yes else case e in #( e) ac_compiler_gnu=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_cxx_compiler_gnu=$ac_compiler_gnu ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5 printf "%s\n" "$ac_cv_cxx_compiler_gnu" >&6; } ac_compiler_gnu=$ac_cv_cxx_compiler_gnu if test $ac_compiler_gnu = yes; then GXX=yes else GXX= fi ac_test_CXXFLAGS=${CXXFLAGS+y} ac_save_CXXFLAGS=$CXXFLAGS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5 printf %s "checking whether $CXX accepts -g... " >&6; } if test ${ac_cv_prog_cxx_g+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_save_cxx_werror_flag=$ac_cxx_werror_flag ac_cxx_werror_flag=yes ac_cv_prog_cxx_g=no CXXFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : ac_cv_prog_cxx_g=yes else case e in #( e) CXXFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : else case e in #( e) ac_cxx_werror_flag=$ac_save_cxx_werror_flag CXXFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : ac_cv_prog_cxx_g=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cxx_werror_flag=$ac_save_cxx_werror_flag ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5 printf "%s\n" "$ac_cv_prog_cxx_g" >&6; } if test $ac_test_CXXFLAGS; then CXXFLAGS=$ac_save_CXXFLAGS elif test $ac_cv_prog_cxx_g = yes; then if test "$GXX" = yes; then CXXFLAGS="-g -O2" else CXXFLAGS="-g" fi else if test "$GXX" = yes; then CXXFLAGS="-O2" else CXXFLAGS= fi fi ac_prog_cxx_stdcxx=no if test x$ac_prog_cxx_stdcxx = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CXX option to enable C++11 features" >&5 printf %s "checking for $CXX option to enable C++11 features... " >&6; } if test ${ac_cv_prog_cxx_cxx11+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cxx_cxx11=no ac_save_CXX=$CXX cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_cxx_conftest_cxx11_program _ACEOF for ac_arg in '' -std=gnu++11 -std=gnu++0x -std=c++11 -std=c++0x -qlanglvl=extended0x -AA do CXX="$ac_save_CXX $ac_arg" if ac_fn_cxx_try_compile "$LINENO" then : ac_cv_prog_cxx_cxx11=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cxx_cxx11" != "xno" && break done rm -f conftest.$ac_ext CXX=$ac_save_CXX ;; esac fi if test "x$ac_cv_prog_cxx_cxx11" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cxx_cxx11" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_cxx11" >&5 printf "%s\n" "$ac_cv_prog_cxx_cxx11" >&6; } CXX="$CXX $ac_cv_prog_cxx_cxx11" ;; esac fi ac_cv_prog_cxx_stdcxx=$ac_cv_prog_cxx_cxx11 ac_prog_cxx_stdcxx=cxx11 ;; esac fi fi if test x$ac_prog_cxx_stdcxx = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CXX option to enable C++98 features" >&5 printf %s "checking for $CXX option to enable C++98 features... " >&6; } if test ${ac_cv_prog_cxx_cxx98+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_cv_prog_cxx_cxx98=no ac_save_CXX=$CXX cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $ac_cxx_conftest_cxx98_program _ACEOF for ac_arg in '' -std=gnu++98 -std=c++98 -qlanglvl=extended -AA do CXX="$ac_save_CXX $ac_arg" if ac_fn_cxx_try_compile "$LINENO" then : ac_cv_prog_cxx_cxx98=$ac_arg fi rm -f core conftest.err conftest.$ac_objext conftest.beam test "x$ac_cv_prog_cxx_cxx98" != "xno" && break done rm -f conftest.$ac_ext CXX=$ac_save_CXX ;; esac fi if test "x$ac_cv_prog_cxx_cxx98" = xno then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 printf "%s\n" "unsupported" >&6; } else case e in #( e) if test "x$ac_cv_prog_cxx_cxx98" = x then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 printf "%s\n" "none needed" >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_cxx98" >&5 printf "%s\n" "$ac_cv_prog_cxx_cxx98" >&6; } CXX="$CXX $ac_cv_prog_cxx_cxx98" ;; esac fi ac_cv_prog_cxx_stdcxx=$ac_cv_prog_cxx_cxx98 ac_prog_cxx_stdcxx=cxx98 ;; esac fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu depcc="$CXX" am_compiler_list= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 printf %s "checking dependency style of $depcc... " >&6; } if test ${am_cv_CXX_dependencies_compiler_type+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named 'D' -- because '-MD' means "put the output # in D". rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_CXX_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` fi am__universal=false case " $depcc " in #( *\ -arch\ *\ -arch\ *) am__universal=true ;; esac for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with # Solaris 10 /bin/sh. echo '/* dummy */' > sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf # We check with '-c' and '-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle '-M -o', and we need to detect this. Also, some Intel # versions had trouble with output in subdirs. am__obj=sub/conftest.${OBJEXT-o} am__minus_obj="-o $am__obj" case $depmode in gcc) # This depmode causes a compiler race in universal mode. test "$am__universal" = false || continue ;; nosideeffect) # After this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested. if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok '-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. am__obj=conftest.${OBJEXT-o} am__minus_obj= ;; none) break ;; esac if depmode=$depmode \ source=sub/conftest.c object=$am__obj \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep $am__obj sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thus: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_CXX_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_CXX_dependencies_compiler_type=none fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_cv_CXX_dependencies_compiler_type" >&5 printf "%s\n" "$am_cv_CXX_dependencies_compiler_type" >&6; } CXXDEPMODE=depmode=$am_cv_CXX_dependencies_compiler_type if test "x$enable_dependency_tracking" != xno \ && test "$am_cv_CXX_dependencies_compiler_type" = gcc3; then am__fastdepCXX_TRUE= am__fastdepCXX_FALSE='#' else am__fastdepCXX_TRUE='#' am__fastdepCXX_FALSE= fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 printf %s "checking how to run the C preprocessor... " >&6; } # On Suns, sometimes $CPP names a directory. if test -n "$CPP" && test -d "$CPP"; then CPP= fi if test -z "$CPP"; then if test ${ac_cv_prog_CPP+y} then : printf %s "(cached) " >&6 else case e in #( e) # Double quotes because $CC needs to be expanded for CPP in "$CC -E" "$CC -E -traditional-cpp" cpp /lib/cpp do ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO" then : else case e in #( e) # Broken: fails on valid input. continue ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO" then : # Broken: success on invalid input. continue else case e in #( e) # Passes both tests. ac_preproc_ok=: break ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok then : break fi done ac_cv_prog_CPP=$CPP ;; esac fi CPP=$ac_cv_prog_CPP else ac_cv_prog_CPP=$CPP fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 printf "%s\n" "$CPP" >&6; } ac_preproc_ok=false for ac_c_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include Syntax error _ACEOF if ac_fn_c_try_cpp "$LINENO" then : else case e in #( e) # Broken: fails on valid input. continue ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO" then : # Broken: success on invalid input. continue else case e in #( e) # Passes both tests. ac_preproc_ok=: break ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok then : else case e in #( e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "C preprocessor \"$CPP\" fails sanity check See 'config.log' for more details" "$LINENO" 5; } ;; esac fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to run the C++ preprocessor" >&5 printf %s "checking how to run the C++ preprocessor... " >&6; } if test -z "$CXXCPP"; then if test ${ac_cv_prog_CXXCPP+y} then : printf %s "(cached) " >&6 else case e in #( e) # Double quotes because $CXX needs to be expanded for CXXCPP in "$CXX -E" cpp /lib/cpp do ac_preproc_ok=false for ac_cxx_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include Syntax error _ACEOF if ac_fn_cxx_try_cpp "$LINENO" then : else case e in #( e) # Broken: fails on valid input. continue ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_cxx_try_cpp "$LINENO" then : # Broken: success on invalid input. continue else case e in #( e) # Passes both tests. ac_preproc_ok=: break ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok then : break fi done ac_cv_prog_CXXCPP=$CXXCPP ;; esac fi CXXCPP=$ac_cv_prog_CXXCPP else ac_cv_prog_CXXCPP=$CXXCPP fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CXXCPP" >&5 printf "%s\n" "$CXXCPP" >&6; } ac_preproc_ok=false for ac_cxx_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include Syntax error _ACEOF if ac_fn_cxx_try_cpp "$LINENO" then : else case e in #( e) # Broken: fails on valid input. continue ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_cxx_try_cpp "$LINENO" then : # Broken: success on invalid input. continue else case e in #( e) # Passes both tests. ac_preproc_ok=: break ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok then : else case e in #( e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "C++ preprocessor \"$CXXCPP\" fails sanity check See 'config.log' for more details" "$LINENO" 5; } ;; esac fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_linker_flag="-Wl,--copy-dt-needed-entries" ac_clang_active="no" if test "$CXX" = "clang++" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether clang works" >&5 printf %s "checking whether clang works... " >&6; } ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #if __has_include () #include #endif #if __has_include () #include #endif int main (void) { ; return 0; } _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } printf "%s\n" "#define CLANG_SUPPORT 1" >>confdefs.h ac_linker_flag="" ac_clang_active="yes" else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } as_fn_error $? "Compiler could not parse C++ headers. Use CC=c-compiler CXX=c++-compiler ./configure ..." "$LINENO" 5 ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi LINKER_FLAGS=$ac_linker_flag ac_build_docs="no" ac_build_windows="no" ac_build_portmidi="no" ac_build_qtmidi="yes" ac_build_rtcli="no" ac_build_rtmidi="yes" ac_build_sessions="yes" ac_build_testing="no" ac_build_qt="yes" ac_app_name="qseq66" ac_app_type="qt" ac_app_engine="rtmidi" ac_app_build_issue="Linux" ac_app_build_os="Linux" ac_client_name="seq66" ac_config_name="qseq66" ac_config_dir_name="seq66" ac_icon_name="qseq66" case $host_os in *cygwin* | *msys* | windows*) ac_build_windows="yes";; * ) ac_build_windows="no";; esac if test "$ac_build_windows" = "yes" ; then ac_build_portmidi="yes" ac_build_rtmidi="no" ac_build_sessions="no" fi PACKAGE="seq66" printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h printf "%s\n" "#define APP_TYPE \"qt5\"" >>confdefs.h printf "%s\n" "#define APP_ENGINE \"rtmidi\"" >>confdefs.h ac_app_build_os="'$(uname -srm)'" SEQ66_SUITE_NAME="SEQ66" SEQ66_API_MAJOR="0" SEQ66_API_MINOR="99" SEQ66_API_PATCH="0" SEQ66_LT_CURRENT="0" SEQ66_LT_REVISION="0" SEQ66_LT_AGE="0" SEQ66_LIBTOOL_VERSION="$SEQ66_LT_CURRENT:$SEQ66_LT_REVISION:$SEQ66_LT_AGE" SEQ66_API_VERSION="$SEQ66_API_MAJOR.$SEQ66_API_MINOR" printf "%s\n" "#define API_VERSION \"0.99\"" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking major version" >&5 printf %s "checking major version... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SEQ66_API_MAJOR" >&5 printf "%s\n" "$SEQ66_API_MAJOR" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking minor version" >&5 printf %s "checking minor version... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SEQ66_API_MINOR" >&5 printf "%s\n" "$SEQ66_API_MINOR" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking patchlevel" >&5 printf %s "checking patchlevel... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SEQ66_API_PATCH" >&5 printf "%s\n" "$SEQ66_API_PATCH" >&6; } SEQ66_PROJECT_NAME="SEQ66" case `pwd` in *\ * | *\ *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&5 printf "%s\n" "$as_me: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&2;} ;; esac macro_version='2.5.4' macro_revision='2.5.4' ltmain=$ac_aux_dir/ltmain.sh # Backslashify metacharacters that are still active within # double-quoted strings. sed_quote_subst='s/\(["`$\\]\)/\\\1/g' # Same as above, but do not quote variable references. double_quote_subst='s/\(["`\\]\)/\\\1/g' # Sed substitution to delay expansion of an escaped shell variable in a # double_quote_subst'ed string. delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' # Sed substitution to delay expansion of an escaped single quote. delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' # Sed substitution to avoid accidental globbing in evaled expressions no_glob_subst='s/\*/\\\*/g' ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to print strings" >&5 printf %s "checking how to print strings... " >&6; } # Test print first, because it will be a builtin if present. if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then ECHO='print -r --' elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then ECHO='printf %s\n' else # Use this function as a fallback that always works. func_fallback_echo () { eval 'cat <<_LTECHO_EOF $1 _LTECHO_EOF' } ECHO='func_fallback_echo' fi # func_echo_all arg... # Invoke $ECHO with all args, space-separated. func_echo_all () { $ECHO "" } case $ECHO in printf*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: printf" >&5 printf "%s\n" "printf" >&6; } ;; print*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: print -r" >&5 printf "%s\n" "print -r" >&6; } ;; *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: cat" >&5 printf "%s\n" "cat" >&6; } ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 printf %s "checking for a sed that does not truncate output... " >&6; } if test ${ac_cv_path_SED+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ for ac_i in 1 2 3 4 5 6 7; do ac_script="$ac_script$as_nl$ac_script" done echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed { ac_script=; unset ac_script;} if test -z "$SED"; then ac_path_SED_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in sed gsed do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_SED="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_SED" || continue # Check for GNU ac_path_SED and select it if it is found. # Check for GNU $ac_path_SED case `"$ac_path_SED" --version 2>&1` in #( *GNU*) ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;; #( *) ac_count=0 printf %s 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" printf "%s\n" '' >> "conftest.nl" "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_SED_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_SED="$ac_path_SED" ac_path_SED_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_SED_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_SED"; then as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5 fi else ac_cv_path_SED=$SED fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 printf "%s\n" "$ac_cv_path_SED" >&6; } SED="$ac_cv_path_SED" rm -f conftest.sed test -z "$SED" && SED=sed Xsed="$SED -e 1s/^X//" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 printf %s "checking for grep that handles long lines and -e... " >&6; } if test ${ac_cv_path_GREP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -z "$GREP"; then ac_path_GREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in grep ggrep do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_GREP="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_GREP" || continue # Check for GNU ac_path_GREP and select it if it is found. # Check for GNU $ac_path_GREP case `"$ac_path_GREP" --version 2>&1` in #( *GNU*) ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; #( *) ac_count=0 printf %s 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" printf "%s\n" 'GREP' >> "conftest.nl" "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_GREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_GREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_GREP"; then as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_GREP=$GREP fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 printf "%s\n" "$ac_cv_path_GREP" >&6; } GREP="$ac_cv_path_GREP" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 printf %s "checking for egrep... " >&6; } if test ${ac_cv_path_EGREP+y} then : printf %s "(cached) " >&6 else case e in #( e) if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 then ac_cv_path_EGREP="$GREP -E" else if test -z "$EGREP"; then ac_path_EGREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in egrep do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_EGREP="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_EGREP" || continue # Check for GNU ac_path_EGREP and select it if it is found. # Check for GNU $ac_path_EGREP case `"$ac_path_EGREP" --version 2>&1` in #( *GNU*) ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; #( *) ac_count=0 printf %s 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" printf "%s\n" 'EGREP' >> "conftest.nl" "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_EGREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_EGREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_EGREP"; then as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_EGREP=$EGREP fi fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 printf "%s\n" "$ac_cv_path_EGREP" >&6; } EGREP="$ac_cv_path_EGREP" EGREP_TRADITIONAL=$EGREP ac_cv_path_EGREP_TRADITIONAL=$EGREP { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for fgrep" >&5 printf %s "checking for fgrep... " >&6; } if test ${ac_cv_path_FGREP+y} then : printf %s "(cached) " >&6 else case e in #( e) if echo 'ab*c' | $GREP -F 'ab*c' >/dev/null 2>&1 then ac_cv_path_FGREP="$GREP -F" else if test -z "$FGREP"; then ac_path_FGREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in fgrep do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_FGREP="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_FGREP" || continue # Check for GNU ac_path_FGREP and select it if it is found. # Check for GNU $ac_path_FGREP case `"$ac_path_FGREP" --version 2>&1` in #( *GNU*) ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_found=:;; #( *) ac_count=0 printf %s 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" printf "%s\n" 'FGREP' >> "conftest.nl" "$ac_path_FGREP" FGREP < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_FGREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_FGREP_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_FGREP"; then as_fn_error $? "no acceptable fgrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_FGREP=$FGREP fi fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_FGREP" >&5 printf "%s\n" "$ac_cv_path_FGREP" >&6; } FGREP="$ac_cv_path_FGREP" test -z "$GREP" && GREP=grep # Check whether --with-gnu-ld was given. if test ${with_gnu_ld+y} then : withval=$with_gnu_ld; test no = "$withval" || with_gnu_ld=yes else case e in #( e) with_gnu_ld=no ;; esac fi ac_prog=ld if test yes = "$GCC"; then # Check if gcc -print-prog-name=ld gives a path. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 printf %s "checking for ld used by $CC... " >&6; } case $host in *-*-mingw* | *-*-windows*) # gcc leaves a trailing carriage return, which upsets mingw ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; *) ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; esac case $ac_prog in # Accept absolute paths. [\\/]* | ?:[\\/]*) re_direlt='/[^/][^/]*/\.\./' # Canonicalize the pathname of ld ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` done test -z "$LD" && LD=$ac_prog ;; "") # If it fails, then pretend we aren't using GCC. ac_prog=ld ;; *) # If it is relative, then search for the first ld in PATH. with_gnu_ld=unknown ;; esac elif test yes = "$with_gnu_ld"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 printf %s "checking for GNU ld... " >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 printf %s "checking for non-GNU ld... " >&6; } fi if test ${lt_cv_path_LD+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -z "$LD"; then lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR for ac_dir in $PATH; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then lt_cv_path_LD=$ac_dir/$ac_prog # Check to see if the program is GNU ld. I'd rather use --version, # but apparently some variants of GNU ld only accept -v. # Break only if it was the GNU/non-GNU ld that we prefer. case `"$lt_cv_path_LD" -v 2>&1 &5 printf "%s\n" "$LD" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 printf %s "checking if the linker ($LD) is GNU ld... " >&6; } if test ${lt_cv_prog_gnu_ld+y} then : printf %s "(cached) " >&6 else case e in #( e) # I'd rather use --version here, but apparently some GNU lds only accept -v. case `$LD -v 2>&1 &5 printf "%s\n" "$lt_cv_prog_gnu_ld" >&6; } with_gnu_ld=$lt_cv_prog_gnu_ld { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for BSD- or MS-compatible name lister (nm)" >&5 printf %s "checking for BSD- or MS-compatible name lister (nm)... " >&6; } if test ${lt_cv_path_NM+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$NM"; then # Let the user override the test. lt_cv_path_NM=$NM else lt_nm_to_check=${ac_tool_prefix}nm if test -n "$ac_tool_prefix" && test "$build" = "$host"; then lt_nm_to_check="$lt_nm_to_check nm" fi for lt_tmp_nm in $lt_nm_to_check; do lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. tmp_nm=$ac_dir/$lt_tmp_nm if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then # Check to see if the nm accepts a BSD-compat flag. # Adding the 'sed 1q' prevents false positives on HP-UX, which says: # nm: unknown option "B" ignored # Tru64's nm complains that /dev/null is an invalid object file # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty case $build_os in mingw* | windows*) lt_bad_file=conftest.nm/nofile ;; *) lt_bad_file=/dev/null ;; esac case `"$tmp_nm" -B $lt_bad_file 2>&1 | $SED '1q'` in *$lt_bad_file* | *'Invalid file or object type'*) lt_cv_path_NM="$tmp_nm -B" break 2 ;; *) case `"$tmp_nm" -p /dev/null 2>&1 | $SED '1q'` in */dev/null*) lt_cv_path_NM="$tmp_nm -p" break 2 ;; *) lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but continue # so that we can try to find one that supports BSD flags ;; esac ;; esac fi done IFS=$lt_save_ifs done : ${lt_cv_path_NM=no} fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_NM" >&5 printf "%s\n" "$lt_cv_path_NM" >&6; } if test no != "$lt_cv_path_NM"; then NM=$lt_cv_path_NM else # Didn't find any BSD compatible name lister, look for dumpbin. if test -n "$DUMPBIN"; then : # Let the user override the test. else if test -n "$ac_tool_prefix"; then for ac_prog in dumpbin "link -dump" do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_DUMPBIN+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$DUMPBIN"; then ac_cv_prog_DUMPBIN="$DUMPBIN" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_DUMPBIN="$ac_tool_prefix$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi DUMPBIN=$ac_cv_prog_DUMPBIN if test -n "$DUMPBIN"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $DUMPBIN" >&5 printf "%s\n" "$DUMPBIN" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$DUMPBIN" && break done fi if test -z "$DUMPBIN"; then ac_ct_DUMPBIN=$DUMPBIN for ac_prog in dumpbin "link -dump" do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_DUMPBIN+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_DUMPBIN"; then ac_cv_prog_ac_ct_DUMPBIN="$ac_ct_DUMPBIN" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_DUMPBIN="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_DUMPBIN=$ac_cv_prog_ac_ct_DUMPBIN if test -n "$ac_ct_DUMPBIN"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DUMPBIN" >&5 printf "%s\n" "$ac_ct_DUMPBIN" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$ac_ct_DUMPBIN" && break done if test "x$ac_ct_DUMPBIN" = x; then DUMPBIN=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac DUMPBIN=$ac_ct_DUMPBIN fi fi case `$DUMPBIN -symbols -headers /dev/null 2>&1 | $SED '1q'` in *COFF*) DUMPBIN="$DUMPBIN -symbols -headers" ;; *) DUMPBIN=: ;; esac fi if test : != "$DUMPBIN"; then NM=$DUMPBIN fi fi test -z "$NM" && NM=nm { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking the name lister ($NM) interface" >&5 printf %s "checking the name lister ($NM) interface... " >&6; } if test ${lt_cv_nm_interface+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_nm_interface="BSD nm" echo "int some_variable = 0;" > conftest.$ac_ext (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&5) (eval "$ac_compile" 2>conftest.err) cat conftest.err >&5 (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&5) (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) cat conftest.err >&5 (eval echo "\"\$as_me:$LINENO: output\"" >&5) cat conftest.out >&5 if $GREP 'External.*some_variable' conftest.out > /dev/null; then lt_cv_nm_interface="MS dumpbin" fi rm -f conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_nm_interface" >&5 printf "%s\n" "$lt_cv_nm_interface" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5 printf %s "checking whether ln -s works... " >&6; } LN_S=$as_ln_s if test "$LN_S" = "ln -s"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5 printf "%s\n" "no, using $LN_S" >&6; } fi # find the maximum length of command line arguments { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking the maximum length of command line arguments" >&5 printf %s "checking the maximum length of command line arguments... " >&6; } if test ${lt_cv_sys_max_cmd_len+y} then : printf %s "(cached) " >&6 else case e in #( e) i=0 teststring=ABCD case $build_os in msdosdjgpp*) # On DJGPP, this test can blow up pretty badly due to problems in libc # (any single argument exceeding 2000 bytes causes a buffer overrun # during glob expansion). Even if it were fixed, the result of this # check would be larger than it should be. lt_cv_sys_max_cmd_len=12288; # 12K is about right ;; gnu* | ironclad*) # Under GNU Hurd and Ironclad, this test is not required because there # is no limit to the length of command line arguments. # Libtool will interpret -1 as no limit whatsoever lt_cv_sys_max_cmd_len=-1; ;; cygwin* | mingw* | windows* | cegcc*) # On Win9x/ME, this test blows up -- it succeeds, but takes # about 5 minutes as the teststring grows exponentially. # Worse, since 9x/ME are not pre-emptively multitasking, # you end up with a "frozen" computer, even though with patience # the test eventually succeeds (with a max line length of 256k). # Instead, let's just punt: use the minimum linelength reported by # all of the supported platforms: 8192 (on NT/2K/XP). lt_cv_sys_max_cmd_len=8192; ;; mint*) # On MiNT this can take a long time and run out of memory. lt_cv_sys_max_cmd_len=8192; ;; amigaos*) # On AmigaOS with pdksh, this test takes hours, literally. # So we just punt and use a minimum line length of 8192. lt_cv_sys_max_cmd_len=8192; ;; darwin* | dragonfly* | freebsd* | midnightbsd* | netbsd* | openbsd*) # This has been around since 386BSD, at least. Likely further. if test -x /sbin/sysctl; then lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` elif test -x /usr/sbin/sysctl; then lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` else lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs fi # And add a safety zone lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` ;; interix*) # We know the value 262144 and hardcode it with a safety zone (like BSD) lt_cv_sys_max_cmd_len=196608 ;; os2*) # The test takes a long time on OS/2. lt_cv_sys_max_cmd_len=8192 ;; osf*) # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not # nice to cause kernel panics so lets avoid the loop below. # First set a reasonable default. lt_cv_sys_max_cmd_len=16384 # if test -x /sbin/sysconfig; then case `/sbin/sysconfig -q proc exec_disable_arg_limit` in *1*) lt_cv_sys_max_cmd_len=-1 ;; esac fi ;; sco3.2v5*) lt_cv_sys_max_cmd_len=102400 ;; sysv5* | sco5v6* | sysv4.2uw2*) kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` if test -n "$kargmax"; then lt_cv_sys_max_cmd_len=`echo $kargmax | $SED 's/.*[ ]//'` else lt_cv_sys_max_cmd_len=32768 fi ;; *) lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` if test -n "$lt_cv_sys_max_cmd_len" && \ test undefined != "$lt_cv_sys_max_cmd_len"; then lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` else # Make teststring a little bigger before we do anything with it. # a 1K string should be a reasonable start. for i in 1 2 3 4 5 6 7 8; do teststring=$teststring$teststring done SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} # If test is not a shell built-in, we'll probably end up computing a # maximum length that is only half of the actual maximum length, but # we can't tell. while { test X`env echo "$teststring$teststring" 2>/dev/null` \ = "X$teststring$teststring"; } >/dev/null 2>&1 && test 17 != "$i" # 1/2 MB should be enough do i=`expr $i + 1` teststring=$teststring$teststring done # Only check the string length outside the loop. lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` teststring= # Add a significant safety factor because C++ compilers can tack on # massive amounts of additional arguments before passing them to the # linker. It appears as though 1/2 is a usable value. lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` fi ;; esac ;; esac fi if test -n "$lt_cv_sys_max_cmd_len"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sys_max_cmd_len" >&5 printf "%s\n" "$lt_cv_sys_max_cmd_len" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none" >&5 printf "%s\n" "none" >&6; } fi max_cmd_len=$lt_cv_sys_max_cmd_len : ${CP="cp -f"} : ${MV="mv -f"} : ${RM="rm -f"} if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then lt_unset=unset else lt_unset=false fi # test EBCDIC or ASCII case `echo X|tr X '\101'` in A) # ASCII based system # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr lt_SP2NL='tr \040 \012' lt_NL2SP='tr \015\012 \040\040' ;; *) # EBCDIC based system lt_SP2NL='tr \100 \n' lt_NL2SP='tr \r\n \100\100' ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to $host format" >&5 printf %s "checking how to convert $build file names to $host format... " >&6; } if test ${lt_cv_to_host_file_cmd+y} then : printf %s "(cached) " >&6 else case e in #( e) case $host in *-*-mingw* ) case $build in *-*-mingw* | *-*-windows* ) # actually msys lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 ;; *-*-cygwin* ) lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 ;; * ) # otherwise, assume *nix lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 ;; esac ;; *-*-cygwin* ) case $build in *-*-mingw* | *-*-windows* ) # actually msys lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin ;; *-*-cygwin* ) lt_cv_to_host_file_cmd=func_convert_file_noop ;; * ) # otherwise, assume *nix lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin ;; esac ;; * ) # unhandled hosts (and "normal" native builds) lt_cv_to_host_file_cmd=func_convert_file_noop ;; esac ;; esac fi to_host_file_cmd=$lt_cv_to_host_file_cmd { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_host_file_cmd" >&5 printf "%s\n" "$lt_cv_to_host_file_cmd" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to toolchain format" >&5 printf %s "checking how to convert $build file names to toolchain format... " >&6; } if test ${lt_cv_to_tool_file_cmd+y} then : printf %s "(cached) " >&6 else case e in #( e) #assume ordinary cross tools, or native build. lt_cv_to_tool_file_cmd=func_convert_file_noop case $host in *-*-mingw* | *-*-windows* ) case $build in *-*-mingw* | *-*-windows* ) # actually msys lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 ;; esac ;; esac ;; esac fi to_tool_file_cmd=$lt_cv_to_tool_file_cmd { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_tool_file_cmd" >&5 printf "%s\n" "$lt_cv_to_tool_file_cmd" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $LD option to reload object files" >&5 printf %s "checking for $LD option to reload object files... " >&6; } if test ${lt_cv_ld_reload_flag+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_ld_reload_flag='-r' ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_reload_flag" >&5 printf "%s\n" "$lt_cv_ld_reload_flag" >&6; } reload_flag=$lt_cv_ld_reload_flag case $reload_flag in "" | " "*) ;; *) reload_flag=" $reload_flag" ;; esac reload_cmds='$LD$reload_flag -o $output$reload_objs' case $host_os in cygwin* | mingw* | windows* | pw32* | cegcc*) if test yes != "$GCC"; then reload_cmds=false fi ;; darwin*) if test yes = "$GCC"; then reload_cmds='$LTCC $LTCFLAGS -nostdlib $wl-r -o $output$reload_objs' else reload_cmds='$LD$reload_flag -o $output$reload_objs' fi ;; esac # Extract the first word of "file", so it can be a program name with args. set dummy file; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_FILECMD+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$FILECMD"; then ac_cv_prog_FILECMD="$FILECMD" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_FILECMD="file" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_FILECMD" && ac_cv_prog_FILECMD=":" fi ;; esac fi FILECMD=$ac_cv_prog_FILECMD if test -n "$FILECMD"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $FILECMD" >&5 printf "%s\n" "$FILECMD" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}objdump", so it can be a program name with args. set dummy ${ac_tool_prefix}objdump; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_OBJDUMP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$OBJDUMP"; then ac_cv_prog_OBJDUMP="$OBJDUMP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_OBJDUMP="${ac_tool_prefix}objdump" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi OBJDUMP=$ac_cv_prog_OBJDUMP if test -n "$OBJDUMP"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $OBJDUMP" >&5 printf "%s\n" "$OBJDUMP" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_OBJDUMP"; then ac_ct_OBJDUMP=$OBJDUMP # Extract the first word of "objdump", so it can be a program name with args. set dummy objdump; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_OBJDUMP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_OBJDUMP"; then ac_cv_prog_ac_ct_OBJDUMP="$ac_ct_OBJDUMP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_OBJDUMP="objdump" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_OBJDUMP=$ac_cv_prog_ac_ct_OBJDUMP if test -n "$ac_ct_OBJDUMP"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJDUMP" >&5 printf "%s\n" "$ac_ct_OBJDUMP" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_OBJDUMP" = x; then OBJDUMP="false" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac OBJDUMP=$ac_ct_OBJDUMP fi else OBJDUMP="$ac_cv_prog_OBJDUMP" fi test -z "$OBJDUMP" && OBJDUMP=objdump { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to recognize dependent libraries" >&5 printf %s "checking how to recognize dependent libraries... " >&6; } if test ${lt_cv_deplibs_check_method+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_file_magic_cmd='$MAGIC_CMD' lt_cv_file_magic_test_file= lt_cv_deplibs_check_method='unknown' # Need to set the preceding variable on all platforms that support # interlibrary dependencies. # 'none' -- dependencies not supported. # 'unknown' -- same as none, but documents that we really don't know. # 'pass_all' -- all dependencies passed with no checks. # 'file_magic [[regex]]' -- check by looking for files in library path # that responds to the $file_magic_cmd with a given extended regex. # If you have 'file' or equivalent on your system and you're not sure # whether 'pass_all' will *always* work, you probably want this one. case $host_os in aix[4-9]*) lt_cv_deplibs_check_method=pass_all ;; beos*) lt_cv_deplibs_check_method=pass_all ;; bsdi[45]*) lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)' lt_cv_file_magic_cmd='$FILECMD -L' lt_cv_file_magic_test_file=/shlib/libc.so ;; cygwin*) # func_win32_libid is a shell function defined in ltmain.sh lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' lt_cv_file_magic_cmd='func_win32_libid' ;; mingw* | windows* | pw32*) # Base MSYS/MinGW do not provide the 'file' command needed by # func_win32_libid shell function, so use a weaker test based on 'objdump', # unless we find 'file', for example because we are cross-compiling. if ( file / ) >/dev/null 2>&1; then lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' lt_cv_file_magic_cmd='func_win32_libid' else # Keep this pattern in sync with the one in func_win32_libid. lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64|pe-aarch64)' lt_cv_file_magic_cmd='$OBJDUMP -f' fi ;; cegcc*) # use the weaker test based on 'objdump'. See mingw*. lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' lt_cv_file_magic_cmd='$OBJDUMP -f' ;; darwin* | rhapsody*) lt_cv_deplibs_check_method=pass_all ;; freebsd* | dragonfly* | midnightbsd*) if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then case $host_cpu in i*86 ) # Not sure whether the presence of OpenBSD here was a mistake. # Let's accept both of them until this is cleared up. lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[3-9]86 (compact )?demand paged shared library' lt_cv_file_magic_cmd=$FILECMD lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` ;; esac else lt_cv_deplibs_check_method=pass_all fi ;; haiku*) lt_cv_deplibs_check_method=pass_all ;; hpux10.20* | hpux11*) lt_cv_file_magic_cmd=$FILECMD case $host_cpu in ia64*) lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - IA64' lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so ;; hppa*64*) lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]' lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl ;; *) lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9]\.[0-9]) shared library' lt_cv_file_magic_test_file=/usr/lib/libc.sl ;; esac ;; interix[3-9]*) # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|\.a)$' ;; irix5* | irix6* | nonstopux*) case $LD in *-32|*"-32 ") libmagic=32-bit;; *-n32|*"-n32 ") libmagic=N32;; *-64|*"-64 ") libmagic=64-bit;; *) libmagic=never-match;; esac lt_cv_deplibs_check_method=pass_all ;; # This must be glibc/ELF. linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) lt_cv_deplibs_check_method=pass_all ;; *-mlibc) lt_cv_deplibs_check_method=pass_all ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' else lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|_pic\.a)$' fi ;; newos6*) lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)' lt_cv_file_magic_cmd=$FILECMD lt_cv_file_magic_test_file=/usr/lib/libnls.so ;; *nto* | *qnx*) lt_cv_deplibs_check_method=pass_all ;; openbsd*) if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|\.so|_pic\.a)$' else lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' fi ;; osf3* | osf4* | osf5*) lt_cv_deplibs_check_method=pass_all ;; rdos*) lt_cv_deplibs_check_method=pass_all ;; serenity*) lt_cv_deplibs_check_method=pass_all ;; solaris*) lt_cv_deplibs_check_method=pass_all ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) lt_cv_deplibs_check_method=pass_all ;; sysv4 | sysv4.3*) case $host_vendor in motorola) lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]' lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` ;; ncr) lt_cv_deplibs_check_method=pass_all ;; sequent) lt_cv_file_magic_cmd='/bin/file' lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )' ;; sni) lt_cv_file_magic_cmd='/bin/file' lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib" lt_cv_file_magic_test_file=/lib/libc.so ;; siemens) lt_cv_deplibs_check_method=pass_all ;; pc) lt_cv_deplibs_check_method=pass_all ;; esac ;; tpf*) lt_cv_deplibs_check_method=pass_all ;; os2*) lt_cv_deplibs_check_method=pass_all ;; esac ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_deplibs_check_method" >&5 printf "%s\n" "$lt_cv_deplibs_check_method" >&6; } file_magic_glob= want_nocaseglob=no if test "$build" = "$host"; then case $host_os in mingw* | windows* | pw32*) if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then want_nocaseglob=yes else file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[\1]\/[\1]\/g;/g"` fi ;; esac fi file_magic_cmd=$lt_cv_file_magic_cmd deplibs_check_method=$lt_cv_deplibs_check_method test -z "$deplibs_check_method" && deplibs_check_method=unknown if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}dlltool", so it can be a program name with args. set dummy ${ac_tool_prefix}dlltool; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_DLLTOOL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$DLLTOOL"; then ac_cv_prog_DLLTOOL="$DLLTOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_DLLTOOL="${ac_tool_prefix}dlltool" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi DLLTOOL=$ac_cv_prog_DLLTOOL if test -n "$DLLTOOL"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $DLLTOOL" >&5 printf "%s\n" "$DLLTOOL" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_DLLTOOL"; then ac_ct_DLLTOOL=$DLLTOOL # Extract the first word of "dlltool", so it can be a program name with args. set dummy dlltool; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_DLLTOOL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_DLLTOOL"; then ac_cv_prog_ac_ct_DLLTOOL="$ac_ct_DLLTOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_DLLTOOL="dlltool" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_DLLTOOL=$ac_cv_prog_ac_ct_DLLTOOL if test -n "$ac_ct_DLLTOOL"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DLLTOOL" >&5 printf "%s\n" "$ac_ct_DLLTOOL" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_DLLTOOL" = x; then DLLTOOL="false" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac DLLTOOL=$ac_ct_DLLTOOL fi else DLLTOOL="$ac_cv_prog_DLLTOOL" fi test -z "$DLLTOOL" && DLLTOOL=dlltool { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to associate runtime and link libraries" >&5 printf %s "checking how to associate runtime and link libraries... " >&6; } if test ${lt_cv_sharedlib_from_linklib_cmd+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_sharedlib_from_linklib_cmd='unknown' case $host_os in cygwin* | mingw* | windows* | pw32* | cegcc*) # two different shell functions defined in ltmain.sh; # decide which one to use based on capabilities of $DLLTOOL case `$DLLTOOL --help 2>&1` in *--identify-strict*) lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib ;; *) lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback ;; esac ;; *) # fallback: assume linklib IS sharedlib lt_cv_sharedlib_from_linklib_cmd=$ECHO ;; esac ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sharedlib_from_linklib_cmd" >&5 printf "%s\n" "$lt_cv_sharedlib_from_linklib_cmd" >&6; } sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. set dummy ${ac_tool_prefix}ranlib; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_RANLIB+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$RANLIB"; then ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi RANLIB=$ac_cv_prog_RANLIB if test -n "$RANLIB"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 printf "%s\n" "$RANLIB" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_RANLIB"; then ac_ct_RANLIB=$RANLIB # Extract the first word of "ranlib", so it can be a program name with args. set dummy ranlib; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_RANLIB+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_RANLIB"; then ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_RANLIB="ranlib" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB if test -n "$ac_ct_RANLIB"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 printf "%s\n" "$ac_ct_RANLIB" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_RANLIB" = x; then RANLIB=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac RANLIB=$ac_ct_RANLIB fi else RANLIB="$ac_cv_prog_RANLIB" fi if test -n "$ac_tool_prefix"; then for ac_prog in ar do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_AR+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$AR"; then ac_cv_prog_AR="$AR" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_AR="$ac_tool_prefix$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi AR=$ac_cv_prog_AR if test -n "$AR"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 printf "%s\n" "$AR" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$AR" && break done fi if test -z "$AR"; then ac_ct_AR=$AR for ac_prog in ar do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_AR+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_AR"; then ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_AR="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_AR=$ac_cv_prog_ac_ct_AR if test -n "$ac_ct_AR"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 printf "%s\n" "$ac_ct_AR" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$ac_ct_AR" && break done if test "x$ac_ct_AR" = x; then AR="false" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac AR=$ac_ct_AR fi fi : ${AR=ar} # Use ARFLAGS variable as AR's operation code to sync the variable naming with # Automake. If both AR_FLAGS and ARFLAGS are specified, AR_FLAGS should have # higher priority because that's what people were doing historically (setting # ARFLAGS for automake and AR_FLAGS for libtool). FIXME: Make the AR_FLAGS # variable obsoleted/removed. test ${AR_FLAGS+y} || AR_FLAGS=${ARFLAGS-cr} lt_ar_flags=$AR_FLAGS # Make AR_FLAGS overridable by 'make ARFLAGS='. Don't try to run-time override # by AR_FLAGS because that was never working and AR_FLAGS is about to die. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for archiver @FILE support" >&5 printf %s "checking for archiver @FILE support... " >&6; } if test ${lt_cv_ar_at_file+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_ar_at_file=no cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : echo conftest.$ac_objext > conftest.lst lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&5' { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 (eval $lt_ar_try) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test 0 -eq "$ac_status"; then # Ensure the archiver fails upon bogus file names. rm -f conftest.$ac_objext libconftest.a { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 (eval $lt_ar_try) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test 0 -ne "$ac_status"; then lt_cv_ar_at_file=@ fi fi rm -f conftest.* libconftest.a fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ar_at_file" >&5 printf "%s\n" "$lt_cv_ar_at_file" >&6; } if test no = "$lt_cv_ar_at_file"; then archiver_list_spec= else archiver_list_spec=$lt_cv_ar_at_file fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. set dummy ${ac_tool_prefix}strip; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_STRIP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$STRIP"; then ac_cv_prog_STRIP="$STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_STRIP="${ac_tool_prefix}strip" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi STRIP=$ac_cv_prog_STRIP if test -n "$STRIP"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 printf "%s\n" "$STRIP" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_STRIP"; then ac_ct_STRIP=$STRIP # Extract the first word of "strip", so it can be a program name with args. set dummy strip; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_STRIP+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_STRIP"; then ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_STRIP="strip" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP if test -n "$ac_ct_STRIP"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 printf "%s\n" "$ac_ct_STRIP" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_STRIP" = x; then STRIP=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac STRIP=$ac_ct_STRIP fi else STRIP="$ac_cv_prog_STRIP" fi test -z "$STRIP" && STRIP=: test -z "$RANLIB" && RANLIB=: # Determine commands to create old-style static archives. old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' old_postinstall_cmds='chmod 644 $oldlib' old_postuninstall_cmds= if test -n "$RANLIB"; then old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" fi case $host_os in darwin*) lock_old_archive_extraction=yes ;; *) lock_old_archive_extraction=no ;; esac # If no C compiler was specified, use CC. LTCC=${LTCC-"$CC"} # If no C compiler flags were specified, use CFLAGS. LTCFLAGS=${LTCFLAGS-"$CFLAGS"} # Allow CC to be a program name with arguments. compiler=$CC # Check for command to grab the raw symbol name followed by C symbol from nm. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking command to parse $NM output from $compiler object" >&5 printf %s "checking command to parse $NM output from $compiler object... " >&6; } if test ${lt_cv_sys_global_symbol_pipe+y} then : printf %s "(cached) " >&6 else case e in #( e) # These are sane defaults that work on at least a few old systems. # [They come from Ultrix. What could be older than Ultrix?!! ;)] # Character class describing NM global symbol codes. symcode='[BCDEGRST]' # Regexp to match symbols that can be accessed directly from C. sympat='\([_A-Za-z][_A-Za-z0-9]*\)' # Define system-specific variables. case $host_os in aix*) symcode='[BCDT]' ;; cygwin* | mingw* | windows* | pw32* | cegcc*) symcode='[ABCDGISTW]' ;; hpux*) if test ia64 = "$host_cpu"; then symcode='[ABCDEGRST]' fi ;; irix* | nonstopux*) symcode='[BCDEGRST]' ;; osf*) symcode='[BCDEGQRST]' ;; solaris*) symcode='[BCDRT]' ;; sco3.2v5*) symcode='[DT]' ;; sysv4.2uw2*) symcode='[DT]' ;; sysv5* | sco5v6* | unixware* | OpenUNIX*) symcode='[ABDT]' ;; sysv4) symcode='[DFNSTU]' ;; esac # If we're using GNU nm, then use its standard symbol codes. case `$NM -V 2>&1` in *GNU* | *'with BFD'*) symcode='[ABCDGIRSTW]' ;; esac if test "$lt_cv_nm_interface" = "MS dumpbin"; then # Gets list of data symbols to import. lt_cv_sys_global_symbol_to_import="$SED -n -e 's/^I .* \(.*\)$/\1/p'" # Adjust the below global symbol transforms to fixup imported variables. lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" lt_c_name_lib_hook="\ -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" else # Disable hooks by default. lt_cv_sys_global_symbol_to_import= lt_cdecl_hook= lt_c_name_hook= lt_c_name_lib_hook= fi # Transform an extracted symbol line into a proper C declaration. # Some systems (esp. on ia64) link data and code symbols differently, # so use this general approach. lt_cv_sys_global_symbol_to_cdecl="$SED -n"\ $lt_cdecl_hook\ " -e 's/^T .* \(.*\)$/extern int \1();/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" # Transform an extracted symbol line into symbol name and symbol address lt_cv_sys_global_symbol_to_c_name_address="$SED -n"\ $lt_c_name_hook\ " -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" # Transform an extracted symbol line into symbol name with lib prefix and # symbol address. lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="$SED -n"\ $lt_c_name_lib_hook\ " -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ " -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ " -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" # Handle CRLF in mingw tool chain opt_cr= case $build_os in mingw* | windows*) opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp ;; esac # Try without a prefix underscore, then with it. for ac_symprfx in "" "_"; do # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. symxfrm="\\1 $ac_symprfx\\2 \\2" # Write the raw and C identifiers. if test "$lt_cv_nm_interface" = "MS dumpbin"; then # Fake it for dumpbin and say T for any non-static function, # D for any global variable and I for any imported variable. # Also find C++ and __fastcall symbols from MSVC++ or ICC, # which start with @ or ?. lt_cv_sys_global_symbol_pipe="$AWK '"\ " {last_section=section; section=\$ 3};"\ " /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ " /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ " /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ " /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ " /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ " \$ 0!~/External *\|/{next};"\ " / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ " {if(hide[section]) next};"\ " {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ " {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ " s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ " s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ " ' prfx=^$ac_symprfx" else lt_cv_sys_global_symbol_pipe="$SED -n -e 's/^.*[ ]\($symcode$symcode*\)[ ][ ]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" fi lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | $SED '/ __gnu_lto/d'" # Check to see that the pipe works correctly. pipe_works=no rm -f conftest* cat > conftest.$ac_ext <<_LT_EOF #ifdef __cplusplus extern "C" { #endif char nm_test_var; void nm_test_func(void); void nm_test_func(void){} #ifdef __cplusplus } #endif int main(void){nm_test_var='a';nm_test_func();return(0);} _LT_EOF if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then # Now try to grab the symbols. nlist=conftest.nm $ECHO "$as_me:$LINENO: $NM conftest.$ac_objext | $lt_cv_sys_global_symbol_pipe > $nlist" >&5 if eval "$NM" conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist 2>&5 && test -s "$nlist"; then # Try sorting and uniquifying the output. if sort "$nlist" | uniq > "$nlist"T; then mv -f "$nlist"T "$nlist" else rm -f "$nlist"T fi # Make sure that we snagged all the symbols we need. if $GREP ' nm_test_var$' "$nlist" >/dev/null; then if $GREP ' nm_test_func$' "$nlist" >/dev/null; then cat <<_LT_EOF > conftest.$ac_ext /* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ #if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE /* DATA imports from DLLs on WIN32 can't be const, because runtime relocations are performed -- see ld's documentation on pseudo-relocs. */ # define LT_DLSYM_CONST #elif defined __osf__ /* This system does not cope well with relocations in const data. */ # define LT_DLSYM_CONST #else # define LT_DLSYM_CONST const #endif #ifdef __cplusplus extern "C" { #endif _LT_EOF # Now generate the symbol file. eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' cat <<_LT_EOF >> conftest.$ac_ext /* The mapping between symbol names and symbols. */ LT_DLSYM_CONST struct { const char *name; void *address; } lt__PROGRAM__LTX_preloaded_symbols[] = { { "@PROGRAM@", (void *) 0 }, _LT_EOF $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext cat <<\_LT_EOF >> conftest.$ac_ext {0, (void *) 0} }; /* This works around a problem in FreeBSD linker */ #ifdef FREEBSD_WORKAROUND static const void *lt_preloaded_setup() { return lt__PROGRAM__LTX_preloaded_symbols; } #endif #ifdef __cplusplus } #endif _LT_EOF # Now try linking the two files. mv conftest.$ac_objext conftstm.$ac_objext lt_globsym_save_LIBS=$LIBS lt_globsym_save_CFLAGS=$CFLAGS LIBS=conftstm.$ac_objext CFLAGS="$CFLAGS$lt_prog_compiler_no_builtin_flag" if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 (eval $ac_link) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && test -s conftest$ac_exeext; then pipe_works=yes fi LIBS=$lt_globsym_save_LIBS CFLAGS=$lt_globsym_save_CFLAGS else echo "cannot find nm_test_func in $nlist" >&5 fi else echo "cannot find nm_test_var in $nlist" >&5 fi else echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5 fi else echo "$progname: failed program was:" >&5 cat conftest.$ac_ext >&5 fi rm -rf conftest* conftst* # Do not use the global_symbol_pipe unless it works. if test yes = "$pipe_works"; then break else lt_cv_sys_global_symbol_pipe= fi done ;; esac fi if test -z "$lt_cv_sys_global_symbol_pipe"; then lt_cv_sys_global_symbol_to_cdecl= fi if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: failed" >&5 printf "%s\n" "failed" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ok" >&5 printf "%s\n" "ok" >&6; } fi # Response file support. if test "$lt_cv_nm_interface" = "MS dumpbin"; then nm_file_list_spec='@' elif $NM --help 2>/dev/null | grep '[@]FILE' >/dev/null; then nm_file_list_spec='@' fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for sysroot" >&5 printf %s "checking for sysroot... " >&6; } # Check whether --with-sysroot was given. if test ${with_sysroot+y} then : withval=$with_sysroot; else case e in #( e) with_sysroot=no ;; esac fi lt_sysroot= case $with_sysroot in #( yes) if test yes = "$GCC"; then # Trim trailing / since we'll always append absolute paths and we want # to avoid //, if only for less confusing output for the user. lt_sysroot=`$CC --print-sysroot 2>/dev/null | $SED 's:/\+$::'` fi ;; #( /*) lt_sysroot=`echo "$with_sysroot" | $SED -e "$sed_quote_subst"` ;; #( no|'') ;; #( *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_sysroot" >&5 printf "%s\n" "$with_sysroot" >&6; } as_fn_error $? "The sysroot must be an absolute path." "$LINENO" 5 ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${lt_sysroot:-no}" >&5 printf "%s\n" "${lt_sysroot:-no}" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for a working dd" >&5 printf %s "checking for a working dd... " >&6; } if test ${ac_cv_path_lt_DD+y} then : printf %s "(cached) " >&6 else case e in #( e) printf 0123456789abcdef0123456789abcdef >conftest.i cat conftest.i conftest.i >conftest2.i : ${lt_DD:=$DD} if test -z "$lt_DD"; then ac_path_lt_DD_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in dd do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_lt_DD="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_lt_DD" || continue if "$ac_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then cmp -s conftest.i conftest.out \ && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: fi $ac_path_lt_DD_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_lt_DD"; then : fi else ac_cv_path_lt_DD=$lt_DD fi rm -f conftest.i conftest2.i conftest.out ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_lt_DD" >&5 printf "%s\n" "$ac_cv_path_lt_DD" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to truncate binary pipes" >&5 printf %s "checking how to truncate binary pipes... " >&6; } if test ${lt_cv_truncate_bin+y} then : printf %s "(cached) " >&6 else case e in #( e) printf 0123456789abcdef0123456789abcdef >conftest.i cat conftest.i conftest.i >conftest2.i lt_cv_truncate_bin= if "$ac_cv_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then cmp -s conftest.i conftest.out \ && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" fi rm -f conftest.i conftest2.i conftest.out test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q" ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_truncate_bin" >&5 printf "%s\n" "$lt_cv_truncate_bin" >&6; } # Calculate cc_basename. Skip known compiler wrappers and cross-prefix. func_cc_basename () { for cc_temp in $*""; do case $cc_temp in compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; \-*) ;; *) break;; esac done func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` } # Check whether --enable-libtool-lock was given. if test ${enable_libtool_lock+y} then : enableval=$enable_libtool_lock; fi test no = "$enable_libtool_lock" || enable_libtool_lock=yes # Some flags need to be propagated to the compiler or linker for good # libtool support. case $host in ia64-*-hpux*) # Find out what ABI is being produced by ac_compile, and set mode # options accordingly. echo 'int i;' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then case `$FILECMD conftest.$ac_objext` in *ELF-32*) HPUX_IA64_MODE=32 ;; *ELF-64*) HPUX_IA64_MODE=64 ;; esac fi rm -rf conftest* ;; *-*-irix6*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo '#line '$LINENO' "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then if test yes = "$lt_cv_prog_gnu_ld"; then case `$FILECMD conftest.$ac_objext` in *32-bit*) LD="${LD-ld} -melf32bsmip" ;; *N32*) LD="${LD-ld} -melf32bmipn32" ;; *64-bit*) LD="${LD-ld} -melf64bmip" ;; esac else case `$FILECMD conftest.$ac_objext` in *32-bit*) LD="${LD-ld} -32" ;; *N32*) LD="${LD-ld} -n32" ;; *64-bit*) LD="${LD-ld} -64" ;; esac fi fi rm -rf conftest* ;; mips64*-*linux*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo '#line '$LINENO' "configure"' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then emul=elf case `$FILECMD conftest.$ac_objext` in *32-bit*) emul="${emul}32" ;; *64-bit*) emul="${emul}64" ;; esac case `$FILECMD conftest.$ac_objext` in *MSB*) emul="${emul}btsmip" ;; *LSB*) emul="${emul}ltsmip" ;; esac case `$FILECMD conftest.$ac_objext` in *N32*) emul="${emul}n32" ;; esac LD="${LD-ld} -m $emul" fi rm -rf conftest* ;; x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ s390*-*linux*|s390*-*tpf*|sparc*-*linux*|x86_64-gnu*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. Note that the listed cases only cover the # situations where additional linker options are needed (such as when # doing 32-bit compilation for a host where ld defaults to 64-bit, or # vice versa); the common cases where no linker options are needed do # not appear in the list. echo 'int i;' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then case `$FILECMD conftest.o` in *32-bit*) case $host in x86_64-*kfreebsd*-gnu) LD="${LD-ld} -m elf_i386_fbsd" ;; x86_64-*linux*|x86_64-gnu*) case `$FILECMD conftest.o` in *x86-64*) LD="${LD-ld} -m elf32_x86_64" ;; *) LD="${LD-ld} -m elf_i386" ;; esac ;; powerpc64le-*linux*) LD="${LD-ld} -m elf32lppclinux" ;; powerpc64-*linux*) LD="${LD-ld} -m elf32ppclinux" ;; s390x-*linux*) LD="${LD-ld} -m elf_s390" ;; sparc64-*linux*) LD="${LD-ld} -m elf32_sparc" ;; esac ;; *64-bit*) case $host in x86_64-*kfreebsd*-gnu) LD="${LD-ld} -m elf_x86_64_fbsd" ;; x86_64-*linux*|x86_64-gnu*) LD="${LD-ld} -m elf_x86_64" ;; powerpcle-*linux*) LD="${LD-ld} -m elf64lppc" ;; powerpc-*linux*) LD="${LD-ld} -m elf64ppc" ;; s390*-*linux*|s390*-*tpf*) LD="${LD-ld} -m elf64_s390" ;; sparc*-*linux*) LD="${LD-ld} -m elf64_sparc" ;; esac ;; esac fi rm -rf conftest* ;; *-*-sco3.2v5*) # On SCO OpenServer 5, we need -belf to get full-featured binaries. SAVE_CFLAGS=$CFLAGS CFLAGS="$CFLAGS -belf" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C compiler needs -belf" >&5 printf %s "checking whether the C compiler needs -belf... " >&6; } if test ${lt_cv_cc_needs_belf+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : lt_cv_cc_needs_belf=yes else case e in #( e) lt_cv_cc_needs_belf=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_cc_needs_belf" >&5 printf "%s\n" "$lt_cv_cc_needs_belf" >&6; } if test yes != "$lt_cv_cc_needs_belf"; then # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf CFLAGS=$SAVE_CFLAGS fi ;; *-*solaris*) # Find out what ABI is being produced by ac_compile, and set linker # options accordingly. echo 'int i;' > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then case `$FILECMD conftest.o` in *64-bit*) case $lt_cv_prog_gnu_ld in yes*) case $host in i?86-*-solaris*|x86_64-*-solaris*) LD="${LD-ld} -m elf_x86_64" ;; sparc*-*-solaris*) LD="${LD-ld} -m elf64_sparc" ;; esac # GNU ld 2.21 introduced _sol2 emulations. Use them if available. if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then LD=${LD-ld}_sol2 fi ;; *) if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then LD="${LD-ld} -64" fi ;; esac ;; esac fi rm -rf conftest* ;; esac need_locks=$enable_libtool_lock if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}mt", so it can be a program name with args. set dummy ${ac_tool_prefix}mt; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_MANIFEST_TOOL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$MANIFEST_TOOL"; then ac_cv_prog_MANIFEST_TOOL="$MANIFEST_TOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_MANIFEST_TOOL="${ac_tool_prefix}mt" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi MANIFEST_TOOL=$ac_cv_prog_MANIFEST_TOOL if test -n "$MANIFEST_TOOL"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MANIFEST_TOOL" >&5 printf "%s\n" "$MANIFEST_TOOL" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_MANIFEST_TOOL"; then ac_ct_MANIFEST_TOOL=$MANIFEST_TOOL # Extract the first word of "mt", so it can be a program name with args. set dummy mt; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_MANIFEST_TOOL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_MANIFEST_TOOL"; then ac_cv_prog_ac_ct_MANIFEST_TOOL="$ac_ct_MANIFEST_TOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_MANIFEST_TOOL="mt" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_MANIFEST_TOOL=$ac_cv_prog_ac_ct_MANIFEST_TOOL if test -n "$ac_ct_MANIFEST_TOOL"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_MANIFEST_TOOL" >&5 printf "%s\n" "$ac_ct_MANIFEST_TOOL" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_MANIFEST_TOOL" = x; then MANIFEST_TOOL=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac MANIFEST_TOOL=$ac_ct_MANIFEST_TOOL fi else MANIFEST_TOOL="$ac_cv_prog_MANIFEST_TOOL" fi test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $MANIFEST_TOOL is a manifest tool" >&5 printf %s "checking if $MANIFEST_TOOL is a manifest tool... " >&6; } if test ${lt_cv_path_manifest_tool+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_path_manifest_tool=no echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&5 $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out cat conftest.err >&5 if $GREP 'Manifest Tool' conftest.out > /dev/null; then lt_cv_path_manifest_tool=yes fi rm -f conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_manifest_tool" >&5 printf "%s\n" "$lt_cv_path_manifest_tool" >&6; } if test yes != "$lt_cv_path_manifest_tool"; then MANIFEST_TOOL=: fi case $host_os in rhapsody* | darwin*) if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}dsymutil", so it can be a program name with args. set dummy ${ac_tool_prefix}dsymutil; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_DSYMUTIL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$DSYMUTIL"; then ac_cv_prog_DSYMUTIL="$DSYMUTIL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_DSYMUTIL="${ac_tool_prefix}dsymutil" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi DSYMUTIL=$ac_cv_prog_DSYMUTIL if test -n "$DSYMUTIL"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $DSYMUTIL" >&5 printf "%s\n" "$DSYMUTIL" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_DSYMUTIL"; then ac_ct_DSYMUTIL=$DSYMUTIL # Extract the first word of "dsymutil", so it can be a program name with args. set dummy dsymutil; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_DSYMUTIL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_DSYMUTIL"; then ac_cv_prog_ac_ct_DSYMUTIL="$ac_ct_DSYMUTIL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_DSYMUTIL="dsymutil" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_DSYMUTIL=$ac_cv_prog_ac_ct_DSYMUTIL if test -n "$ac_ct_DSYMUTIL"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DSYMUTIL" >&5 printf "%s\n" "$ac_ct_DSYMUTIL" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_DSYMUTIL" = x; then DSYMUTIL=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac DSYMUTIL=$ac_ct_DSYMUTIL fi else DSYMUTIL="$ac_cv_prog_DSYMUTIL" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}nmedit", so it can be a program name with args. set dummy ${ac_tool_prefix}nmedit; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_NMEDIT+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$NMEDIT"; then ac_cv_prog_NMEDIT="$NMEDIT" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_NMEDIT="${ac_tool_prefix}nmedit" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi NMEDIT=$ac_cv_prog_NMEDIT if test -n "$NMEDIT"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $NMEDIT" >&5 printf "%s\n" "$NMEDIT" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_NMEDIT"; then ac_ct_NMEDIT=$NMEDIT # Extract the first word of "nmedit", so it can be a program name with args. set dummy nmedit; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_NMEDIT+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_NMEDIT"; then ac_cv_prog_ac_ct_NMEDIT="$ac_ct_NMEDIT" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_NMEDIT="nmedit" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_NMEDIT=$ac_cv_prog_ac_ct_NMEDIT if test -n "$ac_ct_NMEDIT"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NMEDIT" >&5 printf "%s\n" "$ac_ct_NMEDIT" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_NMEDIT" = x; then NMEDIT=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac NMEDIT=$ac_ct_NMEDIT fi else NMEDIT="$ac_cv_prog_NMEDIT" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}lipo", so it can be a program name with args. set dummy ${ac_tool_prefix}lipo; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_LIPO+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$LIPO"; then ac_cv_prog_LIPO="$LIPO" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_LIPO="${ac_tool_prefix}lipo" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi LIPO=$ac_cv_prog_LIPO if test -n "$LIPO"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LIPO" >&5 printf "%s\n" "$LIPO" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_LIPO"; then ac_ct_LIPO=$LIPO # Extract the first word of "lipo", so it can be a program name with args. set dummy lipo; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_LIPO+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_LIPO"; then ac_cv_prog_ac_ct_LIPO="$ac_ct_LIPO" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_LIPO="lipo" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_LIPO=$ac_cv_prog_ac_ct_LIPO if test -n "$ac_ct_LIPO"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_LIPO" >&5 printf "%s\n" "$ac_ct_LIPO" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_LIPO" = x; then LIPO=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac LIPO=$ac_ct_LIPO fi else LIPO="$ac_cv_prog_LIPO" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}otool", so it can be a program name with args. set dummy ${ac_tool_prefix}otool; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_OTOOL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$OTOOL"; then ac_cv_prog_OTOOL="$OTOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_OTOOL="${ac_tool_prefix}otool" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi OTOOL=$ac_cv_prog_OTOOL if test -n "$OTOOL"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $OTOOL" >&5 printf "%s\n" "$OTOOL" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_OTOOL"; then ac_ct_OTOOL=$OTOOL # Extract the first word of "otool", so it can be a program name with args. set dummy otool; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_OTOOL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_OTOOL"; then ac_cv_prog_ac_ct_OTOOL="$ac_ct_OTOOL" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_OTOOL="otool" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_OTOOL=$ac_cv_prog_ac_ct_OTOOL if test -n "$ac_ct_OTOOL"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL" >&5 printf "%s\n" "$ac_ct_OTOOL" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_OTOOL" = x; then OTOOL=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac OTOOL=$ac_ct_OTOOL fi else OTOOL="$ac_cv_prog_OTOOL" fi if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}otool64", so it can be a program name with args. set dummy ${ac_tool_prefix}otool64; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_OTOOL64+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$OTOOL64"; then ac_cv_prog_OTOOL64="$OTOOL64" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_OTOOL64="${ac_tool_prefix}otool64" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi OTOOL64=$ac_cv_prog_OTOOL64 if test -n "$OTOOL64"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $OTOOL64" >&5 printf "%s\n" "$OTOOL64" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_prog_OTOOL64"; then ac_ct_OTOOL64=$OTOOL64 # Extract the first word of "otool64", so it can be a program name with args. set dummy otool64; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_OTOOL64+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_OTOOL64"; then ac_cv_prog_ac_ct_OTOOL64="$ac_ct_OTOOL64" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_OTOOL64="otool64" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_OTOOL64=$ac_cv_prog_ac_ct_OTOOL64 if test -n "$ac_ct_OTOOL64"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL64" >&5 printf "%s\n" "$ac_ct_OTOOL64" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_ct_OTOOL64" = x; then OTOOL64=":" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac OTOOL64=$ac_ct_OTOOL64 fi else OTOOL64="$ac_cv_prog_OTOOL64" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for -single_module linker flag" >&5 printf %s "checking for -single_module linker flag... " >&6; } if test ${lt_cv_apple_cc_single_mod+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_apple_cc_single_mod=no if test -z "$LT_MULTI_MODULE"; then # By default we will add the -single_module flag. You can override # by either setting the environment variable LT_MULTI_MODULE # non-empty at configure time, or by adding -multi_module to the # link flags. rm -rf libconftest.dylib* echo "int foo(void){return 1;}" > conftest.c echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ -dynamiclib -Wl,-single_module conftest.c" >&5 $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ -dynamiclib -Wl,-single_module conftest.c 2>conftest.err _lt_result=$? # If there is a non-empty error log, and "single_module" # appears in it, assume the flag caused a linker warning if test -s conftest.err && $GREP single_module conftest.err; then cat conftest.err >&5 # Otherwise, if the output was created with a 0 exit code from # the compiler, it worked. elif test -f libconftest.dylib && test 0 = "$_lt_result"; then lt_cv_apple_cc_single_mod=yes else cat conftest.err >&5 fi rm -rf libconftest.dylib* rm -f conftest.* fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_apple_cc_single_mod" >&5 printf "%s\n" "$lt_cv_apple_cc_single_mod" >&6; } # Feature test to disable chained fixups since it is not # compatible with '-undefined dynamic_lookup' { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for -no_fixup_chains linker flag" >&5 printf %s "checking for -no_fixup_chains linker flag... " >&6; } if test ${lt_cv_support_no_fixup_chains+y} then : printf %s "(cached) " >&6 else case e in #( e) save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -Wl,-no_fixup_chains" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : lt_cv_support_no_fixup_chains=yes else case e in #( e) lt_cv_support_no_fixup_chains=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$save_LDFLAGS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_support_no_fixup_chains" >&5 printf "%s\n" "$lt_cv_support_no_fixup_chains" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for -exported_symbols_list linker flag" >&5 printf %s "checking for -exported_symbols_list linker flag... " >&6; } if test ${lt_cv_ld_exported_symbols_list+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_ld_exported_symbols_list=no save_LDFLAGS=$LDFLAGS echo "_main" > conftest.sym LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : lt_cv_ld_exported_symbols_list=yes else case e in #( e) lt_cv_ld_exported_symbols_list=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$save_LDFLAGS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_exported_symbols_list" >&5 printf "%s\n" "$lt_cv_ld_exported_symbols_list" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for -force_load linker flag" >&5 printf %s "checking for -force_load linker flag... " >&6; } if test ${lt_cv_ld_force_load+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_ld_force_load=no cat > conftest.c << _LT_EOF int forced_loaded() { return 2;} _LT_EOF echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&5 $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&5 echo "$AR $AR_FLAGS libconftest.a conftest.o" >&5 $AR $AR_FLAGS libconftest.a conftest.o 2>&5 echo "$RANLIB libconftest.a" >&5 $RANLIB libconftest.a 2>&5 cat > conftest.c << _LT_EOF int main(void) { return 0;} _LT_EOF echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&5 $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err _lt_result=$? if test -s conftest.err && $GREP force_load conftest.err; then cat conftest.err >&5 elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then lt_cv_ld_force_load=yes else cat conftest.err >&5 fi rm -f conftest.err libconftest.a conftest conftest.c rm -rf conftest.dSYM ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_force_load" >&5 printf "%s\n" "$lt_cv_ld_force_load" >&6; } case $host_os in rhapsody* | darwin1.[012]) _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; darwin1.*) _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; darwin*) case $MACOSX_DEPLOYMENT_TARGET,$host in 10.[012],*|,*powerpc*-darwin[5-8]*) _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; *) _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' if test yes = "$lt_cv_support_no_fixup_chains"; then as_fn_append _lt_dar_allow_undefined ' $wl-no_fixup_chains' fi ;; esac ;; esac if test yes = "$lt_cv_apple_cc_single_mod"; then _lt_dar_single_mod='$single_module' fi _lt_dar_needs_single_mod=no case $host_os in rhapsody* | darwin1.*) _lt_dar_needs_single_mod=yes ;; darwin*) # When targeting Mac OS X 10.4 (darwin 8) or later, # -single_module is the default and -multi_module is unsupported. # The toolchain on macOS 10.14 (darwin 18) and later cannot # target any OS version that needs -single_module. case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in 10.0,*-darwin[567].*|10.[0-3],*-darwin[5-9].*|10.[0-3],*-darwin1[0-7].*) _lt_dar_needs_single_mod=yes ;; esac ;; esac if test yes = "$lt_cv_ld_exported_symbols_list"; then _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' else _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' fi if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then _lt_dsymutil='~$DSYMUTIL $lib || :' else _lt_dsymutil= fi ;; esac # func_munge_path_list VARIABLE PATH # ----------------------------------- # VARIABLE is name of variable containing _space_ separated list of # directories to be munged by the contents of PATH, which is string # having a format: # "DIR[:DIR]:" # string "DIR[ DIR]" will be prepended to VARIABLE # ":DIR[:DIR]" # string "DIR[ DIR]" will be appended to VARIABLE # "DIRP[:DIRP]::[DIRA:]DIRA" # string "DIRP[ DIRP]" will be prepended to VARIABLE and string # "DIRA[ DIRA]" will be appended to VARIABLE # "DIR[:DIR]" # VARIABLE will be replaced by "DIR[ DIR]" func_munge_path_list () { case x$2 in x) ;; *:) eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" ;; x:*) eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" ;; *::*) eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" ;; *) eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" ;; esac } ac_header= ac_cache= for ac_item in $ac_header_c_list do if test $ac_cache; then ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then printf "%s\n" "#define $ac_item 1" >> confdefs.h fi ac_header= ac_cache= elif test $ac_header; then ac_cache=$ac_item else ac_header=$ac_item fi done if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes then : printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default " if test "x$ac_cv_header_dlfcn_h" = xyes then : printf "%s\n" "#define HAVE_DLFCN_H 1" >>confdefs.h fi func_stripname_cnf () { case $2 in .*) func_stripname_result=`$ECHO "$3" | $SED "s%^$1%%; s%\\\\$2\$%%"`;; *) func_stripname_result=`$ECHO "$3" | $SED "s%^$1%%; s%$2\$%%"`;; esac } # func_stripname_cnf # Set options enable_dlopen=no enable_win32_dll=no # Check whether --enable-shared was given. if test ${enable_shared+y} then : enableval=$enable_shared; p=${PACKAGE-default} case $enableval in yes) enable_shared=yes ;; no) enable_shared=no ;; *) enable_shared=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_shared=yes fi done IFS=$lt_save_ifs ;; esac else case e in #( e) enable_shared=yes ;; esac fi # Check whether --enable-static was given. if test ${enable_static+y} then : enableval=$enable_static; p=${PACKAGE-default} case $enableval in yes) enable_static=yes ;; no) enable_static=no ;; *) enable_static=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_static=yes fi done IFS=$lt_save_ifs ;; esac else case e in #( e) enable_static=yes ;; esac fi # Check whether --enable-pic was given. if test ${enable_pic+y} then : enableval=$enable_pic; lt_p=${PACKAGE-default} case $enableval in yes|no) pic_mode=$enableval ;; *) pic_mode=default # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for lt_pkg in $enableval; do IFS=$lt_save_ifs if test "X$lt_pkg" = "X$lt_p"; then pic_mode=yes fi done IFS=$lt_save_ifs ;; esac else case e in #( e) # Check whether --with-pic was given. if test ${with_pic+y} then : withval=$with_pic; lt_p=${PACKAGE-default} case $withval in yes|no) pic_mode=$withval ;; *) pic_mode=default # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for lt_pkg in $withval; do IFS=$lt_save_ifs if test "X$lt_pkg" = "X$lt_p"; then pic_mode=yes fi done IFS=$lt_save_ifs ;; esac else case e in #( e) pic_mode=default ;; esac fi ;; esac fi # Check whether --enable-fast-install was given. if test ${enable_fast_install+y} then : enableval=$enable_fast_install; p=${PACKAGE-default} case $enableval in yes) enable_fast_install=yes ;; no) enable_fast_install=no ;; *) enable_fast_install=no # Look at the argument we got. We use all the common list separators. lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, for pkg in $enableval; do IFS=$lt_save_ifs if test "X$pkg" = "X$p"; then enable_fast_install=yes fi done IFS=$lt_save_ifs ;; esac else case e in #( e) enable_fast_install=yes ;; esac fi shared_archive_member_spec= case $host,$enable_shared in power*-*-aix[5-9]*,yes) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking which variant of shared library versioning to provide" >&5 printf %s "checking which variant of shared library versioning to provide... " >&6; } # Check whether --enable-aix-soname was given. if test ${enable_aix_soname+y} then : enableval=$enable_aix_soname; case $enableval in aix|svr4|both) ;; *) as_fn_error $? "Unknown argument to --enable-aix-soname" "$LINENO" 5 ;; esac lt_cv_with_aix_soname=$enable_aix_soname else case e in #( e) # Check whether --with-aix-soname was given. if test ${with_aix_soname+y} then : withval=$with_aix_soname; case $withval in aix|svr4|both) ;; *) as_fn_error $? "Unknown argument to --with-aix-soname" "$LINENO" 5 ;; esac lt_cv_with_aix_soname=$with_aix_soname else case e in #( e) if test ${lt_cv_with_aix_soname+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_with_aix_soname=aix ;; esac fi ;; esac fi enable_aix_soname=$lt_cv_with_aix_soname ;; esac fi with_aix_soname=$enable_aix_soname { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_aix_soname" >&5 printf "%s\n" "$with_aix_soname" >&6; } if test aix != "$with_aix_soname"; then # For the AIX way of multilib, we name the shared archive member # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, # the AIX toolchain works better with OBJECT_MODE set (default 32). if test 64 = "${OBJECT_MODE-32}"; then shared_archive_member_spec=shr_64 else shared_archive_member_spec=shr fi fi ;; *) with_aix_soname=aix ;; esac # This can be used to rebuild libtool when needed LIBTOOL_DEPS=$ltmain # Always use our own libtool. LIBTOOL='$(SHELL) $(top_builddir)/libtool' test -z "$LN_S" && LN_S="ln -s" if test -n "${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for objdir" >&5 printf %s "checking for objdir... " >&6; } if test ${lt_cv_objdir+y} then : printf %s "(cached) " >&6 else case e in #( e) rm -f .libs 2>/dev/null mkdir .libs 2>/dev/null if test -d .libs; then lt_cv_objdir=.libs else # MS-DOS does not allow filenames that begin with a dot. lt_cv_objdir=_libs fi rmdir .libs 2>/dev/null ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_objdir" >&5 printf "%s\n" "$lt_cv_objdir" >&6; } objdir=$lt_cv_objdir printf "%s\n" "#define LT_OBJDIR \"$lt_cv_objdir/\"" >>confdefs.h case $host_os in aix3*) # AIX sometimes has problems with the GCC collect2 program. For some # reason, if we set the COLLECT_NAMES environment variable, the problems # vanish in a puff of smoke. if test set != "${COLLECT_NAMES+set}"; then COLLECT_NAMES= export COLLECT_NAMES fi ;; esac # Global variables: ofile=libtool can_build_shared=yes # All known linkers require a '.a' archive for static linking (except MSVC and # ICC, which need '.lib'). libext=a with_gnu_ld=$lt_cv_prog_gnu_ld old_CC=$CC old_CFLAGS=$CFLAGS # Set sane defaults for various variables test -z "$CC" && CC=cc test -z "$LTCC" && LTCC=$CC test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS test -z "$LD" && LD=ld test -z "$ac_objext" && ac_objext=o func_cc_basename $compiler cc_basename=$func_cc_basename_result # Only perform the check for file, if the check method requires it test -z "$MAGIC_CMD" && MAGIC_CMD=file case $deplibs_check_method in file_magic*) if test "$file_magic_cmd" = '$MAGIC_CMD'; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ${ac_tool_prefix}file" >&5 printf %s "checking for ${ac_tool_prefix}file... " >&6; } if test ${lt_cv_path_MAGIC_CMD+y} then : printf %s "(cached) " >&6 else case e in #( e) case $MAGIC_CMD in [\\/*] | ?:[\\/]*) lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. ;; *) lt_save_MAGIC_CMD=$MAGIC_CMD lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" for ac_dir in $ac_dummy; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/${ac_tool_prefix}file"; then lt_cv_path_MAGIC_CMD=$ac_dir/"${ac_tool_prefix}file" if test -n "$file_magic_test_file"; then case $deplibs_check_method in "file_magic "*) file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` MAGIC_CMD=$lt_cv_path_MAGIC_CMD if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | $EGREP "$file_magic_regex" > /dev/null; then : else cat <<_LT_EOF 1>&2 *** Warning: the command libtool uses to detect shared libraries, *** $file_magic_cmd, produces output that libtool cannot recognize. *** The result is that libtool may fail to recognize shared libraries *** as such. This will affect the creation of libtool libraries that *** depend on shared libraries, but programs linked with such libtool *** libraries will work regardless of this problem. Nevertheless, you *** may want to report the problem to your system manager and/or to *** bug-libtool@gnu.org _LT_EOF fi ;; esac fi break fi done IFS=$lt_save_ifs MAGIC_CMD=$lt_save_MAGIC_CMD ;; esac ;; esac fi MAGIC_CMD=$lt_cv_path_MAGIC_CMD if test -n "$MAGIC_CMD"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 printf "%s\n" "$MAGIC_CMD" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test -z "$lt_cv_path_MAGIC_CMD"; then if test -n "$ac_tool_prefix"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for file" >&5 printf %s "checking for file... " >&6; } if test ${lt_cv_path_MAGIC_CMD+y} then : printf %s "(cached) " >&6 else case e in #( e) case $MAGIC_CMD in [\\/*] | ?:[\\/]*) lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. ;; *) lt_save_MAGIC_CMD=$MAGIC_CMD lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" for ac_dir in $ac_dummy; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/file"; then lt_cv_path_MAGIC_CMD=$ac_dir/"file" if test -n "$file_magic_test_file"; then case $deplibs_check_method in "file_magic "*) file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` MAGIC_CMD=$lt_cv_path_MAGIC_CMD if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | $EGREP "$file_magic_regex" > /dev/null; then : else cat <<_LT_EOF 1>&2 *** Warning: the command libtool uses to detect shared libraries, *** $file_magic_cmd, produces output that libtool cannot recognize. *** The result is that libtool may fail to recognize shared libraries *** as such. This will affect the creation of libtool libraries that *** depend on shared libraries, but programs linked with such libtool *** libraries will work regardless of this problem. Nevertheless, you *** may want to report the problem to your system manager and/or to *** bug-libtool@gnu.org _LT_EOF fi ;; esac fi break fi done IFS=$lt_save_ifs MAGIC_CMD=$lt_save_MAGIC_CMD ;; esac ;; esac fi MAGIC_CMD=$lt_cv_path_MAGIC_CMD if test -n "$MAGIC_CMD"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 printf "%s\n" "$MAGIC_CMD" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi else MAGIC_CMD=: fi fi fi ;; esac # Use C for the default configuration in the libtool script lt_save_CC=$CC ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu # Source file extension for C test sources. ac_ext=c # Object file extension for compiled C test sources. objext=o objext=$objext # Code to be used in simple compile tests lt_simple_compile_test_code="int some_variable = 0;" # Code to be used in simple link tests lt_simple_link_test_code='int main(void){return(0);}' # If no C compiler was specified, use CC. LTCC=${LTCC-"$CC"} # If no C compiler flags were specified, use CFLAGS. LTCFLAGS=${LTCFLAGS-"$CFLAGS"} # Allow CC to be a program name with arguments. compiler=$CC # Save the default compiler, since it gets overwritten when the other # tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. compiler_DEFAULT=$CC # save warnings/boilerplate of simple test code ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" >conftest.$ac_ext eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_compiler_boilerplate=`cat conftest.err` $RM conftest* ac_outfile=conftest.$ac_objext echo "$lt_simple_link_test_code" >conftest.$ac_ext eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_linker_boilerplate=`cat conftest.err` $RM -r conftest* ## CAVEAT EMPTOR: ## There is no encapsulation within the following macros, do not change ## the running order or otherwise move them around unless you know exactly ## what you are doing... if test -n "$compiler"; then lt_prog_compiler_no_builtin_flag= if test yes = "$GCC"; then case $cc_basename in nvcc*) lt_prog_compiler_no_builtin_flag=' -Xcompiler -fno-builtin' ;; *) lt_prog_compiler_no_builtin_flag=' -fno-builtin' ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -fno-rtti -fno-exceptions" >&5 printf %s "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; } if test ${lt_cv_prog_compiler_rtti_exceptions+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_rtti_exceptions=no ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-fno-rtti -fno-exceptions" ## exclude from sc_useless_quotes_in_assignment # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. # The option is referenced via a variable to avoid confusing sed. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_rtti_exceptions=yes fi fi $RM conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_rtti_exceptions" >&5 printf "%s\n" "$lt_cv_prog_compiler_rtti_exceptions" >&6; } if test yes = "$lt_cv_prog_compiler_rtti_exceptions"; then lt_prog_compiler_no_builtin_flag="$lt_prog_compiler_no_builtin_flag -fno-rtti -fno-exceptions" else : fi fi lt_prog_compiler_wl= lt_prog_compiler_pic= lt_prog_compiler_static= if test yes = "$GCC"; then lt_prog_compiler_wl='-Wl,' lt_prog_compiler_static='-static' case $host_os in aix*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static='-Bstatic' fi lt_prog_compiler_pic='-fPIC' ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support lt_prog_compiler_pic='-fPIC' ;; m68k) # FIXME: we need at least 68020 code to build shared libraries, but # adding the '-m68020' flag to GCC prevents building anything better, # like '-m68040'. lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4' ;; esac ;; beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) # PIC is the default for these OSes. ;; mingw* | windows* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). # Although the cygwin gcc ignores -fPIC, still need this for old-style # (--disable-auto-import) libraries lt_prog_compiler_pic='-DDLL_EXPORT' case $host_os in os2*) lt_prog_compiler_static='$wl-static' ;; esac ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files lt_prog_compiler_pic='-fno-common' ;; haiku*) # PIC is the default for Haiku. # The "-static" flag exists, but is broken. lt_prog_compiler_static= ;; hpux*) # PIC is the default for 64-bit PA HP-UX, but not for 32-bit # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag # sets the default TLS model and affects inlining. case $host_cpu in hppa*64*) # +Z the default ;; *) lt_prog_compiler_pic='-fPIC' ;; esac ;; interix[3-9]*) # Interix 3.x gcc -fpic/-fPIC options generate broken code. # Instead, we relocate shared libraries at runtime. ;; msdosdjgpp*) # Just because we use GCC doesn't mean we suddenly get shared libraries # on systems that don't support them. lt_prog_compiler_can_build_shared=no enable_shared=no ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic='-fPIC -shared' ;; sysv4*MP*) if test -d /usr/nec; then lt_prog_compiler_pic=-Kconform_pic fi ;; *) lt_prog_compiler_pic='-fPIC' ;; esac case $cc_basename in nvcc*) # Cuda Compiler Driver 2.2 lt_prog_compiler_wl='-Xlinker ' if test -n "$lt_prog_compiler_pic"; then lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic" fi ;; esac else # PORTME Check for flag to pass linker flags through the system compiler. case $host_os in aix*) lt_prog_compiler_wl='-Wl,' if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static='-Bstatic' else lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp' fi ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files lt_prog_compiler_pic='-fno-common' case $cc_basename in nagfor*) # NAG Fortran compiler lt_prog_compiler_wl='-Wl,-Wl,,' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; esac ;; mingw* | windows* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). lt_prog_compiler_pic='-DDLL_EXPORT' case $host_os in os2*) lt_prog_compiler_static='$wl-static' ;; esac ;; hpux9* | hpux10* | hpux11*) lt_prog_compiler_wl='-Wl,' # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but # not for PA HP-UX. case $host_cpu in hppa*64*|ia64*) # +Z the default ;; *) lt_prog_compiler_pic='+Z' ;; esac # Is there a better lt_prog_compiler_static that works with the bundled CC? lt_prog_compiler_static='$wl-a ${wl}archive' ;; irix5* | irix6* | nonstopux*) lt_prog_compiler_wl='-Wl,' # PIC (with -KPIC) is the default. lt_prog_compiler_static='-non_shared' ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in # old Intel for x86_64, which still supported -KPIC. ecc*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-static' ;; *flang* | ftn | f18* | f95*) # Flang compiler. lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # flang / f18. f95 an alias for gfortran or flang on Debian flang* | f18* | f95*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # icc used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. icc* | ifort*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # Lahey Fortran 8.1. lf95*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='--shared' lt_prog_compiler_static='--static' ;; nagfor*) # NAG Fortran compiler lt_prog_compiler_wl='-Wl,-Wl,,' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; tcc*) # Fabrice Bellard et al's Tiny C Compiler lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) # Portland Group compilers (*not* the Pentium gcc compiler, # which looks to be a dead project) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fpic' lt_prog_compiler_static='-Bstatic' ;; ccc*) lt_prog_compiler_wl='-Wl,' # All Alpha code is PIC. lt_prog_compiler_static='-non_shared' ;; xl* | bgxl* | bgf* | mpixl*) # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-qpic' lt_prog_compiler_static='-qstaticlink' ;; *) case `$CC -V 2>&1 | $SED 5q` in *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*) # Sun Fortran 8.3 passes all unrecognized flags to the linker lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='' ;; *Sun\ F* | *Sun*Fortran*) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='-Qoption ld ' ;; *Sun\ C*) # Sun C 5.9 lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='-Wl,' ;; *Intel*\ [CF]*Compiler*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; *Portland\ Group*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fpic' lt_prog_compiler_static='-Bstatic' ;; esac ;; esac ;; newsos6) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; *-mlibc) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic='-fPIC -shared' ;; osf3* | osf4* | osf5*) lt_prog_compiler_wl='-Wl,' # All OSF/1 code is PIC. lt_prog_compiler_static='-non_shared' ;; rdos*) lt_prog_compiler_static='-non_shared' ;; serenity*) ;; solaris*) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' case $cc_basename in f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) lt_prog_compiler_wl='-Qoption ld ';; *) lt_prog_compiler_wl='-Wl,';; esac ;; sunos4*) lt_prog_compiler_wl='-Qoption ld ' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; sysv4 | sysv4.2uw2* | sysv4.3*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; sysv4*MP*) if test -d /usr/nec; then lt_prog_compiler_pic='-Kconform_pic' lt_prog_compiler_static='-Bstatic' fi ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; unicos*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_can_build_shared=no ;; uts4*) lt_prog_compiler_pic='-pic' lt_prog_compiler_static='-Bstatic' ;; *) lt_prog_compiler_can_build_shared=no ;; esac fi case $host_os in # For platforms that do not support PIC, -DPIC is meaningless: *djgpp*) lt_prog_compiler_pic= ;; *) lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC" ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 printf %s "checking for $compiler option to produce PIC... " >&6; } if test ${lt_cv_prog_compiler_pic+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_pic=$lt_prog_compiler_pic ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5 printf "%s\n" "$lt_cv_prog_compiler_pic" >&6; } lt_prog_compiler_pic=$lt_cv_prog_compiler_pic # # Check to make sure the PIC flag actually works. # if test -n "$lt_prog_compiler_pic"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5 printf %s "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; } if test ${lt_cv_prog_compiler_pic_works+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_pic_works=no ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="$lt_prog_compiler_pic -DPIC" ## exclude from sc_useless_quotes_in_assignment # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. # The option is referenced via a variable to avoid confusing sed. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_pic_works=yes fi fi $RM conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5 printf "%s\n" "$lt_cv_prog_compiler_pic_works" >&6; } if test yes = "$lt_cv_prog_compiler_pic_works"; then case $lt_prog_compiler_pic in "" | " "*) ;; *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;; esac else lt_prog_compiler_pic= lt_prog_compiler_can_build_shared=no fi fi # # Check to make sure the static flag actually works. # wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 printf %s "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } if test ${lt_cv_prog_compiler_static_works+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_static_works=no save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS $lt_tmp_static_flag" echo "$lt_simple_link_test_code" > conftest.$ac_ext if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then # The linker can only warn and ignore the option if not recognized # So say no if there are warnings if test -s conftest.err; then # Append any errors to the config.log. cat conftest.err 1>&5 $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_static_works=yes fi else lt_cv_prog_compiler_static_works=yes fi fi $RM -r conftest* LDFLAGS=$save_LDFLAGS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5 printf "%s\n" "$lt_cv_prog_compiler_static_works" >&6; } if test yes = "$lt_cv_prog_compiler_static_works"; then : else lt_prog_compiler_static= fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 printf %s "checking if $compiler supports -c -o file.$ac_objext... " >&6; } if test ${lt_cv_prog_compiler_c_o+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_c_o=no $RM -r conftest 2>/dev/null mkdir conftest cd conftest mkdir out echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-o out/conftest2.$ac_objext" # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then lt_cv_prog_compiler_c_o=yes fi fi chmod u+w . 2>&5 $RM conftest* # SGI C++ compiler will create directory out/ii_files/ for # template instantiation test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files $RM out/* && rmdir out cd .. $RM -r conftest $RM conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 printf "%s\n" "$lt_cv_prog_compiler_c_o" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 printf %s "checking if $compiler supports -c -o file.$ac_objext... " >&6; } if test ${lt_cv_prog_compiler_c_o+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_c_o=no $RM -r conftest 2>/dev/null mkdir conftest cd conftest mkdir out echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-o out/conftest2.$ac_objext" # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then lt_cv_prog_compiler_c_o=yes fi fi chmod u+w . 2>&5 $RM conftest* # SGI C++ compiler will create directory out/ii_files/ for # template instantiation test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files $RM out/* && rmdir out cd .. $RM -r conftest $RM conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 printf "%s\n" "$lt_cv_prog_compiler_c_o" >&6; } hard_links=nottested if test no = "$lt_cv_prog_compiler_c_o" && test no != "$need_locks"; then # do not overwrite the value of need_locks provided by the user { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5 printf %s "checking if we can lock with hard links... " >&6; } hard_links=yes $RM conftest* ln conftest.a conftest.b 2>/dev/null && hard_links=no touch conftest.a ln conftest.a conftest.b 2>&5 || hard_links=no ln conftest.a conftest.b 2>/dev/null && hard_links=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5 printf "%s\n" "$hard_links" >&6; } if test no = "$hard_links"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&5 printf "%s\n" "$as_me: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&2;} need_locks=warn fi else need_locks=no fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 printf %s "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } runpath_var= allow_undefined_flag= always_export_symbols=no archive_cmds= archive_expsym_cmds= compiler_needs_object=no enable_shared_with_static_runtimes=no export_dynamic_flag_spec= export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' hardcode_automatic=no hardcode_direct=no hardcode_direct_absolute=no hardcode_libdir_flag_spec= hardcode_libdir_separator= hardcode_minus_L=no hardcode_shlibpath_var=unsupported inherit_rpath=no link_all_deplibs=unknown module_cmds= module_expsym_cmds= old_archive_from_new_cmds= old_archive_from_expsyms_cmds= thread_safe_flag_spec= whole_archive_flag_spec= # include_expsyms should be a list of space-separated symbols to be *always* # included in the symbol list include_expsyms= # exclude_expsyms can be an extended regexp of symbols to exclude # it will be wrapped by ' (' and ')$', so one must not match beginning or # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', # as well as any symbol that contains 'd'. exclude_expsyms='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out # platforms (ab)use it in PIC code, but their linkers get confused if # the symbol is explicitly referenced. Since portable code cannot # rely on this symbol name, it's probably fine to never include it in # preloaded symbol tables. # Exclude shared library initialization/finalization symbols. extract_expsyms_cmds= case $host_os in cygwin* | mingw* | windows* | pw32* | cegcc*) # FIXME: the MSVC++ and ICC port hasn't been tested in a loooong time # When not using gcc, we currently assume that we are using # Microsoft Visual C++ or Intel C++ Compiler. if test yes != "$GCC"; then with_gnu_ld=no fi ;; interix*) # we just hope/assume this is gcc and not c89 (= MSVC++ or ICC) with_gnu_ld=yes ;; linux* | k*bsd*-gnu | gnu*) link_all_deplibs=no ;; esac ld_shlibs=yes # On some targets, GNU ld is compatible enough with the native linker # that we're better off using the native interface for both. lt_use_gnu_ld_interface=no if test yes = "$with_gnu_ld"; then case $host_os in aix*) # The AIX port of GNU ld has always aspired to compatibility # with the native linker. However, as the warning in the GNU ld # block says, versions before 2.19.5* couldn't really create working # shared libraries, regardless of the interface used. case `$LD -v 2>&1` in *\ \(GNU\ Binutils\)\ 2.19.5*) ;; *\ \(GNU\ Binutils\)\ 2.[2-9]*) ;; *\ \(GNU\ Binutils\)\ [3-9]*) ;; *) lt_use_gnu_ld_interface=yes ;; esac ;; *) lt_use_gnu_ld_interface=yes ;; esac fi if test yes = "$lt_use_gnu_ld_interface"; then # If archive_cmds runs LD, not CC, wlarc should be empty wlarc='$wl' # Set some defaults for GNU ld with shared library support. These # are reset later if shared libraries are not supported. Putting them # here allows them to be overridden if necessary. runpath_var=LD_RUN_PATH hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' export_dynamic_flag_spec='$wl--export-dynamic' # ancient GNU ld didn't support --whole-archive et. al. if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then whole_archive_flag_spec=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' else whole_archive_flag_spec= fi supports_anon_versioning=no case `$LD -v | $SED -e 's/([^)]\+)\s\+//' 2>&1` in *GNU\ gold*) supports_anon_versioning=yes ;; *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11 *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... *\ 2.11.*) ;; # other 2.11 versions *) supports_anon_versioning=yes ;; esac # See if GNU ld supports shared libraries. case $host_os in aix[3-9]*) # On AIX/PPC, the GNU linker is very broken if test ia64 != "$host_cpu"; then ld_shlibs=no cat <<_LT_EOF 1>&2 *** Warning: the GNU linker, at least up to release 2.19, is reported *** to be unable to reliably create shared libraries on AIX. *** Therefore, libtool is disabling shared libraries support. If you *** really care for shared libraries, you may want to install binutils *** 2.20 or above, or modify your PATH so that a non-GNU linker is found. *** You will then need to restart the configuration process. _LT_EOF fi ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='' ;; m68k) archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; esac ;; beos*) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then allow_undefined_flag=unsupported # Joseph Beckenbach says some releases of gcc # support --undefined. This deserves some investigation. FIXME archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' else ld_shlibs=no fi ;; cygwin* | mingw* | windows* | pw32* | cegcc*) # _LT_TAGVAR(hardcode_libdir_flag_spec, ) is actually meaningless, # as there is no search path for DLLs. hardcode_libdir_flag_spec='-L$libdir' export_dynamic_flag_spec='$wl--export-all-symbols' allow_undefined_flag=unsupported always_export_symbols=no enable_shared_with_static_runtimes=yes export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols' exclude_expsyms='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname' file_list_spec='@' if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' # If the export-symbols file already is a .def file, use it as # is; otherwise, prepend EXPORTS... archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then cp $export_symbols $output_objdir/$soname.def; else echo EXPORTS > $output_objdir/$soname.def; cat $export_symbols >> $output_objdir/$soname.def; fi~ $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' else ld_shlibs=no fi ;; haiku*) archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' link_all_deplibs=no ;; os2*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes allow_undefined_flag=unsupported shrext_cmds=.dll archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' old_archive_from_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' enable_shared_with_static_runtimes=yes file_list_spec='@' ;; interix[3-9]*) hardcode_direct=no hardcode_shlibpath_var=no hardcode_libdir_flag_spec='$wl-rpath,$libdir' export_dynamic_flag_spec='$wl-E' # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. # Instead, shared libraries are loaded at an image base (0x10000000 by # default) and relocated if they conflict, which is a slow very memory # consuming and fragmenting process. To avoid this, we pick a random, # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link # time. Moving up from 0x10000000 also allows more sbrk(2) space. archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' archive_expsym_cmds='$SED "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' ;; gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) tmp_diet=no if test linux-dietlibc = "$host_os"; then case $cc_basename in diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) esac fi if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ && test no = "$tmp_diet" then tmp_addflag=' $pic_flag' tmp_sharedflag='-shared' case $cc_basename,$host_cpu in pgcc*) # Portland Group C compiler whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' tmp_addflag=' $pic_flag' ;; pgf77* | pgf90* | pgf95* | pgfortran*) # Portland Group f77 and f90 compilers whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' tmp_addflag=' $pic_flag -Mnomain' ;; ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 tmp_addflag=' -i_dynamic' ;; efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 tmp_addflag=' -i_dynamic -nofor_main' ;; ifc* | ifort*) # Intel Fortran compiler tmp_addflag=' -nofor_main' ;; lf95*) # Lahey Fortran 8.1 whole_archive_flag_spec= tmp_sharedflag='--shared' ;; nagfor*) # NAGFOR 5.3 tmp_sharedflag='-Wl,-shared' ;; xl[cC]* | bgxl[cC]* | mpixl[cC]*) # IBM XL C 8.0 on PPC (deal with xlf below) tmp_sharedflag='-qmkshrobj' tmp_addflag= ;; nvcc*) # Cuda Compiler Driver 2.2 whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' compiler_needs_object=yes ;; esac case `$CC -V 2>&1 | $SED 5q` in *Sun\ C*) # Sun C 5.9 whole_archive_flag_spec='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' compiler_needs_object=yes tmp_sharedflag='-G' ;; *Sun\ F*) # Sun Fortran 8.3 tmp_sharedflag='-G' ;; esac archive_cmds='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' if test yes = "$supports_anon_versioning"; then archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' fi case $cc_basename in tcc*) hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' export_dynamic_flag_spec='-rdynamic' ;; xlf* | bgf* | bgxlf* | mpixlf*) # IBM XL Fortran 10.1 on PPC cannot create shared libs itself whole_archive_flag_spec='--whole-archive$convenience --no-whole-archive' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' if test yes = "$supports_anon_versioning"; then archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' fi ;; esac else ld_shlibs=no fi ;; *-mlibc) archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' wlarc= else archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' fi ;; solaris*) if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then ld_shlibs=no cat <<_LT_EOF 1>&2 *** Warning: The releases 2.8.* of the GNU linker cannot reliably *** create shared libraries on Solaris systems. Therefore, libtool *** is disabling shared libraries support. We urge you to upgrade GNU *** binutils to release 2.9.1 or newer. Another option is to modify *** your PATH or compiler configuration so that the native linker is *** used, and then restart. _LT_EOF elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else ld_shlibs=no fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) case `$LD -v 2>&1` in *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) ld_shlibs=no cat <<_LT_EOF 1>&2 *** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot *** reliably create shared libraries on SCO systems. Therefore, libtool *** is disabling shared libraries support. We urge you to upgrade GNU *** binutils to release 2.16.91.0.3 or newer. Another option is to modify *** your PATH or compiler configuration so that the native linker is *** used, and then restart. _LT_EOF ;; *) # For security reasons, it is highly recommended that you always # use absolute paths for naming shared libraries, and exclude the # DT_RUNPATH tag from executables and libraries. But doing so # requires that you compile everything twice, which is a pain. if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else ld_shlibs=no fi ;; esac ;; sunos4*) archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' wlarc= hardcode_direct=yes hardcode_shlibpath_var=no ;; *) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' else ld_shlibs=no fi ;; esac if test no = "$ld_shlibs"; then runpath_var= hardcode_libdir_flag_spec= export_dynamic_flag_spec= whole_archive_flag_spec= fi else # PORTME fill in a description of your system's linker (not GNU ld) case $host_os in aix3*) allow_undefined_flag=unsupported always_export_symbols=yes archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' # Note: this linker hardcodes the directories in LIBPATH if there # are no directories specified by -L. hardcode_minus_L=yes if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then # Neither direct hardcoding nor static linking is supported with a # broken collect2. hardcode_direct=unsupported fi ;; aix[4-9]*) if test ia64 = "$host_cpu"; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no exp_sym_flag='-Bexport' no_entry_flag= else # If we're using GNU nm, then we don't want the "-C" option. # -C means demangle to GNU nm, but means don't demangle to AIX nm. # Without the "-l" option, or with the "-B" option, AIX nm treats # weak defined symbols like other global defined symbols, whereas # GNU nm marks them as "W". # While the 'weak' keyword is ignored in the Export File, we need # it in the Import File for the 'aix-soname' feature, so we have # to replace the "-B" option with "-P" for AIX nm. if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then export_symbols_cmds='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' else export_symbols_cmds='`func_echo_all $NM | $SED -e '\''s/B\([^B]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "L") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && (substr(\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' fi aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # have runtime linking enabled, and use it for executables. # For shared libraries, we enable/disable runtime linking # depending on the kind of the shared library created - # when "with_aix_soname,aix_use_runtimelinking" is: # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables # "aix,yes" lib.so shared, rtl:yes, for executables # lib.a static archive # "both,no" lib.so.V(shr.o) shared, rtl:yes # lib.a(lib.so.V) shared, rtl:no, for executables # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a(lib.so.V) shared, rtl:no # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a static archive case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) for ld_flag in $LDFLAGS; do if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then aix_use_runtimelinking=yes break fi done if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then # With aix-soname=svr4, we create the lib.so.V shared archives only, # so we don't have lib.a shared libs to link our executables. # We have to force runtime linking in this case. aix_use_runtimelinking=yes LDFLAGS="$LDFLAGS -Wl,-brtl" fi ;; esac exp_sym_flag='-bexport' no_entry_flag='-bnoentry' fi # When large executables or shared objects are built, AIX ld can # have problems creating the table of contents. If linking a library # or program results in "error TOC overflow" add -mminimal-toc to # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. archive_cmds='' hardcode_direct=yes hardcode_direct_absolute=yes hardcode_libdir_separator=':' link_all_deplibs=yes file_list_spec='$wl-f,' case $with_aix_soname,$aix_use_runtimelinking in aix,*) ;; # traditional, no import file svr4,* | *,yes) # use import file # The Import File defines what to hardcode. hardcode_direct=no hardcode_direct_absolute=no ;; esac if test yes = "$GCC"; then case $host_os in aix4.[012]|aix4.[012].*) # We only want to do this on AIX 4.2 and lower, the check # below for broken collect2 doesn't work under 4.3+ collect2name=`$CC -print-prog-name=collect2` if test -f "$collect2name" && strings "$collect2name" | $GREP resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 hardcode_direct=unsupported # It fails to find uninstalled libraries when the uninstalled # path is not listed in the libpath. Setting hardcode_minus_L # to unsupported forces relinking hardcode_minus_L=yes hardcode_libdir_flag_spec='-L$libdir' hardcode_libdir_separator= fi ;; esac shared_flag='-shared' if test yes = "$aix_use_runtimelinking"; then shared_flag="$shared_flag "'$wl-G' fi # Need to ensure runtime linking is disabled for the traditional # shared library, or the linker may eventually find shared libraries # /with/ Import File - we do not want to mix them. shared_flag_aix='-shared' shared_flag_svr4='-shared $wl-G' else # not using gcc if test ia64 = "$host_cpu"; then # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release # chokes on -Wl,-G. The following line is correct: shared_flag='-G' else if test yes = "$aix_use_runtimelinking"; then shared_flag='$wl-G' else shared_flag='$wl-bM:SRE' fi shared_flag_aix='$wl-bM:SRE' shared_flag_svr4='$wl-G' fi fi export_dynamic_flag_spec='$wl-bexpall' # It seems that -bexpall does not export symbols beginning with # underscore (_), so it is better to generate a list of symbols to export. always_export_symbols=yes if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then # Warning - without using the other runtime loading flags (-brtl), # -berok will link without error, but may produce a broken library. allow_undefined_flag='-berok' # Determine the default libpath from the value encoded in an # empty executable. if test set = "${lt_cv_aix_libpath+set}"; then aix_libpath=$lt_cv_aix_libpath else if test ${lt_cv_aix_libpath_+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : lt_aix_libpath_sed=' /Import File Strings/,/^$/ { /^0/ { s/^0 *\([^ ]*\) *$/\1/ p } }' lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` # Check for a 64-bit object if we didn't find anything. if test -z "$lt_cv_aix_libpath_"; then lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` fi fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext if test -z "$lt_cv_aix_libpath_"; then lt_cv_aix_libpath_=/usr/lib:/lib fi ;; esac fi aix_libpath=$lt_cv_aix_libpath_ fi hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" archive_expsym_cmds='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag else if test ia64 = "$host_cpu"; then hardcode_libdir_flag_spec='$wl-R $libdir:/usr/lib:/lib' allow_undefined_flag="-z nodefs" archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" else # Determine the default libpath from the value encoded in an # empty executable. if test set = "${lt_cv_aix_libpath+set}"; then aix_libpath=$lt_cv_aix_libpath else if test ${lt_cv_aix_libpath_+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : lt_aix_libpath_sed=' /Import File Strings/,/^$/ { /^0/ { s/^0 *\([^ ]*\) *$/\1/ p } }' lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` # Check for a 64-bit object if we didn't find anything. if test -z "$lt_cv_aix_libpath_"; then lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` fi fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext if test -z "$lt_cv_aix_libpath_"; then lt_cv_aix_libpath_=/usr/lib:/lib fi ;; esac fi aix_libpath=$lt_cv_aix_libpath_ fi hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" # Warning - without using the other run time loading flags, # -berok will link without error, but may produce a broken library. no_undefined_flag=' $wl-bernotok' allow_undefined_flag=' $wl-berok' if test yes = "$with_gnu_ld"; then # We only use this code for GNU lds that support --whole-archive. whole_archive_flag_spec='$wl--whole-archive$convenience $wl--no-whole-archive' else # Exported symbols can be pulled into shared objects from archives whole_archive_flag_spec='$convenience' fi archive_cmds_need_lc=yes archive_expsym_cmds='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' # -brtl affects multiple linker settings, -berok does not and is overridden later compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([, ]\\)%-berok\\1%g"`' if test svr4 != "$with_aix_soname"; then # This is similar to how AIX traditionally builds its shared libraries. archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' fi if test aix != "$with_aix_soname"; then archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' else # used by -dlpreopen to get the symbols archive_expsym_cmds="$archive_expsym_cmds"'~$MV $output_objdir/$realname.d/$soname $output_objdir' fi archive_expsym_cmds="$archive_expsym_cmds"'~$RM -r $output_objdir/$realname.d' fi fi ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds='' ;; m68k) archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; esac ;; bsdi[45]*) export_dynamic_flag_spec=-rdynamic ;; cygwin* | mingw* | windows* | pw32* | cegcc*) # When not using gcc, we currently assume that we are using # Microsoft Visual C++ or Intel C++ Compiler. # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. case $cc_basename in cl* | icl*) # Native MSVC or ICC hardcode_libdir_flag_spec=' ' allow_undefined_flag=unsupported always_export_symbols=yes file_list_spec='@' # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. archive_cmds='$CC -Fe$output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then cp "$export_symbols" "$output_objdir/$soname.def"; echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; else $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; fi~ $CC -Fe$tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ linknames=' # The linker will not automatically build a static lib if we build a DLL. # _LT_TAGVAR(old_archive_from_new_cmds, )='true' enable_shared_with_static_runtimes=yes exclude_expsyms='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1,DATA/'\'' | $SED -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols' # Don't use ranlib old_postinstall_cmds='chmod 644 $oldlib' postlink_cmds='lt_outputfile="@OUTPUT@"~ lt_tool_outputfile="@TOOL_OUTPUT@"~ case $lt_outputfile in *.exe|*.EXE) ;; *) lt_outputfile=$lt_outputfile.exe lt_tool_outputfile=$lt_tool_outputfile.exe ;; esac~ if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; $RM "$lt_outputfile.manifest"; fi' ;; *) # Assume MSVC and ICC wrapper hardcode_libdir_flag_spec=' ' allow_undefined_flag=unsupported # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. archive_cmds='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' # The linker will automatically build a .lib file if we build a DLL. old_archive_from_new_cmds='true' # FIXME: Should let the user specify the lib program. old_archive_cmds='lib -OUT:$oldlib$oldobjs$old_deplibs' enable_shared_with_static_runtimes=yes ;; esac ;; darwin* | rhapsody*) archive_cmds_need_lc=no hardcode_direct=no hardcode_automatic=yes hardcode_shlibpath_var=unsupported if test yes = "$lt_cv_ld_force_load"; then whole_archive_flag_spec='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' else whole_archive_flag_spec='' fi link_all_deplibs=yes allow_undefined_flag=$_lt_dar_allow_undefined case $cc_basename in ifort*|nagfor*) _lt_dar_can_shared=yes ;; *) _lt_dar_can_shared=$GCC ;; esac if test yes = "$_lt_dar_can_shared"; then output_verbose_link_cmd=func_echo_all archive_cmds="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" module_cmds="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" archive_expsym_cmds="$SED 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" module_expsym_cmds="$SED -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" else ld_shlibs=no fi ;; dgux*) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_libdir_flag_spec='-L$libdir' hardcode_shlibpath_var=no ;; # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor # support. Future versions do this automatically, but an explicit c++rt0.o # does not break anything, and helps significantly (at the cost of a little # extra space). freebsd2.2*) archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes hardcode_shlibpath_var=no ;; # Unfortunately, older versions of FreeBSD 2 do not have this feature. freebsd2.*) archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' hardcode_direct=yes hardcode_minus_L=yes hardcode_shlibpath_var=no ;; # FreeBSD 3 and greater uses gcc -shared to do shared libraries. freebsd* | dragonfly* | midnightbsd*) archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes hardcode_shlibpath_var=no ;; hpux9*) if test yes = "$GCC"; then archive_cmds='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' else archive_cmds='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' fi hardcode_libdir_flag_spec='$wl+b $wl$libdir' hardcode_libdir_separator=: hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes export_dynamic_flag_spec='$wl-E' ;; hpux10*) if test yes,no = "$GCC,$with_gnu_ld"; then archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' fi if test no = "$with_gnu_ld"; then hardcode_libdir_flag_spec='$wl+b $wl$libdir' hardcode_libdir_separator=: hardcode_direct=yes hardcode_direct_absolute=yes export_dynamic_flag_spec='$wl-E' # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes fi ;; hpux11*) if test yes,no = "$GCC,$with_gnu_ld"; then case $host_cpu in hppa*64*) archive_cmds='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' ;; ia64*) archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' ;; *) archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' ;; esac else case $host_cpu in hppa*64*) archive_cmds='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' ;; ia64*) archive_cmds='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' ;; *) # Older versions of the 11.00 compiler do not understand -b yet # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $CC understands -b" >&5 printf %s "checking if $CC understands -b... " >&6; } if test ${lt_cv_prog_compiler__b+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler__b=no save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -b" echo "$lt_simple_link_test_code" > conftest.$ac_ext if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then # The linker can only warn and ignore the option if not recognized # So say no if there are warnings if test -s conftest.err; then # Append any errors to the config.log. cat conftest.err 1>&5 $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler__b=yes fi else lt_cv_prog_compiler__b=yes fi fi $RM -r conftest* LDFLAGS=$save_LDFLAGS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler__b" >&5 printf "%s\n" "$lt_cv_prog_compiler__b" >&6; } if test yes = "$lt_cv_prog_compiler__b"; then archive_cmds='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' fi ;; esac fi if test no = "$with_gnu_ld"; then hardcode_libdir_flag_spec='$wl+b $wl$libdir' hardcode_libdir_separator=: case $host_cpu in hppa*64*|ia64*) hardcode_direct=no hardcode_shlibpath_var=no ;; *) hardcode_direct=yes hardcode_direct_absolute=yes export_dynamic_flag_spec='$wl-E' # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes ;; esac fi ;; irix5* | irix6* | nonstopux*) if test yes = "$GCC"; then archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' # Try to use the -exported_symbol ld option, if it does not # work, assume that -exports_file does not work either and # implicitly export all symbols. # This should be the same for all languages, so no per-tag cache variable. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the $host_os linker accepts -exported_symbol" >&5 printf %s "checking whether the $host_os linker accepts -exported_symbol... " >&6; } if test ${lt_cv_irix_exported_symbol+y} then : printf %s "(cached) " >&6 else case e in #( e) save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int foo (void) { return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : lt_cv_irix_exported_symbol=yes else case e in #( e) lt_cv_irix_exported_symbol=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$save_LDFLAGS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_irix_exported_symbol" >&5 printf "%s\n" "$lt_cv_irix_exported_symbol" >&6; } if test yes = "$lt_cv_irix_exported_symbol"; then archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' fi link_all_deplibs=no else archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' fi archive_cmds_need_lc='no' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' hardcode_libdir_separator=: inherit_rpath=yes link_all_deplibs=yes ;; linux*) case $cc_basename in tcc*) # Fabrice Bellard et al's Tiny C Compiler ld_shlibs=yes archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' ;; esac ;; *-mlibc) ;; netbsd* | netbsdelf*-gnu) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out else archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF fi hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes hardcode_shlibpath_var=no ;; newsos6) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_direct=yes hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' hardcode_libdir_separator=: hardcode_shlibpath_var=no ;; *nto* | *qnx*) ;; openbsd*) if test -f /usr/libexec/ld.so; then hardcode_direct=yes hardcode_shlibpath_var=no hardcode_direct_absolute=yes if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' hardcode_libdir_flag_spec='$wl-rpath,$libdir' export_dynamic_flag_spec='$wl-E' else archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' hardcode_libdir_flag_spec='$wl-rpath,$libdir' fi else ld_shlibs=no fi ;; os2*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes allow_undefined_flag=unsupported shrext_cmds=.dll archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' old_archive_from_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' enable_shared_with_static_runtimes=yes file_list_spec='@' ;; osf3*) if test yes = "$GCC"; then allow_undefined_flag=' $wl-expect_unresolved $wl\*' archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' else allow_undefined_flag=' -expect_unresolved \*' archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' fi archive_cmds_need_lc='no' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' hardcode_libdir_separator=: ;; osf4* | osf5*) # as osf3* with the addition of -msym flag if test yes = "$GCC"; then allow_undefined_flag=' $wl-expect_unresolved $wl\*' archive_cmds='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' else allow_undefined_flag=' -expect_unresolved \*' archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' archive_expsym_cmds='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' # Both c and cxx compiler support -rpath directly hardcode_libdir_flag_spec='-rpath $libdir' fi archive_cmds_need_lc='no' hardcode_libdir_separator=: ;; serenity*) ;; solaris*) no_undefined_flag=' -z defs' if test yes = "$GCC"; then wlarc='$wl' archive_cmds='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' else case `$CC -V 2>&1` in *"Compilers 5.0"*) wlarc='' archive_cmds='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' ;; *) wlarc='$wl' archive_cmds='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' ;; esac fi hardcode_libdir_flag_spec='-R$libdir' hardcode_shlibpath_var=no case $host_os in solaris2.[0-5] | solaris2.[0-5].*) ;; *) # The compiler driver will combine and reorder linker options, # but understands '-z linker_flag'. GCC discards it without '$wl', # but is careful enough not to reorder. # Supported since Solaris 2.6 (maybe 2.5.1?) if test yes = "$GCC"; then whole_archive_flag_spec='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' else whole_archive_flag_spec='-z allextract$convenience -z defaultextract' fi ;; esac link_all_deplibs=yes ;; sunos4*) if test sequent = "$host_vendor"; then # Use $CC to link under sequent, because it throws in some extra .o # files that make .init and .fini sections work. archive_cmds='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' fi hardcode_libdir_flag_spec='-L$libdir' hardcode_direct=yes hardcode_minus_L=yes hardcode_shlibpath_var=no ;; sysv4) case $host_vendor in sni) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_direct=yes # is this really true??? ;; siemens) ## LD is ld it makes a PLAMLIB ## CC just makes a GrossModule. archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags' reload_cmds='$CC -r -o $output$reload_objs' hardcode_direct=no ;; motorola) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_direct=no #Motorola manual says yes, but my tests say they lie ;; esac runpath_var='LD_RUN_PATH' hardcode_shlibpath_var=no ;; sysv4.3*) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_shlibpath_var=no export_dynamic_flag_spec='-Bexport' ;; sysv4*MP*) if test -d /usr/nec; then archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_shlibpath_var=no runpath_var=LD_RUN_PATH hardcode_runpath_var=yes ld_shlibs=yes fi ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) no_undefined_flag='$wl-z,text' archive_cmds_need_lc=no hardcode_shlibpath_var=no runpath_var='LD_RUN_PATH' if test yes = "$GCC"; then archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' fi ;; sysv5* | sco3.2v5* | sco5v6*) # Note: We CANNOT use -z defs as we might desire, because we do not # link with -lc, and that would cause any symbols used from libc to # always be unresolved, which means just about no library would # ever link correctly. If we're not using GNU ld we use -z text # though, which does catch some bad symbols but isn't as heavy-handed # as -z defs. no_undefined_flag='$wl-z,text' allow_undefined_flag='$wl-z,nodefs' archive_cmds_need_lc=no hardcode_shlibpath_var=no hardcode_libdir_flag_spec='$wl-R,$libdir' hardcode_libdir_separator=':' link_all_deplibs=yes export_dynamic_flag_spec='$wl-Bexport' runpath_var='LD_RUN_PATH' if test yes = "$GCC"; then archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' else archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' fi ;; uts4*) archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' hardcode_libdir_flag_spec='-L$libdir' hardcode_shlibpath_var=no ;; *) ld_shlibs=no ;; esac if test sni = "$host_vendor"; then case $host in sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) export_dynamic_flag_spec='$wl-Blargedynsym' ;; esac fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs" >&5 printf "%s\n" "$ld_shlibs" >&6; } test no = "$ld_shlibs" && can_build_shared=no with_gnu_ld=$with_gnu_ld # # Do we need to explicitly link libc? # case "x$archive_cmds_need_lc" in x|xyes) # Assume -lc should be added archive_cmds_need_lc=yes if test yes,yes = "$GCC,$enable_shared"; then case $archive_cmds in *'~'*) # FIXME: we may have to deal with multi-command sequences. ;; '$CC '*) # Test whether the compiler implicitly links with -lc since on some # systems, -lgcc has to come before -lc. If gcc already passes -lc # to ld, don't add -lc before -lgcc. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5 printf %s "checking whether -lc should be explicitly linked in... " >&6; } if test ${lt_cv_archive_cmds_need_lc+y} then : printf %s "(cached) " >&6 else case e in #( e) $RM conftest* echo "$lt_simple_compile_test_code" > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } 2>conftest.err; then soname=conftest lib=conftest libobjs=conftest.$ac_objext deplibs= wl=$lt_prog_compiler_wl pic_flag=$lt_prog_compiler_pic compiler_flags=-v linker_flags=-v verstring= output_objdir=. libname=conftest lt_save_allow_undefined_flag=$allow_undefined_flag allow_undefined_flag= if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5 (eval $archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then lt_cv_archive_cmds_need_lc=no else lt_cv_archive_cmds_need_lc=yes fi allow_undefined_flag=$lt_save_allow_undefined_flag else cat conftest.err 1>&5 fi $RM conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc" >&5 printf "%s\n" "$lt_cv_archive_cmds_need_lc" >&6; } archive_cmds_need_lc=$lt_cv_archive_cmds_need_lc ;; esac fi ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5 printf %s "checking dynamic linker characteristics... " >&6; } if test yes = "$GCC"; then case $host_os in darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; *) lt_awk_arg='/^libraries:/' ;; esac case $host_os in mingw* | windows* | cegcc*) lt_sed_strip_eq='s|=\([A-Za-z]:\)|\1|g' ;; *) lt_sed_strip_eq='s|=/|/|g' ;; esac lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` case $lt_search_path_spec in *\;*) # if the path contains ";" then we assume it to be the separator # otherwise default to the standard path separator (i.e. ":") - it is # assumed that no part of a normal pathname contains ";" but that should # okay in the real world where ";" in dirpaths is itself problematic. lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` ;; *) lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` ;; esac # Ok, now we have the path, separated by spaces, we can step through it # and add multilib dir if necessary... lt_tmp_lt_search_path_spec= lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` # ...but if some path component already ends with the multilib dir we assume # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). case "$lt_multi_os_dir; $lt_search_path_spec " in "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) lt_multi_os_dir= ;; esac for lt_sys_path in $lt_search_path_spec; do if test -d "$lt_sys_path$lt_multi_os_dir"; then lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" elif test -n "$lt_multi_os_dir"; then test -d "$lt_sys_path" && \ lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" fi done lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' BEGIN {RS = " "; FS = "/|\n";} { lt_foo = ""; lt_count = 0; for (lt_i = NF; lt_i > 0; lt_i--) { if ($lt_i != "" && $lt_i != ".") { if ($lt_i == "..") { lt_count++; } else { if (lt_count == 0) { lt_foo = "/" $lt_i lt_foo; } else { lt_count--; } } } } if (lt_foo != "") { lt_freq[lt_foo]++; } if (lt_freq[lt_foo] == 1) { print lt_foo; } }'` # AWK program above erroneously prepends '/' to C:/dos/paths # for these hosts. case $host_os in mingw* | windows* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ $SED 's|/\([A-Za-z]:\)|\1|g'` ;; esac sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` else sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" fi library_names_spec= libname_spec='lib$name' soname_spec= shrext_cmds=.so postinstall_cmds= postuninstall_cmds= finish_cmds= finish_eval= shlibpath_var= shlibpath_overrides_runpath=unknown version_type=none dynamic_linker="$host_os ld.so" sys_lib_dlsearch_path_spec="/lib /usr/lib" need_lib_prefix=unknown hardcode_into_libs=no # when you set need_version to no, make sure it does not cause -set_version # flags to be left without arguments need_version=unknown case $host_os in aix3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname.a' shlibpath_var=LIBPATH # AIX 3 has no versioning support, so we append a major version to the name. soname_spec='$libname$release$shared_ext$major' ;; aix[4-9]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no hardcode_into_libs=yes if test ia64 = "$host_cpu"; then # AIX 5 supports IA64 library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH else # With GCC up to 2.95.x, collect2 would create an import file # for dependence libraries. The import file would start with # the line '#! .'. This would cause the generated library to # depend on '.', always an invalid library. This was fixed in # development snapshots of GCC prior to 3.0. case $host_os in aix4 | aix4.[01] | aix4.[01].*) if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' echo ' yes ' echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then : else can_build_shared=no fi ;; esac # Using Import Files as archive members, it is possible to support # filename-based versioning of shared library archives on AIX. While # this would work for both with and without runtime linking, it will # prevent static linking of such archives. So we do filename-based # shared library versioning with .so extension only, which is used # when both runtime linking and shared linking is enabled. # Unfortunately, runtime linking may impact performance, so we do # not want this to be the default eventually. Also, we use the # versioned .so libs for executables only if there is the -brtl # linker flag in LDFLAGS as well, or --enable-aix-soname=svr4 only. # To allow for filename-based versioning support, we need to create # libNAME.so.V as an archive file, containing: # *) an Import File, referring to the versioned filename of the # archive as well as the shared archive member, telling the # bitwidth (32 or 64) of that shared object, and providing the # list of exported symbols of that shared object, eventually # decorated with the 'weak' keyword # *) the shared object with the F_LOADONLY flag set, to really avoid # it being seen by the linker. # At run time we better use the real file rather than another symlink, # but for link time we create the symlink libNAME.so -> libNAME.so.V case $with_aix_soname,$aix_use_runtimelinking in # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct # soname into executable. Probably we can add versioning support to # collect2, so additional links can be useful in future. aix,yes) # traditional libtool dynamic_linker='AIX unversionable lib.so' # If using run time linking (on AIX 4.2 or later) use lib.so # instead of lib.a to let people know that these are not # typical AIX shared libraries. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; aix,no) # traditional AIX only dynamic_linker='AIX lib.a(lib.so.V)' # We preserve .a as extension for shared libraries through AIX4.2 # and later when we are not doing run time linking. library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' ;; svr4,*) # full svr4 only dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o)" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,yes) # both, prefer svr4 dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o), lib.a(lib.so.V)" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # unpreferred sharedlib libNAME.a needs extra handling postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,no) # both, prefer aix dynamic_linker="AIX lib.a(lib.so.V), lib.so.V($shared_archive_member_spec.o)" library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' ;; esac shlibpath_var=LIBPATH fi ;; amigaos*) case $host_cpu in powerpc) # Since July 2007 AmigaOS4 officially supports .so libraries. # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; m68k) library_names_spec='$libname.ixlibrary $libname.a' # Create ${libname}_ixlibrary.a entries in /sys/libs. finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' ;; esac ;; beos*) library_names_spec='$libname$shared_ext' dynamic_linker="$host_os ld.so" shlibpath_var=LIBRARY_PATH ;; bsdi[45]*) version_type=linux # correct to gnu/linux during the next big refactor need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" # the default ld.so.conf also contains /usr/contrib/lib and # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow # libtool to hard-code these into programs ;; cygwin* | mingw* | windows* | pw32* | cegcc*) version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no case $GCC,$cc_basename in yes,*) # gcc library_names_spec='$libname.dll.a' # DLL is installed to $(libdir)/../bin by postinstall_cmds # If user builds GCC with multilib enabled, # it should just install on $(libdir) # not on $(libdir)/../bin or 32 bits dlls would override 64 bit ones. if test xyes = x"$multilib"; then postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ $install_prog $dir/$dlname $destdir/$dlname~ chmod a+x $destdir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib $destdir/$dlname'\'' || exit \$?; fi' else postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' fi postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes case $host_os in cygwin*) # Cygwin DLLs use 'cyg' prefix rather than 'lib' soname_spec='`echo $libname | $SED -e 's/^lib/cyg/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api" ;; mingw* | windows* | cegcc*) # MinGW DLLs use traditional 'lib' prefix soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' ;; pw32*) # pw32 DLLs use 'pw' prefix rather than 'lib' library_names_spec='`echo $libname | $SED -e 's/^lib/pw/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' ;; esac dynamic_linker='Win32 ld.exe' ;; *,cl* | *,icl*) # Native MSVC or ICC libname_spec='$name' soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' library_names_spec='$libname.dll.lib' case $build_os in mingw* | windows*) sys_lib_search_path_spec= lt_save_ifs=$IFS IFS=';' for lt_path in $LIB do IFS=$lt_save_ifs # Let DOS variable expansion print the short 8.3 style file name. lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" done IFS=$lt_save_ifs # Convert to MSYS style. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'` ;; cygwin*) # Convert to unix form, then to dos form, then back to unix form # but this time dos style (no spaces!) so that the unix form looks # like /cygdrive/c/PROGRA~1:/cygdr... sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` ;; *) sys_lib_search_path_spec=$LIB if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then # It is most probably a Windows format PATH. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` else sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` fi # FIXME: find the short name or the path components, as spaces are # common. (e.g. "Program Files" -> "PROGRA~1") ;; esac # DLL is installed to $(libdir)/../bin by postinstall_cmds postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes dynamic_linker='Win32 link.exe' ;; *) # Assume MSVC and ICC wrapper library_names_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext $libname.lib' dynamic_linker='Win32 ld.exe' ;; esac # FIXME: first we should search . and the directory the executable is in shlibpath_var=PATH ;; darwin* | rhapsody*) dynamic_linker="$host_os dyld" version_type=darwin need_lib_prefix=no need_version=no library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' soname_spec='$libname$release$major$shared_ext' shlibpath_overrides_runpath=yes shlibpath_var=DYLD_LIBRARY_PATH shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib" sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' ;; dgux*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; freebsd* | dragonfly* | midnightbsd*) # DragonFly does not have aout. When/if they implement a new # versioning mechanism, adjust this. if test -x /usr/bin/objformat; then objformat=`/usr/bin/objformat` else case $host_os in freebsd[23].*) objformat=aout ;; *) objformat=elf ;; esac fi version_type=freebsd-$objformat case $version_type in freebsd-elf*) library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' need_version=no need_lib_prefix=no ;; freebsd-*) library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' need_version=yes ;; esac case $host_cpu in powerpc64) # On FreeBSD bi-arch platforms, a different variable is used for 32-bit # binaries. See . cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int test_pointer_size[sizeof (void *) - 5]; _ACEOF if ac_fn_c_try_compile "$LINENO" then : shlibpath_var=LD_LIBRARY_PATH else case e in #( e) shlibpath_var=LD_32_LIBRARY_PATH ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; *) shlibpath_var=LD_LIBRARY_PATH ;; esac case $host_os in freebsd2.*) shlibpath_overrides_runpath=yes ;; freebsd3.[01]* | freebsdelf3.[01]*) shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; *) # from 4.6 on, and DragonFly shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; esac ;; haiku*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no dynamic_linker="$host_os runtime_loader" library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LIBRARY_PATH shlibpath_overrides_runpath=no sys_lib_search_path_spec='/boot/system/non-packaged/develop/lib /boot/system/develop/lib' sys_lib_dlsearch_path_spec='/boot/home/config/non-packaged/lib /boot/home/config/lib /boot/system/non-packaged/lib /boot/system/lib' hardcode_into_libs=no ;; hpux9* | hpux10* | hpux11*) # Give a soname corresponding to the major version so that dld.sl refuses to # link against other versions. version_type=sunos need_lib_prefix=no need_version=no case $host_cpu in ia64*) shrext_cmds='.so' hardcode_into_libs=yes dynamic_linker="$host_os dld.so" shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' if test 32 = "$HPUX_IA64_MODE"; then sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" sys_lib_dlsearch_path_spec=/usr/lib/hpux32 else sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" sys_lib_dlsearch_path_spec=/usr/lib/hpux64 fi ;; hppa*64*) shrext_cmds='.sl' hardcode_into_libs=yes dynamic_linker="$host_os dld.sl" shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; *) shrext_cmds='.sl' dynamic_linker="$host_os dld.sl" shlibpath_var=SHLIB_PATH shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' ;; esac # HP-UX runs *really* slowly unless shared libraries are mode 555, ... postinstall_cmds='chmod 555 $lib' # or fails outright, so override atomically: install_override_mode=555 ;; interix[3-9]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; irix5* | irix6* | nonstopux*) case $host_os in nonstopux*) version_type=nonstopux ;; *) if test yes = "$lt_cv_prog_gnu_ld"; then version_type=linux # correct to gnu/linux during the next big refactor else version_type=irix fi ;; esac need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' case $host_os in irix5* | nonstopux*) libsuff= shlibsuff= ;; *) case $LD in # libtool.m4 will add one of these switches to LD *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= libmagic=32-bit;; *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 libmagic=N32;; *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 libmagic=64-bit;; *) libsuff= shlibsuff= libmagic=never-match;; esac ;; esac shlibpath_var=LD_LIBRARY${shlibsuff}_PATH shlibpath_overrides_runpath=no sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" hardcode_into_libs=yes ;; # No shared lib support for Linux oldld, aout, or coff. linux*oldld* | linux*aout* | linux*coff*) dynamic_linker=no ;; linux*android*) version_type=none # Android doesn't support versioned libraries. need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext $libname$shared_ext' soname_spec='$libname$release$shared_ext' finish_cmds= shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes dynamic_linker='Android linker' # -rpath works at least for libraries that are not overridden by # libraries installed in system locations. hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' ;; # This must be glibc/ELF. linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no # Some binutils ld are patched to set DT_RUNPATH if test ${lt_cv_shlibpath_overrides_runpath+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_shlibpath_overrides_runpath=no save_LDFLAGS=$LDFLAGS save_libdir=$libdir eval "libdir=/foo; wl=\"$lt_prog_compiler_wl\"; \ LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec\"" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null then : lt_cv_shlibpath_overrides_runpath=yes fi fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$save_LDFLAGS libdir=$save_libdir ;; esac fi shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes # Ideally, we could use ldconfig to report *all* directories which are # searched for libraries, however this is still not possible. Aside from not # being certain /sbin/ldconfig is available, command # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, # even though it is searched at run-time. Try to do the best guess by # appending ld.so.conf contents (and includes) to the search path. if test -f /etc/ld.so.conf; then lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" fi # We used to test for /lib/ld.so.1 and disable shared libraries on # powerpc, because MkLinux only supported shared libraries with the # GNU dynamic linker. Since this was broken with cross compilers, # most powerpc-linux boxes support dynamic linking these days and # people can always --disable-shared, the test was removed, and we # assume the GNU/Linux dynamic linker is in use. dynamic_linker='GNU/Linux ld.so' ;; netbsdelf*-gnu) version_type=linux need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='NetBSD ld.elf_so' ;; netbsdelf*-gnu) version_type=linux need_lib_prefix=no need_version=no library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' soname_spec='${libname}${release}${shared_ext}$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='NetBSD ld.elf_so' ;; netbsd*) version_type=sunos need_lib_prefix=no need_version=no if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' dynamic_linker='NetBSD (a.out) ld.so' else library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='NetBSD ld.elf_so' fi shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; *-mlibc) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='mlibc ld.so' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; newsos6) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; *nto* | *qnx*) version_type=qnx need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='ldqnx.so' ;; openbsd*) version_type=sunos sys_lib_dlsearch_path_spec=/usr/lib need_lib_prefix=no if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then need_version=no else need_version=yes fi library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; os2*) libname_spec='$name' version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no # OS/2 can only load a DLL with a base name of 8 characters or less. soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; v=$($ECHO $release$versuffix | tr -d .-); n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); $ECHO $n$v`$shared_ext' library_names_spec='${libname}_dll.$libext' dynamic_linker='OS/2 ld.exe' shlibpath_var=BEGINLIBPATH sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' ;; osf3* | osf4* | osf5*) version_type=osf need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; rdos*) dynamic_linker=no ;; serenity*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no dynamic_linker='SerenityOS LibELF' ;; solaris*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes # ldd complains unless libraries are executable postinstall_cmds='chmod +x $lib' ;; sunos4*) version_type=sunos library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes if test yes = "$with_gnu_ld"; then need_lib_prefix=no fi need_version=yes ;; sysv4 | sysv4.3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH case $host_vendor in sni) shlibpath_overrides_runpath=no need_lib_prefix=no runpath_var=LD_RUN_PATH ;; siemens) need_lib_prefix=no ;; motorola) need_lib_prefix=no need_version=no shlibpath_overrides_runpath=no sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' ;; esac ;; sysv4*MP*) if test -d /usr/nec; then version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' soname_spec='$libname$shared_ext.$major' shlibpath_var=LD_LIBRARY_PATH fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) version_type=sco need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes if test yes = "$with_gnu_ld"; then sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' else sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' case $host_os in sco3.2v5*) sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" ;; esac fi sys_lib_dlsearch_path_spec='/usr/lib' ;; tpf*) # TPF is a cross-target only. Preferred cross-host = GNU/Linux. version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; uts4*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; emscripten*) version_type=none need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext' soname_spec='$libname$release$shared_ext' finish_cmds= dynamic_linker="Emscripten linker" lt_prog_compiler_wl= lt_prog_compiler_pic= lt_prog_compiler_static= if test yes = "$GCC"; then lt_prog_compiler_wl='-Wl,' lt_prog_compiler_static='-static' case $host_os in aix*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static='-Bstatic' fi lt_prog_compiler_pic='-fPIC' ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support lt_prog_compiler_pic='-fPIC' ;; m68k) # FIXME: we need at least 68020 code to build shared libraries, but # adding the '-m68020' flag to GCC prevents building anything better, # like '-m68040'. lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4' ;; esac ;; beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) # PIC is the default for these OSes. ;; mingw* | windows* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). # Although the cygwin gcc ignores -fPIC, still need this for old-style # (--disable-auto-import) libraries lt_prog_compiler_pic='-DDLL_EXPORT' case $host_os in os2*) lt_prog_compiler_static='$wl-static' ;; esac ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files lt_prog_compiler_pic='-fno-common' ;; haiku*) # PIC is the default for Haiku. # The "-static" flag exists, but is broken. lt_prog_compiler_static= ;; hpux*) # PIC is the default for 64-bit PA HP-UX, but not for 32-bit # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag # sets the default TLS model and affects inlining. case $host_cpu in hppa*64*) # +Z the default ;; *) lt_prog_compiler_pic='-fPIC' ;; esac ;; interix[3-9]*) # Interix 3.x gcc -fpic/-fPIC options generate broken code. # Instead, we relocate shared libraries at runtime. ;; msdosdjgpp*) # Just because we use GCC doesn't mean we suddenly get shared libraries # on systems that don't support them. lt_prog_compiler_can_build_shared=no enable_shared=no ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic='-fPIC -shared' ;; sysv4*MP*) if test -d /usr/nec; then lt_prog_compiler_pic=-Kconform_pic fi ;; *) lt_prog_compiler_pic='-fPIC' ;; esac case $cc_basename in nvcc*) # Cuda Compiler Driver 2.2 lt_prog_compiler_wl='-Xlinker ' if test -n "$lt_prog_compiler_pic"; then lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic" fi ;; esac else # PORTME Check for flag to pass linker flags through the system compiler. case $host_os in aix*) lt_prog_compiler_wl='-Wl,' if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static='-Bstatic' else lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp' fi ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files lt_prog_compiler_pic='-fno-common' case $cc_basename in nagfor*) # NAG Fortran compiler lt_prog_compiler_wl='-Wl,-Wl,,' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; esac ;; mingw* | windows* | cygwin* | pw32* | os2* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). lt_prog_compiler_pic='-DDLL_EXPORT' case $host_os in os2*) lt_prog_compiler_static='$wl-static' ;; esac ;; hpux9* | hpux10* | hpux11*) lt_prog_compiler_wl='-Wl,' # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but # not for PA HP-UX. case $host_cpu in hppa*64*|ia64*) # +Z the default ;; *) lt_prog_compiler_pic='+Z' ;; esac # Is there a better lt_prog_compiler_static that works with the bundled CC? lt_prog_compiler_static='$wl-a ${wl}archive' ;; irix5* | irix6* | nonstopux*) lt_prog_compiler_wl='-Wl,' # PIC (with -KPIC) is the default. lt_prog_compiler_static='-non_shared' ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in # old Intel for x86_64, which still supported -KPIC. ecc*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-static' ;; *flang* | ftn | f18* | f95*) # Flang compiler. lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # flang / f18. f95 an alias for gfortran or flang on Debian flang* | f18* | f95*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # icc used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. icc* | ifort*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; # Lahey Fortran 8.1. lf95*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='--shared' lt_prog_compiler_static='--static' ;; nagfor*) # NAG Fortran compiler lt_prog_compiler_wl='-Wl,-Wl,,' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; tcc*) # Fabrice Bellard et al's Tiny C Compiler lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) # Portland Group compilers (*not* the Pentium gcc compiler, # which looks to be a dead project) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fpic' lt_prog_compiler_static='-Bstatic' ;; ccc*) lt_prog_compiler_wl='-Wl,' # All Alpha code is PIC. lt_prog_compiler_static='-non_shared' ;; xl* | bgxl* | bgf* | mpixl*) # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-qpic' lt_prog_compiler_static='-qstaticlink' ;; *) case `$CC -V 2>&1 | $SED 5q` in *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*) # Sun Fortran 8.3 passes all unrecognized flags to the linker lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='' ;; *Sun\ F* | *Sun*Fortran*) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='-Qoption ld ' ;; *Sun\ C*) # Sun C 5.9 lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' lt_prog_compiler_wl='-Wl,' ;; *Intel*\ [CF]*Compiler*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; *Portland\ Group*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fpic' lt_prog_compiler_static='-Bstatic' ;; esac ;; esac ;; newsos6) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; *-mlibc) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-fPIC' lt_prog_compiler_static='-static' ;; *nto* | *qnx*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic='-fPIC -shared' ;; osf3* | osf4* | osf5*) lt_prog_compiler_wl='-Wl,' # All OSF/1 code is PIC. lt_prog_compiler_static='-non_shared' ;; rdos*) lt_prog_compiler_static='-non_shared' ;; serenity*) ;; solaris*) lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' case $cc_basename in f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) lt_prog_compiler_wl='-Qoption ld ';; *) lt_prog_compiler_wl='-Wl,';; esac ;; sunos4*) lt_prog_compiler_wl='-Qoption ld ' lt_prog_compiler_pic='-PIC' lt_prog_compiler_static='-Bstatic' ;; sysv4 | sysv4.2uw2* | sysv4.3*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; sysv4*MP*) if test -d /usr/nec; then lt_prog_compiler_pic='-Kconform_pic' lt_prog_compiler_static='-Bstatic' fi ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-Bstatic' ;; unicos*) lt_prog_compiler_wl='-Wl,' lt_prog_compiler_can_build_shared=no ;; uts4*) lt_prog_compiler_pic='-pic' lt_prog_compiler_static='-Bstatic' ;; *) lt_prog_compiler_can_build_shared=no ;; esac fi case $host_os in # For platforms that do not support PIC, -DPIC is meaningless: *djgpp*) lt_prog_compiler_pic= ;; *) lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC" ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 printf %s "checking for $compiler option to produce PIC... " >&6; } if test ${lt_cv_prog_compiler_pic+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_pic=$lt_prog_compiler_pic ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5 printf "%s\n" "$lt_cv_prog_compiler_pic" >&6; } lt_prog_compiler_pic=$lt_cv_prog_compiler_pic # # Check to make sure the PIC flag actually works. # if test -n "$lt_prog_compiler_pic"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5 printf %s "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; } if test ${lt_cv_prog_compiler_pic_works+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_pic_works=no ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="$lt_prog_compiler_pic -DPIC" ## exclude from sc_useless_quotes_in_assignment # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. # The option is referenced via a variable to avoid confusing sed. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_pic_works=yes fi fi $RM conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5 printf "%s\n" "$lt_cv_prog_compiler_pic_works" >&6; } if test yes = "$lt_cv_prog_compiler_pic_works"; then case $lt_prog_compiler_pic in "" | " "*) ;; *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;; esac else lt_prog_compiler_pic= lt_prog_compiler_can_build_shared=no fi fi # # Check to make sure the static flag actually works. # wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 printf %s "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } if test ${lt_cv_prog_compiler_static_works+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_static_works=no save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS $lt_tmp_static_flag" echo "$lt_simple_link_test_code" > conftest.$ac_ext if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then # The linker can only warn and ignore the option if not recognized # So say no if there are warnings if test -s conftest.err; then # Append any errors to the config.log. cat conftest.err 1>&5 $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_static_works=yes fi else lt_cv_prog_compiler_static_works=yes fi fi $RM -r conftest* LDFLAGS=$save_LDFLAGS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5 printf "%s\n" "$lt_cv_prog_compiler_static_works" >&6; } if test yes = "$lt_cv_prog_compiler_static_works"; then : else lt_prog_compiler_static= fi ='-fPIC' archive_cmds='$CC -sSIDE_MODULE=2 -shared $libobjs $deplibs $compiler_flags -o $lib' archive_expsym_cmds='$SED "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -sSIDE_MODULE=2 -shared $libobjs $deplibs $compiler_flags -o $lib -s EXPORTED_FUNCTIONS=@$output_objdir/$soname.expsym' archive_cmds_need_lc=no no_undefined_flag= ;; *) dynamic_linker=no ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5 printf "%s\n" "$dynamic_linker" >&6; } test no = "$dynamic_linker" && can_build_shared=no variables_saved_for_relink="PATH $shlibpath_var $runpath_var" if test yes = "$GCC"; then variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" fi if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec fi if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec fi # remember unaugmented sys_lib_dlsearch_path content for libtool script decls... configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec # ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" # to be used as default LT_SYS_LIBRARY_PATH value in generated libtool configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5 printf %s "checking how to hardcode library paths into programs... " >&6; } hardcode_action= if test -n "$hardcode_libdir_flag_spec" || test -n "$runpath_var" || test yes = "$hardcode_automatic"; then # We can hardcode non-existent directories. if test no != "$hardcode_direct" && # If the only mechanism to avoid hardcoding is shlibpath_var, we # have to relink, otherwise we might link with an installed library # when we should be linking with a yet-to-be-installed one ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, )" && test no != "$hardcode_minus_L"; then # Linking always hardcodes the temporary library directory. hardcode_action=relink else # We can link without hardcoding, and we can hardcode nonexisting dirs. hardcode_action=immediate fi else # We cannot hardcode anything, or else we can only hardcode existing # directories. hardcode_action=unsupported fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $hardcode_action" >&5 printf "%s\n" "$hardcode_action" >&6; } if test relink = "$hardcode_action" || test yes = "$inherit_rpath"; then # Fast installation is not supported enable_fast_install=no elif test yes = "$shlibpath_overrides_runpath" || test no = "$enable_shared"; then # Fast installation is not necessary enable_fast_install=needless fi if test yes != "$enable_dlopen"; then enable_dlopen=unknown enable_dlopen_self=unknown enable_dlopen_self_static=unknown else lt_cv_dlopen=no lt_cv_dlopen_libs= case $host_os in beos*) lt_cv_dlopen=load_add_on lt_cv_dlopen_libs= lt_cv_dlopen_self=yes ;; mingw* | windows* | pw32* | cegcc*) lt_cv_dlopen=LoadLibrary lt_cv_dlopen_libs= ;; cygwin*) lt_cv_dlopen=dlopen lt_cv_dlopen_libs= ;; darwin*) # if libdl is installed we need to link against it { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 printf %s "checking for dlopen in -ldl... " >&6; } if test ${ac_cv_lib_dl_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dl_dlopen=yes else case e in #( e) ac_cv_lib_dl_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 printf "%s\n" "$ac_cv_lib_dl_dlopen" >&6; } if test "x$ac_cv_lib_dl_dlopen" = xyes then : lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl else case e in #( e) lt_cv_dlopen=dyld lt_cv_dlopen_libs= lt_cv_dlopen_self=yes ;; esac fi ;; tpf*) # Don't try to run any link tests for TPF. We know it's impossible # because TPF is a cross-compiler, and we know how we open DSOs. lt_cv_dlopen=dlopen lt_cv_dlopen_libs= lt_cv_dlopen_self=no ;; *) ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load" if test "x$ac_cv_func_shl_load" = xyes then : lt_cv_dlopen=shl_load else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 printf %s "checking for shl_load in -ldld... " >&6; } if test ${ac_cv_lib_dld_shl_load+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldld $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char shl_load (void); int main (void) { return shl_load (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dld_shl_load=yes else case e in #( e) ac_cv_lib_dld_shl_load=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 printf "%s\n" "$ac_cv_lib_dld_shl_load" >&6; } if test "x$ac_cv_lib_dld_shl_load" = xyes then : lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld else case e in #( e) ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen" if test "x$ac_cv_func_dlopen" = xyes then : lt_cv_dlopen=dlopen else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 printf %s "checking for dlopen in -ldl... " >&6; } if test ${ac_cv_lib_dl_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dl_dlopen=yes else case e in #( e) ac_cv_lib_dl_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 printf "%s\n" "$ac_cv_lib_dl_dlopen" >&6; } if test "x$ac_cv_lib_dl_dlopen" = xyes then : lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5 printf %s "checking for dlopen in -lsvld... " >&6; } if test ${ac_cv_lib_svld_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lsvld $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_svld_dlopen=yes else case e in #( e) ac_cv_lib_svld_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5 printf "%s\n" "$ac_cv_lib_svld_dlopen" >&6; } if test "x$ac_cv_lib_svld_dlopen" = xyes then : lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5 printf %s "checking for dld_link in -ldld... " >&6; } if test ${ac_cv_lib_dld_dld_link+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldld $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dld_link (void); int main (void) { return dld_link (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dld_dld_link=yes else case e in #( e) ac_cv_lib_dld_dld_link=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5 printf "%s\n" "$ac_cv_lib_dld_dld_link" >&6; } if test "x$ac_cv_lib_dld_dld_link" = xyes then : lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld fi ;; esac fi ;; esac fi ;; esac fi ;; esac fi ;; esac fi ;; esac if test no = "$lt_cv_dlopen"; then enable_dlopen=no else enable_dlopen=yes fi case $lt_cv_dlopen in dlopen) save_CPPFLAGS=$CPPFLAGS test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" save_LDFLAGS=$LDFLAGS wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" save_LIBS=$LIBS LIBS="$lt_cv_dlopen_libs $LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether a program can dlopen itself" >&5 printf %s "checking whether a program can dlopen itself... " >&6; } if test ${lt_cv_dlopen_self+y} then : printf %s "(cached) " >&6 else case e in #( e) if test yes = "$cross_compiling"; then : lt_cv_dlopen_self=cross else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF #line $LINENO "configure" #include "confdefs.h" #if HAVE_DLFCN_H #include #endif #include #ifdef RTLD_GLOBAL # define LT_DLGLOBAL RTLD_GLOBAL #else # ifdef DL_GLOBAL # define LT_DLGLOBAL DL_GLOBAL # else # define LT_DLGLOBAL 0 # endif #endif /* We may have to define LT_DLLAZY_OR_NOW in the command line if we find out it does not work in some platform. */ #ifndef LT_DLLAZY_OR_NOW # ifdef RTLD_LAZY # define LT_DLLAZY_OR_NOW RTLD_LAZY # else # ifdef DL_LAZY # define LT_DLLAZY_OR_NOW DL_LAZY # else # ifdef RTLD_NOW # define LT_DLLAZY_OR_NOW RTLD_NOW # else # ifdef DL_NOW # define LT_DLLAZY_OR_NOW DL_NOW # else # define LT_DLLAZY_OR_NOW 0 # endif # endif # endif # endif #endif /* When -fvisibility=hidden is used, assume the code has been annotated correspondingly for the symbols needed. */ #if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) int fnord (void) __attribute__((visibility("default"))); #endif int fnord (void) { return 42; } int main (void) { void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); int status = $lt_dlunknown; if (self) { if (dlsym (self,"fnord")) status = $lt_dlno_uscore; else { if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; else puts (dlerror ()); } /* dlclose (self); */ } else puts (dlerror ()); return status; } _LT_EOF if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 (eval $ac_link) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then (./conftest; exit; ) >&5 2>/dev/null lt_status=$? case x$lt_status in x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;; x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;; x$lt_dlunknown|x*) lt_cv_dlopen_self=no ;; esac else : # compilation failed lt_cv_dlopen_self=no fi fi rm -fr conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self" >&5 printf "%s\n" "$lt_cv_dlopen_self" >&6; } if test yes = "$lt_cv_dlopen_self"; then wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether a statically linked program can dlopen itself" >&5 printf %s "checking whether a statically linked program can dlopen itself... " >&6; } if test ${lt_cv_dlopen_self_static+y} then : printf %s "(cached) " >&6 else case e in #( e) if test yes = "$cross_compiling"; then : lt_cv_dlopen_self_static=cross else lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext <<_LT_EOF #line $LINENO "configure" #include "confdefs.h" #if HAVE_DLFCN_H #include #endif #include #ifdef RTLD_GLOBAL # define LT_DLGLOBAL RTLD_GLOBAL #else # ifdef DL_GLOBAL # define LT_DLGLOBAL DL_GLOBAL # else # define LT_DLGLOBAL 0 # endif #endif /* We may have to define LT_DLLAZY_OR_NOW in the command line if we find out it does not work in some platform. */ #ifndef LT_DLLAZY_OR_NOW # ifdef RTLD_LAZY # define LT_DLLAZY_OR_NOW RTLD_LAZY # else # ifdef DL_LAZY # define LT_DLLAZY_OR_NOW DL_LAZY # else # ifdef RTLD_NOW # define LT_DLLAZY_OR_NOW RTLD_NOW # else # ifdef DL_NOW # define LT_DLLAZY_OR_NOW DL_NOW # else # define LT_DLLAZY_OR_NOW 0 # endif # endif # endif # endif #endif /* When -fvisibility=hidden is used, assume the code has been annotated correspondingly for the symbols needed. */ #if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) int fnord (void) __attribute__((visibility("default"))); #endif int fnord (void) { return 42; } int main (void) { void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); int status = $lt_dlunknown; if (self) { if (dlsym (self,"fnord")) status = $lt_dlno_uscore; else { if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; else puts (dlerror ()); } /* dlclose (self); */ } else puts (dlerror ()); return status; } _LT_EOF if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 (eval $ac_link) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then (./conftest; exit; ) >&5 2>/dev/null lt_status=$? case x$lt_status in x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;; x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;; x$lt_dlunknown|x*) lt_cv_dlopen_self_static=no ;; esac else : # compilation failed lt_cv_dlopen_self_static=no fi fi rm -fr conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self_static" >&5 printf "%s\n" "$lt_cv_dlopen_self_static" >&6; } fi CPPFLAGS=$save_CPPFLAGS LDFLAGS=$save_LDFLAGS LIBS=$save_LIBS ;; esac case $lt_cv_dlopen_self in yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; *) enable_dlopen_self=unknown ;; esac case $lt_cv_dlopen_self_static in yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; *) enable_dlopen_self_static=unknown ;; esac fi striplib= old_striplib= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether stripping libraries is possible" >&5 printf %s "checking whether stripping libraries is possible... " >&6; } if test -z "$STRIP"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } else if $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then old_striplib="$STRIP --strip-debug" striplib="$STRIP --strip-unneeded" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else case $host_os in darwin*) # FIXME - insert some real tests, host_os isn't really good enough striplib="$STRIP -x" old_striplib="$STRIP -S" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } ;; freebsd*) if $STRIP -V 2>&1 | $GREP "elftoolchain" >/dev/null; then old_striplib="$STRIP --strip-debug" striplib="$STRIP --strip-unneeded" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi ;; *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } ;; esac fi fi # Report what library types will actually be built { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if libtool supports shared libraries" >&5 printf %s "checking if libtool supports shared libraries... " >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $can_build_shared" >&5 printf "%s\n" "$can_build_shared" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to build shared libraries" >&5 printf %s "checking whether to build shared libraries... " >&6; } test no = "$can_build_shared" && enable_shared=no # On AIX, shared libraries and static libraries use the same namespace, and # are all built from PIC. case $host_os in aix3*) test yes = "$enable_shared" && enable_static=no if test -n "$RANLIB"; then archive_cmds="$archive_cmds~\$RANLIB \$lib" postinstall_cmds='$RANLIB $lib' fi ;; aix[4-9]*) if test ia64 != "$host_cpu"; then case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in yes,aix,yes) ;; # shared object as lib.so file only yes,svr4,*) ;; # shared object as lib.so archive member only yes,*) enable_static=no ;; # shared object in lib.a archive as well esac fi ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enable_shared" >&5 printf "%s\n" "$enable_shared" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to build static libraries" >&5 printf %s "checking whether to build static libraries... " >&6; } # Make sure either enable_shared or enable_static is yes. test yes = "$enable_shared" || enable_static=yes { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $enable_static" >&5 printf "%s\n" "$enable_static" >&6; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu CC=$lt_save_CC if test -n "$CXX" && ( test no != "$CXX" && ( (test g++ = "$CXX" && `g++ -v >/dev/null 2>&1` ) || (test g++ != "$CXX"))); then ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to run the C++ preprocessor" >&5 printf %s "checking how to run the C++ preprocessor... " >&6; } if test -z "$CXXCPP"; then if test ${ac_cv_prog_CXXCPP+y} then : printf %s "(cached) " >&6 else case e in #( e) # Double quotes because $CXX needs to be expanded for CXXCPP in "$CXX -E" cpp /lib/cpp do ac_preproc_ok=false for ac_cxx_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include Syntax error _ACEOF if ac_fn_cxx_try_cpp "$LINENO" then : else case e in #( e) # Broken: fails on valid input. continue ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_cxx_try_cpp "$LINENO" then : # Broken: success on invalid input. continue else case e in #( e) # Passes both tests. ac_preproc_ok=: break ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok then : break fi done ac_cv_prog_CXXCPP=$CXXCPP ;; esac fi CXXCPP=$ac_cv_prog_CXXCPP else ac_cv_prog_CXXCPP=$CXXCPP fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CXXCPP" >&5 printf "%s\n" "$CXXCPP" >&6; } ac_preproc_ok=false for ac_cxx_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include Syntax error _ACEOF if ac_fn_cxx_try_cpp "$LINENO" then : else case e in #( e) # Broken: fails on valid input. continue ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_cxx_try_cpp "$LINENO" then : # Broken: success on invalid input. continue else case e in #( e) # Passes both tests. ac_preproc_ok=: break ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext done # Because of 'break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.i conftest.err conftest.$ac_ext if $ac_preproc_ok then : else case e in #( e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "C++ preprocessor \"$CXXCPP\" fails sanity check See 'config.log' for more details" "$LINENO" 5; } ;; esac fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu else _lt_caught_CXX_error=yes fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu archive_cmds_need_lc_CXX=no allow_undefined_flag_CXX= always_export_symbols_CXX=no archive_expsym_cmds_CXX= compiler_needs_object_CXX=no export_dynamic_flag_spec_CXX= hardcode_direct_CXX=no hardcode_direct_absolute_CXX=no hardcode_libdir_flag_spec_CXX= hardcode_libdir_separator_CXX= hardcode_minus_L_CXX=no hardcode_shlibpath_var_CXX=unsupported hardcode_automatic_CXX=no inherit_rpath_CXX=no module_cmds_CXX= module_expsym_cmds_CXX= link_all_deplibs_CXX=unknown old_archive_cmds_CXX=$old_archive_cmds reload_flag_CXX=$reload_flag reload_cmds_CXX=$reload_cmds no_undefined_flag_CXX= whole_archive_flag_spec_CXX= enable_shared_with_static_runtimes_CXX=no # Source file extension for C++ test sources. ac_ext=cpp # Object file extension for compiled C++ test sources. objext=o objext_CXX=$objext # No sense in running all these tests if we already determined that # the CXX compiler isn't working. Some variables (like enable_shared) # are currently assumed to apply to all compilers on this platform, # and will be corrupted by setting them based on a non-working compiler. if test yes != "$_lt_caught_CXX_error"; then # Code to be used in simple compile tests lt_simple_compile_test_code="int some_variable = 0;" # Code to be used in simple link tests lt_simple_link_test_code='int main(int, char *[]) { return(0); }' # ltmain only uses $CC for tagged configurations so make sure $CC is set. # If no C compiler was specified, use CC. LTCC=${LTCC-"$CC"} # If no C compiler flags were specified, use CFLAGS. LTCFLAGS=${LTCFLAGS-"$CFLAGS"} # Allow CC to be a program name with arguments. compiler=$CC # save warnings/boilerplate of simple test code ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" >conftest.$ac_ext eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_compiler_boilerplate=`cat conftest.err` $RM conftest* ac_outfile=conftest.$ac_objext echo "$lt_simple_link_test_code" >conftest.$ac_ext eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err _lt_linker_boilerplate=`cat conftest.err` $RM -r conftest* # Allow CC to be a program name with arguments. lt_save_CC=$CC lt_save_CFLAGS=$CFLAGS lt_save_LD=$LD lt_save_GCC=$GCC GCC=$GXX lt_save_with_gnu_ld=$with_gnu_ld lt_save_path_LD=$lt_cv_path_LD if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx else $as_unset lt_cv_prog_gnu_ld fi if test -n "${lt_cv_path_LDCXX+set}"; then lt_cv_path_LD=$lt_cv_path_LDCXX else $as_unset lt_cv_path_LD fi test -z "${LDCXX+set}" || LD=$LDCXX CC=${CXX-"c++"} CFLAGS=$CXXFLAGS compiler=$CC compiler_CXX=$CC func_cc_basename $compiler cc_basename=$func_cc_basename_result if test -n "$compiler"; then # We don't want -fno-exception when compiling C++ code, so set the # no_builtin_flag separately if test yes = "$GXX"; then lt_prog_compiler_no_builtin_flag_CXX=' -fno-builtin' else lt_prog_compiler_no_builtin_flag_CXX= fi if test yes = "$GXX"; then # Set up default GNU C++ configuration # Check whether --with-gnu-ld was given. if test ${with_gnu_ld+y} then : withval=$with_gnu_ld; test no = "$withval" || with_gnu_ld=yes else case e in #( e) with_gnu_ld=no ;; esac fi ac_prog=ld if test yes = "$GCC"; then # Check if gcc -print-prog-name=ld gives a path. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 printf %s "checking for ld used by $CC... " >&6; } case $host in *-*-mingw* | *-*-windows*) # gcc leaves a trailing carriage return, which upsets mingw ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; *) ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; esac case $ac_prog in # Accept absolute paths. [\\/]* | ?:[\\/]*) re_direlt='/[^/][^/]*/\.\./' # Canonicalize the pathname of ld ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` done test -z "$LD" && LD=$ac_prog ;; "") # If it fails, then pretend we aren't using GCC. ac_prog=ld ;; *) # If it is relative, then search for the first ld in PATH. with_gnu_ld=unknown ;; esac elif test yes = "$with_gnu_ld"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 printf %s "checking for GNU ld... " >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 printf %s "checking for non-GNU ld... " >&6; } fi if test ${lt_cv_path_LD+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -z "$LD"; then lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR for ac_dir in $PATH; do IFS=$lt_save_ifs test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then lt_cv_path_LD=$ac_dir/$ac_prog # Check to see if the program is GNU ld. I'd rather use --version, # but apparently some variants of GNU ld only accept -v. # Break only if it was the GNU/non-GNU ld that we prefer. case `"$lt_cv_path_LD" -v 2>&1 &5 printf "%s\n" "$LD" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 printf %s "checking if the linker ($LD) is GNU ld... " >&6; } if test ${lt_cv_prog_gnu_ld+y} then : printf %s "(cached) " >&6 else case e in #( e) # I'd rather use --version here, but apparently some GNU lds only accept -v. case `$LD -v 2>&1 &5 printf "%s\n" "$lt_cv_prog_gnu_ld" >&6; } with_gnu_ld=$lt_cv_prog_gnu_ld # Check if GNU C++ uses GNU ld as the underlying linker, since the # archiving commands below assume that GNU ld is being used. if test yes = "$with_gnu_ld"; then archive_cmds_CXX='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds_CXX='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' export_dynamic_flag_spec_CXX='$wl--export-dynamic' # If archive_cmds runs LD, not CC, wlarc should be empty # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to # investigate it a little bit more. (MM) wlarc='$wl' # ancient GNU ld didn't support --whole-archive et. al. if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then whole_archive_flag_spec_CXX=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' else whole_archive_flag_spec_CXX= fi else with_gnu_ld=no wlarc= # A generic and very simple default shared library creation # command for GNU C++ for the case where it uses the native # linker, instead of GNU ld. If possible, this setting should # overridden to take advantage of the native linker features on # the platform it is being used on. archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' fi # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " [-]L"' else GXX=no with_gnu_ld=no wlarc= fi # PORTME: fill in a description of your system's C++ link characteristics { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 printf %s "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } ld_shlibs_CXX=yes case $host_os in aix3*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; aix[4-9]*) if test ia64 = "$host_cpu"; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no exp_sym_flag='-Bexport' no_entry_flag= else aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # have runtime linking enabled, and use it for executables. # For shared libraries, we enable/disable runtime linking # depending on the kind of the shared library created - # when "with_aix_soname,aix_use_runtimelinking" is: # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables # "aix,yes" lib.so shared, rtl:yes, for executables # lib.a static archive # "both,no" lib.so.V(shr.o) shared, rtl:yes # lib.a(lib.so.V) shared, rtl:no, for executables # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a(lib.so.V) shared, rtl:no # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables # lib.a static archive case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) for ld_flag in $LDFLAGS; do case $ld_flag in *-brtl*) aix_use_runtimelinking=yes break ;; esac done if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then # With aix-soname=svr4, we create the lib.so.V shared archives only, # so we don't have lib.a shared libs to link our executables. # We have to force runtime linking in this case. aix_use_runtimelinking=yes LDFLAGS="$LDFLAGS -Wl,-brtl" fi ;; esac exp_sym_flag='-bexport' no_entry_flag='-bnoentry' fi # When large executables or shared objects are built, AIX ld can # have problems creating the table of contents. If linking a library # or program results in "error TOC overflow" add -mminimal-toc to # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. archive_cmds_CXX='' hardcode_direct_CXX=yes hardcode_direct_absolute_CXX=yes hardcode_libdir_separator_CXX=':' link_all_deplibs_CXX=yes file_list_spec_CXX='$wl-f,' case $with_aix_soname,$aix_use_runtimelinking in aix,*) ;; # no import file svr4,* | *,yes) # use import file # The Import File defines what to hardcode. hardcode_direct_CXX=no hardcode_direct_absolute_CXX=no ;; esac if test yes = "$GXX"; then case $host_os in aix4.[012]|aix4.[012].*) # We only want to do this on AIX 4.2 and lower, the check # below for broken collect2 doesn't work under 4.3+ collect2name=`$CC -print-prog-name=collect2` if test -f "$collect2name" && strings "$collect2name" | $GREP resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 hardcode_direct_CXX=unsupported # It fails to find uninstalled libraries when the uninstalled # path is not listed in the libpath. Setting hardcode_minus_L # to unsupported forces relinking hardcode_minus_L_CXX=yes hardcode_libdir_flag_spec_CXX='-L$libdir' hardcode_libdir_separator_CXX= fi esac shared_flag='-shared' if test yes = "$aix_use_runtimelinking"; then shared_flag=$shared_flag' $wl-G' fi # Need to ensure runtime linking is disabled for the traditional # shared library, or the linker may eventually find shared libraries # /with/ Import File - we do not want to mix them. shared_flag_aix='-shared' shared_flag_svr4='-shared $wl-G' else # not using gcc if test ia64 = "$host_cpu"; then # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release # chokes on -Wl,-G. The following line is correct: shared_flag='-G' else if test yes = "$aix_use_runtimelinking"; then shared_flag='$wl-G' else shared_flag='$wl-bM:SRE' fi shared_flag_aix='$wl-bM:SRE' shared_flag_svr4='$wl-G' fi fi export_dynamic_flag_spec_CXX='$wl-bexpall' # It seems that -bexpall does not export symbols beginning with # underscore (_), so it is better to generate a list of symbols to # export. always_export_symbols_CXX=yes if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then # Warning - without using the other runtime loading flags (-brtl), # -berok will link without error, but may produce a broken library. # The "-G" linker flag allows undefined symbols. no_undefined_flag_CXX='-bernotok' # Determine the default libpath from the value encoded in an empty # executable. if test set = "${lt_cv_aix_libpath+set}"; then aix_libpath=$lt_cv_aix_libpath else if test ${lt_cv_aix_libpath__CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_cxx_try_link "$LINENO" then : lt_aix_libpath_sed=' /Import File Strings/,/^$/ { /^0/ { s/^0 *\([^ ]*\) *$/\1/ p } }' lt_cv_aix_libpath__CXX=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` # Check for a 64-bit object if we didn't find anything. if test -z "$lt_cv_aix_libpath__CXX"; then lt_cv_aix_libpath__CXX=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` fi fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext if test -z "$lt_cv_aix_libpath__CXX"; then lt_cv_aix_libpath__CXX=/usr/lib:/lib fi ;; esac fi aix_libpath=$lt_cv_aix_libpath__CXX fi hardcode_libdir_flag_spec_CXX='$wl-blibpath:$libdir:'"$aix_libpath" archive_expsym_cmds_CXX='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag else if test ia64 = "$host_cpu"; then hardcode_libdir_flag_spec_CXX='$wl-R $libdir:/usr/lib:/lib' allow_undefined_flag_CXX="-z nodefs" archive_expsym_cmds_CXX="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" else # Determine the default libpath from the value encoded in an # empty executable. if test set = "${lt_cv_aix_libpath+set}"; then aix_libpath=$lt_cv_aix_libpath else if test ${lt_cv_aix_libpath__CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_cxx_try_link "$LINENO" then : lt_aix_libpath_sed=' /Import File Strings/,/^$/ { /^0/ { s/^0 *\([^ ]*\) *$/\1/ p } }' lt_cv_aix_libpath__CXX=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` # Check for a 64-bit object if we didn't find anything. if test -z "$lt_cv_aix_libpath__CXX"; then lt_cv_aix_libpath__CXX=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` fi fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext if test -z "$lt_cv_aix_libpath__CXX"; then lt_cv_aix_libpath__CXX=/usr/lib:/lib fi ;; esac fi aix_libpath=$lt_cv_aix_libpath__CXX fi hardcode_libdir_flag_spec_CXX='$wl-blibpath:$libdir:'"$aix_libpath" # Warning - without using the other run time loading flags, # -berok will link without error, but may produce a broken library. no_undefined_flag_CXX=' $wl-bernotok' allow_undefined_flag_CXX=' $wl-berok' if test yes = "$with_gnu_ld"; then # We only use this code for GNU lds that support --whole-archive. whole_archive_flag_spec_CXX='$wl--whole-archive$convenience $wl--no-whole-archive' else # Exported symbols can be pulled into shared objects from archives whole_archive_flag_spec_CXX='$convenience' fi archive_cmds_need_lc_CXX=yes archive_expsym_cmds_CXX='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' # -brtl affects multiple linker settings, -berok does not and is overridden later compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([, ]\\)%-berok\\1%g"`' if test svr4 != "$with_aix_soname"; then # This is similar to how AIX traditionally builds its shared # libraries. Need -bnortl late, we may have -brtl in LDFLAGS. archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' fi if test aix != "$with_aix_soname"; then archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' else # used by -dlpreopen to get the symbols archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$MV $output_objdir/$realname.d/$soname $output_objdir' fi archive_expsym_cmds_CXX="$archive_expsym_cmds_CXX"'~$RM -r $output_objdir/$realname.d' fi fi ;; beos*) if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then allow_undefined_flag_CXX=unsupported # Joseph Beckenbach says some releases of gcc # support --undefined. This deserves some investigation. FIXME archive_cmds_CXX='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' else ld_shlibs_CXX=no fi ;; chorus*) case $cc_basename in *) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; esac ;; cygwin* | mingw* | windows* | pw32* | cegcc*) case $GXX,$cc_basename in ,cl* | no,cl* | ,icl* | no,icl*) # Native MSVC or ICC # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. hardcode_libdir_flag_spec_CXX=' ' allow_undefined_flag_CXX=unsupported always_export_symbols_CXX=yes file_list_spec_CXX='@' # Tell ltmain to make .lib files, not .a files. libext=lib # Tell ltmain to make .dll files, not .so files. shrext_cmds=.dll # FIXME: Setting linknames here is a bad hack. archive_cmds_CXX='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' archive_expsym_cmds_CXX='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then cp "$export_symbols" "$output_objdir/$soname.def"; echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; else $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; fi~ $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ linknames=' # The linker will not automatically build a static lib if we build a DLL. # _LT_TAGVAR(old_archive_from_new_cmds, CXX)='true' enable_shared_with_static_runtimes_CXX=yes # Don't use ranlib old_postinstall_cmds_CXX='chmod 644 $oldlib' postlink_cmds_CXX='lt_outputfile="@OUTPUT@"~ lt_tool_outputfile="@TOOL_OUTPUT@"~ case $lt_outputfile in *.exe|*.EXE) ;; *) lt_outputfile=$lt_outputfile.exe lt_tool_outputfile=$lt_tool_outputfile.exe ;; esac~ func_to_tool_file "$lt_outputfile"~ if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; $RM "$lt_outputfile.manifest"; fi' ;; *) # g++ # _LT_TAGVAR(hardcode_libdir_flag_spec, CXX) is actually meaningless, # as there is no search path for DLLs. hardcode_libdir_flag_spec_CXX='-L$libdir' export_dynamic_flag_spec_CXX='$wl--export-all-symbols' allow_undefined_flag_CXX=unsupported always_export_symbols_CXX=no enable_shared_with_static_runtimes_CXX=yes file_list_spec_CXX='@' if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then archive_cmds_CXX='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' # If the export-symbols file already is a .def file, use it as # is; otherwise, prepend EXPORTS... archive_expsym_cmds_CXX='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then cp $export_symbols $output_objdir/$soname.def; else echo EXPORTS > $output_objdir/$soname.def; cat $export_symbols >> $output_objdir/$soname.def; fi~ $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' else ld_shlibs_CXX=no fi ;; esac ;; darwin* | rhapsody*) archive_cmds_need_lc_CXX=no hardcode_direct_CXX=no hardcode_automatic_CXX=yes hardcode_shlibpath_var_CXX=unsupported if test yes = "$lt_cv_ld_force_load"; then whole_archive_flag_spec_CXX='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' else whole_archive_flag_spec_CXX='' fi link_all_deplibs_CXX=yes allow_undefined_flag_CXX=$_lt_dar_allow_undefined case $cc_basename in ifort*|nagfor*) _lt_dar_can_shared=yes ;; *) _lt_dar_can_shared=$GCC ;; esac if test yes = "$_lt_dar_can_shared"; then output_verbose_link_cmd=func_echo_all archive_cmds_CXX="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" module_cmds_CXX="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" archive_expsym_cmds_CXX="$SED 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" module_expsym_cmds_CXX="$SED -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" if test yes = "$_lt_dar_needs_single_mod" -a yes != "$lt_cv_apple_cc_single_mod"; then archive_cmds_CXX="\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dsymutil" archive_expsym_cmds_CXX="$SED 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dar_export_syms$_lt_dsymutil" fi else ld_shlibs_CXX=no fi ;; os2*) hardcode_libdir_flag_spec_CXX='-L$libdir' hardcode_minus_L_CXX=yes allow_undefined_flag_CXX=unsupported shrext_cmds=.dll archive_cmds_CXX='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' archive_expsym_cmds_CXX='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ $ECHO EXPORTS >> $output_objdir/$libname.def~ prefix_cmds="$SED"~ if test EXPORTS = "`$SED 1q $export_symbols`"; then prefix_cmds="$prefix_cmds -e 1d"; fi~ prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ emximp -o $lib $output_objdir/$libname.def' old_archive_from_new_cmds_CXX='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' enable_shared_with_static_runtimes_CXX=yes file_list_spec_CXX='@' ;; dgux*) case $cc_basename in ec++*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; ghcx*) # Green Hills C++ Compiler # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; *) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; esac ;; freebsd2.*) # C++ shared libraries reported to be fairly broken before # switch to ELF ld_shlibs_CXX=no ;; freebsd-elf*) archive_cmds_need_lc_CXX=no ;; freebsd* | dragonfly* | midnightbsd*) # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF # conventions ld_shlibs_CXX=yes ;; haiku*) archive_cmds_CXX='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' link_all_deplibs_CXX=no ;; hpux9*) hardcode_libdir_flag_spec_CXX='$wl+b $wl$libdir' hardcode_libdir_separator_CXX=: export_dynamic_flag_spec_CXX='$wl-E' hardcode_direct_CXX=yes hardcode_minus_L_CXX=yes # Not in the search PATH, # but as the default # location of the library. case $cc_basename in CC*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; aCC*) archive_cmds_CXX='$RM $output_objdir/$soname~$CC -b $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "[-]L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes = "$GXX"; then archive_cmds_CXX='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' else # FIXME: insert proper C++ library support ld_shlibs_CXX=no fi ;; esac ;; hpux10*|hpux11*) if test no = "$with_gnu_ld"; then hardcode_libdir_flag_spec_CXX='$wl+b $wl$libdir' hardcode_libdir_separator_CXX=: case $host_cpu in hppa*64*|ia64*) ;; *) export_dynamic_flag_spec_CXX='$wl-E' ;; esac fi case $host_cpu in hppa*64*|ia64*) hardcode_direct_CXX=no hardcode_shlibpath_var_CXX=no ;; *) hardcode_direct_CXX=yes hardcode_direct_absolute_CXX=yes hardcode_minus_L_CXX=yes # Not in the search PATH, # but as the default # location of the library. ;; esac case $cc_basename in CC*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; aCC*) case $host_cpu in hppa*64*) archive_cmds_CXX='$CC -b $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; ia64*) archive_cmds_CXX='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; *) archive_cmds_CXX='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; esac # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP " [-]L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes = "$GXX"; then if test no = "$with_gnu_ld"; then case $host_cpu in hppa*64*) archive_cmds_CXX='$CC -shared -nostdlib -fPIC $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; ia64*) archive_cmds_CXX='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; *) archive_cmds_CXX='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' ;; esac fi else # FIXME: insert proper C++ library support ld_shlibs_CXX=no fi ;; esac ;; interix[3-9]*) hardcode_direct_CXX=no hardcode_shlibpath_var_CXX=no hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' export_dynamic_flag_spec_CXX='$wl-E' # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. # Instead, shared libraries are loaded at an image base (0x10000000 by # default) and relocated if they conflict, which is a slow very memory # consuming and fragmenting process. To avoid this, we pick a random, # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link # time. Moving up from 0x10000000 also allows more sbrk(2) space. archive_cmds_CXX='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' archive_expsym_cmds_CXX='$SED "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' ;; irix5* | irix6*) case $cc_basename in CC*) # SGI C++ archive_cmds_CXX='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' # Archives containing C++ object files must be created using # "CC -ar", where "CC" is the IRIX C++ compiler. This is # necessary to make sure instantiated templates are included # in the archive. old_archive_cmds_CXX='$CC -ar -WR,-u -o $oldlib $oldobjs' ;; *) if test yes = "$GXX"; then if test no = "$with_gnu_ld"; then archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' else archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` -o $lib' fi fi link_all_deplibs_CXX=yes ;; esac hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' hardcode_libdir_separator_CXX=: inherit_rpath_CXX=yes ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in KCC*) # Kuck and Associates, Inc. (KAI) C++ Compiler # KCC will only create a shared library if the output file # ends with ".so" (or ".sl" for HP-UX), so rename the library # to its proper name (with version) after linking. archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' archive_expsym_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib $wl-retain-symbols-file,$export_symbols; mv \$templib $lib' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' export_dynamic_flag_spec_CXX='$wl--export-dynamic' # Archives containing C++ object files must be created using # "CC -Bstatic", where "CC" is the KAI C++ compiler. old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs' ;; icpc* | ecpc* ) # Intel C++ with_gnu_ld=yes # version 8.0 and above of icpc choke on multiply defined symbols # if we add $predep_objects and $postdep_objects, however 7.1 and # earlier do not add the objects themselves. case `$CC -V 2>&1` in *"Version 7."*) archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; *) # Version 8.0 or newer tmp_idyn= case $host_cpu in ia64*) tmp_idyn=' -i_dynamic';; esac archive_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds_CXX='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; esac archive_cmds_need_lc_CXX=no hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' export_dynamic_flag_spec_CXX='$wl--export-dynamic' whole_archive_flag_spec_CXX='$wl--whole-archive$convenience $wl--no-whole-archive' ;; pgCC* | pgcpp*) # Portland Group C++ compiler case `$CC -V` in *pgCC\ [1-5].* | *pgcpp\ [1-5].*) prelink_cmds_CXX='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"' old_archive_cmds_CXX='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~ $RANLIB $oldlib' archive_cmds_CXX='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds_CXX='tpldir=Template.dir~ rm -rf $tpldir~ $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; *) # Version 6 and above use weak symbols archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' ;; esac hardcode_libdir_flag_spec_CXX='$wl--rpath $wl$libdir' export_dynamic_flag_spec_CXX='$wl--export-dynamic' whole_archive_flag_spec_CXX='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' ;; cxx*) # Compaq C++ archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' archive_expsym_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib $wl-retain-symbols-file $wl$export_symbols' runpath_var=LD_RUN_PATH hardcode_libdir_flag_spec_CXX='-rpath $libdir' hardcode_libdir_separator_CXX=: # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed' ;; xl* | mpixl* | bgxl*) # IBM XL 8.0 on PPC, with GNU ld hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' export_dynamic_flag_spec_CXX='$wl--export-dynamic' archive_cmds_CXX='$CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' if test yes = "$supports_anon_versioning"; then archive_expsym_cmds_CXX='echo "{ global:" > $output_objdir/$libname.ver~ cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ echo "local: *; };" >> $output_objdir/$libname.ver~ $CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' fi ;; *) case `$CC -V 2>&1 | $SED 5q` in *Sun\ C*) # Sun C++ 5.9 no_undefined_flag_CXX=' -zdefs' archive_cmds_CXX='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' archive_expsym_cmds_CXX='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file $wl$export_symbols' hardcode_libdir_flag_spec_CXX='-R$libdir' whole_archive_flag_spec_CXX='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' compiler_needs_object_CXX=yes # Not sure whether something based on # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 # would be better. output_verbose_link_cmd='func_echo_all' # Archives containing C++ object files must be created using # "CC -xar", where "CC" is the Sun C++ compiler. This is # necessary to make sure instantiated templates are included # in the archive. old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs' ;; esac ;; esac ;; lynxos*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; m88k*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; mvs*) case $cc_basename in cxx*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; *) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; esac ;; *-mlibc) ld_shlibs_CXX=yes ;; netbsd*) if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then archive_cmds_CXX='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' wlarc= hardcode_libdir_flag_spec_CXX='-R$libdir' hardcode_direct_CXX=yes hardcode_shlibpath_var_CXX=no fi # Workaround some broken pre-1.5 toolchains output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' ;; *nto* | *qnx*) ld_shlibs_CXX=yes ;; openbsd*) if test -f /usr/libexec/ld.so; then hardcode_direct_CXX=yes hardcode_shlibpath_var_CXX=no hardcode_direct_absolute_CXX=yes archive_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`"; then archive_expsym_cmds_CXX='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file,$export_symbols -o $lib' export_dynamic_flag_spec_CXX='$wl-E' whole_archive_flag_spec_CXX=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' fi output_verbose_link_cmd=func_echo_all else ld_shlibs_CXX=no fi ;; osf3* | osf4* | osf5*) case $cc_basename in KCC*) # Kuck and Associates, Inc. (KAI) C++ Compiler # KCC will only create a shared library if the output file # ends with ".so" (or ".sl" for HP-UX), so rename the library # to its proper name (with version) after linking. archive_cmds_CXX='tempext=`echo $shared_ext | $SED -e '\''s/\([^()0-9A-Za-z{}]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' hardcode_libdir_flag_spec_CXX='$wl-rpath,$libdir' hardcode_libdir_separator_CXX=: # Archives containing C++ object files must be created using # the KAI C++ compiler. case $host in osf3*) old_archive_cmds_CXX='$CC -Bstatic -o $oldlib $oldobjs' ;; *) old_archive_cmds_CXX='$CC -o $oldlib $oldobjs' ;; esac ;; RCC*) # Rational C++ 2.4.1 # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; cxx*) case $host in osf3*) allow_undefined_flag_CXX=' $wl-expect_unresolved $wl\*' archive_cmds_CXX='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $soname `test -n "$verstring" && func_echo_all "$wl-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' ;; *) allow_undefined_flag_CXX=' -expect_unresolved \*' archive_cmds_CXX='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' archive_expsym_cmds_CXX='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ echo "-hidden">> $lib.exp~ $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname $wl-input $wl$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~ $RM $lib.exp' hardcode_libdir_flag_spec_CXX='-rpath $libdir' ;; esac hardcode_libdir_separator_CXX=: # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. # # There doesn't appear to be a way to prevent this compiler from # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes,no = "$GXX,$with_gnu_ld"; then allow_undefined_flag_CXX=' $wl-expect_unresolved $wl\*' case $host in osf3*) archive_cmds_CXX='$CC -shared -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' ;; *) archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' ;; esac hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' hardcode_libdir_separator_CXX=: # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " [-]L"' else # FIXME: insert proper C++ library support ld_shlibs_CXX=no fi ;; esac ;; psos*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; serenity*) ;; sunos4*) case $cc_basename in CC*) # Sun C++ 4.x # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; lcc*) # Lucid # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; *) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; esac ;; solaris*) case $cc_basename in CC* | sunCC*) # Sun C++ 4.2, 5.x and Centerline C++ archive_cmds_need_lc_CXX=yes no_undefined_flag_CXX=' -zdefs' archive_cmds_CXX='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -G$allow_undefined_flag $wl-M $wl$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' hardcode_libdir_flag_spec_CXX='-R$libdir' hardcode_shlibpath_var_CXX=no case $host_os in solaris2.[0-5] | solaris2.[0-5].*) ;; *) # The compiler driver will combine and reorder linker options, # but understands '-z linker_flag'. # Supported since Solaris 2.6 (maybe 2.5.1?) whole_archive_flag_spec_CXX='-z allextract$convenience -z defaultextract' ;; esac link_all_deplibs_CXX=yes output_verbose_link_cmd='func_echo_all' # Archives containing C++ object files must be created using # "CC -xar", where "CC" is the Sun C++ compiler. This is # necessary to make sure instantiated templates are included # in the archive. old_archive_cmds_CXX='$CC -xar -o $oldlib $oldobjs' ;; gcx*) # Green Hills C++ Compiler archive_cmds_CXX='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' # The C++ compiler must be used to create the archive. old_archive_cmds_CXX='$CC $LDFLAGS -archive -o $oldlib $oldobjs' ;; *) # GNU C++ compiler with Solaris linker if test yes,no = "$GXX,$with_gnu_ld"; then no_undefined_flag_CXX=' $wl-z ${wl}defs' if $CC --version | $GREP -v '^2\.7' > /dev/null; then archive_cmds_CXX='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -shared $pic_flag -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " [-]L"' else # g++ 2.7 appears to require '-G' NOT '-shared' on this # platform. archive_cmds_CXX='$CC -G -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' archive_expsym_cmds_CXX='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ $CC -G -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " [-]L"' fi hardcode_libdir_flag_spec_CXX='$wl-R $wl$libdir' case $host_os in solaris2.[0-5] | solaris2.[0-5].*) ;; *) whole_archive_flag_spec_CXX='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' ;; esac fi ;; esac ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) no_undefined_flag_CXX='$wl-z,text' archive_cmds_need_lc_CXX=no hardcode_shlibpath_var_CXX=no runpath_var='LD_RUN_PATH' case $cc_basename in CC*) archive_cmds_CXX='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds_CXX='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' ;; *) archive_cmds_CXX='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds_CXX='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' ;; esac ;; sysv5* | sco3.2v5* | sco5v6*) # Note: We CANNOT use -z defs as we might desire, because we do not # link with -lc, and that would cause any symbols used from libc to # always be unresolved, which means just about no library would # ever link correctly. If we're not using GNU ld we use -z text # though, which does catch some bad symbols but isn't as heavy-handed # as -z defs. no_undefined_flag_CXX='$wl-z,text' allow_undefined_flag_CXX='$wl-z,nodefs' archive_cmds_need_lc_CXX=no hardcode_shlibpath_var_CXX=no hardcode_libdir_flag_spec_CXX='$wl-R,$libdir' hardcode_libdir_separator_CXX=':' link_all_deplibs_CXX=yes export_dynamic_flag_spec_CXX='$wl-Bexport' runpath_var='LD_RUN_PATH' case $cc_basename in CC*) archive_cmds_CXX='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds_CXX='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' old_archive_cmds_CXX='$CC -Tprelink_objects $oldobjs~ '"$old_archive_cmds_CXX" reload_cmds_CXX='$CC -Tprelink_objects $reload_objs~ '"$reload_cmds_CXX" ;; *) archive_cmds_CXX='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' archive_expsym_cmds_CXX='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' ;; esac ;; tandem*) case $cc_basename in NCC*) # NonStop-UX NCC 3.20 # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; *) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; esac ;; vxworks*) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; *) # FIXME: insert proper C++ library support ld_shlibs_CXX=no ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs_CXX" >&5 printf "%s\n" "$ld_shlibs_CXX" >&6; } test no = "$ld_shlibs_CXX" && can_build_shared=no GCC_CXX=$GXX LD_CXX=$LD ## CAVEAT EMPTOR: ## There is no encapsulation within the following macros, do not change ## the running order or otherwise move them around unless you know exactly ## what you are doing... # Dependencies to place before and after the object being linked: predep_objects_CXX= postdep_objects_CXX= predeps_CXX= postdeps_CXX= compiler_lib_search_path_CXX= cat > conftest.$ac_ext <<_LT_EOF class Foo { public: Foo (void) { a = 0; } private: int a; }; _LT_EOF _lt_libdeps_save_CFLAGS=$CFLAGS case "$CC $CFLAGS " in #( *\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;; *\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;; *\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;; esac if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then # Parse the compiler output and extract the necessary # objects, libraries and library flags. # Sentinel used to keep track of whether or not we are before # the conftest object file. pre_test_object_deps_done=no for p in `eval "$output_verbose_link_cmd"`; do case $prev$p in -L* | -R* | -l*) # Some compilers place space between "-{L,R,l}" and the path. # Remove the space. if test x-L = x"$p" || test x-R = x"$p" || test x-l = x"$p"; then prev=$p continue fi # Expand the sysroot to ease extracting the directories later. if test -z "$prev"; then case $p in -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;; -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;; -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;; esac fi case $p in =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;; esac if test no = "$pre_test_object_deps_done"; then case $prev in -L | -R) # Internal compiler library paths should come after those # provided the user. The postdeps already come after the # user supplied libs so there is no need to process them. if test -z "$compiler_lib_search_path_CXX"; then compiler_lib_search_path_CXX=$prev$p else compiler_lib_search_path_CXX="${compiler_lib_search_path_CXX} $prev$p" fi ;; # The "-l" case would never come before the object being # linked, so don't bother handling this case. esac else if test -z "$postdeps_CXX"; then postdeps_CXX=$prev$p else postdeps_CXX="${postdeps_CXX} $prev$p" fi fi prev= ;; *.lto.$objext) ;; # Ignore GCC LTO objects *.$objext) # This assumes that the test object file only shows up # once in the compiler output. if test "$p" = "conftest.$objext"; then pre_test_object_deps_done=yes continue fi if test no = "$pre_test_object_deps_done"; then if test -z "$predep_objects_CXX"; then predep_objects_CXX=$p else predep_objects_CXX="$predep_objects_CXX $p" fi else if test -z "$postdep_objects_CXX"; then postdep_objects_CXX=$p else postdep_objects_CXX="$postdep_objects_CXX $p" fi fi ;; *) ;; # Ignore the rest. esac done # Clean up. rm -f a.out a.exe else echo "libtool.m4: error: problem compiling CXX test program" fi $RM -f confest.$objext CFLAGS=$_lt_libdeps_save_CFLAGS # PORTME: override above test on systems where it is broken case $host_os in interix[3-9]*) # Interix 3.5 installs completely hosed .la files for C++, so rather than # hack all around it, let's just trust "g++" to DTRT. predep_objects_CXX= postdep_objects_CXX= postdeps_CXX= ;; esac case " $postdeps_CXX " in *" -lc "*) archive_cmds_need_lc_CXX=no ;; esac compiler_lib_search_dirs_CXX= if test -n "${compiler_lib_search_path_CXX}"; then compiler_lib_search_dirs_CXX=`echo " ${compiler_lib_search_path_CXX}" | $SED -e 's! -L! !g' -e 's!^ !!'` fi lt_prog_compiler_wl_CXX= lt_prog_compiler_pic_CXX= lt_prog_compiler_static_CXX= # C++ specific cases for pic, static, wl, etc. if test yes = "$GXX"; then lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_static_CXX='-static' case $host_os in aix*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static_CXX='-Bstatic' fi lt_prog_compiler_pic_CXX='-fPIC' ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support lt_prog_compiler_pic_CXX='-fPIC' ;; m68k) # FIXME: we need at least 68020 code to build shared libraries, but # adding the '-m68020' flag to GCC prevents building anything better, # like '-m68040'. lt_prog_compiler_pic_CXX='-m68020 -resident32 -malways-restore-a4' ;; esac ;; beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) # PIC is the default for these OSes. ;; mingw* | windows* | cygwin* | os2* | pw32* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). # Although the cygwin gcc ignores -fPIC, still need this for old-style # (--disable-auto-import) libraries lt_prog_compiler_pic_CXX='-DDLL_EXPORT' case $host_os in os2*) lt_prog_compiler_static_CXX='$wl-static' ;; esac ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files lt_prog_compiler_pic_CXX='-fno-common' ;; *djgpp*) # DJGPP does not support shared libraries at all lt_prog_compiler_pic_CXX= ;; haiku*) # PIC is the default for Haiku. # The "-static" flag exists, but is broken. lt_prog_compiler_static_CXX= ;; interix[3-9]*) # Interix 3.x gcc -fpic/-fPIC options generate broken code. # Instead, we relocate shared libraries at runtime. ;; sysv4*MP*) if test -d /usr/nec; then lt_prog_compiler_pic_CXX=-Kconform_pic fi ;; hpux*) # PIC is the default for 64-bit PA HP-UX, but not for 32-bit # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag # sets the default TLS model and affects inlining. case $host_cpu in hppa*64*) ;; *) lt_prog_compiler_pic_CXX='-fPIC' ;; esac ;; *qnx* | *nto*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic_CXX='-fPIC -shared' ;; *) lt_prog_compiler_pic_CXX='-fPIC' ;; esac else case $host_os in aix[4-9]*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static_CXX='-Bstatic' else lt_prog_compiler_static_CXX='-bnso -bI:/lib/syscalls.exp' fi ;; chorus*) case $cc_basename in cxch68*) # Green Hills C++ Compiler # _LT_TAGVAR(lt_prog_compiler_static, CXX)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" ;; esac ;; mingw* | windows* | cygwin* | os2* | pw32* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). lt_prog_compiler_pic_CXX='-DDLL_EXPORT' ;; dgux*) case $cc_basename in ec++*) lt_prog_compiler_pic_CXX='-KPIC' ;; ghcx*) # Green Hills C++ Compiler lt_prog_compiler_pic_CXX='-pic' ;; *) ;; esac ;; freebsd* | dragonfly* | midnightbsd*) # FreeBSD uses GNU C++ ;; hpux9* | hpux10* | hpux11*) case $cc_basename in CC*) lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_static_CXX='$wl-a ${wl}archive' if test ia64 != "$host_cpu"; then lt_prog_compiler_pic_CXX='+Z' fi ;; aCC*) lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_static_CXX='$wl-a ${wl}archive' case $host_cpu in hppa*64*|ia64*) # +Z the default ;; *) lt_prog_compiler_pic_CXX='+Z' ;; esac ;; *) ;; esac ;; interix*) # This is c89, which is MS Visual C++ (no shared libs) # Anyone wants to do a port? ;; irix5* | irix6* | nonstopux*) case $cc_basename in CC*) lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_static_CXX='-non_shared' # CC pic flag -KPIC is the default. ;; *) ;; esac ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in KCC*) # KAI C++ Compiler lt_prog_compiler_wl_CXX='--backend -Wl,' lt_prog_compiler_pic_CXX='-fPIC' ;; ecpc* ) # old Intel C++ for x86_64, which still supported -KPIC. lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-KPIC' lt_prog_compiler_static_CXX='-static' ;; icpc* ) # Intel C++, used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-fPIC' lt_prog_compiler_static_CXX='-static' ;; pgCC* | pgcpp*) # Portland Group C++ compiler lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-fpic' lt_prog_compiler_static_CXX='-Bstatic' ;; cxx*) # Compaq C++ # Make sure the PIC flag is empty. It appears that all Alpha # Linux and Compaq Tru64 Unix objects are PIC. lt_prog_compiler_pic_CXX= lt_prog_compiler_static_CXX='-non_shared' ;; xlc* | xlC* | bgxl[cC]* | mpixl[cC]*) # IBM XL 8.0, 9.0 on PPC and BlueGene lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-qpic' lt_prog_compiler_static_CXX='-qstaticlink' ;; *) case `$CC -V 2>&1 | $SED 5q` in *Sun\ C*) # Sun C++ 5.9 lt_prog_compiler_pic_CXX='-KPIC' lt_prog_compiler_static_CXX='-Bstatic' lt_prog_compiler_wl_CXX='-Qoption ld ' ;; esac ;; esac ;; lynxos*) ;; m88k*) ;; mvs*) case $cc_basename in cxx*) lt_prog_compiler_pic_CXX='-W c,exportall' ;; *) ;; esac ;; netbsd* | netbsdelf*-gnu) ;; *-mlibc) ;; *qnx* | *nto*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic_CXX='-fPIC -shared' ;; osf3* | osf4* | osf5*) case $cc_basename in KCC*) lt_prog_compiler_wl_CXX='--backend -Wl,' ;; RCC*) # Rational C++ 2.4.1 lt_prog_compiler_pic_CXX='-pic' ;; cxx*) # Digital/Compaq C++ lt_prog_compiler_wl_CXX='-Wl,' # Make sure the PIC flag is empty. It appears that all Alpha # Linux and Compaq Tru64 Unix objects are PIC. lt_prog_compiler_pic_CXX= lt_prog_compiler_static_CXX='-non_shared' ;; *) ;; esac ;; psos*) ;; serenity*) ;; solaris*) case $cc_basename in CC* | sunCC*) # Sun C++ 4.2, 5.x and Centerline C++ lt_prog_compiler_pic_CXX='-KPIC' lt_prog_compiler_static_CXX='-Bstatic' lt_prog_compiler_wl_CXX='-Qoption ld ' ;; gcx*) # Green Hills C++ Compiler lt_prog_compiler_pic_CXX='-PIC' ;; *) ;; esac ;; sunos4*) case $cc_basename in CC*) # Sun C++ 4.x lt_prog_compiler_pic_CXX='-pic' lt_prog_compiler_static_CXX='-Bstatic' ;; lcc*) # Lucid lt_prog_compiler_pic_CXX='-pic' ;; *) ;; esac ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) case $cc_basename in CC*) lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-KPIC' lt_prog_compiler_static_CXX='-Bstatic' ;; esac ;; tandem*) case $cc_basename in NCC*) # NonStop-UX NCC 3.20 lt_prog_compiler_pic_CXX='-KPIC' ;; *) ;; esac ;; vxworks*) ;; *) lt_prog_compiler_can_build_shared_CXX=no ;; esac fi case $host_os in # For platforms that do not support PIC, -DPIC is meaningless: *djgpp*) lt_prog_compiler_pic_CXX= ;; *) lt_prog_compiler_pic_CXX="$lt_prog_compiler_pic_CXX -DPIC" ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 printf %s "checking for $compiler option to produce PIC... " >&6; } if test ${lt_cv_prog_compiler_pic_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_pic_CXX=$lt_prog_compiler_pic_CXX ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_CXX" >&5 printf "%s\n" "$lt_cv_prog_compiler_pic_CXX" >&6; } lt_prog_compiler_pic_CXX=$lt_cv_prog_compiler_pic_CXX # # Check to make sure the PIC flag actually works. # if test -n "$lt_prog_compiler_pic_CXX"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works" >&5 printf %s "checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works... " >&6; } if test ${lt_cv_prog_compiler_pic_works_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_pic_works_CXX=no ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="$lt_prog_compiler_pic_CXX -DPIC" ## exclude from sc_useless_quotes_in_assignment # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. # The option is referenced via a variable to avoid confusing sed. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_pic_works_CXX=yes fi fi $RM conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works_CXX" >&5 printf "%s\n" "$lt_cv_prog_compiler_pic_works_CXX" >&6; } if test yes = "$lt_cv_prog_compiler_pic_works_CXX"; then case $lt_prog_compiler_pic_CXX in "" | " "*) ;; *) lt_prog_compiler_pic_CXX=" $lt_prog_compiler_pic_CXX" ;; esac else lt_prog_compiler_pic_CXX= lt_prog_compiler_can_build_shared_CXX=no fi fi # # Check to make sure the static flag actually works. # wl=$lt_prog_compiler_wl_CXX eval lt_tmp_static_flag=\"$lt_prog_compiler_static_CXX\" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 printf %s "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } if test ${lt_cv_prog_compiler_static_works_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_static_works_CXX=no save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS $lt_tmp_static_flag" echo "$lt_simple_link_test_code" > conftest.$ac_ext if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then # The linker can only warn and ignore the option if not recognized # So say no if there are warnings if test -s conftest.err; then # Append any errors to the config.log. cat conftest.err 1>&5 $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_static_works_CXX=yes fi else lt_cv_prog_compiler_static_works_CXX=yes fi fi $RM -r conftest* LDFLAGS=$save_LDFLAGS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works_CXX" >&5 printf "%s\n" "$lt_cv_prog_compiler_static_works_CXX" >&6; } if test yes = "$lt_cv_prog_compiler_static_works_CXX"; then : else lt_prog_compiler_static_CXX= fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 printf %s "checking if $compiler supports -c -o file.$ac_objext... " >&6; } if test ${lt_cv_prog_compiler_c_o_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_c_o_CXX=no $RM -r conftest 2>/dev/null mkdir conftest cd conftest mkdir out echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-o out/conftest2.$ac_objext" # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then lt_cv_prog_compiler_c_o_CXX=yes fi fi chmod u+w . 2>&5 $RM conftest* # SGI C++ compiler will create directory out/ii_files/ for # template instantiation test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files $RM out/* && rmdir out cd .. $RM -r conftest $RM conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_CXX" >&5 printf "%s\n" "$lt_cv_prog_compiler_c_o_CXX" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 printf %s "checking if $compiler supports -c -o file.$ac_objext... " >&6; } if test ${lt_cv_prog_compiler_c_o_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_c_o_CXX=no $RM -r conftest 2>/dev/null mkdir conftest cd conftest mkdir out echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="-o out/conftest2.$ac_objext" # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>out/conftest.err) ac_status=$? cat out/conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s out/conftest2.$ac_objext then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then lt_cv_prog_compiler_c_o_CXX=yes fi fi chmod u+w . 2>&5 $RM conftest* # SGI C++ compiler will create directory out/ii_files/ for # template instantiation test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files $RM out/* && rmdir out cd .. $RM -r conftest $RM conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o_CXX" >&5 printf "%s\n" "$lt_cv_prog_compiler_c_o_CXX" >&6; } hard_links=nottested if test no = "$lt_cv_prog_compiler_c_o_CXX" && test no != "$need_locks"; then # do not overwrite the value of need_locks provided by the user { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5 printf %s "checking if we can lock with hard links... " >&6; } hard_links=yes $RM conftest* ln conftest.a conftest.b 2>/dev/null && hard_links=no touch conftest.a ln conftest.a conftest.b 2>&5 || hard_links=no ln conftest.a conftest.b 2>/dev/null && hard_links=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5 printf "%s\n" "$hard_links" >&6; } if test no = "$hard_links"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&5 printf "%s\n" "$as_me: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&2;} need_locks=warn fi else need_locks=no fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 printf %s "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' exclude_expsyms_CXX='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' case $host_os in aix[4-9]*) # If we're using GNU nm, then we don't want the "-C" option. # -C means demangle to GNU nm, but means don't demangle to AIX nm. # Without the "-l" option, or with the "-B" option, AIX nm treats # weak defined symbols like other global defined symbols, whereas # GNU nm marks them as "W". # While the 'weak' keyword is ignored in the Export File, we need # it in the Import File for the 'aix-soname' feature, so we have # to replace the "-B" option with "-P" for AIX nm. if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then export_symbols_cmds_CXX='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' else export_symbols_cmds_CXX='`func_echo_all $NM | $SED -e '\''s/B\([^B]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "L") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && (substr(\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' fi ;; pw32*) export_symbols_cmds_CXX=$ltdll_cmds ;; cygwin* | mingw* | windows* | cegcc*) case $cc_basename in cl* | icl*) exclude_expsyms_CXX='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' ;; *) export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols' exclude_expsyms_CXX='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname' ;; esac ;; linux* | k*bsd*-gnu | gnu*) link_all_deplibs_CXX=no ;; *) export_symbols_cmds_CXX='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs_CXX" >&5 printf "%s\n" "$ld_shlibs_CXX" >&6; } test no = "$ld_shlibs_CXX" && can_build_shared=no with_gnu_ld_CXX=$with_gnu_ld # # Do we need to explicitly link libc? # case "x$archive_cmds_need_lc_CXX" in x|xyes) # Assume -lc should be added archive_cmds_need_lc_CXX=yes if test yes,yes = "$GCC,$enable_shared"; then case $archive_cmds_CXX in *'~'*) # FIXME: we may have to deal with multi-command sequences. ;; '$CC '*) # Test whether the compiler implicitly links with -lc since on some # systems, -lgcc has to come before -lc. If gcc already passes -lc # to ld, don't add -lc before -lgcc. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5 printf %s "checking whether -lc should be explicitly linked in... " >&6; } if test ${lt_cv_archive_cmds_need_lc_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) $RM conftest* echo "$lt_simple_compile_test_code" > conftest.$ac_ext if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 (eval $ac_compile) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } 2>conftest.err; then soname=conftest lib=conftest libobjs=conftest.$ac_objext deplibs= wl=$lt_prog_compiler_wl_CXX pic_flag=$lt_prog_compiler_pic_CXX compiler_flags=-v linker_flags=-v verstring= output_objdir=. libname=conftest lt_save_allow_undefined_flag=$allow_undefined_flag_CXX allow_undefined_flag_CXX= if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds_CXX 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5 (eval $archive_cmds_CXX 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } then lt_cv_archive_cmds_need_lc_CXX=no else lt_cv_archive_cmds_need_lc_CXX=yes fi allow_undefined_flag_CXX=$lt_save_allow_undefined_flag else cat conftest.err 1>&5 fi $RM conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc_CXX" >&5 printf "%s\n" "$lt_cv_archive_cmds_need_lc_CXX" >&6; } archive_cmds_need_lc_CXX=$lt_cv_archive_cmds_need_lc_CXX ;; esac fi ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5 printf %s "checking dynamic linker characteristics... " >&6; } library_names_spec= libname_spec='lib$name' soname_spec= shrext_cmds=.so postinstall_cmds= postuninstall_cmds= finish_cmds= finish_eval= shlibpath_var= shlibpath_overrides_runpath=unknown version_type=none dynamic_linker="$host_os ld.so" sys_lib_dlsearch_path_spec="/lib /usr/lib" need_lib_prefix=unknown hardcode_into_libs=no # when you set need_version to no, make sure it does not cause -set_version # flags to be left without arguments need_version=unknown case $host_os in aix3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname.a' shlibpath_var=LIBPATH # AIX 3 has no versioning support, so we append a major version to the name. soname_spec='$libname$release$shared_ext$major' ;; aix[4-9]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no hardcode_into_libs=yes if test ia64 = "$host_cpu"; then # AIX 5 supports IA64 library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH else # With GCC up to 2.95.x, collect2 would create an import file # for dependence libraries. The import file would start with # the line '#! .'. This would cause the generated library to # depend on '.', always an invalid library. This was fixed in # development snapshots of GCC prior to 3.0. case $host_os in aix4 | aix4.[01] | aix4.[01].*) if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' echo ' yes ' echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then : else can_build_shared=no fi ;; esac # Using Import Files as archive members, it is possible to support # filename-based versioning of shared library archives on AIX. While # this would work for both with and without runtime linking, it will # prevent static linking of such archives. So we do filename-based # shared library versioning with .so extension only, which is used # when both runtime linking and shared linking is enabled. # Unfortunately, runtime linking may impact performance, so we do # not want this to be the default eventually. Also, we use the # versioned .so libs for executables only if there is the -brtl # linker flag in LDFLAGS as well, or --enable-aix-soname=svr4 only. # To allow for filename-based versioning support, we need to create # libNAME.so.V as an archive file, containing: # *) an Import File, referring to the versioned filename of the # archive as well as the shared archive member, telling the # bitwidth (32 or 64) of that shared object, and providing the # list of exported symbols of that shared object, eventually # decorated with the 'weak' keyword # *) the shared object with the F_LOADONLY flag set, to really avoid # it being seen by the linker. # At run time we better use the real file rather than another symlink, # but for link time we create the symlink libNAME.so -> libNAME.so.V case $with_aix_soname,$aix_use_runtimelinking in # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct # soname into executable. Probably we can add versioning support to # collect2, so additional links can be useful in future. aix,yes) # traditional libtool dynamic_linker='AIX unversionable lib.so' # If using run time linking (on AIX 4.2 or later) use lib.so # instead of lib.a to let people know that these are not # typical AIX shared libraries. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; aix,no) # traditional AIX only dynamic_linker='AIX lib.a(lib.so.V)' # We preserve .a as extension for shared libraries through AIX4.2 # and later when we are not doing run time linking. library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' ;; svr4,*) # full svr4 only dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o)" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,yes) # both, prefer svr4 dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o), lib.a(lib.so.V)" library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' # unpreferred sharedlib libNAME.a needs extra handling postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' # We do not specify a path in Import Files, so LIBPATH fires. shlibpath_overrides_runpath=yes ;; *,no) # both, prefer aix dynamic_linker="AIX lib.a(lib.so.V), lib.so.V($shared_archive_member_spec.o)" library_names_spec='$libname$release.a $libname.a' soname_spec='$libname$release$shared_ext$major' # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' ;; esac shlibpath_var=LIBPATH fi ;; amigaos*) case $host_cpu in powerpc) # Since July 2007 AmigaOS4 officially supports .so libraries. # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' ;; m68k) library_names_spec='$libname.ixlibrary $libname.a' # Create ${libname}_ixlibrary.a entries in /sys/libs. finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' ;; esac ;; beos*) library_names_spec='$libname$shared_ext' dynamic_linker="$host_os ld.so" shlibpath_var=LIBRARY_PATH ;; bsdi[45]*) version_type=linux # correct to gnu/linux during the next big refactor need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" # the default ld.so.conf also contains /usr/contrib/lib and # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow # libtool to hard-code these into programs ;; cygwin* | mingw* | windows* | pw32* | cegcc*) version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no case $GCC,$cc_basename in yes,*) # gcc library_names_spec='$libname.dll.a' # DLL is installed to $(libdir)/../bin by postinstall_cmds # If user builds GCC with multilib enabled, # it should just install on $(libdir) # not on $(libdir)/../bin or 32 bits dlls would override 64 bit ones. if test xyes = x"$multilib"; then postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ $install_prog $dir/$dlname $destdir/$dlname~ chmod a+x $destdir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib $destdir/$dlname'\'' || exit \$?; fi' else postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' fi postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes case $host_os in cygwin*) # Cygwin DLLs use 'cyg' prefix rather than 'lib' soname_spec='`echo $libname | $SED -e 's/^lib/cyg/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' ;; mingw* | windows* | cegcc*) # MinGW DLLs use traditional 'lib' prefix soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' ;; pw32*) # pw32 DLLs use 'pw' prefix rather than 'lib' library_names_spec='`echo $libname | $SED -e 's/^lib/pw/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' ;; esac dynamic_linker='Win32 ld.exe' ;; *,cl* | *,icl*) # Native MSVC or ICC libname_spec='$name' soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' library_names_spec='$libname.dll.lib' case $build_os in mingw* | windows*) sys_lib_search_path_spec= lt_save_ifs=$IFS IFS=';' for lt_path in $LIB do IFS=$lt_save_ifs # Let DOS variable expansion print the short 8.3 style file name. lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" done IFS=$lt_save_ifs # Convert to MSYS style. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'` ;; cygwin*) # Convert to unix form, then to dos form, then back to unix form # but this time dos style (no spaces!) so that the unix form looks # like /cygdrive/c/PROGRA~1:/cygdr... sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` ;; *) sys_lib_search_path_spec=$LIB if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then # It is most probably a Windows format PATH. sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` else sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` fi # FIXME: find the short name or the path components, as spaces are # common. (e.g. "Program Files" -> "PROGRA~1") ;; esac # DLL is installed to $(libdir)/../bin by postinstall_cmds postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' shlibpath_overrides_runpath=yes dynamic_linker='Win32 link.exe' ;; *) # Assume MSVC and ICC wrapper library_names_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext $libname.lib' dynamic_linker='Win32 ld.exe' ;; esac # FIXME: first we should search . and the directory the executable is in shlibpath_var=PATH ;; darwin* | rhapsody*) dynamic_linker="$host_os dyld" version_type=darwin need_lib_prefix=no need_version=no library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' soname_spec='$libname$release$major$shared_ext' shlibpath_overrides_runpath=yes shlibpath_var=DYLD_LIBRARY_PATH shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' ;; dgux*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; freebsd* | dragonfly* | midnightbsd*) # DragonFly does not have aout. When/if they implement a new # versioning mechanism, adjust this. if test -x /usr/bin/objformat; then objformat=`/usr/bin/objformat` else case $host_os in freebsd[23].*) objformat=aout ;; *) objformat=elf ;; esac fi version_type=freebsd-$objformat case $version_type in freebsd-elf*) library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' need_version=no need_lib_prefix=no ;; freebsd-*) library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' need_version=yes ;; esac case $host_cpu in powerpc64) # On FreeBSD bi-arch platforms, a different variable is used for 32-bit # binaries. See . cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int test_pointer_size[sizeof (void *) - 5]; _ACEOF if ac_fn_cxx_try_compile "$LINENO" then : shlibpath_var=LD_LIBRARY_PATH else case e in #( e) shlibpath_var=LD_32_LIBRARY_PATH ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; *) shlibpath_var=LD_LIBRARY_PATH ;; esac case $host_os in freebsd2.*) shlibpath_overrides_runpath=yes ;; freebsd3.[01]* | freebsdelf3.[01]*) shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; *) # from 4.6 on, and DragonFly shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; esac ;; haiku*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no dynamic_linker="$host_os runtime_loader" library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LIBRARY_PATH shlibpath_overrides_runpath=no sys_lib_search_path_spec='/boot/system/non-packaged/develop/lib /boot/system/develop/lib' sys_lib_dlsearch_path_spec='/boot/home/config/non-packaged/lib /boot/home/config/lib /boot/system/non-packaged/lib /boot/system/lib' hardcode_into_libs=no ;; hpux9* | hpux10* | hpux11*) # Give a soname corresponding to the major version so that dld.sl refuses to # link against other versions. version_type=sunos need_lib_prefix=no need_version=no case $host_cpu in ia64*) shrext_cmds='.so' hardcode_into_libs=yes dynamic_linker="$host_os dld.so" shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' if test 32 = "$HPUX_IA64_MODE"; then sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" sys_lib_dlsearch_path_spec=/usr/lib/hpux32 else sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" sys_lib_dlsearch_path_spec=/usr/lib/hpux64 fi ;; hppa*64*) shrext_cmds='.sl' hardcode_into_libs=yes dynamic_linker="$host_os dld.sl" shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; *) shrext_cmds='.sl' dynamic_linker="$host_os dld.sl" shlibpath_var=SHLIB_PATH shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' ;; esac # HP-UX runs *really* slowly unless shared libraries are mode 555, ... postinstall_cmds='chmod 555 $lib' # or fails outright, so override atomically: install_override_mode=555 ;; interix[3-9]*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; irix5* | irix6* | nonstopux*) case $host_os in nonstopux*) version_type=nonstopux ;; *) if test yes = "$lt_cv_prog_gnu_ld"; then version_type=linux # correct to gnu/linux during the next big refactor else version_type=irix fi ;; esac need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' case $host_os in irix5* | nonstopux*) libsuff= shlibsuff= ;; *) case $LD in # libtool.m4 will add one of these switches to LD *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= libmagic=32-bit;; *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 libmagic=N32;; *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 libmagic=64-bit;; *) libsuff= shlibsuff= libmagic=never-match;; esac ;; esac shlibpath_var=LD_LIBRARY${shlibsuff}_PATH shlibpath_overrides_runpath=no sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" hardcode_into_libs=yes ;; # No shared lib support for Linux oldld, aout, or coff. linux*oldld* | linux*aout* | linux*coff*) dynamic_linker=no ;; linux*android*) version_type=none # Android doesn't support versioned libraries. need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext $libname$shared_ext' soname_spec='$libname$release$shared_ext' finish_cmds= shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes dynamic_linker='Android linker' # -rpath works at least for libraries that are not overridden by # libraries installed in system locations. hardcode_libdir_flag_spec_CXX='$wl-rpath $wl$libdir' ;; # This must be glibc/ELF. linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no # Some binutils ld are patched to set DT_RUNPATH if test ${lt_cv_shlibpath_overrides_runpath+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_shlibpath_overrides_runpath=no save_LDFLAGS=$LDFLAGS save_libdir=$libdir eval "libdir=/foo; wl=\"$lt_prog_compiler_wl_CXX\"; \ LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec_CXX\"" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_cxx_try_link "$LINENO" then : if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null then : lt_cv_shlibpath_overrides_runpath=yes fi fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LDFLAGS=$save_LDFLAGS libdir=$save_libdir ;; esac fi shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath # This implies no fast_install, which is unacceptable. # Some rework will be needed to allow for fast_install # before this can be enabled. hardcode_into_libs=yes # Ideally, we could use ldconfig to report *all* directories which are # searched for libraries, however this is still not possible. Aside from not # being certain /sbin/ldconfig is available, command # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, # even though it is searched at run-time. Try to do the best guess by # appending ld.so.conf contents (and includes) to the search path. if test -f /etc/ld.so.conf; then lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" fi # We used to test for /lib/ld.so.1 and disable shared libraries on # powerpc, because MkLinux only supported shared libraries with the # GNU dynamic linker. Since this was broken with cross compilers, # most powerpc-linux boxes support dynamic linking these days and # people can always --disable-shared, the test was removed, and we # assume the GNU/Linux dynamic linker is in use. dynamic_linker='GNU/Linux ld.so' ;; netbsdelf*-gnu) version_type=linux need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='NetBSD ld.elf_so' ;; netbsdelf*-gnu) version_type=linux need_lib_prefix=no need_version=no library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' soname_spec='${libname}${release}${shared_ext}$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='NetBSD ld.elf_so' ;; netbsd*) version_type=sunos need_lib_prefix=no need_version=no if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' dynamic_linker='NetBSD (a.out) ld.so' else library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='NetBSD ld.elf_so' fi shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes ;; *-mlibc) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' dynamic_linker='mlibc ld.so' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; newsos6) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; *nto* | *qnx*) version_type=qnx need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes dynamic_linker='ldqnx.so' ;; openbsd*) version_type=sunos sys_lib_dlsearch_path_spec=/usr/lib need_lib_prefix=no if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then need_version=no else need_version=yes fi library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes ;; os2*) libname_spec='$name' version_type=windows shrext_cmds=.dll need_version=no need_lib_prefix=no # OS/2 can only load a DLL with a base name of 8 characters or less. soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; v=$($ECHO $release$versuffix | tr -d .-); n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); $ECHO $n$v`$shared_ext' library_names_spec='${libname}_dll.$libext' dynamic_linker='OS/2 ld.exe' shlibpath_var=BEGINLIBPATH sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec postinstall_cmds='base_file=`basename \$file`~ dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ dldir=$destdir/`dirname \$dlpath`~ test -d \$dldir || mkdir -p \$dldir~ $install_prog $dir/$dlname \$dldir/$dlname~ chmod a+x \$dldir/$dlname~ if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; fi' postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ dlpath=$dir/\$dldll~ $RM \$dlpath' ;; osf3* | osf4* | osf5*) version_type=osf need_lib_prefix=no need_version=no soname_spec='$libname$release$shared_ext$major' library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec ;; rdos*) dynamic_linker=no ;; serenity*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no dynamic_linker='SerenityOS LibELF' ;; solaris*) version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes # ldd complains unless libraries are executable postinstall_cmds='chmod +x $lib' ;; sunos4*) version_type=sunos library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes if test yes = "$with_gnu_ld"; then need_lib_prefix=no fi need_version=yes ;; sysv4 | sysv4.3*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH case $host_vendor in sni) shlibpath_overrides_runpath=no need_lib_prefix=no runpath_var=LD_RUN_PATH ;; siemens) need_lib_prefix=no ;; motorola) need_lib_prefix=no need_version=no shlibpath_overrides_runpath=no sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' ;; esac ;; sysv4*MP*) if test -d /usr/nec; then version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' soname_spec='$libname$shared_ext.$major' shlibpath_var=LD_LIBRARY_PATH fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) version_type=sco need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=yes hardcode_into_libs=yes if test yes = "$with_gnu_ld"; then sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' else sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' case $host_os in sco3.2v5*) sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" ;; esac fi sys_lib_dlsearch_path_spec='/usr/lib' ;; tpf*) # TPF is a cross-target only. Preferred cross-host = GNU/Linux. version_type=linux # correct to gnu/linux during the next big refactor need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' shlibpath_var=LD_LIBRARY_PATH shlibpath_overrides_runpath=no hardcode_into_libs=yes ;; uts4*) version_type=linux # correct to gnu/linux during the next big refactor library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' soname_spec='$libname$release$shared_ext$major' shlibpath_var=LD_LIBRARY_PATH ;; emscripten*) version_type=none need_lib_prefix=no need_version=no library_names_spec='$libname$release$shared_ext' soname_spec='$libname$release$shared_ext' finish_cmds= dynamic_linker="Emscripten linker" lt_prog_compiler_wl_CXX= lt_prog_compiler_pic_CXX= lt_prog_compiler_static_CXX= # C++ specific cases for pic, static, wl, etc. if test yes = "$GXX"; then lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_static_CXX='-static' case $host_os in aix*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static_CXX='-Bstatic' fi lt_prog_compiler_pic_CXX='-fPIC' ;; amigaos*) case $host_cpu in powerpc) # see comment about AmigaOS4 .so support lt_prog_compiler_pic_CXX='-fPIC' ;; m68k) # FIXME: we need at least 68020 code to build shared libraries, but # adding the '-m68020' flag to GCC prevents building anything better, # like '-m68040'. lt_prog_compiler_pic_CXX='-m68020 -resident32 -malways-restore-a4' ;; esac ;; beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) # PIC is the default for these OSes. ;; mingw* | windows* | cygwin* | os2* | pw32* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). # Although the cygwin gcc ignores -fPIC, still need this for old-style # (--disable-auto-import) libraries lt_prog_compiler_pic_CXX='-DDLL_EXPORT' case $host_os in os2*) lt_prog_compiler_static_CXX='$wl-static' ;; esac ;; darwin* | rhapsody*) # PIC is the default on this platform # Common symbols not allowed in MH_DYLIB files lt_prog_compiler_pic_CXX='-fno-common' ;; *djgpp*) # DJGPP does not support shared libraries at all lt_prog_compiler_pic_CXX= ;; haiku*) # PIC is the default for Haiku. # The "-static" flag exists, but is broken. lt_prog_compiler_static_CXX= ;; interix[3-9]*) # Interix 3.x gcc -fpic/-fPIC options generate broken code. # Instead, we relocate shared libraries at runtime. ;; sysv4*MP*) if test -d /usr/nec; then lt_prog_compiler_pic_CXX=-Kconform_pic fi ;; hpux*) # PIC is the default for 64-bit PA HP-UX, but not for 32-bit # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag # sets the default TLS model and affects inlining. case $host_cpu in hppa*64*) ;; *) lt_prog_compiler_pic_CXX='-fPIC' ;; esac ;; *qnx* | *nto*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic_CXX='-fPIC -shared' ;; *) lt_prog_compiler_pic_CXX='-fPIC' ;; esac else case $host_os in aix[4-9]*) # All AIX code is PIC. if test ia64 = "$host_cpu"; then # AIX 5 now supports IA64 processor lt_prog_compiler_static_CXX='-Bstatic' else lt_prog_compiler_static_CXX='-bnso -bI:/lib/syscalls.exp' fi ;; chorus*) case $cc_basename in cxch68*) # Green Hills C++ Compiler # _LT_TAGVAR(lt_prog_compiler_static, CXX)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" ;; esac ;; mingw* | windows* | cygwin* | os2* | pw32* | cegcc*) # This hack is so that the source file can tell whether it is being # built for inclusion in a dll (and should export symbols for example). lt_prog_compiler_pic_CXX='-DDLL_EXPORT' ;; dgux*) case $cc_basename in ec++*) lt_prog_compiler_pic_CXX='-KPIC' ;; ghcx*) # Green Hills C++ Compiler lt_prog_compiler_pic_CXX='-pic' ;; *) ;; esac ;; freebsd* | dragonfly* | midnightbsd*) # FreeBSD uses GNU C++ ;; hpux9* | hpux10* | hpux11*) case $cc_basename in CC*) lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_static_CXX='$wl-a ${wl}archive' if test ia64 != "$host_cpu"; then lt_prog_compiler_pic_CXX='+Z' fi ;; aCC*) lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_static_CXX='$wl-a ${wl}archive' case $host_cpu in hppa*64*|ia64*) # +Z the default ;; *) lt_prog_compiler_pic_CXX='+Z' ;; esac ;; *) ;; esac ;; interix*) # This is c89, which is MS Visual C++ (no shared libs) # Anyone wants to do a port? ;; irix5* | irix6* | nonstopux*) case $cc_basename in CC*) lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_static_CXX='-non_shared' # CC pic flag -KPIC is the default. ;; *) ;; esac ;; linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) case $cc_basename in KCC*) # KAI C++ Compiler lt_prog_compiler_wl_CXX='--backend -Wl,' lt_prog_compiler_pic_CXX='-fPIC' ;; ecpc* ) # old Intel C++ for x86_64, which still supported -KPIC. lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-KPIC' lt_prog_compiler_static_CXX='-static' ;; icpc* ) # Intel C++, used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-fPIC' lt_prog_compiler_static_CXX='-static' ;; pgCC* | pgcpp*) # Portland Group C++ compiler lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-fpic' lt_prog_compiler_static_CXX='-Bstatic' ;; cxx*) # Compaq C++ # Make sure the PIC flag is empty. It appears that all Alpha # Linux and Compaq Tru64 Unix objects are PIC. lt_prog_compiler_pic_CXX= lt_prog_compiler_static_CXX='-non_shared' ;; xlc* | xlC* | bgxl[cC]* | mpixl[cC]*) # IBM XL 8.0, 9.0 on PPC and BlueGene lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-qpic' lt_prog_compiler_static_CXX='-qstaticlink' ;; *) case `$CC -V 2>&1 | $SED 5q` in *Sun\ C*) # Sun C++ 5.9 lt_prog_compiler_pic_CXX='-KPIC' lt_prog_compiler_static_CXX='-Bstatic' lt_prog_compiler_wl_CXX='-Qoption ld ' ;; esac ;; esac ;; lynxos*) ;; m88k*) ;; mvs*) case $cc_basename in cxx*) lt_prog_compiler_pic_CXX='-W c,exportall' ;; *) ;; esac ;; netbsd* | netbsdelf*-gnu) ;; *-mlibc) ;; *qnx* | *nto*) # QNX uses GNU C++, but need to define -shared option too, otherwise # it will coredump. lt_prog_compiler_pic_CXX='-fPIC -shared' ;; osf3* | osf4* | osf5*) case $cc_basename in KCC*) lt_prog_compiler_wl_CXX='--backend -Wl,' ;; RCC*) # Rational C++ 2.4.1 lt_prog_compiler_pic_CXX='-pic' ;; cxx*) # Digital/Compaq C++ lt_prog_compiler_wl_CXX='-Wl,' # Make sure the PIC flag is empty. It appears that all Alpha # Linux and Compaq Tru64 Unix objects are PIC. lt_prog_compiler_pic_CXX= lt_prog_compiler_static_CXX='-non_shared' ;; *) ;; esac ;; psos*) ;; serenity*) ;; solaris*) case $cc_basename in CC* | sunCC*) # Sun C++ 4.2, 5.x and Centerline C++ lt_prog_compiler_pic_CXX='-KPIC' lt_prog_compiler_static_CXX='-Bstatic' lt_prog_compiler_wl_CXX='-Qoption ld ' ;; gcx*) # Green Hills C++ Compiler lt_prog_compiler_pic_CXX='-PIC' ;; *) ;; esac ;; sunos4*) case $cc_basename in CC*) # Sun C++ 4.x lt_prog_compiler_pic_CXX='-pic' lt_prog_compiler_static_CXX='-Bstatic' ;; lcc*) # Lucid lt_prog_compiler_pic_CXX='-pic' ;; *) ;; esac ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) case $cc_basename in CC*) lt_prog_compiler_wl_CXX='-Wl,' lt_prog_compiler_pic_CXX='-KPIC' lt_prog_compiler_static_CXX='-Bstatic' ;; esac ;; tandem*) case $cc_basename in NCC*) # NonStop-UX NCC 3.20 lt_prog_compiler_pic_CXX='-KPIC' ;; *) ;; esac ;; vxworks*) ;; *) lt_prog_compiler_can_build_shared_CXX=no ;; esac fi case $host_os in # For platforms that do not support PIC, -DPIC is meaningless: *djgpp*) lt_prog_compiler_pic_CXX= ;; *) lt_prog_compiler_pic_CXX="$lt_prog_compiler_pic_CXX -DPIC" ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 printf %s "checking for $compiler option to produce PIC... " >&6; } if test ${lt_cv_prog_compiler_pic_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_pic_CXX=$lt_prog_compiler_pic_CXX ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_CXX" >&5 printf "%s\n" "$lt_cv_prog_compiler_pic_CXX" >&6; } lt_prog_compiler_pic_CXX=$lt_cv_prog_compiler_pic_CXX # # Check to make sure the PIC flag actually works. # if test -n "$lt_prog_compiler_pic_CXX"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works" >&5 printf %s "checking if $compiler PIC flag $lt_prog_compiler_pic_CXX works... " >&6; } if test ${lt_cv_prog_compiler_pic_works_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_pic_works_CXX=no ac_outfile=conftest.$ac_objext echo "$lt_simple_compile_test_code" > conftest.$ac_ext lt_compiler_flag="$lt_prog_compiler_pic_CXX -DPIC" ## exclude from sc_useless_quotes_in_assignment # Insert the option either (1) after the last *FLAGS variable, or # (2) before a word containing "conftest.", or (3) at the end. # Note that $ac_compile itself does not contain backslashes and begins # with a dollar sign (not a hyphen), so the echo should work correctly. # The option is referenced via a variable to avoid confusing sed. lt_compile=`echo "$ac_compile" | $SED \ -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ -e 's:$: $lt_compiler_flag:'` (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) (eval "$lt_compile" 2>conftest.err) ac_status=$? cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 if (exit $ac_status) && test -s "$ac_outfile"; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings other than the usual output. $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_pic_works_CXX=yes fi fi $RM conftest* ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works_CXX" >&5 printf "%s\n" "$lt_cv_prog_compiler_pic_works_CXX" >&6; } if test yes = "$lt_cv_prog_compiler_pic_works_CXX"; then case $lt_prog_compiler_pic_CXX in "" | " "*) ;; *) lt_prog_compiler_pic_CXX=" $lt_prog_compiler_pic_CXX" ;; esac else lt_prog_compiler_pic_CXX= lt_prog_compiler_can_build_shared_CXX=no fi fi # # Check to make sure the static flag actually works. # wl=$lt_prog_compiler_wl_CXX eval lt_tmp_static_flag=\"$lt_prog_compiler_static_CXX\" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 printf %s "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } if test ${lt_cv_prog_compiler_static_works_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) lt_cv_prog_compiler_static_works_CXX=no save_LDFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS $lt_tmp_static_flag" echo "$lt_simple_link_test_code" > conftest.$ac_ext if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then # The linker can only warn and ignore the option if not recognized # So say no if there are warnings if test -s conftest.err; then # Append any errors to the config.log. cat conftest.err 1>&5 $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 if diff conftest.exp conftest.er2 >/dev/null; then lt_cv_prog_compiler_static_works_CXX=yes fi else lt_cv_prog_compiler_static_works_CXX=yes fi fi $RM -r conftest* LDFLAGS=$save_LDFLAGS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works_CXX" >&5 printf "%s\n" "$lt_cv_prog_compiler_static_works_CXX" >&6; } if test yes = "$lt_cv_prog_compiler_static_works_CXX"; then : else lt_prog_compiler_static_CXX= fi ='-fPIC' archive_cmds_CXX='$CC -sSIDE_MODULE=2 -shared $libobjs $deplibs $compiler_flags -o $lib' archive_expsym_cmds_CXX='$SED "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -sSIDE_MODULE=2 -shared $libobjs $deplibs $compiler_flags -o $lib -s EXPORTED_FUNCTIONS=@$output_objdir/$soname.expsym' archive_cmds_need_lc_CXX=no no_undefined_flag_CXX= ;; *) dynamic_linker=no ;; esac { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5 printf "%s\n" "$dynamic_linker" >&6; } test no = "$dynamic_linker" && can_build_shared=no variables_saved_for_relink="PATH $shlibpath_var $runpath_var" if test yes = "$GCC"; then variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" fi if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec fi if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec fi # remember unaugmented sys_lib_dlsearch_path content for libtool script decls... configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec # ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" # to be used as default LT_SYS_LIBRARY_PATH value in generated libtool configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5 printf %s "checking how to hardcode library paths into programs... " >&6; } hardcode_action_CXX= if test -n "$hardcode_libdir_flag_spec_CXX" || test -n "$runpath_var_CXX" || test yes = "$hardcode_automatic_CXX"; then # We can hardcode non-existent directories. if test no != "$hardcode_direct_CXX" && # If the only mechanism to avoid hardcoding is shlibpath_var, we # have to relink, otherwise we might link with an installed library # when we should be linking with a yet-to-be-installed one ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, CXX)" && test no != "$hardcode_minus_L_CXX"; then # Linking always hardcodes the temporary library directory. hardcode_action_CXX=relink else # We can link without hardcoding, and we can hardcode nonexisting dirs. hardcode_action_CXX=immediate fi else # We cannot hardcode anything, or else we can only hardcode existing # directories. hardcode_action_CXX=unsupported fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $hardcode_action_CXX" >&5 printf "%s\n" "$hardcode_action_CXX" >&6; } if test relink = "$hardcode_action_CXX" || test yes = "$inherit_rpath_CXX"; then # Fast installation is not supported enable_fast_install=no elif test yes = "$shlibpath_overrides_runpath" || test no = "$enable_shared"; then # Fast installation is not necessary enable_fast_install=needless fi fi # test -n "$compiler" CC=$lt_save_CC CFLAGS=$lt_save_CFLAGS LDCXX=$LD LD=$lt_save_LD GCC=$lt_save_GCC with_gnu_ld=$lt_save_with_gnu_ld lt_cv_path_LDCXX=$lt_cv_path_LD lt_cv_path_LD=$lt_save_path_LD lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld fi # test yes != "$_lt_caught_CXX_error" ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_config_commands="$ac_config_commands libtool" # Only expand once: LT_RELEASE="$SEQ66_API_MAJOR.$SEQ66_API_MINOR" LT_CURRENT="$SEQ66_API_MAJOR" LT_REVISION="$SEQ66_API_MINOR" LT_AGE="$SEQ66_API_PATCH" seq66includedir="${includedir}/seq66-${SEQ66_API_VERSION}" seq66libdir="${libdir}/seq66-${SEQ66_API_VERSION}" seq66docdir="${datadir}/doc/seq66-${SEQ66_API_VERSION}" seq66datadir="${datadir}/seq66-${SEQ66_API_VERSION}" seq66pixdir="${datadir}/pixmaps/seq66-${SEQ66_API_VERSION}" # Check whether --with-client was given. if test ${with_client+y} then : withval=$with_client; ac_client_name=$withval fi printf "%s\n" "#define CLIENT_NAME \"$ac_client_name\"" >>confdefs.h CFLAGS="${CFLAGS} -I/usr/local/include " CXXFLAGS="${CXXFLAGS} -I/usr/local/include " LDFLAGS="${LDFLAGS} -L/usr/local/lib " ac_fn_c_check_header_compile "$LINENO" "getopt.h" "ac_cv_header_getopt_h" "$ac_includes_default" if test "x$ac_cv_header_getopt_h" = xyes then : printf "%s\n" "#define HAVE_GETOPT_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "string.h" "ac_cv_header_string_h" "$ac_includes_default" if test "x$ac_cv_header_string_h" = xyes then : printf "%s\n" "#define HAVE_STRING_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "limits.h" "ac_cv_header_limits_h" "$ac_includes_default" if test "x$ac_cv_header_limits_h" = xyes then : printf "%s\n" "#define HAVE_LIMITS_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "stdarg.h" "ac_cv_header_stdarg_h" "$ac_includes_default" if test "x$ac_cv_header_stdarg_h" = xyes then : printf "%s\n" "#define HAVE_STDARG_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "stdio.h" "ac_cv_header_stdio_h" "$ac_includes_default" if test "x$ac_cv_header_stdio_h" = xyes then : printf "%s\n" "#define HAVE_STDIO_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "stddef.h" "ac_cv_header_stddef_h" "$ac_includes_default" if test "x$ac_cv_header_stddef_h" = xyes then : printf "%s\n" "#define HAVE_STDDEF_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "stdlib.h" "ac_cv_header_stdlib_h" "$ac_includes_default" if test "x$ac_cv_header_stdlib_h" = xyes then : printf "%s\n" "#define HAVE_STDLIB_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "string.h" "ac_cv_header_string_h" "$ac_includes_default" if test "x$ac_cv_header_string_h" = xyes then : printf "%s\n" "#define HAVE_STRING_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "limits.h" "ac_cv_header_limits_h" "$ac_includes_default" if test "x$ac_cv_header_limits_h" = xyes then : printf "%s\n" "#define HAVE_LIMITS_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "ctype.h" "ac_cv_header_ctype_h" "$ac_includes_default" if test "x$ac_cv_header_ctype_h" = xyes then : printf "%s\n" "#define HAVE_CTYPE_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "sys/time.h" "ac_cv_header_sys_time_h" "$ac_includes_default" if test "x$ac_cv_header_sys_time_h" = xyes then : printf "%s\n" "#define HAVE_SYS_TIME_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "time.h" "ac_cv_header_time_h" "$ac_includes_default" if test "x$ac_cv_header_time_h" = xyes then : printf "%s\n" "#define HAVE_TIME_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "errno.h" "ac_cv_header_errno_h" "$ac_includes_default" if test "x$ac_cv_header_errno_h" = xyes then : printf "%s\n" "#define HAVE_ERRNO_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "fcntl.h" "ac_cv_header_fcntl_h" "$ac_includes_default" if test "x$ac_cv_header_fcntl_h" = xyes then : printf "%s\n" "#define HAVE_FCNTL_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "sys/stat.h" "ac_cv_header_sys_stat_h" "$ac_includes_default" if test "x$ac_cv_header_sys_stat_h" = xyes then : printf "%s\n" "#define HAVE_SYS_STAT_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "sys/sysctl.h" "ac_cv_header_sys_sysctl_h" "$ac_includes_default" if test "x$ac_cv_header_sys_sysctl_h" = xyes then : printf "%s\n" "#define HAVE_SYS_SYSCTL_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "syslog.h" "ac_cv_header_syslog_h" "$ac_includes_default" if test "x$ac_cv_header_syslog_h" = xyes then : printf "%s\n" "#define HAVE_SYSLOG_H 1" >>confdefs.h fi ac_fn_c_check_header_compile "$LINENO" "unistd.h" "ac_cv_header_unistd_h" "$ac_includes_default" if test "x$ac_cv_header_unistd_h" = xyes then : printf "%s\n" "#define HAVE_UNISTD_H 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for an ANSI C-conforming const" >&5 printf %s "checking for an ANSI C-conforming const... " >&6; } if test ${ac_cv_c_const+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { #ifndef __cplusplus /* Ultrix mips cc rejects this sort of thing. */ typedef int charset[2]; const charset cs = { 0, 0 }; /* SunOS 4.1.1 cc rejects this. */ char const *const *pcpcc; char **ppc; /* NEC SVR4.0.2 mips cc rejects this. */ struct point {int x, y;}; static struct point const zero = {0,0}; /* IBM XL C 1.02.0.0 rejects this. It does not let you subtract one const X* pointer from another in an arm of an if-expression whose if-part is not a constant expression */ const char *g = "string"; pcpcc = &g + (g ? g-g : 0); /* HPUX 7.0 cc rejects these. */ ++pcpcc; ppc = (char**) pcpcc; pcpcc = (char const *const *) ppc; { /* SCO 3.2v4 cc rejects this sort of thing. */ char tx; char *t = &tx; char const *s = 0 ? (char *) 0 : (char const *) 0; *t++ = 0; if (s) return 0; } { /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */ int x[] = {25, 17}; const int *foo = &x[0]; ++foo; } { /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */ typedef const int *iptr; iptr p = 0; ++p; } { /* IBM XL C 1.02.0.0 rejects this sort of thing, saying "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */ struct s { int j; const int *ap[3]; } bx; struct s *b = &bx; b->j = 5; } { /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */ const int foo = 10; if (!foo) return 0; } return !cs[0] && !zero.x; #endif ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : ac_cv_c_const=yes else case e in #( e) ac_cv_c_const=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_const" >&5 printf "%s\n" "$ac_cv_c_const" >&6; } if test $ac_cv_c_const = no; then printf "%s\n" "#define const /**/" >>confdefs.h fi for ac_prog in doxygen do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_DOXYGEN+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$DOXYGEN"; then ac_cv_prog_DOXYGEN="$DOXYGEN" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_DOXYGEN="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi DOXYGEN=$ac_cv_prog_DOXYGEN if test -n "$DOXYGEN"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $DOXYGEN" >&5 printf "%s\n" "$DOXYGEN" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$DOXYGEN" && break done if test -z "$DOXYGEN" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Doxygen not found, no developer docs will be built" >&5 printf "%s\n" "$as_me: WARNING: Doxygen not found, no developer docs will be built" >&2;} else # Check whether --enable-docs was given. if test ${enable_docs+y} then : enableval=$enable_docs; docs=$enableval else case e in #( e) docs=no ;; esac fi if test "$docs" != no ; then ac_build_docs="yes" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Doxygen document build enabled" >&5 printf "%s\n" "Doxygen document build enabled" >&6; } for ac_prog in latex do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_LATEX+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$LATEX"; then ac_cv_prog_LATEX="$LATEX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_LATEX="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi LATEX=$ac_cv_prog_LATEX if test -n "$LATEX"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $LATEX" >&5 printf "%s\n" "$LATEX" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$LATEX" && break done if test -z "$LATEX" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: LaTeX not found, no docs will be built" >&5 printf "%s\n" "$as_me: WARNING: LaTeX not found, no docs will be built" >&2;} ac_build_docs="no" fi fi fi if test "$mingw" = "yes" ; then X_CFLAGS="" X_LIBS="" fi if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_PKG_CONFIG+y} then : printf %s "(cached) " >&6 else case e in #( e) case $PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_PKG_CONFIG="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac ;; esac fi PKG_CONFIG=$ac_cv_path_PKG_CONFIG if test -n "$PKG_CONFIG"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 printf "%s\n" "$PKG_CONFIG" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi if test -z "$ac_cv_path_PKG_CONFIG"; then ac_pt_PKG_CONFIG=$PKG_CONFIG # Extract the first word of "pkg-config", so it can be a program name with args. set dummy pkg-config; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_path_ac_pt_PKG_CONFIG+y} then : printf %s "(cached) " >&6 else case e in #( e) case $ac_pt_PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_path_ac_pt_PKG_CONFIG="$as_dir$ac_word$ac_exec_ext" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac ;; esac fi ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG if test -n "$ac_pt_PKG_CONFIG"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5 printf "%s\n" "$ac_pt_PKG_CONFIG" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ac_pt_PKG_CONFIG" = x; then PKG_CONFIG="" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac PKG_CONFIG=$ac_pt_PKG_CONFIG fi else PKG_CONFIG="$ac_cv_path_PKG_CONFIG" fi fi if test -n "$PKG_CONFIG"; then _pkg_min_version=0.9.0 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5 printf %s "checking pkg-config is at least version $_pkg_min_version... " >&6; } if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } PKG_CONFIG="" fi fi if test -z "$PKG_CONFIG"; then as_fn_error $? "pkg-config not found" "$LINENO" 5 fi pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for jack >= 0.90.0" >&5 printf %s "checking for jack >= 0.90.0... " >&6; } if test -n "$JACK_CFLAGS"; then pkg_cv_JACK_CFLAGS="$JACK_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"jack >= 0.90.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "jack >= 0.90.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_JACK_CFLAGS=`$PKG_CONFIG --cflags "jack >= 0.90.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$JACK_LIBS"; then pkg_cv_JACK_LIBS="$JACK_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"jack >= 0.90.0\""; } >&5 ($PKG_CONFIG --exists --print-errors "jack >= 0.90.0") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_JACK_LIBS=`$PKG_CONFIG --libs "jack >= 0.90.0" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then JACK_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "jack >= 0.90.0" 2>&1` else JACK_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "jack >= 0.90.0" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$JACK_PKG_ERRORS" >&5 jack_found="no" elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } jack_found="no" else JACK_CFLAGS=$pkg_cv_JACK_CFLAGS JACK_LIBS=$pkg_cv_JACK_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } jack_found="yes" fi if test "$jack_found" = "yes" -a "$ac_build_windows" = "no" ; then # Check whether --enable-jack was given. if test ${enable_jack+y} then : enableval=$enable_jack; jack=$enableval else case e in #( e) jack=yes ;; esac fi # Check whether --enable-jack-session was given. if test ${enable_jack_session+y} then : enableval=$enable_jack_session; jack_session=$enableval else case e in #( e) jack_session=yes ;; esac fi # Check whether --enable-jack-metadata was given. if test ${enable_jack_metadata+y} then : enableval=$enable_jack_metadata; jack_metadata=$enableval else case e in #( e) jack_metadata=yes ;; esac fi if test "$jack" != "no" ; then printf "%s\n" "#define JACK_SUPPORT 1" >>confdefs.h if test "$jack_session" != "no" ; then ac_fn_c_check_header_compile "$LINENO" "jack/session.h" "ac_cv_header_jack_session_h" "$ac_includes_default" if test "x$ac_cv_header_jack_session_h" = xyes then : jack_session_found="yes" else case e in #( e) jack_session_found="no" ;; esac fi if test "$jack_session_found" = "yes" ; then printf "%s\n" "#define JACK_SESSION 1" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: JACK session support enabled" >&5 printf "%s\n" "JACK session support enabled" >&6; }; fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: JACK support enabled" >&5 printf "%s\n" "JACK support enabled" >&6; } ac_fn_c_check_header_compile "$LINENO" "jack/jack.h" "ac_cv_header_jack_jack_h" "$ac_includes_default" if test "x$ac_cv_header_jack_jack_h" = xyes then : printf "%s\n" "#define HAVE_JACK_JACK_H 1" >>confdefs.h fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for jack_get_version_string in -ljack" >&5 printf %s "checking for jack_get_version_string in -ljack... " >&6; } if test ${ac_cv_lib_jack_jack_get_version_string+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ljack $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char jack_get_version_string (void); int main (void) { return jack_get_version_string (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_jack_jack_get_version_string=yes else case e in #( e) ac_cv_lib_jack_jack_get_version_string=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_jack_jack_get_version_string" >&5 printf "%s\n" "$ac_cv_lib_jack_jack_get_version_string" >&6; } if test "x$ac_cv_lib_jack_jack_get_version_string" = xyes then : jack_get_version="yes" else case e in #( e) jack_get_version="no" ;; esac fi if test "$jack_get_version" = "yes" ; then printf "%s\n" "#define JACK_GET_VERSION_STRING 1" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: JACK jack_get_version_string available" >&5 printf "%s\n" "JACK jack_get_version_string available" >&6; }; fi if test "$jack_metadata" = "yes" ; then ac_fn_c_check_header_compile "$LINENO" "jack/metadata.h" "ac_cv_header_jack_metadata_h" "$ac_includes_default" if test "x$ac_cv_header_jack_metadata_h" = xyes then : jack_metadata="yes" else case e in #( e) jack_metadata="no" ;; esac fi if test "$jack_metadata" = "yes" ; then printf "%s\n" "#define JACK_METADATA 1" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: JACK metadata support enabled" >&5 printf "%s\n" "JACK metadata support enabled" >&6; }; fi fi fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: No JACK library, disabling JACK" >&5 printf "%s\n" "$as_me: WARNING: No JACK library, disabling JACK" >&2;} fi # Check whether --enable-port-refresh was given. if test ${enable_port_refresh+y} then : enableval=$enable_port_refresh; port_refresh=$enableval else case e in #( e) port_refresh=no ;; esac fi if test "$port_refresh" != "no" ; then printf "%s\n" "#define MIDI_PORT_REFRESH 1" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: JACK port refresh test code enabled" >&5 printf "%s\n" "JACK port refresh test code enabled" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: JACK port refresh not enabled" >&5 printf "%s\n" "$as_me: JACK port refresh not enabled" >&6;} fi NSM_CFLAGS= NSM_LIBS= NSM_DEPS= pkg_failed=no { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for liblo" >&5 printf %s "checking for liblo... " >&6; } if test -n "$LIBLO_CFLAGS"; then pkg_cv_LIBLO_CFLAGS="$LIBLO_CFLAGS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"liblo\""; } >&5 ($PKG_CONFIG --exists --print-errors "liblo") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_LIBLO_CFLAGS=`$PKG_CONFIG --cflags "liblo" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test -n "$LIBLO_LIBS"; then pkg_cv_LIBLO_LIBS="$LIBLO_LIBS" elif test -n "$PKG_CONFIG"; then if test -n "$PKG_CONFIG" && \ { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"liblo\""; } >&5 ($PKG_CONFIG --exists --print-errors "liblo") 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; then pkg_cv_LIBLO_LIBS=`$PKG_CONFIG --libs "liblo" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes else pkg_failed=yes fi else pkg_failed=untried fi if test $pkg_failed = yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then LIBLO_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "liblo" 2>&1` else LIBLO_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "liblo" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$LIBLO_PKG_ERRORS" >&5 ac_liblo="no" elif test $pkg_failed = untried; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } ac_liblo="no" else LIBLO_CFLAGS=$pkg_cv_LIBLO_CFLAGS LIBLO_LIBS=$pkg_cv_LIBLO_LIBS { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } ac_liblo="yes" fi if test "$ac_liblo" = "yes" ; then # Check whether --enable-nsm was given. if test ${enable_nsm+y} then : enableval=$enable_nsm; nsm=$enableval else case e in #( e) nsm=yes ;; esac fi printf "%s\n" "#define LIBLO_SUPPORT 1" >>confdefs.h { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: LIBLO support for NSM enabled" >&5 printf "%s\n" "LIBLO support for NSM enabled" >&6; }; if test "$nsm" != "no" ; then printf "%s\n" "#define NSM_SUPPORT 1" >>confdefs.h NSM_CFLAGS="-I ../libsessions/include" NSM_LIBS="-llo -L../libsessions/src/.libs/ -lsessions" NSM_DEPS="../libsessions/src/.libs/libsessions.la" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Sessions/NSM support enabled" >&5 printf "%s\n" "Sessions/NSM support enabled" >&6; }; fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: LIBLO dev package not found, required for NSM" >&5 printf "%s\n" "$as_me: WARNING: LIBLO dev package not found, required for NSM" >&2;} fi # Check whether --enable-both was given. if test ${enable_both+y} then : enableval=$enable_both; both=$enableval else case e in #( e) both=no ;; esac fi if test "$both" != "no" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Both rtmidi Qt and command-line builds enabled" >&5 printf "%s\n" "Both rtmidi Qt and command-line builds enabled" >&6; }; if test "$mingw" != "yes" ; then ac_build_rtcli="yes" ac_build_qtmidi="yes" alsa_save_CFLAGS="$CFLAGS" alsa_save_LDFLAGS="$LDFLAGS" alsa_save_LIBS="$LIBS" alsa_found=yes alsa_topology_found=no # Check whether --with-alsa-prefix was given. if test ${with_alsa_prefix+y} then : withval=$with_alsa_prefix; alsa_prefix="$withval" else case e in #( e) alsa_prefix="" ;; esac fi # Check whether --with-alsa-inc-prefix was given. if test ${with_alsa_inc_prefix+y} then : withval=$with_alsa_inc_prefix; alsa_inc_prefix="$withval" else case e in #( e) alsa_inc_prefix="" ;; esac fi # Check whether --enable-alsa-topology was given. if test ${enable_alsa_topology+y} then : enableval=$enable_alsa_topology; enable_atopology="$enableval" else case e in #( e) enable_atopology=no ;; esac fi # Check whether --enable-alsatest was given. if test ${enable_alsatest+y} then : enableval=$enable_alsatest; enable_alsatest="$enableval" else case e in #( e) enable_alsatest=yes ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA CFLAGS" >&5 printf %s "checking for ALSA CFLAGS... " >&6; } if test "$alsa_inc_prefix" != "" ; then ALSA_CFLAGS="$ALSA_CFLAGS -I$alsa_inc_prefix" CFLAGS="$CFLAGS -I$alsa_inc_prefix" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_CFLAGS" >&5 printf "%s\n" "$ALSA_CFLAGS" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lc" >&5 printf %s "checking for dlopen in -lc... " >&6; } if test ${ac_cv_lib_c_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lc $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_c_dlopen=yes else case e in #( e) ac_cv_lib_c_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_dlopen" >&5 printf "%s\n" "$ac_cv_lib_c_dlopen" >&6; } if test "x$ac_cv_lib_c_dlopen" = xyes then : LIBDL="" else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 printf %s "checking for dlopen in -ldl... " >&6; } if test ${ac_cv_lib_dl_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dl_dlopen=yes else case e in #( e) ac_cv_lib_dl_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 printf "%s\n" "$ac_cv_lib_dl_dlopen" >&6; } if test "x$ac_cv_lib_dl_dlopen" = xyes then : LIBDL="-ldl" fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA LDFLAGS" >&5 printf %s "checking for ALSA LDFLAGS... " >&6; } if test "$alsa_prefix" != "" ; then ALSA_LIBS="$ALSA_LIBS -L$alsa_prefix" LDFLAGS="$LDFLAGS $ALSA_LIBS" fi ALSA_LIBS="$ALSA_LIBS -lasound -lm $LIBDL -lpthread" LIBS="$ALSA_LIBS $LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_LIBS" >&5 printf "%s\n" "$ALSA_LIBS" >&6; } if test "x$enable_alsatest" = "xyes"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking required libasound headers version" >&5 printf %s "checking required libasound headers version... " >&6; } min_alsa_version=0.9.0 no_alsa="" alsa_min_major_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\1/'` alsa_min_minor_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\2/'` alsa_min_micro_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\3/'` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version" >&5 printf "%s\n" "$alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version" >&6; } ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libasound headers version >= $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version ($min_alsa_version)" >&5 printf %s "checking for libasound headers version >= $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version ($min_alsa_version)... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { /* ensure backward compatibility */ #if !defined(SND_LIB_MAJOR) && defined(SOUNDLIB_VERSION_MAJOR) #define SND_LIB_MAJOR SOUNDLIB_VERSION_MAJOR #endif #if !defined(SND_LIB_MINOR) && defined(SOUNDLIB_VERSION_MINOR) #define SND_LIB_MINOR SOUNDLIB_VERSION_MINOR #endif #if !defined(SND_LIB_SUBMINOR) && defined(SOUNDLIB_VERSION_SUBMINOR) #define SND_LIB_SUBMINOR SOUNDLIB_VERSION_SUBMINOR #endif # if(SND_LIB_MAJOR > $alsa_min_major_version) exit(0); # else # if(SND_LIB_MAJOR < $alsa_min_major_version) # error not present # endif # if(SND_LIB_MINOR > $alsa_min_minor_version) exit(0); # else # if(SND_LIB_MINOR < $alsa_min_minor_version) # error not present # endif # if(SND_LIB_SUBMINOR < $alsa_min_micro_version) # error not present # endif # endif # endif exit(0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: found." >&5 printf "%s\n" "found." >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not present." >&5 printf "%s\n" "not present." >&6; } as_fn_error $? "Sufficiently new version of libasound not found." "$LINENO" 5 alsa_found=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libatopology (sound headers version > 1.1.9)" >&5 printf %s "checking for libatopology (sound headers version > 1.1.9)... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main (void) { /* ensure backward compatibility */ #if !defined(SND_LIB_VERSION) #define SND_LIB_VERSION 0 #endif #if SND_LIB_VERSION > 0x00010109 exit(0); #else # error not present #endif exit(0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } enable_atopology="yes" else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi if test "x$enable_alsatest" = "xyes"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for snd_ctl_open in -lasound" >&5 printf %s "checking for snd_ctl_open in -lasound... " >&6; } if test ${ac_cv_lib_asound_snd_ctl_open+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lasound $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char snd_ctl_open (void); int main (void) { return snd_ctl_open (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_asound_snd_ctl_open=yes else case e in #( e) ac_cv_lib_asound_snd_ctl_open=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_asound_snd_ctl_open" >&5 printf "%s\n" "$ac_cv_lib_asound_snd_ctl_open" >&6; } if test "x$ac_cv_lib_asound_snd_ctl_open" = xyes then : printf "%s\n" "#define HAVE_LIBASOUND 1" >>confdefs.h LIBS="-lasound $LIBS" else case e in #( e) as_fn_error $? "No linkable libasound was found." "$LINENO" 5 alsa_found=no ;; esac fi if test "x$enable_atopology" = "xyes"; then alsa_topology_found=yes alsa_save_LIBS2="$LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for snd_tplg_new in -latopology" >&5 printf %s "checking for snd_tplg_new in -latopology... " >&6; } if test ${ac_cv_lib_atopology_snd_tplg_new+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-latopology $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char snd_tplg_new (void); int main (void) { return snd_tplg_new (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_atopology_snd_tplg_new=yes else case e in #( e) ac_cv_lib_atopology_snd_tplg_new=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_atopology_snd_tplg_new" >&5 printf "%s\n" "$ac_cv_lib_atopology_snd_tplg_new" >&6; } if test "x$ac_cv_lib_atopology_snd_tplg_new" = xyes then : printf "%s\n" "#define HAVE_LIBATOPOLOGY 1" >>confdefs.h LIBS="-latopology $LIBS" else case e in #( e) as_fn_error $? "No linkable libatopology was found." "$LINENO" 5 alsa_topology_found=no, ;; esac fi LIBS="$alsa_save_LIBS2" fi else if test "x$enable_atopology" = "xyes"; then alsa_topology_found=yes fi fi if test "x$alsa_found" = "xyes" ; then : LIBS=`echo $LIBS | sed 's/-lasound//g'` LIBS=`echo $LIBS | sed 's/ //'` LIBS="-lasound $LIBS" fi if test "x$alsa_found" = "xno" ; then : CFLAGS="$alsa_save_CFLAGS" LDFLAGS="$alsa_save_LDFLAGS" LIBS="$alsa_save_LIBS" ALSA_CFLAGS="" ALSA_LIBS="" ALSA_TOPOLOGY_LIBS="" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA topology LDFLAGS" >&5 printf %s "checking for ALSA topology LDFLAGS... " >&6; } if test "x$alsa_topology_found" = "xyes"; then ALSA_TOPOLOGY_LIBS="$ALSA_TOPOLOGY_LIBS -latopology" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_TOPOLOGY_LIBS" >&5 printf "%s\n" "$ALSA_TOPOLOGY_LIBS" >&6; } fi fi if test "$ac_build_windows" = "no" ; then # Check whether --enable-rtmidi was given. if test ${enable_rtmidi+y} then : enableval=$enable_rtmidi; rtmidi=$enableval else case e in #( e) rtmidi=yes ;; esac fi if test "$rtmidi" != "no" -o "$both" != "no" ; then ac_build_rtmidi="yes" printf "%s\n" "#define RTMIDI_SUPPORT 1" >>confdefs.h alsa_save_CFLAGS="$CFLAGS" alsa_save_LDFLAGS="$LDFLAGS" alsa_save_LIBS="$LIBS" alsa_found=yes alsa_topology_found=no # Check whether --with-alsa-prefix was given. if test ${with_alsa_prefix+y} then : withval=$with_alsa_prefix; alsa_prefix="$withval" else case e in #( e) alsa_prefix="" ;; esac fi # Check whether --with-alsa-inc-prefix was given. if test ${with_alsa_inc_prefix+y} then : withval=$with_alsa_inc_prefix; alsa_inc_prefix="$withval" else case e in #( e) alsa_inc_prefix="" ;; esac fi # Check whether --enable-alsa-topology was given. if test ${enable_alsa_topology+y} then : enableval=$enable_alsa_topology; enable_atopology="$enableval" else case e in #( e) enable_atopology=no ;; esac fi # Check whether --enable-alsatest was given. if test ${enable_alsatest+y} then : enableval=$enable_alsatest; enable_alsatest="$enableval" else case e in #( e) enable_alsatest=yes ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA CFLAGS" >&5 printf %s "checking for ALSA CFLAGS... " >&6; } if test "$alsa_inc_prefix" != "" ; then ALSA_CFLAGS="$ALSA_CFLAGS -I$alsa_inc_prefix" CFLAGS="$CFLAGS -I$alsa_inc_prefix" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_CFLAGS" >&5 printf "%s\n" "$ALSA_CFLAGS" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lc" >&5 printf %s "checking for dlopen in -lc... " >&6; } if test ${ac_cv_lib_c_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lc $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_c_dlopen=yes else case e in #( e) ac_cv_lib_c_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_dlopen" >&5 printf "%s\n" "$ac_cv_lib_c_dlopen" >&6; } if test "x$ac_cv_lib_c_dlopen" = xyes then : LIBDL="" else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 printf %s "checking for dlopen in -ldl... " >&6; } if test ${ac_cv_lib_dl_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dl_dlopen=yes else case e in #( e) ac_cv_lib_dl_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 printf "%s\n" "$ac_cv_lib_dl_dlopen" >&6; } if test "x$ac_cv_lib_dl_dlopen" = xyes then : LIBDL="-ldl" fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA LDFLAGS" >&5 printf %s "checking for ALSA LDFLAGS... " >&6; } if test "$alsa_prefix" != "" ; then ALSA_LIBS="$ALSA_LIBS -L$alsa_prefix" LDFLAGS="$LDFLAGS $ALSA_LIBS" fi ALSA_LIBS="$ALSA_LIBS -lasound -lm $LIBDL -lpthread" LIBS="$ALSA_LIBS $LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_LIBS" >&5 printf "%s\n" "$ALSA_LIBS" >&6; } if test "x$enable_alsatest" = "xyes"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking required libasound headers version" >&5 printf %s "checking required libasound headers version... " >&6; } min_alsa_version=0.9.0 no_alsa="" alsa_min_major_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\1/'` alsa_min_minor_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\2/'` alsa_min_micro_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\3/'` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version" >&5 printf "%s\n" "$alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version" >&6; } ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libasound headers version >= $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version ($min_alsa_version)" >&5 printf %s "checking for libasound headers version >= $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version ($min_alsa_version)... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { /* ensure backward compatibility */ #if !defined(SND_LIB_MAJOR) && defined(SOUNDLIB_VERSION_MAJOR) #define SND_LIB_MAJOR SOUNDLIB_VERSION_MAJOR #endif #if !defined(SND_LIB_MINOR) && defined(SOUNDLIB_VERSION_MINOR) #define SND_LIB_MINOR SOUNDLIB_VERSION_MINOR #endif #if !defined(SND_LIB_SUBMINOR) && defined(SOUNDLIB_VERSION_SUBMINOR) #define SND_LIB_SUBMINOR SOUNDLIB_VERSION_SUBMINOR #endif # if(SND_LIB_MAJOR > $alsa_min_major_version) exit(0); # else # if(SND_LIB_MAJOR < $alsa_min_major_version) # error not present # endif # if(SND_LIB_MINOR > $alsa_min_minor_version) exit(0); # else # if(SND_LIB_MINOR < $alsa_min_minor_version) # error not present # endif # if(SND_LIB_SUBMINOR < $alsa_min_micro_version) # error not present # endif # endif # endif exit(0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: found." >&5 printf "%s\n" "found." >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not present." >&5 printf "%s\n" "not present." >&6; } as_fn_error $? "Sufficiently new version of libasound not found." "$LINENO" 5 alsa_found=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libatopology (sound headers version > 1.1.9)" >&5 printf %s "checking for libatopology (sound headers version > 1.1.9)... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main (void) { /* ensure backward compatibility */ #if !defined(SND_LIB_VERSION) #define SND_LIB_VERSION 0 #endif #if SND_LIB_VERSION > 0x00010109 exit(0); #else # error not present #endif exit(0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } enable_atopology="yes" else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi if test "x$enable_alsatest" = "xyes"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for snd_ctl_open in -lasound" >&5 printf %s "checking for snd_ctl_open in -lasound... " >&6; } if test ${ac_cv_lib_asound_snd_ctl_open+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lasound $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char snd_ctl_open (void); int main (void) { return snd_ctl_open (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_asound_snd_ctl_open=yes else case e in #( e) ac_cv_lib_asound_snd_ctl_open=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_asound_snd_ctl_open" >&5 printf "%s\n" "$ac_cv_lib_asound_snd_ctl_open" >&6; } if test "x$ac_cv_lib_asound_snd_ctl_open" = xyes then : printf "%s\n" "#define HAVE_LIBASOUND 1" >>confdefs.h LIBS="-lasound $LIBS" else case e in #( e) as_fn_error $? "No linkable libasound was found." "$LINENO" 5 alsa_found=no ;; esac fi if test "x$enable_atopology" = "xyes"; then alsa_topology_found=yes alsa_save_LIBS2="$LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for snd_tplg_new in -latopology" >&5 printf %s "checking for snd_tplg_new in -latopology... " >&6; } if test ${ac_cv_lib_atopology_snd_tplg_new+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-latopology $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char snd_tplg_new (void); int main (void) { return snd_tplg_new (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_atopology_snd_tplg_new=yes else case e in #( e) ac_cv_lib_atopology_snd_tplg_new=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_atopology_snd_tplg_new" >&5 printf "%s\n" "$ac_cv_lib_atopology_snd_tplg_new" >&6; } if test "x$ac_cv_lib_atopology_snd_tplg_new" = xyes then : printf "%s\n" "#define HAVE_LIBATOPOLOGY 1" >>confdefs.h LIBS="-latopology $LIBS" else case e in #( e) as_fn_error $? "No linkable libatopology was found." "$LINENO" 5 alsa_topology_found=no, ;; esac fi LIBS="$alsa_save_LIBS2" fi else if test "x$enable_atopology" = "xyes"; then alsa_topology_found=yes fi fi if test "x$alsa_found" = "xyes" ; then : LIBS=`echo $LIBS | sed 's/-lasound//g'` LIBS=`echo $LIBS | sed 's/ //'` LIBS="-lasound $LIBS" fi if test "x$alsa_found" = "xno" ; then : CFLAGS="$alsa_save_CFLAGS" LDFLAGS="$alsa_save_LDFLAGS" LIBS="$alsa_save_LIBS" ALSA_CFLAGS="" ALSA_LIBS="" ALSA_TOPOLOGY_LIBS="" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA topology LDFLAGS" >&5 printf %s "checking for ALSA topology LDFLAGS... " >&6; } if test "x$alsa_topology_found" = "xyes"; then ALSA_TOPOLOGY_LIBS="$ALSA_TOPOLOGY_LIBS -latopology" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_TOPOLOGY_LIBS" >&5 printf "%s\n" "$ALSA_TOPOLOGY_LIBS" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: rtmidi engine build enabled" >&5 printf "%s\n" "rtmidi engine build enabled" >&6; }; fi fi # Check whether --enable-cli was given. if test ${enable_cli+y} then : enableval=$enable_cli; cli=$enableval else case e in #( e) cli=no ;; esac fi if test "$cli" != "no" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: rtmidi command-line build enabled" >&5 printf "%s\n" "rtmidi command-line build enabled" >&6; }; printf "%s\n" "#define APP_CLI 1" >>confdefs.h ac_app_name="seq66cli" ac_app_type="cli" ac_config_name="seq66cli" if test "$mingw" != "yes" ; then ac_build_rtcli="yes" ac_build_qtmidi="no" printf "%s\n" "#define RTMIDI_SUPPORT 1" >>confdefs.h alsa_save_CFLAGS="$CFLAGS" alsa_save_LDFLAGS="$LDFLAGS" alsa_save_LIBS="$LIBS" alsa_found=yes alsa_topology_found=no # Check whether --with-alsa-prefix was given. if test ${with_alsa_prefix+y} then : withval=$with_alsa_prefix; alsa_prefix="$withval" else case e in #( e) alsa_prefix="" ;; esac fi # Check whether --with-alsa-inc-prefix was given. if test ${with_alsa_inc_prefix+y} then : withval=$with_alsa_inc_prefix; alsa_inc_prefix="$withval" else case e in #( e) alsa_inc_prefix="" ;; esac fi # Check whether --enable-alsa-topology was given. if test ${enable_alsa_topology+y} then : enableval=$enable_alsa_topology; enable_atopology="$enableval" else case e in #( e) enable_atopology=no ;; esac fi # Check whether --enable-alsatest was given. if test ${enable_alsatest+y} then : enableval=$enable_alsatest; enable_alsatest="$enableval" else case e in #( e) enable_alsatest=yes ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA CFLAGS" >&5 printf %s "checking for ALSA CFLAGS... " >&6; } if test "$alsa_inc_prefix" != "" ; then ALSA_CFLAGS="$ALSA_CFLAGS -I$alsa_inc_prefix" CFLAGS="$CFLAGS -I$alsa_inc_prefix" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_CFLAGS" >&5 printf "%s\n" "$ALSA_CFLAGS" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lc" >&5 printf %s "checking for dlopen in -lc... " >&6; } if test ${ac_cv_lib_c_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lc $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_c_dlopen=yes else case e in #( e) ac_cv_lib_c_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_dlopen" >&5 printf "%s\n" "$ac_cv_lib_c_dlopen" >&6; } if test "x$ac_cv_lib_c_dlopen" = xyes then : LIBDL="" else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 printf %s "checking for dlopen in -ldl... " >&6; } if test ${ac_cv_lib_dl_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dl_dlopen=yes else case e in #( e) ac_cv_lib_dl_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 printf "%s\n" "$ac_cv_lib_dl_dlopen" >&6; } if test "x$ac_cv_lib_dl_dlopen" = xyes then : LIBDL="-ldl" fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA LDFLAGS" >&5 printf %s "checking for ALSA LDFLAGS... " >&6; } if test "$alsa_prefix" != "" ; then ALSA_LIBS="$ALSA_LIBS -L$alsa_prefix" LDFLAGS="$LDFLAGS $ALSA_LIBS" fi ALSA_LIBS="$ALSA_LIBS -lasound -lm $LIBDL -lpthread" LIBS="$ALSA_LIBS $LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_LIBS" >&5 printf "%s\n" "$ALSA_LIBS" >&6; } if test "x$enable_alsatest" = "xyes"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking required libasound headers version" >&5 printf %s "checking required libasound headers version... " >&6; } min_alsa_version=0.9.0 no_alsa="" alsa_min_major_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\1/'` alsa_min_minor_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\2/'` alsa_min_micro_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\3/'` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version" >&5 printf "%s\n" "$alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version" >&6; } ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libasound headers version >= $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version ($min_alsa_version)" >&5 printf %s "checking for libasound headers version >= $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version ($min_alsa_version)... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { /* ensure backward compatibility */ #if !defined(SND_LIB_MAJOR) && defined(SOUNDLIB_VERSION_MAJOR) #define SND_LIB_MAJOR SOUNDLIB_VERSION_MAJOR #endif #if !defined(SND_LIB_MINOR) && defined(SOUNDLIB_VERSION_MINOR) #define SND_LIB_MINOR SOUNDLIB_VERSION_MINOR #endif #if !defined(SND_LIB_SUBMINOR) && defined(SOUNDLIB_VERSION_SUBMINOR) #define SND_LIB_SUBMINOR SOUNDLIB_VERSION_SUBMINOR #endif # if(SND_LIB_MAJOR > $alsa_min_major_version) exit(0); # else # if(SND_LIB_MAJOR < $alsa_min_major_version) # error not present # endif # if(SND_LIB_MINOR > $alsa_min_minor_version) exit(0); # else # if(SND_LIB_MINOR < $alsa_min_minor_version) # error not present # endif # if(SND_LIB_SUBMINOR < $alsa_min_micro_version) # error not present # endif # endif # endif exit(0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: found." >&5 printf "%s\n" "found." >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not present." >&5 printf "%s\n" "not present." >&6; } as_fn_error $? "Sufficiently new version of libasound not found." "$LINENO" 5 alsa_found=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libatopology (sound headers version > 1.1.9)" >&5 printf %s "checking for libatopology (sound headers version > 1.1.9)... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main (void) { /* ensure backward compatibility */ #if !defined(SND_LIB_VERSION) #define SND_LIB_VERSION 0 #endif #if SND_LIB_VERSION > 0x00010109 exit(0); #else # error not present #endif exit(0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } enable_atopology="yes" else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi if test "x$enable_alsatest" = "xyes"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for snd_ctl_open in -lasound" >&5 printf %s "checking for snd_ctl_open in -lasound... " >&6; } if test ${ac_cv_lib_asound_snd_ctl_open+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lasound $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char snd_ctl_open (void); int main (void) { return snd_ctl_open (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_asound_snd_ctl_open=yes else case e in #( e) ac_cv_lib_asound_snd_ctl_open=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_asound_snd_ctl_open" >&5 printf "%s\n" "$ac_cv_lib_asound_snd_ctl_open" >&6; } if test "x$ac_cv_lib_asound_snd_ctl_open" = xyes then : printf "%s\n" "#define HAVE_LIBASOUND 1" >>confdefs.h LIBS="-lasound $LIBS" else case e in #( e) as_fn_error $? "No linkable libasound was found." "$LINENO" 5 alsa_found=no ;; esac fi if test "x$enable_atopology" = "xyes"; then alsa_topology_found=yes alsa_save_LIBS2="$LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for snd_tplg_new in -latopology" >&5 printf %s "checking for snd_tplg_new in -latopology... " >&6; } if test ${ac_cv_lib_atopology_snd_tplg_new+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-latopology $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char snd_tplg_new (void); int main (void) { return snd_tplg_new (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_atopology_snd_tplg_new=yes else case e in #( e) ac_cv_lib_atopology_snd_tplg_new=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_atopology_snd_tplg_new" >&5 printf "%s\n" "$ac_cv_lib_atopology_snd_tplg_new" >&6; } if test "x$ac_cv_lib_atopology_snd_tplg_new" = xyes then : printf "%s\n" "#define HAVE_LIBATOPOLOGY 1" >>confdefs.h LIBS="-latopology $LIBS" else case e in #( e) as_fn_error $? "No linkable libatopology was found." "$LINENO" 5 alsa_topology_found=no, ;; esac fi LIBS="$alsa_save_LIBS2" fi else if test "x$enable_atopology" = "xyes"; then alsa_topology_found=yes fi fi if test "x$alsa_found" = "xyes" ; then : LIBS=`echo $LIBS | sed 's/-lasound//g'` LIBS=`echo $LIBS | sed 's/ //'` LIBS="-lasound $LIBS" fi if test "x$alsa_found" = "xno" ; then : CFLAGS="$alsa_save_CFLAGS" LDFLAGS="$alsa_save_LDFLAGS" LIBS="$alsa_save_LIBS" ALSA_CFLAGS="" ALSA_LIBS="" ALSA_TOPOLOGY_LIBS="" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA topology LDFLAGS" >&5 printf %s "checking for ALSA topology LDFLAGS... " >&6; } if test "x$alsa_topology_found" = "xyes"; then ALSA_TOPOLOGY_LIBS="$ALSA_TOPOLOGY_LIBS -latopology" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_TOPOLOGY_LIBS" >&5 printf "%s\n" "$ALSA_TOPOLOGY_LIBS" >&6; } fi ac_build_qt="no" else # Check whether --enable-qt was given. if test ${enable_qt+y} then : enableval=$enable_qt; ac_build_qt=$enableval else case e in #( e) ac_build_qt=yes ;; esac fi fi if test "$ac_build_qt" != "no" ; then ac_app_name="qseq66" ac_app_type="qt" if test "$ac_build_windows" != "yes" ; then alsa_save_CFLAGS="$CFLAGS" alsa_save_LDFLAGS="$LDFLAGS" alsa_save_LIBS="$LIBS" alsa_found=yes alsa_topology_found=no # Check whether --with-alsa-prefix was given. if test ${with_alsa_prefix+y} then : withval=$with_alsa_prefix; alsa_prefix="$withval" else case e in #( e) alsa_prefix="" ;; esac fi # Check whether --with-alsa-inc-prefix was given. if test ${with_alsa_inc_prefix+y} then : withval=$with_alsa_inc_prefix; alsa_inc_prefix="$withval" else case e in #( e) alsa_inc_prefix="" ;; esac fi # Check whether --enable-alsa-topology was given. if test ${enable_alsa_topology+y} then : enableval=$enable_alsa_topology; enable_atopology="$enableval" else case e in #( e) enable_atopology=no ;; esac fi # Check whether --enable-alsatest was given. if test ${enable_alsatest+y} then : enableval=$enable_alsatest; enable_alsatest="$enableval" else case e in #( e) enable_alsatest=yes ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA CFLAGS" >&5 printf %s "checking for ALSA CFLAGS... " >&6; } if test "$alsa_inc_prefix" != "" ; then ALSA_CFLAGS="$ALSA_CFLAGS -I$alsa_inc_prefix" CFLAGS="$CFLAGS -I$alsa_inc_prefix" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_CFLAGS" >&5 printf "%s\n" "$ALSA_CFLAGS" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lc" >&5 printf %s "checking for dlopen in -lc... " >&6; } if test ${ac_cv_lib_c_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lc $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_c_dlopen=yes else case e in #( e) ac_cv_lib_c_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_dlopen" >&5 printf "%s\n" "$ac_cv_lib_c_dlopen" >&6; } if test "x$ac_cv_lib_c_dlopen" = xyes then : LIBDL="" else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 printf %s "checking for dlopen in -ldl... " >&6; } if test ${ac_cv_lib_dl_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dl_dlopen=yes else case e in #( e) ac_cv_lib_dl_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 printf "%s\n" "$ac_cv_lib_dl_dlopen" >&6; } if test "x$ac_cv_lib_dl_dlopen" = xyes then : LIBDL="-ldl" fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA LDFLAGS" >&5 printf %s "checking for ALSA LDFLAGS... " >&6; } if test "$alsa_prefix" != "" ; then ALSA_LIBS="$ALSA_LIBS -L$alsa_prefix" LDFLAGS="$LDFLAGS $ALSA_LIBS" fi ALSA_LIBS="$ALSA_LIBS -lasound -lm $LIBDL -lpthread" LIBS="$ALSA_LIBS $LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_LIBS" >&5 printf "%s\n" "$ALSA_LIBS" >&6; } if test "x$enable_alsatest" = "xyes"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking required libasound headers version" >&5 printf %s "checking required libasound headers version... " >&6; } min_alsa_version=0.9.0 no_alsa="" alsa_min_major_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\1/'` alsa_min_minor_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\2/'` alsa_min_micro_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\3/'` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version" >&5 printf "%s\n" "$alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version" >&6; } ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libasound headers version >= $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version ($min_alsa_version)" >&5 printf %s "checking for libasound headers version >= $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version ($min_alsa_version)... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { /* ensure backward compatibility */ #if !defined(SND_LIB_MAJOR) && defined(SOUNDLIB_VERSION_MAJOR) #define SND_LIB_MAJOR SOUNDLIB_VERSION_MAJOR #endif #if !defined(SND_LIB_MINOR) && defined(SOUNDLIB_VERSION_MINOR) #define SND_LIB_MINOR SOUNDLIB_VERSION_MINOR #endif #if !defined(SND_LIB_SUBMINOR) && defined(SOUNDLIB_VERSION_SUBMINOR) #define SND_LIB_SUBMINOR SOUNDLIB_VERSION_SUBMINOR #endif # if(SND_LIB_MAJOR > $alsa_min_major_version) exit(0); # else # if(SND_LIB_MAJOR < $alsa_min_major_version) # error not present # endif # if(SND_LIB_MINOR > $alsa_min_minor_version) exit(0); # else # if(SND_LIB_MINOR < $alsa_min_minor_version) # error not present # endif # if(SND_LIB_SUBMINOR < $alsa_min_micro_version) # error not present # endif # endif # endif exit(0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: found." >&5 printf "%s\n" "found." >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not present." >&5 printf "%s\n" "not present." >&6; } as_fn_error $? "Sufficiently new version of libasound not found." "$LINENO" 5 alsa_found=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libatopology (sound headers version > 1.1.9)" >&5 printf %s "checking for libatopology (sound headers version > 1.1.9)... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main (void) { /* ensure backward compatibility */ #if !defined(SND_LIB_VERSION) #define SND_LIB_VERSION 0 #endif #if SND_LIB_VERSION > 0x00010109 exit(0); #else # error not present #endif exit(0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } enable_atopology="yes" else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi if test "x$enable_alsatest" = "xyes"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for snd_ctl_open in -lasound" >&5 printf %s "checking for snd_ctl_open in -lasound... " >&6; } if test ${ac_cv_lib_asound_snd_ctl_open+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lasound $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char snd_ctl_open (void); int main (void) { return snd_ctl_open (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_asound_snd_ctl_open=yes else case e in #( e) ac_cv_lib_asound_snd_ctl_open=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_asound_snd_ctl_open" >&5 printf "%s\n" "$ac_cv_lib_asound_snd_ctl_open" >&6; } if test "x$ac_cv_lib_asound_snd_ctl_open" = xyes then : printf "%s\n" "#define HAVE_LIBASOUND 1" >>confdefs.h LIBS="-lasound $LIBS" else case e in #( e) as_fn_error $? "No linkable libasound was found." "$LINENO" 5 alsa_found=no ;; esac fi if test "x$enable_atopology" = "xyes"; then alsa_topology_found=yes alsa_save_LIBS2="$LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for snd_tplg_new in -latopology" >&5 printf %s "checking for snd_tplg_new in -latopology... " >&6; } if test ${ac_cv_lib_atopology_snd_tplg_new+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-latopology $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char snd_tplg_new (void); int main (void) { return snd_tplg_new (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_atopology_snd_tplg_new=yes else case e in #( e) ac_cv_lib_atopology_snd_tplg_new=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_atopology_snd_tplg_new" >&5 printf "%s\n" "$ac_cv_lib_atopology_snd_tplg_new" >&6; } if test "x$ac_cv_lib_atopology_snd_tplg_new" = xyes then : printf "%s\n" "#define HAVE_LIBATOPOLOGY 1" >>confdefs.h LIBS="-latopology $LIBS" else case e in #( e) as_fn_error $? "No linkable libatopology was found." "$LINENO" 5 alsa_topology_found=no, ;; esac fi LIBS="$alsa_save_LIBS2" fi else if test "x$enable_atopology" = "xyes"; then alsa_topology_found=yes fi fi if test "x$alsa_found" = "xyes" ; then : LIBS=`echo $LIBS | sed 's/-lasound//g'` LIBS=`echo $LIBS | sed 's/ //'` LIBS="-lasound $LIBS" fi if test "x$alsa_found" = "xno" ; then : CFLAGS="$alsa_save_CFLAGS" LDFLAGS="$alsa_save_LDFLAGS" LIBS="$alsa_save_LIBS" ALSA_CFLAGS="" ALSA_LIBS="" ALSA_TOPOLOGY_LIBS="" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA topology LDFLAGS" >&5 printf %s "checking for ALSA topology LDFLAGS... " >&6; } if test "x$alsa_topology_found" = "xyes"; then ALSA_TOPOLOGY_LIBS="$ALSA_TOPOLOGY_LIBS -latopology" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_TOPOLOGY_LIBS" >&5 printf "%s\n" "$ALSA_TOPOLOGY_LIBS" >&6; } fi if test "$ac_build_windows" = "yes" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for X" >&5 printf %s "checking for X... " >&6; } # Check whether --with-x was given. if test ${with_x+y} then : withval=$with_x; fi # $have_x is 'yes', 'no', 'disabled', or empty when we do not yet know. if test "x$with_x" = xno; then # The user explicitly disabled X. have_x=disabled else case $x_includes,$x_libraries in #( *\'*) as_fn_error $? "cannot use X directory names containing '" "$LINENO" 5;; #( *,NONE | NONE,*) if test ${ac_cv_have_x+y} then : printf %s "(cached) " >&6 else case e in #( e) # One or both of the vars are not set, and there is no cached value. ac_x_includes=no ac_x_libraries=no # Do we need to do anything special at all? ac_save_LIBS=$LIBS LIBS="-lX11 $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { XrmInitialize () ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : # We can compile and link X programs with no special options. ac_x_includes= ac_x_libraries= fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS="$ac_save_LIBS" # If that didn't work, only try xmkmf and file system searches # for native compilation. if test x"$ac_x_includes" = xno && test "$cross_compiling" = no then : rm -f -r conftest.dir if mkdir conftest.dir; then cd conftest.dir cat >Imakefile <<'_ACEOF' incroot: @echo incroot='${INCROOT}' usrlibdir: @echo usrlibdir='${USRLIBDIR}' libdir: @echo libdir='${LIBDIR}' _ACEOF if (export CC; ${XMKMF-xmkmf}) >/dev/null 2>/dev/null && test -f Makefile; then # GNU make sometimes prints "make[1]: Entering ...", which would confuse us. for ac_var in incroot usrlibdir libdir; do eval "ac_im_$ac_var=\`\${MAKE-make} $ac_var 2>/dev/null | sed -n 's/^$ac_var=//p'\`" done # Open Windows xmkmf reportedly sets LIBDIR instead of USRLIBDIR. for ac_extension in a so sl dylib la dll; do if test ! -f "$ac_im_usrlibdir/libX11.$ac_extension" && test -f "$ac_im_libdir/libX11.$ac_extension"; then ac_im_usrlibdir=$ac_im_libdir; break fi done # Screen out bogus values from the imake configuration. They are # bogus both because they are the default anyway, and because # using them would break gcc on systems where it needs fixed includes. case $ac_im_incroot in /usr/include) ac_x_includes= ;; *) test -f "$ac_im_incroot/X11/Xos.h" && ac_x_includes=$ac_im_incroot;; esac case $ac_im_usrlibdir in /usr/lib | /usr/lib64 | /lib | /lib64) ;; *) test -d "$ac_im_usrlibdir" && ac_x_libraries=$ac_im_usrlibdir ;; esac fi cd .. rm -f -r conftest.dir fi # Standard set of common directories for X headers. # Check X11 before X11Rn because it is often a symlink to the current release. ac_x_header_dirs=' /usr/X11/include /usr/X11R7/include /usr/X11R6/include /usr/X11R5/include /usr/X11R4/include /usr/include/X11 /usr/include/X11R7 /usr/include/X11R6 /usr/include/X11R5 /usr/include/X11R4 /usr/local/X11/include /usr/local/X11R7/include /usr/local/X11R6/include /usr/local/X11R5/include /usr/local/X11R4/include /usr/local/include/X11 /usr/local/include/X11R7 /usr/local/include/X11R6 /usr/local/include/X11R5 /usr/local/include/X11R4 /opt/X11/include /usr/X386/include /usr/x386/include /usr/XFree86/include/X11 /usr/include /usr/local/include /usr/unsupported/include /usr/athena/include /usr/local/x11r5/include /usr/lpp/Xamples/include /usr/openwin/include /usr/openwin/share/include' if test "$ac_x_includes" = no; then # Guess where to find include files, by looking for Xlib.h. # First, try using that file with no special directory specified. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include _ACEOF if ac_fn_c_try_cpp "$LINENO" then : # We can compile using X headers with no special include directory. ac_x_includes= else case e in #( e) for ac_dir in $ac_x_header_dirs; do if test -r "$ac_dir/X11/Xlib.h"; then ac_x_includes=$ac_dir break fi done ;; esac fi rm -f conftest.err conftest.i conftest.$ac_ext fi # $ac_x_includes = no if test "$ac_x_libraries" = no; then # Check for the libraries. # See if we find them without any special options. # Don't add to $LIBS permanently. ac_save_LIBS=$LIBS LIBS="-lX11 $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { XrmInitialize () ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : LIBS=$ac_save_LIBS # We can link X programs with no special library path. ac_x_libraries= else case e in #( e) LIBS=$ac_save_LIBS for ac_dir in `printf "%s\n" "$ac_x_includes $ac_x_header_dirs" | sed s/include/lib/g` do # Don't even attempt the hair of trying to link an X program! for ac_extension in a so sl dylib la dll; do if test -r "$ac_dir/libX11.$ac_extension"; then ac_x_libraries=$ac_dir break 2 fi done done ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi # $ac_x_libraries = no fi # Record the results. case $ac_x_includes,$ac_x_libraries in #( no,* | *,no | *\'*) : # Didn't find X, or a directory has "'" in its name. ac_cv_have_x="have_x=no" ;; #( *) : # Record where we found X for the cache. ac_cv_have_x="have_x=yes\ ac_x_includes='$ac_x_includes'\ ac_x_libraries='$ac_x_libraries'" ;; esac ;; esac fi ;; #( *) have_x=yes;; esac eval "$ac_cv_have_x" fi # $with_x != no if test "$have_x" != yes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_x" >&5 printf "%s\n" "$have_x" >&6; } no_x=yes else # If each of the values was on the command line, it overrides each guess. test "x$x_includes" = xNONE && x_includes=$ac_x_includes test "x$x_libraries" = xNONE && x_libraries=$ac_x_libraries # Update the cache value to reflect the command line values. ac_cv_have_x="have_x=yes\ ac_x_includes='$x_includes'\ ac_x_libraries='$x_libraries'" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: libraries $x_libraries, headers $x_includes" >&5 printf "%s\n" "libraries $x_libraries, headers $x_includes" >&6; } fi if test "$no_x" = yes; then # Not all programs may use this symbol, but it does not hurt to define it. printf "%s\n" "#define X_DISPLAY_MISSING 1" >>confdefs.h X_CFLAGS= X_PRE_LIBS= X_LIBS= X_EXTRA_LIBS= else if test -n "$x_includes"; then X_CFLAGS="$X_CFLAGS -I$x_includes" fi # It would also be nice to do this for all -L options, not just this one. if test -n "$x_libraries"; then X_LIBS="$X_LIBS -L$x_libraries" # For Solaris; some versions of Sun CC require a space after -R and # others require no space. Words are not sufficient . . . . { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether -R must be followed by a space" >&5 printf %s "checking whether -R must be followed by a space... " >&6; } ac_xsave_LIBS=$LIBS; LIBS="$LIBS -R$x_libraries" ac_xsave_c_werror_flag=$ac_c_werror_flag ac_c_werror_flag=yes cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } X_LIBS="$X_LIBS -R$x_libraries" else case e in #( e) LIBS="$ac_xsave_LIBS -R $x_libraries" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main (void) { ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } X_LIBS="$X_LIBS -R $x_libraries" else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: neither works" >&5 printf "%s\n" "neither works" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ac_c_werror_flag=$ac_xsave_c_werror_flag LIBS=$ac_xsave_LIBS fi # Check for system-dependent libraries X programs must link with. # Do this before checking for the system-independent R6 libraries # (-lICE), since we may need -lsocket or whatever for X linking. if test "$ISC" = yes; then X_EXTRA_LIBS="$X_EXTRA_LIBS -lnsl_s -linet" else # Martyn Johnson says this is needed for Ultrix, if the X # libraries were built with DECnet support. And Karl Berry says # the Alpha needs dnet_stub (dnet does not exist). ac_xsave_LIBS="$LIBS"; LIBS="$LIBS $X_LIBS -lX11" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char XOpenDisplay (void); int main (void) { return XOpenDisplay (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dnet_ntoa in -ldnet" >&5 printf %s "checking for dnet_ntoa in -ldnet... " >&6; } if test ${ac_cv_lib_dnet_dnet_ntoa+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldnet $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dnet_ntoa (void); int main (void) { return dnet_ntoa (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dnet_dnet_ntoa=yes else case e in #( e) ac_cv_lib_dnet_dnet_ntoa=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dnet_dnet_ntoa" >&5 printf "%s\n" "$ac_cv_lib_dnet_dnet_ntoa" >&6; } if test "x$ac_cv_lib_dnet_dnet_ntoa" = xyes then : X_EXTRA_LIBS="$X_EXTRA_LIBS -ldnet" fi if test $ac_cv_lib_dnet_dnet_ntoa = no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dnet_ntoa in -ldnet_stub" >&5 printf %s "checking for dnet_ntoa in -ldnet_stub... " >&6; } if test ${ac_cv_lib_dnet_stub_dnet_ntoa+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldnet_stub $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dnet_ntoa (void); int main (void) { return dnet_ntoa (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dnet_stub_dnet_ntoa=yes else case e in #( e) ac_cv_lib_dnet_stub_dnet_ntoa=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dnet_stub_dnet_ntoa" >&5 printf "%s\n" "$ac_cv_lib_dnet_stub_dnet_ntoa" >&6; } if test "x$ac_cv_lib_dnet_stub_dnet_ntoa" = xyes then : X_EXTRA_LIBS="$X_EXTRA_LIBS -ldnet_stub" fi fi ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS="$ac_xsave_LIBS" # msh@cis.ufl.edu says -lnsl (and -lsocket) are needed for his 386/AT, # to get the SysV transport functions. # Chad R. Larson says the Pyramis MIS-ES running DC/OSx (SVR4) # needs -lnsl. # The nsl library prevents programs from opening the X display # on Irix 5.2, according to T.E. Dickey. # The functions gethostbyname, getservbyname, and inet_addr are # in -lbsd on LynxOS 3.0.1/i386, according to Lars Hecking. ac_fn_c_check_func "$LINENO" "gethostbyname" "ac_cv_func_gethostbyname" if test "x$ac_cv_func_gethostbyname" = xyes then : fi if test $ac_cv_func_gethostbyname = no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for gethostbyname in -lnsl" >&5 printf %s "checking for gethostbyname in -lnsl... " >&6; } if test ${ac_cv_lib_nsl_gethostbyname+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lnsl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char gethostbyname (void); int main (void) { return gethostbyname (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_nsl_gethostbyname=yes else case e in #( e) ac_cv_lib_nsl_gethostbyname=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nsl_gethostbyname" >&5 printf "%s\n" "$ac_cv_lib_nsl_gethostbyname" >&6; } if test "x$ac_cv_lib_nsl_gethostbyname" = xyes then : X_EXTRA_LIBS="$X_EXTRA_LIBS -lnsl" fi if test $ac_cv_lib_nsl_gethostbyname = no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for gethostbyname in -lbsd" >&5 printf %s "checking for gethostbyname in -lbsd... " >&6; } if test ${ac_cv_lib_bsd_gethostbyname+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lbsd $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char gethostbyname (void); int main (void) { return gethostbyname (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_bsd_gethostbyname=yes else case e in #( e) ac_cv_lib_bsd_gethostbyname=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bsd_gethostbyname" >&5 printf "%s\n" "$ac_cv_lib_bsd_gethostbyname" >&6; } if test "x$ac_cv_lib_bsd_gethostbyname" = xyes then : X_EXTRA_LIBS="$X_EXTRA_LIBS -lbsd" fi fi fi # lieder@skyler.mavd.honeywell.com says without -lsocket, # socket/setsockopt and other routines are undefined under SCO ODT # 2.0. But -lsocket is broken on IRIX 5.2 (and is not necessary # on later versions), says Simon Leinen: it contains gethostby* # variants that don't use the name server (or something). -lsocket # must be given before -lnsl if both are needed. We assume that # if connect needs -lnsl, so does gethostbyname. ac_fn_c_check_func "$LINENO" "connect" "ac_cv_func_connect" if test "x$ac_cv_func_connect" = xyes then : fi if test $ac_cv_func_connect = no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for connect in -lsocket" >&5 printf %s "checking for connect in -lsocket... " >&6; } if test ${ac_cv_lib_socket_connect+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lsocket $X_EXTRA_LIBS $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char connect (void); int main (void) { return connect (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_socket_connect=yes else case e in #( e) ac_cv_lib_socket_connect=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_connect" >&5 printf "%s\n" "$ac_cv_lib_socket_connect" >&6; } if test "x$ac_cv_lib_socket_connect" = xyes then : X_EXTRA_LIBS="-lsocket $X_EXTRA_LIBS" fi fi # Guillermo Gomez says -lposix is necessary on A/UX. ac_fn_c_check_func "$LINENO" "remove" "ac_cv_func_remove" if test "x$ac_cv_func_remove" = xyes then : fi if test $ac_cv_func_remove = no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for remove in -lposix" >&5 printf %s "checking for remove in -lposix... " >&6; } if test ${ac_cv_lib_posix_remove+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lposix $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char remove (void); int main (void) { return remove (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_posix_remove=yes else case e in #( e) ac_cv_lib_posix_remove=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_posix_remove" >&5 printf "%s\n" "$ac_cv_lib_posix_remove" >&6; } if test "x$ac_cv_lib_posix_remove" = xyes then : X_EXTRA_LIBS="$X_EXTRA_LIBS -lposix" fi fi # BSDI BSD/OS 2.1 needs -lipc for XOpenDisplay. ac_fn_c_check_func "$LINENO" "shmat" "ac_cv_func_shmat" if test "x$ac_cv_func_shmat" = xyes then : fi if test $ac_cv_func_shmat = no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for shmat in -lipc" >&5 printf %s "checking for shmat in -lipc... " >&6; } if test ${ac_cv_lib_ipc_shmat+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lipc $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char shmat (void); int main (void) { return shmat (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_ipc_shmat=yes else case e in #( e) ac_cv_lib_ipc_shmat=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ipc_shmat" >&5 printf "%s\n" "$ac_cv_lib_ipc_shmat" >&6; } if test "x$ac_cv_lib_ipc_shmat" = xyes then : X_EXTRA_LIBS="$X_EXTRA_LIBS -lipc" fi fi fi # Check for libraries that X11R6 Xt/Xaw programs need. ac_save_LDFLAGS=$LDFLAGS test -n "$x_libraries" && LDFLAGS="$LDFLAGS -L$x_libraries" # SM needs ICE to (dynamically) link under SunOS 4.x (so we have to # check for ICE first), but we must link in the order -lSM -lICE or # we get undefined symbols. So assume we have SM if we have ICE. # These have to be linked with before -lX11, unlike the other # libraries we check for below, so use a different variable. # John Interrante, Karl Berry { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for IceConnectionNumber in -lICE" >&5 printf %s "checking for IceConnectionNumber in -lICE... " >&6; } if test ${ac_cv_lib_ICE_IceConnectionNumber+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lICE $X_EXTRA_LIBS $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char IceConnectionNumber (void); int main (void) { return IceConnectionNumber (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_ICE_IceConnectionNumber=yes else case e in #( e) ac_cv_lib_ICE_IceConnectionNumber=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ICE_IceConnectionNumber" >&5 printf "%s\n" "$ac_cv_lib_ICE_IceConnectionNumber" >&6; } if test "x$ac_cv_lib_ICE_IceConnectionNumber" = xyes then : X_PRE_LIBS="$X_PRE_LIBS -lSM -lICE" fi LDFLAGS=$ac_save_LDFLAGS fi # openSUSE leap 15.3 installs qmake-qt5, not qmake, for example. # Store the full name (like qmake-qt5) into QMAKE # and the specifier (like -qt5 or empty) into am_have_qt_qmexe_suff. if test -n "$ac_tool_prefix"; then for ac_prog in qmake qmake-qt6 qmake-qt5 do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_QMAKE+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$QMAKE"; then ac_cv_prog_QMAKE="$QMAKE" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_QMAKE="$ac_tool_prefix$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi QMAKE=$ac_cv_prog_QMAKE if test -n "$QMAKE"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $QMAKE" >&5 printf "%s\n" "$QMAKE" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$QMAKE" && break done fi if test -z "$QMAKE"; then ac_ct_QMAKE=$QMAKE for ac_prog in qmake qmake-qt6 qmake-qt5 do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ac_ct_QMAKE+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ac_ct_QMAKE"; then ac_cv_prog_ac_ct_QMAKE="$ac_ct_QMAKE" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_QMAKE="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi ac_ct_QMAKE=$ac_cv_prog_ac_ct_QMAKE if test -n "$ac_ct_QMAKE"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_QMAKE" >&5 printf "%s\n" "$ac_ct_QMAKE" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$ac_ct_QMAKE" && break done if test "x$ac_ct_QMAKE" = x; then QMAKE="false" else case $cross_compiling:$ac_tool_warned in yes:) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac QMAKE=$ac_ct_QMAKE fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt" >&5 printf %s "checking for Qt... " >&6; } am_have_qt_qmexe_suff=`echo $QMAKE | sed 's,^.*qmake,,'` # If we have Qt5 or later in the path, we're golden ver=`$QMAKE --version | grep -o "Qt version ."` if test "$ver" ">" "Qt version 4"; then have_qt=yes # This pro file dumps qmake's variables, but it only works on Qt 5 or later am_have_qt_dir=`mktemp -d` am_have_qt_pro="$am_have_qt_dir/test.pro" am_have_qt_stash="$am_have_qt_dir/.qmake.stash" am_have_qt_makefile="$am_have_qt_dir/Makefile" # http://qt-project.org/doc/qt-5/qmake-variable-reference.html#qt cat > $am_have_qt_pro << EOF win32 { CONFIG -= debug_and_release CONFIG += release } qtHaveModule(axcontainer): QT += axcontainer qtHaveModule(axserver): QT += axserver qtHaveModule(concurrent): QT += concurrent qtHaveModule(core): QT += core qtHaveModule(dbus): QT += dbus qtHaveModule(declarative): QT += declarative qtHaveModule(designer): QT += designer qtHaveModule(gui): QT += gui qtHaveModule(help): QT += help qtHaveModule(multimedia): QT += multimedia qtHaveModule(multimediawidgets): QT += multimediawidgets qtHaveModule(network): QT += network qtHaveModule(opengl): QT += opengl qtHaveModule(printsupport): QT += printsupport qtHaveModule(qml): QT += qml qtHaveModule(qmltest): QT += qmltest qtHaveModule(x11extras): QT += x11extras qtHaveModule(script): QT += script qtHaveModule(scripttools): QT += scripttools qtHaveModule(sensors): QT += sensors qtHaveModule(serialport): QT += serialport qtHaveModule(sql): QT += sql qtHaveModule(svg): QT += svg qtHaveModule(testlib): QT += testlib qtHaveModule(uitools): QT += uitools qtHaveModule(webkit): QT += webkit qtHaveModule(webkitwidgets): QT += webkitwidgets qtHaveModule(xml): QT += xml qtHaveModule(xmlpatterns): QT += xmlpatterns percent.target = % percent.commands = @echo -n "\$(\$(@))\ " QMAKE_EXTRA_TARGETS += percent EOF $QMAKE $am_have_qt_pro -o $am_have_qt_makefile QT_CXXFLAGS=`cd $am_have_qt_dir; make -s -f $am_have_qt_makefile CXXFLAGS INCPATH` QT_LIBS=`cd $am_have_qt_dir; make -s -f $am_have_qt_makefile LIBS` rm $am_have_qt_pro $am_have_qt_stash $am_have_qt_makefile rmdir $am_have_qt_dir # Look for specific tools in $PATH QT_MOC=`which moc$am_have_qt_qmexe_suff` QT_UIC=`which uic$am_have_qt_qmexe_suff` QT_RCC=`which rcc$am_have_qt_qmexe_suff` QT_LRELEASE=`which lrelease$am_have_qt_qmexe_suff` QT_LUPDATE=`which lupdate$am_have_qt_qmexe_suff` # Get Qt version from qmake QT_DIR=`$QMAKE --version | grep -o -E /.+` # All variables are defined, report the result { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_qt: QT_CXXFLAGS=$QT_CXXFLAGS QT_DIR=$QT_DIR QT_LIBS=$QT_LIBS QT_UIC=$QT_UIC QT_MOC=$QT_MOC QT_RCC=$QT_RCC QT_LRELEASE=$QT_LRELEASE QT_LUPDATE=$QT_LUPDATE" >&5 printf "%s\n" "$have_qt: QT_CXXFLAGS=$QT_CXXFLAGS QT_DIR=$QT_DIR QT_LIBS=$QT_LIBS QT_UIC=$QT_UIC QT_MOC=$QT_MOC QT_RCC=$QT_RCC QT_LRELEASE=$QT_LRELEASE QT_LUPDATE=$QT_LUPDATE" >&6; } else # Qt was not found have_qt=no QT_CXXFLAGS= QT_DIR= QT_LIBS= QT_UIC= QT_MOC= QT_RCC= QT_LRELEASE= QT_LUPDATE= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_qt" >&5 printf "%s\n" "$have_qt" >&6; } fi #### Being paranoid: if test x"$have_qt" = xyes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking correct functioning of Qt installation" >&5 printf %s "checking correct functioning of Qt installation... " >&6; } if test ${ax_cv_qt_test_result+y} then : printf %s "(cached) " >&6 else case e in #( e) cat > ax_qt_test.h << EOF #include class Test : public QObject { Q_OBJECT public: Test() {} ~Test() {} public slots: void receive() {} signals: void send(); }; EOF cat > ax_qt_main.$ac_ext << EOF #include "ax_qt_test.h" #include int main( int argc, char **argv ) { QApplication app( argc, argv ); Test t; QObject::connect( &t, SIGNAL(send()), &t, SLOT(receive()) ); } EOF ax_cv_qt_test_result="failure" ax_try_1="$QT_MOC ax_qt_test.h -o moc_ax_qt_test.$ac_ext >/dev/null 2>/dev/null" { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ax_try_1\""; } >&5 (eval $ax_try_1) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test x"$ac_status" != x0; then echo "$ax_err_1" >&5 echo "configure: could not run $QT_MOC on:" >&5 cat ax_qt_test.h >&5 else ax_try_2="$CXX $QT_CXXFLAGS -c $CXXFLAGS -o moc_ax_qt_test.o moc_ax_qt_test.$ac_ext >/dev/null 2>/dev/null" { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ax_try_2\""; } >&5 (eval $ax_try_2) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test x"$ac_status" != x0; then echo "$ax_err_2" >&5 echo "configure: could not compile:" >&5 cat moc_ax_qt_test.$ac_ext >&5 else ax_try_3="$CXX $QT_CXXFLAGS -c $CXXFLAGS -o ax_qt_main.o ax_qt_main.$ac_ext >/dev/null 2>/dev/null" { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ax_try_3\""; } >&5 (eval $ax_try_3) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test x"$ac_status" != x0; then echo "$ax_err_3" >&5 echo "configure: could not compile:" >&5 cat ax_qt_main.$ac_ext >&5 else ax_try_4="$CXX -o ax_qt_main ax_qt_main.o moc_ax_qt_test.o $QT_LIBS $LIBS >/dev/null 2>/dev/null" { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ax_try_4\""; } >&5 (eval $ax_try_4) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test x"$ac_status" != x0; then echo "$ax_err_4" >&5 else ax_cv_qt_test_result="success" fi fi fi fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_qt_test_result" >&5 printf "%s\n" "$ax_cv_qt_test_result" >&6; } if test x"$ax_cv_qt_test_result" = "xfailure"; then as_fn_error $? "Failed to find matching components of a complete Qt installation. Try using more options, see ./configure --help." "$LINENO" 5 fi rm -f ax_qt_test.h moc_ax_qt_test.$ac_ext moc_ax_qt_test.o \ ax_qt_main.$ac_ext ax_qt_main.o ax_qt_main fi else if test "$ac_clang_active" = "yes" ; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt" >&5 printf %s "checking for Qt... " >&6; } # From serial 19 to handle issue #54. # # openSUSE leap 15.3 installs qmake-qt5, not qmake, for example. # Store the full name (like qmake-qt5) into am_have_qt_qmexe # and the specifier (like -qt5 or empty) into am_have_qt_qmexe_suff. for ac_prog in qmake qmake-qt6 qmake-qt5 do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_am_have_qt_qmexe+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$am_have_qt_qmexe"; then ac_cv_prog_am_have_qt_qmexe="$am_have_qt_qmexe" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_am_have_qt_qmexe="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi am_have_qt_qmexe=$ac_cv_prog_am_have_qt_qmexe if test -n "$am_have_qt_qmexe"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_have_qt_qmexe" >&5 printf "%s\n" "$am_have_qt_qmexe" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$am_have_qt_qmexe" && break done am_have_qt_qmexe_suff=`echo $am_have_qt_qmexe | cut -b 6-` # If we have Qt5 or later in the path, we're golden ver=`$am_have_qt_qmexe --version | grep -o "Qt version ."` if test "$ver" ">" "Qt version 4"; then have_qt=yes # This pro file dumps qmake's variables, but it only works on Qt 5 or later am_have_qt_dir=`mktemp -d` am_have_qt_pro="$am_have_qt_dir/test.pro" am_have_qt_makefile="$am_have_qt_dir/Makefile" # http://qt-project.org/doc/qt-5/qmake-variable-reference.html#qt cat > $am_have_qt_pro << EOF win32 { CONFIG -= debug_and_release CONFIG += release } qtHaveModule(core): QT += core qtHaveModule(gui): QT += gui qtHaveModule(widgets): QT += widgets percent.target = % percent.commands = @echo -n "\$(\$(@))\ " QMAKE_EXTRA_TARGETS += percent EOF $am_have_qt_qmexe $am_have_qt_pro -o $am_have_qt_makefile echo "QT TEST Makefile = $am_have_qt_makefile" # Get Qt version from qmake QT_CXXFLAGS=`cd $am_have_qt_dir; make -s -f $am_have_qt_makefile CXXFLAGS INCPATH` QT_LIBS=`cd $am_have_qt_dir; make -s -f $am_have_qt_makefile LIBS` rm $am_have_qt_pro $am_have_qt_makefile rmdir $am_have_qt_dir # Look for specific tools in $PATH QT_MOC=`which moc$am_have_qt_qmexe_suff` QT_UIC=`which uic$am_have_qt_qmexe_suff` QT_RCC=`which rcc$am_have_qt_qmexe_suff` QT_LRELEASE=`which lrelease$am_have_qt_qmexe_suff` QT_LUPDATE=`which lupdate$am_have_qt_qmexe_suff` # Get Qt version from qmake QT_DIR=`$am_have_qt_qmexe --version | grep -o -E /.+` # All variables are defined, report the result { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_qt: QT_CXXFLAGS=$QT_CXXFLAGS QT_DIR=$QT_DIR QT_LIBS=$QT_LIBS QT_UIC=$QT_UIC QT_MOC=$QT_MOC QT_RCC=$QT_RCC QT_LRELEASE=$QT_LRELEASE QT_LUPDATE=$QT_LUPDATE" >&5 printf "%s\n" "$have_qt: QT_CXXFLAGS=$QT_CXXFLAGS QT_DIR=$QT_DIR QT_LIBS=$QT_LIBS QT_UIC=$QT_UIC QT_MOC=$QT_MOC QT_RCC=$QT_RCC QT_LRELEASE=$QT_LRELEASE QT_LUPDATE=$QT_LUPDATE" >&6; } else # Qt was not found have_qt=no QT_CXXFLAGS= QT_DIR= QT_LIBS= QT_UIC= QT_MOC= QT_RCC= QT_LRELEASE= QT_LUPDATE= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_qt" >&5 printf "%s\n" "$have_qt" >&6; } as_fn_error $? "qmake/Qt not found... is any qmake on your system?" "$LINENO" 5 fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt" >&5 printf %s "checking for Qt... " >&6; } # From serial 19 to handle issue #54. # # openSUSE leap 15.3 installs qmake-qt5, not qmake, for example. # Store the full name (like qmake-qt5) into am_have_qt_qmexe # and the specifier (like -qt5 or empty) into am_have_qt_qmexe_suff. for ac_prog in qmake qmake-qt6 qmake-qt5 do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_am_have_qt_qmexe+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$am_have_qt_qmexe"; then ac_cv_prog_am_have_qt_qmexe="$am_have_qt_qmexe" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_am_have_qt_qmexe="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi am_have_qt_qmexe=$ac_cv_prog_am_have_qt_qmexe if test -n "$am_have_qt_qmexe"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $am_have_qt_qmexe" >&5 printf "%s\n" "$am_have_qt_qmexe" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$am_have_qt_qmexe" && break done am_have_qt_qmexe_suff=`echo $am_have_qt_qmexe | cut -b 6-` # If we have Qt5 or later in the path, we're golden ver=`$am_have_qt_qmexe --version | grep -o "Qt version ."` if test "$ver" ">" "Qt version 4"; then have_qt=yes # This pro file dumps qmake's variables, but it only works on Qt 5 or later am_have_qt_dir=`mktemp -d` am_have_qt_pro="$am_have_qt_dir/test.pro" am_have_qt_makefile="$am_have_qt_dir/Makefile" # http://qt-project.org/doc/qt-5/qmake-variable-reference.html#qt cat > $am_have_qt_pro << EOF win32 { CONFIG -= debug_and_release CONFIG += release } qtHaveModule(core): QT += core qtHaveModule(gui): QT += gui qtHaveModule(widgets): QT += widgets percent.target = % percent.commands = @echo -n "\$(\$(@))\ " QMAKE_EXTRA_TARGETS += percent EOF $am_have_qt_qmexe $am_have_qt_pro -o $am_have_qt_makefile # Get Qt version from qmake QT_CXXFLAGS=`cd $am_have_qt_dir; make -s -f $am_have_qt_makefile CXXFLAGS INCPATH` QT_LIBS=`cd $am_have_qt_dir; make -s -f $am_have_qt_makefile LIBS` rm $am_have_qt_pro $am_have_qt_makefile rmdir $am_have_qt_dir # Look for specific tools in $PATH QT_MOC=`which moc$am_have_qt_qmexe_suff` QT_UIC=`which uic$am_have_qt_qmexe_suff` QT_RCC=`which rcc$am_have_qt_qmexe_suff` QT_LRELEASE=`which lrelease$am_have_qt_qmexe_suff` QT_LUPDATE=`which lupdate$am_have_qt_qmexe_suff` # Get Qt version from qmake QT_DIR=`$am_have_qt_qmexe --version | grep -o -E /.+` # All variables are defined, report the result { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_qt: QT_CXXFLAGS=$QT_CXXFLAGS QT_DIR=$QT_DIR QT_LIBS=$QT_LIBS QT_UIC=$QT_UIC QT_MOC=$QT_MOC QT_RCC=$QT_RCC QT_LRELEASE=$QT_LRELEASE QT_LUPDATE=$QT_LUPDATE" >&5 printf "%s\n" "$have_qt: QT_CXXFLAGS=$QT_CXXFLAGS QT_DIR=$QT_DIR QT_LIBS=$QT_LIBS QT_UIC=$QT_UIC QT_MOC=$QT_MOC QT_RCC=$QT_RCC QT_LRELEASE=$QT_LRELEASE QT_LUPDATE=$QT_LUPDATE" >&6; } else # Qt was not found have_qt=no QT_CXXFLAGS= QT_DIR= QT_LIBS= QT_UIC= QT_MOC= QT_RCC= QT_LRELEASE= QT_LUPDATE= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $have_qt" >&5 printf "%s\n" "$have_qt" >&6; } as_fn_error $? "qmake/Qt not found... is any qmake on your system?" "$LINENO" 5 fi #### Being paranoid: if test x"$have_qt" = xyes; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking correct functioning of Qt installation" >&5 printf %s "checking correct functioning of Qt installation... " >&6; } if test ${ax_cv_qt_test_result+y} then : printf %s "(cached) " >&6 else case e in #( e) cat > ax_qt_test.h << EOF #include class Test : public QObject { Q_OBJECT public: Test() {} ~Test() {} public slots: void receive() {} signals: void send(); }; EOF cat > ax_qt_main.$ac_ext << EOF #include "ax_qt_test.h" #include int main( int argc, char **argv ) { QApplication app( argc, argv ); Test t; QObject::connect( &t, SIGNAL(send()), &t, SLOT(receive()) ); } EOF ax_cv_qt_test_result="failure" ax_try_1="$QT_MOC ax_qt_test.h -o moc_ax_qt_test.$ac_ext >/dev/null 2>/dev/null" { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ax_try_1\""; } >&5 (eval $ax_try_1) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test x"$ac_status" != x0; then echo "$ax_err_1" >&5 echo "configure: could not run $QT_MOC on:" >&5 cat ax_qt_test.h >&5 else ax_try_2="$CXX $QT_CXXFLAGS -c $CXXFLAGS -o moc_ax_qt_test.o moc_ax_qt_test.$ac_ext >/dev/null 2>/dev/null" { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ax_try_2\""; } >&5 (eval $ax_try_2) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test x"$ac_status" != x0; then echo "$ax_err_2" >&5 echo "configure: could not compile:" >&5 cat moc_ax_qt_test.$ac_ext >&5 else ax_try_3="$CXX $QT_CXXFLAGS -c $CXXFLAGS -o ax_qt_main.o ax_qt_main.$ac_ext >/dev/null 2>/dev/null" { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ax_try_3\""; } >&5 (eval $ax_try_3) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test x"$ac_status" != x0; then echo "$ax_err_3" >&5 echo "configure: could not compile:" >&5 cat ax_qt_main.$ac_ext >&5 else ax_try_4="$CXX -o ax_qt_main ax_qt_main.o moc_ax_qt_test.o $QT_LIBS $LIBS >/dev/null 2>/dev/null" { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ax_try_4\""; } >&5 (eval $ax_try_4) 2>&5 ac_status=$? printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if test x"$ac_status" != x0; then echo "$ax_err_4" >&5 else ax_cv_qt_test_result="success" fi fi fi fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_qt_test_result" >&5 printf "%s\n" "$ax_cv_qt_test_result" >&6; } if test x"$ax_cv_qt_test_result" = "xfailure"; then as_fn_error $? "Failed to find matching components of a complete Qt installation. Try using more options, see ./configure --help." "$LINENO" 5 fi rm -f ax_qt_test.h moc_ax_qt_test.$ac_ext moc_ax_qt_test.o \ ax_qt_main.$ac_ext ax_qt_main.o ax_qt_main fi fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: qt user-interface build enabled" >&5 printf "%s\n" "qt user-interface build enabled" >&6; }; fi # Check whether --enable-portmidi was given. if test ${enable_portmidi+y} then : enableval=$enable_portmidi; portmidi=$enableval else case e in #( e) portmidi=no ;; esac fi if test "$portmidi" != "no" ; then ac_build_portmidi="yes" ac_build_rtmidi="no" ac_app_name="seq66portmidi" ac_config_name="seq66portmidi" printf "%s\n" "#define PORTMIDI_SUPPORT 1" >>confdefs.h alsa_save_CFLAGS="$CFLAGS" alsa_save_LDFLAGS="$LDFLAGS" alsa_save_LIBS="$LIBS" alsa_found=yes alsa_topology_found=no # Check whether --with-alsa-prefix was given. if test ${with_alsa_prefix+y} then : withval=$with_alsa_prefix; alsa_prefix="$withval" else case e in #( e) alsa_prefix="" ;; esac fi # Check whether --with-alsa-inc-prefix was given. if test ${with_alsa_inc_prefix+y} then : withval=$with_alsa_inc_prefix; alsa_inc_prefix="$withval" else case e in #( e) alsa_inc_prefix="" ;; esac fi # Check whether --enable-alsa-topology was given. if test ${enable_alsa_topology+y} then : enableval=$enable_alsa_topology; enable_atopology="$enableval" else case e in #( e) enable_atopology=no ;; esac fi # Check whether --enable-alsatest was given. if test ${enable_alsatest+y} then : enableval=$enable_alsatest; enable_alsatest="$enableval" else case e in #( e) enable_alsatest=yes ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA CFLAGS" >&5 printf %s "checking for ALSA CFLAGS... " >&6; } if test "$alsa_inc_prefix" != "" ; then ALSA_CFLAGS="$ALSA_CFLAGS -I$alsa_inc_prefix" CFLAGS="$CFLAGS -I$alsa_inc_prefix" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_CFLAGS" >&5 printf "%s\n" "$ALSA_CFLAGS" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lc" >&5 printf %s "checking for dlopen in -lc... " >&6; } if test ${ac_cv_lib_c_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lc $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_c_dlopen=yes else case e in #( e) ac_cv_lib_c_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_dlopen" >&5 printf "%s\n" "$ac_cv_lib_c_dlopen" >&6; } if test "x$ac_cv_lib_c_dlopen" = xyes then : LIBDL="" else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 printf %s "checking for dlopen in -ldl... " >&6; } if test ${ac_cv_lib_dl_dlopen+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-ldl $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char dlopen (void); int main (void) { return dlopen (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_dl_dlopen=yes else case e in #( e) ac_cv_lib_dl_dlopen=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 printf "%s\n" "$ac_cv_lib_dl_dlopen" >&6; } if test "x$ac_cv_lib_dl_dlopen" = xyes then : LIBDL="-ldl" fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA LDFLAGS" >&5 printf %s "checking for ALSA LDFLAGS... " >&6; } if test "$alsa_prefix" != "" ; then ALSA_LIBS="$ALSA_LIBS -L$alsa_prefix" LDFLAGS="$LDFLAGS $ALSA_LIBS" fi ALSA_LIBS="$ALSA_LIBS -lasound -lm $LIBDL -lpthread" LIBS="$ALSA_LIBS $LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_LIBS" >&5 printf "%s\n" "$ALSA_LIBS" >&6; } if test "x$enable_alsatest" = "xyes"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking required libasound headers version" >&5 printf %s "checking required libasound headers version... " >&6; } min_alsa_version=0.9.0 no_alsa="" alsa_min_major_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\1/'` alsa_min_minor_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\2/'` alsa_min_micro_version=`echo $min_alsa_version | \ sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\3/'` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version" >&5 printf "%s\n" "$alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version" >&6; } ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libasound headers version >= $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version ($min_alsa_version)" >&5 printf %s "checking for libasound headers version >= $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version ($min_alsa_version)... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include int main (void) { /* ensure backward compatibility */ #if !defined(SND_LIB_MAJOR) && defined(SOUNDLIB_VERSION_MAJOR) #define SND_LIB_MAJOR SOUNDLIB_VERSION_MAJOR #endif #if !defined(SND_LIB_MINOR) && defined(SOUNDLIB_VERSION_MINOR) #define SND_LIB_MINOR SOUNDLIB_VERSION_MINOR #endif #if !defined(SND_LIB_SUBMINOR) && defined(SOUNDLIB_VERSION_SUBMINOR) #define SND_LIB_SUBMINOR SOUNDLIB_VERSION_SUBMINOR #endif # if(SND_LIB_MAJOR > $alsa_min_major_version) exit(0); # else # if(SND_LIB_MAJOR < $alsa_min_major_version) # error not present # endif # if(SND_LIB_MINOR > $alsa_min_minor_version) exit(0); # else # if(SND_LIB_MINOR < $alsa_min_minor_version) # error not present # endif # if(SND_LIB_SUBMINOR < $alsa_min_micro_version) # error not present # endif # endif # endif exit(0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: found." >&5 printf "%s\n" "found." >&6; } else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: not present." >&5 printf "%s\n" "not present." >&6; } as_fn_error $? "Sufficiently new version of libasound not found." "$LINENO" 5 alsa_found=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libatopology (sound headers version > 1.1.9)" >&5 printf %s "checking for libatopology (sound headers version > 1.1.9)... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include #include int main (void) { /* ensure backward compatibility */ #if !defined(SND_LIB_VERSION) #define SND_LIB_VERSION 0 #endif #if SND_LIB_VERSION > 0x00010109 exit(0); #else # error not present #endif exit(0); ; return 0; } _ACEOF if ac_fn_c_try_compile "$LINENO" then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } enable_atopology="yes" else case e in #( e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu fi if test "x$enable_alsatest" = "xyes"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for snd_ctl_open in -lasound" >&5 printf %s "checking for snd_ctl_open in -lasound... " >&6; } if test ${ac_cv_lib_asound_snd_ctl_open+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-lasound $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char snd_ctl_open (void); int main (void) { return snd_ctl_open (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_asound_snd_ctl_open=yes else case e in #( e) ac_cv_lib_asound_snd_ctl_open=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_asound_snd_ctl_open" >&5 printf "%s\n" "$ac_cv_lib_asound_snd_ctl_open" >&6; } if test "x$ac_cv_lib_asound_snd_ctl_open" = xyes then : printf "%s\n" "#define HAVE_LIBASOUND 1" >>confdefs.h LIBS="-lasound $LIBS" else case e in #( e) as_fn_error $? "No linkable libasound was found." "$LINENO" 5 alsa_found=no ;; esac fi if test "x$enable_atopology" = "xyes"; then alsa_topology_found=yes alsa_save_LIBS2="$LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for snd_tplg_new in -latopology" >&5 printf %s "checking for snd_tplg_new in -latopology... " >&6; } if test ${ac_cv_lib_atopology_snd_tplg_new+y} then : printf %s "(cached) " >&6 else case e in #( e) ac_check_lib_save_LIBS=$LIBS LIBS="-latopology $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char snd_tplg_new (void); int main (void) { return snd_tplg_new (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_cv_lib_atopology_snd_tplg_new=yes else case e in #( e) ac_cv_lib_atopology_snd_tplg_new=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_atopology_snd_tplg_new" >&5 printf "%s\n" "$ac_cv_lib_atopology_snd_tplg_new" >&6; } if test "x$ac_cv_lib_atopology_snd_tplg_new" = xyes then : printf "%s\n" "#define HAVE_LIBATOPOLOGY 1" >>confdefs.h LIBS="-latopology $LIBS" else case e in #( e) as_fn_error $? "No linkable libatopology was found." "$LINENO" 5 alsa_topology_found=no, ;; esac fi LIBS="$alsa_save_LIBS2" fi else if test "x$enable_atopology" = "xyes"; then alsa_topology_found=yes fi fi if test "x$alsa_found" = "xyes" ; then : LIBS=`echo $LIBS | sed 's/-lasound//g'` LIBS=`echo $LIBS | sed 's/ //'` LIBS="-lasound $LIBS" fi if test "x$alsa_found" = "xno" ; then : CFLAGS="$alsa_save_CFLAGS" LDFLAGS="$alsa_save_LDFLAGS" LIBS="$alsa_save_LIBS" ALSA_CFLAGS="" ALSA_LIBS="" ALSA_TOPOLOGY_LIBS="" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ALSA topology LDFLAGS" >&5 printf %s "checking for ALSA topology LDFLAGS... " >&6; } if test "x$alsa_topology_found" = "xyes"; then ALSA_TOPOLOGY_LIBS="$ALSA_TOPOLOGY_LIBS -latopology" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ALSA_TOPOLOGY_LIBS" >&5 printf "%s\n" "$ALSA_TOPOLOGY_LIBS" >&6; } { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Portmidi build enabled" >&5 printf "%s\n" "Portmidi build enabled" >&6; }; else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Portmidi build disabled" >&5 printf "%s\n" "$as_me: Portmidi build disabled" >&6;}; fi printf "%s\n" "#define APP_NAME \"$ac_app_name\"" >>confdefs.h printf "%s\n" "#define APP_TYPE \"$ac_app_type\"" >>confdefs.h printf "%s\n" "#define APP_ENGINE \"$ac_app_engine\"" >>confdefs.h printf "%s\n" "#define APP_BUILD_ISSUE \"GNU/Linux\"" >>confdefs.h printf "%s\n" "#define APP_BUILD_OS \"$ac_app_build_os\"" >>confdefs.h printf "%s\n" "#define CLIENT_NAME \"$ac_client_name\"" >>confdefs.h printf "%s\n" "#define CONFIG_NAME \"$ac_config_name\"" >>confdefs.h printf "%s\n" "#define CONFIG_DIR_NAME \"$ac_config_dir_name\"" >>confdefs.h printf "%s\n" "#define ICON_NAME \"$ac_icon_name\"" >>confdefs.h if test "$ac_build_docs" = "yes"; then BUILD_DOCS_TRUE= BUILD_DOCS_FALSE='#' else BUILD_DOCS_TRUE='#' BUILD_DOCS_FALSE= fi if test "$ac_build_portmidi" = "yes"; then BUILD_PORTMIDI_TRUE= BUILD_PORTMIDI_FALSE='#' else BUILD_PORTMIDI_TRUE='#' BUILD_PORTMIDI_FALSE= fi if test "$ac_build_qtmidi" = "yes"; then BUILD_QTMIDI_TRUE= BUILD_QTMIDI_FALSE='#' else BUILD_QTMIDI_TRUE='#' BUILD_QTMIDI_FALSE= fi if test "$ac_build_rtcli" = "yes"; then BUILD_RTCLI_TRUE= BUILD_RTCLI_FALSE='#' else BUILD_RTCLI_TRUE='#' BUILD_RTCLI_FALSE= fi if test "$ac_build_rtmidi" = "yes"; then BUILD_RTMIDI_TRUE= BUILD_RTMIDI_FALSE='#' else BUILD_RTMIDI_TRUE='#' BUILD_RTMIDI_FALSE= fi if test "$ac_build_sessions" = "yes"; then BUILD_SESSIONS_TRUE= BUILD_SESSIONS_FALSE='#' else BUILD_SESSIONS_TRUE='#' BUILD_SESSIONS_FALSE= fi COVFLAGS="" PROFLAGS="" PROLDFLAGS="" OPTFLAGS="" DBGFLAGS="" MORFLAGS="" if test -n "$GCC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to enable gcov coverage tests" >&5 printf %s "checking whether to enable gcov coverage tests... " >&6; } # Check whether --enable-coverage was given. if test ${enable_coverage+y} then : enableval=$enable_coverage; case "${enableval}" in yes) coverage=yes ;; no) coverage=no ;; *) as_fn_error $? "bad value ${enableval} for --enable-coverage" "$LINENO" 5 ;; esac else case e in #( e) coverage=no ;; esac fi if test x$coverage = xyes; then DOCOVERAGE_TRUE= DOCOVERAGE_FALSE='#' else DOCOVERAGE_TRUE='#' DOCOVERAGE_FALSE= fi if test "x$coverage" = "xyes" ; then COVFLAGS="-fprofile-arcs -ftest-coverage" OPTFLAGS="-O0" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi printf "%s\n" "#define COVFLAGS $COVFLAGS" >>confdefs.h if test -n "$GCC"; then PROFLAGS= { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to enable gprof profiling" >&5 printf %s "checking whether to enable gprof profiling... " >&6; } # Check whether --enable-profile was given. if test ${enable_profile+y} then : enableval=$enable_profile; case "${enableval}" in yes) profile=yes ;; no) profile=no ;; prof) profile=prof ;; gprof) profile=gprof ;; *) as_fn_error $? "bad value ${enableval} for --enable-profile" "$LINENO" 5 ;; esac else case e in #( e) profile=no ;; esac fi if test x$profile = xyes; then DOPROFILE_TRUE= DOPROFILE_FALSE='#' else DOPROFILE_TRUE='#' DOPROFILE_FALSE= fi if test "x$profile" = "xyes" ; then PROFLAGS="-pg" PROLDFLAGS="-pg" OPTFLAGS="-O0" DBGFLAGS="-g" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } elif test "x$profile" = "xprof" ; then PROFLAGS="-p" PROLDFLAGS="-p" OPTFLAGS="-O0" DBGFLAGS="-g" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: prof" >&5 printf "%s\n" "prof" >&6; } elif test "x$profile" = "xgprof" ; then PROFLAGS="-pg" PROLDFLAGS="-pg" OPTFLAGS="-O0" DBGFLAGS="-g" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: prof" >&5 printf "%s\n" "prof" >&6; } else DBGFLAGS="" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi fi printf "%s\n" "#define PROFLAGS $PROFLAGS" >>confdefs.h MORFLAGS="-DDEBUG -D_DEBUG -fno-inline" if test -n "$GCC"; then DBGFLAGS="$COVFLAGS $PROFLAGS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to enable gdb debugging" >&5 printf %s "checking whether to enable gdb debugging... " >&6; } # Check whether --enable-debug was given. if test ${enable_debug+y} then : enableval=$enable_debug; case "${enableval}" in yes) debug=yes ;; no) debug=no ;; gdb) debug=gdb ;; db) debug=db ;; *) as_fn_error $? "bad value ${enableval} for --enable-debug" "$LINENO" 5 ;; esac else case e in #( e) debug=no ;; esac fi if test x$debug != xno; then DODEBUG_TRUE= DODEBUG_FALSE='#' else DODEBUG_TRUE='#' DODEBUG_FALSE= fi if test "x$debug" = "xyes" ; then OPTFLAGS="-O0" DBGFLAGS="-g $OPTFLAGS $MORFLAGS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } elif test "x$debug" = "xdb" ; then OPTFLAGS="-O0" DBGFLAGS="-g $OPTFLAGS $MORFLAGS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } elif test "x$debug" = "xgdb" ; then OPTFLAGS="-O0" DBGFLAGS="-ggdb $OPTFLAGS $MORFLAGS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 printf "%s\n" "yes" >&6; } else if test "x$OPTFLAGS" = "x" ; then OPTFLAGS="-O3" DBGFLAGS="" fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to enable debug and function instrumentation" >&5 printf %s "checking whether to enable debug and function instrumentation... " >&6; } # Check whether --enable-calls was given. if test ${enable_calls+y} then : enableval=$enable_calls; case "${enableval}" in yes) calls=yes ;; no) calls=no ;; *) as_fn_error $? "bad value ${enableval} for --enable-calls" "$LINENO" 5 ;; esac else case e in #( e) calls=no ;; esac fi fi printf "%s\n" "#define DBGFLAGS $DBGFLAGS" >>confdefs.h WARNFLAGS="-Wall -Wextra -pedantic -Wno-parentheses $WARNINGS" APIDEF="-DAPI_VERSION=\"$SEQ66_API_VERSION\"" SPEEDFLAGS="-ffast-math" COMMONFLAGS="$WARNFLAGS -D_REENTRANT $APIDEF $DBGFLAGS" WARNINGS_DISABLED="-Wno-unused-parameter -Wno-non-virtual-dtor" VERBCFLAGS="" ac_gnuwin="yes" case "$host_os" in *cygwin*) ac_gnuwin="yes" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for egrep -e" >&5 printf %s "checking for egrep -e... " >&6; } if test ${ac_cv_path_EGREP_TRADITIONAL+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -z "$EGREP_TRADITIONAL"; then ac_path_EGREP_TRADITIONAL_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in grep ggrep do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_EGREP_TRADITIONAL="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_EGREP_TRADITIONAL" || continue # Check for GNU ac_path_EGREP_TRADITIONAL and select it if it is found. # Check for GNU $ac_path_EGREP_TRADITIONAL case `"$ac_path_EGREP_TRADITIONAL" --version 2>&1` in #( *GNU*) ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_found=:;; #( *) ac_count=0 printf %s 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" printf "%s\n" 'EGREP_TRADITIONAL' >> "conftest.nl" "$ac_path_EGREP_TRADITIONAL" -E 'EGR(EP|AC)_TRADITIONAL$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_EGREP_TRADITIONAL_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_EGREP_TRADITIONAL_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_EGREP_TRADITIONAL"; then : fi else ac_cv_path_EGREP_TRADITIONAL=$EGREP_TRADITIONAL fi if test "$ac_cv_path_EGREP_TRADITIONAL" then : ac_cv_path_EGREP_TRADITIONAL="$ac_cv_path_EGREP_TRADITIONAL -E" else case e in #( e) if test -z "$EGREP_TRADITIONAL"; then ac_path_EGREP_TRADITIONAL_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_prog in egrep do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_EGREP_TRADITIONAL="$as_dir$ac_prog$ac_exec_ext" as_fn_executable_p "$ac_path_EGREP_TRADITIONAL" || continue # Check for GNU ac_path_EGREP_TRADITIONAL and select it if it is found. # Check for GNU $ac_path_EGREP_TRADITIONAL case `"$ac_path_EGREP_TRADITIONAL" --version 2>&1` in #( *GNU*) ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_found=:;; #( *) ac_count=0 printf %s 0123456789 >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" printf "%s\n" 'EGREP_TRADITIONAL' >> "conftest.nl" "$ac_path_EGREP_TRADITIONAL" 'EGR(EP|AC)_TRADITIONAL$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break as_fn_arith $ac_count + 1 && ac_count=$as_val if test $ac_count -gt ${ac_path_EGREP_TRADITIONAL_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_EGREP_TRADITIONAL="$ac_path_EGREP_TRADITIONAL" ac_path_EGREP_TRADITIONAL_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_EGREP_TRADITIONAL_found && break 3 done done done IFS=$as_save_IFS if test -z "$ac_cv_path_EGREP_TRADITIONAL"; then as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 fi else ac_cv_path_EGREP_TRADITIONAL=$EGREP_TRADITIONAL fi ;; esac fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP_TRADITIONAL" >&5 printf "%s\n" "$ac_cv_path_EGREP_TRADITIONAL" >&6; } EGREP_TRADITIONAL=$ac_cv_path_EGREP_TRADITIONAL ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on Tru64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then ax_pthread_save_CC="$CC" ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" if test "x$PTHREAD_CC" != "x" then : CC="$PTHREAD_CC" fi if test "x$PTHREAD_CXX" != "x" then : CXX="$PTHREAD_CXX" fi CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS" >&5 printf %s "checking for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char pthread_join (void); int main (void) { return pthread_join (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ax_pthread_ok=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5 printf "%s\n" "$ax_pthread_ok" >&6; } if test "x$ax_pthread_ok" = "xno"; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi CC="$ax_pthread_save_CC" CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items with a "," contain both # C compiler flags (before ",") and linker flags (after ","). Other items # starting with a "-" are C compiler flags, and remaining items are # library names, except for "none" which indicates that we try without # any flags at all, and "pthread-config" which is a program returning # the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 # (Note: HP C rejects this with "bad form for `-t' option") # -pthreads: Solaris/gcc (Note: HP C also rejects) # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads and # -D_REENTRANT too), HP C (must be checked before -lpthread, which # is present but should not be used directly; and before -mthreads, # because the compiler interprets this as "-mt" + "-hreads") # -mthreads: Mingw32/gcc, Lynx/gcc # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case $host_os in freebsd*) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) ax_pthread_flags="-kthread lthread $ax_pthread_flags" ;; hpux*) # From the cc(1) man page: "[-mt] Sets various -D flags to enable # multi-threading and also sets -lpthread." ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" ;; openedition*) # IBM z/OS requires a feature-test macro to be defined in order to # enable POSIX threads at all, so give the user a hint if this is # not set. (We don't define these ourselves, as they can affect # other portions of the system API in unpredictable ways.) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ # if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) AX_PTHREAD_ZOS_MISSING # endif _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP_TRADITIONAL "AX_PTHREAD_ZOS_MISSING" >/dev/null 2>&1 then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support." >&5 printf "%s\n" "$as_me: WARNING: IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support." >&2;} fi rm -rf conftest* ;; solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (N.B.: The stubs are missing # pthread_cleanup_push, or rather a function called by this macro, # so we could check for that, but who knows whether they'll stub # that too in a future libc.) So we'll check first for the # standard Solaris way of linking pthreads (-mt -lpthread). ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" ;; esac # Are we compiling with Clang? { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC is Clang" >&5 printf %s "checking whether $CC is Clang... " >&6; } if test ${ax_cv_PTHREAD_CLANG+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_cv_PTHREAD_CLANG=no # Note that Autoconf sets GCC=yes for Clang as well as GCC if test "x$GCC" = "xyes"; then cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Note: Clang 2.7 lacks __clang_[a-z]+__ */ # if defined(__clang__) && defined(__llvm__) AX_PTHREAD_CC_IS_CLANG # endif _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP_TRADITIONAL "AX_PTHREAD_CC_IS_CLANG" >/dev/null 2>&1 then : ax_cv_PTHREAD_CLANG=yes fi rm -rf conftest* fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_CLANG" >&5 printf "%s\n" "$ax_cv_PTHREAD_CLANG" >&6; } ax_pthread_clang="$ax_cv_PTHREAD_CLANG" # GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) # Note that for GCC and Clang -pthread generally implies -lpthread, # except when -nostdlib is passed. # This is problematic using libtool to build C++ shared libraries with pthread: # [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 # [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 # [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 # To solve this, first try -pthread together with -lpthread for GCC if test "x$GCC" = "xyes" then : ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags" fi # Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first if test "x$ax_pthread_clang" = "xyes" then : ax_pthread_flags="-pthread,-lpthread -pthread" fi # The presence of a feature test macro requesting re-entrant function # definitions is, on some systems, a strong hint that pthreads support is # correctly enabled case $host_os in darwin* | hpux* | linux* | osf* | solaris*) ax_pthread_check_macro="_REENTRANT" ;; aix*) ax_pthread_check_macro="_THREAD_SAFE" ;; *) ax_pthread_check_macro="--" ;; esac if test "x$ax_pthread_check_macro" = "x--" then : ax_pthread_check_cond=0 else case e in #( e) ax_pthread_check_cond="!defined($ax_pthread_check_macro)" ;; esac fi if test "x$ax_pthread_ok" = "xno"; then for ax_pthread_try_flag in $ax_pthread_flags; do case $ax_pthread_try_flag in none) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthreads work without any flags" >&5 printf %s "checking whether pthreads work without any flags... " >&6; } ;; *,*) PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthreads work with \"$PTHREAD_CFLAGS\" and \"$PTHREAD_LIBS\"" >&5 printf %s "checking whether pthreads work with \"$PTHREAD_CFLAGS\" and \"$PTHREAD_LIBS\"... " >&6; } ;; -*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthreads work with $ax_pthread_try_flag" >&5 printf %s "checking whether pthreads work with $ax_pthread_try_flag... " >&6; } PTHREAD_CFLAGS="$ax_pthread_try_flag" ;; pthread-config) # Extract the first word of "pthread-config", so it can be a program name with args. set dummy pthread-config; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ax_pthread_config+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ax_pthread_config"; then ac_cv_prog_ax_pthread_config="$ax_pthread_config" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ax_pthread_config="yes" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_ax_pthread_config" && ac_cv_prog_ax_pthread_config="no" fi ;; esac fi ax_pthread_config=$ac_cv_prog_ax_pthread_config if test -n "$ax_pthread_config"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_config" >&5 printf "%s\n" "$ax_pthread_config" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ax_pthread_config" = "xno" then : continue fi PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for the pthreads library -l$ax_pthread_try_flag" >&5 printf %s "checking for the pthreads library -l$ax_pthread_try_flag... " >&6; } PTHREAD_LIBS="-l$ax_pthread_try_flag" ;; esac ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include # if $ax_pthread_check_cond # error "$ax_pthread_check_macro must be defined" # endif static void *some_global = NULL; static void routine(void *a) { /* To avoid any unused-parameter or unused-but-set-parameter warning. */ some_global = a; } static void *start_routine(void *a) { return a; } int main (void) { pthread_t th; pthread_attr_t attr; pthread_create(&th, 0, start_routine, 0); pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_cleanup_pop(0) /* ; */ ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ax_pthread_ok=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5 printf "%s\n" "$ax_pthread_ok" >&6; } if test "x$ax_pthread_ok" = "xyes" then : break fi PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Clang needs special handling, because older versions handle the -pthread # option in a rather... idiosyncratic way if test "x$ax_pthread_clang" = "xyes"; then # Clang takes -pthread; it has never supported any other flag # (Note 1: This will need to be revisited if a system that Clang # supports has POSIX threads in a separate library. This tends not # to be the way of modern systems, but it's conceivable.) # (Note 2: On some systems, notably Darwin, -pthread is not needed # to get POSIX threads support; the API is always present and # active. We could reasonably leave PTHREAD_CFLAGS empty. But # -pthread does define _REENTRANT, and while the Darwin headers # ignore this macro, third-party headers might not.) # However, older versions of Clang make a point of warning the user # that, in an invocation where only linking and no compilation is # taking place, the -pthread option has no effect ("argument unused # during compilation"). They expect -pthread to be passed in only # when source code is being compiled. # # Problem is, this is at odds with the way Automake and most other # C build frameworks function, which is that the same flags used in # compilation (CFLAGS) are also used in linking. Many systems # supported by AX_PTHREAD require exactly this for POSIX threads # support, and in fact it is often not straightforward to specify a # flag that is used only in the compilation phase and not in # linking. Such a scenario is extremely rare in practice. # # Even though use of the -pthread flag in linking would only print # a warning, this can be a nuisance for well-run software projects # that build with -Werror. So if the active version of Clang has # this misfeature, we search for an option to squash it. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether Clang needs flag to prevent \"argument unused\" warning when linking with -pthread" >&5 printf %s "checking whether Clang needs flag to prevent \"argument unused\" warning when linking with -pthread... " >&6; } if test ${ax_cv_PTHREAD_CLANG_NO_WARN_FLAG+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown # Create an alternate version of $ac_link that compiles and # links in two steps (.c -> .o, .o -> exe) instead of one # (.c -> exe), because the warning occurs only in the second # step ax_pthread_save_ac_link="$ac_link" ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' ax_pthread_link_step=`printf "%s\n" "$ac_link" | sed "$ax_pthread_sed"` ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" ax_pthread_save_CFLAGS="$CFLAGS" for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do if test "x$ax_pthread_try" = "xunknown" then : break fi CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" ac_link="$ax_pthread_save_ac_link" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main(void){return 0;} _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_link="$ax_pthread_2step_ac_link" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main(void){return 0;} _ACEOF if ac_fn_c_try_link "$LINENO" then : break fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext done ac_link="$ax_pthread_save_ac_link" CFLAGS="$ax_pthread_save_CFLAGS" if test "x$ax_pthread_try" = "x" then : ax_pthread_try=no fi ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" >&5 printf "%s\n" "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" >&6; } case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in no | unknown) ;; *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; esac fi # $ax_pthread_clang = yes # Various other checks: if test "x$ax_pthread_ok" = "xyes"; then ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for joinable pthread attribute" >&5 printf %s "checking for joinable pthread attribute... " >&6; } if test ${ax_cv_PTHREAD_JOINABLE_ATTR+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_cv_PTHREAD_JOINABLE_ATTR=unknown for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { int attr = $ax_pthread_attr; return attr /* ; */ ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext done ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_JOINABLE_ATTR" >&5 printf "%s\n" "$ax_cv_PTHREAD_JOINABLE_ATTR" >&6; } if test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ test "x$ax_pthread_joinable_attr_defined" != "xyes" then : printf "%s\n" "#define PTHREAD_CREATE_JOINABLE $ax_cv_PTHREAD_JOINABLE_ATTR" >>confdefs.h ax_pthread_joinable_attr_defined=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether more special flags are required for pthreads" >&5 printf %s "checking whether more special flags are required for pthreads... " >&6; } if test ${ax_cv_PTHREAD_SPECIAL_FLAGS+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_cv_PTHREAD_SPECIAL_FLAGS=no case $host_os in solaris*) ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" ;; esac ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_SPECIAL_FLAGS" >&5 printf "%s\n" "$ax_cv_PTHREAD_SPECIAL_FLAGS" >&6; } if test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ test "x$ax_pthread_special_flags_added" != "xyes" then : PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" ax_pthread_special_flags_added=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for PTHREAD_PRIO_INHERIT" >&5 printf %s "checking for PTHREAD_PRIO_INHERIT... " >&6; } if test ${ax_cv_PTHREAD_PRIO_INHERIT+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { int i = PTHREAD_PRIO_INHERIT; return i; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ax_cv_PTHREAD_PRIO_INHERIT=yes else case e in #( e) ax_cv_PTHREAD_PRIO_INHERIT=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_PRIO_INHERIT" >&5 printf "%s\n" "$ax_cv_PTHREAD_PRIO_INHERIT" >&6; } if test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ test "x$ax_pthread_prio_inherit_defined" != "xyes" then : printf "%s\n" "#define HAVE_PTHREAD_PRIO_INHERIT 1" >>confdefs.h ax_pthread_prio_inherit_defined=yes fi CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" # More AIX lossage: compile with *_r variant if test "x$GCC" != "xyes"; then case $host_os in aix*) case "x/$CC" in #( x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6) : #handle absolute path differently from PATH based program lookup case "x$CC" in #( x/*) : if as_fn_executable_p ${CC}_r then : PTHREAD_CC="${CC}_r" fi if test "x${CXX}" != "x" then : if as_fn_executable_p ${CXX}_r then : PTHREAD_CXX="${CXX}_r" fi fi ;; #( *) : for ac_prog in ${CC}_r do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_PTHREAD_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$PTHREAD_CC"; then ac_cv_prog_PTHREAD_CC="$PTHREAD_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_PTHREAD_CC="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi PTHREAD_CC=$ac_cv_prog_PTHREAD_CC if test -n "$PTHREAD_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PTHREAD_CC" >&5 printf "%s\n" "$PTHREAD_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$PTHREAD_CC" && break done test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" if test "x${CXX}" != "x" then : for ac_prog in ${CXX}_r do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_PTHREAD_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$PTHREAD_CXX"; then ac_cv_prog_PTHREAD_CXX="$PTHREAD_CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_PTHREAD_CXX="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi PTHREAD_CXX=$ac_cv_prog_PTHREAD_CXX if test -n "$PTHREAD_CXX"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PTHREAD_CXX" >&5 printf "%s\n" "$PTHREAD_CXX" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$PTHREAD_CXX" && break done test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" fi ;; esac ;; #( *) : ;; esac ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test "x$ax_pthread_ok" = "xyes"; then printf "%s\n" "#define HAVE_PTHREAD 1" >>confdefs.h : else ax_pthread_ok=no fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ;; *mingw*) ac_gnuwin="yes" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Setting up MingW pthreads support" >&5 printf "%s\n" "$as_me: Setting up MingW pthreads support" >&6;} CFLAGS="$CFLAGS -mthreads -pthread " CPPFLAGS="-DPTW32_STATIC_LIB $CPPFLAGS " CXXFLAGS="$CXXFLAGS -mthreads -pthread " LDFLAGS="$LDFLAGS -mthreads -pthread " printf "%s\n" "#define HAVE_PTHREAD 1" >>confdefs.h ;; *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Normal pthreads support" >&5 printf "%s\n" "$as_me: Normal pthreads support" >&6;} { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking Running normal PTHREAD test" >&5 printf %s "checking Running normal PTHREAD test... " >&6; } ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on Tru64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then ax_pthread_save_CC="$CC" ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" if test "x$PTHREAD_CC" != "x" then : CC="$PTHREAD_CC" fi if test "x$PTHREAD_CXX" != "x" then : CXX="$PTHREAD_CXX" fi CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS" >&5 printf %s "checking for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. The 'extern "C"' is for builds by C++ compilers; although this is not generally supported in C code supporting it here has little cost and some practical benefit (sr 110532). */ #ifdef __cplusplus extern "C" #endif char pthread_join (void); int main (void) { return pthread_join (); ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ax_pthread_ok=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5 printf "%s\n" "$ax_pthread_ok" >&6; } if test "x$ax_pthread_ok" = "xno"; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi CC="$ax_pthread_save_CC" CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items with a "," contain both # C compiler flags (before ",") and linker flags (after ","). Other items # starting with a "-" are C compiler flags, and remaining items are # library names, except for "none" which indicates that we try without # any flags at all, and "pthread-config" which is a program returning # the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 # (Note: HP C rejects this with "bad form for `-t' option") # -pthreads: Solaris/gcc (Note: HP C also rejects) # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads and # -D_REENTRANT too), HP C (must be checked before -lpthread, which # is present but should not be used directly; and before -mthreads, # because the compiler interprets this as "-mt" + "-hreads") # -mthreads: Mingw32/gcc, Lynx/gcc # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case $host_os in freebsd*) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) ax_pthread_flags="-kthread lthread $ax_pthread_flags" ;; hpux*) # From the cc(1) man page: "[-mt] Sets various -D flags to enable # multi-threading and also sets -lpthread." ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" ;; openedition*) # IBM z/OS requires a feature-test macro to be defined in order to # enable POSIX threads at all, so give the user a hint if this is # not set. (We don't define these ourselves, as they can affect # other portions of the system API in unpredictable ways.) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ # if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) AX_PTHREAD_ZOS_MISSING # endif _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP_TRADITIONAL "AX_PTHREAD_ZOS_MISSING" >/dev/null 2>&1 then : { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support." >&5 printf "%s\n" "$as_me: WARNING: IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support." >&2;} fi rm -rf conftest* ;; solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (N.B.: The stubs are missing # pthread_cleanup_push, or rather a function called by this macro, # so we could check for that, but who knows whether they'll stub # that too in a future libc.) So we'll check first for the # standard Solaris way of linking pthreads (-mt -lpthread). ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" ;; esac # Are we compiling with Clang? { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CC is Clang" >&5 printf %s "checking whether $CC is Clang... " >&6; } if test ${ax_cv_PTHREAD_CLANG+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_cv_PTHREAD_CLANG=no # Note that Autoconf sets GCC=yes for Clang as well as GCC if test "x$GCC" = "xyes"; then cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Note: Clang 2.7 lacks __clang_[a-z]+__ */ # if defined(__clang__) && defined(__llvm__) AX_PTHREAD_CC_IS_CLANG # endif _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP_TRADITIONAL "AX_PTHREAD_CC_IS_CLANG" >/dev/null 2>&1 then : ax_cv_PTHREAD_CLANG=yes fi rm -rf conftest* fi ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_CLANG" >&5 printf "%s\n" "$ax_cv_PTHREAD_CLANG" >&6; } ax_pthread_clang="$ax_cv_PTHREAD_CLANG" # GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) # Note that for GCC and Clang -pthread generally implies -lpthread, # except when -nostdlib is passed. # This is problematic using libtool to build C++ shared libraries with pthread: # [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 # [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 # [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 # To solve this, first try -pthread together with -lpthread for GCC if test "x$GCC" = "xyes" then : ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags" fi # Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first if test "x$ax_pthread_clang" = "xyes" then : ax_pthread_flags="-pthread,-lpthread -pthread" fi # The presence of a feature test macro requesting re-entrant function # definitions is, on some systems, a strong hint that pthreads support is # correctly enabled case $host_os in darwin* | hpux* | linux* | osf* | solaris*) ax_pthread_check_macro="_REENTRANT" ;; aix*) ax_pthread_check_macro="_THREAD_SAFE" ;; *) ax_pthread_check_macro="--" ;; esac if test "x$ax_pthread_check_macro" = "x--" then : ax_pthread_check_cond=0 else case e in #( e) ax_pthread_check_cond="!defined($ax_pthread_check_macro)" ;; esac fi if test "x$ax_pthread_ok" = "xno"; then for ax_pthread_try_flag in $ax_pthread_flags; do case $ax_pthread_try_flag in none) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthreads work without any flags" >&5 printf %s "checking whether pthreads work without any flags... " >&6; } ;; *,*) PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthreads work with \"$PTHREAD_CFLAGS\" and \"$PTHREAD_LIBS\"" >&5 printf %s "checking whether pthreads work with \"$PTHREAD_CFLAGS\" and \"$PTHREAD_LIBS\"... " >&6; } ;; -*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether pthreads work with $ax_pthread_try_flag" >&5 printf %s "checking whether pthreads work with $ax_pthread_try_flag... " >&6; } PTHREAD_CFLAGS="$ax_pthread_try_flag" ;; pthread-config) # Extract the first word of "pthread-config", so it can be a program name with args. set dummy pthread-config; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_ax_pthread_config+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$ax_pthread_config"; then ac_cv_prog_ax_pthread_config="$ax_pthread_config" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ax_pthread_config="yes" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_prog_ax_pthread_config" && ac_cv_prog_ax_pthread_config="no" fi ;; esac fi ax_pthread_config=$ac_cv_prog_ax_pthread_config if test -n "$ax_pthread_config"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_config" >&5 printf "%s\n" "$ax_pthread_config" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi if test "x$ax_pthread_config" = "xno" then : continue fi PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for the pthreads library -l$ax_pthread_try_flag" >&5 printf %s "checking for the pthreads library -l$ax_pthread_try_flag... " >&6; } PTHREAD_LIBS="-l$ax_pthread_try_flag" ;; esac ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include # if $ax_pthread_check_cond # error "$ax_pthread_check_macro must be defined" # endif static void *some_global = NULL; static void routine(void *a) { /* To avoid any unused-parameter or unused-but-set-parameter warning. */ some_global = a; } static void *start_routine(void *a) { return a; } int main (void) { pthread_t th; pthread_attr_t attr; pthread_create(&th, 0, start_routine, 0); pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_cleanup_pop(0) /* ; */ ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ax_pthread_ok=yes fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_pthread_ok" >&5 printf "%s\n" "$ax_pthread_ok" >&6; } if test "x$ax_pthread_ok" = "xyes" then : break fi PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Clang needs special handling, because older versions handle the -pthread # option in a rather... idiosyncratic way if test "x$ax_pthread_clang" = "xyes"; then # Clang takes -pthread; it has never supported any other flag # (Note 1: This will need to be revisited if a system that Clang # supports has POSIX threads in a separate library. This tends not # to be the way of modern systems, but it's conceivable.) # (Note 2: On some systems, notably Darwin, -pthread is not needed # to get POSIX threads support; the API is always present and # active. We could reasonably leave PTHREAD_CFLAGS empty. But # -pthread does define _REENTRANT, and while the Darwin headers # ignore this macro, third-party headers might not.) # However, older versions of Clang make a point of warning the user # that, in an invocation where only linking and no compilation is # taking place, the -pthread option has no effect ("argument unused # during compilation"). They expect -pthread to be passed in only # when source code is being compiled. # # Problem is, this is at odds with the way Automake and most other # C build frameworks function, which is that the same flags used in # compilation (CFLAGS) are also used in linking. Many systems # supported by AX_PTHREAD require exactly this for POSIX threads # support, and in fact it is often not straightforward to specify a # flag that is used only in the compilation phase and not in # linking. Such a scenario is extremely rare in practice. # # Even though use of the -pthread flag in linking would only print # a warning, this can be a nuisance for well-run software projects # that build with -Werror. So if the active version of Clang has # this misfeature, we search for an option to squash it. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether Clang needs flag to prevent \"argument unused\" warning when linking with -pthread" >&5 printf %s "checking whether Clang needs flag to prevent \"argument unused\" warning when linking with -pthread... " >&6; } if test ${ax_cv_PTHREAD_CLANG_NO_WARN_FLAG+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown # Create an alternate version of $ac_link that compiles and # links in two steps (.c -> .o, .o -> exe) instead of one # (.c -> exe), because the warning occurs only in the second # step ax_pthread_save_ac_link="$ac_link" ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' ax_pthread_link_step=`printf "%s\n" "$ac_link" | sed "$ax_pthread_sed"` ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" ax_pthread_save_CFLAGS="$CFLAGS" for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do if test "x$ax_pthread_try" = "xunknown" then : break fi CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" ac_link="$ax_pthread_save_ac_link" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main(void){return 0;} _ACEOF if ac_fn_c_try_link "$LINENO" then : ac_link="$ax_pthread_2step_ac_link" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main(void){return 0;} _ACEOF if ac_fn_c_try_link "$LINENO" then : break fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext done ac_link="$ax_pthread_save_ac_link" CFLAGS="$ax_pthread_save_CFLAGS" if test "x$ax_pthread_try" = "x" then : ax_pthread_try=no fi ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" >&5 printf "%s\n" "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" >&6; } case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in no | unknown) ;; *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; esac fi # $ax_pthread_clang = yes # Various other checks: if test "x$ax_pthread_ok" = "xyes"; then ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for joinable pthread attribute" >&5 printf %s "checking for joinable pthread attribute... " >&6; } if test ${ax_cv_PTHREAD_JOINABLE_ATTR+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_cv_PTHREAD_JOINABLE_ATTR=unknown for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { int attr = $ax_pthread_attr; return attr /* ; */ ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext done ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_JOINABLE_ATTR" >&5 printf "%s\n" "$ax_cv_PTHREAD_JOINABLE_ATTR" >&6; } if test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ test "x$ax_pthread_joinable_attr_defined" != "xyes" then : printf "%s\n" "#define PTHREAD_CREATE_JOINABLE $ax_cv_PTHREAD_JOINABLE_ATTR" >>confdefs.h ax_pthread_joinable_attr_defined=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether more special flags are required for pthreads" >&5 printf %s "checking whether more special flags are required for pthreads... " >&6; } if test ${ax_cv_PTHREAD_SPECIAL_FLAGS+y} then : printf %s "(cached) " >&6 else case e in #( e) ax_cv_PTHREAD_SPECIAL_FLAGS=no case $host_os in solaris*) ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" ;; esac ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_SPECIAL_FLAGS" >&5 printf "%s\n" "$ax_cv_PTHREAD_SPECIAL_FLAGS" >&6; } if test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ test "x$ax_pthread_special_flags_added" != "xyes" then : PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" ax_pthread_special_flags_added=yes fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for PTHREAD_PRIO_INHERIT" >&5 printf %s "checking for PTHREAD_PRIO_INHERIT... " >&6; } if test ${ax_cv_PTHREAD_PRIO_INHERIT+y} then : printf %s "(cached) " >&6 else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int main (void) { int i = PTHREAD_PRIO_INHERIT; return i; ; return 0; } _ACEOF if ac_fn_c_try_link "$LINENO" then : ax_cv_PTHREAD_PRIO_INHERIT=yes else case e in #( e) ax_cv_PTHREAD_PRIO_INHERIT=no ;; esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext conftest.$ac_ext ;; esac fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_PTHREAD_PRIO_INHERIT" >&5 printf "%s\n" "$ax_cv_PTHREAD_PRIO_INHERIT" >&6; } if test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ test "x$ax_pthread_prio_inherit_defined" != "xyes" then : printf "%s\n" "#define HAVE_PTHREAD_PRIO_INHERIT 1" >>confdefs.h ax_pthread_prio_inherit_defined=yes fi CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" # More AIX lossage: compile with *_r variant if test "x$GCC" != "xyes"; then case $host_os in aix*) case "x/$CC" in #( x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6) : #handle absolute path differently from PATH based program lookup case "x$CC" in #( x/*) : if as_fn_executable_p ${CC}_r then : PTHREAD_CC="${CC}_r" fi if test "x${CXX}" != "x" then : if as_fn_executable_p ${CXX}_r then : PTHREAD_CXX="${CXX}_r" fi fi ;; #( *) : for ac_prog in ${CC}_r do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_PTHREAD_CC+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$PTHREAD_CC"; then ac_cv_prog_PTHREAD_CC="$PTHREAD_CC" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_PTHREAD_CC="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi PTHREAD_CC=$ac_cv_prog_PTHREAD_CC if test -n "$PTHREAD_CC"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PTHREAD_CC" >&5 printf "%s\n" "$PTHREAD_CC" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$PTHREAD_CC" && break done test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" if test "x${CXX}" != "x" then : for ac_prog in ${CXX}_r do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } if test ${ac_cv_prog_PTHREAD_CXX+y} then : printf %s "(cached) " >&6 else case e in #( e) if test -n "$PTHREAD_CXX"; then ac_cv_prog_PTHREAD_CXX="$PTHREAD_CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac for ac_exec_ext in '' $ac_executable_extensions; do if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_PTHREAD_CXX="$ac_prog" printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi ;; esac fi PTHREAD_CXX=$ac_cv_prog_PTHREAD_CXX if test -n "$PTHREAD_CXX"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PTHREAD_CXX" >&5 printf "%s\n" "$PTHREAD_CXX" >&6; } else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 printf "%s\n" "no" >&6; } fi test -n "$PTHREAD_CXX" && break done test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" fi ;; esac ;; #( *) : ;; esac ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test "x$ax_pthread_ok" = "xyes"; then printf "%s\n" "#define HAVE_PTHREAD 1" >>confdefs.h : else ax_pthread_ok=no fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ;; esac if test "$ac_clang_active" = "yes" ; then CFLAGS="$CFLAGS $COMMONFLAGS -Wno-ignored-optimization-argument " CXXFLAGS="$CFLAGS -std=c++17 -pipe -Wno-variadic-macros -Wno-deprecated-declarations -Wno-unnecessary-virtual-specifier" else CFLAGS="$CFLAGS $COMMONFLAGS" CXXFLAGS="$CFLAGS -std=c++17 -pipe -Wno-variadic-macros -Wno-deprecated-declarations" fi LDFLAGS="$LDFLAGS $PROLDFLAGS" if test "$ac_gnuwin" = "yes"; then GNU_WIN_TRUE= GNU_WIN_FALSE='#' else GNU_WIN_TRUE='#' GNU_WIN_FALSE= fi ac_config_files="$ac_config_files Makefile libseq66/Makefile libseq66/include/Makefile libseq66/src/Makefile libsessions/Makefile libsessions/include/Makefile libsessions/src/Makefile m4/Makefile man/Makefile seq_portmidi/Makefile seq_portmidi/include/Makefile seq_portmidi/src/Makefile seq_rtmidi/Makefile seq_rtmidi/include/Makefile seq_rtmidi/src/Makefile seq_qt5/Makefile seq_qt5/include/Makefile seq_qt5/forms/Makefile seq_qt5/src/Makefile resources/pixmaps/Makefile Seq66qt5/Makefile Seq66cli/Makefile data/Makefile doc/Makefile doc/latex/Makefile doc/latex/tex/Makefile" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # 'ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* 'ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( *) { eval $ac_var=; unset $ac_var;} ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # 'set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # 'set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 printf "%s\n" "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else case $cache_file in #( */* | ?:*) mv -f confcache "$cache_file"$$ && mv -f "$cache_file"$$ "$cache_file" ;; #( *) mv -f confcache "$cache_file" ;; esac fi fi else { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DEFS=-DHAVE_CONFIG_H ac_libobjs= ac_ltlibobjs= U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking that generated files are newer than configure" >&5 printf %s "checking that generated files are newer than configure... " >&6; } if test -n "$am_sleep_pid"; then # Hide warnings about reused PIDs. wait $am_sleep_pid 2>/dev/null fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: done" >&5 printf "%s\n" "done" >&6; } case $enable_silent_rules in # ((( yes) AM_DEFAULT_VERBOSITY=0;; no) AM_DEFAULT_VERBOSITY=1;; esac if test $am_cv_make_support_nested_variables = yes; then AM_V='$(V)' AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' else AM_V=$AM_DEFAULT_VERBOSITY AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY fi if test -n "$EXEEXT"; then am__EXEEXT_TRUE= am__EXEEXT_FALSE='#' else am__EXEEXT_TRUE='#' am__EXEEXT_FALSE= fi if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then as_fn_error $? "conditional \"AMDEP\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then as_fn_error $? "conditional \"am__fastdepCC\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${am__fastdepCXX_TRUE}" && test -z "${am__fastdepCXX_FALSE}"; then as_fn_error $? "conditional \"am__fastdepCXX\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${BUILD_DOCS_TRUE}" && test -z "${BUILD_DOCS_FALSE}"; then as_fn_error $? "conditional \"BUILD_DOCS\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${BUILD_PORTMIDI_TRUE}" && test -z "${BUILD_PORTMIDI_FALSE}"; then as_fn_error $? "conditional \"BUILD_PORTMIDI\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${BUILD_QTMIDI_TRUE}" && test -z "${BUILD_QTMIDI_FALSE}"; then as_fn_error $? "conditional \"BUILD_QTMIDI\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${BUILD_RTCLI_TRUE}" && test -z "${BUILD_RTCLI_FALSE}"; then as_fn_error $? "conditional \"BUILD_RTCLI\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${BUILD_RTMIDI_TRUE}" && test -z "${BUILD_RTMIDI_FALSE}"; then as_fn_error $? "conditional \"BUILD_RTMIDI\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${BUILD_SESSIONS_TRUE}" && test -z "${BUILD_SESSIONS_FALSE}"; then as_fn_error $? "conditional \"BUILD_SESSIONS\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${DOCOVERAGE_TRUE}" && test -z "${DOCOVERAGE_FALSE}"; then as_fn_error $? "conditional \"DOCOVERAGE\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${DOPROFILE_TRUE}" && test -z "${DOPROFILE_FALSE}"; then as_fn_error $? "conditional \"DOPROFILE\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${DODEBUG_TRUE}" && test -z "${DODEBUG_FALSE}"; then as_fn_error $? "conditional \"DODEBUG\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi if test -z "${GNU_WIN_TRUE}" && test -z "${GNU_WIN_FALSE}"; then as_fn_error $? "conditional \"GNU_WIN\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} export SHELL _ASEOF cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 ## -------------------- ## ## M4sh Initialization. ## ## -------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case e in #( e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; esac ;; esac fi # Reset variables that may have inherited troublesome values from # the environment. # IFS needs to be set, to space, tab, and newline, in precisely that order. # (If _AS_PATH_WALK were called with IFS unset, it would have the # side effect of setting IFS to empty, thus disabling word splitting.) # Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl IFS=" "" $as_nl" PS1='$ ' PS2='> ' PS4='+ ' # Ensure predictable behavior from utilities with locale-dependent output. LC_ALL=C export LC_ALL LANGUAGE=C export LANGUAGE # We cannot yet rely on "unset" to work, but we need these variables # to be unset--not just set to an empty or harmless value--now, to # avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct # also avoids known problems related to "unset" and subshell syntax # in other old shells (e.g. bash 2.01 and pdksh 5.2.14). for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH do eval test \${$as_var+y} \ && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : done # Ensure that fds 0, 1, and 2 are open. if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || PATH_SEPARATOR=';' } fi # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS case $as_dir in #((( '') as_dir=./ ;; */) ;; *) as_dir=$as_dir/ ;; esac test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- # Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are # provided, also output the error to LOG_FD, referencing LINENO. Then exit the # script with STATUS, using 1 if that was 0. as_fn_error () { as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. as_fn_set_status () { return $1 } # as_fn_set_status # as_fn_exit STATUS # ----------------- # Exit the shell with STATUS, even in a "trap 0" or "set -e" context. as_fn_exit () { set +e as_fn_set_status $1 exit $1 } # as_fn_exit # as_fn_unset VAR # --------------- # Portably unset VAR. as_fn_unset () { { eval $1=; unset $1;} } as_unset=as_fn_unset # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null then : eval 'as_fn_append () { eval $1+=\$2 }' else case e in #( e) as_fn_append () { eval $1=\$$1\$2 } ;; esac fi # as_fn_append # as_fn_arith ARG... # ------------------ # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null then : eval 'as_fn_arith () { as_val=$(( $* )) }' else case e in #( e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } ;; esac fi # as_fn_arith if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits # Determine whether it's possible to make 'echo' print without a newline. # These variables are no longer used directly by Autoconf, but are AC_SUBSTed # for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) case `echo 'xy\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. xy) ECHO_C='\c';; *) echo `echo ksh88 bug on AIX 6.1` > /dev/null ECHO_T=' ';; esac;; *) ECHO_N='-n';; esac # For backward compatibility with old third-party macros, we provide # the shell variables $as_echo and $as_echo_n. New code should use # AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. as_echo='printf %s\n' as_echo_n='printf %s' rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir 2>/dev/null fi if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -pR' fi else as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null # as_fn_mkdir_p # ------------- # Create "$as_dir" as a directory, including parents if necessary. as_fn_mkdir_p () { case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || eval $as_mkdir_p || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" } # as_fn_mkdir_p if mkdir -p . 2>/dev/null; then as_mkdir_p='mkdir -p "$as_dir"' else test -d ./-p && rmdir ./-p as_mkdir_p=false fi # as_fn_executable_p FILE # ----------------------- # Test if FILE is an executable regular file. as_fn_executable_p () { test -f "$1" && test -x "$1" } # as_fn_executable_p as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" as_tr_sh="eval sed '$as_sed_sh'" # deprecated exec 6>&1 ## ----------------------------------- ## ## Main body of $CONFIG_STATUS script. ## ## ----------------------------------- ## _ASEOF test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Save the log message, to keep $0 and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by Seq66 $as_me 0.99.24, which was generated by GNU Autoconf 2.72. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF case $ac_config_files in *" "*) set x $ac_config_files; shift; ac_config_files=$*;; esac case $ac_config_headers in *" "*) set x $ac_config_headers; shift; ac_config_headers=$*;; esac cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" config_commands="$ac_config_commands" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ '$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. Usage: $0 [OPTION]... [TAG]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit --config print configuration, then exit -q, --quiet, --silent do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE --header=FILE[:TEMPLATE] instantiate the configuration header FILE Configuration files: $config_files Configuration headers: $config_headers Configuration commands: $config_commands Report bugs to ." _ACEOF ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ Seq66 config.status 0.99.24 configured by $0, generated by GNU Autoconf 2.72, with options \\"\$ac_cs_config\\" Copyright (C) 2023 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' INSTALL='$INSTALL' MKDIR_P='$MKDIR_P' AWK='$AWK' test -n "\$AWK" || AWK=awk _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # The default lists apply if the user does not specify any file. ac_need_defaults=: while test $# != 0 do case $1 in --*=?*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; --*=) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg= ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) printf "%s\n" "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) printf "%s\n" "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift case $ac_optarg in *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; esac as_fn_append CONFIG_HEADERS " '$ac_optarg'" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header as_fn_error $? "ambiguous option: '$1' Try '$0 --help' for more information.";; --help | --hel | -h ) printf "%s\n" "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) as_fn_error $? "unrecognized option: '$1' Try '$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX printf "%s\n" "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # # INIT-COMMANDS # PACKAGE="$PACKAGE" AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}" # The HP-UX ksh and POSIX shell print the target directory to stdout # if CDPATH is set. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH sed_quote_subst='$sed_quote_subst' double_quote_subst='$double_quote_subst' delay_variable_subst='$delay_variable_subst' macro_version='`$ECHO "$macro_version" | $SED "$delay_single_quote_subst"`' macro_revision='`$ECHO "$macro_revision" | $SED "$delay_single_quote_subst"`' enable_shared='`$ECHO "$enable_shared" | $SED "$delay_single_quote_subst"`' enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`' pic_mode='`$ECHO "$pic_mode" | $SED "$delay_single_quote_subst"`' enable_fast_install='`$ECHO "$enable_fast_install" | $SED "$delay_single_quote_subst"`' shared_archive_member_spec='`$ECHO "$shared_archive_member_spec" | $SED "$delay_single_quote_subst"`' SHELL='`$ECHO "$SHELL" | $SED "$delay_single_quote_subst"`' ECHO='`$ECHO "$ECHO" | $SED "$delay_single_quote_subst"`' PATH_SEPARATOR='`$ECHO "$PATH_SEPARATOR" | $SED "$delay_single_quote_subst"`' host_alias='`$ECHO "$host_alias" | $SED "$delay_single_quote_subst"`' host='`$ECHO "$host" | $SED "$delay_single_quote_subst"`' host_os='`$ECHO "$host_os" | $SED "$delay_single_quote_subst"`' build_alias='`$ECHO "$build_alias" | $SED "$delay_single_quote_subst"`' build='`$ECHO "$build" | $SED "$delay_single_quote_subst"`' build_os='`$ECHO "$build_os" | $SED "$delay_single_quote_subst"`' SED='`$ECHO "$SED" | $SED "$delay_single_quote_subst"`' Xsed='`$ECHO "$Xsed" | $SED "$delay_single_quote_subst"`' GREP='`$ECHO "$GREP" | $SED "$delay_single_quote_subst"`' EGREP='`$ECHO "$EGREP" | $SED "$delay_single_quote_subst"`' FGREP='`$ECHO "$FGREP" | $SED "$delay_single_quote_subst"`' LD='`$ECHO "$LD" | $SED "$delay_single_quote_subst"`' NM='`$ECHO "$NM" | $SED "$delay_single_quote_subst"`' LN_S='`$ECHO "$LN_S" | $SED "$delay_single_quote_subst"`' max_cmd_len='`$ECHO "$max_cmd_len" | $SED "$delay_single_quote_subst"`' ac_objext='`$ECHO "$ac_objext" | $SED "$delay_single_quote_subst"`' exeext='`$ECHO "$exeext" | $SED "$delay_single_quote_subst"`' lt_unset='`$ECHO "$lt_unset" | $SED "$delay_single_quote_subst"`' lt_SP2NL='`$ECHO "$lt_SP2NL" | $SED "$delay_single_quote_subst"`' lt_NL2SP='`$ECHO "$lt_NL2SP" | $SED "$delay_single_quote_subst"`' lt_cv_to_host_file_cmd='`$ECHO "$lt_cv_to_host_file_cmd" | $SED "$delay_single_quote_subst"`' lt_cv_to_tool_file_cmd='`$ECHO "$lt_cv_to_tool_file_cmd" | $SED "$delay_single_quote_subst"`' reload_flag='`$ECHO "$reload_flag" | $SED "$delay_single_quote_subst"`' reload_cmds='`$ECHO "$reload_cmds" | $SED "$delay_single_quote_subst"`' FILECMD='`$ECHO "$FILECMD" | $SED "$delay_single_quote_subst"`' OBJDUMP='`$ECHO "$OBJDUMP" | $SED "$delay_single_quote_subst"`' deplibs_check_method='`$ECHO "$deplibs_check_method" | $SED "$delay_single_quote_subst"`' file_magic_cmd='`$ECHO "$file_magic_cmd" | $SED "$delay_single_quote_subst"`' file_magic_glob='`$ECHO "$file_magic_glob" | $SED "$delay_single_quote_subst"`' want_nocaseglob='`$ECHO "$want_nocaseglob" | $SED "$delay_single_quote_subst"`' DLLTOOL='`$ECHO "$DLLTOOL" | $SED "$delay_single_quote_subst"`' sharedlib_from_linklib_cmd='`$ECHO "$sharedlib_from_linklib_cmd" | $SED "$delay_single_quote_subst"`' AR='`$ECHO "$AR" | $SED "$delay_single_quote_subst"`' lt_ar_flags='`$ECHO "$lt_ar_flags" | $SED "$delay_single_quote_subst"`' AR_FLAGS='`$ECHO "$AR_FLAGS" | $SED "$delay_single_quote_subst"`' archiver_list_spec='`$ECHO "$archiver_list_spec" | $SED "$delay_single_quote_subst"`' STRIP='`$ECHO "$STRIP" | $SED "$delay_single_quote_subst"`' RANLIB='`$ECHO "$RANLIB" | $SED "$delay_single_quote_subst"`' old_postinstall_cmds='`$ECHO "$old_postinstall_cmds" | $SED "$delay_single_quote_subst"`' old_postuninstall_cmds='`$ECHO "$old_postuninstall_cmds" | $SED "$delay_single_quote_subst"`' old_archive_cmds='`$ECHO "$old_archive_cmds" | $SED "$delay_single_quote_subst"`' lock_old_archive_extraction='`$ECHO "$lock_old_archive_extraction" | $SED "$delay_single_quote_subst"`' CC='`$ECHO "$CC" | $SED "$delay_single_quote_subst"`' CFLAGS='`$ECHO "$CFLAGS" | $SED "$delay_single_quote_subst"`' compiler='`$ECHO "$compiler" | $SED "$delay_single_quote_subst"`' GCC='`$ECHO "$GCC" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_pipe='`$ECHO "$lt_cv_sys_global_symbol_pipe" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_to_cdecl='`$ECHO "$lt_cv_sys_global_symbol_to_cdecl" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_to_import='`$ECHO "$lt_cv_sys_global_symbol_to_import" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_to_c_name_address='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address" | $SED "$delay_single_quote_subst"`' lt_cv_sys_global_symbol_to_c_name_address_lib_prefix='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address_lib_prefix" | $SED "$delay_single_quote_subst"`' lt_cv_nm_interface='`$ECHO "$lt_cv_nm_interface" | $SED "$delay_single_quote_subst"`' nm_file_list_spec='`$ECHO "$nm_file_list_spec" | $SED "$delay_single_quote_subst"`' lt_sysroot='`$ECHO "$lt_sysroot" | $SED "$delay_single_quote_subst"`' lt_cv_truncate_bin='`$ECHO "$lt_cv_truncate_bin" | $SED "$delay_single_quote_subst"`' objdir='`$ECHO "$objdir" | $SED "$delay_single_quote_subst"`' MAGIC_CMD='`$ECHO "$MAGIC_CMD" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_no_builtin_flag='`$ECHO "$lt_prog_compiler_no_builtin_flag" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_pic='`$ECHO "$lt_prog_compiler_pic" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_wl='`$ECHO "$lt_prog_compiler_wl" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_static='`$ECHO "$lt_prog_compiler_static" | $SED "$delay_single_quote_subst"`' lt_cv_prog_compiler_c_o='`$ECHO "$lt_cv_prog_compiler_c_o" | $SED "$delay_single_quote_subst"`' need_locks='`$ECHO "$need_locks" | $SED "$delay_single_quote_subst"`' MANIFEST_TOOL='`$ECHO "$MANIFEST_TOOL" | $SED "$delay_single_quote_subst"`' DSYMUTIL='`$ECHO "$DSYMUTIL" | $SED "$delay_single_quote_subst"`' NMEDIT='`$ECHO "$NMEDIT" | $SED "$delay_single_quote_subst"`' LIPO='`$ECHO "$LIPO" | $SED "$delay_single_quote_subst"`' OTOOL='`$ECHO "$OTOOL" | $SED "$delay_single_quote_subst"`' OTOOL64='`$ECHO "$OTOOL64" | $SED "$delay_single_quote_subst"`' libext='`$ECHO "$libext" | $SED "$delay_single_quote_subst"`' shrext_cmds='`$ECHO "$shrext_cmds" | $SED "$delay_single_quote_subst"`' extract_expsyms_cmds='`$ECHO "$extract_expsyms_cmds" | $SED "$delay_single_quote_subst"`' archive_cmds_need_lc='`$ECHO "$archive_cmds_need_lc" | $SED "$delay_single_quote_subst"`' enable_shared_with_static_runtimes='`$ECHO "$enable_shared_with_static_runtimes" | $SED "$delay_single_quote_subst"`' export_dynamic_flag_spec='`$ECHO "$export_dynamic_flag_spec" | $SED "$delay_single_quote_subst"`' whole_archive_flag_spec='`$ECHO "$whole_archive_flag_spec" | $SED "$delay_single_quote_subst"`' compiler_needs_object='`$ECHO "$compiler_needs_object" | $SED "$delay_single_quote_subst"`' old_archive_from_new_cmds='`$ECHO "$old_archive_from_new_cmds" | $SED "$delay_single_quote_subst"`' old_archive_from_expsyms_cmds='`$ECHO "$old_archive_from_expsyms_cmds" | $SED "$delay_single_quote_subst"`' archive_cmds='`$ECHO "$archive_cmds" | $SED "$delay_single_quote_subst"`' archive_expsym_cmds='`$ECHO "$archive_expsym_cmds" | $SED "$delay_single_quote_subst"`' module_cmds='`$ECHO "$module_cmds" | $SED "$delay_single_quote_subst"`' module_expsym_cmds='`$ECHO "$module_expsym_cmds" | $SED "$delay_single_quote_subst"`' with_gnu_ld='`$ECHO "$with_gnu_ld" | $SED "$delay_single_quote_subst"`' allow_undefined_flag='`$ECHO "$allow_undefined_flag" | $SED "$delay_single_quote_subst"`' no_undefined_flag='`$ECHO "$no_undefined_flag" | $SED "$delay_single_quote_subst"`' hardcode_libdir_flag_spec='`$ECHO "$hardcode_libdir_flag_spec" | $SED "$delay_single_quote_subst"`' hardcode_libdir_separator='`$ECHO "$hardcode_libdir_separator" | $SED "$delay_single_quote_subst"`' hardcode_direct='`$ECHO "$hardcode_direct" | $SED "$delay_single_quote_subst"`' hardcode_direct_absolute='`$ECHO "$hardcode_direct_absolute" | $SED "$delay_single_quote_subst"`' hardcode_minus_L='`$ECHO "$hardcode_minus_L" | $SED "$delay_single_quote_subst"`' hardcode_shlibpath_var='`$ECHO "$hardcode_shlibpath_var" | $SED "$delay_single_quote_subst"`' hardcode_automatic='`$ECHO "$hardcode_automatic" | $SED "$delay_single_quote_subst"`' inherit_rpath='`$ECHO "$inherit_rpath" | $SED "$delay_single_quote_subst"`' link_all_deplibs='`$ECHO "$link_all_deplibs" | $SED "$delay_single_quote_subst"`' always_export_symbols='`$ECHO "$always_export_symbols" | $SED "$delay_single_quote_subst"`' export_symbols_cmds='`$ECHO "$export_symbols_cmds" | $SED "$delay_single_quote_subst"`' exclude_expsyms='`$ECHO "$exclude_expsyms" | $SED "$delay_single_quote_subst"`' include_expsyms='`$ECHO "$include_expsyms" | $SED "$delay_single_quote_subst"`' prelink_cmds='`$ECHO "$prelink_cmds" | $SED "$delay_single_quote_subst"`' postlink_cmds='`$ECHO "$postlink_cmds" | $SED "$delay_single_quote_subst"`' file_list_spec='`$ECHO "$file_list_spec" | $SED "$delay_single_quote_subst"`' variables_saved_for_relink='`$ECHO "$variables_saved_for_relink" | $SED "$delay_single_quote_subst"`' need_lib_prefix='`$ECHO "$need_lib_prefix" | $SED "$delay_single_quote_subst"`' need_version='`$ECHO "$need_version" | $SED "$delay_single_quote_subst"`' version_type='`$ECHO "$version_type" | $SED "$delay_single_quote_subst"`' runpath_var='`$ECHO "$runpath_var" | $SED "$delay_single_quote_subst"`' shlibpath_var='`$ECHO "$shlibpath_var" | $SED "$delay_single_quote_subst"`' shlibpath_overrides_runpath='`$ECHO "$shlibpath_overrides_runpath" | $SED "$delay_single_quote_subst"`' libname_spec='`$ECHO "$libname_spec" | $SED "$delay_single_quote_subst"`' library_names_spec='`$ECHO "$library_names_spec" | $SED "$delay_single_quote_subst"`' soname_spec='`$ECHO "$soname_spec" | $SED "$delay_single_quote_subst"`' install_override_mode='`$ECHO "$install_override_mode" | $SED "$delay_single_quote_subst"`' postinstall_cmds='`$ECHO "$postinstall_cmds" | $SED "$delay_single_quote_subst"`' postuninstall_cmds='`$ECHO "$postuninstall_cmds" | $SED "$delay_single_quote_subst"`' finish_cmds='`$ECHO "$finish_cmds" | $SED "$delay_single_quote_subst"`' finish_eval='`$ECHO "$finish_eval" | $SED "$delay_single_quote_subst"`' hardcode_into_libs='`$ECHO "$hardcode_into_libs" | $SED "$delay_single_quote_subst"`' sys_lib_search_path_spec='`$ECHO "$sys_lib_search_path_spec" | $SED "$delay_single_quote_subst"`' configure_time_dlsearch_path='`$ECHO "$configure_time_dlsearch_path" | $SED "$delay_single_quote_subst"`' configure_time_lt_sys_library_path='`$ECHO "$configure_time_lt_sys_library_path" | $SED "$delay_single_quote_subst"`' hardcode_action='`$ECHO "$hardcode_action" | $SED "$delay_single_quote_subst"`' enable_dlopen='`$ECHO "$enable_dlopen" | $SED "$delay_single_quote_subst"`' enable_dlopen_self='`$ECHO "$enable_dlopen_self" | $SED "$delay_single_quote_subst"`' enable_dlopen_self_static='`$ECHO "$enable_dlopen_self_static" | $SED "$delay_single_quote_subst"`' old_striplib='`$ECHO "$old_striplib" | $SED "$delay_single_quote_subst"`' striplib='`$ECHO "$striplib" | $SED "$delay_single_quote_subst"`' compiler_lib_search_dirs='`$ECHO "$compiler_lib_search_dirs" | $SED "$delay_single_quote_subst"`' predep_objects='`$ECHO "$predep_objects" | $SED "$delay_single_quote_subst"`' postdep_objects='`$ECHO "$postdep_objects" | $SED "$delay_single_quote_subst"`' predeps='`$ECHO "$predeps" | $SED "$delay_single_quote_subst"`' postdeps='`$ECHO "$postdeps" | $SED "$delay_single_quote_subst"`' compiler_lib_search_path='`$ECHO "$compiler_lib_search_path" | $SED "$delay_single_quote_subst"`' LD_CXX='`$ECHO "$LD_CXX" | $SED "$delay_single_quote_subst"`' reload_flag_CXX='`$ECHO "$reload_flag_CXX" | $SED "$delay_single_quote_subst"`' reload_cmds_CXX='`$ECHO "$reload_cmds_CXX" | $SED "$delay_single_quote_subst"`' old_archive_cmds_CXX='`$ECHO "$old_archive_cmds_CXX" | $SED "$delay_single_quote_subst"`' compiler_CXX='`$ECHO "$compiler_CXX" | $SED "$delay_single_quote_subst"`' GCC_CXX='`$ECHO "$GCC_CXX" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_no_builtin_flag_CXX='`$ECHO "$lt_prog_compiler_no_builtin_flag_CXX" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_pic_CXX='`$ECHO "$lt_prog_compiler_pic_CXX" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_wl_CXX='`$ECHO "$lt_prog_compiler_wl_CXX" | $SED "$delay_single_quote_subst"`' lt_prog_compiler_static_CXX='`$ECHO "$lt_prog_compiler_static_CXX" | $SED "$delay_single_quote_subst"`' lt_cv_prog_compiler_c_o_CXX='`$ECHO "$lt_cv_prog_compiler_c_o_CXX" | $SED "$delay_single_quote_subst"`' archive_cmds_need_lc_CXX='`$ECHO "$archive_cmds_need_lc_CXX" | $SED "$delay_single_quote_subst"`' enable_shared_with_static_runtimes_CXX='`$ECHO "$enable_shared_with_static_runtimes_CXX" | $SED "$delay_single_quote_subst"`' export_dynamic_flag_spec_CXX='`$ECHO "$export_dynamic_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' whole_archive_flag_spec_CXX='`$ECHO "$whole_archive_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' compiler_needs_object_CXX='`$ECHO "$compiler_needs_object_CXX" | $SED "$delay_single_quote_subst"`' old_archive_from_new_cmds_CXX='`$ECHO "$old_archive_from_new_cmds_CXX" | $SED "$delay_single_quote_subst"`' old_archive_from_expsyms_cmds_CXX='`$ECHO "$old_archive_from_expsyms_cmds_CXX" | $SED "$delay_single_quote_subst"`' archive_cmds_CXX='`$ECHO "$archive_cmds_CXX" | $SED "$delay_single_quote_subst"`' archive_expsym_cmds_CXX='`$ECHO "$archive_expsym_cmds_CXX" | $SED "$delay_single_quote_subst"`' module_cmds_CXX='`$ECHO "$module_cmds_CXX" | $SED "$delay_single_quote_subst"`' module_expsym_cmds_CXX='`$ECHO "$module_expsym_cmds_CXX" | $SED "$delay_single_quote_subst"`' with_gnu_ld_CXX='`$ECHO "$with_gnu_ld_CXX" | $SED "$delay_single_quote_subst"`' allow_undefined_flag_CXX='`$ECHO "$allow_undefined_flag_CXX" | $SED "$delay_single_quote_subst"`' no_undefined_flag_CXX='`$ECHO "$no_undefined_flag_CXX" | $SED "$delay_single_quote_subst"`' hardcode_libdir_flag_spec_CXX='`$ECHO "$hardcode_libdir_flag_spec_CXX" | $SED "$delay_single_quote_subst"`' hardcode_libdir_separator_CXX='`$ECHO "$hardcode_libdir_separator_CXX" | $SED "$delay_single_quote_subst"`' hardcode_direct_CXX='`$ECHO "$hardcode_direct_CXX" | $SED "$delay_single_quote_subst"`' hardcode_direct_absolute_CXX='`$ECHO "$hardcode_direct_absolute_CXX" | $SED "$delay_single_quote_subst"`' hardcode_minus_L_CXX='`$ECHO "$hardcode_minus_L_CXX" | $SED "$delay_single_quote_subst"`' hardcode_shlibpath_var_CXX='`$ECHO "$hardcode_shlibpath_var_CXX" | $SED "$delay_single_quote_subst"`' hardcode_automatic_CXX='`$ECHO "$hardcode_automatic_CXX" | $SED "$delay_single_quote_subst"`' inherit_rpath_CXX='`$ECHO "$inherit_rpath_CXX" | $SED "$delay_single_quote_subst"`' link_all_deplibs_CXX='`$ECHO "$link_all_deplibs_CXX" | $SED "$delay_single_quote_subst"`' always_export_symbols_CXX='`$ECHO "$always_export_symbols_CXX" | $SED "$delay_single_quote_subst"`' export_symbols_cmds_CXX='`$ECHO "$export_symbols_cmds_CXX" | $SED "$delay_single_quote_subst"`' exclude_expsyms_CXX='`$ECHO "$exclude_expsyms_CXX" | $SED "$delay_single_quote_subst"`' include_expsyms_CXX='`$ECHO "$include_expsyms_CXX" | $SED "$delay_single_quote_subst"`' prelink_cmds_CXX='`$ECHO "$prelink_cmds_CXX" | $SED "$delay_single_quote_subst"`' postlink_cmds_CXX='`$ECHO "$postlink_cmds_CXX" | $SED "$delay_single_quote_subst"`' file_list_spec_CXX='`$ECHO "$file_list_spec_CXX" | $SED "$delay_single_quote_subst"`' hardcode_action_CXX='`$ECHO "$hardcode_action_CXX" | $SED "$delay_single_quote_subst"`' compiler_lib_search_dirs_CXX='`$ECHO "$compiler_lib_search_dirs_CXX" | $SED "$delay_single_quote_subst"`' predep_objects_CXX='`$ECHO "$predep_objects_CXX" | $SED "$delay_single_quote_subst"`' postdep_objects_CXX='`$ECHO "$postdep_objects_CXX" | $SED "$delay_single_quote_subst"`' predeps_CXX='`$ECHO "$predeps_CXX" | $SED "$delay_single_quote_subst"`' postdeps_CXX='`$ECHO "$postdeps_CXX" | $SED "$delay_single_quote_subst"`' compiler_lib_search_path_CXX='`$ECHO "$compiler_lib_search_path_CXX" | $SED "$delay_single_quote_subst"`' LTCC='$LTCC' LTCFLAGS='$LTCFLAGS' compiler='$compiler_DEFAULT' # A function that is used when there is no print builtin or printf. func_fallback_echo () { eval 'cat <<_LTECHO_EOF \$1 _LTECHO_EOF' } # Quote evaled strings. for var in SHELL \ ECHO \ PATH_SEPARATOR \ SED \ GREP \ EGREP \ FGREP \ LD \ NM \ LN_S \ lt_SP2NL \ lt_NL2SP \ reload_flag \ FILECMD \ OBJDUMP \ deplibs_check_method \ file_magic_cmd \ file_magic_glob \ want_nocaseglob \ DLLTOOL \ sharedlib_from_linklib_cmd \ AR \ archiver_list_spec \ STRIP \ RANLIB \ CC \ CFLAGS \ compiler \ lt_cv_sys_global_symbol_pipe \ lt_cv_sys_global_symbol_to_cdecl \ lt_cv_sys_global_symbol_to_import \ lt_cv_sys_global_symbol_to_c_name_address \ lt_cv_sys_global_symbol_to_c_name_address_lib_prefix \ lt_cv_nm_interface \ nm_file_list_spec \ lt_cv_truncate_bin \ lt_prog_compiler_no_builtin_flag \ lt_prog_compiler_pic \ lt_prog_compiler_wl \ lt_prog_compiler_static \ lt_cv_prog_compiler_c_o \ need_locks \ MANIFEST_TOOL \ DSYMUTIL \ NMEDIT \ LIPO \ OTOOL \ OTOOL64 \ shrext_cmds \ export_dynamic_flag_spec \ whole_archive_flag_spec \ compiler_needs_object \ with_gnu_ld \ allow_undefined_flag \ no_undefined_flag \ hardcode_libdir_flag_spec \ hardcode_libdir_separator \ exclude_expsyms \ include_expsyms \ file_list_spec \ variables_saved_for_relink \ libname_spec \ library_names_spec \ soname_spec \ install_override_mode \ finish_eval \ old_striplib \ striplib \ compiler_lib_search_dirs \ predep_objects \ postdep_objects \ predeps \ postdeps \ compiler_lib_search_path \ LD_CXX \ reload_flag_CXX \ compiler_CXX \ lt_prog_compiler_no_builtin_flag_CXX \ lt_prog_compiler_pic_CXX \ lt_prog_compiler_wl_CXX \ lt_prog_compiler_static_CXX \ lt_cv_prog_compiler_c_o_CXX \ export_dynamic_flag_spec_CXX \ whole_archive_flag_spec_CXX \ compiler_needs_object_CXX \ with_gnu_ld_CXX \ allow_undefined_flag_CXX \ no_undefined_flag_CXX \ hardcode_libdir_flag_spec_CXX \ hardcode_libdir_separator_CXX \ exclude_expsyms_CXX \ include_expsyms_CXX \ file_list_spec_CXX \ compiler_lib_search_dirs_CXX \ predep_objects_CXX \ postdep_objects_CXX \ predeps_CXX \ postdeps_CXX \ compiler_lib_search_path_CXX; do case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in *[\\\\\\\`\\"\\\$]*) eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes ;; *) eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" ;; esac done # Double-quote double-evaled strings. for var in reload_cmds \ old_postinstall_cmds \ old_postuninstall_cmds \ old_archive_cmds \ extract_expsyms_cmds \ old_archive_from_new_cmds \ old_archive_from_expsyms_cmds \ archive_cmds \ archive_expsym_cmds \ module_cmds \ module_expsym_cmds \ export_symbols_cmds \ prelink_cmds \ postlink_cmds \ postinstall_cmds \ postuninstall_cmds \ finish_cmds \ sys_lib_search_path_spec \ configure_time_dlsearch_path \ configure_time_lt_sys_library_path \ reload_cmds_CXX \ old_archive_cmds_CXX \ old_archive_from_new_cmds_CXX \ old_archive_from_expsyms_cmds_CXX \ archive_cmds_CXX \ archive_expsym_cmds_CXX \ module_cmds_CXX \ module_expsym_cmds_CXX \ export_symbols_cmds_CXX \ prelink_cmds_CXX \ postlink_cmds_CXX; do case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in *[\\\\\\\`\\"\\\$]*) eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes ;; *) eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" ;; esac done ac_aux_dir='$ac_aux_dir' # See if we are running on zsh, and set the options that allow our # commands through without removal of \ escapes INIT. if test -n "\${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi PACKAGE='$PACKAGE' VERSION='$VERSION' RM='$RM' ofile='$ofile' _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "include/config.h") CONFIG_HEADERS="$CONFIG_HEADERS include/config.h" ;; "include/seq66-config.h") CONFIG_COMMANDS="$CONFIG_COMMANDS include/seq66-config.h" ;; "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "libseq66/Makefile") CONFIG_FILES="$CONFIG_FILES libseq66/Makefile" ;; "libseq66/include/Makefile") CONFIG_FILES="$CONFIG_FILES libseq66/include/Makefile" ;; "libseq66/src/Makefile") CONFIG_FILES="$CONFIG_FILES libseq66/src/Makefile" ;; "libsessions/Makefile") CONFIG_FILES="$CONFIG_FILES libsessions/Makefile" ;; "libsessions/include/Makefile") CONFIG_FILES="$CONFIG_FILES libsessions/include/Makefile" ;; "libsessions/src/Makefile") CONFIG_FILES="$CONFIG_FILES libsessions/src/Makefile" ;; "m4/Makefile") CONFIG_FILES="$CONFIG_FILES m4/Makefile" ;; "man/Makefile") CONFIG_FILES="$CONFIG_FILES man/Makefile" ;; "seq_portmidi/Makefile") CONFIG_FILES="$CONFIG_FILES seq_portmidi/Makefile" ;; "seq_portmidi/include/Makefile") CONFIG_FILES="$CONFIG_FILES seq_portmidi/include/Makefile" ;; "seq_portmidi/src/Makefile") CONFIG_FILES="$CONFIG_FILES seq_portmidi/src/Makefile" ;; "seq_rtmidi/Makefile") CONFIG_FILES="$CONFIG_FILES seq_rtmidi/Makefile" ;; "seq_rtmidi/include/Makefile") CONFIG_FILES="$CONFIG_FILES seq_rtmidi/include/Makefile" ;; "seq_rtmidi/src/Makefile") CONFIG_FILES="$CONFIG_FILES seq_rtmidi/src/Makefile" ;; "seq_qt5/Makefile") CONFIG_FILES="$CONFIG_FILES seq_qt5/Makefile" ;; "seq_qt5/include/Makefile") CONFIG_FILES="$CONFIG_FILES seq_qt5/include/Makefile" ;; "seq_qt5/forms/Makefile") CONFIG_FILES="$CONFIG_FILES seq_qt5/forms/Makefile" ;; "seq_qt5/src/Makefile") CONFIG_FILES="$CONFIG_FILES seq_qt5/src/Makefile" ;; "resources/pixmaps/Makefile") CONFIG_FILES="$CONFIG_FILES resources/pixmaps/Makefile" ;; "Seq66qt5/Makefile") CONFIG_FILES="$CONFIG_FILES Seq66qt5/Makefile" ;; "Seq66cli/Makefile") CONFIG_FILES="$CONFIG_FILES Seq66cli/Makefile" ;; "data/Makefile") CONFIG_FILES="$CONFIG_FILES data/Makefile" ;; "doc/Makefile") CONFIG_FILES="$CONFIG_FILES doc/Makefile" ;; "doc/latex/Makefile") CONFIG_FILES="$CONFIG_FILES doc/latex/Makefile" ;; "doc/latex/tex/Makefile") CONFIG_FILES="$CONFIG_FILES doc/latex/tex/Makefile" ;; *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files test ${CONFIG_HEADERS+y} || CONFIG_HEADERS=$config_headers test ${CONFIG_COMMANDS+y} || CONFIG_COMMANDS=$config_commands fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to '$tmp'. $debug || { tmp= ac_tmp= trap 'exit_status=$? : "${ac_tmp:=$tmp}" { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status ' 0 trap 'as_fn_exit 1' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. # This happens for instance with './config.status config.h'. if test -n "$CONFIG_FILES"; then ac_cr=`echo X | tr X '\015'` # On cygwin, bash can eat \r inside `` if the user requested igncr. # But we know of no other shell where ac_cr would be empty at this # point, so we can use a bashism as a fallback. if test "x$ac_cr" = x; then eval ac_cr=\$\'\\r\' fi ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then ac_cs_awk_cr='\\r' else ac_cs_awk_cr=$ac_cr fi echo 'BEGIN {' >"$ac_tmp/subs1.awk" && _ACEOF { echo "cat >conf$$subs.awk <<_ACEOF" && echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && echo "_ACEOF" } >conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` ac_delim='%!_!# ' for ac_last_try in false false false false false :; do . ./conf$$subs.sh || as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` if test $ac_delim_n = $ac_delim_num; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done rm -f conf$$subs.sh cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && _ACEOF sed -n ' h s/^/S["/; s/!.*/"]=/ p g s/^[^!]*!// :repl t repl s/'"$ac_delim"'$// t delim :nl h s/\(.\{148\}\)..*/\1/ t more1 s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ p n b repl :more1 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t nl :delim h s/\(.\{148\}\)..*/\1/ t more2 s/["\\]/\\&/g; s/^/"/; s/$/"/ p b :more2 s/["\\]/\\&/g; s/^/"/; s/$/"\\/ p g s/.\{148\}// t delim ' >$CONFIG_STATUS || ac_write_fail=1 rm -f conf$$subs.awk cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 _ACAWK cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && for (key in S) S_is_set[key] = 1 FS = "" } { line = $ 0 nfields = split(line, field, "@") substed = 0 len = length(field[1]) for (i = 2; i < nfields; i++) { key = field[i] keylen = length(key) if (S_is_set[key]) { value = S[key] line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) len += length(value) + length(field[++i]) substed = 1 } else len += 1 + keylen } print line } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" else cat fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 _ACEOF # VPATH may cause trouble with some makes, so we remove sole $(srcdir), # ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ h s/// s/^/:/ s/[ ]*$/:/ s/:\$(srcdir):/:/g s/:\${srcdir}:/:/g s/:@srcdir@:/:/g s/^:*// s/:*$// x s/\(=[ ]*\).*/\1/ G s/\n// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 fi # test -n "$CONFIG_FILES" # Set up the scripts for CONFIG_HEADERS section. # No need to generate them if there are no CONFIG_HEADERS. # This happens for instance with './config.status Makefile'. if test -n "$CONFIG_HEADERS"; then cat >"$ac_tmp/defines.awk" <<\_ACAWK || BEGIN { _ACEOF # Transform confdefs.h into an awk script 'defines.awk', embedded as # here-document in config.status, that substitutes the proper values into # config.h.in to produce config.h. # Create a delimiter string that does not exist in confdefs.h, to ease # handling of long lines. ac_delim='%!_!# ' for ac_last_try in false false :; do ac_tt=`sed -n "/$ac_delim/p" confdefs.h` if test -z "$ac_tt"; then break elif $ac_last_try; then as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done # For the awk script, D is an array of macro values keyed by name, # likewise P contains macro parameters if any. Preserve backslash # newline sequences. ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* sed -n ' s/.\{148\}/&'"$ac_delim"'/g t rset :rset s/^[ ]*#[ ]*define[ ][ ]*/ / t def d :def s/\\$// t bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3"/p s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p d :bsnl s/["\\]/\\&/g s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ D["\1"]=" \3\\\\\\n"\\/p t cont s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p t cont d :cont n s/.\{148\}/&'"$ac_delim"'/g t clear :clear s/\\$// t bsnlc s/["\\]/\\&/g; s/^/"/; s/$/"/p d :bsnlc s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p b cont ' >$CONFIG_STATUS || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 for (key in D) D_is_set[key] = 1 FS = "" } /^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { line = \$ 0 split(line, arg, " ") if (arg[1] == "#") { defundef = arg[2] mac1 = arg[3] } else { defundef = substr(arg[1], 2) mac1 = arg[2] } split(mac1, mac2, "(") #) macro = mac2[1] prefix = substr(line, 1, index(line, defundef) - 1) if (D_is_set[macro]) { # Preserve the white space surrounding the "#". print prefix "define", macro P[macro] D[macro] next } else { # Replace #undef with comments. This is necessary, for example, # in the case of _POSIX_SOURCE, which is predefined and required # on some systems where configure will not decide to define it. if (defundef == "undef") { print "/*", prefix defundef, macro, "*/" next } } } { print } _ACAWK _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 fi # test -n "$CONFIG_HEADERS" eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS" shift for ac_tag do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain ':'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; esac case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done # Let's still pretend it is 'configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 printf "%s\n" "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) ac_sed_conf_input=`printf "%s\n" "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac case $ac_tag in *:-:* | *:-) cat >"$ac_tmp/stdin" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # case $INSTALL in [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; esac ac_MKDIR_P=$MKDIR_P case $MKDIR_P in [\\/$]* | ?:[\\/]* ) ;; */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;; esac _ACEOF # Neutralize VPATH when '$srcdir' = '.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_sed_extra="$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s|@configure_input@|$ac_sed_conf_input|;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@top_build_prefix@&$ac_top_build_prefix&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t s&@MKDIR_P@&$ac_MKDIR_P&;t t " eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 rm -f "$ac_tmp/stdin" case $ac_file in -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; esac \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; :H) # # CONFIG_HEADER # if test x"$ac_file" != x-; then { printf "%s\n" "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" } >"$ac_tmp/config.h" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 printf "%s\n" "$as_me: $ac_file is unchanged" >&6;} else rm -f "$ac_file" mv "$ac_tmp/config.h" "$ac_file" \ || as_fn_error $? "could not create $ac_file" "$LINENO" 5 fi else printf "%s\n" "/* $configure_input */" >&1 \ && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ || as_fn_error $? "could not create -" "$LINENO" 5 fi # Compute "$ac_file"'s index in $config_headers. _am_arg="$ac_file" _am_stamp_count=1 for _am_header in $config_headers :; do case $_am_header in $_am_arg | $_am_arg:* ) break ;; * ) _am_stamp_count=`expr $_am_stamp_count + 1` ;; esac done echo "timestamp for $_am_arg" >`$as_dirname -- "$_am_arg" || $as_expr X"$_am_arg" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$_am_arg" : 'X\(//\)[^/]' \| \ X"$_am_arg" : 'X\(//\)$' \| \ X"$_am_arg" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$_am_arg" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'`/stamp-h$_am_stamp_count ;; :C) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 printf "%s\n" "$as_me: executing $ac_file commands" >&6;} ;; esac case $ac_file$ac_mode in "include/seq66-config.h":C) ac_prefix_conf_OUT=`echo include/seq66-config.h` ac_prefix_conf_DEF=`echo _$ac_prefix_conf_OUT | sed -e "y:abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:" -e "s/[^abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]/_/g"` ac_prefix_conf_PKG=`echo seq66` ac_prefix_conf_LOW=`echo _$ac_prefix_conf_PKG | sed -e "y:ABCDEFGHIJKLMNOPQRSTUVWXYZ-:abcdefghijklmnopqrstuvwxyz_:"` ac_prefix_conf_UPP=`echo $ac_prefix_conf_PKG | sed -e "y:abcdefghijklmnopqrstuvwxyz-:ABCDEFGHIJKLMNOPQRSTUVWXYZ_:" -e "/^[0123456789]/s/^/_/"` ac_prefix_conf_INP=`echo "" | sed -e 's/ *//'` if test ".$ac_prefix_conf_INP" = "."; then for ac_file in : $CONFIG_HEADERS; do test "_$ac_file" = _: && continue case "$ac_file" in *.h) ac_prefix_conf_INP=$ac_file ;; *) esac test ".$ac_prefix_conf_INP" != "." && break done fi if test ".$ac_prefix_conf_INP" = "."; then case "$ac_prefix_conf_OUT" in */*) ac_prefix_conf_INP=`basename "$ac_prefix_conf_OUT"` ;; *-*) ac_prefix_conf_INP=`echo "$ac_prefix_conf_OUT" | sed -e "s/[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_]*-//"` ;; *) ac_prefix_conf_INP=config.h ;; esac fi if test -z "$ac_prefix_conf_PKG" ; then as_fn_error $? "no prefix for _PREFIX_PKG_CONFIG_H" "$LINENO" 5 else if test ! -f "$ac_prefix_conf_INP" ; then if test -f "$srcdir/$ac_prefix_conf_INP" ; then ac_prefix_conf_INP="$srcdir/$ac_prefix_conf_INP" fi fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_prefix_conf_OUT - prefix $ac_prefix_conf_UPP for $ac_prefix_conf_INP defines" >&5 printf "%s\n" "$as_me: creating $ac_prefix_conf_OUT - prefix $ac_prefix_conf_UPP for $ac_prefix_conf_INP defines" >&6;} if test -f $ac_prefix_conf_INP ; then printf "%s\n" "s/^#undef *\\([ABCDEFGHIJKLMNOPQRSTUVWXYZ_]\\)/#undef $ac_prefix_conf_UPP""_\\1/" > conftest.prefix printf "%s\n" "s/^#undef *\\([abcdefghijklmnopqrstuvwxyz]\\)/#undef $ac_prefix_conf_LOW""_\\1/" >> conftest.prefix printf "%s\n" "s/^#define *\\([ABCDEFGHIJKLMNOPQRSTUVWXYZ_][abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_]*\\)\\(.*\\)/#ifndef $ac_prefix_conf_UPP""_\\1\\" >> conftest.prefix printf "%s\n" "#define $ac_prefix_conf_UPP""_\\1\\2\\" >> conftest.prefix printf "%s\n" "#endif/" >> conftest.prefix printf "%s\n" "s/^#define *\\([abcdefghijklmnopqrstuvwxyz][abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_]*\\)\\(.*\\)/#ifndef $ac_prefix_conf_LOW""_\\1\\" >> conftest.prefix printf "%s\n" "#define $ac_prefix_conf_LOW""_\\1\\2\\" >> conftest.prefix printf "%s\n" "#endif/" >> conftest.prefix # now executing _script on _DEF input to create _OUT output file echo "#ifndef $ac_prefix_conf_DEF" >$tmp/pconfig.h echo "#define $ac_prefix_conf_DEF 1" >>$tmp/pconfig.h echo ' ' >>$tmp/pconfig.h echo /'*' $ac_prefix_conf_OUT. Generated automatically at end of configure. '*'/ >>$tmp/pconfig.h sed -f conftest.prefix $ac_prefix_conf_INP >>$tmp/pconfig.h echo ' ' >>$tmp/pconfig.h echo '/* once:' $ac_prefix_conf_DEF '*/' >>$tmp/pconfig.h echo "#endif" >>$tmp/pconfig.h if cmp -s $ac_prefix_conf_OUT $tmp/pconfig.h 2>/dev/null; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: $ac_prefix_conf_OUT is unchanged" >&5 printf "%s\n" "$as_me: $ac_prefix_conf_OUT is unchanged" >&6;} else ac_dir=`$as_dirname -- "$ac_prefix_conf_OUT" || $as_expr X"$ac_prefix_conf_OUT" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_prefix_conf_OUT" : 'X\(//\)[^/]' \| \ X"$ac_prefix_conf_OUT" : 'X\(//\)$' \| \ X"$ac_prefix_conf_OUT" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$ac_prefix_conf_OUT" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` as_dir="$ac_dir"; as_fn_mkdir_p rm -f "$ac_prefix_conf_OUT" mv $tmp/pconfig.h "$ac_prefix_conf_OUT" fi else as_fn_error $? "input file $ac_prefix_conf_INP does not exist - skip generating $ac_prefix_conf_OUT" "$LINENO" 5 fi rm -f conftest.* fi ;; "depfiles":C) test x"$AMDEP_TRUE" != x"" || { # Older Autoconf quotes --file arguments for eval, but not when files # are listed without --file. Let's play safe and only enable the eval # if we detect the quoting. # TODO: see whether this extra hack can be removed once we start # requiring Autoconf 2.70 or later. case $CONFIG_FILES in #( *\'*) : eval set x "$CONFIG_FILES" ;; #( *) : set x $CONFIG_FILES ;; #( *) : ;; esac shift # Used to flag and report bootstrapping failures. am_rc=0 for am_mf do # Strip MF so we end up with the name of the file. am_mf=`printf "%s\n" "$am_mf" | sed -e 's/:.*$//'` # Check whether this is an Automake generated Makefile which includes # dependency-tracking related rules and includes. # Grep'ing the whole file directly is not great: AIX grep has a line # limit of 2048, but all sed's we know have understand at least 4000. sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ || continue am_dirpart=`$as_dirname -- "$am_mf" || $as_expr X"$am_mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$am_mf" : 'X\(//\)[^/]' \| \ X"$am_mf" : 'X\(//\)$' \| \ X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X"$am_mf" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` am_filepart=`$as_basename -- "$am_mf" || $as_expr X/"$am_mf" : '.*/\([^/][^/]*\)/*$' \| \ X"$am_mf" : 'X\(//\)$' \| \ X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || printf "%s\n" X/"$am_mf" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` { echo "$as_me:$LINENO: cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles" >&5 (cd "$am_dirpart" \ && sed -e '/# am--include-marker/d' "$am_filepart" \ | $MAKE -f - am--depfiles) >&5 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } || am_rc=$? done if test $am_rc -ne 0; then { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "Something went wrong bootstrapping makefile fragments for automatic dependency tracking. If GNU make was not used, consider re-running the configure script with MAKE=\"gmake\" (or whatever is necessary). You can also try re-running configure with the '--disable-dependency-tracking' option to at least be able to build the package (albeit without support for automatic dependency tracking). See 'config.log' for more details" "$LINENO" 5; } fi { am_dirpart=; unset am_dirpart;} { am_filepart=; unset am_filepart;} { am_mf=; unset am_mf;} { am_rc=; unset am_rc;} rm -f conftest-deps.mk } ;; "libtool":C) # See if we are running on zsh, and set the options that allow our # commands through without removal of \ escapes. if test -n "${ZSH_VERSION+set}"; then setopt NO_GLOB_SUBST fi cfgfile=${ofile}T trap "$RM \"$cfgfile\"; exit 1" 1 2 15 $RM "$cfgfile" cat <<_LT_EOF >> "$cfgfile" #! $SHELL # Generated automatically by $as_me ($PACKAGE) $VERSION # NOTE: Changes made to this file will be lost: look at ltmain.sh. # Provide generalized library-building support services. # Written by Gordon Matzigkeit, 1996 # Copyright (C) 2024 Free Software Foundation, Inc. # This is free software; see the source for copying conditions. There is NO # warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # GNU Libtool 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. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program or library that is built # using GNU Libtool, you may include this file under the same # distribution terms that you use for the rest of that program. # # GNU Libtool 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, see . # The names of the tagged configurations supported by this script. available_tags='CXX ' # Configured defaults for sys_lib_dlsearch_path munging. : \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} # ### BEGIN LIBTOOL CONFIG # Which release of libtool.m4 was used? macro_version=$macro_version macro_revision=$macro_revision # Whether or not to build shared libraries. build_libtool_libs=$enable_shared # Whether or not to build static libraries. build_old_libs=$enable_static # What type of objects to build. pic_mode=$pic_mode # Whether or not to optimize for fast installation. fast_install=$enable_fast_install # Shared archive member basename,for filename based shared library versioning on AIX. shared_archive_member_spec=$shared_archive_member_spec # Shell to use when invoking shell scripts. SHELL=$lt_SHELL # An echo program that protects backslashes. ECHO=$lt_ECHO # The PATH separator for the build system. PATH_SEPARATOR=$lt_PATH_SEPARATOR # The host system. host_alias=$host_alias host=$host host_os=$host_os # The build system. build_alias=$build_alias build=$build build_os=$build_os # A sed program that does not truncate output. SED=$lt_SED # Sed that helps us avoid accidentally triggering echo(1) options like -n. Xsed="\$SED -e 1s/^X//" # A grep program that handles long lines. GREP=$lt_GREP # An ERE matcher. EGREP=$lt_EGREP # A literal string matcher. FGREP=$lt_FGREP # A BSD- or MS-compatible name lister. NM=$lt_NM # Whether we need soft or hard links. LN_S=$lt_LN_S # What is the maximum length of a command? max_cmd_len=$max_cmd_len # Object file suffix (normally "o"). objext=$ac_objext # Executable file suffix (normally ""). exeext=$exeext # whether the shell understands "unset". lt_unset=$lt_unset # turn spaces into newlines. SP2NL=$lt_lt_SP2NL # turn newlines into spaces. NL2SP=$lt_lt_NL2SP # convert \$build file names to \$host format. to_host_file_cmd=$lt_cv_to_host_file_cmd # convert \$build files to toolchain format. to_tool_file_cmd=$lt_cv_to_tool_file_cmd # A file(cmd) program that detects file types. FILECMD=$lt_FILECMD # An object symbol dumper. OBJDUMP=$lt_OBJDUMP # Method to check whether dependent libraries are shared objects. deplibs_check_method=$lt_deplibs_check_method # Command to use when deplibs_check_method = "file_magic". file_magic_cmd=$lt_file_magic_cmd # How to find potential files when deplibs_check_method = "file_magic". file_magic_glob=$lt_file_magic_glob # Find potential files using nocaseglob when deplibs_check_method = "file_magic". want_nocaseglob=$lt_want_nocaseglob # DLL creation program. DLLTOOL=$lt_DLLTOOL # Command to associate shared and link libraries. sharedlib_from_linklib_cmd=$lt_sharedlib_from_linklib_cmd # The archiver. AR=$lt_AR # Flags to create an archive (by configure). lt_ar_flags=$lt_ar_flags # Flags to create an archive. AR_FLAGS=\${ARFLAGS-"\$lt_ar_flags"} # How to feed a file listing to the archiver. archiver_list_spec=$lt_archiver_list_spec # A symbol stripping program. STRIP=$lt_STRIP # Commands used to install an old-style archive. RANLIB=$lt_RANLIB old_postinstall_cmds=$lt_old_postinstall_cmds old_postuninstall_cmds=$lt_old_postuninstall_cmds # Whether to use a lock for old archive extraction. lock_old_archive_extraction=$lock_old_archive_extraction # A C compiler. LTCC=$lt_CC # LTCC compiler flags. LTCFLAGS=$lt_CFLAGS # Take the output of nm and produce a listing of raw symbols and C names. global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe # Transform the output of nm in a proper C declaration. global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl # Transform the output of nm into a list of symbols to manually relocate. global_symbol_to_import=$lt_lt_cv_sys_global_symbol_to_import # Transform the output of nm in a C name address pair. global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address # Transform the output of nm in a C name address pair when lib prefix is needed. global_symbol_to_c_name_address_lib_prefix=$lt_lt_cv_sys_global_symbol_to_c_name_address_lib_prefix # The name lister interface. nm_interface=$lt_lt_cv_nm_interface # Specify filename containing input files for \$NM. nm_file_list_spec=$lt_nm_file_list_spec # The root where to search for dependent libraries,and where our libraries should be installed. lt_sysroot=$lt_sysroot # Command to truncate a binary pipe. lt_truncate_bin=$lt_lt_cv_truncate_bin # The name of the directory that contains temporary libtool files. objdir=$objdir # Used to examine libraries when file_magic_cmd begins with "file". MAGIC_CMD=$MAGIC_CMD # Must we lock files when doing compilation? need_locks=$lt_need_locks # Manifest tool. MANIFEST_TOOL=$lt_MANIFEST_TOOL # Tool to manipulate archived DWARF debug symbol files on Mac OS X. DSYMUTIL=$lt_DSYMUTIL # Tool to change global to local symbols on Mac OS X. NMEDIT=$lt_NMEDIT # Tool to manipulate fat objects and archives on Mac OS X. LIPO=$lt_LIPO # ldd/readelf like tool for Mach-O binaries on Mac OS X. OTOOL=$lt_OTOOL # ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4. OTOOL64=$lt_OTOOL64 # Old archive suffix (normally "a"). libext=$libext # Shared library suffix (normally ".so"). shrext_cmds=$lt_shrext_cmds # The commands to extract the exported symbol list from a shared archive. extract_expsyms_cmds=$lt_extract_expsyms_cmds # Variables whose values should be saved in libtool wrapper scripts and # restored at link time. variables_saved_for_relink=$lt_variables_saved_for_relink # Do we need the "lib" prefix for modules? need_lib_prefix=$need_lib_prefix # Do we need a version for libraries? need_version=$need_version # Library versioning type. version_type=$version_type # Shared library runtime path variable. runpath_var=$runpath_var # Shared library path variable. shlibpath_var=$shlibpath_var # Is shlibpath searched before the hard-coded library search path? shlibpath_overrides_runpath=$shlibpath_overrides_runpath # Format of library name prefix. libname_spec=$lt_libname_spec # List of archive names. First name is the real one, the rest are links. # The last name is the one that the linker finds with -lNAME library_names_spec=$lt_library_names_spec # The coded name of the library, if different from the real name. soname_spec=$lt_soname_spec # Permission mode override for installation of shared libraries. install_override_mode=$lt_install_override_mode # Command to use after installation of a shared archive. postinstall_cmds=$lt_postinstall_cmds # Command to use after uninstallation of a shared archive. postuninstall_cmds=$lt_postuninstall_cmds # Commands used to finish a libtool library installation in a directory. finish_cmds=$lt_finish_cmds # As "finish_cmds", except a single script fragment to be evaled but # not shown. finish_eval=$lt_finish_eval # Whether we should hardcode library paths into libraries. hardcode_into_libs=$hardcode_into_libs # Compile-time system search path for libraries. sys_lib_search_path_spec=$lt_sys_lib_search_path_spec # Detected run-time system search path for libraries. sys_lib_dlsearch_path_spec=$lt_configure_time_dlsearch_path # Explicit LT_SYS_LIBRARY_PATH set during ./configure time. configure_time_lt_sys_library_path=$lt_configure_time_lt_sys_library_path # Whether dlopen is supported. dlopen_support=$enable_dlopen # Whether dlopen of programs is supported. dlopen_self=$enable_dlopen_self # Whether dlopen of statically linked programs is supported. dlopen_self_static=$enable_dlopen_self_static # Commands to strip libraries. old_striplib=$lt_old_striplib striplib=$lt_striplib # The linker used to build libraries. LD=$lt_LD # How to create reloadable object files. reload_flag=$lt_reload_flag reload_cmds=$lt_reload_cmds # Commands used to build an old-style archive. old_archive_cmds=$lt_old_archive_cmds # A language specific compiler. CC=$lt_compiler # Is the compiler the GNU compiler? with_gcc=$GCC # Compiler flag to turn off builtin functions. no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag # Additional compiler flags for building library objects. pic_flag=$lt_lt_prog_compiler_pic # How to pass a linker flag through the compiler. wl=$lt_lt_prog_compiler_wl # Compiler flag to prevent dynamic linking. link_static_flag=$lt_lt_prog_compiler_static # Does compiler simultaneously support -c and -o options? compiler_c_o=$lt_lt_cv_prog_compiler_c_o # Whether or not to add -lc for building shared libraries. build_libtool_need_lc=$archive_cmds_need_lc # Whether or not to disallow shared libs when runtime libs are static. allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes # Compiler flag to allow reflexive dlopens. export_dynamic_flag_spec=$lt_export_dynamic_flag_spec # Compiler flag to generate shared objects directly from archives. whole_archive_flag_spec=$lt_whole_archive_flag_spec # Whether the compiler copes with passing no objects directly. compiler_needs_object=$lt_compiler_needs_object # Create an old-style archive from a shared archive. old_archive_from_new_cmds=$lt_old_archive_from_new_cmds # Create a temporary old-style archive to link instead of a shared archive. old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds # Commands used to build a shared archive. archive_cmds=$lt_archive_cmds archive_expsym_cmds=$lt_archive_expsym_cmds # Commands used to build a loadable module if different from building # a shared archive. module_cmds=$lt_module_cmds module_expsym_cmds=$lt_module_expsym_cmds # Whether we are building with GNU ld or not. with_gnu_ld=$lt_with_gnu_ld # Flag that allows shared libraries with undefined symbols to be built. allow_undefined_flag=$lt_allow_undefined_flag # Flag that enforces no undefined symbols. no_undefined_flag=$lt_no_undefined_flag # Flag to hardcode \$libdir into a binary during linking. # This must work even if \$libdir does not exist hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec # Whether we need a single "-rpath" flag with a separated argument. hardcode_libdir_separator=$lt_hardcode_libdir_separator # Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes # DIR into the resulting binary. hardcode_direct=$hardcode_direct # Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes # DIR into the resulting binary and the resulting library dependency is # "absolute",i.e. impossible to change by setting \$shlibpath_var if the # library is relocated. hardcode_direct_absolute=$hardcode_direct_absolute # Set to "yes" if using the -LDIR flag during linking hardcodes DIR # into the resulting binary. hardcode_minus_L=$hardcode_minus_L # Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR # into the resulting binary. hardcode_shlibpath_var=$hardcode_shlibpath_var # Set to "yes" if building a shared library automatically hardcodes DIR # into the library and all subsequent libraries and executables linked # against it. hardcode_automatic=$hardcode_automatic # Set to yes if linker adds runtime paths of dependent libraries # to runtime path list. inherit_rpath=$inherit_rpath # Whether libtool must link a program against all its dependency libraries. link_all_deplibs=$link_all_deplibs # Set to "yes" if exported symbols are required. always_export_symbols=$always_export_symbols # The commands to list exported symbols. export_symbols_cmds=$lt_export_symbols_cmds # Symbols that should not be listed in the preloaded symbols. exclude_expsyms=$lt_exclude_expsyms # Symbols that must always be exported. include_expsyms=$lt_include_expsyms # Commands necessary for linking programs (against libraries) with templates. prelink_cmds=$lt_prelink_cmds # Commands necessary for finishing linking programs. postlink_cmds=$lt_postlink_cmds # Specify filename containing input files. file_list_spec=$lt_file_list_spec # How to hardcode a shared library path into an executable. hardcode_action=$hardcode_action # The directories searched by this compiler when creating a shared library. compiler_lib_search_dirs=$lt_compiler_lib_search_dirs # Dependencies to place before and after the objects being linked to # create a shared library. predep_objects=$lt_predep_objects postdep_objects=$lt_postdep_objects predeps=$lt_predeps postdeps=$lt_postdeps # The library search path used internally by the compiler when linking # a shared library. compiler_lib_search_path=$lt_compiler_lib_search_path # ### END LIBTOOL CONFIG _LT_EOF cat <<'_LT_EOF' >> "$cfgfile" # ### BEGIN FUNCTIONS SHARED WITH CONFIGURE # func_munge_path_list VARIABLE PATH # ----------------------------------- # VARIABLE is name of variable containing _space_ separated list of # directories to be munged by the contents of PATH, which is string # having a format: # "DIR[:DIR]:" # string "DIR[ DIR]" will be prepended to VARIABLE # ":DIR[:DIR]" # string "DIR[ DIR]" will be appended to VARIABLE # "DIRP[:DIRP]::[DIRA:]DIRA" # string "DIRP[ DIRP]" will be prepended to VARIABLE and string # "DIRA[ DIRA]" will be appended to VARIABLE # "DIR[:DIR]" # VARIABLE will be replaced by "DIR[ DIR]" func_munge_path_list () { case x$2 in x) ;; *:) eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" ;; x:*) eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" ;; *::*) eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" ;; *) eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" ;; esac } # Calculate cc_basename. Skip known compiler wrappers and cross-prefix. func_cc_basename () { for cc_temp in $*""; do case $cc_temp in compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; \-*) ;; *) break;; esac done func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` } # ### END FUNCTIONS SHARED WITH CONFIGURE _LT_EOF case $host_os in aix3*) cat <<\_LT_EOF >> "$cfgfile" # AIX sometimes has problems with the GCC collect2 program. For some # reason, if we set the COLLECT_NAMES environment variable, the problems # vanish in a puff of smoke. if test set != "${COLLECT_NAMES+set}"; then COLLECT_NAMES= export COLLECT_NAMES fi _LT_EOF ;; esac ltmain=$ac_aux_dir/ltmain.sh # We use sed instead of cat because bash on DJGPP gets confused if # if finds mixed CR/LF and LF-only lines. Since sed operates in # text mode, it properly converts lines to CR/LF. This bash problem # is reportedly fixed, but why not run on old versions too? $SED '$q' "$ltmain" >> "$cfgfile" \ || (rm -f "$cfgfile"; exit 1) mv -f "$cfgfile" "$ofile" || (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") chmod +x "$ofile" cat <<_LT_EOF >> "$ofile" # ### BEGIN LIBTOOL TAG CONFIG: CXX # The linker used to build libraries. LD=$lt_LD_CXX # How to create reloadable object files. reload_flag=$lt_reload_flag_CXX reload_cmds=$lt_reload_cmds_CXX # Commands used to build an old-style archive. old_archive_cmds=$lt_old_archive_cmds_CXX # A language specific compiler. CC=$lt_compiler_CXX # Is the compiler the GNU compiler? with_gcc=$GCC_CXX # Compiler flag to turn off builtin functions. no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag_CXX # Additional compiler flags for building library objects. pic_flag=$lt_lt_prog_compiler_pic_CXX # How to pass a linker flag through the compiler. wl=$lt_lt_prog_compiler_wl_CXX # Compiler flag to prevent dynamic linking. link_static_flag=$lt_lt_prog_compiler_static_CXX # Does compiler simultaneously support -c and -o options? compiler_c_o=$lt_lt_cv_prog_compiler_c_o_CXX # Whether or not to add -lc for building shared libraries. build_libtool_need_lc=$archive_cmds_need_lc_CXX # Whether or not to disallow shared libs when runtime libs are static. allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes_CXX # Compiler flag to allow reflexive dlopens. export_dynamic_flag_spec=$lt_export_dynamic_flag_spec_CXX # Compiler flag to generate shared objects directly from archives. whole_archive_flag_spec=$lt_whole_archive_flag_spec_CXX # Whether the compiler copes with passing no objects directly. compiler_needs_object=$lt_compiler_needs_object_CXX # Create an old-style archive from a shared archive. old_archive_from_new_cmds=$lt_old_archive_from_new_cmds_CXX # Create a temporary old-style archive to link instead of a shared archive. old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds_CXX # Commands used to build a shared archive. archive_cmds=$lt_archive_cmds_CXX archive_expsym_cmds=$lt_archive_expsym_cmds_CXX # Commands used to build a loadable module if different from building # a shared archive. module_cmds=$lt_module_cmds_CXX module_expsym_cmds=$lt_module_expsym_cmds_CXX # Whether we are building with GNU ld or not. with_gnu_ld=$lt_with_gnu_ld_CXX # Flag that allows shared libraries with undefined symbols to be built. allow_undefined_flag=$lt_allow_undefined_flag_CXX # Flag that enforces no undefined symbols. no_undefined_flag=$lt_no_undefined_flag_CXX # Flag to hardcode \$libdir into a binary during linking. # This must work even if \$libdir does not exist hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec_CXX # Whether we need a single "-rpath" flag with a separated argument. hardcode_libdir_separator=$lt_hardcode_libdir_separator_CXX # Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes # DIR into the resulting binary. hardcode_direct=$hardcode_direct_CXX # Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes # DIR into the resulting binary and the resulting library dependency is # "absolute",i.e. impossible to change by setting \$shlibpath_var if the # library is relocated. hardcode_direct_absolute=$hardcode_direct_absolute_CXX # Set to "yes" if using the -LDIR flag during linking hardcodes DIR # into the resulting binary. hardcode_minus_L=$hardcode_minus_L_CXX # Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR # into the resulting binary. hardcode_shlibpath_var=$hardcode_shlibpath_var_CXX # Set to "yes" if building a shared library automatically hardcodes DIR # into the library and all subsequent libraries and executables linked # against it. hardcode_automatic=$hardcode_automatic_CXX # Set to yes if linker adds runtime paths of dependent libraries # to runtime path list. inherit_rpath=$inherit_rpath_CXX # Whether libtool must link a program against all its dependency libraries. link_all_deplibs=$link_all_deplibs_CXX # Set to "yes" if exported symbols are required. always_export_symbols=$always_export_symbols_CXX # The commands to list exported symbols. export_symbols_cmds=$lt_export_symbols_cmds_CXX # Symbols that should not be listed in the preloaded symbols. exclude_expsyms=$lt_exclude_expsyms_CXX # Symbols that must always be exported. include_expsyms=$lt_include_expsyms_CXX # Commands necessary for linking programs (against libraries) with templates. prelink_cmds=$lt_prelink_cmds_CXX # Commands necessary for finishing linking programs. postlink_cmds=$lt_postlink_cmds_CXX # Specify filename containing input files. file_list_spec=$lt_file_list_spec_CXX # How to hardcode a shared library path into an executable. hardcode_action=$hardcode_action_CXX # The directories searched by this compiler when creating a shared library. compiler_lib_search_dirs=$lt_compiler_lib_search_dirs_CXX # Dependencies to place before and after the objects being linked to # create a shared library. predep_objects=$lt_predep_objects_CXX postdep_objects=$lt_postdep_objects_CXX predeps=$lt_predeps_CXX postdeps=$lt_postdeps_CXX # The library search path used internally by the compiler when linking # a shared library. compiler_lib_search_path=$lt_compiler_lib_search_path_CXX # ### END LIBTOOL TAG CONFIG: CXX _LT_EOF ;; esac done # for ac_tag as_fn_exit 0 _ACEOF ac_clean_files=$ac_clean_files_save test $ac_write_fail = 0 || as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SEQ66_APP_NAME" >&5 printf "%s\n" "$SEQ66_APP_NAME" >&6; }; cat << E_O_F Run 'make' to build the application. Run 'make install' as root/sudo to install the application. Do 'make dist', etc. to create gzip and other archives. To build the PDF user manual, make sure TexLive and latexmk are installed. Change to the doc/latex directory, and run make. E_O_F ================================================ FILE: configure.ac ================================================ dnl *************************************************************************** dnl configure.ac (Seq66) dnl --------------------------------------------------------------------------- dnl dnl \file configure.ac dnl \library Seq66 dnl \author Chris Ahlstrom dnl \date 2018-11-09 dnl \update 2026-05-01 dnl \version $Revision$ dnl \license $XPC_SUITE_GPL_LICENSE$ dnl dnl This script is for the Seq66 project, a repackaging and updating dnl of sequencer64. dnl dnl Process this file with bootstrap (autoconf) to produce a configure dnl script. Run "./bootstrap --help" for more information, or read the dnl INSTALL file. dnl dnl Issue: #145. Reduce the autoconf version by 1. dnl dnl AC_PREREQ([2.72]) dnl dnl --------------------------------------------------------------------------- AC_PREREQ([2.71]) AC_REVISION($Revision: 0.99$) AC_INIT([Seq66],[0.99.24],[ahlstromcj@gmail.com]) AC_CONFIG_AUX_DIR([aux-files]) AC_CONFIG_MACRO_DIR([m4]) AC_CANONICAL_HOST AM_INIT_AUTOMAKE([dist-bzip2]) AC_DEFINE(_GNU_SOURCE, 1, [gnu source]) AC_CONFIG_HEADERS([include/config.h]) export PKG_CONFIG=$(which pkg-config) dnl Prepends "SEQ66_" to the macro definitions, for a better guarantee of dnl avoiding naming conflicts in macros. AX_PREFIX_CONFIG_H([include/seq66-config.h], [seq66]) dnl c/c++ configuration. AC_PROG_CC(gcc clang llvm-gcc) AC_PROG_CXX(g++ clang++ llvm-g++) AC_PROG_CPP() AC_PROG_CXXCPP() dnl Important: see dnl dnl https://android.googlesource.com/toolchain/llvm/+/release_34/ dnl autoconf/configure.ac dnl dnl If CXX is Clang, check that it can find and parse C++ standard library dnl headers. Note that space between 'include' and '(' is required. dnl There's a broken regex in aclocal that otherwise will think that we call dnl m4's include builtin. The linker flags are used with gcc, but not dnl clang. ac_linker_flag="-Wl,--copy-dt-needed-entries" ac_clang_active="no" if test "$CXX" = "clang++" ; then AC_MSG_CHECKING([whether clang works]) AC_LANG_PUSH([C++]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #if __has_include () #include #endif #if __has_include () #include #endif ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE(CLANG_SUPPORT, 1, [Define if clang is active]) ac_linker_flag="" ac_clang_active="yes" ], [ AC_MSG_RESULT([no]) AC_MSG_ERROR([Compiler could not parse C++ headers. Use CC=c-compiler CXX=c++-compiler ./configure ...]) ]) AC_LANG_POP([C++]) fi LINKER_FLAGS=$ac_linker_flag AC_SUBST([LINKER_FLAGS]) dnl 5.c. Mingw support. Missing, qmake support is more reliable at dnl present. dnl Note that the BUILD_XXXX macros "merely" select which subdirectories we dnl descend into during the build. ac_build_docs="no" ac_build_windows="no" ac_build_portmidi="no" ac_build_qtmidi="yes" ac_build_rtcli="no" ac_build_rtmidi="yes" ac_build_sessions="yes" ac_build_testing="no" ac_build_qt="yes" dnl The default names describing aspects of the application. ac_app_name="qseq66" ac_app_type="qt" ac_app_engine="rtmidi" ac_app_build_issue="Linux" ac_app_build_os="Linux" ac_client_name="seq66" ac_config_name="qseq66" ac_config_dir_name="seq66" ac_icon_name="qseq66" dnl Test for CYGWIN/MSYS2; this macro is marked as obsolete, though: dnl AC_CYGWIN. Instead, see AC_CANONICAL_HOST at the top of this file. dnl It uses the host_os variable. case $host_os in *cygwin* | *msys* | windows*) ac_build_windows="yes";; * ) ac_build_windows="no";; esac if test "$ac_build_windows" = "yes" ; then ac_build_portmidi="yes" ac_build_rtmidi="no" ac_build_sessions="no" fi dnl Windows build not supported. qmake and mingw are used for Windows code. dnl But we are trying to change that. dnl New for autoconf 2.60, prevents spurious datarootdir warnings. AC_DEFUN([AC_DATAROOTDIR_CHECKED]) dnl 1. Package version information. We define the name of the package, the dnl name of the client application for display, and the version. PACKAGE="seq66" AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE") AC_SUBST([PACKAGE]) AC_DEFINE(APP_TYPE, ["qt5"], "Type of the Seq66 executable, qt/cli") AC_DEFINE(APP_ENGINE, ["rtmidi"], "Seq66 MIDI engine, rtmidi/portmidi") ac_app_build_os="'$(uname -srm)'" SEQ66_SUITE_NAME="SEQ66" AC_SUBST([SEQ66_SUITE_NAME]) dnl Set up version information for this project. SEQ66_API_MAJOR="0" AC_SUBST([SEQ66_API_MAJOR]) SEQ66_API_MINOR="99" AC_SUBST([SEQ66_API_MINOR]) SEQ66_API_PATCH="0" AC_SUBST([SEQ66_API_PATCH]) dnl For version 1.0.0, we will reset the libtool version information. SEQ66_LT_CURRENT="0" AC_SUBST([SEQ66_LT_CURRENT]) SEQ66_LT_REVISION="0" AC_SUBST([SEQ66_LT_REVISION]) SEQ66_LT_AGE="0" AC_SUBST([SEQ66_LT_AGE]) SEQ66_LIBTOOL_VERSION="$SEQ66_LT_CURRENT:$SEQ66_LT_REVISION:$SEQ66_LT_AGE" AC_SUBST([SEQ66_LIBTOOL_VERSION]) dnl 1.b. Set up the version strings. The primary version string of interest dnl is SEQ66_API_VERSION. SEQ66_API_VERSION="$SEQ66_API_MAJOR.$SEQ66_API_MINOR" AC_DEFINE(API_VERSION, ["0.99"], [Seq66 API version]) AC_SUBST([API_VERSION]) dnl 1.c. Report the version information in the progress output. AC_MSG_CHECKING(major version) AC_MSG_RESULT($SEQ66_API_MAJOR) AC_MSG_CHECKING(minor version) AC_MSG_RESULT($SEQ66_API_MINOR) AC_MSG_CHECKING(patchlevel) AC_MSG_RESULT($SEQ66_API_PATCH) dnl 1.d. Set up the suite name and current-project name. SEQ66_PROJECT_NAME="SEQ66" AC_SUBST([SEQ66_PROJECT_NAME]) dnl 2. Libtool dnl dnl Initialize libtool in the default manner, which turns on shared dnl libraries if available, and enable static libraries if they do not conflict dnl with shared libraries. Also sets the shell variable LIBTOOL_DEPS. dnl The latest GNU documentation specifies version 2 of libtool. LT_PREREQ([2.4.2]) LT_INIT AC_SUBST([LIBTOOL_DEPS]) LT_RELEASE="$SEQ66_API_MAJOR.$SEQ66_API_MINOR" AC_SUBST([LT_RELEASE]) LT_CURRENT="$SEQ66_API_MAJOR" AC_SUBST([LT_CURRENT]) LT_REVISION="$SEQ66_API_MINOR" AC_SUBST([LT_REVISION]) LT_AGE="$SEQ66_API_PATCH" AC_SUBST([LT_AGE]) dnl 3. Set up expandable installation directory(ies). seq66includedir="${includedir}/seq66-${SEQ66_API_VERSION}" AC_SUBST(seq66includedir) seq66libdir="${libdir}/seq66-${SEQ66_API_VERSION}" AC_SUBST(seq66libdir) seq66docdir="${datadir}/doc/seq66-${SEQ66_API_VERSION}" AC_SUBST(seq66docdir) seq66datadir="${datadir}/seq66-${SEQ66_API_VERSION}" AC_SUBST(seq66datadir) seq66pixdir="${datadir}/pixmaps/seq66-${SEQ66_API_VERSION}" AC_SUBST(seq66pixdir) dnl Allow the user to change client/port name. AC_ARG_WITH(client, [AS_HELP_STRING(--with-client, [Change name of client/port from default])], [ac_client_name=$withval], []) AC_DEFINE_UNQUOTED(CLIENT_NAME, ["$ac_client_name"], "Name of client/port") dnl 4. Get external flags, if any. CFLAGS="${CFLAGS} -I/usr/local/include " CXXFLAGS="${CXXFLAGS} -I/usr/local/include " LDFLAGS="${LDFLAGS} -L/usr/local/lib " dnl Check for header files. Added more to support daemonization. dnl dnl m4_warn([obsolete], dnl [Preprocessor macro STDC_HEADERS is obsolete. dnl Except in unusual embedded environments, one can include all ISO C90 dnl headers unconditionally.]) dnl dnl Autoupdate added the next two lines to ensure that your configure dnl script's behavior did not change. They are probably safe to remove. dnl In fact, they cause issues on Ubuntu (as opposed to Debian Sid). dnl dnl AC_CHECK_INCLUDES_DEFAULT dnl AC_PROG_EGREP AC_CHECK_HEADERS([getopt.h string.h limits.h]) AC_CHECK_HEADERS([stdarg.h stdio.h stddef.h stdlib.h string.h]) AC_CHECK_HEADERS([limits.h ctype.h sys/time.h time.h errno.h]) AC_CHECK_HEADERS([fcntl.h sys/stat.h sys/sysctl.h]) AC_CHECK_HEADERS([syslog.h unistd.h]) dnl Checks for typedefs, structures, and compiler characteristics. AC_C_CONST dnl Doxygen support has been added back! However, it is disabled by default. dnl Check for the presence of Doxygen. Not an error to be missing, but dnl we don't want to try to build documentation if it is not present. dnl See http://www.gnu.org/software/autoconf-archive/ax_prog_doxygen.html dnl Note the building the documentation also requires graphviz to be installed. AC_CHECK_PROGS([DOXYGEN], [doxygen]) if test -z "$DOXYGEN" ; then AC_MSG_WARN([Doxygen not found, no developer docs will be built]) else AC_ARG_ENABLE(docs, [AS_HELP_STRING(--enable-docs, [Enable developer document support])], [docs=$enableval], [docs=no]) if test "$docs" != no ; then ac_build_docs="yes" AC_MSG_RESULT([Doxygen document build enabled]) AC_CHECK_PROGS([LATEX], [latex]) if test -z "$LATEX" ; then AC_MSG_WARN([LaTeX not found, no docs will be built]) ac_build_docs="no" fi fi fi dnl Clear out the X11 flags for the case where we are cross-compiling dnl for i586-mingw32 targets, under UNIX. if test "$mingw" = "yes" ; then X_CFLAGS="" X_LIBS="" fi dnl JACK support, plus session and metadata support. dnl JACK session support is now deprecated (circa 2020-10-26), but still the dnl default for this option is 'yes'. JACK recommends using NSM instead! But dnl Seq66 will support it for awhile yet. dnl JACK metadata, seems to work, so now enabled by default. PKG_CHECK_MODULES(JACK, jack >= 0.90.0, jack_found="yes", jack_found="no") if test "$jack_found" = "yes" -a "$ac_build_windows" = "no" ; then AC_ARG_ENABLE(jack, [AS_HELP_STRING(--disable-jack, [Disable JACK support])], [jack=$enableval], [jack=yes]) AC_ARG_ENABLE(jack-session, [AS_HELP_STRING(--disable-jack-session, [Disable JACK session])], [jack_session=$enableval], [jack_session=yes]) AC_ARG_ENABLE(jack-metadata, [AS_HELP_STRING(--disable-jack-metadata, [Disable JACK metadata])], [jack_metadata=$enableval], [jack_metadata=yes]) if test "$jack" != "no" ; then AC_DEFINE(JACK_SUPPORT, 1, [Define to enable JACK driver]) AC_SUBST(JACK_CFLAGS) AC_SUBST(JACK_LIBS) if test "$jack_session" != "no" ; then AC_CHECK_HEADER(jack/session.h, jack_session_found="yes", jack_session_found="no") if test "$jack_session_found" = "yes" ; then AC_DEFINE(JACK_SESSION, 1, [Define to enable JACK session]) AC_MSG_RESULT([JACK session support enabled]); fi fi AC_MSG_RESULT([JACK support enabled]) AC_CHECK_HEADERS([jack/jack.h]) AC_CHECK_LIB([jack], [jack_get_version_string], jack_get_version="yes", jack_get_version="no") if test "$jack_get_version" = "yes" ; then AC_DEFINE(JACK_GET_VERSION_STRING, 1, [Enable JACK version string]) AC_MSG_RESULT([JACK jack_get_version_string available]); fi if test "$jack_metadata" = "yes" ; then AC_CHECK_HEADER(jack/metadata.h, jack_metadata="yes", jack_metadata="no") if test "$jack_metadata" = "yes" ; then AC_DEFINE(JACK_METADATA, 1, [Define to enable JACK metadata]) AC_MSG_RESULT([JACK metadata support enabled]); fi fi fi else AC_MSG_WARN([No JACK library, disabling JACK]) fi dnl Port-refresh support. Far from ready for prime-time, but added for dnl exploration and testing. Disabled by default. AC_ARG_ENABLE(port-refresh, [AS_HELP_STRING(--enable-port-refresh, [Enable JACK port refresh support])], [port_refresh=$enableval], [port_refresh=no]) if test "$port_refresh" != "no" ; then AC_DEFINE(MIDI_PORT_REFRESH, 1, [Define to enable JACK port refresh]) AC_MSG_RESULT([JACK port refresh test code enabled]) else AC_MSG_NOTICE([JACK port refresh not enabled]) fi AC_SUBST(MIDI_PORT_REFRESH) dnl LASH support has been deleted, this time for good. We will support only dnl JACK Session and NSM. Enable NSM support. Now ready for prime time! dnl Check for now-required LIBLO library, needed only for NSM support. NSM_CFLAGS= NSM_LIBS= NSM_DEPS= PKG_CHECK_MODULES([LIBLO], [liblo], [ac_liblo="yes"], [ac_liblo="no"]) if test "$ac_liblo" = "yes" ; then AC_ARG_ENABLE(nsm, AS_HELP_STRING([--disable-nsm], [Disable NSM support]), [nsm=$enableval], [nsm=yes]) AC_DEFINE(LIBLO_SUPPORT, 1, [Define if LIBLO library is available]) AC_SUBST(LIBLO_CFLAGS) AC_SUBST(LIBLO_LIBS) AC_MSG_RESULT([LIBLO support for NSM enabled]); if test "$nsm" != "no" ; then AC_DEFINE(NSM_SUPPORT, 1, [Define if NSM support is available]) NSM_CFLAGS="-I ../libsessions/include" NSM_LIBS="-llo -L../libsessions/src/.libs/ -lsessions" NSM_DEPS="../libsessions/src/.libs/libsessions.la" AC_MSG_RESULT([Sessions/NSM support enabled]); fi else AC_MSG_WARN([LIBLO dev package not found, required for NSM]) fi AC_SUBST(NSM_CFLAGS) AC_SUBST(NSM_LIBS) AC_SUBST(NSM_DEPS) dnl Can enable oth "CLI" and "rtmidi/qtsupport". The CLI version ignores dnl the macros and fills in its values with functions from the dnl xxx module. AC_ARG_ENABLE(both, [AS_HELP_STRING(--enable-both, [Enable Qt and command-line builds])], [both=$enableval], [both=no]) if test "$both" != "no" ; then AC_MSG_RESULT([Both rtmidi Qt and command-line builds enabled]); if test "$mingw" != "yes" ; then ac_build_rtcli="yes" ac_build_qtmidi="yes" AM_PATH_ALSA(0.9.0) fi fi dnl "rtmidi" support. This is now the default build for Seq66. if test "$ac_build_windows" = "no" ; then AC_ARG_ENABLE(rtmidi, [AS_HELP_STRING(--disable-rtmidi, [Disable rtmidi MIDI engine])], [rtmidi=$enableval], [rtmidi=yes]) if test "$rtmidi" != "no" -o "$both" != "no" ; then ac_build_rtmidi="yes" AC_DEFINE(RTMIDI_SUPPORT, 1, [Indicates the rtmidi engine is enabled]) AM_PATH_ALSA(0.9.0) AC_MSG_RESULT([rtmidi engine build enabled]); fi fi dnl "CLI" or "Command-line" support. AC_ARG_ENABLE(cli, [AS_HELP_STRING(--enable-cli, [Enable rtmidi command-line build])], [cli=$enableval], [cli=no]) if test "$cli" != "no" ; then AC_MSG_RESULT([rtmidi command-line build enabled]); AC_DEFINE(APP_CLI, [1], [Indicate the CLI version]) ac_app_name="seq66cli" ac_app_type="cli" ac_config_name="seq66cli" if test "$mingw" != "yes" ; then ac_build_rtcli="yes" ac_build_qtmidi="no" AC_DEFINE(RTMIDI_SUPPORT, 1, [Indicates that rtmidi is enabled]) AM_PATH_ALSA(0.9.0) fi ac_build_qt="no" else dnl "Qt" support. Enabled by default, and actually mandatory for this dnl application if not built for the headless mode. AC_ARG_ENABLE(qt, [AS_HELP_STRING(--disable-qt, [Disable Qt5 user-interface])], [ac_build_qt=$enableval], [ac_build_qt=yes]) fi dnl Will set the default to yes later. Will need to distinguish between rtmidi dnl and portmidi builds. if test "$ac_build_qt" != "no" ; then ac_app_name="qseq66" ac_app_type="qt" if test "$ac_build_windows" != "yes" ; then AM_PATH_ALSA(0.9.0) fi if test "$ac_build_windows" = "yes" ; then AX_HAVE_QT else if test "$ac_clang_active" = "yes" ; then AX_HAVE_QT_CLANG else AX_HAVE_QT_MIN fi fi AC_MSG_RESULT([qt user-interface build enabled]); fi dnl portmidi support. Deprecated for Linux, but we still build it, for dnl testing purposes. It needs the ALSA libraries to work, in Linux, but dnl in Windows it uses the Windows MultiMedia API. AC_ARG_ENABLE(portmidi, [AS_HELP_STRING(--enable-portmidi, [Enable portmidi build)])], [portmidi=$enableval], [portmidi=no]) if test "$portmidi" != "no" ; then ac_build_portmidi="yes" ac_build_rtmidi="no" ac_app_name="seq66portmidi" ac_config_name="seq66portmidi" AC_DEFINE(PORTMIDI_SUPPORT, 1, [Indicates if portmidi is enabled]) AM_PATH_ALSA(0.9.0) AC_MSG_RESULT([Portmidi build enabled]); else AC_MSG_NOTICE([Portmidi build disabled]); fi AC_DEFINE_UNQUOTED(APP_NAME, ["$ac_app_name"], [Name of the CLI application]) AC_DEFINE_UNQUOTED(APP_TYPE, ["$ac_app_type"], [Name of the GUI/UI]) AC_DEFINE_UNQUOTED(APP_ENGINE, ["$ac_app_engine"], [Name of the MIDI engine]) AC_DEFINE_UNQUOTED(APP_BUILD_ISSUE, "[m4_normalize(esyscmd([uname -o]))]", [Operating system of build]) AC_DEFINE_UNQUOTED(APP_BUILD_OS, ["$ac_app_build_os"], [OS/kernel of build]) AC_DEFINE_UNQUOTED(CLIENT_NAME, ["$ac_client_name"], [Client/port base name]) AC_DEFINE_UNQUOTED(CONFIG_NAME, ["$ac_config_name"], [Configuration base file name]) AC_DEFINE_UNQUOTED(CONFIG_DIR_NAME, ["$ac_config_dir_name"], [Configuration sub-directory]) AC_DEFINE_UNQUOTED(ICON_NAME, ["$ac_icon_name"], [Icon name for Freedesktop]) AC_SUBST(APP_NAME) AC_SUBST(APP_TYPE) AC_SUBST(APP_ENGINE) AC_SUBST(APP_BUILD_ISSUE) AC_SUBST(APP_BUILD_OS) AC_SUBST(CLIENT_NAME) AC_SUBST(CONFIG_NAME) AC_SUBST(CONFIG_DIR_NAME) AC_SUBST(ICON_NAME) AM_CONDITIONAL([BUILD_DOCS], [test "$ac_build_docs" = "yes"]) AM_CONDITIONAL([BUILD_PORTMIDI], [test "$ac_build_portmidi" = "yes"]) AM_CONDITIONAL([BUILD_QTMIDI], [test "$ac_build_qtmidi" = "yes"]) AM_CONDITIONAL([BUILD_RTCLI], [test "$ac_build_rtcli" = "yes"]) AM_CONDITIONAL([BUILD_RTMIDI], [test "$ac_build_rtmidi" = "yes"]) AM_CONDITIONAL([BUILD_SESSIONS], [test "$ac_build_sessions" = "yes"]) dnl 6.0 Top portion of the config.h/seq66-config.h header files. The dnl seq66-config.h header file has "SEQ66_" prepended to these dnl macros automatically. AH_TOP( #define VERSION_DATE_SHORT "2026-05-01" #define API_VERSION "0.99" #define VERSION "0.99.24" ) dnl 7. Checks for build configuration. dnl 7.a. Compiling with debugging, coverage, or profiling options. dnl Implemented in m4/xpc_debug.m4. dnl dnl --enable-debug dnl --enable-coverage dnl --enable-profiling AC_XPC_DEBUGGING dnl 8. Set up other options in the compiler macros. dnl dnl -Wno-error=date-time breaks the build on KX Studio (gcc 4.8), removed. dnl gcc 8 uncovers a warnings issue in libsigc++, so remove -Wextra for now, dnl and add -Wno-parentheses. 2019-11-30: Add it back, to match qmake's use of dnl equivalent -W option, to uncover more issues. dnl dnl m4/xpc_debug.m4 defines DBGFLAGS to enable debugging/coverage/profiling. dnl m4/xpc_errorlog.m4 defines NOERRLOG. Currently not used. dnl m4/xpc_thisptr.m4 defines NOTHISPTR. Currently not used. WARNFLAGS="-Wall -Wextra -pedantic -Wno-parentheses $WARNINGS" APIDEF="-DAPI_VERSION=\"$SEQ66_API_VERSION\"" SPEEDFLAGS="-ffast-math" COMMONFLAGS="$WARNFLAGS -D_REENTRANT $APIDEF $DBGFLAGS" WARNINGS_DISABLED="-Wno-unused-parameter -Wno-non-virtual-dtor" dnl Check for MinGW. Workaround for libtool's DLL_EXPORT stupidity. dnl AX_PTHREAD leaves PTHREADS_LIBS empty for gcc, and sets PTHREADS_CFLAGS dnl to -pthread, which causes problems if we need -lpthread to appear in dnl pkgconfig files. dnl dnl AX_PTHREAD dnl dnl That macro will cause output of just what tools are being used to build dnl code: VERBCFLAGS="-v" VERBCFLAGS="" ac_gnuwin="yes" case "$host_os" in *cygwin*) ac_gnuwin="yes" AX_PTHREAD ;; *mingw*) ac_gnuwin="yes" AC_MSG_NOTICE([Setting up MingW pthreads support]) CFLAGS="$CFLAGS -mthreads -pthread " CPPFLAGS="-DPTW32_STATIC_LIB $CPPFLAGS " CXXFLAGS="$CXXFLAGS -mthreads -pthread " LDFLAGS="$LDFLAGS -mthreads -pthread " AC_DEFINE(HAVE_PTHREAD,1,[Defined to POSIX threads for mingw]) ;; *) AC_MSG_NOTICE([Normal pthreads support]) AC_MSG_CHECKING([Running normal PTHREAD test]) AX_PTHREAD ;; esac dnl Note the c++17 option. Also note that PROLDFLAGS comes from xpc_debug.m4. dnl Trying out gnu++14 or gnu++1y (they don't work) instead, to see if we can dnl eliminate the problem of debug linkage on some of our laptops. dnl The warning disabling for clang does prevents the "clang-16: warning: dnl optimization flag '-fno-fat-lto-objects' is not supported". dnl Finally, after getting an std::sort() error in the triggers class under dnl clang-16.0.6, we will force C++14. dnl dnl Clang 21 comes up with a warning about not being able to override dnl virtual function in a final class. if test "$ac_clang_active" = "yes" ; then CFLAGS="$CFLAGS $COMMONFLAGS -Wno-ignored-optimization-argument " CXXFLAGS="$CFLAGS -std=c++17 -pipe -Wno-variadic-macros -Wno-deprecated-declarations -Wno-unnecessary-virtual-specifier" else CFLAGS="$CFLAGS $COMMONFLAGS" CXXFLAGS="$CFLAGS -std=c++17 -pipe -Wno-variadic-macros -Wno-deprecated-declarations" fi LDFLAGS="$LDFLAGS $PROLDFLAGS" AM_CONDITIONAL(GNU_WIN, test "$ac_gnuwin" = "yes") dnl Try to fix the build flags; we enable RTMIDI by default, but have to dnl disable it when ALSA or PortMIDI builds are specified. We have to dnl use a trick to fool configure, which will strip out any bare #undef dnl statement it sees. Don't like this one bit. AH_BOTTOM( #if defined SEQ66_PORTMIDI_SUPPORT #/**/undef/**/ SEQ66_RTMIDI_SUPPORT #endif #if defined SEQ66_WINDOWS_SUPPORT #/**/undef/**/ SEQ66_RTMIDI_SUPPORT #endif ) dnl Set up the Makefiles. AC_CONFIG_FILES([ Makefile libseq66/Makefile libseq66/include/Makefile libseq66/src/Makefile libsessions/Makefile libsessions/include/Makefile libsessions/src/Makefile m4/Makefile man/Makefile seq_portmidi/Makefile seq_portmidi/include/Makefile seq_portmidi/src/Makefile seq_rtmidi/Makefile seq_rtmidi/include/Makefile seq_rtmidi/src/Makefile seq_qt5/Makefile seq_qt5/include/Makefile seq_qt5/forms/Makefile seq_qt5/src/Makefile resources/pixmaps/Makefile Seq66qt5/Makefile Seq66cli/Makefile data/Makefile doc/Makefile doc/latex/Makefile doc/latex/tex/Makefile ]) dnl See AC_CONFIG_COMMANDS AC_OUTPUT AC_MSG_RESULT([$SEQ66_APP_NAME]); cat << E_O_F Run 'make' to build the application. Run 'make install' as root/sudo to install the application. Do 'make dist', etc. to create gzip and other archives. To build the PDF user manual, make sure TexLive and latexmk are installed. Change to the doc/latex directory, and run make. E_O_F dnl configure.ac (seq66) dnl dnl vim: ts=4 sw=4 et ft=config ================================================ FILE: configure.help ================================================ `configure' configures Seq66 0.99.12 to adapt to many kinds of systems. Usage: ./configure [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print `checking ...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for `--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or `..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [/usr/local] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, `make install' will install all the files in `/usr/local/bin', `/usr/local/lib' etc. You can specify an installation prefix other than `/usr/local' using `--prefix', for instance `--prefix=$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/seq66] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] Program names: --program-prefix=PREFIX prepend PREFIX to installed program names --program-suffix=SUFFIX append SUFFIX to installed program names --program-transform-name=PROGRAM run sed PROGRAM on installed program names X features: --x-includes=DIR X include files are in DIR --x-libraries=DIR X library files are in DIR System types: --build=BUILD configure for building on BUILD [guessed] --host=HOST cross-compile to build programs to run on HOST [BUILD] Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-silent-rules less verbose build output (undo: "make V=1") --disable-silent-rules verbose build output (undo: "make V=0") --enable-dependency-tracking do not reject slow dependency extractors --disable-dependency-tracking speeds up one-time build --enable-shared[=PKGS] build shared libraries [default=yes] --enable-static[=PKGS] build static libraries [default=yes] --enable-fast-install[=PKGS] optimize for fast installation [default=yes] --disable-libtool-lock avoid locking (might break parallel builds) --enable-docs Enable developer document support --disable-jack Disable JACK support --disable-jack-session Disable JACK session support --disable-jack-metadata Disable JACK metadata support --enable-port-refresh Enable JACK port refresh support --disable-nsm Disable NSM support --enable-both Enable Qt and command-line builds --enable-alsatopology Force to use the Alsa topology library --disable-alsatest Do not try to compile and run a test Alsa program --disable-rtmidi Disable rtmidi MIDI engine --enable-cli Enable rtmidi command-line build --disable-qt Disable Qt5 user-interface --enable-portmidi Enable portmidi build) --enable-coverage=(no/yes) Turn on a test-coverage build (default=no) --enable-profile=(no/yes/gprof/prof) Turn on profiling builds (default=no, yes=gprof) --enable-debug=(no/yes/db/gdb) Turn on debug builds (default=no, yes=gdb) --enable-debug=(no/yes) Turn on call instrumentation (default=no) Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-pic[=PKGS] try to use only PIC/non-PIC objects [default=use both] --with-aix-soname=aix|svr4|both shared library versioning (aka "SONAME") variant to provide on AIX, [default=aix]. --with-gnu-ld assume the C compiler uses GNU ld [default=no] --with-sysroot[=DIR] Search for dependent libraries within DIR (or the compiler's sysroot if not specified). --with-alsa-prefix=PFX Prefix where Alsa library is installed(optional) --with-alsa-inc-prefix=PFX Prefix where include libraries are (optional) --with-x use the X Window System Some influential environment variables: CC C compiler command CFLAGS C compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory CXX C++ compiler command CXXFLAGS C++ compiler flags CPP C preprocessor CXXCPP C++ preprocessor LT_SYS_LIBRARY_PATH User-defined run-time library search path. PKG_CONFIG path to pkg-config utility PKG_CONFIG_PATH directories to add to pkg-config's search path PKG_CONFIG_LIBDIR path overriding pkg-config's built-in search path JACK_CFLAGS C compiler flags for JACK, overriding pkg-config JACK_LIBS linker flags for JACK, overriding pkg-config LIBLO_CFLAGS C compiler flags for LIBLO, overriding pkg-config LIBLO_LIBS linker flags for LIBLO, overriding pkg-config XMKMF Path to xmkmf, Makefile generator for X Window System Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to . ================================================ FILE: contrib/DIR_COLORS ================================================ #****************************************************************************** # /etc/DIR_COLORS #------------------------------------------------------------------------------ # # \file DIR_COLORS # \author Chris Ahlstrom # \date 2008-02-26 # \updates 2025-02-16 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Configuration file for dircolors, a utility to help you set the # LS_COLORS environment variable used by GNU ls with the --color option. # # Many edits and updates by Chris Ahlstrom, and updates from GNU, # Debian, Gentoo. Also adds file extensions used by Seq66. # # The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the slackware # version of dircolors) are recognized but ignored. # # Below, there should be one TERM entry for each termtype that is # colorizable # #------------------------------------------------------------------------------ TERM Eterm TERM ansi TERM color-xterm TERM con132x25 TERM con132x30 TERM con132x43 TERM con132x60 TERM con80x25 TERM con80x28 TERM con80x30 TERM con80x43 TERM con80x50 TERM con80x60 TERM cons25 TERM console TERM cygwin TERM dtterm TERM eterm-color TERM gnome TERM gnome-256color TERM jfbterm TERM konsole TERM kterm TERM linux TERM linux-c TERM mach-color TERM mlterm TERM putty TERM rxvt TERM rxvt-256color TERM rxvt-cygwin TERM rxvt-cygwin-native TERM rxvt-unicode TERM rxvt-unicode-256color TERM rxvt-unicode256 TERM screen TERM screen-256color TERM screen-256color-bce TERM screen-bce TERM screen-w TERM screen.Eterm TERM screen.rxvt TERM screen.linux TERM terminator TERM tmux-256color TERM vt100 TERM xterm TERM xterm-16color TERM xterm-256color TERM xterm-88color TERM xterm-color TERM xterm-debian #------------------------------------------------------------------------------ # Below are the color init strings for the basic file types. A color init # string consists of one or more of the following numeric codes: # # Attribute codes: # # 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed # # Text color codes: # # 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white # # Background color codes: # # 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white #------------------------------------------------------------------------------ # No color at all: NORMAL 00 # global default, although everything should be something. FILE 00 # normal file RESET 0 # reset to "normal" color DIR 01;34 # directory LINK 01;36 # symbolic link. (If you set this to 'target' instead of a # numerical value, the color is as for the file pointed to.) MULTIHARDLINK 00 # regular file with more than one link FIFO 40;33 # pipe SOCK 01;35 # socket DOOR 01;35 # door BLK 40;33;01 # block device driver CHR 40;33;01 # character device driver # ORPHAN 40;31;01 # symlink to nonexistent file ORPHAN 01;05;37;41 # orphaned syminks MISSING 01;05;37;41 # ... and the files they point to SETUID 37;41 # file that is setuid (u+s) SETGID 30;43 # file that is setgid (g+s) CAPABILITY 30;41 # file with capability STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w) OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky # Samba mounts are hard to read with this coloring. # # OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky OTHER_WRITABLE 30;42 # dir that is other-writable (o+w) and not sticky STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable # This (bold green) is for files with execute permission: EXEC 01;32 # *.keep files from Rational ClearCase .keep 05;31 # Internationalization files from GNU (black-on-white) .gmo 07;30;47 .mo 07;30;47 .po 07;30;47 .pot 07;30;47 .qm 07;30;47 # List any file extensions like '.gz' or '.tar' that you would like ls # to colorize below. Put the extension, a space, and the color init string. # (and any comments you want to add after a '#') # If you use DOS-style suffixes, you may want to uncomment the following: # # .cmd 01;32 # executables (bright green) # .exe 01;32 # .com 01;32 # .btm 01;32 # .bat 01;32 # DOS executable formats (dark yellow) and other stuff, like codecs, sed # scripts, and intl-sed scripts (sin). .acm 00;33 .ax 00;33 .bat 00;33 .BAT 00;33 .btm 00;33 .cmd 00;33 .com 00;33 .COM 00;33 .dll 00;33 .DLL 00;33 .exe 00;33 .EXE 00;33 .m4 00;33 .qts 00;33 .qtx 00;33 .sed 00;33 .sig 00;33 .sin 00;33 .sh 00;33 # ISO files (bright yellow) and font files, including sound fonts and # patches. .iso 00;33;01 .pat 00;33;01 .pfm 00;33;01 .raw 00;33;01 .sf2 00;33;01 .ttf 00;33;01 # kernel modules .ko 00 ;33 # Open-format documents or libraries (green) .0 00;32 .1 00;32 .2 00;32 .3 00;32 .4 00;32 .5 00;32 .6 00;32 .7 00;32 .8 00;32 .9 00;32 .a 00;32 .css 00;32 .dat 00;32 .dia 00;32 .diff 00;32 .dot 00;32 .dox 00;32 .gnumeric 00;32 .info 00;32 .key 00;32 .la 00;32 .lib 00;32 .list 00;32 .log 00;32 .lst 00;32 .lyx 00;32 .md 00;32 .mib 00;32 .odg 00;32 .odp 00;32 .ods 00;32 .odt 00;32 .ogt 00;32 .patch 00;32 .pdf 00;32 .ps 00;32 .so 00;32 .spec 00;32 .sty 00;32 .sxw 00;32 .tex 00;32 .texi 00;32 .tjx 00;32 .text 00;32 .txt 00;32 .TXT 00;32 .uml 00;32 # XML .xhtml 01;30;47 .xmi 01;30;47 .html 01;30;47 .ssml 01;30;47 .xml 01;30;47 # archives or compressed (bright red) .7z 01;31 .ace 01;31 .apk 01;31 .ar 01;31 .arc 01;31 .arj 01;31 .bz 01;31 .bz2 01;31 .cab 01;31 .cfs 01;31 .cpio 01;31 .dar 01;31 .deb 01;31 .dmg 01;31 .dz 01;31 .ear 01;31 .gz 01;31 .hqx 01;31 .infl 01;31 .jar 01;31 .lha 01;31 .lz 01;31 .lzh 01;31 .lzma 01;31 .lzo 01;31 .lzx 01;31 .mar 01;31 .pea 01;31 .rar 01;31 .rpm 01;31 .rz 01;31 .s7z 01;31 .sar 01;31 .sea 01;31 .sfark 01;31 .sfx 01;31 .shar 01;31 .tar 01;31 .taz 01;31 .tbz2 01;31 .tgz 01;31 .tlz 01;31 .txz 01;31 .tz 01;31 .war 01;31 .wim 01;31 .wsz 01;31 .xar 01;31 .xz 01;31 .z 01;31 .Z 01;31 .zip 01;31 .zipx 01;31 .zoo 01;31 .zpaq 01;31 .zz 01;31 # Zip archive renamed for transmission in screw-ball secured networks. .piz 05;31 # image formats .bmp 00;35 .cel 00;35 .dl 00;35 .eps 00;35 .fli 00;35 .gif 00;35 .gl 00;35 .ico 00;35 .jpeg 00;35 .jpg 00;35 .mng 00;35 .mpg 00;35 .pat 00;35 .pbm 00;35 .pcx 00;35 .pgm 00;35 .png 00;35 .ppm 00;35 .psd 00;35 .svg 00;35 .tga 00;35 .tif 00;35 .tiff 00;35 .xbm 00;35 .xcf 00;35 .xpm 00;35 .xwd 00;35 # Video formats .avi 01;35 .flc 01;35 # AutoDesk Animator .flv 01;35 # Apple? .m2v 01;35 # MPEG-2 Video only .mkv 01;35 # Matroska (http://matroska.org/) .m4v 01;35 # MPEG-4 Video only .mp4 01;35 # 'Offical' container for MPEG-4 .mpeg 01;35 .mov 01;35 .mp4v 01;35 # MPEG-4 Video only .nuv 01;35 .ogm 01;35 # Ogg Media File .qt 01;35 # Quicktime (http://developer.apple.com/qa/qtw/qtw99.html) .swf 01;35 .rm 01;35 # Real Media .rmvb 01;35 # Real Media Variable Bitrate .vob 01;35 # MPEG-2 DVD .yuv 01;35 .webm 01;35 # Source-code formats (cyan) .C 00;36 .CPP 00;36 .H 00;36 .asm 00;36 .awk 00;36 .c 00;36 .c++ 00;36 .cc 00;36 .ch 00;36 .cpp 00;36 .cs 00;36 .cxx 00;36 .f77 00;36 .for 00;36 .groovy 00;36 .h 00;36 .hh 00;36 .hpp 00;36 .hxx 00;36 .java 00;36 .js 00;36 .m 00;36 .mc 00;36 .pas 00;36 .pl 00;36 .pm 00;36 .py 00;36 .pyw 01;36 .s 00;36 .sl 00;36 # Project files and configuration files, thin and dark red .ac 01;33 .am 01;33 .bcc 01;33 .bpr 01;33 .bpk 01;33 .bpg 01;33 .build 01;33 .cfg 01;33 .ctrl 01;33 .def 01;33 .dfm 01;33 .drums 01;33 .guess 01;33 .header 01;33 .in 01;33 .ini 01;33 .keymap 01;33 .mutes 01;33 .notemap 01;33 .opts 01;33 .options 01;33 .patches 01;33 .pc 01;33 .PL 01;33 .palette 01;33 .playlist 01;33 .prj 01;33 .pro 01;33 .pws 01;33 .tjp 01;33 .tji 01;33 .qss 01;33 .rc 01;33 .rh 01;33 .resp 01;33 .session 01;33 .sessions 01;33 .state 01;33 .sub 01;33 .ui 01;33 .user 01;33 .usr 01;33 .vim 01;33 .wrap 01;33 # Audio wave formats (white) .MID 00;37 .MP3 00;37 .WAV 00;37 .WMA 00;37 .aac 00;37 .au 00;37 .flac 00;37 .h2 00;37 .h2song 00;37 .h2pattern 00;37 .ogg 00;37 .m3u 00;37 .mid 00;37 .midi 00;37 .mod 00;37 .mka 00;37 .mp3 00;37 .pcm 00;37 .pls 00;37 .ra 00;37 .rm 00;37 .s3m 00;37 .sid 00;37 .sph 00;37 .wav 00;37 .wma 00;37 .xm 00;37 # Test formats (bright white) # # These extensions are made-up ones to see these colors. You might not be # able to see some of the numbers on your screen . .blk 00;30 .wht 01;37 .x38 00;38 .x39 00;39 # Microsoft formats not specified elsewhere in this file. Changed from # 34 (blue) to 30 (black), made bold for easier reading on a black # terminal. Note that .chm is actually a compressed file, using (like # .cab) a form of compression that Microsoft copped from the Amiga!!! # # .asp 01;33;43 .asp 00;33 .aspx 00;33 .chm 00;33 .doc 00;33 .docx 00;33 .dsp 00;33 .filters 00;33 .htm 00;33 .img 00;33 .mak 00;33 .mk 00;33 .mdl 00;33 .mpp 00;33 .pj 00;33 .ppt 00;33 .pptx 00;33 .reg 00;33 .rtf 00;33 .sln 00;33 .sys 00;33 .use 00;33 .vc7 00;33 .vcproj 00;33 .vcxproj 00;33 .vsd 00;33 .wrk 00;33 .xls 00;33 .xlsx 00;33 .DOC 00;33 .HTM 00;33 .PPT 00;33 .XLS 00;33 .VSD 00;33 # Test formats (bold black) .bbl 01;30 # Object files and intermediate formats (reduce their visibility) .aux 00;34 .dvi 00;34 .idb 00;34 .idx 00;34 .map 00;34 .moc 00;34 .ncb 00;34 .o 00;34 .obj 00;34 .out 00;34 .pdb 00;34 .pyc 00;34 .pyo 00;34 .suo 00;34 .toc 00;34 # AutoCAD formats .dwg 00;34 .dxf 00;34 # Hybrid formats (white on blue) .asf 44;37 # Advanced Systems Format (contains Windows Media Video) .wmv 44;37 # Windows Media Video #****************************************************************************** # /etc/DIR_COLORS #------------------------------------------------------------------------------ # Local Variables: # End: #------------------------------------------------------------------------------ # vim: ts=3 sw=3 et ft=dircolors #------------------------------------------------------------------------------ ================================================ FILE: contrib/VMPK.conf ================================================ [Connections] AdvancedEnabled=true InEnabled=false InPort= InputDriver=ALSA OmniEnabled=false OutPort=Midi Through OutputDriver=ALSA ThruEnabled=false [Controllers0] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers1] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers10] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers11] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers12] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers13] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers14] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers15] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers2] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers3] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers4] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers5] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers6] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers7] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers8] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [Controllers9] 1=0 10=64 11=127 2=0 4=0 5=0 64=0 65=0 66=0 67=0 69=0 7=100 8=0 91=127 92=0 93=0 94=0 95=0 [DrumstickRT] PublicNameIN=VMPK Input PublicNameOUT=VMPK Output [ExtraControllers] 00="Sustain,64,0,0,64,0," 01="Sostenuto,66,0,0,64,0," 02="Soft,67,0,0,64,0," [Instrument0] Bank=-1 Controller=1 Program=0 [Instrument1] Bank=-1 Controller=1 Program=0 [Instrument10] Bank=-1 Controller=1 Program=0 [Instrument11] Bank=-1 Controller=1 Program=0 [Instrument12] Bank=-1 Controller=1 Program=0 [Instrument13] Bank=-1 Controller=1 Program=0 [Instrument14] Bank=-1 Controller=1 Program=0 [Instrument15] Bank=-1 Controller=1 Program=0 [Instrument2] Bank=-1 Controller=1 Program=0 [Instrument3] Bank=-1 Controller=1 Program=0 [Instrument4] Bank=-1 Controller=1 Program=0 [Instrument5] Bank=-1 Controller=1 Program=0 [Instrument6] Bank=-1 Controller=1 Program=0 [Instrument7] Bank=-1 Controller=1 Program=0 [Instrument8] Bank=-1 Controller=1 Program=0 [Instrument9] Bank=-1 Controller=1 Program=0 [Keyboard] MapFile=default RawKeyboardMode=false RawMapFile=default [Palette_0] 1\color=@Variant(\0\0\0\x43\x1\xff\xff\x30\x30\x8c\x8c\xc6\xc6\0\0) size=1 [Palette_1] 1\color=@Variant(\0\0\0\x43\x1\xff\xff\x30\x30\x8c\x8c\xc6\xc6\0\0) 2\color=@Variant(\0\0\0\x43\x1\xff\xff||\xfc\xfc\0\0\0\0) size=2 [Palette_2] 1\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\0\0\0\0\0\0) 10\color=@Variant(\0\0\0\x43\x1\xff\xff\x30\x30\x8c\x8c\xc6\xc6\0\0) 11\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\x80\x80\x80\x80\0\0) 12\color=@Variant(\0\0\0\x43\x1\xff\xff\xd2\xd2ii\x1e\x1e\0\0) 13\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\0\0\xff\xff\0\0) 14\color=@Variant(\0\0\0\x43\x1\xff\xffkk\x8e\x8e##\0\0) 15\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\xff\xff\xff\xff\0\0) 16\color=@Variant(\0\0\0\x43\x1\xff\xff\xad\xad\xff\xff//\0\0) 2\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\xff\xff\0\0\0\0) 3\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\0\0\xff\xff\0\0) 4\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\xd7\xd7\0\0\0\0) 5\color=@Variant(\0\0\0\x43\x1\xff\xff\x80\x80\0\0\0\0\0\0) 6\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\x80\x80\0\0\0\0) 7\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\0\0\x80\x80\0\0) 8\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\x8c\x8c\0\0\0\0) 9\color=@Variant(\0\0\0\x43\x1\xff\xff\x80\x80\0\0\x80\x80\0\0) size=16 [Palette_3] 1\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\0\0\0\0\0\0) 10\color=@Variant(\0\0\0\x43\x1\xff\xff\x7f\x7f\0\0\xff\xff\0\0) 11\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\0\0\xff\xff\0\0) 12\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\0\0\x7f\x7f\0\0) 2\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\x7f\x7f\0\0\0\0) 3\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\xff\xff\0\0\0\0) 4\color=@Variant(\0\0\0\x43\x1\xff\xff\x7f\x7f\xff\xff\0\0\0\0) 5\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\xff\xff\0\0\0\0) 6\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\xff\xff\x7f\x7f\0\0) 7\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\xff\xff\xff\xff\0\0) 8\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\x7f\x7f\xff\xff\0\0) 9\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\0\0\xff\xff\0\0) size=12 [Palette_4] 1\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0) 2\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\0\0\0\0\0\0) size=2 [Palette_5] 1\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\0\0\0\0\0\0) 2\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0) 3\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0) 4\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0) size=4 [Palette_6] 1\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\0\0\0\0\0\0) 10\color=@Variant(\0\0\0\x43\x1\xff\xff\x7f\x7f\0\0\xff\xff\0\0) 11\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\0\0\xff\xff\0\0) 12\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\0\0\x7f\x7f\0\0) 2\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\x7f\x7f\0\0\0\0) 3\color=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\xff\xff\0\0\0\0) 4\color=@Variant(\0\0\0\x43\x1\xff\xff\x7f\x7f\xff\xff\0\0\0\0) 5\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\xff\xff\0\0\0\0) 6\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\xff\xff\x7f\x7f\0\0) 7\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\xff\xff\xff\xff\0\0) 8\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\x7f\x7f\xff\xff\0\0) 9\color=@Variant(\0\0\0\x43\x1\xff\xff\0\0\0\0\xff\xff\0\0) size=12 [Preferences] AlwaysOnTop=false BaseOctave=0 Channel=0 CurrentPalette=0 DrumsChannel=9 EnableKeyboardInput=true EnableMouseInput=true EnableTouchInput=true EnforceChannelState=false ForcedDarkMode=false InstrumentName=General MIDI InstrumentsDefinition=:/vpiano/gmgsxg.ins Language= NumKeys=25 QtStyle= ShowColorScale=false ShowStatusBar=false StartingKey=0 Transpose=0 Velocity=100 VelocityColor=true [Shortcuts] actionChannelDown=Down actionChannelUp=Up actionContents=F1 actionControllerDown=Alt+- actionControllerUp=Alt++ actionNextBank=Ctrl+PgUp actionNextController=Ctrl++ actionNextProgram=PgUp actionOctaveDown=Left actionOctaveUp=Right actionPanic=Esc actionPreviousBank=Ctrl+PgDown actionPreviousController=Ctrl+- actionPreviousProgram=PgDown actionTransposeDown=Ctrl+Left actionTransposeUp=Ctrl+Right actionVelocityDown=Home actionVelocityUp=End [TextSettings] namesAlteration=0 namesFont="Helvetica,50" namesOctave=1 namesOrientation=0 namesVisibility=3 octaveSubscript=true [Window] Geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\x5\n\0\0\x3\xf\0\0\a\"\0\0\x3\xf0\0\0\x5\x10\0\0\x3'\0\0\a\x1c\0\0\x3\xea\0\0\0\0\0\0\0\0\a\x80\0\0\x5\x10\0\0\x3'\0\0\a\x1c\0\0\x3\xea) State=@ByteArray(\0\0\0\xff\0\0\0\0\xfd\0\0\0\0\0\0\x2\r\0\0\0\x85\0\0\0\x4\0\0\0\x4\0\0\0\b\0\0\0\b\xfc\0\0\0\x2\0\0\0\x2\0\0\0\x2\0\0\0\x18\0t\0o\0o\0l\0\x42\0\x61\0r\0N\0o\0t\0\x65\0s\x1\0\0\0\0\xff\xff\xff\xff\0\0\0\0\0\0\0\0\0\0\0\x1e\0t\0o\0o\0l\0\x42\0\x61\0r\0P\0r\0o\0g\0r\0\x61\0m\0s\0\0\0\x2\r\xff\xff\xff\xff\0\0\0\0\0\0\0\0\0\0\0\x2\0\0\0\x3\0\0\0$\0t\0o\0o\0l\0\x42\0\x61\0r\0\x43\0o\0n\0t\0r\0o\0l\0l\0\x65\0r\0s\0\0\0\0\0\xff\xff\xff\xff\0\0\0\0\0\0\0\0\0\0\0\x18\0t\0o\0o\0l\0\x42\0\x61\0r\0\x45\0x\0t\0r\0\x61\0\0\0\0\0\xff\xff\xff\xff\0\0\0\0\0\0\0\0\0\0\0\x1a\0t\0o\0o\0l\0\x42\0\x61\0r\0\x42\0\x65\0n\0\x64\0\x65\0r\0\0\0\x1\x34\xff\xff\xff\xff\0\0\0\0\0\0\0\0) ================================================ FILE: contrib/Xdefaults.rc ================================================ !****************************************************************************** ! Xdefaults (~/.Xdefaults) !------------------------------------------------------------------------------ ! ! Maintainer: Chris Ahlstrom ! Last Change: 2011-10-18 to 2024-08-16 ! Project: Portable Configuration ! License: None. Use it in any manner whatsover, and don't blame me. ! Usage: ! ! Also see the following for getting internationalization (unicode fonts) ! to work: ! ! http://www.gentoo.org/doc/en/utf-8.xml ! ! To use this file, simply copy it to your home directory in this ! manner: ! ! cp Xdefaults.rc ~/.Xdefaults ! !------------------------------------------------------------------------------ ! ------------ Resource name "urxvt" ------------------------------------- urxvt.geometry : 82x56 ! Transparency without a compositing manager. ! Shading ranges from darkest (0) to no shading (100) to lightest (200). ! Blur radius is in pixels. ! ! urxvt.transparent : true ! urxvt.tintColor : #142020 ! urxvt.shading : 110 ! urxvt.blurRadius : 8 urxvt.transparent : false ! Transparency under compositing manager ! urxvt.depth : 32 ! urxvt.background : rgba:0000/0000/0000/aaaa ! urxvt*background: #000000 ! urxvt*foreground: #ffffff ! urxvt*background : #142020 ! urxvt*foreground : #ffdfaf urxvt.background : #000000 urxvt*foreground : #20ff20 urxvt*borderLess : false ! Colors for man pages urxvt.colorIT : #87af5f urxvt.colorBD : #00a0a0 urxvt.colorUL : #00a000 ! Colors for scroll-bar urxvt.scrollBar : false urxvt.scrollColor : #808080 urxvt.troughColor : #202020 ! Fading of text, in percent ! ! urxvt.fading : 50 ! urxvt.fadeColor : #142020 ! Colors. Gnome-terminal and Mrxvt varieties supplied. ! black urxvt.color0 : #000000 urxvt.color8 : #555555 ! red urxvt.color1 : #AA0000 urxvt.color9 : #FF5555 ! green urxvt.color2 : #00AA00 urxvt.color10 : #55FF55 ! yellow urxvt.color3 : #AA5500 urxvt.color11 : #FFFF55 ! blue urxvt.color4 : #0000AA urxvt.color12 : #5555FF ! magenta urxvt.color5 : #AA00AA urxvt.color13 : #FF55FF ! cyan urxvt.color6 : #00AAAA urxvt.color14 : #55FFFF ! white urxvt.color7 : #AAAAAA urxvt.color15 : #FFFFFF ! urxvt*font: xft:Arimo:pixelsize=12 ! urxvt*boldFont: xft:Arimo:pixelsize=12 ! urxvt*font: xft:Droid-Sans:pixelsize=12 ! urxvt*boldFont: xft:Droid-Sans:pixelsize=12 ! urxvt*font: xft:Monospace:pixelsize=12:antialias=false ! urxvt*font: xft:Anonymous Pro:pixelsize=16:antialias=true ! urxvt*boldFont: xft:Monospace:pixelsize=14 urxvt*font: terminus-16 ! urxvt*letterSpace: -1 ! urxvt.perl-ext-common : default,matcher ! urxvt.perl-ext-common: default,tabbed ! urxvt.urlLauncher : firefox ! urxvt.matcher.button : 1 ! urxvt.tabbed.tabbar-fg : 3 ! urxvt.tabbed.tabbar-bg : 0 ! urxvt.tabbed.tab-fg : 0 ! urxvt.tabbed.tab-bg : 1 ! ------------ Resource name "emrxvt" ------------------------------------- ! ! Using this resource name with urxvt makes it closely match the appearance ! of my old mrxvt setup. Smaller. emrxvt.geometry : 82x44 emrxvt.saveLines : 8192 emrxvt.pointerBlank : true ! Transparency without a compositing manager. ! ! Shading ranges from darkest (0) to no shading (100) to lightest (200). ! The tintColor is such that black (#000000) deeply shades the background, ! while white (#ffffff) very lightly shades it, preserving the colors. ! Blur radius is in pixels. emrxvt.transparent : true emrxvt.tintColor : #ffffff emrxvt.shading : 20 emrxvt.blurRadius : 8 ! Transparency under compositing manager emrxvt.depth : 32 emrxvt.background : rgba:0000/0000/0000/aaaa emrxvt*borderLess:false emrxvt*background : #142020 emrxvt*foreground : #ffdfaf ! Borders emrxvt*internalBorder : 1 emrxvt*externalBorder : 1 ! Cursor settings emrxvt*cursorBlink : true emrxvt*cursorColor : #00ff00 emrxvt*cursorColor2 : #0000ff ! Colors for man pages emrxvt.colorIT : #87af5f emrxvt.colorBD : #00a0a0 emrxvt.colorUL : #00a000 ! Colors for scroll-bar emrxvt*scrollBar : false emrxvt*scrollstyle : rxvt emrxvt*scrollBar_floating : true emrxvt*borderColor : #808080 emrxvt*scrollColor : #80d080 emrxvt*troughColor : #208020 emrxvt*font: xft:Monospace:pixelsize=12 ! emrxvt*font: xft:Anonymous Pro:pixelsize=15:antialias=true ! emxvt*letterSpace: -1 ! Uncomment the following if you do not want BOLD text to be used. ! Allowing bold text makes some file types in 'ls' bold, and some ! keywords in C (under vim) bold. If you do not like bold file/directory ! names in 'ls', then modify /etc/DIR_COLORS as the preferred method. ! ! emrxvt*boldFont: xft:Monospace:pixelsize=14 ! ! Fading of text, in percent ! ! Does not seem to have any effect. Oh, needs the afterimage and fading ! options to be enabled when compiling on Gentoo. emrxvt.fading : 20 emrxvt.fadeColor : #142020 ! Colors. Gnome-terminal and Mrxvt varieties supplied. ! black emrxvt.color0 : #101010 emrxvt.color8 : #303030 ! red emrxvt.color1 : #ef0000 emrxvt.color9 : #ff0000 ! green emrxvt.color2 : #00a000 emrxvt.color10 : #00f000 ! yellow emrxvt.color3 : #909000 emrxvt.color11 : #c0c000 ! blue emrxvt.color4 : #0000ff emrxvt.color12 : #0000ff ! magenta emrxvt.color5 : #ef00ef emrxvt.color13 : #a000a0 ! cyan emrxvt.color6 : #00c0c0 emrxvt.color14 : #00c0c0 ! white emrxvt.color7 : #b0b0b0 emrxvt.color15 : #e0e0e0 ! emrxvt.perl-ext-common : default,matcher ! Tabs ! ! This is junk, therefore I am adopting GNU screen instead. ! ! emrxvt.perl-ext-common : tabbed ! emrxvt.tabbed.tabbar-fg : 5 ! emrxvt.tabbed.tabbar-bg : 0 ! emrxvt.tabbed.tab-fg : 14 ! emrxvt.tabbed.tab-bg : 1 ! emrxvt.autohide: no ! emrxvt.new-button: no ! emrxvt.urlLauncher : firefox ! emrxvt.matcher.button : 1 ! ------------ Resource name "XTerm" ------------------------------------- XTerm*geometry : 82x44 XTerm*scrollBar : false XTerm*internalBorder : 6 XTerm*cursorBlink : true XTerm*cursorColor : #00ff00 XTerm*cursorColor2 : #0000ff # XTerm*background : #142020 # XTerm*foreground : #ffdfaf XTerm*background : #0a1010 XTerm*foreground : #ffdfaf ! Colors. Gnome-terminal and Mrxvt varieties supplied. ! black XTerm*color0 : #000000 XTerm*color8 : #555555 ! red XTerm*color1 : #CC0000 XTerm*color9 : #FF5555 ! green XTerm*color2 : #00CC00 XTerm*color10 : #55FF55 ! yellow XTerm*color3 : #CC5500 XTerm*color11 : #FFFF55 ! blue XTerm*color4 : #0000CC XTerm*color12 : #5555FF ! magenta XTerm*color5 : #CC00CC XTerm*color13 : #FF55FF ! cyan XTerm*color6 : #00CCCC XTerm*color14 : #55FFFF ! white XTerm*color7 : #CCCCCC XTerm*color15 : #FFFFFF XTerm*faceName: Anonymous Pro:pixelsize=16 !------------------------------------------------------------------------------ ! End of .Xdefaults !------------------------------------------------------------------------------ ! vim: ts=3 noet ft=xdefaults syntax=xdefaults !------------------------------------------------------------------------------ ================================================ FILE: contrib/code/affinity.cpp ================================================ #include /* std::cout */ #include /* std::recursive_mutex, etc. */ #include /* std::thread */ #include /* std::vector<> */ #include /* C::CPU_SET macros and types */ int main (int argc, const char ** argv) { constexpr unsigned num_threads = 4; // A mutex ensures orderly access to std::cout from multiple threads. std::mutex iomutex; std::vector threads(num_threads); for (unsigned i = 0; i < num_threads; ++i) { threads[i] = std::thread ( [&iomutex, i] { std::this_thread::sleep_for(std::chrono::milliseconds(20)); while (1) { { // Use a lexical scope and lock_guard to safely lock the // mutex only for the duration of std::cout usage. std::lock_guard iolock(iomutex); std::cout << "Thread #" << i << ": on CPU " << sched_getcpu() << "\n"; } // Simulate important work done by the tread by sleeping for // a bit... std::this_thread::sleep_for(std::chrono::milliseconds(900)); } } ); // Create a cpu_set_t object representing a set of CPUs. Clear it and mark // only CPU i as set. cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(i, &cpuset); int rc = pthread_setaffinity_np(threads[i].native_handle(), sizeof(cpu_set_t), &cpuset); if (rc != 0) { std::cerr << "Error calling pthread_setaffinity_np: " << rc << "\n"; } } for (auto & t : threads) { t.join(); } return 0; } /* * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: contrib/code/ametro.c ================================================ /* * ALSA MIDI CLI Metronome * Copyright (C) 2002-2009 Pedro Lopez-Cabanillas * Copyright (C) 2002-2009 Pedro Lopez-Cabanillas * * 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. * * Compile with: * * No: gcc -o ametro -lasound ametro.c * Yes gcc -o ametro ametro.c -lasound * * Found at these two sites: * * http://lists.linuxaudio.org/pipermail/linux-audio-user/2009-August/061724.html * http://lalists.stanford.edu/lau/2009/08/att-0005/ametro.c * * "Only trick I don't get in first place was to put -m flag to send master * clock to my sequencer which was necessary in this point to do so." * * Modified by C. Ahlstrom 2022-01-16 to 2022-08-29: * * - Changed tabs to spaces. * - White space alignment. * - Tweaks to coding conventions. * - Added ability to output start, stop, and continue clock events. * - More comprehensive error detection. * - More verbosity. * * Further research: * * For handling characters in order to have the user emit various MIDI * clock commands: * * https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html * https://github.com/ShakaUVM/colors */ #include #include #include #include #include #include #include #include #include #define nullptr NULL #define MIDI_CHANNEL 9 #define MIDI_STRONG_NOTE 34 #define MIDI_WEAK_NOTE 33 #define MIDI_VELOCITY 64 #define MIDI_PROGRAM 0 #define TICKS_PER_QUARTER 120 #define TIME_SIGNATURE_NUM 4 #define TIME_SIGNATURE_FIG 4 #define BPM 100 #define FALSE 0 #define TRUE 1 char * port_address = nullptr; snd_seq_t * seq_handle = nullptr; int queue_id = (-1); int port_in_id = (-1); int port_out_id = (-1); int measure = 0; int bpm = BPM; int resolution = TICKS_PER_QUARTER; int weak = MIDI_WEAK_NOTE; int strong = MIDI_STRONG_NOTE; int velocity = MIDI_VELOCITY; int program = MIDI_PROGRAM; int channel = MIDI_CHANNEL; int num_parts = TIME_SIGNATURE_NUM; int part_fig = TIME_SIGNATURE_FIG; int verbose = TRUE; int master = FALSE; int notes = TRUE; int slave = FALSE; typedef enum { CT_START, CT_CONTINUE, CT_STOP, CT_CLOCK } clock_type; void make_clock_event (int tick, clock_type ct); static void show_error (const char * msg) { fprintf(stderr, "Error: %s\n", msg); } static void show_info (const char * msg) { fprintf(stdout, "%s\n", msg); } static void show_error_string (const char * msg, int rc) { fprintf(stderr, "%s (%s)\n", msg, snd_strerror(rc)); } void show_msg (const char * msg) { printf("%s\n", msg); } void usage () { show_msg ( "Usage:\n" " ametro\n" " [ --output CLIENT:PORT ] [ --resolution PPQ ]\n" " [ --signature N:M ] [ --tempo BPM ]\n" " [ --weak NOTE ] [ --strong NOTE ]\n" " [ --velocity 0..127 ] [ --channel 0..15 ]\n" " [ --program 0..127 ] (more options shown below)\n" "\n" "Options:\n" "\n" " -c, --channel MIDI channel, range 0 to 15, default 9.\n" " -g, --strong MIDI note# for each measure's strong part, default 34.\n" " -h, --help This message.\n" " -m, --master Output also MIDI clock messages.\n" " -M, --masterclock Output only MIDI clock messages, not note on/off.\n" " -o, --output Pair of CLIENT:PORT, as ALSA numbers or names.\n" " -p, --program MIDI Program, default 0.\n" " -q, --quiet Don't display messages or banners.\n" " -r, --resolution Tick resolution per quarter note (PPQ), default 120.\n" " -s, --signature Time signature (#:#), default 4:4.\n" " -S, --slave Accept/send MIDI start, stop and continue messages.\n" " -t, --tempo Speed, in BPM, default 100.\n" " -v, --velocity MIDI note on velocity, default 64.\n" " -w, --weak MIDI note# for each measure's weak part, default 33.\n" "\n" " The output port is required, either on the command line or the environment:\n" "\n" " ALSA_OUTPUT_PORTS = 128:1\n" " ALSA_OUT_PORT = 128:1 or the initial part of a client name\n" "\n" ); } /** * This function changes the standard input from "canonical" mode (which * means it buffers until a newline is read) into raw mode, where it will * return one keystroke at a time. * * set_raw_mode(TRUE) will turn on nonblocking I/O for standard input. * * set_raw_mode(FALSE) will reset I/O to work like normal. */ static int raw_mode = FALSE; /* default: canonical mode */ void set_raw_mode (int flag) { static struct termios old_tio; /* to save the old settings */ if (flag && ! raw_mode) /* save original term mode */ { int rc = tcgetattr(STDIN_FILENO, &old_tio); if (rc == 0) { struct termios tio = old_tio; /* * Disable echo and canonical (cooked) mode. */ tio.c_lflag &= ~(ICANON | ECHO); rc = tcsetattr(STDIN_FILENO, TCSANOW, &tio); if (rc != 0) { show_error("tcsetattr() failed to start raw mode"); } else { raw_mode = TRUE; show_msg("Raw console termio activated"); } } } else if (! flag && raw_mode) /* restore terminal mode */ { int rc = tcsetattr(STDIN_FILENO,TCSANOW, &old_tio); if (rc != 0) { show_error("tcsetattr() failed to restore cooked mode"); } else { raw_mode = FALSE; show_msg("Raw console termio deactivated"); } } } /** * Returns how many bytes are waiting in the input buffer. * * Precondition: * * Requires set_raw_mode(true) to work. * * Example: * * int bytes_available = kbcount() returns how many bytes are in the input * queue to be read> */ int kbcount () { int count = 0; if (raw_mode) ioctl(STDIN_FILENO, FIONREAD, &count); return count; } /** * Does a non-blocking I/O read from standard input and returns one * keystroke. It is a lightweight equivalent to ncurses' getch() function. * It returns all characters, including Esc. * * Precondition: * * Requires set_raw_mode(true) to work. * * Example: * * int ch = quick_read() returns -1 if no key has been hit, or the key * actually struck. */ int quick_read () { int result = (-1); if (raw_mode) { int bytes_available = kbcount(); if (bytes_available > 0) { int c = getchar(); --bytes_available; if (bytes_available == 0) { result = c; /* * Debugging only: */ if (verbose) printf("char 0x%02x returned\n", c); } /* * Dump the remaining bytes. We don't need them for our simple * purposes. Also we don't care about mouse events. */ for (int i = 0; i < bytes_available; ++i) { c = getchar(); /* * Debugging only: */ if (verbose) printf("Discarding char 0x%02x\n", c); } } } return result; } void bail_out () { set_raw_mode(FALSE); } int handle_char (int ch, int tick) { int result = 0; /* 1 == 'Esc' */ switch (ch) { case 's': make_clock_event(tick, CT_START); break; case 'c': make_clock_event(tick, CT_CONTINUE); break; case 'x': make_clock_event(tick, CT_STOP); break; case '.': make_clock_event(tick, CT_CLOCK); break; case 033: result = 1; break; } return result; } void open_sequencer () { int rc = snd_seq_open ( &seq_handle, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK ); if (rc < 0) { show_error("Opening ALSA sequencer"); exit(EXIT_FAILURE); } rc = snd_seq_set_client_name(seq_handle, "Metronome"); if (rc < 0) { show_error("Naming ALSA sequencer"); exit(EXIT_FAILURE); } port_out_id = snd_seq_create_simple_port ( seq_handle, "output", SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, SND_SEQ_PORT_TYPE_APPLICATION | SND_SEQ_PORT_TYPE_MIDI_GENERIC ); if (port_out_id < 0) { show_error("Creating output port"); snd_seq_close(seq_handle); exit(EXIT_FAILURE); } port_in_id = snd_seq_create_simple_port ( seq_handle, "input", SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_APPLICATION | SND_SEQ_PORT_TYPE_MIDI_GENERIC ); if (port_in_id < 0) { show_error("creating input port"); snd_seq_close(seq_handle); exit(EXIT_FAILURE); } } /** * Subscribe to a destination port. It's name or address must already be in * the global variable port_address. */ void subscribe () { snd_seq_addr_t dest; snd_seq_addr_t source; snd_seq_port_subscribe_t * subs; int rc = snd_seq_client_id(seq_handle); if (rc >= 0) { source.client = rc; source.port = port_out_id; } else { show_error("Could not get client ID"); exit(EXIT_FAILURE); } rc = snd_seq_parse_address(seq_handle, &dest, port_address); if (rc < 0) { fprintf(stderr, "Invalid source address %s\n", port_address); snd_seq_close(seq_handle); exit(EXIT_FAILURE); } snd_seq_port_subscribe_alloca(&subs); snd_seq_port_subscribe_set_sender(subs, &source); snd_seq_port_subscribe_set_dest(subs, &dest); snd_seq_port_subscribe_set_queue(subs, queue_id); snd_seq_port_subscribe_set_time_update(subs, 1); rc = snd_seq_get_port_subscription(seq_handle, subs); if (rc == 0) { show_error("Connection already subscribed"); snd_seq_close(seq_handle); exit(EXIT_FAILURE); } rc = snd_seq_subscribe_port(seq_handle, subs); if (rc < 0) { show_error_string("Connection failed", rc); snd_seq_close(seq_handle); exit(EXIT_FAILURE); } } /** * Queue commands */ void create_queue () { queue_id = snd_seq_alloc_queue(seq_handle); if (queue_id < 0) { show_error("Could not get queue ID"); exit(EXIT_FAILURE); } } void set_tempo (int tempo) { snd_seq_queue_tempo_t * queue_tempo; int truetempo = (int) ((6e7 * part_fig) / (tempo * 4)); snd_seq_queue_tempo_alloca(&queue_tempo); snd_seq_queue_tempo_set_tempo(queue_tempo, truetempo); snd_seq_queue_tempo_set_ppq(queue_tempo, resolution); snd_seq_set_queue_tempo(seq_handle, queue_id, queue_tempo); } void clear_queue () { snd_seq_remove_events_t * remove_ev; snd_seq_remove_events_alloca(&remove_ev); snd_seq_remove_events_set_queue(remove_ev, queue_id); snd_seq_remove_events_set_condition ( remove_ev, SND_SEQ_REMOVE_OUTPUT | SND_SEQ_REMOVE_IGNORE_OFF ); snd_seq_remove_events(seq_handle, remove_ev); if (verbose) show_info("Clear"); } void start_queue () { snd_seq_start_queue(seq_handle, queue_id, NULL); snd_seq_drain_output(seq_handle); if (verbose) show_info("Start"); } void stop_queue () { snd_seq_stop_queue(seq_handle, queue_id, NULL); snd_seq_drain_output(seq_handle); if (verbose) show_info("Stop"); } void continue_queue () { snd_seq_continue_queue(seq_handle, queue_id, NULL); snd_seq_drain_output(seq_handle); if (verbose) show_info("Continue"); } /** * Event commands */ void make_note (unsigned char note, int tick) { int sendcount; snd_seq_event_t ev; snd_seq_ev_clear(&ev); snd_seq_ev_set_note(&ev, channel, note, velocity, 1); snd_seq_ev_schedule_tick(&ev, queue_id, 1, tick); snd_seq_ev_set_source(&ev, port_out_id); snd_seq_ev_set_subs(&ev); sendcount = snd_seq_event_output_direct(seq_handle, &ev); if (sendcount < 0) show_error("make_note() output-direct"); } void make_echo (int tick) { int sendcount; snd_seq_event_t ev; snd_seq_ev_clear(&ev); ev.type = SND_SEQ_EVENT_USR1; snd_seq_ev_schedule_tick(&ev, queue_id, 1, tick); snd_seq_ev_set_dest(&ev, snd_seq_client_id(seq_handle), port_in_id); sendcount = snd_seq_event_output_direct(seq_handle, &ev); if (sendcount < 0) show_error("make_echo() output-direct"); } void make_clock (int tick) { int sendcount; snd_seq_event_t ev; snd_seq_ev_clear(&ev); ev.type = SND_SEQ_EVENT_CLOCK; snd_seq_ev_schedule_tick(&ev, queue_id, 1, tick); snd_seq_ev_set_source(&ev, port_out_id); snd_seq_ev_set_subs(&ev); sendcount = snd_seq_event_output_direct(seq_handle, &ev); if (sendcount < 0) show_error("make_clock() output-direct"); } void make_clock_event (int tick, clock_type ct) { const char * msg = "?"; int sendcount; snd_seq_event_t ev; snd_seq_ev_clear(&ev); switch (ct) { case CT_START: ev.type = SND_SEQ_EVENT_START; msg = "start"; break; case CT_CONTINUE: ev.type = SND_SEQ_EVENT_CONTINUE; msg = "continue"; break; case CT_STOP: ev.type = SND_SEQ_EVENT_STOP; msg = "stop"; break; case CT_CLOCK: ev.type = SND_SEQ_EVENT_CLOCK; msg = "clock"; break; } snd_seq_ev_schedule_tick(&ev, queue_id, 1, tick); snd_seq_ev_set_source(&ev, port_out_id); snd_seq_ev_set_subs(&ev); sendcount = snd_seq_event_output_direct(seq_handle, &ev); if (sendcount >= 0) show_msg(msg); else show_error("make_clock_event() output-direct"); } void pattern () { int part, tick, duration; /* * MIDI clock events */ if (master) { int maxtick = resolution * 4 * num_parts / part_fig; duration = resolution / 24; for (tick = 0; tick < maxtick; tick += duration) make_clock(tick); if (verbose) show_info("Clock"); } /* * Metronome notes */ tick = 0; duration = resolution * 4 / part_fig; for (part = 0; part < num_parts; ++part) { if (notes) make_note(part ? weak : strong, tick); tick += duration; } make_echo(tick); if (verbose) printf(" measure: %5d\r", ++measure); } void set_program () { int sendcount; snd_seq_event_t ev; if (verbose) { printf ( "Setting program %d, channel %d for output port %d\n", program, channel, port_out_id ); } snd_seq_ev_clear(&ev); snd_seq_ev_set_pgmchange(&ev, channel, program); snd_seq_ev_set_source(&ev, port_out_id); snd_seq_ev_set_subs(&ev); sendcount = snd_seq_event_output_direct(seq_handle, &ev); if (sendcount < 0) show_error_string("set_program() output-direct failed", sendcount); } void midi_action () { snd_seq_event_t * ev; do { snd_seq_event_input(seq_handle, &ev); switch (ev->type) { case SND_SEQ_EVENT_USR1: pattern(); break; case SND_SEQ_EVENT_START: measure = 0; start_queue(); pattern(); break; case SND_SEQ_EVENT_CONTINUE: continue_queue(); break; case SND_SEQ_EVENT_STOP: stop_queue(); break; } } while (snd_seq_event_input_pending(seq_handle, 0) > 0); } void sigterm_exit (int sig) { clear_queue(); sleep(1); snd_seq_stop_queue(seq_handle, queue_id, NULL); snd_seq_free_queue(seq_handle, queue_id); snd_seq_close(seq_handle); exit(0); } int check_range (int val, int min, int max, char * msg) { if ((val < min) | (val > max)) { fprintf(stderr, "Invalid %s, range is %d to %d\n", msg, min, max); return 1; } return 0; } int parse_options (int argc, char * argv []) { int c; long x; char * sep; int option_index = 0; struct option long_options[] = { {"channel", 1, 0, 'c'}, {"strong", 1, 0, 'g'}, {"help", 0, 0, 'h'}, {"master", 0, 0, 'm'}, {"masterclock", 0, 0, 'M'}, {"output", 1, 0, 'o'}, {"program", 1, 0, 'p'}, {"quiet", 0, 0, 'q'}, {"resolution", 1, 0, 'r'}, {"signature", 1, 0, 's'}, {"slave", 0, 0, 'S'}, {"tempo", 1, 0, 't'}, {"velocity", 1, 0, 'v'}, {"weak", 1, 0, 'w'}, {0, 0, 0, 0} }; for (;;) { c = getopt_long ( argc, argv, "c:g:hmMo:p:qr:s:St:v:w:", long_options, &option_index ); if (c == -1) break; switch (c) { case 'c': channel = atoi(optarg); if (check_range(channel, 0, 15, "channel")) return 1; break; case 'g': strong = atoi(optarg); if (check_range(strong, 0, 127, "strong note")) return 1; break; case 'm': master = TRUE; break; case 'M': master = TRUE; notes = FALSE; break; case 'o': port_address = optarg; break; case 'p': program = atoi(optarg); if (check_range(program, 0, 127, "program")) return 1; break; case 'q': verbose = FALSE; break; case 'r': resolution = atoi(optarg); if (check_range(resolution, 48, 480, "resolution")) return 1; break; case 's': x = strtol(optarg, &sep, 10); if ((x < 1) | (x > 32) | (*sep != ':')) { show_error("invalid time signature"); return 1; } num_parts = x; x = strtol(++sep, NULL, 10); if ((x < 1) | (x > 32)) { show_error("invalid time signature"); return 1; } part_fig = x; break; case 'S': slave = TRUE; break; case 't': bpm = atoi(optarg); if (check_range(bpm, 16, 240, "tempo")) return 1; break; case 'v': velocity = atoi(optarg); if (check_range(velocity, 0, 127, "velocity")) return 1; break; case 'w': weak = atoi(optarg); if (check_range(weak, 0, 127, "weak note")) return 1; break; case 0: case 'h': default: return 1; } } return 0; } /** * The main routine. */ int main (int argc, char * argv []) { int npfd, j; struct pollfd * pfd; if (verbose) { show_msg("ametro: MIDI metronome using ALSA sequencer"); } if (parse_options(argc, argv) != 0) { usage(); return EXIT_FAILURE; } if (port_address == NULL) { port_address = getenv("ALSA_OUTPUT_PORTS"); /* * Try the old name for the environment variable. */ if (port_address == NULL) port_address = getenv("ALSA_OUT_PORT"); if (port_address == NULL) { show_error ( "No client/port specified. Use --output or set" "environment value ALSA_OUTPUT_PORTS" ); usage(); return EXIT_FAILURE; } } /* * These next three lines prevent us from leaving the terminal in a bad * state if we ctrl-c out or exit(). The bail_out() function is the * callback when we quit; call it to clean up the terminal. */ atexit(bail_out); signal(SIGINT, sigterm_exit); signal(SIGTERM, sigterm_exit); set_raw_mode(TRUE); open_sequencer(); create_queue(); subscribe(); npfd = snd_seq_poll_descriptors_count(seq_handle, POLLIN); pfd = (struct pollfd *) alloca(npfd * sizeof(struct pollfd)); snd_seq_poll_descriptors(seq_handle, pfd, npfd, POLLIN); set_tempo(bpm); set_program(); if (slave == FALSE) { start_queue(); pattern(); } for (;;) { int ch = quick_read(); int rc; if (ch > 0) { if (handle_char(ch, 0) == 1) break; } rc = poll(pfd, npfd, 500) > 0; if (rc > 0) { for (j = 0; j < npfd; ++j) { if (pfd[j].revents > 0) midi_action(); } } else { if (rc == 0) show_error("poll timeout"); else show_error_string("poll failed", rc); } } } /* * vim: sw=4 ts=4 wm=4 et */ ================================================ FILE: contrib/code/function_calls_gnu.c ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file function_calls_gnu.c * * This module provides a function for instrumenting the entry into a * function as supported by the GNU C/C++ compiler. * * \library seq66 application * \author Chris Ahlstrom * \date 2022-01-21 * \updates 2022-01-22 * \license GNU GPLv2 or above * * Requires the use of gcc's -finstrument-functions option. * * https://gcc.gnu.org/onlinedocs/gcc-4.5.1/gcc/ * Code-Gen-Options.html#index-finstrument_002dfunctions-2114 * * https://balau82.wordpress.com/2010/10/06/ * trace-and-profile-function-calls-with-gcc/ * * To use this function, run "./bootstrap --calls", which builds in the * instructmentation and also build for release, so that shared libraries are * used, which allows the dladdr(2) function to work. * * Unfortunately, the resulting output covers many shared libraries outside * the application, and, for Seq66, a few minutes of running result in a trace * file about 0.5 Gb in size. Oh well, it was worth a try, and maybe we can * make it more pin-pointed at a later date. */ #include "function_calls_gnu.h" #if defined SEQ66_PLATFORM_GNU #define _GNU_SOURCE #include /* C::dladdr(3) */ #include #include void __cyg_profile_func_enter (void *, void *) __attribute__((no_instrument_function)); static FILE * s_fp_trace = NULL; void __attribute__ ((constructor)) trace_begin (void) { #if defined USE_THE_DESCRIPTOR int TRACE_FD = 3; if (s_fp_trace == NULL) { s_fp_trace = fdopen(TRACE_FD, "w"); if (s_fp_trace == NULL) abort(); setbuf(s_fp_trace, NULL); } #else if (s_fp_trace != NULL) { s_fp_trace = fopen("trace.out", "w"); if (s_fp_trace != NULL) printf("[trace] Function log file 'trace.out' opened.\n"); } #endif } void __attribute__ ((destructor)) trace_end (void) { if (s_fp_trace != NULL) { fclose(s_fp_trace); printf("[trace] Function log file 'trace.out' closed.\n"); } } /* * fprintf(fp_trace, "e %p %p %lu\n", func, caller, time(NULL) ); */ /** * Instrumentation "callback". */ void __attribute__((no_instrument_function)) __cyg_profile_func_enter (void * func, void * caller) { if (s_fp_trace == NULL) trace_begin(); if (func != NULL && caller != NULL) { Dl_info info; if (dladdr(func, &info)) { fprintf ( s_fp_trace, "[trace] %p [%s] %s\n", func, info.dli_fname ? info.dli_fname : "?", info.dli_sname ? info.dli_sname : "?" ); } } } #endif // defined SEQ66_PLATFORM_GNU /* * function_calls_gnu.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: contrib/code/function_calls_gnu.h ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file function_calls_gnu.c * * This module provides a function for instrumenting the entry into a function * as supported by the GNU C/C++ compiler. * * \library seq66 application * \author Chris Ahlstrom * \date 2022-01-21 * \updates 2022-01-22 * \license GNU GPLv2 or above * * Requires the use of gcc's -finstrument-functions option. */ #include "seq66_features.h" /* feature macros only, for C code */ #if defined SEQ66_PLATFORM_GNU extern void __attribute__((no_instrument_function)) __cyg_profile_func_enter (void *, void *); #endif // defined SEQ66_PLATFORM_GNU /* * function_calls_gnu.h * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: contrib/code/make_ametro ================================================ #!/bin/bash # #****************************************************************************** # make_ametro #------------------------------------------------------------------------------ ## # \file make_ametro # \library Any project # \author Chris Ahlstrom # \date 2022-01-17 # \update 2022-01-18 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This script just makes the "ametro" program for now. The original # instruction in ametro.c were wrong or out-of-date, and led to unresolved # ALSA functions. # #------------------------------------------------------------------------------ DEBUGFLAG="" if [ "$1" == "debug" ] ; then DEBUGFLAG="-g" echo "Building ametro for debugging..." else echo "Building ametro..." fi gcc $DEBUGFLAG -o ametro ametro.c -lasound #****************************************************************************** # make_ametro #------------------------------------------------------------------------------ # vim: ts=3 sw=3 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/code/qchannelpopup.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qchannelpopup.cpp * * This module declares/defines the base class for plastering pattern / * sequence data information in the pattern editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2026-03-12 * \updates 2026-03-12 * \license GNU GPLv2 or above * * */ #include #include #include "play/sequence.hpp" /* seq66::sequence */ #include "util/strfunctions.hpp" /* seq66::string_to_int() */ #include "qchannelpopup.hpp" /* seq66::qchannelpopup class */ #include "qt5_helpers.hpp" /* seq66::qt(), qt_set_icon() */ namespace seq66 { qchannelpopup::qchannelpopup ( sequence & s, QComboBox * chcombo // QWidget * parent ) : m_track (s), m_channel_combo (chcombo), m_edit_channel (s.midi_channel()) { set_midi_channel(m_edit_channel); // , qbase::status::startup); // set_initialized(); } /** * Selects the given MIDI channel parameter in the main sequence object, so * that it will use that channel. If "Free" is selected, all that happens is * that the pattern is set to "no-channel" mode for MIDI output. * * \param ch * The MIDI channel value to set. * * \param user_change * True if the user made this change, and thus has potentially modified * the song. The default is false. * \param qs * Indicates if the changes was made at startup, or by a user-edit. Set * to qbase::status::startup in the former case, and qbase::status::edit * in the latter case. If the user made this change, they've potentially * modified the song. If the bus number has changed, then the MIDI * channel and event menus are repopulated to reflect the new bus. This * parameter is "startup" in the constructor because those items have not * been set up at that time. The default value is "edit". */ void qchannelpopup::set_midi_channel (int ch, bool userchange) // qbase::status qs) { int initialchan { track().seq_midi_channel() }; if (ch != initialchan || ! userchange) { int chindex { ch }; midibyte channel { midibyte(ch) }; if (! is_good_channel(channel)) { chindex = c_midichannel_max; /* "Free" */ channel = null_channel(); } if (track().set_midi_channel(channel, userchange)) { m_edit_channel = channel; if (is_null_channel(channel)) { channel_combo()->setCurrentIndex(chindex); } else { #if 0 // WHY DOES THIS EVEN COMPILE?????? if (user_change) set_track_change(); /* to solve issue #90 */ else #endif channel_combo()->setCurrentIndex(chindex); } emit signal_change_channel(channel, userchange); } } } /** * Note that c_midichannel_max (16) is a legal value. It is remapped in * sequence::set_midi_channel() to null_channel(). */ void qchannelpopup::update_midi_channel (int index) { set_midi_channel(index); } void qchannelpopup::reset_midi_channel () { set_midi_channel(0); } /** * Populates the MIDI Channel combo-box with the number and names of each * channel. This action is needed at startup of the seqedit window, and when * the user changes the active buss for the sequence. * * When the output buss or channel are changed, we get the 16 "channels" from * the new buss's definition, get the corresponding instrument, and load its * name into this midich popup. Then we need to go to the instrument/channel * that has been selected, and repopulate the event menu with that item's * controller values/names. * * \param buss * The new value for the buss from which to get the [user-instrument-N] * settings in the [user-instrument-definitions] section. */ void qchannelpopup::repopulate_midich_combo (int buss) { disconnect ( channel_combo(), SIGNAL(currentIndexChanged(int)), this, SLOT(update_midi_channel(int)) ); channel_combo()->clear(); for (int channel = 0; channel <= c_midichannel_max; ++channel) { char b[4]; /* 2 digits or less */ snprintf(b, sizeof b, "%2d", channel + 1); std::string name { std::string(b) }; std::string s { usr().instrument_name(buss, channel) }; if (! s.empty()) { name += " ["; name += s; name += "]"; } if (channel == c_midichannel_max) /* i.e. 16 */ { QString combo_text("Free"); channel_combo()->insertItem(channel, combo_text); } else { QString combo_text(qt(name)); channel_combo()->insertItem(channel, combo_text); } } int ch = track().midi_channel(); if (is_null_channel(ch)) ch = c_midichannel_max; channel_combo()->setCurrentIndex(ch); connect ( channel_combo(), SIGNAL(currentIndexChanged(int)), this, SLOT(update_midi_channel(int)) ); } } // namespace seq66 /* * qeventpopups.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: contrib/code/qchannelpopup.hpp ================================================ #if ! defined SEQ66_QCHANNELPOPUP_HPP #define SEQ66_QCHANNELPOPUP_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ /** * \file qchannelpopup.hpp * * This module declares/defines the edit frame for sequences. * * \library seq66 application * \author Chris Ahlstrom * \date 2026-03-12 * \updates 2026-03-12 * \license GNU GPLv2 or above * */ #include #include "cfg/settings.hpp" /* seq66::combolist class, helpers */ /* * A few forward declarations. The Qt header files are in the cpp file. */ class QComboBox; /* * Note that the forward references somewhat duplicate those in qseqframe. */ namespace seq66 { class sequence; /** * This frame holds tools for editing an individual MIDI sequence. This * frame is a more advanced version of qseqeditframe (now moved to * contrib/code), which was based on Kepler34's EditFrame class. */ class qchannelpopup : public QObject { // friend class qstriggereditor; Q_OBJECT public: qchannelpopup () = delete; qchannelpopup ( sequence & s, QComboBox * combo // QWidget * parent ); qchannelpopup (const qchannelpopup &) = delete; qchannelpopup & operator = (const qchannelpopup &) = delete; qchannelpopup (qchannelpopup &&) = default; qchannelpopup & operator = (qchannelpopup &&) = default; ~qchannelpopup () = default; void get_position (int & x, int & y); protected: void set_track_change (bool modified = true); void set_external_frame_title (bool modified = true); sequence & track () { return m_track; } const sequence & track () const { return m_track; } QComboBox * channel_combo () { return m_channel_combo; } const QComboBox * channel_combo () const { return m_channel_combo; } int edit_channel () const { return m_edit_channel; } private: void set_dirty () { track().set_dirty(); } signals: // signals cannot have an access specifier void signal_change_channel (int chan, bool userchange); private slots: void update_midi_channel (int index); void reset_midi_channel (); private: /* slot helper functions */ void repopulate_midich_combo (int buss); private: /* setters and getters */ void set_midi_channel ( int midichannel, bool userchange = false // qbase::status qs = qbase::status::edit ); private: sequence & m_track; /** * Needed for Qt. */ // ? Ui::qchannelpopup * ui; QComboBox * m_channel_combo { nullptr }; /** * Indicates what MIDI channel the data window is currently editing. */ int m_edit_channel { null_channel() }; }; // class qchannelpopup } // namespace seq66 #endif // SEQ66_QCHANNELPOPUP_HPP /* * qchannelpopup.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: contrib/code/readbinaryplist.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file readbinaryplist.c * * PortMidi os-dependent code for Mac OSX. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2018-05-13 * \updates 2018-05-13 * \license GNU GPLv2 or above * * readbinaryplist.c -- Roger B. Dannenberg, Jun 2008 * * Based on ReadBinaryPList.m by Jens Ayton, 2007 * * Used only for the Mac OSX implementation. * * Note that this code is intended to read preference files and has an upper * bound on file size (currently 100MB) and assumes in some places that 32 * bit offsets are sufficient. * * Here are his comments: * * Reader for binary property list files (version 00). * * This has been found to work on all 566 binary plists in my * ~/Library/Preferences/ and /Library/Preferences/ directories. This * probably does not provide full test coverage. It has also been found to * provide different data to Apple's implementation when presented with a * key-value archive. This is because Apple's implementation produces * undocumented CFKeyArchiverUID objects. My implementation produces * dictionaries instead, matching the in-file representation used in XML and * OpenStep plists. See extract_uid(). * * Full disclosure: in implementing this software, I read one comment and one * struct defintion in CFLite, Apple's implementation, which is under the * APSL license. I also deduced the information about CFKeyArchiverUID from * that code. However, none of the implementation was copied. * * Copyright (C) 2007 Jens Ayton * * 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. * * A note about memory management: * * Strings and possibly other values are unique and because the values * associated with IDs are cached, you end up with a directed graph rather * than a tree. It is tricky to free the data because if you do a simple * depth-first search to free nodes, you will free nodes twice. I decided to * allocate memory from blocks of 1024 bytes and keep the blocks in a list * associated with but private to this module. So the user should access this * module by calling: bplist_read_file() or bplist_read_user_pref() or * bplist_read_system_pref() which returns a value. When you are done with * the value, call bplist_free_data(). This will of course free the * value_ptr returned by bplist_read_*(). * * To deal with memory exhaustion (what happens when malloc returns NULL?), * use setjmp/longjmp -- a single setjmp protects the whole parser, and * allocate uses longjmp to abort. After abort, memory is freed and NULL is * returned to caller. There is not much here in the way of error reporting. * * Memory is obtained by calling allocate which either returns the memory * requested or calls longjmp, so callers don't have to check. */ #include #include #include #include #include #include #include "readbinaryplist.h" #include #define NO 0 #define YES 1 #define BOOL int #define MAXPATHLEN 256 /** * There are 2 levels of error logging/printing: BPLIST_LOG and * BPLIST_LOG_VERBOSE. Either or both can be set to non-zero to turn on. If * BPLIST_LOG_VERBOSE is true, then BPLIST_LOG is also true. * * In the code, logging is done by calling either bplist_log() or * bplist_log_verbose(), which take parameters like printf but might be a * no-op. */ /* #define BPLIST_LOG_VERBOSE 1 */ #if BPLIST_LOG_VERBOSE #ifndef BPLIST_LOG #define BPLIST_LOG 1 #endif #endif #if BPLIST_LOG #define bplist_log printf #else #define bplist_log(...) #endif #if BPLIST_LOG_VERBOSE #define bplist_log_verbose bplist_log #else #define bplist_log_verbose(...) #endif /********* MEMORY MANAGEMENT ********/ #define BLOCK_SIZE 1024 /* * Memory is aligned to multiples of this; assume malloc automatically aligns * to this number and assume this number is > sizeof(void *) */ #define ALIGNMENT 8 static void * block_list = NULL; static char * free_ptr = NULL; static char * end_ptr = NULL; static jmp_buf abort_parsing; static void * allocate(size_t size) { void * result; if (free_ptr + size > end_ptr) { size_t how_much = BLOCK_SIZE; if (size > BLOCK_SIZE - ALIGNMENT) /* align everything to 8 bytes */ { how_much = size + ALIGNMENT; } result = malloc(how_much); if (result == NULL) { longjmp(abort_parsing, 1); /* serious problem */ } *((void **)result) = block_list; block_list = result; free_ptr = ((char *) result) + ALIGNMENT; end_ptr = ((char *) result) + how_much; } result = free_ptr; /* enough rooom at free_ptr */ free_ptr += size; return result; } void bplist_free_data () { while (block_list) { void * next = *(void **) block_list; free(block_list); block_list = next; } free_ptr = NULL; end_ptr = NULL; } /* * Layout of trailer -- last 32 bytes in plist data. */ uint8_t unused[6]; uint8_t offset_int_size; uint8_t object_ref_size; uint64_t object_count; uint64_t top_level_object; uint64_t offset_table_offset; enum { kHEADER_SIZE = 8, kTRAILER_SIZE = 32, /* sizeof(bplist_trailer_node) */ kMINIMUM_SANE_SIZE = kHEADER_SIZE + kTRAILER_SIZE }; static const char kHEADER_BYTES[kHEADER_SIZE] = "bplist00"; /** * Map from UID key to previously parsed value. */ typedef struct cache_struct { uint64_t key; value_ptr value; struct cache_struct * next; } cache_node, * cache_ptr; typedef struct bplist_info { uint64_t object_count; const uint8_t *data_bytes; uint64_t length; uint64_t offset_table_offset; uint8_t offset_int_size; uint8_t object_ref_size; cache_ptr cache; } bplist_info_node, * bplist_info_ptr; /* * Static functions. */ static value_ptr bplist_read_pldata (pldata_ptr data); static value_ptr bplist_read_pref (char * filename, OSType folder_type); static uint64_t read_sized_int ( bplist_info_ptr bplist, uint64_t offset, uint8_t size ); static uint64_t read_offset(bplist_info_ptr bplist, uint64_t index); static BOOL read_self_sized_int ( bplist_info_ptr bplist, uint64_t offset, uint64_t * outValue, size_t * outSize ); static value_ptr extract_object (bplist_info_ptr bplist, uint64_t objectRef); static value_ptr extract_simple (bplist_info_ptr bplist, uint64_t offset); static value_ptr extract_int (bplist_info_ptr bplist, uint64_t offset); static value_ptr extract_real (bplist_info_ptr bplist, uint64_t offset); static value_ptr extract_date (bplist_info_ptr bplist, uint64_t offset); static value_ptr extract_data (bplist_info_ptr bplist, uint64_t offset); static value_ptr extract_ascii_string (bplist_info_ptr bplist, uint64_t offset); static value_ptr extract_unicode_string (bplist_info_ptr bplist, uint64_t offset); static value_ptr extract_uid (bplist_info_ptr bplist, uint64_t offset); static value_ptr extract_array (bplist_info_ptr bplist, uint64_t offset); static value_ptr extract_dictionary (bplist_info_ptr bplist, uint64_t offset); value_ptr value_create () { value_ptr value = (value_ptr) allocate(sizeof(value_node)); return value; } void value_set_integer (value_ptr v, int64_t i) { v->tag = kTAG_INT; v->integer = i; } void value_set_real (value_ptr v, double d) { v->tag = kTAG_REAL; v->real = d; } /* * d is seconds since 1 January 2001 */ void value_set_date (value_ptr v, double d) { v->tag = kTAG_DATE; v->real = d; } void value_set_ascii_string (value_ptr v, const uint8_t * s, size_t len) { v->tag = kTAG_ASCIISTRING; v->string = (char *) allocate(len + 1); memcpy(v->string, s, len); v->string[len] = 0; } void value_set_unicode_string (value_ptr v, const uint8_t * s, size_t len) { v->tag = kTAG_UNICODESTRING; v->string = (char *) allocate(len + 1); memcpy(v->string, s, len); v->string[len] = 0; } void value_set_uid (value_ptr v, uint64_t uid) { v->tag = kTAG_UID; v->uinteger = uid; } /** * v->data points to a pldata that points to the actual bytes * the bytes are copied, so caller must free byte source (*data) */ void value_set_data (value_ptr v, const uint8_t * data, size_t len) { v->tag = kTAG_DATA; pldata_ptr pldata = (pldata_ptr) allocate(sizeof(pldata_node)); pldata->data = (uint8_t *) allocate(len); memcpy(pldata->data, data, len); pldata->len = len; v->data = pldata; printf("value at %p gets data at %p\n", v, pldata); } /** * The caller releases ownership of array to value_ptr v. */ void value_set_array(value_ptr v, value_ptr * array, size_t length) { array_ptr a = (array_ptr) allocate(sizeof(array_node)); a->array = array; a->length = length; v->tag = kTAG_ARRAY; v->array = a; } /** * The caller releases ownership of dict to value_ptr v. */ void value_set_dict (value_ptr v, dict_ptr dict) { v->tag = kTAG_DICTIONARY; v->dict = dict; } /** * Look up an objectref in the cache, a ref->value_ptr mapping. */ value_ptr cache_lookup (cache_ptr cache, uint64_t ref) { while (cache) { if (cache->key == ref) return cache->value; cache = cache->next; } return NULL; } /** * Insert an objectref and value in the cache. */ void cache_insert (cache_ptr * cache, uint64_t ref, value_ptr value) { cache_ptr c = (cache_ptr) allocate(sizeof(cache_node)); c->key = ref; c->value = value; c->next = *cache; *cache = c; } /** * Insert an objectref and value in a dictionary. */ void dict_insert (dict_ptr * dict, value_ptr key, value_ptr value) { dict_ptr d = (dict_ptr) allocate(sizeof(dict_node)); d->key = key; d->value = value; d->next = *dict; *dict = d; } BOOL is_binary_plist (pldata_ptr data) { if (data->len < kMINIMUM_SANE_SIZE) return NO; return memcmp(data->data, kHEADER_BYTES, kHEADER_SIZE) == 0; } value_ptr bplist_read_file (char * filename) { struct stat stbuf; pldata_node pldata; FILE * file; size_t n; value_ptr value; int rslt = stat(filename, &stbuf); if (rslt) { #if BPLIST_LOG perror("in stat"); #endif bplist_log("Could not stat %s, error %d\n", filename, rslt); return NULL; } /* * If file is >100MB, assume it is not a preferences file and give up */ if (stbuf.st_size > 100000000) { bplist_log("Large file %s encountered (%llu bytes) -- not read\n", filename, stbuf.st_size); return NULL; } pldata.len = (size_t) stbuf.st_size; /* * \note * This is supposed to be malloc, not allocate. It is separate from * the graph structure, large, and easy to free right after parsing. */ pldata.data = (uint8_t *) malloc(pldata.len); if (!pldata.data) { bplist_log("Could not allocate %lu bytes for %s\n", (unsigned long) pldata.len, filename); return NULL; } file = fopen(filename, "rb"); if (! file) { bplist_log("Could not open %s\n", filename); return NULL; } n = fread(pldata.data, 1, pldata.len, file); if (n != pldata.len) { bplist_log("Error reading from %s\n", filename); fclose(file); /* added as per cppcheck */ return NULL; } value = bplist_read_pldata(&pldata); free(pldata.data); /* * We probably need to fclose() the file pointer here, as suggested * by cppcheck. */ return value; } value_ptr bplist_read_pref (char * filename, OSType folder_type) { FSRef prefdir; char cstr[MAXPATHLEN]; OSErr err = FSFindFolder(kOnAppropriateDisk, folder_type, FALSE, &prefdir); if (err) { bplist_log("Error finding preferences folder: %d\n", err); return NULL; } err = FSRefMakePath(&prefdir, (UInt8 *) cstr, (UInt32)(MAXPATHLEN - 1)); if (err) { bplist_log("Error making path name for preferences folder: %d\n", err); return NULL; } strlcat(cstr, "/", MAXPATHLEN); strlcat(cstr, filename, MAXPATHLEN); return bplist_read_file(cstr); } value_ptr bplist_read_system_pref (char * filename) { return bplist_read_pref(filename, kSystemPreferencesFolderType); } value_ptr bplist_read_user_pref (char * filename) { return bplist_read_pref(filename, kPreferencesFolderType); } /** * Data is stored with high-order bytes first. * Read from plist data in a machine-independent fashion. */ uint64_t convert_uint64 (uint8_t * ptr) { uint64_t rslt = 0; int i; for (i = 0; i < sizeof(uint64_t); i++) // shift in bytes, high-order first { rslt <<= 8; rslt += ptr[i]; } return rslt; } value_ptr bplist_read_pldata (pldata_ptr data) { value_ptr result = NULL; bplist_info_node bplist; uint8_t * ptr; uint64_t top_level_object; int i; if (data == NULL) return NULL; if (! is_binary_plist(data)) { bplist_log("Bad binary plist: too short or invalid header.\n"); return NULL; } ptr = (uint8_t *)(data->data + data->len - kTRAILER_SIZE); // read trailer bplist.offset_int_size = ptr[6]; bplist.object_ref_size = ptr[7]; bplist.object_count = convert_uint64(ptr + 8); top_level_object = convert_uint64(ptr + 16); bplist.offset_table_offset = convert_uint64(ptr + 24); /* * Basic sanity checks */ if ( bplist.offset_int_size < 1 || bplist.offset_int_size > 8 || bplist.object_ref_size < 1 || bplist.object_ref_size > 8 || bplist.offset_table_offset < kHEADER_SIZE ) { bplist_log("Bad binary plist: trailer declared insane.\n"); return NULL; } /* * Ensure offset table is inside file. */ uint64_t offsetTableSize = bplist.offset_int_size * bplist.object_count; if (offsetTableSize + bplist.offset_table_offset + kTRAILER_SIZE > data->len) { bplist_log("Bad binary plist: offset table overlaps end of container.\n"); return NULL; } bplist.data_bytes = data->data; bplist.length = data->len; bplist.cache = NULL; /* dictionary is empty */ bplist_log_verbose ( "Got a sane bplist with %llu items, offset_int_size: %u, " "object_ref_size: %u\n", bplist.object_count, bplist.offset_int_size, bplist.object_ref_size ); /* * At this point, we are ready to do some parsing which allocates memory * for the result data structure. If memory allocation (using allocate * fails, a longjmp will return to here and we simply give up */ i = setjmp(abort_parsing); if (i == 0) { result = extract_object(&bplist, top_level_object); } else { bplist_log("allocate() failed to allocate memory. Giving up.\n"); result = NULL; } if (! result) { bplist_free_data(); } return result; } static value_ptr extract_object (bplist_info_ptr bplist, uint64_t objectRef) { uint64_t offset; value_ptr result = NULL; uint8_t objectTag; if (objectRef >= bplist->object_count) { bplist_log("Bad binary plist: object index is out of range.\n"); return NULL; } /* * Use cached object if it exists. */ result = cache_lookup(bplist->cache, objectRef); if (result != NULL) return result; offset = read_offset(bplist, objectRef); // else, find object in file if (offset > bplist->length) // Out-of-range offset { bplist_log("Bad binary plist: object outside container.\n"); return NULL; } objectTag = *(bplist->data_bytes + offset); switch (objectTag & 0xF0) { case kTAG_SIMPLE: result = extract_simple(bplist, offset); break; case kTAG_INT: result = extract_int(bplist, offset); break; case kTAG_REAL: result = extract_real(bplist, offset); break; case kTAG_DATE: result = extract_date(bplist, offset); break; case kTAG_DATA: result = extract_data(bplist, offset); break; case kTAG_ASCIISTRING: result = extract_ascii_string(bplist, offset); break; case kTAG_UNICODESTRING: result = extract_unicode_string(bplist, offset); break; case kTAG_UID: result = extract_uid(bplist, offset); break; case kTAG_ARRAY: result = extract_array(bplist, offset); break; case kTAG_DICTIONARY: result = extract_dictionary(bplist, offset); break; default: // Unknown tag. bplist_log ( "Bad binary plist: unknown tag 0x%X.\n", (objectTag & 0x0F) >> 4 ); result = NULL; } /* * Cache and return result. */ if (result != NULL) cache_insert(&bplist->cache, objectRef, result); return result; } static uint64_t read_sized_int (bplist_info_ptr bplist, uint64_t offset, uint8_t size) { assert ( bplist->data_bytes != NULL && size >= 1 && size <= 8 && offset + size <= bplist->length ); uint64_t result = 0; const uint8_t * byte = bplist->data_bytes + offset; do /* note that ints seem to be high-order first */ { result = (result << 8) | *byte++; } while (--size); return result; } static uint64_t read_offset (bplist_info_ptr bplist, uint64_t index) { assert(index < bplist->object_count); return read_sized_int ( bplist, bplist->offset_table_offset + bplist->offset_int_size * index, bplist->offset_int_size ); } static BOOL read_self_sized_int ( bplist_info_ptr bplist, uint64_t offset, uint64_t * outValue, size_t * outSize ) { uint32_t size; int64_t value; assert(bplist->data_bytes != NULL && offset < bplist->length); size = 1 << (bplist->data_bytes[offset] & 0x0F); if (size > 8) { /* * Maximum allowable size in this implementation is 1<<3 = 8 bytes. * This also happens to be the biggest we can handle. */ return NO; } if (offset + 1 + size > bplist->length) { return NO; /* Out of range */ } value = read_sized_int(bplist, offset + 1, size); if (outValue != NULL) *outValue = value; if (outSize != NULL) *outSize = size + 1; /* +1 for tag byte */ return YES; } static value_ptr extract_simple (bplist_info_ptr bplist, uint64_t offset) { assert(bplist->data_bytes != NULL && offset < bplist->length); value_ptr value = value_create(); switch (bplist->data_bytes[offset]) { case kVALUE_NULL: value->tag = kVALUE_NULL; return value; case kVALUE_TRUE: value->tag = kVALUE_TRUE; return value; case kVALUE_FALSE: value->tag = kVALUE_FALSE; return value; } // Note: kVALUE_FILLER is treated as invalid, because it, er, is. bplist_log("Bad binary plist: invalid atom.\n"); free(value); return NULL; } static value_ptr extract_int (bplist_info_ptr bplist, uint64_t offset) { value_ptr value = value_create(); value->tag = kTAG_INT; if (! read_self_sized_int(bplist, offset, &value->uinteger, NULL)) { bplist_log("Bad binary plist: invalid integer object.\n"); } /* * \note * Originally, I sign-extended here. This was the wrong thing; it * turns out that negative ints are always stored as 64-bit, and * smaller ints are unsigned. */ return value; } static value_ptr extract_real (bplist_info_ptr bplist, uint64_t offset) { value_ptr value = value_create(); uint32_t size; assert(bplist->data_bytes != NULL && offset < bplist->length); size = 1 << (bplist->data_bytes[offset] & 0x0F); // FIXME: what to do if faced with other sizes for float/double? assert(sizeof(float) == sizeof(uint32_t) && sizeof(double) == sizeof(uint64_t)); if (offset + 1 + size > bplist->length) { bplist_log("Bad binary plist: %s object overlaps end of container.\n", "floating-point number"); free(value); return NULL; } if (size == sizeof(float)) { // cast is ok because we know size is 4 bytes uint32_t i = (uint32_t) read_sized_int(bplist, offset + 1, size); value_set_real(value, *(float *)&i); /* handles byte swapping */ return value; } else if (size == sizeof(double)) { uint64_t i = read_sized_int(bplist, offset + 1, size); value_set_real(value, *(double *)&i); /* handles byte swapping */ return value; } else { // Can't handle floats of other sizes. bplist_log("Bad binary plist: can't handle %u-byte float.\n", size); free(value); return NULL; } } /** * Data has size code like int and real, but only 3 (meaning 8 bytes) is * valid. */ static value_ptr extract_date (bplist_info_ptr bplist, uint64_t offset) { value_ptr value; assert(bplist->data_bytes != NULL && offset < bplist->length); if (bplist->data_bytes[offset] != kVALUE_FULLDATETAG) { bplist_log("Bad binary plist: invalid size for date object.\n"); return NULL; } if (offset + 1 + sizeof(double) > bplist->length) { bplist_log("Bad binary plist: %s object overlaps end of container.\n", "date"); return NULL; } // FIXME: what to do if faced with other sizes for double? assert(sizeof(double) == sizeof(uint64_t)); uint64_t date = read_sized_int(bplist, offset + 1, sizeof(double)); value = value_create(); /* handles byte swapping */ value_set_date(value, *(double *)&date); return value; } uint64_t bplist_get_a_size (bplist_info_ptr bplist, uint64_t * offset_ptr, char *msg) { uint64_t size = bplist->data_bytes[*offset_ptr] & 0x0F; (*offset_ptr)++; if (size == 0x0F) { /* * 0x0F means separate int size follows. * Smaller values are used for short data. */ size_t extra; /* length of the data size we are about to read */ if ((bplist->data_bytes[*offset_ptr] & 0xF0) != kTAG_INT) { // Bad data, mistagged size int bplist_log("Bad binary plist: %s object size is not tagged as int.\n", msg); return UINT64_MAX; // error } // read integer data as size, extra tells how many bytes to skip if (!read_self_sized_int(bplist, *offset_ptr, &size, &extra)) { bplist_log("Bad binary plist: invalid %s object size tag.\n", "data"); return UINT64_MAX; // error } (*offset_ptr) += extra; } if (*offset_ptr + size > bplist->length) { bplist_log("Bad binary plist: %s object overlaps end of container.\n", "data"); return UINT64_MAX; // error } return size; } static value_ptr extract_data (bplist_info_ptr bplist, uint64_t offset) { uint64_t size; value_ptr value; assert(bplist->data_bytes != NULL && offset < bplist->length); if ((size = bplist_get_a_size(bplist, &offset, "data")) == UINT64_MAX) return NULL; value = value_create(); // cast is ok because we only allow files up to 100MB: value_set_data(value, bplist->data_bytes + (size_t) offset, (size_t) size); return value; } static value_ptr extract_ascii_string (bplist_info_ptr bplist, uint64_t offset) { uint64_t size; value_ptr value; // return value assert(bplist->data_bytes != NULL && offset < bplist->length); if ( (size = bplist_get_a_size(bplist, &offset, "ascii string")) == UINT64_MAX ) { return NULL; } value = value_create(); // cast is ok because we only allow 100MB files value_set_ascii_string ( value, bplist->data_bytes + (size_t) offset, (size_t) size ); return value; } static value_ptr extract_unicode_string (bplist_info_ptr bplist, uint64_t offset) { uint64_t size; value_ptr value; assert(bplist->data_bytes != NULL && offset < bplist->length); if ((size = bplist_get_a_size(bplist, &offset, "unicode string")) == UINT64_MAX) { return NULL; } value = value_create(); // cast is ok because we only allow 100MB files value_set_unicode_string(value, bplist->data_bytes + (size_t) offset, (size_t) size); return value; } /** * UIDs are used by Cocoa's key-value coder. When writing other plist * formats, they are expanded to dictionaries of the form * CF$UIDvalue, so we do the same * here on reading. This results in plists identical to what running plutil * -convert xml1 gives us. However, this is not the same result as * [Core]Foundation's plist parser, which extracts them as un- introspectable * CF objects. In fact, it even seems to convert the CF$UID dictionaries from * XML plists on the fly. */ static value_ptr extract_uid (bplist_info_ptr bplist, uint64_t offset) { value_ptr value; uint64_t uid; if (!read_self_sized_int(bplist, offset, &uid, NULL)) { bplist_log("Bad binary plist: invalid UID object.\n"); return NULL; } /* * assert(NO); // original code suggests using a string for a key but our * dictionaries all use big ints for keys, so I don't know what to do here * * In practice, I believe this code is never executed by PortMidi. I * changed it to do something and not raise compiler warnings, but not * sure what the code should do. */ value = value_create(); value_set_uid(value, uid); // return [NSDictionary dictionaryWithObject: // [NSNumber numberWithUnsignedLongLong:value] // forKey:"CF$UID"]; return value; } static value_ptr extract_array (bplist_info_ptr bplist, uint64_t offset) { uint64_t i, count; uint64_t size; uint64_t elementID; value_ptr element = NULL; value_ptr * array = NULL; value_ptr value = NULL; BOOL ok = YES; assert(bplist->data_bytes != NULL && offset < bplist->length); if ((count = bplist_get_a_size(bplist, &offset, "array")) == UINT64_MAX) return NULL; if (count > UINT64_MAX / bplist->object_ref_size - offset) { // Offset overflow. bplist_log("Bad binary plist: %s object overlaps end of container.\n", "array"); return NULL; } size = bplist->object_ref_size * count; if (size + offset > bplist->length) { bplist_log("Bad binary plist: %s object overlaps end of container.\n", "array"); return NULL; } // got count, the number of array elements value = value_create(); assert(value); if (count == 0) { // count must be size_t or smaller because max file size is 100MB value_set_array(value, array, (size_t) count); return value; } array = allocate(sizeof(value_ptr) * (size_t) count); for (i = 0; i != count; ++i) { bplist_log_verbose("[%u]\n", i); elementID = read_sized_int(bplist, offset + i * bplist->object_ref_size, bplist->object_ref_size); element = extract_object(bplist, elementID); if (element != NULL) { array[i] = element; } else { ok = NO; break; } } if (ok) // count is smaller than size_t max because of 100MB file limit { value_set_array(value, array, (size_t) count); } return value; } static value_ptr extract_dictionary (bplist_info_ptr bplist, uint64_t offset) { uint64_t i, count; uint64_t size; uint64_t elementID; value_ptr value = NULL; dict_ptr dict = NULL; BOOL ok = YES; assert(bplist->data_bytes != NULL && offset < bplist->length); if ((count = bplist_get_a_size(bplist, &offset, "array")) == UINT64_MAX) return NULL; if (count > UINT64_MAX / (bplist->object_ref_size * 2) - offset) { // Offset overflow. bplist_log("Bad binary plist: %s object overlaps end of container.\n", "dictionary"); return NULL; } size = bplist->object_ref_size * count * 2; if (size + offset > bplist->length) { bplist_log("Bad binary plist: %s object overlaps end of container.\n", "dictionary"); return NULL; } value = value_create(); if (count == 0) { value_set_dict(value, NULL); return value; } for (i = 0; i != count; ++i) { value_ptr key; value_ptr val; elementID = read_sized_int ( bplist, offset + i * bplist->object_ref_size, bplist->object_ref_size ); key = extract_object(bplist, elementID); if (key != NULL) { bplist_log_verbose("key: %p\n", key); } else { ok = NO; break; } elementID = read_sized_int ( bplist, offset + (i + count) * bplist->object_ref_size, bplist->object_ref_size ); val = extract_object(bplist, elementID); if (val != NULL) { dict_insert(&dict, key, val); } else { ok = NO; break; } } if (ok) { value_set_dict(value, dict); } return value; } /* * Functions for accessing values. */ char * value_get_asciistring (value_ptr v) { if (v->tag != kTAG_ASCIISTRING) return NULL; return v->string; } value_ptr value_dict_lookup_using_string (value_ptr v, char * key) { dict_ptr dict; if (v->tag != kTAG_DICTIONARY) return NULL; /* not a dictionary */ dict = v->dict; while (dict) /* search for key */ { if (dict->key && dict->key->tag == kTAG_ASCIISTRING && strcmp(key, dict->key->string) == 0) // found it { return dict->value; } dict = dict->next; } return NULL; /* not found */ } value_ptr value_dict_lookup_using_path (value_ptr v, char * path) { char key[MAX_KEY_SIZE]; while (*path) /* more to the path */ { int i = 0; while (i < MAX_KEY_SIZE - 1) { key[i] = *path++; if (key[i] == '/') /* end of entry in path */ { key[i + 1] = 0; break; } if (!key[i]) { path--; /* back up to end of string char */ break; /* will cause outer loop to exit */ } i++; } if (!v || v->tag != kTAG_DICTIONARY) return NULL; /* now, look up the key to get next value */ v = value_dict_lookup_using_string(v, key); if (v == NULL) return NULL; } return v; } /* * Functions for debugging. */ void plist_print (value_ptr v) { size_t i; int comma_needed; dict_ptr dict; if (!v) { printf("NULL"); return; } switch (v->tag & 0xF0) { case kTAG_SIMPLE: switch (v->tag) { case kVALUE_NULL: printf("NULL@%p", v); break; case kVALUE_FALSE: printf("FALSE@%p", v); break; case kVALUE_TRUE: printf("TRUE@%p", v); break; default: printf("UNKNOWN tag=%x@%p", v->tag, v); break; } break; case kTAG_INT: printf("%lld@%p", v->integer, v); break; case kTAG_REAL: printf("%g@%p", v->real, v); break; case kTAG_DATE: printf("date:%g@%p", v->real, v); break; case kTAG_DATA: printf("data@%p->%p:[%p:", v, v->data, v->data->data); for (i = 0; i < v->data->len; i++) { printf(" %2x", v->data->data[i]); } printf("]"); break; case kTAG_ASCIISTRING: printf("%p:\"%s\"@%p", v->string, v->string, v); break; case kTAG_UNICODESTRING: printf("unicode:%p:\"%s\"@%p", v->string, v->string, v); break; case kTAG_UID: printf("UID:%llu@%p", v->uinteger, v); break; case kTAG_ARRAY: comma_needed = FALSE; printf("%p->%p:[%p:", v, v->array, v->array->array); for (i = 0; i < v->array->length; i++) { if (comma_needed) printf(", "); plist_print(v->array->array[i]); comma_needed = TRUE; } printf("]"); break; case kTAG_DICTIONARY: comma_needed = FALSE; printf("%p:[", v); dict = v->dict; while (dict) { if (comma_needed) printf(", "); printf("%p:", dict); plist_print(dict->key); printf("->"); plist_print(dict->value); comma_needed = TRUE; dict = dict->next; } printf("]"); break; default: printf("UNKNOWN tag=%x", v->tag); break; } } /* * readbinaryplist.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: contrib/code/readbinaryplist.h ================================================ #ifndef SEQ66_READBINARYPLIST_H #define SEQ66_READBINARYPLIST_H /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file readbinaryplist.h * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2018-05-13 * \updates 2018-05-13 * \license GNU GPLv2 or above * * Header to read preference files. Not really needed, as we have our own * style. TBD. * * Roger B. Dannenberg, Jun 2008 */ #include /* for uint8_t ... */ #include "pminternal.h" /* PmDeviceID typedef */ #if defined __cplusplus extern "C" { #endif #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif #define MAX_KEY_SIZE 256 enum { // Object tags (high nybble) kTAG_SIMPLE = 0x00, // Null, true, false, filler, or invalid kTAG_INT = 0x10, kTAG_REAL = 0x20, kTAG_DATE = 0x30, kTAG_DATA = 0x40, kTAG_ASCIISTRING = 0x50, kTAG_UNICODESTRING = 0x60, kTAG_UID = 0x80, kTAG_ARRAY = 0xA0, kTAG_DICTIONARY = 0xD0, // "simple" object values kVALUE_NULL = 0x00, kVALUE_FALSE = 0x08, kVALUE_TRUE = 0x09, kVALUE_FILLER = 0x0F, kVALUE_FULLDATETAG = 0x33 // Dates are tagged with a whole byte. }; typedef struct pldata_struct { uint8_t * data; size_t len; } pldata_node, * pldata_ptr; typedef struct array_struct { struct value_struct ** array; uint64_t length; } array_node, * array_ptr; /* * A dict_node is a list of pairs. */ typedef struct dict_struct { struct value_struct * key; struct value_struct * value; struct dict_struct * next; } dict_node, * dict_ptr; /** * A value_node is a value with a tag telling the type. */ typedef struct value_struct { int tag; union { int64_t integer; uint64_t uinteger; double real; char * string; /* whoops */ pldata_ptr data; array_ptr array; struct dict_struct * dict; }; } value_node, * value_ptr; value_ptr bplist_read_file (char * filename); value_ptr bplist_read_user_pref (char * filename); value_ptr bplist_read_system_pref (char * filename); void bplist_free_data (); char * value_get_asciistring (value_ptr v); value_ptr value_dict_lookup_using_string (value_ptr v, char * key); value_ptr value_dict_lookup_using_path (value_ptr v, char * path); void plist_print (value_ptr v); #if defined __cplusplus } // extern "C" #endif #endif // SEQ66_READBINARYPLIST_H /* * readbinaryplist.h * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: contrib/code/ring_buffer.hpp ================================================ #include #include #include template class ring_buffer { public: using value_type = TYPE; using reference = TYPE & ; using const_reference = const TYPE &; using size_type = std::size_t; using container = std::vector; private: container m_array; size_type m_head; size_type m_tail; size_type m_contents_size; const size_type m_array_size; template struct ring_iterator; using iterator = ring_iterator; using const_iterator = ring_iterator; public: ring_buffer (size_type sz = 8) : m_array (sz <= 1 ? 8 : sz), m_array_size (sz <= 1 ? 8 : sz), m_head (1), m_tail (0), m_contents_size (0) { // No code needed } ring_buffer (std::initializer_list lst) : m_array (lst), m_array_size (lst.size()), m_head (0), m_tail (lst.size() - 1), m_contents_size (lst.size()) { // No code, no check on size } reference operator [] (size_type index) { // const ring_buffer& constMe = *this; return const_cast(operator [](index)); } const_reference operator [] (size_type index) const { index += m_head; index %= m_array_size; return m_array[index]; } reference at (size_type index) { if (index < m_contents_size) return operator [](index); throw std::out_of_range("index too large"); } const_reference at (size_type index) const { if (index < m_contents_size) return operator[](index); throw std::out_of_range("index too large"); } iterator begin (); const_iterator begin () const; iterator end (); const_iterator end () const; // iterator cbegin () const; TODO const_iterator cbegin () const; iterator rbegin (); const_iterator rbegin () const; // iterator cend () const; TODO const_iterator cend () const; iterator rend (); const_iterator rend () const; reference front () { return m_array[m_head]; } const_reference front () const { return m_array[m_head]; } reference back () { return m_array[m_tail]; } const_reference back () const { return m_array[m_tail]; } void clear () { m_head = 1; m_tail = m_contents_size = 0; } void push_back (const value_type & item); void pop_front (); size_type size () const { return m_contents_size; } size_type capacity () const { return m_array_size; } bool empty () const { return m_contents_size == 0; } bool full () const { return m_contents_size == m_array_size; } size_type max_size () const { return size_type(-1) / sizeof(value_type); } private: /* * Start of nested class ring_iterator */ template class ring_iterator { friend class ring_buffer; private: using iterator_category = std::random_access_iterator_tag; using difference_type = long long; using reference = typename std::conditional_t < isconst, TYPE const &, TYPE & >; using pointer = typename std::conditional_t < isconst, TYPE const *, TYPE * >; using vec_pointer = typename std::conditional_t < isconst, std::vector const *, std::vector * >; private: vec_pointer buffer_ptr; size_type offset; size_type index; bool reverse; bool comparable (const ring_iterator & other) { return (reverse == other.reverse); } public: ring_iterator () : buffer_ptr (nullptr), offset (0), index (0), reverse (false) { // No code } ring_iterator (const ring_buffer::ring_iterator & i) : buffer_ptr (i.buffer_ptr), offset (i.offset), index (i.index), reverse (i.reverse) { // No code } reference operator * () { if (reverse) return (*buffer_ptr)[ (buffer_ptr->size() + offset - index) % (buffer_ptr->size())]; return (*buffer_ptr)[(offset+index)%(buffer_ptr->size())]; } reference operator [] (size_type index) { ring_iterator iter = *this; iter.index += index; return *iter; } pointer operator->() { return &(operator *()); } ring_iterator & operator ++ () { ++index; return *this; } ring_iterator operator ++ (int) { ring_iterator iter = *this; ++index; return iter; } ring_iterator & operator -- () { --index; return *this; } ring_iterator operator -- (int) { ring_iterator iter = *this; --index; return iter; } friend ring_iterator operator + (ring_iterator lhs, int rhs) { lhs.index += rhs; return lhs; } friend ring_iterator operator + (int lhs, ring_iterator rhs) { rhs.index += lhs; return rhs; } ring_iterator & operator += (int n) { index += n; return *this; } friend ring_iterator operator - (ring_iterator lhs, int rhs) { lhs.index -= rhs; return lhs; } friend difference_type operator - ( const ring_iterator & lhs, const ring_iterator & rhs ) { lhs.index -= rhs; return lhs.index - rhs.index; } ring_iterator & operator -= (int n) { index -= n; return *this; } bool operator == (const ring_iterator & other) { if (comparable(other)) return (index + offset == other.index + other.offset); return false; } bool operator != (const ring_iterator & other) { if (comparable(other)) return ! this->operator ==(other); return true; } bool operator < (const ring_iterator & other) { if (comparable(other)) return (index + offset < other.index + other.offset); return false; } bool operator <= (const ring_iterator & other) { if (comparable(other)) return (index + offset <= other.index + other.offset); return false; } bool operator > (const ring_iterator & other) { if (comparable(other)) return !this->operator<=(other); return false; } bool operator >= (const ring_iterator & other) { if (comparable(other)) return !this->operator<(other); return false; } }; // nested class ring_iterator }; // class ring_buffer /* * Inline functions. */ template void ring_buffer::push_back (const value_type & item) { /* * Increment tail */ ++m_tail; ++m_contents_size; if (m_tail == m_array_size) m_tail = 0; if (m_contents_size > m_array_size) { /* * Increment head */ if (m_contents_size == 0) return; ++m_head; --m_contents_size; if (m_head == m_array_size) m_head = 0; } m_array[m_tail] = item; } template void ring_buffer::pop_front () { /* * Increment head */ if (m_contents_size == 0) return; ++m_head; --m_contents_size; if (m_head == m_array_size) m_head = 0; } template typename ring_buffer::iterator ring_buffer::begin() { iterator iter; iter.buffer_ptr = &m_array; iter.offset = m_head; iter.index = 0; iter.reverse = false; return iter; } template typename ring_buffer::const_iterator ring_buffer::begin() const { const_iterator iter; iter.buffer_ptr = &m_array; iter.offset = m_head; iter.index = 0; iter.reverse = false; return iter; } template typename ring_buffer::const_iterator ring_buffer::cbegin() const { const_iterator iter; iter.buffer_ptr = &m_array; iter.offset = m_head; iter.index = 0; iter.reverse = false; return iter; } template typename ring_buffer::iterator ring_buffer::rbegin() { iterator iter; iter.buffer_ptr = &m_array; iter.offset = m_tail; iter.index = 0; iter.reverse = true; return iter; } template typename ring_buffer::const_iterator ring_buffer::rbegin() const { const_iterator iter; iter.buffer_ptr = &m_array; iter.offset = m_tail; iter.index = 0; iter.reverse = true; return iter; } template typename ring_buffer::iterator ring_buffer::end() { iterator iter; iter.buffer_ptr = &m_array; iter.offset = m_head; iter.index = m_contents_size; iter.reverse = false; return iter; } template typename ring_buffer::const_iterator ring_buffer::end() const { const_iterator iter; iter.buffer_ptr = &m_array; iter.offset = m_head; iter.index = m_contents_size; iter.reverse = false; return iter; } template typename ring_buffer::const_iterator ring_buffer::cend() const { const_iterator iter; iter.buffer_ptr = &m_array; iter.offset = m_head; iter.index = m_contents_size; iter.reverse = false; return iter; } template typename ring_buffer::iterator ring_buffer::rend() { iterator iter; iter.buffer_ptr = &m_array; iter.offset = m_tail; iter.index = m_contents_size; iter.reverse = true; return iter; } template typename ring_buffer::const_iterator ring_buffer::rend() const { const_iterator iter; iter.buffer_ptr = &m_array; iter.offset = m_tail; iter.index = m_contents_size; iter.reverse = true; return iter; } ================================================ FILE: contrib/code/test/filename_split.cpp ================================================ #if defined SEQ66_PLATFORM_DEBUG #include "util/filefunctions.hpp" static const char * fmt = "%28s: %28s; %28s; %28s\n"; static void split_test (const std::string & s, bool useext) { std::string path, filebase, filebare, extension; std::string t = "'" + s + "'"; bool haspath = useext ? seq66::filename_split_ext(s, path, filebare, extension) : seq66::filename_split(s, path, filebase) ; if (! haspath) path = ""; else path = "'" + path + "'"; if (filebase.empty()) filebase = ""; else filebase = "'" + filebase + "'"; if (filebare.empty()) filebare = ""; else filebare = "'" + filebare + "'"; if (extension.empty()) extension = ""; else extension = "'" + extension + "'"; if (useext) { printf ( fmt, t.c_str(), path.c_str(), filebare.c_str(), extension.c_str() ); } else { printf ( fmt, t.c_str(), path.c_str(), filebase.c_str(), extension.c_str() ); } } static void filename_split_tests () { static std::vector s_tests = { "", "aptitude", "aptitude.", "aptitude.exe", ".", ".filename", ".filename.", ".filename.extra", "relative/path/file", "relative/path/file.", "relative/path/file.extra", "relative/path/file/", "/absolute/path/file", "/absolute/path/file.", "/absolute/path/file.extra", "/absolute/path/file/", ".config/path/file", ".config/path/file.", ".config/path/file.extra", ".config/path/file/", }; printf(fmt, "Full Path", "Path", "Base name", "Extension"); for (const auto & s : s_tests) split_test(s, false); /* no use of extension */ printf("\n"); printf(fmt, "Full Path", "Path", "Bare name", "Extension"); for (const auto & s : s_tests) split_test(s, true); /* split with extension */ } #endif // defined SEQ66_PLATFORM_DEBUG ================================================ FILE: contrib/code/ttymidi.c ================================================ /* This file is part of ttymidi. ttymidi 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 3 of the License, or (at your option) any later version. ttymidi 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 ttymidi. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mod-semaphore.h" // MOD specific #define MAX_DEV_STR_LEN 32 #define MAX_MSG_SIZE 1024 /* import ioctl definition here, as we can't include both "sys/ioctl.h" and "asm/termios.h" */ extern int ioctl (int __fd, unsigned long int __request, ...) __THROW; /* --------------------------------------------------------------------- */ // Globals volatile bool run; int serial; /* --------------------------------------------------------------------- */ // Program options static struct argp_option options[] = { {"serialdevice" , 's', "DEV" , 0, "Serial device to use. Default = /dev/ttyUSB0", 0 }, {"baudrate" , 'b', "BAUD", 0, "Serial port baud rate. Default = 31250", 0 }, #ifdef DEBUG {"verbose" , 'v', 0 , 0, "For debugging: Produce verbose output", 0 }, {"printonly" , 'p', 0 , 0, "Super debugging: Print values read from serial -- and do nothing else", 0 }, #endif {"name" , 'n', "NAME", 0, "Name of the JACK client. Default = ttymidi", 0 }, { 0 } }; typedef struct _arguments { #ifdef DEBUG int verbose; int printonly; #endif char serialdevice[MAX_DEV_STR_LEN]; int baudrate; char name[MAX_DEV_STR_LEN]; } arguments_t; typedef struct _jackdata { jack_client_t* client; jack_port_t* port_in; jack_port_t* port_out; jack_ringbuffer_t* ringbuffer_in; jack_ringbuffer_t* ringbuffer_out; jack_nframes_t bufsize_compensation; sem_t sem; bool internal; volatile jack_nframes_t last_frame_time; } jackdata_t; void exit_cli(int sig) { run = false; printf("\rttymidi closing down ... "); // unused return; (void)sig; } static error_t parse_opt (int key, char *arg, struct argp_state *state) { /* Get the input argument from argp_parse, which we know is a pointer to our arguments structure. */ arguments_t *arguments = state->input; int baud_temp; switch (key) { #ifdef DEBUG case 'p': arguments->printonly = 1; break; case 'v': arguments->verbose = 1; break; #endif case 's': if (arg == NULL) break; strncpy(arguments->serialdevice, arg, MAX_DEV_STR_LEN-1); break; case 'n': if (arg == NULL) break; strncpy(arguments->name, arg, MAX_DEV_STR_LEN-1); break; case 'b': if (arg == NULL) break; errno = 0; baud_temp = strtol(arg, NULL, 0); if (errno == EINVAL || errno == ERANGE) { printf("Baud rate %s is invalid.\n",arg); exit(1); } arguments->baudrate = baud_temp; break; case ARGP_KEY_ARG: case ARGP_KEY_END: break; default: return ARGP_ERR_UNKNOWN; } return 0; } void arg_set_defaults(arguments_t *arguments) { #ifdef DEBUG arguments->verbose = 0; arguments->printonly = 0; #endif arguments->baudrate = 31250; strncpy(arguments->serialdevice, "/dev/ttyUSB0", MAX_DEV_STR_LEN); strncpy(arguments->name, "ttymidi", MAX_DEV_STR_LEN); } const char *argp_program_version = "ttymidi 1.0.0"; const char *argp_program_bug_address = "falktx@moddevices.com"; static char doc[] = "ttymidi - Connect serial port devices to JACK MIDI programs!"; static struct argp argp = { options, parse_opt, NULL, doc, NULL, NULL, NULL }; static arguments_t arguments; /* --------------------------------------------------------------------- */ // The following read/write wrappers handle the case of interruption by system signals static inline uint8_t read_retry_or_error(int fd, void* dst) { int error; do { error = read(fd, dst, 1); } while (error == -1 && errno == EINTR); return error; } static inline uint8_t write_retry_or_success(int fd, const void* src, uint8_t size) { int error; do { error = write(fd, src, size); } while (error == -1 && errno == EINTR); // in case of error, return full size so outer loop can stop return error >= 0 ? (uint8_t)error : size; } /* --------------------------------------------------------------------- */ // JACK stuff const uint8_t ringbuffer_msg_size = 3 + sizeof(uint8_t) + sizeof(jack_nframes_t); /** * How this works: * * 1. Get the cycle_start frame-number, fc. * 2. bufsize_compensation, b = jack_get_buffer_size() / 10.0 + 0.5) * 3. Read the ringbuffer data into bufc in the old rtmidi way. New * wrinkle format: [data, data_size, frame]. The frame is set this way: * a. Clock messages. The frame = jack_frame_time(). * b. Other MIDI. Same. * 4. We have the frame, f, from the data, and the frame-count, F. * f += F - b. * 5. If last_buf_frame > f, f = last_buf_frame else last_buf_frame = f. * 6. If f >= fc offset = f - fc else 0. If offset > F, offset = F-1 * */ static int process_client(jack_nframes_t frames, void* ptr) { if (! run) return 0; jackdata_t * jackdata = (jackdata_t*) ptr; const jack_nframes_t cycle_start = jack_last_frame_time(jackdata->client); void * portbuf_in = jack_port_get_buffer(jackdata->port_in, frames); void * portbuf_out = jack_port_get_buffer(jackdata->port_out, frames); jack_midi_clear_buffer(portbuf_in); // MIDI from serial to JACK char bufc[ringbuffer_msg_size]; jack_midi_data_t bufj[3]; uint8_t bsize; jack_nframes_t buf_frame, offset, last_buf_frame = 0; while ( jack_ringbuffer_read(jackdata->ringbuffer_in, bufc, ringbuffer_msg_size) == ringbuffer_msg_size ) { // Format: [data, data_size, frame] memcpy(&bsize, bufc+3, sizeof(uint8_t)); memcpy(&buf_frame, bufc+3+sizeof(uint8_t), sizeof(jack_nframes_t)); uint8_t i = 0; for (; ibufsize_compensation; if (last_buf_frame > buf_frame) buf_frame = last_buf_frame; else last_buf_frame = buf_frame; if (buf_frame >= cycle_start) { offset = buf_frame - cycle_start; if (offset >= frames) offset = frames - 1; } else offset = 0; // fixup NoteOn with velocity 0 if ((bufj[0] & 0xF0) == 0x90 && bufj[2] == 0x00) { bufj[0] = 0x80 + (bufj[0] & 0x0F); bufj[2] = 0x40; } jack_midi_event_write(portbuf_in, offset, bufj, bsize); } // MIDI from JACK to serial const uint32_t event_count = jack_midi_get_event_count(portbuf_out); if (event_count > 0) { bool needs_post = false; jack_midi_event_t event; for (uint32_t i=0; i 3) continue; // set first byte as size bufc[0] = event.size; // copy the rest uint8_t j=0; for (; jringbuffer_out, bufc, 4); buf_frame = cycle_start + event.time; jack_ringbuffer_write(jackdata->ringbuffer_out, (char*)&buf_frame, sizeof(jack_nframes_t)); needs_post = true; } if (needs_post) { // Tell MIDI-out thread we have data jackdata->last_frame_time = cycle_start; sem_post(&jackdata->sem); } } return 0; } bool open_client(jackdata_t* jackdata, jack_client_t* client) { jack_port_t *port_in, *port_out; jack_ringbuffer_t *ringbuffer_in, *ringbuffer_out; bzero(jackdata, sizeof(*jackdata)); if (client == NULL) { client = jack_client_open(arguments.name, JackNoStartServer|JackUseExactName, NULL); if (client == NULL) { fprintf(stderr, "Error opening JACK client.\n"); return false; } } else { jackdata->internal = true; } if ((port_in = jack_port_register(client, "MIDI_in", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput|JackPortIsPhysical|JackPortIsTerminal, 0x0)) == NULL) { fprintf(stderr, "Error creating input port.\n"); } if ((port_out = jack_port_register(client, "MIDI_out", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput|JackPortIsPhysical|JackPortIsTerminal, 0x0)) == NULL) { fprintf(stderr, "Error creating output port.\n"); } if ((ringbuffer_in = jack_ringbuffer_create(MAX_MSG_SIZE*2-1)) == NULL) { fprintf(stderr, "Error creating JACK input ringbuffer.\n"); } if ((ringbuffer_out = jack_ringbuffer_create(MAX_MSG_SIZE*2-1)) == NULL) { fprintf(stderr, "Error creating JACK output ringbuffer.\n"); } if (port_in == NULL || port_out == NULL || ringbuffer_in == NULL || ringbuffer_out == NULL) { jack_client_close(client); return false; } jackdata->client = client; jackdata->port_in = port_in; jackdata->port_out = port_out; jackdata->ringbuffer_in = ringbuffer_in; jackdata->ringbuffer_out = ringbuffer_out; jackdata->bufsize_compensation = (int)((double)jack_get_buffer_size(jackdata->client) / 10.0 + 0.5); jack_set_process_callback(client, process_client, jackdata); if (jack_activate(client) != 0) { fprintf(stderr, "Error activating JACK client.\n"); jack_client_close(client); return false; } sem_init(&jackdata->sem, 0, 0); jack_ringbuffer_mlock(ringbuffer_in); jack_ringbuffer_mlock(ringbuffer_out); if (jack_port_by_name(client, "mod-host:midi_in") != NULL) { char ourportname[255]; sprintf(ourportname, "%s:MIDI_in", jack_get_client_name(client)); jack_connect(client, ourportname, "mod-host:midi_in"); } return true; } void close_client(jackdata_t* jackdata) { jack_deactivate(jackdata->client); jack_port_unregister(jackdata->client, jackdata->port_in); jack_port_unregister(jackdata->client, jackdata->port_out); jack_ringbuffer_free(jackdata->ringbuffer_in); jack_ringbuffer_free(jackdata->ringbuffer_out); if (! jackdata->internal) jack_client_close(jackdata->client); sem_destroy(&jackdata->sem); bzero(jackdata, sizeof(*jackdata)); } /* --------------------------------------------------------------------- */ // MIDI stuff void* write_midi_from_jack(void* ptr) { jackdata_t* jackdata = (jackdata_t*) ptr; char bufc[4]; jack_nframes_t buf_frame, buf_diff, cycle_start; uint8_t size, written; const jack_nframes_t sample_rate = jack_get_sample_rate(jackdata->client); /* used for select sleep * (compared to usleep which sleeps at *least* x us, select sleeps at *most*) */ fd_set fd; FD_ZERO(&fd); struct timeval tv = { 0, 0 }; while (run) { if (sem_timedwait_secs(&jackdata->sem, 1) != 0) continue; if (! run) break; cycle_start = jackdata->last_frame_time; buf_diff = 0; while (jack_ringbuffer_read(jackdata->ringbuffer_out, bufc, 4) == 4) { if (jack_ringbuffer_read(jackdata->ringbuffer_out, (char*)&buf_frame, sizeof(jack_nframes_t)) != sizeof(jack_nframes_t)) continue; if (buf_frame > cycle_start) { buf_diff = buf_frame - cycle_start - buf_diff; tv.tv_usec = (buf_diff * 1000000) / sample_rate; if (tv.tv_usec > 60 && tv.tv_usec < 10000 /* 10 ms */) { // assume write takes 50 us tv.tv_usec -= 50; select(0, &fd, NULL, NULL, &tv); } } else { buf_diff = 0; } size = (uint8_t)bufc[0]; written = 0; do { written += write_retry_or_success(serial, bufc+1+written, size-written); } while (written < size); } } return NULL; } void * read_midi_from_serial_port(void* ptr) { jack_midi_data_t buffer[ringbuffer_msg_size]; jackdata_t * jackdata = (jackdata_t*) ptr; #ifdef DEBUG /* * Super-debug mode: * * Print to screen whatever comes through the serial port. Send * nothing into the MIDI-event queue. */ if (arguments.printonly) { while (run) { if (read(serial, buffer, 1) == 1) { printf("%02x\t", buffer[0] & 0xFF); fflush(stdout); } } } else #endif { int error; ssize_t read_cnt; uint8_t data_bytes_cnt = 0; uint8_t last_status_byte = 0; bool has_status_byte; rerun: while (run) { // Clean the buffer memset(buffer, 0, ringbuffer_msg_size); // Read a byte and go ahead iff it is a valid status byte. read_cnt = read(serial, buffer, 1); if (read_cnt != 1) { // Nothing to read. Try again in the next loop. continue; } #ifdef DEBUG if (arguments.verbose) { printf("%02x\t", buffer[0] & 0xFF); fflush(stdout); } #endif // Ignore active-sensing if (buffer[0] == 0xFE) { continue; } // Check if the first bit is set... has_status_byte = (buffer[0] & 0x80) == 0x80; if (has_status_byte || last_status_byte != 0) { // ...then is a MIDI message. No SysEx data. if (!has_status_byte) { buffer[1] = buffer[0]; buffer[0] = last_status_byte; read_cnt = 2; } if (buffer[0] < 0xF0) { // Channel Voice or Mode Message ahead last_status_byte = buffer[0]; // Program Change and Channel Pressure only have 1 data byte following! switch(buffer[0] & 0xF0) { case 0xC0: // Program Change case 0xD0: // Channel Pressure data_bytes_cnt = 1; break; default: data_bytes_cnt = 2; break; } } else { // Compare https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message switch(buffer[0]) { case 0xF0: // System exclusive begin // Unknown data byte count. Note that Real-Time messages // may be interleaved with a System Exclusive! // Every SysEx byte until 0xF7 should start with a 0-bit, so skipping is safe. last_status_byte = 0; continue; case 0xF7: // System exclusive end last_status_byte = 0; continue; case 0xF2: // Song Position Pointer data_bytes_cnt = 2; last_status_byte = 0; break; case 0xF1: // MIDI Time Code Quarter Frame case 0xF3: // Song Select data_bytes_cnt = 1; last_status_byte = 0; break; case 0xF8: // Clock case 0xFA: // Start case 0xFB: // Continue case 0xFC: // Stop // NOTE: we do not reset `last_status_byte` here data_bytes_cnt = 0; break; default: // Others, like Tune Request and Reserved data_bytes_cnt = 0; last_status_byte = 0; break; } } while (read_cnt < data_bytes_cnt+1) { error = read_retry_or_error(serial, buffer+read_cnt); if (error == 0) { continue; } if (error < 0) { #ifdef DEBUG if (arguments.verbose) { printf("error %i while reading serial: %s\n", error, strerror(error)); fflush(stdout); } #endif goto rerun; break; } // Ignore or handle some stuff in the middle of voice messages switch (buffer[read_cnt]) { // Ignore active-sensing case 0xFE: continue; // Handle clock messages, see below for the same code case 0xF8: case 0xFA: case 0xFB: case 0xFC: { // remaining bytes buffer[1] = buffer[2] = 0; // size buffer[3] = 1; // timestamp const jack_nframes_t frames = jack_frame_time(jackdata->client); memcpy(buffer+4, &frames, sizeof(jack_nframes_t)); // done jack_ringbuffer_write(jackdata->ringbuffer_in, (const char *) buffer, ringbuffer_msg_size); } continue; } read_cnt += error; } // Whole payload in the buffer, ready to forward #ifdef DEBUG if (arguments.verbose) { for (uint8_t i=1U; iclient); memcpy(buffer+4, &frames, sizeof(jack_nframes_t)); // Sanity check if ((buffer[0] & 0x80) && !(buffer[1] & 0x80) && !(buffer[2] & 0x80)) { jack_ringbuffer_write(jackdata->ringbuffer_in, (const char *) buffer, ringbuffer_msg_size); } else { // Bad bytes. Discard the event. #ifdef DEBUG if (arguments.verbose) { printf("Sanity check failed, bad bytes: %02x\t%02x\t%02x\n", buffer[0], buffer[1], buffer[2]); fflush(stdout); } #endif } } else { // Unexpected data byte. Discard it. #ifdef DEBUG if (arguments.verbose) { printf("Status byte check failed, first bad byte: %02x\n", buffer[0]); fflush(stdout); } #endif } } } return NULL; } /* --------------------------------------------------------------------- */ // Main program static struct termios2 oldtio, newtio; static jackdata_t jackdata; static pthread_t midi_out_thread; static bool _ttymidi_init(bool exit_on_failure, jack_client_t* client) { /* * Open JACK stuff */ if (! open_client(&jackdata, client)) { if (exit_on_failure) { fprintf(stderr, "Error creating jack client.\n"); exit(-1); } return false; } /* * Open modem device for reading and not as controlling tty because we don't * want to get killed if linenoise sends CTRL-C. */ serial = open(arguments.serialdevice, O_RDWR | O_NOCTTY); if (serial < 0) { if (exit_on_failure) { perror(arguments.serialdevice); exit(-1); } return false; } /* save current serial port settings */ ioctl(serial, TCGETS2, &oldtio); /* clear struct for new port settings */ bzero(&newtio, sizeof(newtio)); /* * CRTSCTS : output hardware flow control (only used if the cable has * all necessary lines. See sect. 7 of Serial-HOWTO) * CS8 : 8n1 (8bit, no parity, 1 stopbit) * CLOCAL : local connection, no modem contol * CREAD : enable receiving characters */ newtio.c_cflag = BOTHER | CS8 | CLOCAL | CREAD; // CRTSCTS removed /* * IGNPAR : ignore bytes with parity errors * ICRNL : map CR to NL (otherwise a CR input on the other computer will not terminate input) * otherwise make device raw (no other input processing) */ newtio.c_iflag = IGNPAR; /* Raw output */ newtio.c_oflag = 0; /* * ICANON : enable canonical input * disable all echo functionality, and don't send signals to calling program */ newtio.c_lflag = 0; // non-canonical /* Speed */ newtio.c_ispeed = arguments.baudrate; newtio.c_ospeed = arguments.baudrate; /* * set up: we'll be reading 4 bytes at a time. */ newtio.c_cc[VTIME] = 0; /* inter-character timer unused */ newtio.c_cc[VMIN] = 1; /* blocking read until n character arrives */ /* * now activate the settings for the port */ ioctl(serial, TCSETS2, &newtio); // Linux-specific: enable low latency mode (FTDI "nagling off") struct serial_struct ser_info; bzero(&ser_info, sizeof(ser_info)); ioctl(serial, TIOCGSERIAL, &ser_info); ser_info.flags |= ASYNC_LOW_LATENCY; ioctl(serial, TIOCSSERIAL, &ser_info); #ifdef DEBUG if (arguments.printonly) { printf("Super debug mode: Only printing the signal to screen. Nothing else.\n"); } #endif /* * read commands */ run = true; /* Give high priority to our threads */ pthread_attr_t attributes; pthread_attr_init(&attributes); pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_JOINABLE); pthread_attr_setinheritsched(&attributes, PTHREAD_EXPLICIT_SCHED); pthread_attr_setscope(&attributes, (client != NULL) ? PTHREAD_SCOPE_PROCESS : PTHREAD_SCOPE_SYSTEM); pthread_attr_setschedpolicy(&attributes, SCHED_FIFO); struct sched_param rt_param; memset(&rt_param, 0, sizeof(rt_param)); rt_param.sched_priority = 80; pthread_attr_setschedparam(&attributes, &rt_param); /* Starting thread that is writing jack port data */ pthread_create(&midi_out_thread, &attributes, write_midi_from_jack, (void*) &jackdata); /* And also thread for polling serial data. As serial is currently read in blocking mode, by this we can enable ctrl+c quiting and avoid zombie alsa ports when killing app with ctrl+z */ pthread_t midi_in_thread; pthread_create(&midi_in_thread, NULL, read_midi_from_serial_port, (void*) &jackdata); pthread_attr_destroy(&attributes); return true; } void _ttymidi_finish(void) { close_client(&jackdata); pthread_join(midi_out_thread, NULL); /* restore the old port settings */ ioctl(serial, TCSETS2, &oldtio); printf("\ndone!\n"); } int main(int argc, char** argv) { arg_set_defaults(&arguments); argp_parse(&argp, argc, argv, 0, 0, &arguments); if (! _ttymidi_init(true, NULL)) return 1; signal(SIGINT, exit_cli); signal(SIGTERM, exit_cli); while (run) usleep(100000); // 100 ms _ttymidi_finish(); } __attribute__ ((visibility("default"))) int jack_initialize(jack_client_t* client, const char* load_init); int jack_initialize(jack_client_t* client, const char* load_init) { arg_set_defaults(&arguments); #ifdef DEBUG // Enable logs for debug build arguments.verbose = 1; #endif if (load_init != NULL && load_init[0] != '\0') { strncpy(arguments.serialdevice, load_init, MAX_DEV_STR_LEN-1); } else { const char* serialdevice_env = getenv("MOD_MIDI_SERIAL_PORT"); if (serialdevice_env != NULL && serialdevice_env[0] != '\0') strncpy(arguments.serialdevice, serialdevice_env, MAX_DEV_STR_LEN-1); } if (! _ttymidi_init(false, client)) return 1; return 0; } __attribute__ ((visibility("default"))) void jack_finish(void); void jack_finish(void) { run = false; _ttymidi_finish(); } ================================================ FILE: contrib/config.rpath ================================================ #! /bin/sh # Output a system dependent set of variables, describing how to set the # run time search path of shared libraries in an executable. # # Copyright 1996-2010 Free Software Foundation, Inc. # Taken from GNU libtool, 2001 # Originally by Gordon Matzigkeit , 1996 # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # # The first argument passed to this file is the canonical host specification, # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # The environment variables CC, GCC, LDFLAGS, LD, with_gnu_ld # should be set by the caller. # # The set of defined variables is at the end of this script. # Known limitations: # - On IRIX 6.5 with CC="cc", the run time search patch must not be longer # than 256 bytes, otherwise the compiler driver will dump core. The only # known workaround is to choose shorter directory names for the build # directory and/or the installation directory. # All known linkers require a `.a' archive for static linking (except MSVC, # which needs '.lib'). libext=a shrext=.so host="$1" host_cpu=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` host_vendor=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` host_os=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` # Code taken from libtool.m4's _LT_CC_BASENAME. for cc_temp in $CC""; do case $cc_temp in compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; \-*) ;; *) break;; esac done cc_basename=`echo "$cc_temp" | sed -e 's%^.*/%%'` # Code taken from libtool.m4's _LT_COMPILER_PIC. wl= if test "$GCC" = yes; then wl='-Wl,' else case "$host_os" in aix*) wl='-Wl,' ;; darwin*) case $cc_basename in xlc*) wl='-Wl,' ;; esac ;; mingw* | cygwin* | pw32* | os2* | cegcc*) ;; hpux9* | hpux10* | hpux11*) wl='-Wl,' ;; irix5* | irix6* | nonstopux*) wl='-Wl,' ;; newsos6) ;; linux* | k*bsd*-gnu) case $cc_basename in ecc*) wl='-Wl,' ;; icc* | ifort*) wl='-Wl,' ;; lf95*) wl='-Wl,' ;; pgcc | pgf77 | pgf90) wl='-Wl,' ;; ccc*) wl='-Wl,' ;; como) wl='-lopt=' ;; *) case `$CC -V 2>&1 | sed 5q` in *Sun\ C*) wl='-Wl,' ;; esac ;; esac ;; osf3* | osf4* | osf5*) wl='-Wl,' ;; rdos*) ;; solaris*) wl='-Wl,' ;; sunos4*) wl='-Qoption ld ' ;; sysv4 | sysv4.2uw2* | sysv4.3*) wl='-Wl,' ;; sysv4*MP*) ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) wl='-Wl,' ;; unicos*) wl='-Wl,' ;; uts4*) ;; esac fi # Code taken from libtool.m4's _LT_LINKER_SHLIBS. hardcode_libdir_flag_spec= hardcode_libdir_separator= hardcode_direct=no hardcode_minus_L=no case "$host_os" in cygwin* | mingw* | pw32* | cegcc*) # FIXME: the MSVC++ port hasn't been tested in a loooong time # When not using gcc, we currently assume that we are using # Microsoft Visual C++. if test "$GCC" != yes; then with_gnu_ld=no fi ;; interix*) # we just hope/assume this is gcc and not c89 (= MSVC++) with_gnu_ld=yes ;; openbsd*) with_gnu_ld=no ;; esac ld_shlibs=yes if test "$with_gnu_ld" = yes; then # Set some defaults for GNU ld with shared library support. These # are reset later if shared libraries are not supported. Putting them # here allows them to be overridden if necessary. # Unlike libtool, we use -rpath here, not --rpath, since the documented # option of GNU ld is called -rpath, not --rpath. hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' case "$host_os" in aix[3-9]*) # On AIX/PPC, the GNU linker is very broken if test "$host_cpu" != ia64; then ld_shlibs=no fi ;; amigaos*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes # Samuel A. Falvo II reports # that the semantics of dynamic libraries on AmigaOS, at least up # to version 4, is to share data among multiple programs linked # with the same dynamic library. Since this doesn't match the # behavior of shared libraries on other platforms, we cannot use # them. ld_shlibs=no ;; beos*) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; cygwin* | mingw* | pw32* | cegcc*) # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. hardcode_libdir_flag_spec='-L$libdir' if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then : else ld_shlibs=no fi ;; interix[3-9]*) hardcode_direct=no hardcode_libdir_flag_spec='${wl}-rpath,$libdir' ;; gnu* | linux* | k*bsd*-gnu) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; netbsd*) ;; solaris*) if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then ld_shlibs=no elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) case `$LD -v 2>&1` in *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) ld_shlibs=no ;; *) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-rpath,$libdir`' else ld_shlibs=no fi ;; esac ;; sunos4*) hardcode_direct=yes ;; *) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; esac if test "$ld_shlibs" = no; then hardcode_libdir_flag_spec= fi else case "$host_os" in aix3*) # Note: this linker hardcodes the directories in LIBPATH if there # are no directories specified by -L. hardcode_minus_L=yes if test "$GCC" = yes; then # Neither direct hardcoding nor static linking is supported with a # broken collect2. hardcode_direct=unsupported fi ;; aix[4-9]*) if test "$host_cpu" = ia64; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no else aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # need to do runtime linking. case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) for ld_flag in $LDFLAGS; do if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then aix_use_runtimelinking=yes break fi done ;; esac fi hardcode_direct=yes hardcode_libdir_separator=':' if test "$GCC" = yes; then case $host_os in aix4.[012]|aix4.[012].*) collect2name=`${CC} -print-prog-name=collect2` if test -f "$collect2name" && \ strings "$collect2name" | grep resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 hardcode_direct=unsupported hardcode_minus_L=yes hardcode_libdir_flag_spec='-L$libdir' hardcode_libdir_separator= fi ;; esac fi # Begin _LT_AC_SYS_LIBPATH_AIX. echo 'int main () { return 0; }' > conftest.c ${CC} ${LDFLAGS} conftest.c -o conftest aix_libpath=`dump -H conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } }'` if test -z "$aix_libpath"; then aix_libpath=`dump -HX64 conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } }'` fi if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib" fi rm -f conftest.c conftest # End _LT_AC_SYS_LIBPATH_AIX. if test "$aix_use_runtimelinking" = yes; then hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" else if test "$host_cpu" = ia64; then hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib' else hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" fi fi ;; amigaos*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes # see comment about different semantics on the GNU ld section ld_shlibs=no ;; bsdi[45]*) ;; cygwin* | mingw* | pw32* | cegcc*) # When not using gcc, we currently assume that we are using # Microsoft Visual C++. # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. hardcode_libdir_flag_spec=' ' libext=lib ;; darwin* | rhapsody*) hardcode_direct=no if test "$GCC" = yes ; then : else case $cc_basename in xlc*) ;; *) ld_shlibs=no ;; esac fi ;; dgux*) hardcode_libdir_flag_spec='-L$libdir' ;; freebsd1*) ld_shlibs=no ;; freebsd2.2*) hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes ;; freebsd2*) hardcode_direct=yes hardcode_minus_L=yes ;; freebsd* | dragonfly*) hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes ;; hpux9*) hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes ;; hpux10*) if test "$with_gnu_ld" = no; then hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes fi ;; hpux11*) if test "$with_gnu_ld" = no; then hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: case $host_cpu in hppa*64*|ia64*) hardcode_direct=no ;; *) hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes ;; esac fi ;; irix5* | irix6* | nonstopux*) hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; netbsd*) hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes ;; newsos6) hardcode_direct=yes hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; openbsd*) if test -f /usr/libexec/ld.so; then hardcode_direct=yes if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then hardcode_libdir_flag_spec='${wl}-rpath,$libdir' else case "$host_os" in openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*) hardcode_libdir_flag_spec='-R$libdir' ;; *) hardcode_libdir_flag_spec='${wl}-rpath,$libdir' ;; esac fi else ld_shlibs=no fi ;; os2*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; osf3*) hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; osf4* | osf5*) if test "$GCC" = yes; then hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' else # Both cc and cxx compiler support -rpath directly hardcode_libdir_flag_spec='-rpath $libdir' fi hardcode_libdir_separator=: ;; solaris*) hardcode_libdir_flag_spec='-R$libdir' ;; sunos4*) hardcode_libdir_flag_spec='-L$libdir' hardcode_direct=yes hardcode_minus_L=yes ;; sysv4) case $host_vendor in sni) hardcode_direct=yes # is this really true??? ;; siemens) hardcode_direct=no ;; motorola) hardcode_direct=no #Motorola manual says yes, but my tests say they lie ;; esac ;; sysv4.3*) ;; sysv4*MP*) if test -d /usr/nec; then ld_shlibs=yes fi ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) ;; sysv5* | sco3.2v5* | sco5v6*) hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`' hardcode_libdir_separator=':' ;; uts4*) hardcode_libdir_flag_spec='-L$libdir' ;; *) ld_shlibs=no ;; esac fi # Check dynamic linker characteristics # Code taken from libtool.m4's _LT_SYS_DYNAMIC_LINKER. # Unlike libtool.m4, here we don't care about _all_ names of the library, but # only about the one the linker finds when passed -lNAME. This is the last # element of library_names_spec in libtool.m4, or possibly two of them if the # linker has special search rules. library_names_spec= # the last element of library_names_spec in libtool.m4 libname_spec='lib$name' case "$host_os" in aix3*) library_names_spec='$libname.a' ;; aix[4-9]*) library_names_spec='$libname$shrext' ;; amigaos*) library_names_spec='$libname.a' ;; beos*) library_names_spec='$libname$shrext' ;; bsdi[45]*) library_names_spec='$libname$shrext' ;; cygwin* | mingw* | pw32* | cegcc*) shrext=.dll library_names_spec='$libname.dll.a $libname.lib' ;; darwin* | rhapsody*) shrext=.dylib library_names_spec='$libname$shrext' ;; dgux*) library_names_spec='$libname$shrext' ;; freebsd1*) ;; freebsd* | dragonfly*) case "$host_os" in freebsd[123]*) library_names_spec='$libname$shrext$versuffix' ;; *) library_names_spec='$libname$shrext' ;; esac ;; gnu*) library_names_spec='$libname$shrext' ;; hpux9* | hpux10* | hpux11*) case $host_cpu in ia64*) shrext=.so ;; hppa*64*) shrext=.sl ;; *) shrext=.sl ;; esac library_names_spec='$libname$shrext' ;; interix[3-9]*) library_names_spec='$libname$shrext' ;; irix5* | irix6* | nonstopux*) library_names_spec='$libname$shrext' case "$host_os" in irix5* | nonstopux*) libsuff= shlibsuff= ;; *) case $LD in *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= ;; *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 ;; *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 ;; *) libsuff= shlibsuff= ;; esac ;; esac ;; linux*oldld* | linux*aout* | linux*coff*) ;; linux* | k*bsd*-gnu) library_names_spec='$libname$shrext' ;; knetbsd*-gnu) library_names_spec='$libname$shrext' ;; netbsd*) library_names_spec='$libname$shrext' ;; newsos6) library_names_spec='$libname$shrext' ;; nto-qnx*) library_names_spec='$libname$shrext' ;; openbsd*) library_names_spec='$libname$shrext$versuffix' ;; os2*) libname_spec='$name' shrext=.dll library_names_spec='$libname.a' ;; osf3* | osf4* | osf5*) library_names_spec='$libname$shrext' ;; rdos*) ;; solaris*) library_names_spec='$libname$shrext' ;; sunos4*) library_names_spec='$libname$shrext$versuffix' ;; sysv4 | sysv4.3*) library_names_spec='$libname$shrext' ;; sysv4*MP*) library_names_spec='$libname$shrext' ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) library_names_spec='$libname$shrext' ;; uts4*) library_names_spec='$libname$shrext' ;; esac sed_quote_subst='s/\(["`$\\]\)/\\\1/g' escaped_wl=`echo "X$wl" | sed -e 's/^X//' -e "$sed_quote_subst"` shlibext=`echo "$shrext" | sed -e 's,^\.,,'` escaped_libname_spec=`echo "X$libname_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` escaped_library_names_spec=`echo "X$library_names_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` escaped_hardcode_libdir_flag_spec=`echo "X$hardcode_libdir_flag_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` LC_ALL=C sed -e 's/^\([a-zA-Z0-9_]*\)=/acl_cv_\1=/' <" available [automation-control] # "BS" is the backspace (Ctrl-H) character, and indicates that there is no # keystroke for that control (because it's a tri-state control). For a space # character, the name "Space" must be used, so that sscanf() does not get # confused. # # 36 controls so far 0 "'" [0 0 0 0 0 0] [1 0 144 8 1 127] [1 0 144 6 1 127] # BPM Up 1 ";" [0 0 0 0 0 0] [1 0 144 8 1 127] [1 0 144 6 1 127] # BPM Down 2 "]" [0 0 0 0 0 0] [1 0 144 15 1 127] [1 0 144 13 1 127] # Set Up 3 "[" [0 0 0 0 0 0] [1 0 144 15 1 127] [1 0 144 13 1 127] # Set Down 4 "KP_Home" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Replace/Solo 5 "KP_." [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Snapshot 1 6 "o" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Queue 7 "`" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Group Mute 8 "l" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Group Learn 9 "Home" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Playing Set 10 "." [1 0 144 20 1 127] [1 0 144 22 1 127] [1 0 144 18 1 127] # MIDI Pause/Start/Stop 11 "F10" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Song Record 12 "Sh_F1" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Solo 13 "Sh_F2" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # MIDI Thru 14 "PageUp" [0 0 0 0 0 0] [1 0 144 9 1 127] [1 0 144 5 1 127] # BPM Page Up 15 "PageDn" [0 0 0 0 0 0] [1 0 144 9 1 127] [1 0 144 5 1 127] # BPM Page Down 16 "Sh_F3" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Set-by-Number 17 "Sh_F4" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # MIDI Record 18 "Sh_F5" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # MIDI Quantized Record 19 "F12" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Reset Sequence 20 "F11" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # One-shot Queue 21 "F6" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Fast-Forward 22 "F5" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Rewind 23 "F9" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Top 24 "}" [0 0 0 0 0 0] [1 0 144 4 1 127] [1 0 144 0 1 127] # Playlist (value/next/previous) 25 "|" [0 0 0 0 0 0] [1 0 144 3 1 127] [1 0 144 1 1 127] # Song in Playlist (value/next/previous) 26 "Sh_F6" [1 0 144 20 1 127] [1 0 144 22 1 127] [1 0 144 18 1 127] # EXPANSION 0 27 "Space" [1 0 144 20 1 127] [1 0 144 22 1 127] [1 0 144 18 1 127] # Start 28 "Esc" [1 0 144 20 1 127] [1 0 144 22 1 127] [1 0 144 18 1 127] # Stop 29 "{" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Snapshot 2 30 "F8" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Toggle All Mutes 31 "F7" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Song Pointer Position 32 "p" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Keep queue 33 "/" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Slot shift 34 "0xf0" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # EXPANSION 2 35 "0xf1" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # EXPANSION 3 # [extended-keys] # # These cannot apply for MIDI control: # # 20 "F2" "JACK Mode" (Toggle JACK) # 21 "F3" "Menu Mode" # 22 "F4" "JACK Transport" # 19 "F1" "Song/Live" # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: contrib/dd-11/dd-11-notes.log ================================================ The DD-11 first puts out the following Program Changes: Status C0 chan/type 09 d0=0 d1=0 Status C0 chan/type 0A d0=0 d1=0 Status C0 chan/type 0B d0=0 d1=0 Status C0 chan/type 0C d0=5 d1=0 Status C0 chan/type 0D d0=3 d1=0 Status C0 chan/type 0E d0=79 d1=0 Incoming 0 Note 44 vel 85 000000 Note On :- 44 Vel 55 --> 000046 Note Off:- 44 Vel 00 0 Note 56 vel 35 000000 Note On :- 56 Vel 23 --> 000046 Note Off:- 56 Vel 00 0 Note 57 vel 75 000000 Note On :- 57 Vel 4B --> 000046 Note Off:- 57 Vel 00 48 Note 56 vel 1 000048 Note On :- 56 Vel 01 --> 000094 Note Off:- 56 Vel 00 48 Note 57 vel 25 000048 Note On :- 57 Vel 19 --> 000094 Note Off:- 57 Vel 00 96 Note 38 vel 90 000096 Note On :- 38 Vel 5A --> 000142 Note Off:- 38 Vel 00 96 Note 56 vel 35 000096 Note On :- 56 Vel 23 --> 000142 Note Off:- 56 Vel 00 96 Note 57 vel 75 000096 Note On :- 57 Vel 4B --> 000142 Note Off:- 57 Vel 00 144 Note 44 vel 85 000144 Note On :- 44 Vel 55 --> 000190 Note Off:- 44 Vel 00 144 Note 56 vel 1 000144 Note On :- 56 Vel 01 --> 000190 Note Off:- 56 Vel 00 144 Note 57 vel 25 000144 Note On :- 57 Vel 19 --> 000190 Note Off:- 57 Vel 00 192 Note 56 vel 35 000192 Note On :- 56 Vel 23 --> 000238 Note Off:- 56 Vel 00 192 Note 57 vel 75 000192 Note On :- 57 Vel 4B --> 000238 Note Off:- 57 Vel 00 240 Note 56 vel 1 000240 Note On :- 56 Vel 01 --> 000286 Note Off:- 56 Vel 00 240 Note 57 vel 25 000240 Note On :- 57 Vel 19 --> 000286 Note Off:- 57 Vel 00 288 Note 54 vel 70 000288 Note On :- 38 Vel 5A --> 000334 Note Off:- 38 Vel 00 288 Note 38 vel 90 000288 Note On :- 54 Vel 46 --> 000334 Note Off:- 54 Vel 00 288 Note 56 vel 35 000288 Note On :- 56 Vel 23 --> 000334 Note Off:- 56 Vel 00 288 Note 57 vel 75 000288 Note On :- 57 Vel 4B --> 000334 Note Off:- 57 Vel 00 336 Note 44 vel 20 000336 Note On :- 44 Vel 14 --> 000382 Note Off:- 44 Vel 00 336 Note 56 vel 1 000336 Note On :- 56 Vel 01 --> 000382 Note Off:- 56 Vel 00 336 Note 57 vel 25 000336 Note On :- 57 Vel 19 --> 000382 Note Off:- 57 Vel 00 384 Note 44 vel 85 000384 Note On :- 44 Vel 55 --> 000430 Note Off:- 44 Vel 00 384 Note 56 vel 35 000384 Note On :- 56 Vel 23 --> 000430 Note Off:- 56 Vel 00 384 Note 57 vel 75 000384 Note On :- 57 Vel 4B --> 000430 Note Off:- 57 Vel 00 432 Note 56 vel 1 000432 Note On :- 56 Vel 01 --> 000478 Note Off:- 56 Vel 00 432 Note 57 vel 25 000432 Note On :- 57 Vel 19 --> 000478 Note Off:- 57 Vel 00 480 Note 38 vel 90 000480 Note On :- 38 Vel 5A --> 000526 Note Off:- 38 Vel 00 480 Note 56 vel 35 000480 Note On :- 56 Vel 23 --> 000526 Note Off:- 56 Vel 00 480 Note 57 vel 75 000480 Note On :- 57 Vel 4B --> 000526 Note Off:- 57 Vel 00 528 Note 56 vel 1 000528 Note On :- 56 Vel 01 --> 000574 Note Off:- 56 Vel 00 528 Note 57 vel 25 000528 Note On :- 57 Vel 19 --> 000574 Note Off:- 57 Vel 00 576 Note 44 vel 85 000576 Note On :- 44 Vel 55 --> 000622 Note Off:- 44 Vel 00 576 Note 56 vel 35 000576 Note On :- 56 Vel 23 --> 000622 Note Off:- 56 Vel 00 576 Note 57 vel 75 000576 Note On :- 57 Vel 4B --> 000622 Note Off:- 57 Vel 00 624 Note 44 vel 85 000624 Note On :- 44 Vel 55 --> 000670 Note Off:- 44 Vel 00 624 Note 56 vel 1 000624 Note On :- 56 Vel 01 --> 000670 Note Off:- 56 Vel 00 624 Note 57 vel 25 000624 Note On :- 57 Vel 19 --> 000670 Note Off:- 57 Vel 00 672 Note 54 vel 70 000672 Note On :- 38 Vel 5A --> 000718 Note Off:- 38 Vel 00 672 Note 38 vel 90 000672 Note On :- 54 Vel 46 --> 000718 Note Off:- 54 Vel 00 672 Note 56 vel 35 000672 Note On :- 56 Vel 23 --> 000718 Note Off:- 56 Vel 00 672 Note 57 vel 75 000672 Note On :- 57 Vel 4B --> 000718 Note Off:- 57 Vel 00 720 Note 56 vel 1 000720 Note On :- 56 Vel 01 --> 000766 Note Off:- 56 Vel 00 ================================================ FILE: contrib/gdb/cgdbrc ================================================ # /usr/bin/cgdb # # The configuration for the cgdb debugger front end. # # Copy this file to ~/.cgdb/cgdbrc in order to use it. # # :set arrowstyle=long # :set arrowstyle=highlight # :set autosourcereload :set tabstop=4 :highlight SelectedLineNR cterm=bold,reverse # :set wso=vertical :set wso=horizontal # :set pagination off ================================================ FILE: contrib/gdb/dot-gdbinit ================================================ #****************************************************************************** # gdbinit (~/.gdbinit) #------------------------------------------------------------------------------ # # Maintainer: Chris Ahlstrom # Last Change: 2013-09-16 to 2024-10-15 # Project: Portable Configuration # License: None. Use it in any manner whatsover, and don't blame me. # Usage: # # This file provides my personal version of .gdbinit. # It is a personal settings file for the GNU debugger. # # To use it, simply copy it to your home directory in this manner: # # cp gdbinit ~/.gdbinit # #------------------------------------------------------------------------------ set print pretty on set print elements 0 set pagination off handle SIG32 nostop define pvec if $argc == 2 set $elem = $arg0.size() if $arg1 >= $arg0.size() printf "Error, %s.size() = %d, last element:\n", "$arg0", $arg0.size() set $elem = $arg1 -1 end print/x *($arg0._M_impl._M_start + $elem)@1 else print/x *($arg0._M_impl._M_start)@$arg0.size() end end document pvec Display vector contents in hex notation. Usage: pvec v [ index ] v is the name of the vector. index is an optional argument specifying the element to display. end #------------------------------------------------------------------------------ # Installation: Change to the directory .config/gdb and run this command # to get the python std-handling modules: # # svn co svn://gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/python # #------------------------------------------------------------------------------ python import sys sys.path.insert(0, '/home/ahlstrom/.config/gdb/python') from libstdcxx.v6.printers import register_libstdcxx_printers register_libstdcxx_printers (None) end #------------------------------------------------------------------------------ # vim: ts=3 noet ft=gdb syntax=gdb #------------------------------------------------------------------------------ ================================================ FILE: contrib/git/git.text ================================================ Quick Reference for Git Processes on GitHub Chris Ahlstrom 2015-09-09 to 2023-07-01 The content of this file has been moved to the LibreOffice document "git.odt". # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/git/gitconfig ================================================ [user] name = User email = user@gmail.com [core] excludesfile = /home/user/.gitignore editor = vim [diff] tool = gvimdiff # tool = nvim-qt -d [difftool] prompt = false [alias] dn = diff --name-only dt = difftool last = log -1 HEAD changes = diff --name-only [color] diff = auto # status = auto branch = auto interactive = auto ui = true pager = true [color "status"] added = green bold changed = red bold untracked = magenta bold [color "branch"] remote = yellow bold [pull] rebase = false # vim: filetype=gitconfig ================================================ FILE: contrib/gvim.rc ================================================ " The top of this file is similar to .vimrc. :set nocompatible :set nocompatible :set autoindent :set smartindent " Try turning this off for awhile: :set cindent :set formatoptions=tcroql :set nohlsearch :set expandtab :set noic :set foldmethod=manual :set nofoldenable :set ts=4 :set wm=6 :set sw=4 :set noerrorbells :set novisualbell :set vb t_vb= :set modeline :set equalprg=astyle :filetype detect :syntax on :set background=dark :set mps+=<:> :let c_gnu=1 :set makeef=err.t " 'makeef' file is used with the ":make" command :set autowrite :let loaded_matchparen=1 " Inhibit loading of pi_paren plugin. (:NoMatchParen) :match Ignore /\r$/ " Don't show those nasty ^M's " :let c_minlines=200 " Comment out if you think it hurts performance :let c_space_errors=1 " Comment out if you think it hurts your eyes " Specific to .gvimrc: " " Set the font and get rid of the toolbar. Also set wide column for double " edits, and lines that just fill the screen when used with the current font. " " Monospace\ 10 " Droid\ Sans\ Mono\ 10 " FreeMono 11 " WenQuanYi\ Zen\ Hei\ Mono\ Medium\ 11 " Anonymous\ Pro\ 12 :set guifont=Terminus\ 12 :set guioptions-=T :set guioptions-=r :set guioptions-=L " :set columns=168 :winpos 116 32 :set columns=172 :set lines=48 :hi Normal guibg=#101410 guifg=#F5D0A0 :set diffopt=filler,iwhite "gvimdiff - We add the iwhite option. ================================================ FILE: contrib/midi/16-blank-patterns.text ================================================ File name: /home/ahlstrom/Home/ca/mls/git/seq66/contrib/midi/16-blank-patterns.text No. of sets: 32 No. of tracks: 21 MIDI format: 1 PPQN: 192 Sequence #0 'Ch 1' Input port #: 255-->255 Output port #: 0-->0 Channel: 0 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #1 'Ch 2' Input port #: 255-->255 Output port #: 0-->0 Channel: 1 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #2 'Ch 3' Input port #: 255-->255 Output port #: 0-->0 Channel: 2 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #3 'Ch 4' Input port #: 255-->255 Output port #: 0-->0 Channel: 3 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #4 'Ch 5' Input port #: 255-->255 Output port #: 0-->0 Channel: 4 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #5 'Ch 6' Input port #: 255-->255 Output port #: 0-->0 Channel: 5 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #6 'Ch 7' Input port #: 255-->255 Output port #: 0-->0 Channel: 6 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #7 'Ch 8' Input port #: 255-->255 Output port #: 0-->0 Channel: 7 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #8 'Ch 9' Input port #: 255-->255 Output port #: 0-->0 Channel: 8 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #9 'Ch 10' Input port #: 255-->255 Output port #: 0-->0 Channel: 9 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #10 'Ch 11' Input port #: 255-->255 Output port #: 0-->0 Channel: 10 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #11 'Ch 12' Input port #: 255-->255 Output port #: 0-->0 Channel: 11 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #12 'Ch 13' Input port #: 255-->255 Output port #: 0-->0 Channel: 12 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #13 'Ch 14' Input port #: 255-->255 Output port #: 0-->0 Channel: 13 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #14 'Ch 15' Input port #: 255-->255 Output port #: 0-->0 Channel: 14 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #15 'Ch 16' Input port #: 255-->255 Output port #: 0-->0 Channel: 15 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 3 Sequence #16 'Any Channel' Input port #: 255-->255 Output port #: 0-->0 Channel: 128 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 4 Sequence #24 'Buss 1' Input port #: 1-->1 Output port #: 0-->0 Channel: 0 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 1 Sequence #25 'Buss 2' Input port #: 2-->2 Output port #: 0-->0 Channel: 0 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 1 Sequence #26 'Buss 3' Input port #: 3-->3 Output port #: 0-->0 Channel: 0 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 1 Sequence #27 'Buss 4' Input port #: 4-->4 Output port #: 0-->0 Channel: 0 Beats: 4/4 Length (ticks): 768 Events;triggers: 0; 0 Transposable: true Key and scale: 0; 0 Color: 1 Start of SeqSpecs: 0xFF 0x7F 24240010 MIDI control = 0 0xFF 0x7F 24240003 Track clocking = 0 Screen-set Notes: 0xFF 0x7F 24240005 Set notes = 32 Set #0: 'empty' Set #1: 'empty' Set #2: 'empty' Set #3: 'empty' Set #4: 'empty' Set #5: 'empty' Set #6: 'empty' Set #7: 'empty' Set #8: 'empty' Set #9: 'empty' Set #10: 'empty' Set #11: 'empty' Set #12: 'empty' Set #13: 'empty' Set #14: 'empty' Set #15: 'empty' Set #16: 'empty' Set #17: 'empty' Set #18: 'empty' Set #19: 'empty' Set #20: 'empty' Set #21: 'empty' Set #22: 'empty' Set #23: 'empty' Set #24: 'empty' Set #25: 'empty' Set #26: 'empty' Set #27: 'empty' Set #28: 'empty' Set #29: 'empty' Set #30: 'empty' Set #31: 'empty' 0xFF 0x7F 24240007 Main beats/minute = 124 BPM: 124 0xFF 0x7F 24240009 Song mute group data = 32 Mute Groups: 32 of size 32 Mute group #0 empty Mute group #1 empty Mute group #2 empty Mute group #3 empty Mute group #4 empty Mute group #5 empty Mute group #6 empty Mute group #7 empty Mute group #8 empty Mute group #9 empty Mute group #10 empty Mute group #11 empty Mute group #12 empty Mute group #13 empty Mute group #14 empty Mute group #15 empty Mute group #16 empty Mute group #17 empty Mute group #18 empty Mute group #19 empty Mute group #20 empty Mute group #21 empty Mute group #22 empty Mute group #23 empty Mute group #24 empty Mute group #25 empty Mute group #26 empty Mute group #27 empty Mute group #28 empty Mute group #29 empty Mute group #30 empty Mute group #31 empty All mute-groups are of size 0 Global key, scale, and background sequence: 0xFF 0x7F 24240011 Track key = 0 0xFF 0x7F 24240012 Track scale = 0 0xFF 0x7F 24240013 Track background sequence = 2048 Global beats, beat width, and tempo track: 0xFF 0x7F 24240015 Perfedit beats/measure = 4 0xFF 0x7F 24240016 Perfedit beat-width = 4 0xFF 0x7F 2424001a Alternate tempo track number = 0 ================================================ FILE: contrib/midi/2rock.asc ================================================ MThd 0 1 240 MTrk 0 KeySig 0 major 0 SMPTE 96 0 0 0 0 0 SeqSpec 00 00 00 6a 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1d 1d 0 Tempo 508474 0 TimeSig 4/4 24 8 0 Meta Lyric "Ride cymbal" 0 Meta Lyric "Closed hat" 0 On ch=10 n=42 v=64 0 Meta Lyric "Open hat" 0 Meta Lyric "High tom" 0 Meta Lyric "Mid tom" 0 Meta Lyric "Low tom" 0 Meta Lyric "Snare drum" 0 Meta Lyric "Bass drum" 0 On ch=10 n=36 v=64 0 Meta Lyric "(c) 1991 joel Sampson" 0 PoPr ch=1 n=60 v=64 60 On ch=10 n=42 v=0 60 On ch=10 n=36 v=0 240 On ch=10 n=42 v=110 240 On ch=10 n=38 v=64 300 On ch=10 n=42 v=0 300 On ch=10 n=38 v=0 360 On ch=10 n=36 v=64 420 On ch=10 n=36 v=0 480 On ch=10 n=42 v=64 480 On ch=10 n=36 v=64 540 On ch=10 n=42 v=0 540 On ch=10 n=36 v=0 720 On ch=10 n=42 v=64 720 On ch=10 n=38 v=64 780 On ch=10 n=42 v=0 780 On ch=10 n=38 v=0 960 On ch=10 n=42 v=64 960 On ch=10 n=36 v=64 1020 On ch=10 n=42 v=0 1020 On ch=10 n=36 v=0 1200 On ch=10 n=42 v=110 1200 On ch=10 n=38 v=64 1260 On ch=10 n=42 v=0 1260 On ch=10 n=38 v=0 1320 On ch=10 n=36 v=64 1380 On ch=10 n=36 v=0 1440 On ch=10 n=42 v=64 1440 On ch=10 n=36 v=64 1500 On ch=10 n=42 v=0 1500 On ch=10 n=36 v=0 1680 On ch=10 n=42 v=64 1680 On ch=10 n=38 v=64 1740 On ch=10 n=42 v=0 1740 On ch=10 n=38 v=0 1920 On ch=10 n=42 v=64 1920 On ch=10 n=36 v=64 1980 On ch=10 n=42 v=0 1980 On ch=10 n=36 v=0 2160 On ch=10 n=42 v=64 2160 On ch=10 n=38 v=64 2220 On ch=10 n=42 v=0 2220 On ch=10 n=38 v=0 2280 On ch=10 n=36 v=64 2340 On ch=10 n=36 v=0 2400 On ch=10 n=42 v=64 2400 On ch=10 n=36 v=64 2460 On ch=10 n=42 v=0 2460 On ch=10 n=36 v=0 2520 On ch=10 n=36 v=64 2580 On ch=10 n=36 v=0 2640 On ch=10 n=42 v=64 2640 On ch=10 n=38 v=64 2700 On ch=10 n=42 v=0 2700 On ch=10 n=38 v=0 2760 On ch=10 n=36 v=64 2820 On ch=10 n=36 v=0 2880 On ch=10 n=42 v=64 2880 On ch=10 n=36 v=64 2940 On ch=10 n=42 v=0 2940 On ch=10 n=36 v=0 3120 On ch=10 n=42 v=64 3120 On ch=10 n=38 v=64 3180 On ch=10 n=42 v=0 3180 On ch=10 n=38 v=0 3240 On ch=10 n=36 v=64 3300 On ch=10 n=36 v=0 3360 On ch=10 n=42 v=64 3360 On ch=10 n=36 v=64 3420 On ch=10 n=42 v=0 3420 On ch=10 n=36 v=0 3480 On ch=10 n=36 v=64 3540 On ch=10 n=36 v=0 3600 On ch=10 n=42 v=64 3600 On ch=10 n=38 v=64 3660 On ch=10 n=42 v=0 3660 On ch=10 n=38 v=0 3720 On ch=10 n=36 v=64 3780 On ch=10 n=36 v=0 3840 On ch=10 n=42 v=127 3840 On ch=10 n=36 v=110 3900 On ch=10 n=42 v=0 3900 On ch=10 n=36 v=0 3960 On ch=10 n=38 v=64 4020 On ch=10 n=38 v=0 4020 On ch=10 n=50 v=64 4080 On ch=10 n=50 v=0 4080 On ch=10 n=50 v=64 4140 On ch=10 n=50 v=0 4200 On ch=10 n=38 v=64 4260 On ch=10 n=38 v=0 4260 On ch=10 n=50 v=64 4320 On ch=10 n=50 v=0 4320 On ch=10 n=50 v=64 4380 On ch=10 n=50 v=0 4440 On ch=10 n=38 v=64 4500 On ch=10 n=38 v=0 4500 On ch=10 n=50 v=64 4560 On ch=10 n=50 v=0 4560 On ch=10 n=38 v=110 4620 On ch=10 n=38 v=0 4620 On ch=10 n=38 v=64 4680 On ch=10 n=38 v=64 4681 On ch=10 n=38 v=0 4740 On ch=10 n=38 v=110 4741 On ch=10 n=38 v=0 4800 On ch=10 n=38 v=0 4800 On ch=10 n=42 v=127 4800 On ch=10 n=36 v=110 4860 On ch=10 n=42 v=0 4860 On ch=10 n=36 v=0 4920 On ch=10 n=38 v=64 4980 On ch=10 n=38 v=0 4980 On ch=10 n=50 v=64 5040 On ch=10 n=50 v=0 5040 On ch=10 n=50 v=64 5100 On ch=10 n=50 v=0 5160 On ch=10 n=38 v=64 5220 On ch=10 n=38 v=0 5220 On ch=10 n=50 v=64 5280 On ch=10 n=50 v=0 5280 On ch=10 n=50 v=64 5340 On ch=10 n=50 v=0 5400 On ch=10 n=38 v=64 5460 On ch=10 n=38 v=0 5460 On ch=10 n=50 v=64 5520 On ch=10 n=50 v=0 5520 On ch=10 n=38 v=110 5580 On ch=10 n=38 v=0 5580 On ch=10 n=38 v=64 5640 On ch=10 n=38 v=64 5641 On ch=10 n=38 v=0 5700 On ch=10 n=38 v=110 5701 On ch=10 n=38 v=0 5760 On ch=10 n=38 v=0 5760 On ch=10 n=42 v=127 5760 On ch=10 n=36 v=64 5820 On ch=10 n=42 v=0 5820 On ch=10 n=36 v=0 5820 On ch=10 n=42 v=64 5880 On ch=10 n=42 v=127 5880 On ch=10 n=36 v=64 5881 On ch=10 n=42 v=0 5940 On ch=10 n=36 v=0 5940 On ch=10 n=42 v=64 5941 On ch=10 n=42 v=0 6000 On ch=10 n=42 v=127 6000 On ch=10 n=38 v=64 6001 On ch=10 n=42 v=0 6060 On ch=10 n=38 v=0 6060 On ch=10 n=42 v=64 6060 On ch=10 n=36 v=64 6061 On ch=10 n=42 v=0 6120 On ch=10 n=36 v=0 6120 On ch=10 n=42 v=127 6121 On ch=10 n=42 v=0 6180 On ch=10 n=42 v=64 6181 On ch=10 n=42 v=0 6240 On ch=10 n=42 v=127 6240 On ch=10 n=36 v=64 6241 On ch=10 n=42 v=0 6300 On ch=10 n=36 v=0 6300 On ch=10 n=42 v=64 6301 On ch=10 n=42 v=0 6360 On ch=10 n=42 v=127 6361 On ch=10 n=42 v=0 6420 On ch=10 n=42 v=64 6421 On ch=10 n=42 v=0 6480 On ch=10 n=42 v=127 6480 On ch=10 n=38 v=64 6481 On ch=10 n=42 v=0 6540 On ch=10 n=38 v=0 6540 On ch=10 n=42 v=64 6541 On ch=10 n=42 v=0 6600 On ch=10 n=42 v=127 6601 On ch=10 n=42 v=0 6660 On ch=10 n=42 v=0 6660 On ch=10 n=44 v=64 6660 On ch=10 n=36 v=64 6720 On ch=10 n=44 v=0 6720 On ch=10 n=36 v=0 6720 On ch=10 n=42 v=127 6720 On ch=10 n=36 v=64 6780 On ch=10 n=42 v=0 6780 On ch=10 n=36 v=0 6780 On ch=10 n=42 v=64 6840 On ch=10 n=42 v=127 6840 On ch=10 n=36 v=64 6841 On ch=10 n=42 v=0 6900 On ch=10 n=36 v=0 6900 On ch=10 n=42 v=64 6901 On ch=10 n=42 v=0 6960 On ch=10 n=42 v=127 6960 On ch=10 n=38 v=64 6961 On ch=10 n=42 v=0 7020 On ch=10 n=38 v=0 7020 On ch=10 n=42 v=64 7020 On ch=10 n=36 v=64 7021 On ch=10 n=42 v=0 7080 On ch=10 n=36 v=0 7080 On ch=10 n=42 v=127 7081 On ch=10 n=42 v=0 7140 On ch=10 n=42 v=64 7141 On ch=10 n=42 v=0 7200 On ch=10 n=42 v=127 7200 On ch=10 n=36 v=64 7201 On ch=10 n=42 v=0 7260 On ch=10 n=36 v=0 7260 On ch=10 n=42 v=64 7261 On ch=10 n=42 v=0 7320 On ch=10 n=42 v=127 7321 On ch=10 n=42 v=0 7380 On ch=10 n=42 v=64 7381 On ch=10 n=42 v=0 7440 On ch=10 n=42 v=127 7440 On ch=10 n=38 v=64 7441 On ch=10 n=42 v=0 7500 On ch=10 n=38 v=0 7500 On ch=10 n=42 v=64 7501 On ch=10 n=42 v=0 7560 On ch=10 n=42 v=127 7561 On ch=10 n=42 v=0 7620 On ch=10 n=42 v=0 7620 On ch=10 n=44 v=64 7620 On ch=10 n=36 v=64 7680 On ch=10 n=44 v=0 7680 On ch=10 n=42 v=127 7680 On ch=10 n=36 v=64 7681 On ch=10 n=36 v=0 7740 On ch=10 n=42 v=0 7740 On ch=10 n=36 v=0 7740 On ch=10 n=42 v=64 7800 On ch=10 n=42 v=127 7800 On ch=10 n=36 v=64 7801 On ch=10 n=42 v=0 7860 On ch=10 n=36 v=0 7860 On ch=10 n=42 v=64 7861 On ch=10 n=42 v=0 7920 On ch=10 n=42 v=127 7920 On ch=10 n=38 v=64 7921 On ch=10 n=42 v=0 7980 On ch=10 n=38 v=0 7980 On ch=10 n=42 v=64 7980 On ch=10 n=36 v=64 7981 On ch=10 n=42 v=0 8040 On ch=10 n=36 v=0 8040 On ch=10 n=42 v=127 8041 On ch=10 n=42 v=0 8100 On ch=10 n=42 v=0 8100 On ch=10 n=44 v=110 8160 On ch=10 n=44 v=0 8160 On ch=10 n=42 v=127 8160 On ch=10 n=36 v=64 8220 On ch=10 n=42 v=0 8220 On ch=10 n=36 v=0 8220 On ch=10 n=42 v=64 8280 On ch=10 n=42 v=127 8281 On ch=10 n=42 v=0 8340 On ch=10 n=42 v=64 8341 On ch=10 n=42 v=0 8400 On ch=10 n=42 v=127 8400 On ch=10 n=38 v=64 8401 On ch=10 n=42 v=0 8460 On ch=10 n=38 v=0 8460 On ch=10 n=42 v=64 8461 On ch=10 n=42 v=0 8520 On ch=10 n=42 v=127 8520 On ch=10 n=38 v=64 8521 On ch=10 n=42 v=0 8580 On ch=10 n=42 v=0 8580 On ch=10 n=38 v=0 8580 On ch=10 n=44 v=110 8580 On ch=10 n=36 v=88 8640 On ch=10 n=44 v=0 8640 On ch=10 n=36 v=0 8640 On ch=10 n=42 v=127 8640 On ch=10 n=36 v=64 8700 On ch=10 n=42 v=0 8700 On ch=10 n=36 v=0 8700 On ch=10 n=42 v=64 8760 On ch=10 n=42 v=127 8760 On ch=10 n=36 v=64 8761 On ch=10 n=42 v=0 8820 On ch=10 n=36 v=0 8820 On ch=10 n=42 v=64 8821 On ch=10 n=42 v=0 8880 On ch=10 n=42 v=127 8880 On ch=10 n=38 v=64 8881 On ch=10 n=42 v=0 8940 On ch=10 n=38 v=0 8940 On ch=10 n=42 v=64 8940 On ch=10 n=36 v=64 8941 On ch=10 n=42 v=0 9000 On ch=10 n=36 v=0 9000 On ch=10 n=42 v=127 9001 On ch=10 n=42 v=0 9060 On ch=10 n=42 v=0 9060 On ch=10 n=44 v=110 9120 On ch=10 n=44 v=0 9120 On ch=10 n=42 v=127 9120 On ch=10 n=36 v=64 9180 On ch=10 n=42 v=0 9180 On ch=10 n=36 v=0 9180 On ch=10 n=42 v=64 9240 On ch=10 n=42 v=127 9241 On ch=10 n=42 v=0 9300 On ch=10 n=42 v=64 9301 On ch=10 n=42 v=0 9360 On ch=10 n=42 v=127 9360 On ch=10 n=38 v=64 9361 On ch=10 n=42 v=0 9420 On ch=10 n=38 v=0 9420 On ch=10 n=42 v=64 9421 On ch=10 n=42 v=0 9480 On ch=10 n=42 v=127 9480 On ch=10 n=38 v=64 9481 On ch=10 n=42 v=0 9540 On ch=10 n=42 v=0 9540 On ch=10 n=38 v=0 9540 On ch=10 n=44 v=110 9540 On ch=10 n=36 v=88 9600 On ch=10 n=44 v=0 9600 On ch=10 n=38 v=64 9600 On ch=10 n=36 v=88 9601 On ch=10 n=36 v=0 9660 On ch=10 n=38 v=0 9660 On ch=10 n=36 v=0 9660 On ch=10 n=38 v=64 9720 On ch=10 n=38 v=64 9720 On ch=10 n=36 v=64 9721 On ch=10 n=38 v=0 9780 On ch=10 n=36 v=0 9780 On ch=10 n=38 v=64 9781 On ch=10 n=38 v=0 9840 On ch=10 n=38 v=0 9840 On ch=10 n=47 v=64 9900 On ch=10 n=47 v=0 9900 On ch=10 n=47 v=64 9900 On ch=10 n=36 v=64 9960 On ch=10 n=36 v=0 9960 On ch=10 n=47 v=64 9961 On ch=10 n=47 v=0 10020 On ch=10 n=47 v=64 10021 On ch=10 n=47 v=0 10080 On ch=10 n=47 v=0 10080 On ch=10 n=43 v=64 10080 On ch=10 n=36 v=88 10140 On ch=10 n=43 v=0 10140 On ch=10 n=36 v=0 10140 On ch=10 n=43 v=64 10200 On ch=10 n=43 v=64 10200 On ch=10 n=36 v=64 10201 On ch=10 n=43 v=0 10260 On ch=10 n=36 v=0 10260 On ch=10 n=43 v=64 10261 On ch=10 n=43 v=0 10320 On ch=10 n=43 v=0 10320 On ch=10 n=51 v=88 10320 On ch=10 n=36 v=64 10380 On ch=10 n=51 v=0 10380 On ch=10 n=36 v=0 10440 On ch=10 n=51 v=88 10440 On ch=10 n=36 v=64 10500 On ch=10 n=51 v=0 10500 On ch=10 n=36 v=0 10560 On ch=10 n=38 v=64 10560 On ch=10 n=36 v=88 10620 On ch=10 n=38 v=0 10620 On ch=10 n=36 v=0 10620 On ch=10 n=38 v=64 10680 On ch=10 n=38 v=64 10680 On ch=10 n=36 v=64 10681 On ch=10 n=38 v=0 10740 On ch=10 n=36 v=0 10740 On ch=10 n=38 v=64 10741 On ch=10 n=38 v=0 10800 On ch=10 n=38 v=0 10800 On ch=10 n=47 v=64 10860 On ch=10 n=47 v=0 10860 On ch=10 n=47 v=64 10860 On ch=10 n=36 v=64 10920 On ch=10 n=36 v=0 10920 On ch=10 n=47 v=64 10921 On ch=10 n=47 v=0 10980 On ch=10 n=47 v=64 10981 On ch=10 n=47 v=0 11040 On ch=10 n=47 v=0 11040 On ch=10 n=43 v=64 11040 On ch=10 n=36 v=88 11100 On ch=10 n=43 v=0 11100 On ch=10 n=36 v=0 11100 On ch=10 n=43 v=64 11160 On ch=10 n=43 v=64 11160 On ch=10 n=36 v=64 11161 On ch=10 n=43 v=0 11220 On ch=10 n=36 v=0 11220 On ch=10 n=43 v=64 11221 On ch=10 n=43 v=0 11280 On ch=10 n=43 v=0 11280 On ch=10 n=51 v=88 11280 On ch=10 n=36 v=64 11340 On ch=10 n=51 v=0 11340 On ch=10 n=36 v=0 11400 On ch=10 n=51 v=88 11400 On ch=10 n=36 v=64 11460 On ch=10 n=51 v=0 11460 On ch=10 n=36 v=0 11520 On ch=10 n=42 v=64 11520 On ch=10 n=36 v=64 11580 On ch=10 n=42 v=0 11580 On ch=10 n=36 v=0 11640 On ch=10 n=42 v=64 11700 On ch=10 n=42 v=0 11760 On ch=10 n=42 v=64 11760 On ch=10 n=38 v=64 11820 On ch=10 n=42 v=0 11820 On ch=10 n=38 v=0 11880 On ch=10 n=42 v=64 11940 On ch=10 n=42 v=0 12000 On ch=10 n=42 v=64 12060 On ch=10 n=42 v=0 12120 On ch=10 n=42 v=64 12120 On ch=10 n=36 v=64 12180 On ch=10 n=42 v=0 12180 On ch=10 n=36 v=0 12240 On ch=10 n=42 v=64 12240 On ch=10 n=38 v=64 12300 On ch=10 n=42 v=0 12300 On ch=10 n=38 v=0 12360 On ch=10 n=42 v=64 12420 On ch=10 n=42 v=0 12420 On ch=10 n=36 v=64 12480 On ch=10 n=36 v=0 12480 On ch=10 n=42 v=64 12480 On ch=10 n=36 v=64 12540 On ch=10 n=42 v=0 12540 On ch=10 n=36 v=0 12600 On ch=10 n=42 v=64 12660 On ch=10 n=42 v=0 12720 On ch=10 n=42 v=64 12720 On ch=10 n=38 v=64 12780 On ch=10 n=42 v=0 12780 On ch=10 n=38 v=0 12840 On ch=10 n=42 v=64 12900 On ch=10 n=42 v=0 12960 On ch=10 n=42 v=64 13020 On ch=10 n=42 v=0 13080 On ch=10 n=42 v=64 13080 On ch=10 n=36 v=64 13140 On ch=10 n=42 v=0 13140 On ch=10 n=36 v=0 13200 On ch=10 n=42 v=64 13200 On ch=10 n=38 v=64 13260 On ch=10 n=42 v=0 13260 On ch=10 n=38 v=0 13320 On ch=10 n=42 v=64 13380 On ch=10 n=42 v=0 13380 On ch=10 n=36 v=64 13440 On ch=10 n=42 v=64 13440 On ch=10 n=36 v=64 13441 On ch=10 n=36 v=0 13500 On ch=10 n=42 v=0 13500 On ch=10 n=36 v=0 13560 On ch=10 n=42 v=64 13620 On ch=10 n=42 v=0 13680 On ch=10 n=42 v=64 13680 On ch=10 n=38 v=64 13740 On ch=10 n=42 v=0 13740 On ch=10 n=38 v=0 13800 On ch=10 n=42 v=64 13860 On ch=10 n=42 v=0 13920 On ch=10 n=42 v=64 13920 On ch=10 n=36 v=64 13980 On ch=10 n=42 v=0 13980 On ch=10 n=36 v=0 14040 On ch=10 n=42 v=64 14040 On ch=10 n=36 v=64 14100 On ch=10 n=42 v=0 14100 On ch=10 n=36 v=0 14160 On ch=10 n=42 v=64 14160 On ch=10 n=38 v=64 14220 On ch=10 n=42 v=0 14220 On ch=10 n=38 v=0 14280 On ch=10 n=42 v=64 14340 On ch=10 n=42 v=0 14400 On ch=10 n=42 v=64 14400 On ch=10 n=36 v=64 14460 On ch=10 n=42 v=0 14460 On ch=10 n=36 v=0 14520 On ch=10 n=42 v=64 14580 On ch=10 n=42 v=0 14640 On ch=10 n=42 v=64 14640 On ch=10 n=38 v=64 14700 On ch=10 n=42 v=0 14700 On ch=10 n=38 v=0 14760 On ch=10 n=42 v=64 14820 On ch=10 n=42 v=0 14880 On ch=10 n=42 v=64 14880 On ch=10 n=36 v=64 14940 On ch=10 n=42 v=0 14940 On ch=10 n=36 v=0 15000 On ch=10 n=42 v=64 15000 On ch=10 n=36 v=64 15060 On ch=10 n=42 v=0 15060 On ch=10 n=36 v=0 15120 On ch=10 n=42 v=64 15120 On ch=10 n=38 v=64 15180 On ch=10 n=42 v=0 15180 On ch=10 n=38 v=0 15240 On ch=10 n=42 v=64 15300 On ch=10 n=42 v=0 15360 On ch=10 n=42 v=64 15360 On ch=10 n=36 v=64 15420 On ch=10 n=42 v=0 15420 On ch=10 n=36 v=0 15480 On ch=10 n=44 v=64 15480 On ch=10 n=38 v=64 15540 On ch=10 n=44 v=0 15540 On ch=10 n=38 v=0 15600 On ch=10 n=42 v=64 15660 On ch=10 n=42 v=0 15720 On ch=10 n=44 v=64 15720 On ch=10 n=38 v=64 15780 On ch=10 n=44 v=0 15780 On ch=10 n=38 v=0 15840 On ch=10 n=42 v=64 15840 On ch=10 n=36 v=64 15900 On ch=10 n=42 v=0 15900 On ch=10 n=36 v=0 15960 On ch=10 n=44 v=64 15960 On ch=10 n=36 v=64 16020 On ch=10 n=44 v=0 16020 On ch=10 n=36 v=0 16080 On ch=10 n=42 v=64 16080 On ch=10 n=38 v=64 16140 On ch=10 n=42 v=0 16140 On ch=10 n=38 v=0 16200 On ch=10 n=44 v=64 16260 On ch=10 n=44 v=0 16320 On ch=10 n=42 v=64 16320 On ch=10 n=36 v=64 16380 On ch=10 n=42 v=0 16380 On ch=10 n=36 v=0 16440 On ch=10 n=44 v=64 16440 On ch=10 n=38 v=64 16500 On ch=10 n=44 v=0 16500 On ch=10 n=38 v=0 16560 On ch=10 n=42 v=64 16620 On ch=10 n=42 v=0 16680 On ch=10 n=44 v=64 16680 On ch=10 n=38 v=64 16740 On ch=10 n=44 v=0 16740 On ch=10 n=38 v=0 16800 On ch=10 n=42 v=64 16800 On ch=10 n=36 v=64 16860 On ch=10 n=42 v=0 16860 On ch=10 n=36 v=0 16920 On ch=10 n=44 v=64 16920 On ch=10 n=36 v=64 16980 On ch=10 n=44 v=0 16980 On ch=10 n=36 v=0 17040 On ch=10 n=42 v=64 17040 On ch=10 n=38 v=64 17100 On ch=10 n=42 v=0 17100 On ch=10 n=38 v=0 17160 On ch=10 n=44 v=64 17220 On ch=10 n=44 v=0 17280 On ch=10 n=42 v=64 17280 On ch=10 n=36 v=64 17340 On ch=10 n=42 v=0 17340 On ch=10 n=36 v=0 17400 On ch=10 n=42 v=64 17460 On ch=10 n=42 v=0 17520 On ch=10 n=42 v=64 17520 On ch=10 n=38 v=64 17520 On ch=10 n=36 v=64 17580 On ch=10 n=42 v=0 17580 On ch=10 n=38 v=0 17580 On ch=10 n=36 v=0 17640 On ch=10 n=42 v=64 17700 On ch=10 n=42 v=0 17760 On ch=10 n=42 v=64 17760 On ch=10 n=36 v=64 17820 On ch=10 n=42 v=0 17820 On ch=10 n=36 v=0 17880 On ch=10 n=42 v=64 17940 On ch=10 n=42 v=0 18000 On ch=10 n=42 v=64 18000 On ch=10 n=38 v=64 18000 On ch=10 n=36 v=64 18060 On ch=10 n=42 v=0 18060 On ch=10 n=38 v=0 18060 On ch=10 n=36 v=0 18120 On ch=10 n=42 v=64 18180 On ch=10 n=42 v=0 18240 On ch=10 n=42 v=64 18240 On ch=10 n=36 v=64 18300 On ch=10 n=42 v=0 18300 On ch=10 n=36 v=0 18360 On ch=10 n=42 v=64 18420 On ch=10 n=42 v=0 18480 On ch=10 n=42 v=64 18480 On ch=10 n=38 v=64 18480 On ch=10 n=36 v=64 18540 On ch=10 n=42 v=0 18540 On ch=10 n=38 v=0 18540 On ch=10 n=36 v=0 18600 On ch=10 n=42 v=64 18660 On ch=10 n=42 v=0 18720 On ch=10 n=42 v=64 18720 On ch=10 n=36 v=64 18780 On ch=10 n=42 v=0 18780 On ch=10 n=36 v=0 18840 On ch=10 n=42 v=64 18900 On ch=10 n=42 v=0 18960 On ch=10 n=42 v=64 18960 On ch=10 n=38 v=64 18960 On ch=10 n=36 v=64 19020 On ch=10 n=42 v=0 19020 On ch=10 n=38 v=0 19020 On ch=10 n=36 v=0 19080 On ch=10 n=42 v=64 19140 On ch=10 n=42 v=0 19200 On ch=10 n=42 v=64 19200 On ch=10 n=38 v=64 19200 On ch=10 n=36 v=64 19260 On ch=10 n=42 v=0 19260 On ch=10 n=38 v=0 19260 On ch=10 n=36 v=0 19320 On ch=10 n=42 v=64 19380 On ch=10 n=42 v=0 19440 On ch=10 n=42 v=110 19440 On ch=10 n=38 v=64 19440 On ch=10 n=36 v=64 19500 On ch=10 n=42 v=0 19500 On ch=10 n=38 v=0 19500 On ch=10 n=36 v=0 19560 On ch=10 n=42 v=64 19620 On ch=10 n=42 v=0 19680 On ch=10 n=42 v=64 19680 On ch=10 n=38 v=64 19680 On ch=10 n=36 v=64 19740 On ch=10 n=42 v=0 19740 On ch=10 n=38 v=0 19740 On ch=10 n=36 v=0 19800 On ch=10 n=42 v=64 19860 On ch=10 n=42 v=0 19920 On ch=10 n=42 v=110 19920 On ch=10 n=38 v=64 19920 On ch=10 n=36 v=64 19980 On ch=10 n=42 v=0 19980 On ch=10 n=38 v=0 19980 On ch=10 n=36 v=0 20040 On ch=10 n=42 v=64 20100 On ch=10 n=42 v=0 20160 On ch=10 n=42 v=64 20160 On ch=10 n=38 v=64 20160 On ch=10 n=36 v=64 20220 On ch=10 n=42 v=0 20220 On ch=10 n=38 v=0 20220 On ch=10 n=36 v=0 20280 On ch=10 n=42 v=64 20340 On ch=10 n=42 v=0 20400 On ch=10 n=42 v=110 20400 On ch=10 n=38 v=64 20400 On ch=10 n=36 v=64 20460 On ch=10 n=42 v=0 20460 On ch=10 n=38 v=0 20460 On ch=10 n=36 v=0 20520 On ch=10 n=42 v=64 20580 On ch=10 n=42 v=0 20640 On ch=10 n=42 v=64 20640 On ch=10 n=38 v=64 20640 On ch=10 n=36 v=64 20700 On ch=10 n=42 v=0 20700 On ch=10 n=38 v=0 20700 On ch=10 n=36 v=0 20760 On ch=10 n=42 v=64 20820 On ch=10 n=42 v=0 20880 On ch=10 n=42 v=110 20880 On ch=10 n=38 v=64 20880 On ch=10 n=36 v=64 20940 On ch=10 n=42 v=0 20940 On ch=10 n=38 v=0 20940 On ch=10 n=36 v=0 21000 On ch=10 n=42 v=64 21060 On ch=10 n=42 v=0 21120 On ch=10 n=38 v=110 21120 On ch=10 n=36 v=64 21180 On ch=10 n=38 v=0 21180 On ch=10 n=36 v=0 21180 On ch=10 n=38 v=64 21240 On ch=10 n=38 v=64 21241 On ch=10 n=38 v=0 21300 On ch=10 n=38 v=110 21301 On ch=10 n=38 v=0 21360 On ch=10 n=38 v=110 21360 On ch=10 n=36 v=64 21361 On ch=10 n=38 v=0 21420 On ch=10 n=36 v=0 21420 On ch=10 n=38 v=64 21421 On ch=10 n=38 v=0 21480 On ch=10 n=38 v=64 21481 On ch=10 n=38 v=0 21540 On ch=10 n=38 v=110 21541 On ch=10 n=38 v=0 21600 On ch=10 n=38 v=110 21600 On ch=10 n=36 v=64 21601 On ch=10 n=38 v=0 21660 On ch=10 n=36 v=0 21660 On ch=10 n=38 v=64 21661 On ch=10 n=38 v=0 21720 On ch=10 n=38 v=64 21721 On ch=10 n=38 v=0 21780 On ch=10 n=38 v=110 21781 On ch=10 n=38 v=0 21840 On ch=10 n=38 v=110 21840 On ch=10 n=36 v=64 21841 On ch=10 n=38 v=0 21900 On ch=10 n=36 v=0 21900 On ch=10 n=38 v=64 21901 On ch=10 n=38 v=0 21960 On ch=10 n=38 v=64 21961 On ch=10 n=38 v=0 22020 On ch=10 n=38 v=110 22021 On ch=10 n=38 v=0 22080 On ch=10 n=38 v=110 22080 On ch=10 n=36 v=64 22081 On ch=10 n=38 v=0 22140 On ch=10 n=36 v=0 22140 On ch=10 n=38 v=64 22141 On ch=10 n=38 v=0 22200 On ch=10 n=38 v=64 22201 On ch=10 n=38 v=0 22260 On ch=10 n=38 v=110 22261 On ch=10 n=38 v=0 22320 On ch=10 n=38 v=110 22320 On ch=10 n=36 v=64 22321 On ch=10 n=38 v=0 22380 On ch=10 n=36 v=0 22380 On ch=10 n=38 v=64 22381 On ch=10 n=38 v=0 22440 On ch=10 n=38 v=64 22441 On ch=10 n=38 v=0 22500 On ch=10 n=38 v=110 22501 On ch=10 n=38 v=0 22560 On ch=10 n=38 v=110 22560 On ch=10 n=36 v=64 22561 On ch=10 n=38 v=0 22620 On ch=10 n=36 v=0 22620 On ch=10 n=38 v=64 22621 On ch=10 n=38 v=0 22680 On ch=10 n=38 v=64 22681 On ch=10 n=38 v=0 22740 On ch=10 n=38 v=110 22741 On ch=10 n=38 v=0 22800 On ch=10 n=38 v=110 22800 On ch=10 n=36 v=64 22801 On ch=10 n=38 v=0 22860 On ch=10 n=36 v=0 22860 On ch=10 n=38 v=64 22861 On ch=10 n=38 v=0 22920 On ch=10 n=38 v=64 22921 On ch=10 n=38 v=0 22980 On ch=10 n=38 v=110 22981 On ch=10 n=38 v=0 23040 On ch=10 n=38 v=0 23040 On ch=10 n=42 v=64 23040 On ch=10 n=36 v=64 23100 On ch=10 n=42 v=0 23100 On ch=10 n=36 v=0 23280 On ch=10 n=42 v=64 23280 On ch=10 n=38 v=64 23340 On ch=10 n=42 v=0 23340 On ch=10 n=38 v=0 23520 On ch=10 n=42 v=64 23520 On ch=10 n=36 v=64 23580 On ch=10 n=42 v=0 23580 On ch=10 n=36 v=0 23640 On ch=10 n=36 v=64 23700 On ch=10 n=36 v=0 23760 On ch=10 n=42 v=64 23760 On ch=10 n=38 v=64 23820 On ch=10 n=42 v=0 23820 On ch=10 n=38 v=0 24000 On ch=10 n=42 v=64 24000 On ch=10 n=36 v=64 24060 On ch=10 n=42 v=0 24060 On ch=10 n=36 v=0 24240 On ch=10 n=42 v=64 24240 On ch=10 n=38 v=64 24300 On ch=10 n=42 v=0 24300 On ch=10 n=38 v=0 24480 On ch=10 n=42 v=64 24480 On ch=10 n=36 v=64 24540 On ch=10 n=42 v=0 24540 On ch=10 n=36 v=0 24600 On ch=10 n=36 v=64 24660 On ch=10 n=36 v=0 24720 On ch=10 n=42 v=64 24720 On ch=10 n=38 v=64 24780 On ch=10 n=42 v=0 24780 On ch=10 n=38 v=0 24960 On ch=10 n=42 v=64 24960 On ch=10 n=36 v=64 25020 On ch=10 n=42 v=0 25020 On ch=10 n=36 v=0 25200 On ch=10 n=42 v=64 25200 On ch=10 n=38 v=64 25260 On ch=10 n=42 v=0 25260 On ch=10 n=38 v=0 25320 On ch=10 n=36 v=64 25380 On ch=10 n=36 v=0 25440 On ch=10 n=42 v=64 25440 On ch=10 n=36 v=64 25500 On ch=10 n=42 v=0 25500 On ch=10 n=36 v=0 25680 On ch=10 n=42 v=64 25680 On ch=10 n=38 v=64 25740 On ch=10 n=42 v=0 25740 On ch=10 n=38 v=0 25920 On ch=10 n=42 v=64 25920 On ch=10 n=36 v=64 25980 On ch=10 n=42 v=0 25980 On ch=10 n=36 v=0 26160 On ch=10 n=42 v=64 26160 On ch=10 n=38 v=64 26220 On ch=10 n=42 v=0 26220 On ch=10 n=38 v=0 26280 On ch=10 n=36 v=64 26340 On ch=10 n=36 v=0 26400 On ch=10 n=42 v=64 26400 On ch=10 n=36 v=64 26460 On ch=10 n=42 v=0 26460 On ch=10 n=36 v=0 26640 On ch=10 n=42 v=64 26640 On ch=10 n=38 v=64 26700 On ch=10 n=42 v=0 26700 On ch=10 n=38 v=0 26880 On ch=10 n=42 v=64 26880 On ch=10 n=36 v=64 26940 On ch=10 n=42 v=0 26940 On ch=10 n=36 v=0 27120 On ch=10 n=38 v=64 27180 On ch=10 n=38 v=0 27300 On ch=10 n=38 v=88 27360 On ch=10 n=38 v=0 27480 On ch=10 n=38 v=88 27540 On ch=10 n=38 v=0 27600 On ch=10 n=38 v=88 27660 On ch=10 n=38 v=0 27840 On ch=10 n=42 v=64 27840 On ch=10 n=36 v=64 27900 On ch=10 n=42 v=0 27900 On ch=10 n=36 v=0 28080 On ch=10 n=38 v=64 28140 On ch=10 n=38 v=0 28260 On ch=10 n=38 v=88 28320 On ch=10 n=38 v=0 28440 On ch=10 n=38 v=88 28500 On ch=10 n=38 v=0 28560 On ch=10 n=38 v=88 28620 On ch=10 n=38 v=0 28620 Meta TrkEnd TrkEnd ================================================ FILE: contrib/midi/2rock.hex ================================================ 00000000: 4d 54 68 64 00 00 00 06 00 00 00 01 00 f0 4d 54 MThd..........MT 00000010: 72 6b 00 00 0a fa 00 ff 59 02 00 00 00 ff 54 05 rk......Y.....T. 00000020: 60 00 00 00 00 00 ff 7f 18 00 00 00 6a 00 01 00 `...........j... 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1d ................ 00000040: 1d 00 ff 51 03 07 c2 3a 00 ff 58 04 04 02 18 08 ...Q...:..X..... 00000050: 00 ff 05 0b 52 69 64 65 20 63 79 6d 62 61 6c 00 ....Ride cymbal. 00000060: ff 05 0a 43 6c 6f 73 65 64 20 68 61 74 00 99 2a ...Closed hat..* 00000070: 40 00 ff 05 08 4f 70 65 6e 20 68 61 74 00 ff 05 @....Open hat... 00000080: 08 48 69 67 68 20 74 6f 6d 00 ff 05 07 4d 69 64 .High tom....Mid 00000090: 20 74 6f 6d 00 ff 05 07 4c 6f 77 20 74 6f 6d 00 tom....Low tom. 000000a0: ff 05 0a 53 6e 61 72 65 20 64 72 75 6d 00 ff 05 ...Snare drum... 000000b0: 09 42 61 73 73 20 64 72 75 6d 00 24 40 00 ff 05 .Bass drum.$@... 000000c0: 15 28 63 29 20 31 39 39 31 20 6a 6f 65 6c 20 53 .(c) 1991 joel S 000000d0: 61 6d 70 73 6f 6e 00 a0 3c 40 3c 99 2a 00 00 24 ampson..<@<.*..$ 000000e0: 00 81 34 2a 6e 00 26 40 3c 2a 00 00 26 00 3c 24 ..4*n.&@<*..&.<$ 000000f0: 40 3c 24 00 3c 2a 40 00 24 40 3c 2a 00 00 24 00 @<$.<*@.$@<*..$. 00000100: 81 34 2a 40 00 26 40 3c 2a 00 00 26 00 81 34 2a .4*@.&@<*..&..4* 00000110: 40 00 24 40 3c 2a 00 00 24 00 81 34 2a 6e 00 26 @.$@<*..$..4*n.& 00000120: 40 3c 2a 00 00 26 00 3c 24 40 3c 24 00 3c 2a 40 @<*..&.<$@<$.<*@ 00000130: 00 24 40 3c 2a 00 00 24 00 81 34 2a 40 00 26 40 .$@<*..$..4*@.&@ 00000140: 3c 2a 00 00 26 00 81 34 2a 40 00 24 40 3c 2a 00 <*..&..4*@.$@<*. 00000150: 00 24 00 81 34 2a 40 00 26 40 3c 2a 00 00 26 00 .$..4*@.&@<*..&. 00000160: 3c 24 40 3c 24 00 3c 2a 40 00 24 40 3c 2a 00 00 <$@<$.<*@.$@<*.. 00000170: 24 00 3c 24 40 3c 24 00 3c 2a 40 00 26 40 3c 2a $.<$@<$.<*@.&@<* 00000180: 00 00 26 00 3c 24 40 3c 24 00 3c 2a 40 00 24 40 ..&.<$@<$.<*@.$@ 00000190: 3c 2a 00 00 24 00 81 34 2a 40 00 26 40 3c 2a 00 <*..$..4*@.&@<*. 000001a0: 00 26 00 3c 24 40 3c 24 00 3c 2a 40 00 24 40 3c .&.<$@<$.<*@.$@< 000001b0: 2a 00 00 24 00 3c 24 40 3c 24 00 3c 2a 40 00 26 *..$.<$@<$.<*@.& 000001c0: 40 3c 2a 00 00 26 00 3c 24 40 3c 24 00 3c 2a 7f @<*..&.<$@<$.<*. 000001d0: 00 24 6e 3c 2a 00 00 24 00 3c 26 40 3c 26 00 00 .$n<*..$.<&@<&.. 000001e0: 32 40 3c 32 00 00 32 40 3c 32 00 3c 26 40 3c 26 2@<2..2@<2.<&@<& 000001f0: 00 00 32 40 3c 32 00 00 32 40 3c 32 00 3c 26 40 ..2@<2..2@<2.<&@ 00000200: 3c 26 00 00 32 40 3c 32 00 00 26 6e 3c 26 00 00 <&..2@<2..&n<&.. 00000210: 26 40 3c 26 40 01 26 00 3b 26 6e 01 26 00 3b 26 &@<&@.&.;&n.&.;& 00000220: 00 00 2a 7f 00 24 6e 3c 2a 00 00 24 00 3c 26 40 ..*..$n<*..$.<&@ 00000230: 3c 26 00 00 32 40 3c 32 00 00 32 40 3c 32 00 3c <&..2@<2..2@<2.< 00000240: 26 40 3c 26 00 00 32 40 3c 32 00 00 32 40 3c 32 &@<&..2@<2..2@<2 00000250: 00 3c 26 40 3c 26 00 00 32 40 3c 32 00 00 26 6e .<&@<&..2@<2..&n 00000260: 3c 26 00 00 26 40 3c 26 40 01 26 00 3b 26 6e 01 <&..&@<&@.&.;&n. 00000270: 26 00 3b 26 00 00 2a 7f 00 24 40 3c 2a 00 00 24 &.;&..*..$@<*..$ 00000280: 00 00 2a 40 3c 2a 7f 00 24 40 01 2a 00 3b 24 00 ..*@<*..$@.*.;$. 00000290: 00 2a 40 01 2a 00 3b 2a 7f 00 26 40 01 2a 00 3b .*@.*.;*..&@.*.; 000002a0: 26 00 00 2a 40 00 24 40 01 2a 00 3b 24 00 00 2a &..*@.$@.*.;$..* 000002b0: 7f 01 2a 00 3b 2a 40 01 2a 00 3b 2a 7f 00 24 40 ..*.;*@.*.;*..$@ 000002c0: 01 2a 00 3b 24 00 00 2a 40 01 2a 00 3b 2a 7f 01 .*.;$..*@.*.;*.. 000002d0: 2a 00 3b 2a 40 01 2a 00 3b 2a 7f 00 26 40 01 2a *.;*@.*.;*..&@.* 000002e0: 00 3b 26 00 00 2a 40 01 2a 00 3b 2a 7f 01 2a 00 .;&..*@.*.;*..*. 000002f0: 3b 2a 00 00 2c 40 00 24 40 3c 2c 00 00 24 00 00 ;*..,@.$@<,..$.. 00000300: 2a 7f 00 24 40 3c 2a 00 00 24 00 00 2a 40 3c 2a *..$@<*..$..*@<* 00000310: 7f 00 24 40 01 2a 00 3b 24 00 00 2a 40 01 2a 00 ..$@.*.;$..*@.*. 00000320: 3b 2a 7f 00 26 40 01 2a 00 3b 26 00 00 2a 40 00 ;*..&@.*.;&..*@. 00000330: 24 40 01 2a 00 3b 24 00 00 2a 7f 01 2a 00 3b 2a $@.*.;$..*..*.;* 00000340: 40 01 2a 00 3b 2a 7f 00 24 40 01 2a 00 3b 24 00 @.*.;*..$@.*.;$. 00000350: 00 2a 40 01 2a 00 3b 2a 7f 01 2a 00 3b 2a 40 01 .*@.*.;*..*.;*@. 00000360: 2a 00 3b 2a 7f 00 26 40 01 2a 00 3b 26 00 00 2a *.;*..&@.*.;&..* 00000370: 40 01 2a 00 3b 2a 7f 01 2a 00 3b 2a 00 00 2c 40 @.*.;*..*.;*..,@ 00000380: 00 24 40 3c 2c 00 00 2a 7f 00 24 40 01 24 00 3b .$@<,..*..$@.$.; 00000390: 2a 00 00 24 00 00 2a 40 3c 2a 7f 00 24 40 01 2a *..$..*@<*..$@.* 000003a0: 00 3b 24 00 00 2a 40 01 2a 00 3b 2a 7f 00 26 40 .;$..*@.*.;*..&@ 000003b0: 01 2a 00 3b 26 00 00 2a 40 00 24 40 01 2a 00 3b .*.;&..*@.$@.*.; 000003c0: 24 00 00 2a 7f 01 2a 00 3b 2a 00 00 2c 6e 3c 2c $..*..*.;*..,n<, 000003d0: 00 00 2a 7f 00 24 40 3c 2a 00 00 24 00 00 2a 40 ..*..$@<*..$..*@ 000003e0: 3c 2a 7f 01 2a 00 3b 2a 40 01 2a 00 3b 2a 7f 00 <*..*.;*@.*.;*.. 000003f0: 26 40 01 2a 00 3b 26 00 00 2a 40 01 2a 00 3b 2a &@.*.;&..*@.*.;* 00000400: 7f 00 26 40 01 2a 00 3b 2a 00 00 26 00 00 2c 6e ..&@.*.;*..&..,n 00000410: 00 24 58 3c 2c 00 00 24 00 00 2a 7f 00 24 40 3c .$X<,..$..*..$@< 00000420: 2a 00 00 24 00 00 2a 40 3c 2a 7f 00 24 40 01 2a *..$..*@<*..$@.* 00000430: 00 3b 24 00 00 2a 40 01 2a 00 3b 2a 7f 00 26 40 .;$..*@.*.;*..&@ 00000440: 01 2a 00 3b 26 00 00 2a 40 00 24 40 01 2a 00 3b .*.;&..*@.$@.*.; 00000450: 24 00 00 2a 7f 01 2a 00 3b 2a 00 00 2c 6e 3c 2c $..*..*.;*..,n<, 00000460: 00 00 2a 7f 00 24 40 3c 2a 00 00 24 00 00 2a 40 ..*..$@<*..$..*@ 00000470: 3c 2a 7f 01 2a 00 3b 2a 40 01 2a 00 3b 2a 7f 00 <*..*.;*@.*.;*.. 00000480: 26 40 01 2a 00 3b 26 00 00 2a 40 01 2a 00 3b 2a &@.*.;&..*@.*.;* 00000490: 7f 00 26 40 01 2a 00 3b 2a 00 00 26 00 00 2c 6e ..&@.*.;*..&..,n 000004a0: 00 24 58 3c 2c 00 00 26 40 00 24 58 01 24 00 3b .$X<,..&@.$X.$.; 000004b0: 26 00 00 24 00 00 26 40 3c 26 40 00 24 40 01 26 &..$..&@<&@.$@.& 000004c0: 00 3b 24 00 00 26 40 01 26 00 3b 26 00 00 2f 40 .;$..&@.&.;&../@ 000004d0: 3c 2f 00 00 2f 40 00 24 40 3c 24 00 00 2f 40 01 0 Length (ticks): 768 Events;triggers: 28; 2 Transposable: true Key and scale: 0; 0 Color: 1 2 triggers: trigger: 0 to 767 offset 768 transpose by 0 trigger: 1728 to 2495 offset 768 transpose by 0 Sequence #1 'Snap 2' Channel: 128 Beats: 4/4 Busses: 0-->0 Length (ticks): 1536 Events;triggers: 64; 2 Transposable: false Key and scale: 0; 0 Color: 2 2 triggers: trigger: 0 to 1535 offset 1536 transpose by 0 trigger: 3264 to 4799 offset 1536 transpose by 0 Sequence #2 'Unsnap 1' Channel: 0 Beats: 4/4 Busses: 0-->0 Length (ticks): 768 Events;triggers: 28; 2 Transposable: true Key and scale: 0; 0 Color: 1 2 triggers: trigger: 528 to 1295 offset 768 transpose by 0 trigger: 2368 to 3135 offset 768 transpose by 0 Sequence #3 'Unsnap 2' Channel: 128 Beats: 4/4 Busses: 0-->0 Length (ticks): 1536 Events;triggers: 64; 2 Transposable: false Key and scale: 0; 0 Color: 2 2 triggers: trigger: 288 to 1823 offset 1536 transpose by 0 trigger: 3344 to 4879 offset 1536 transpose by 0 Sequence #4 'Offset Test' Channel: 0 Beats: 4/4 Busses: 0-->0 Length (ticks): 768 Events;triggers: 20; 8 Transposable: true Key and scale: 0; 0 Color: 17 8 triggers: trigger: 0 to 767 offset 768 transpose by 0 trigger: 768 to 1535 offset 768 transpose by 0 trigger: 1536 to 2303 offset 768 transpose by 0 trigger: 2304 to 3071 offset 768 transpose by 0 trigger: 3072 to 3839 offset 768 transpose by 0 trigger: 3840 to 4607 offset 768 transpose by 0 trigger: 4608 to 5375 offset 768 transpose by 0 trigger: 6416 to 8191 offset 768 transpose by 0 Start of SeqSpecs: 0xFF 0x7F 24240010 (MIDI control) = 0 0xFF 0x7F 24240003 (Track clocking) = 0 Screen-set Notes: 0xFF 0x7F 24240005 (Set notes) = 1 Set #0: 'New' 0xFF 0x7F 24240007 (Main beats/minute) = 200 BPM: 200 Mute Groups: 32 of size 32 0xFF 0x7F 24240009 (Song mute group data) = 32 Mute group # 0: 00000000 00000000 00000000 00000000 "Group 0" Mute group # 1: 00000000 00000000 00000000 00000000 "Group 1" Mute group # 2: 00000000 00000000 00000000 00000000 "Group 2" Mute group # 3: 00000000 00000000 00000000 00000000 "Group 3" Mute group # 4: 00000000 00000000 00000000 00000000 "Group 4" Mute group # 5: 00000000 00000000 00000000 00000000 "Group 5" Mute group # 6: 00000000 00000000 00000000 00000000 "Group 6" Mute group # 7: 00000000 00000000 00000000 00000000 "Group 7" Mute group # 8: 00000000 00000000 00000000 00000000 "Group 8" Mute group # 9: 00000000 00000000 00000000 00000000 "Group 9" Mute group #10: 00000000 00000000 00000000 00000000 "Group 10" Mute group #11: 00000000 00000000 00000000 00000000 "Group 11" Mute group #12: 00000000 00000000 00000000 00000000 "Group 12" Mute group #13: 00000000 00000000 00000000 00000000 "Group 13" Mute group #14: 00000000 00000000 00000000 00000000 "Group 14" Mute group #15: 00000000 00000000 00000000 00000000 "Group 15" Mute group #16: 00000000 00000000 00000000 00000000 "Group 16" Mute group #17: 00000000 00000000 00000000 00000000 "Group 17" Mute group #18: 00000000 00000000 00000000 00000000 "Group 18" Mute group #19: 00000000 00000000 00000000 00000000 "Group 19" Mute group #20: 00000000 00000000 00000000 00000000 "Group 20" Mute group #21: 00000000 00000000 00000000 00000000 "Group 21" Mute group #22: 00000000 00000000 00000000 00000000 "Group 22" Mute group #23: 00000000 00000000 00000000 00000000 "Group 23" Mute group #24: 00000000 00000000 00000000 00000000 "Group 24" Mute group #25: 00000000 00000000 00000000 00000000 "Group 25" Mute group #26: 00000000 00000000 00000000 00000000 "Group 26" Mute group #27: 00000000 00000000 00000000 00000000 "Group 27" Mute group #28: 00000000 00000000 00000000 00000000 "Group 28" Mute group #29: 00000000 00000000 00000000 00000000 "Group 29" Mute group #30: 00000000 00000000 00000000 00000000 "Group 30" Mute group #31: 00000000 00000000 00000000 00000000 "Group 31" Global key, scale, and background sequence: 0xFF 0x7F 24240011 (Track key) = 0 0xFF 0x7F 24240012 (Track scale) = 0 0xFF 0x7F 24240013 (Track background sequence) = 2048 Global beats, beat width, and tempo track: 0xFF 0x7F 24240015 (Perfedit beats/measure) = 4 0xFF 0x7F 24240016 (Perfedit beat-width) = 4 0xFF 0x7F 2424001a (Alternate tempo track number) = 0 ================================================ FILE: contrib/midnam/README ================================================ MIDI Name Format Notes Chris Ahlstrom 2021-12-29 to 2021-12-29 This README provides notes on the Pro Tools MIDNAM format. Header: . . . The file starts with: Roland MT-32 The ChannelNameSet: . . . . . . The 'usr' file: Instead of devices, Seq66 has a list of MIDI buss definitions and a list of MIDI instrument definitions. User-Instrument data: Number (implied by section header) Name of instrument Number of MIDI controller entries A list of controller names for each of those entries. # vim: sw=3 ts=3 wm=8 et ft=sh ================================================ FILE: contrib/midnam/Roland_MT-32.midnam ================================================ Mark of the Unicorn - converted from FreeMIDI (Adapted by Harrison Consoles) Roland MT-32 ================================================ FILE: contrib/mkinstalldirs-1.10 ================================================ #! /bin/sh # mkinstalldirs --- make directory hierarchy scriptversion=2006-05-11.19 # Original author: Noah Friedman # Created: 1993-05-16 # Public domain. # # This file is maintained in Automake, please report # bugs to or send patches to # . nl=' ' IFS=" "" $nl" errstatus=0 dirmode= usage="\ Usage: mkinstalldirs [-h] [--help] [--version] [-m MODE] DIR ... Create each directory DIR (with mode MODE, if specified), including all leading file name components. Report bugs to ." # process command line arguments while test $# -gt 0 ; do case $1 in -h | --help | --h*) # -h for help echo "$usage" exit $? ;; -m) # -m PERM arg shift test $# -eq 0 && { echo "$usage" 1>&2; exit 1; } dirmode=$1 shift ;; --version) echo "$0 $scriptversion" exit $? ;; --) # stop option processing shift break ;; -*) # unknown option echo "$usage" 1>&2 exit 1 ;; *) # first non-opt arg break ;; esac done for file do if test -d "$file"; then shift else break fi done case $# in 0) exit 0 ;; esac # Solaris 8's mkdir -p isn't thread-safe. If you mkdir -p a/b and # mkdir -p a/c at the same time, both will detect that a is missing, # one will create a, then the other will try to create a and die with # a "File exists" error. This is a problem when calling mkinstalldirs # from a parallel make. We use --version in the probe to restrict # ourselves to GNU mkdir, which is thread-safe. case $dirmode in '') if mkdir -p --version . >/dev/null 2>&1 && test ! -d ./--version; then echo "mkdir -p -- $*" exec mkdir -p -- "$@" else # On NextStep and OpenStep, the `mkdir' command does not # recognize any option. It will interpret all options as # directories to create, and then abort because `.' already # exists. test -d ./-p && rmdir ./-p test -d ./--version && rmdir ./--version fi ;; *) if mkdir -m "$dirmode" -p --version . >/dev/null 2>&1 && test ! -d ./--version; then echo "mkdir -m $dirmode -p -- $*" exec mkdir -m "$dirmode" -p -- "$@" else # Clean up after NextStep and OpenStep mkdir. for d in ./-m ./-p ./--version "./$dirmode"; do test -d $d && rmdir $d done fi ;; esac for file do case $file in /*) pathcomp=/ ;; *) pathcomp= ;; esac oIFS=$IFS IFS=/ set fnord $file shift IFS=$oIFS for d do test "x$d" = x && continue pathcomp=$pathcomp$d case $pathcomp in -*) pathcomp=./$pathcomp ;; esac if test ! -d "$pathcomp"; then echo "mkdir $pathcomp" mkdir "$pathcomp" || lasterr=$? if test ! -d "$pathcomp"; then errstatus=$lasterr else if test ! -z "$dirmode"; then echo "chmod $dirmode $pathcomp" lasterr= chmod "$dirmode" "$pathcomp" || lasterr=$? if test ! -z "$lasterr"; then errstatus=$lasterr fi fi fi fi pathcomp=$pathcomp/ done done exit $errstatus # Local Variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-end: "$" # End: ================================================ FILE: contrib/non/NSM_API.txt ================================================ http://non.tuxfamily.org/nsm/API.html ****** Non Session Management API ****** Jonathan Moore Liles Version 1.2 ****** Table Of Contents ****** * 1._Non_Session_Management_API o 1.1._Client_Behavior_Under_Session_Management # 1.1.1._File_Menu # 1.1.1.1._New # 1.1.1.2._Open # 1.1.1.3._Save # 1.1.1.4._Save_As # 1.1.1.5._Close_(as_distinguished_from_Quit_or_Exit) # 1.1.1.6._Quit_or_Exit # 1.1.2._Data_Storage # 1.1.2.1._Internal_Files # 1.1.2.2._External_Files o 1.2._NSM_OSC_Protocol # 1.2.1._Establishing_a_Connection # 1.2.1.1._Announce # 1.2.1.2._Response # 1.2.2._Server_to_Client_Control_Messages # 1.2.2.1._Quit # 1.2.2.2._Open # 1.2.2.2.1._Response # 1.2.2.3._Save # 1.2.2.3.1._Response # 1.2.3._Server_to_Client_Informational_Messages # 1.2.3.1._Session_is_Loaded # 1.2.3.2._Show_Optional_Gui # 1.2.4._Client_to_Server_Informational_Messages # 1.2.4.1._Optional_GUI # 1.2.4.2._Progress # 1.2.4.3._Dirtiness # 1.2.4.4._Status_Messages # 1.2.5._Error_Code_Definitions # 1.2.6._Client_to_Server_Control # 1.2.7._Server_Control_API # 1.2.7.1._Client_to_Client_Communication ****** 1. Non Session Management API ****** The Non Session Management API is used by the various components of the Non audio production suite to allow any number of independent programs to be managed together as part of a logical session (i.e. a song). Thus, operations such as loading and saving are synchronized. The API comprises a simple Open Sound Control (OSC) based protocol, along with some behavioral guidelines, which can easily be implemented by various applications. The Non project contains an program called nsmd which is an implementation of the server side of the NSM API. nsmd is controlled by the non-session-manager GUI. However, the same server-side API can also be implemented by other session managers (such as LADISH), although consistency and robustness will likely suffer if non-NSM compliant clients are allowed to participate in a session. The only dependency for client implementations liblo (the OSC library), which several Linux audio applications already link to or plan to link to in the future. The aim of this project is to thoroughly define the behavior required of clients. This is an area where other attempts at session management (LASH and JACK-Session) have failed. Often the difficulty with these systems has been not in implementing support for them, but in attempting to interpret the confusing, ambiguous, or ill-conceived API documentation. For these reasons and more all previous attempts at Linux audio session management protocols are considered harmful. You WILL see some unambiguous and emphatic language in this document. For the good of the user, these rules are meant to be followed and are non-negotiable. If an application does not conform to this specification it should be considered broken. Consistency across applications under session management is very important for a good user experience. ***** 1.1. Client Behavior Under Session Management ***** Most graphical applications make available to the user a common set of file operations, typically presented under a File or Project menu. These are: New, Open, Save, Save As, Close and Quit or Exit. The following sub-sections describe how these options should behave when the application is part of an NSM session. These rules only apply when session management is active (that is, after the announce handshake described in the 1.2._NSM_OSC_Protocol section). In order to provide a consistent and predictable user experience, it is critically important for applications to adhere to these guidelines. **** 1.1.1. File Menu **** *** 1.1.1.1. New *** This option may empty/reset the current file or project (possibly after user confirmation). UNDER NO CIRCUMSTANCES should it allow the user to create a new project/file in another location. *** 1.1.1.2. Open *** This option MUST be disabled. The application may, however, elect to implement an option called 'Import into Session', creates a copy of a file/project which is then saved at the session path provided by NSM. *** 1.1.1.3. Save *** This option should behave as normal, saving the current file/project as established by the NSM open message. UNDER NO CIRCUMSTANCES should this option present the user with a choice of where to save the file. *** 1.1.1.4. Save As *** This option MUST be disabled. The application may, however, elect to implement an option called 'Export from Session', which creates a copy of the current file/project which is then saved in a user-specified location outside of the session path provided by NSM. *** 1.1.1.5. Close (as distinguished from Quit or Exit) *** This option MUST be disabled unless its meaning is to disconnect the application from session management. *** 1.1.1.6. Quit or Exit *** This option may behave as normal (possibly asking the user to confirm exiting). **** 1.1.2. Data Storage **** *** 1.1.2.1. Internal Files *** All project specific data created by a client MUST be stored in the per-client storage area provided by NSM. This includes all recorded audio and MIDI files, snapshots, etc. Only global configuration items, exports, and renders of the project may be stored elsewhere (wherever the user specifies). *** 1.1.2.2. External Files *** Files required by the project but external to it (typically read-only data such as audio samples) SHOULD be referenced by creating a symbolic link within the assigned session area, and then referring to the symlink. This allows sessions to be archived and transported simply (e.g. with "tar -h") by tools that have no knowledge of the project formats of the various clients in the session. The symlinks thus created should, at the very least, be named after the files they refer to (some unique component may be required to prevent collisions) samples/drumbeat-1.wav samples/drumbeat-2.wav ***** 1.2. NSM OSC Protocol ***** All message parameters are REQUIRED. All messages MUST be sent from the same socket as the announce message, using the lo_send_from method of liblo or its equivalent, as the server uses the return addresses to distinguish between clients. Clients MUST create thier OSC servers using the same protocol (UDP,TCP) as found in NSM_URL. liblo is lacking a robust TCP implementation at the time of writing, but in the future it may be useful. **** 1.2.1. Establishing a Connection **** *** 1.2.1.1. Announce *** At launch, the client MUST check the environment for the value of NSM_URL. If present, the client MUST send the following message to the provided address as soon as it is ready to respond to the /nsm/client/open event: /nsm/server/announce s:application_name s:capabilities s:executable_name i: api_version_major i:api_version_minor i:pid If NSM_URL is undefined, invalid, or unreachable, then the client should proceed assuming that session management is unavailable. api_version_major and api_version_minor must be the two parts of the version number of the NSM API as defined by this document. Note that if the application intends to register JACK clients, application_name MUST be the same as the name that would normally be passed to jack_client_open. For example, Non-Mixer sends "Non-Mixer" as its application_name. Applications MUST NOT register their JACK clients until receiving an open message; the open message will provide a unique client name prefix suitable for passing to JACK. This is probably the most complex requirement of the NSM API, but it isn't difficult to implement, especially if the application simply wishes to delay its initialization process breifly while awaiting the announce reply and subsequent open message. capabilities MUST be a string containing a colon separated list of the special capabilities the client possesses. e.g. :dirty:switch:progress: executable_name MUST be the executable name that the program was launched with. For C programs, this is simply the value of argv[0]. Note that hardcoding the name of the program here is not the same as using, as the user may have launched the program from a script with a different name using exec, or have created a symlink to the program. Getting the correct value in scripting languages like Python can be more challenging. ________________Fig._1.1._Available_Client_Capabilities_________________ |Name________|Description________________________________________________| |switch |client is capable of responding to multiple `open` messages| |____________|without_restarting_________________________________________| |dirty_______|client_knows_when_it_has_unsaved_changes___________________| |progress |client can send progress updates during time-consuming | |____________|operations_________________________________________________| |message_____|client_can_send_textual_status_updates_____________________| |optional-gui|client_has_an_optional_GUI_________________________________| *** 1.2.1.2. Response *** The server will respond to the client's announce message with the following message: /reply "/nsm/server/announce" s:message s:name_of_session_manager s:capabilities message is a welcome message. The value of name_of_session_manager will depend on the implementation of the NSM server. It might say "Non Session Manager", or it might say "LADISH". This is for display to the user. capabilities will be a string containing a colon separated list of special server capabilities. Presently, the server capabilities are: __________________Fig._1.2._Available_Server_Capabilities__________________ |Name__________|Description_________________________________________________| |server_control|client-to-server_control____________________________________| |broadcast_____|server_responds_to_/nsm/server/broadcast_message____________| | |server responds to optional-gui messages--if this capability| |optional-gui |is not present then clients with optional-guis MUST always | |______________|keep_them_visible___________________________________________| A client should not consider itself to be under session management until it receives this response. For example, the Non applications activate their "SM" blinkers at this time. If there is an error, a reply of the following form will be sent to the client: /error "/nsm/server/announce" i:error_code s:error_message The following table defines possible values of error_code: ____________Fig._1.3._Response_codes_____________ |Code________________|Meaning_____________________| |ERR_GENERAL_________|General_Error_______________| |ERR_INCOMPATIBLE_API|Incompatible_API_version____| |ERR_BLACKLISTED_____|Client_has_been_blacklisted.| **** 1.2.2. Server to Client Control Messages **** Compliant clients MUST accept the client control messages described in this section. All client control messages REQUIRE a response. Responses MUST be delivered back to the sender (NSM) from the same socket used by the client in its announce message (by using lo_send_from) AFTER the action has been completed or if an error is encountered. The required response is described in the subsection for each message. If there is an error and the action cannot be completed, then error_code MUST be set to a valid error code (see 1.2.5._Error_Code_Definitions) and message to a string describing the problem (suitable for display to the user). The reply can take one of the following two forms, where path MUST be the path of the message being replied to (e.g. "nsm/client/save": /reply s:path s:message /error s:path i:error_code s:message *** 1.2.2.1. Quit *** There is no message for this. Clients will receive the Unix SIGTERM signal and MUST close cleanly IMMEDIATELY, without displaying any kind of dialog to the user and regardless of whether or not unsaved changes would be lost. When a session is closed the application will receive this signal soon after having responded to a save message. *** 1.2.2.2. Open *** /nsm/client/open s:path_to_instance_specific_project s:display_name s:client_id path_to_instance_specific_project is a path name assigned to the client for storing its project data. The client may append to the path, creating a sub-directory, e.g. '/song.foo' or simply append the client's native file extension (e.g. '.non' or '.XML'). The same transformation MUST be applied to the name when opening an existing project, as NSM will only provide the instance specific part of the path. If a project exists at the path, the client MUST immediately open it. If a project does not exist at the path, then the client MUST immediately create and open a new one at the specified path or, for clients which hold all their state in memory, store the path for later use when responding to the save message. No file or directory will be created at the specified path by the server. It is up to the client to create what it needs. For clients which HAVE NOT specified the :switch: capability, the open message will only be delivered once, immediately following the announce response. For clients which HAVE specified the :switch: capability, the client MUST immediately switch to the specified project or create a new one if it doesn't exist. Clients which are incapable of switching projects or are prone to crashing upon switching MUST NOT include :switch: in their capability string. If the user the is allowed to run two or more instances of the application simultaneously (that is to say, there is no technical limitation preventing them from doing so, even if it doesn't make sense to the author), then such an application MUST PRE-PEND the provided client_id string to any names it registers with common subsystems (e.g. JACK client names). This ensures that multiple instances of the same application can be restored in any order without scrambling the JACK connections or causing other conflicts. The provided client_id will be a concatenation of the value of application_name sent by the client in its announce message and a unique identifier. Therefore, applications which create single JACK clients can use the value of client_id directly as their JACK client name. Applications which register multiple JACK clients (e.g. Non-Mixer) MUST PRE-PEND client_id value to the client names they register with JACK and the application determined part MUST be unique for that (JACK) client. For example, a suitable JACK client name would be: $CLIENT_ID/track-1 Note that this means that the application MUST NOT register with JACK (or any other subsystem requiring unique names) until it receives an open message from NSM. Likewise, applications with the :switch: capability should close their JACK clients and re-create them with using the new client_id. Re-registering is necessary because the JACK API does currently support renaming existing clients, although this is a sorely needed addition. A response is REQUIRED as soon as the open operation has been completed. Ongoing progress may be indicated by sending messages to /nsm/client/progress. ** 1.2.2.2.1. Response ** The client MUST respond to the 'open' message with: /reply "/nsm/client/open" s:message Or /error "/nsm/client/open" i:error_code s:message ______________________Fig._1.4._Response_Codes______________________ |Code_______________|Meaning_________________________________________| |ERR________________|General_Error___________________________________| |ERR_BAD_PROJECT____|An_existing_project_file_was_found_to_be_corrupt| |ERR_CREATE_FAILED__|A_new_project_could_not_be_created______________| |ERR_UNSAVED_CHANGES|Unsaved_changes_would_be_lost___________________| |ERR_NOT_NOW________|Operation_cannot_be_completed_at_this_time______| *** 1.2.2.3. Save *** /nsm/client/save This message will only be delivered after a previous open message, and may be sent any number of times within the course of a session (including zero, if the user aborts the session). ** 1.2.2.3.1. Response ** The client MUST respond to the 'save' message with: /reply "/nsm/client/save" s:message Or /error "/nsm/client/save" i:error_code s:message _________________Fig._1.5._Response_Codes_________________ |Code___________|Meaning___________________________________| |ERR____________|General_Error_____________________________| |ERR_SAVE_FAILED|Project_could_not_be_saved________________| |ERR_NOT_NOW____|Operation_cannot_be_completed_at_this_time| **** 1.2.3. Server to Client Informational Messages **** *** 1.2.3.1. Session is Loaded *** Accepting this message is optional. The intent is to signal to clients which may have some interdependence (say, peer to peer OSC connections) that the session is fully loaded and all their peers are available. Most clients will not need to act on this message. This message has no meaning when a session is being built or run--only when it is initially loaded. Clients who intend to act on this message MUST not do so by delaying initialization waiting for it. /nsm/client/session_is_loaded This message does not require a response. *** 1.2.3.2. Show Optional Gui *** If the client has specified the optional-gui capability, then it may receive this message from the server when the user wishes to change the visibility state of the GUI. It doesn't matter if the optional GUI is integrated with the program or if it is a separate program (as is the case with SooperLooper). When the GUI is hidden, there should be no window mapped and if the GUI is a separate program, it should be killed. /nsm/client/show_optional_gui /nsm/client/hide_optional_gui No response is message is required. **** 1.2.4. Client to Server Informational Messages **** These are optional messages which a client can send to the NSM server to inform it about the client's status. The client should not expect any reply to these messages. If a client intends to send a message described in this section, then it MUST add the appropriate value to its capabilities string when composing the announce message. *** 1.2.4.1. Optional GUI *** If the client has specified the optional-gui capability, then it MUST send this message whenever the state of visibility of the optional GUI has changed. It also MUST send this message after it's announce message to indicate the initial visibility state of the optional GUI. It is the responsibility of the client to remember the visibility state of its GUI across session loads. /nsm/client/gui_is_hidden /nsm/client/gui_is_shown No response will be delivered. *** 1.2.4.2. Progress *** /nsm/client/progress f:progress For potentially time-consuming operations, such as save and open, progress updates may be indicated throughout the duration by sending a floating point value between 0.0 and 1.0, 1.0 indicating completion, to the NSM server. The server will not send a response to these messages, but will relay the information to the user. Note that even when using the progress feature, the final response to the save or open message is still REQUIRED. Clients which intend to send progress messages should include :progress: in their announce capability string. *** 1.2.4.3. Dirtiness *** /nsm/client/is_dirty /nsm/client/is_clean Some clients may be able to inform the server when they have unsaved changes pending. Such clients may optionally send is_dirty and is_clean messages. Clients which have this capability should include :dirty: in their announce capability string. *** 1.2.4.4. Status Messages *** /nsm/client/message i:priority s:message Clients may send miscellaneous status updates to the server for possible display to the user. This may simply be chatter that is normally written to the console. priority should be a number from 0 to 3, 3 being the most important. Clients which have this capability should include :message: in their announce capability string. **** 1.2.5. Error Code Definitions **** _Fig._1.6._Error_Code_Definitions_ |Symbolic_Name_______|Integer_Value| |ERR_GENERAL_________|-1___________| |ERR_INCOMPATIBLE_API|-2___________| |ERR_BLACKLISTED_____|-3___________| |ERR_LAUNCH_FAILED___|-4___________| |ERR_NO_SUCH_FILE____|-5___________| |ERR_NO_SESSION_OPEN_|-6___________| |ERR_UNSAVED_CHANGES_|-7___________| |ERR_NOT_NOW_________|-8___________| |ERR_BAD_PROJECT_____|-9___________| |ERR_CREATE_FAILED___|-10__________| **** 1.2.6. Client to Server Control **** If the server publishes the :server_control: capability, then clients can also initiate action by the server. For example, a client might implement a 'Save All' option which sends a /nsm/server/save message to the server, rather than requiring the user to switch to the session management interface to effect the save. **** 1.2.7. Server Control API **** The session manager not only manages clients via OSC, but it is itself controlled via OSC messages. The server responds to the following messages. All of the following messages will be responded to, at the sender's address, with one of the two following messages: /reply s:path s:message /error s:path i:error_code s:message The first parameter of the reply is the path to the message being replied to. The /error reply includes an integer error code (non-zero indicates error). message will be a description of the error. The possible errors are: _______________Fig._1.7._Responses_______________ |Code_______________|Meaning______________________| |ERR_GENERAL________|General_Error________________| |ERR_LAUNCH_FAILED__|Launch_failed________________| |ERR_NO_SUCH_FILE___|No_such_file_________________| |ERR_NO_SESSION_____|No_session_is_open___________| |ERR_UNSAVED_CHANGES|Unsaved_changes_would_be_lost| /nsm/server/add s:executable_name Adds a client to the current session. /nsm/server/save Saves the current session. /nsm/server/open s:project_name Saves the current session and loads a new session. /nsm/server/new s:project_name Saves the current session and creates a new session. /nsm/server/duplicate s:new_project Saves and closes the current session, makes a copy, and opens it. /nsm/server/close Saves and closes the current session. /nsm/server/abort Closes the current session WITHOUT SAVING /nsm/server/quit Saves and closes the current session and terminates the server. /nsm/server/list Lists available projects. One /reply message will be sent for each existing project. *** 1.2.7.1. Client to Client Communication *** If the server includes :broadcast: in its capability string, then clients may send broadcast messages to each other through the NSM server. Clients may send messages to the server at the path /nsm/server/broadcast. The format of this message is as follows: /nsm/server/broadcast s:path [arguments...] The message will then be relayed to all clients in the session at the path path (with the arguments shifted by one). For example the message: /nsm/server/broadcast /tempomap/update "0,120,4/4:12351234,240,4/4" Would broadcast the following message to all clients in the session (except for the sender), some of which might respond to the message by updating their own tempo maps. /tempomap/update "0,120,4/4:12351234,240,4/4" The Non programs use this feature to establish peer to peer OSC communication by symbolic names (client IDs) without having to remember the OSC URLs of peers across sessions. ---- End of File ================================================ FILE: contrib/non/messagelist.txt ================================================ Qta cli srv prox x x x x /error x /osc/ping x x x x /reply x x x /nsm/client/gui_is_hidden x x x /nsm/client/gui_is_shown x x x /nsm/client/hide_optional_gui x x x /nsm/client/is_clean x x x /nsm/client/is_dirty x n /nsm/client/label x x x /nsm/client/message x x x x /nsm/client/open x x /nsm/client/progress x x x /nsm/client/save x x x /nsm/client/session_is_loaded x x x /nsm/client/show_optional_gui x /nsm/gui/client/dirty x /nsm/gui/client/has_optional_gui x x /nsm/gui/client/label x /nsm/gui/client/message x /nsm/gui/client/new x /nsm/gui/client/progress x /nsm/gui/client/remove x /nsm/gui/client/resume x /nsm/gui/client/save x /nsm/gui/client/status x /nsm/gui/client/stop x /nsm/gui/client/switch /nsm/gui/client/visible (BAD?) x /nsm/gui/client/gui_visible (NEW) x /nsm/gui/gui_announce x x /nsm/gui/server_announce (NEW) x /nsm/gui/server/message x /nsm/gui/session/name x /nsm/gui/session/root x /nsm/gui/session/session x /nsm/proxy/arguments x /nsm/proxy/client_error x /nsm/proxy/config_file x /nsm/proxy/executable x /nsm/proxy/kill x /nsm/proxy/label x /nsm/proxy/save_signal x /nsm/proxy/start x /nsm/proxy/stop_signal x /nsm/proxy/update x /nsm/server/abort x /nsm/server/add x x x /nsm/server/announce x x /nsm/server/broadcast x /nsm/server/close x /nsm/server/duplicate x /nsm/server/list x /nsm/server/new x /nsm/server/open x /nsm/server/quit x /nsm/server/save /nsm/session/list /nsm/session/name Main features of main messages: 1. After connection and when the client can respond to /nsm/client/open, send /nsm/server/annouce to provide client name, capabilities, PID, etc. 2. Server responds with /reply "/nsm/server/announce" to provide its name and capabilities. Or it might /error "/nsm/server/announce" to indicate an error-code and error-message. After the client accepts a client-control message, it must reply: /reply "/nsm/xxxxx" message (the command it is responding to with "message") or /error "/nsm/xxxxx" errcode message. 3. Quit (SIGTERM). The client must close unconditionally. This message comes after the client responds to an /nsm/client/save message. 4. Open. The server sends /nsm/client/open followed by the path to the project, a display name, and a client ID. If the client can't switch, "open" is sent only once. Otherwise, the client must act on each open message. The path is instance-specific. The client can append to this path (if done consistently). If a project exists at the path, the client must open it. If not, the client must create one at the specified path. It will be used later with the "save" method. If the client can run multiple times, the provided client ID must be prepended to (e.g.) JACK client names: $CLIENT_ID/track-1 The client ID is application_name + unique identifier. Once the "open" is done, the client must send a response: /reply "/nsm/client/open" message /error "/nsm/client/open" errcode message 5. Save. The server can send /nsm/client/save, but only after a previous open message. /reply "/nsm/client/save" message /error "/nsm/client/save" errcode message 6. Session is loaded. /nsm/client/session_is_loaded doesn't require a response. However, if a client acts on this message, it must not delay initialization while waiting for it. 7. Show and hide GUI. Clients that have capabilitity "optional-gui" might receive /nsm/client/show_optional_gui and /nsm/client/hide_optional_gui. The client must send one of these after the "announce" and after GUI state changes (no response to these from server): /nsm/client/gui_is_hidden /nsm/client/gui_is_shown 8. Progress. /nsm/client/progress percent if capable of "progress". 9. Clean/dirty. /nsm/client/is_dirty /nsm/client/is_clean if capable of "dirty". 10. Status. /nsm/client/message priority message where priority is from 0 (lowest priority) to 3 (highest) and client is capable of "message". 11. Server API: - /nsm/server/add exename Adds a client to the current session. - /nsm/server/save Saves the current session. - /nsm/server/open projname Saves current session and loads a new session. - /nsm/server/new projname Saves the current session and creates a new session. - /nsm/server/duplicate newproj Saves and closes the current session, makes a copy, and opens it. - /nsm/server/close Saves and closes the current session. - /nsm/server/abort Closes the current session WITHOUT SAVING - /nsm/server/quit Saves and closes the current session and terminates the server. - /nsm/server/list Lists available projects. One /reply message will be sent for each existing project. ================================================ FILE: contrib/non/nsm-emails.txt ================================================ NSM Tendrils of Information Chris Ahlstrom 2021-03-10 to 2021-09-03 ------------- Anyway, it's nonsensical to start a client withiut a session open. How can NSM tell the client where to put it's files before even it knows? You have to use the GUI or OSC control to open/create a session first. This behavior way one of the awful flaws in jack session etc. They would continually create anyonmous sessions named after UUIDs a d because of this I would lose work. ------------- Yes, you could technically do that, but I wouldn't recommend it. NSMD's server control OSC commands are intended to be initiated by user actions (optionally invoked from within your own app's GUI). The only thing I imagine that you could do is the same bad behavior of giving the new session a random name. The intention is that control of the session name/location should always be with the user. The confusion comes from e.g. X11 session managers. They really only ever manage one session, the most recent one. In that case, it's fine to be ephemeral and anonymous. NSM (that is, the one I invented, not the copies) is intended for use in the context of a multiplicity of song-level units. These units must not be ephemeral or anonymous. It really matters which song you are messing with! And nobody wants to dig through, merge and rename 1000 random folders. (If you've used e.g.a Zoom Handy recorder which names things like ZOOM0001, then you know what a nightmare this can be). Please, be kind to your users and give the control to them. Tell them there's no NSM session open and they need to open one first, then the result they get will be predictable/desirable. This is one of the main reasons that LASH and JACK Session were unusable in practice. I talked about this for years but nobody understood it. It wasn't until I released NSM that people could experience something better and know that it was better. Alas, even now I think I'm the only one who understands *why* the NSM experience is better. ------------- To investigate: aj-snapshot versus the NSM jackpatch applet. The former works with ALSA as well. See this link for a big-ass use case: https://ponderworthy.github.io/the-box-of-no-return/ # vim: sw=4 ts=4 wm=2 et ft=sh ================================================ FILE: contrib/non/nsm-proxy-h2.sh ================================================ # Source: https://github.com/hydrogen-music/hydrogen/issues/32 # # ..of course drumMachine.h2song has to be manually copied into the NSM # Proxy.nXXXX directory in order for it to work, but once that's done you can # duplicate the session and use it as a template. Not ideal, but it at least # works for now! =D only other thing with that setup is I always save via NSM, # then close Hydrogen, then abort the session, otherwise the .h2song will # possibly get truncated/corrupted as mentioned above.. executable nohup hydrogen arguments --nosplash --song ./drumMachine.h2song save signal 10 stop signal 15 label Hydrogen ================================================ FILE: contrib/non/nsm.h ================================================ /*************************************************************************/ /* Copyright (C) 2012 Jonathan Moore Liles */ /* */ /* Permission to use, copy, modify, and/or distribute this software for */ /* any purpose with or without fee is hereby granted, provided that the */ /* above copyright notice and this permission notice appear in all */ /* copies. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL */ /* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED */ /* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE */ /* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL */ /* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR */ /* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER */ /* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR */ /* PERFORMANCE OF THIS SOFTWARE. */ /* */ /* For Seq66, this file is included FOR REFERENCE ONLY. */ /* */ /*************************************************************************/ /*************************************************************/ /* A simple, callback based C API for NSM clients. */ /* */ /* Simplified Example: */ /* */ /* #include "nsm.h" */ /* */ /* int */ /* cb_nsm_open ( const char *name, */ /* const char *display_name, */ /* const char *client_id, */ /* char **out_msg, */ /* void *userdata ) */ /* { */ /* do_open_stuff(); */ /* return ERR_OK; */ /* } */ /* */ /* int */ /* cb_nsm_save ( char **out_msg, */ /* void *userdata ) */ /* { */ /* do_save_stuff(); */ /* return ERR_OK; */ /* } */ /* */ /* static nsm_client_t *nsm = 0 */ /* */ /* int main( int argc, char **argv ) */ /* { */ /* const char *nsm_url = getenv( "NSM_URL" ); */ /* */ /* if ( nsm_url ) */ /* { */ /* nsm = nsm_new(); */ /* */ /* nsm_set_open_callback( nsm, cb_nsm_open, 0 ); */ /* nsm_set_save_callback( nsm, cb_nsm_save, 0 ); */ /* */ /* if ( 0 == nsm_init( nsm, nsm_url ) ) */ /* { */ /* nsm_send_announce( nsm, "FOO", "", argv[0] ); */ /* } */ /* else */ /* { */ /* nsm_free( nsm ); */ /* nsm = 0; */ /* } */ /* } */ /* } */ /*************************************************************/ #ifndef _NSM_H #define _NSM_H #define NSM_API_VERSION_MAJOR 1 #define NSM_API_VERSION_MINOR 0 #include #include #include #include #include #include typedef void * nsm_client_t; typedef int (nsm_open_callback)( const char *name, const char *display_name, const char *client_id, char **out_msg, void *userdata ); typedef int (nsm_save_callback)( char **out_msg, void *userdata ); typedef void (nsm_active_callback)( int b, void *userdata ); typedef void (nsm_session_is_loaded_callback)( void *userdata ); typedef int (nsm_broadcast_callback)( const char *, lo_message m, void *userdata ); #define _NSM() ((struct _nsm_client_t*)nsm) #define NSM_EXPORT __attribute__((unused)) static /* private parts */ struct _nsm_client_t { const char *nsm_url; lo_server _server; lo_server_thread _st; lo_address nsm_addr; int nsm_is_active; char *nsm_client_id; char *_session_manager_name; nsm_open_callback *open; void *open_userdata; nsm_save_callback *save; void *save_userdata; nsm_active_callback *active; void *active_userdata; nsm_session_is_loaded_callback *session_is_loaded; void *session_is_loaded_userdata; nsm_broadcast_callback *broadcast; void *broadcast_userdata; }; enum { ERR_OK = 0, ERR_GENERAL = -1, ERR_INCOMPATIBLE_API = -2, ERR_BLACKLISTED = -3, ERR_LAUNCH_FAILED = -4, ERR_NO_SUCH_FILE = -5, ERR_NO_SESSION_OPEN = -6, ERR_UNSAVED_CHANGES = -7, ERR_NOT_NOW = -8 }; NSM_EXPORT int nsm_is_active ( nsm_client_t *nsm ) { return _NSM()->nsm_is_active; } NSM_EXPORT const char * nsm_get_session_manager_name ( nsm_client_t *nsm ) { return _NSM()->_session_manager_name; } NSM_EXPORT nsm_client_t * nsm_new ( void ) { struct _nsm_client_t *nsm = (struct _nsm_client_t*)malloc( sizeof( struct _nsm_client_t ) ); nsm->nsm_url = 0; nsm->nsm_is_active = 0; nsm->nsm_client_id = 0; nsm->_server = 0; nsm->_st = 0; nsm->nsm_addr = 0; nsm->_session_manager_name = 0; nsm->open = 0; nsm->save = 0; nsm->active = 0; nsm->session_is_loaded = 0; nsm->broadcast = 0; return (nsm_client_t *)nsm; } /*******************************************/ /* CLIENT TO SERVER INFORMATIONAL MESSAGES */ /*******************************************/ NSM_EXPORT void nsm_send_is_dirty ( nsm_client_t *nsm ) { if ( _NSM()->nsm_is_active ) lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/is_dirty", "" ); } NSM_EXPORT void nsm_send_is_clean ( nsm_client_t *nsm ) { if ( _NSM()->nsm_is_active ) lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/is_clean", "" ); } NSM_EXPORT void nsm_send_progress ( nsm_client_t *nsm, float p ) { if ( _NSM()->nsm_is_active ) lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/progress", "f", p ); } NSM_EXPORT void nsm_send_message ( nsm_client_t *nsm, int priority, const char *msg ) { if ( _NSM()->nsm_is_active ) lo_send_from( _NSM()->nsm_addr, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/client/message", "is", priority, msg ); } NSM_EXPORT void nsm_send_announce ( nsm_client_t *nsm, const char *app_name, const char *capabilities, const char *process_name ) { lo_address to = lo_address_new_from_url( _NSM()->nsm_url ); if ( ! to ) { fprintf( stderr, "NSM: Bad address!" ); return; } int pid = (int)getpid(); lo_send_from( to, _NSM()->_server, LO_TT_IMMEDIATE, "/nsm/server/announce", "sssiii", app_name, capabilities, process_name, NSM_API_VERSION_MAJOR, NSM_API_VERSION_MINOR, pid ); lo_address_free( to ); } NSM_EXPORT void nsm_send_broadcast ( nsm_client_t *nsm, lo_message msg ) { if ( _NSM()->nsm_is_active ) lo_send_message_from( _NSM()->nsm_addr, _NSM()->_server, "/nsm/server/broadcast", msg ); } NSM_EXPORT void nsm_check_wait ( nsm_client_t *nsm, int timeout ) { if ( lo_server_wait( _NSM()->_server, timeout ) ) while ( lo_server_recv_noblock( _NSM()->_server, 0 ) ) {} } NSM_EXPORT void nsm_check_nowait (nsm_client_t *nsm ) { nsm_check_wait( nsm, 0 ); } NSM_EXPORT void nsm_thread_start ( nsm_client_t *nsm ) { lo_server_thread_start( _NSM()->_st ); } NSM_EXPORT void nsm_thread_stop ( nsm_client_t *nsm ) { lo_server_thread_stop( _NSM()->_st ); } NSM_EXPORT void nsm_free ( nsm_client_t *nsm ) { if ( _NSM()->_st ) nsm_thread_stop( nsm ); if ( _NSM()->_st ) lo_server_thread_free( _NSM()->_st ); else lo_server_free( _NSM()->_server ); free( _NSM() ); } /*****************/ /* SET CALLBACKS */ /*****************/ NSM_EXPORT void nsm_set_open_callback( nsm_client_t *nsm, nsm_open_callback *open_callback, void *userdata ) { _NSM()->open = open_callback; _NSM()->open_userdata = userdata; } NSM_EXPORT void nsm_set_save_callback( nsm_client_t *nsm, nsm_save_callback *save_callback, void *userdata ) { _NSM()->save = save_callback; _NSM()->save_userdata = userdata; } NSM_EXPORT void nsm_set_active_callback( nsm_client_t *nsm, nsm_active_callback *active_callback, void *userdata ) { _NSM()->active = active_callback; _NSM()->active_userdata = userdata; } NSM_EXPORT void nsm_set_session_is_loaded_callback( nsm_client_t *nsm, nsm_session_is_loaded_callback *session_is_loaded_callback, void *userdata ) { _NSM()->session_is_loaded = session_is_loaded_callback; _NSM()->session_is_loaded_userdata = userdata; } NSM_EXPORT void nsm_set_broadcast_callback( nsm_client_t *nsm, nsm_broadcast_callback *broadcast_callback, void *userdata ) { _NSM()->broadcast = broadcast_callback; _NSM()->broadcast_userdata = userdata; } /****************/ /* OSC HANDLERS */ /****************/ #undef OSC_REPLY #undef OSC_REPLY_ERR #define OSC_REPLY( value ) lo_send_from( ((struct _nsm_client_t*)user_data)->nsm_addr, ((struct _nsm_client_t*)user_data)->_server, LO_TT_IMMEDIATE, "/reply", "ss", path, value ) #define OSC_REPLY_ERR( errcode, value ) lo_send_from( ((struct _nsm_client_t*)user_data)->nsm_addr, ((struct _nsm_client_t*)user_data)->_server, LO_TT_IMMEDIATE, "/error", "sis", path, errcode, value ) NSM_EXPORT int _nsm_osc_open ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) { (void) types; (void) argc; (void) msg; char *out_msg = NULL; struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data; nsm->nsm_client_id = strdup( &argv[2]->s ); if ( ! nsm->open ) return 0; int r = nsm->open( &argv[0]->s, &argv[1]->s, &argv[2]->s, &out_msg, nsm->open_userdata ); if ( r ) OSC_REPLY_ERR( r, ( out_msg ? out_msg : "") ); else OSC_REPLY( "OK" ); if ( out_msg ) free( out_msg ); return 0; } NSM_EXPORT int _nsm_osc_save ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) { (void) types; (void) argv; (void) argc; (void) msg; char *out_msg = NULL; struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data; if ( ! nsm->save ) return 0; int r = nsm->save(&out_msg, nsm->save_userdata ); if ( r ) OSC_REPLY_ERR( r, ( out_msg ? out_msg : "") ); else OSC_REPLY( "OK" ); if ( out_msg ) free( out_msg ); return 0; } NSM_EXPORT int _nsm_osc_announce_reply ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) { (void) path; (void) types; (void) argc; if ( strcmp( &argv[0]->s, "/nsm/server/announce" ) ) return -1; struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data; fprintf( stderr, "NSM: Successfully registered. NSM says: %s", &argv[1]->s ); nsm->nsm_is_active = 1; nsm->_session_manager_name = strdup( &argv[2]->s ); nsm->nsm_addr = lo_address_new_from_url( lo_address_get_url( lo_message_get_source( msg ) )); if ( nsm->active ) nsm->active( nsm->nsm_is_active, nsm->active_userdata ); return 0; } NSM_EXPORT int _nsm_osc_error ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) { (void) path; (void) types; (void) argc; (void) msg; if ( strcmp( &argv[0]->s, "/nsm/server/announce" ) ) return -1; struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data; fprintf( stderr, "NSM: Failed to register with NSM server: %s", &argv[2]->s ); nsm->nsm_is_active = 0; if ( nsm->active ) nsm->active( nsm->nsm_is_active, nsm->active_userdata ); return 0; } NSM_EXPORT int _nsm_osc_session_is_loaded ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) { (void) path; (void) types; (void) argv; (void) argc; (void) msg; struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data; if ( ! nsm->session_is_loaded ) return 0; nsm->session_is_loaded( nsm->session_is_loaded_userdata ); return 0; } NSM_EXPORT int _nsm_osc_broadcast ( const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data ) { (void) types; (void) argv; (void) argc; struct _nsm_client_t *nsm = (struct _nsm_client_t*)user_data; if ( ! nsm->broadcast ) return 0; return nsm->broadcast( path, msg, nsm->broadcast_userdata ); } NSM_EXPORT int nsm_init ( nsm_client_t *nsm, const char *nsm_url ) { _NSM()->nsm_url = nsm_url; lo_address addr = lo_address_new_from_url( nsm_url ); int proto = lo_address_get_protocol( addr ); lo_address_free( addr ); _NSM()->_server = lo_server_new_with_proto( NULL, proto, NULL ); if ( ! _NSM()->_server ) return -1; lo_server_add_method( _NSM()->_server, "/error", "sis", _nsm_osc_error, _NSM() ); lo_server_add_method( _NSM()->_server, "/reply", "ssss", _nsm_osc_announce_reply, _NSM() ); lo_server_add_method( _NSM()->_server, "/nsm/client/open", "sss", _nsm_osc_open, _NSM() ); lo_server_add_method( _NSM()->_server, "/nsm/client/save", "", _nsm_osc_save, _NSM() ); lo_server_add_method( _NSM()->_server, "/nsm/client/session_is_loaded", "", _nsm_osc_session_is_loaded, _NSM() ); lo_server_add_method( _NSM()->_server, NULL, NULL, _nsm_osc_broadcast, _NSM() ); return 0; } NSM_EXPORT int nsm_init_thread ( nsm_client_t *nsm, const char *nsm_url ) { _NSM()->nsm_url = nsm_url; lo_address addr = lo_address_new_from_url( nsm_url ); int proto = lo_address_get_protocol( addr ); lo_address_free( addr ); _NSM()->_st = lo_server_thread_new_with_proto( NULL, proto, NULL ); _NSM()->_server = lo_server_thread_get_server( _NSM()->_st ); if ( ! _NSM()->_server ) return -1; lo_server_thread_add_method( _NSM()->_st, "/error", "sis", _nsm_osc_error, _NSM() ); lo_server_thread_add_method( _NSM()->_st, "/reply", "ssss", _nsm_osc_announce_reply, _NSM() ); lo_server_thread_add_method( _NSM()->_st, "/nsm/client/open", "sss", _nsm_osc_open, _NSM() ); lo_server_thread_add_method( _NSM()->_st, "/nsm/client/save", "", _nsm_osc_save, _NSM() ); lo_server_thread_add_method( _NSM()->_st, "/nsm/client/session_is_loaded", "", _nsm_osc_session_is_loaded, _NSM() ); lo_server_thread_add_method( _NSM()->_st, NULL, NULL, _nsm_osc_broadcast, _NSM() ); return 0; } #endif /* NSM_H */ ================================================ FILE: contrib/non/nsm.sh ================================================ #!/bin/sh #------------------------------------------------------------------------------ ## # \file nsm.sh # \library contrib/non # \author Chris Ahlstrom # \date 2020-03-05 to 2020-09-01 # \version $Revision$ # \license GNU GPLv2 or above, or more generous # # Invoke as: # # nsm.sh projectname # # To consider: # # non-session-manager -- --osc-port 7777 # sleep 4 # oscsend localhost 7777 /nsm/server/open s "$1" # #------------------------------------------------------------------------------ PROJECT=$1 #for better readability # The following command returns a line to provide the NSM_URL, which we use to # set NSM_URL. For example: # # M_URL=osc.udp://mlsleno:12885/ eval $(nsmd --detach) # Note that "nsdm --help" leaves out some options that available. Here are # all the options: # # --detach: Detach from console and fork a new process. # --session-root: Set the session path. Defaults to "$HOME/NSM Sessions". # --osc-port: Provides the UDP port number. Otherwise, it is random. # --gui-url: Connects to the GUI here. Sends the message # "/nsm/gui/server_announce". # # Keep in brackets so that PID and nsm are in the same group. Otherwise, # the wait below will complain. Remember to check (the PID) when the # GUI closes. { non-session-manager --nsm-url $NSM_URL & PID=$! } # Leave NSM time to start up. Otherwise, one will see some glitched # session display. sleep 1 # oscsend is part of liblo, which nsm depends on. On Ubuntu, one must install # the liblo-tools package to get this command. It sends OpenSound Control # messages via UDP. # # Usage: # # oscsend hostname port address types values ... # oscsend url address types values ... # # Note: There is also a sendosc project on GitHub. oscsend $NSM_URL "/nsm/server/open" s $PROJECT wait $PID echo "GUI closed. Closing detached nsdm server." oscsend $NSM_URL "/nsm/server/quit" # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/non/nsm_tendrils.txt ================================================ NSM Notes for Seq66 (nsm_tendrils.txt) Chris Ahlstrom 2020-03-14 to 2024-11-06 These notes are based on the NSM API text, and indicate where changes would need to be made when NSM support is compiled in. Also useful, noted here so we don't forget about it, is this tutorial: https://libremusicproduction.com/tutorials/modular-set-ups-concepts-and-practices-using-non-session-manager.html Qt UI Version qsmainwnd: File/New: Change from calling new_file() to calling new_session(). File/Open: Disable (remove from the menu) this option. May need to create a new function called open_session(). File/Import to Session: Add this menu item, add a function called import_into_session(). File/Save: Change from calling save_file() to calling save_session(), a counterpart to open_session(). All project-specific data created by a client must be stored in the per-client storage area provided by NSM. File/Save As: Disable (remove from the menu) this option. File/Export from Session: Add this menu item, which acts like the original version of Save As, but uses a user-specified directory. File/Close: Add this menu item to call close_session() or disconnect_session(). File/Quit or Exit: Call quit(), but also close_session() or save_session(). connect_to_session(): - Call create_nsmclient() and return "true" if it all worked. - Get everything ready to be able to respond to /nsm/client/open. - Call nsm::announce() to send /nsm/server/announce. It should provide the app-name, capabilities string, exe-name, and pid. The app-name should match the JACK-client name. See the nsm constructor. - The server will respond: /reply "/nsm/server/announce" s:message s:name_of_session_manager s:server_capabilities Upon receiving this message, the client is under session management. An error response (see NSM_API.txt, line 243): /error "/nsm/server/announce" i:error_code s:error_message - Receive the open message, which calls nsm::nsm_open(). - Get the unique client name, then call jack_client_open(). The application must not register with JACK (or any other subsystem requiring unique names) until it receives an open message from NSM. Client Control Messages: - These messages must be accepted, the action completed or errored, and a response (/reply or /error) must be sent back. - Quit: close cleanly immediately upon receipt of SIGTERM, even if changes would be lost. When a session is closed the application will receive this signal soon after having responded to a save message. - Open: See NSM_API, line 280 on. Too complex to note here. NSM provides a path to the project-instance-specific path. The client will open the project or create a new one. Applications with the :switch: capability should close their JACK clients and re-create them with using the new client_id. Re-registering is necessary with JACK. The client must respond to the 'open' message with: /reply "/nsm/client/open" s:message OR /error "/nsm/client/open" i:error_code s:message - Save: The /nsm/client/save message will only be delivered after a previous open message, and may be sent any number of times within the course of a session. The client must respond to the 'save' message with: /reply "/nsm/client/save" s:message OR /error "/nsm/client/save" i:error_code s:message - /nsm/client/session_is_loaded. This message does not require a response. - /nsm/client/show_optional_gui and /nsm/client/hide_optional_gui. No response needed. - /nsm/client/gui_is_hidden and /nsm/client/gui_is_shown. If the client specified optional-gui, then it must send this message whenever the state of visibility of the GUI has changed. It also must send this message after it's announce message to indicate the initial visibility state of the optional GUI. - /nsm/client/progress f:progress. When using the progress feature, the final response to the save or open message is still required. - /nsm/client/is_dirty and /nsm/client/is_clean. Clients may send is_dirty and is_clean messages. Clients with this capability include :dirty: in their announce. - Status. /nsm/client/message i:priority s:message. Clients may send status updates to the server for possible display to the user. - Client to Server Control. If the server publishes :server_control:, clients can initiate action by the server. A client might implement a 'Save All' option which sends a /nsm/server/save message to the server. Session Manager Server Control: - The server responds to the following messages with these replies: /reply s:path s:message /error s:path i:error_code s:message Here are the messages: - /nsm/server/save. Adds a client to the current session. - /nsm/server/open s:project_name. Saves the current session. - /nsm/server/new s:project_name. Saves the current session and loads a new session. - /nsm/server/duplicate s:new_project. Saves the current session and creates a new session. - /nsm/server/close. Saves and closes the current session, makes a copy, and opens it. - /nsm/server/abort. Saves and closes the current session. - /nsm/server/quit. Closes the current session WITHOUT SAVING - /nsm/server/?????. Saves and closes the current session and terminates the server. - /nsm/server/list. Lists available projects. One /reply message will be sent for each existing project. Client-to-client Communication: If the server includes :broadcast: in its capability string, then clients may send broadcast messages to each other through the NSM server. Clients may send messages to the server at the path /nsm/server/broadcast. - /nsm/server/broadcast s:path [arguments...]. See NSM_API.txt, line 559. The Non programs use this feature to establish peer to peer OSC communication by symbolic names (client IDs) without having to remember the OSC URLs of peers across sessions. Other: Files required by the project but external to it (e.g. audio samples) should be referenced by creating a symbolic link within the assigned session area. ----------------------------- Trouble shooting issue #131 ----------------------------- Running non-session-manager (nsm-legacy-gui) and adding a session for qseq66 yields two entries: qseq66 launch seq66 ready Click for qseq66 and it shows stopped, but it is still in the process list. Click for seq66, and it summarily exits. The session.log file is written to ~/. ================================================ FILE: contrib/notes/INSTALL4Ubuntu.md ================================================ # INSTALL Seq66 for Ubuntu Studio Authors: WinkoErades and MQS-mark

Updated: 2026-01-16 Here’s a **step-by-step installation plan** to build and install **Seq66** from source on **Ubuntu Studio 25.10**. This assumes one is *installing from the GitHub repository*: [Seq66 GitHub](https://github.com/ahlstromcj/seq66/tree/master) ([GitHub](https://github.com/ahlstromcj/seq66?utm_source=chatgpt.com "Seq66: Seq24-based live MIDI looper/editor. v. ...")) ## 1. Prepare Ubuntu Studio 25.10 Ubuntu Studio is already tailored for audio production, but ensure your system is up to date: ```bash sudo apt update && sudo apt upgrade -y ``` Reboot if needed after upgrades. ## 2. Install Required Build Dependencies Seq66 uses **GNU Autotools** and **Qt5** for the GUI version, plus OSC/MIDI support. Required development tools and libraries: ```bash sudo apt install -y \ autoconf \ automake \ build-essential \ git \ libasound2-dev \ libatopology-dev \ libc6-dev \ libjack-jackd2-dev \ liblo-dev \ libpcre2-dev \ libqt5widgets5 \ libtool \ libudev-dev pkg-config \ qt5-qmake \ qtbase5-dev \ qttools5-dev \ qttools5-dev-tools \ ``` *Explanation:* - `build-essential`, `automake`, `autoconf`, etc., provide the core build toolchain. - `qtbase5-dev` and related packages supply Qt5 headers/tools for building the Seq66 GUI. - `libasound2-dev`, `libjack-jackd2-dev`, `liblo-dev` cover ALSA, JACK/pipewire jack compatibility, and liblo (OSC), all typical MIDI/audio support libs. - `libatopology-dev` might not be needed, depending on the Ubuntu version. (Exact package names can vary slightly in Ubuntu 25.10; `apt search liblo` etc. helps confirm.) ([INSTALL](https://github.com/ahlstromcj/seq66/blob/master/INSTALL)) ## 3. Clone the Seq66 Repository Choose a location such as your home directory and clone the latest code: ```bash cd ~ git clone https://github.com/ahlstromcj/seq66.git cd seq66 ``` Make sure you’re on the **master** branch (default) unless you want a different release. ## 4. Bootstrap and Configure Some older Seq66 releases may require regenerating the build system if `configure` is missing or outdated: ```bash ./bootstrap ``` If that script is not present or errors, use `autoreconf`: ```bash autoreconf --install --force ``` Now configure the build: ```bash ./configure ``` You can pass optional flags if needed, e.g.: - `--enable-rtmidi` (explicitly enable RtMidi backend) - `--disable-portmidi` (if portmidi isn’t installed) *(Check `./configure --help` for options.)* Note: `configure` will detect Qt5 if installed. ([Seq66 GitHub](https://github.com/ahlstromcj/seq66?utm_source)) 📌 *Common issues*: If `./configure` fails complaining about missing files like `Makefile.in`, ensure you ran `autoreconf` first. Older seq66 sources may need that. ([Issue 25](https://github.com/ahlstromcj/seq66/issues/25)) ## 5. Build Seq66 Compile the code: ```bash make -j$(nproc) ``` `-j$(nproc)` speeds up compilation using all CPU cores. If you only want the command-line version, you can compile only that target after configure: ```bash make seq66cli ``` ## 6. Install Once built, install it system-wide: ```bash sudo make install ``` This typically installs binaries like **qseq66**, **qpseq66** (Qt GUI), and **seq66cli** into `/usr/local/bin` or similar. ## 7. Optional: Add to Desktop Menu If the GUI version built (`qseq66` or `qpseq66`), a `.desktop` file already exists in the repo’s `desktop` folder. You can install it so it appears in your application launcher: ```bash sudo cp desktop/seq66.xpm /usr/share/applications/ ``` Then update desktop DB: ```bash sudo update-desktop-database ``` ## 8. Run and Test Launch either: - **GUI**: `qseq66` or `qpseq66` - **CLI**: `seq66cli` Ensure your MIDI devices are connected and recognized. Ubuntu Studio’s audio tools (like QJackCtl or Ubuntu Studio Audio Config) may help routing JACK/ALSA. ## Troubleshooting Tips ### Missing `configure` or autotools error - Run `autoreconf --install --force` before `./configure`. ([Issue 25](https://github.com/ahlstromcj/seq66/issues/25")) - Ensure `automake`, `autoconf`, `libtool` are installed. - If the system complains about not having Autoconf >= 2.72 installed, edit `configure.ac` to reduce the `AC_PREREQ` version number to what is actually installed, and start over. ### Qt not detected - Confirm `qtbase5-dev`, `qttools5-dev`, `qttools5-dev-tools`, and `qmake` are installed. - Use `qtchooser -list-versions` to check available Qt versions. ### Missing libs (such as liblo) - Install `liblo-dev` (for OSC) before configure. ([Reddit Seq66](https://www.reddit.com/r/sequencers/comments/16o7b7t/seq66_debian_install/)) ### Build fails due to ALSA/JACK - Check `libasound2-dev` and appropriate JACK dev packages are installed. ## Useful References - Official Seq66 **README / INSTALL** in the repository (detailed build instructions). ([Seq66 GitHub](https://github.com/ahlstromcj/seq66)) - User manual and tutorials on the project site (great for usage after install). ([Seq66 Manual](https://raw.githubusercontent.com/ahlstromcj/seq66/master/data/share/doc/seq66-user-manual.pdf)) [//] vim: sw=4 ts=4 wm=2 et ft=markdown ================================================ FILE: contrib/notes/NEWS-template ================================================ ## [0.99.xx] - 2025-xx-xx This release contains ... ### Added - Main window and grid. - Pattern editor. - Song editor. - Event editor. - Preferences dialog. - Import/Export. - Documentation. ### Changed - Main window and grid. - Pattern editor. - Song editor. - Event editor. - Preferences dialog. - Contrib files. - Coalescing common code. - Changed the scales\_policy() function slightly. ### Fixed - Main window and grid. - Pattern editor. - Song editor. - Event editor. - Preferences dialog. - Configuration files. # vim: sw=4 ts=4 wm=15 et ft=markdown ================================================ FILE: contrib/notes/RELNOTES-0_99_0.md ================================================ # Seq66 Release Notes 0.99.0 2022-09-03 This file lists __major__ changes from version 9.98.0 to 0.99.0 (to catch up). For version 0.99.0, a raft of updates and fixes were made as we work through some of the items in the TODO file. This version series will add no new major features, but will follow up on the remaining issues and any new issues that come up. New features will be pushed off to Seq66v2; see the bottom of the TODO file. ## Changes * Version 0.99.0: * Issue #54. Updated ax_have_qt_min.m4 to detect qmake-qt5. * Issue #78 revisited to tighten pattern box display. * Added a --locale option. * Issue #82 allows buttons and fields to expand better in some tabs. * Issue #89 fixed so that MIDI control display reflects queuing status. * Issue #90 improvements so that Save is activated for changes. * Issue #93 fixed. The window of a deleted pattern now closes. * Issue #94. Improvements to scrolling in the song editor. * Added a button to expand the song editor in time. * Issue #97. Added a paste box when pasting notes. Added a metronome. * Added L/R arrow keys to move the selected trigger in song editor. * Improved the handling of the MIDI 'ctrl' file. * Tightened up pattern arming/disarming processing. * Issue #98. * Added a metronome with count-in and 'rc' configuration. * Background automatic recording added. * Various fixes, too numerous to call out here. * Version 0.98.10: * Revisited issue #83: [midi-input]: in-bus data line error, to improve GUI editing of automation port. * Fixed issue #87: Segfault due to mute-group on larger set-sizes. * Fixed issue #88: 4/16 pattern not shown/played properly at first. * Version 0.98.9: * Fixed issue #85: Seqfault from recreating slot buttons in thread. * Improved: Change-detection in Edit / Preferences. * Improved: Cross-dialog change detection (e.g. mute/record/thru display) * Version 0.98.8: * Fixed issue #84: Now able to build Qt and CLI version in one pass. * Added: "Restart" button to Edit / Preferences. * Added: Ctrl-Home and Ctrl-End support to the song editor. * Added: An HTML tutorial and buttons to access it and the user manual. See [Ahlstrom Projects](https://ahlstromcj.github.io/). * Version 0.98.7: * Fixed issue #80: some MIDI controls were getting recorded. * Fixed issue #81: added code to catch bad numeric conversions. * Fixed issue #83: parsing 'rc' port lines with trailing space failed. * Added: "Pattern Fix" tool to allow a multiple changes to a pattern. * Version 0.98.6: * Revisited issue #41: to make sure "Quit" is "Hide" under NSM. * Version 0.98.5: * Added: Locking for the event-drawing loops to prevent segfaults. * Version 0.98.4: * Fixed issue #75: some metadata problems preventing use of app icons. * Version 0.98.3: * Fixed issue #76: broken MIDI Start handling. * Version 0.98.2: * Fixed issue #74: -1 for "no buss-override" was being converted to 0. * Version 0.98.1: * Fixes: Song-editor set-names display; segfault in --help option; more. * Version 0.98.0: * Fixed issue #41: Hide Seq66 on closing window in an NSM session. * Fixed issue #73: Compile error because of jack_get_version_string. * Added: "MIDI macros" to the 'ctrl' file. Can send SysEx, etc. ## Final Notes All too many bug fixes, code refactoring, minor improvements, too many to cover. Read the NEWS and README files. Never-ending! ================================================ FILE: contrib/notes/RPN.text ================================================ Notes on RPN Chris Ahlstrom 2025-01-13 to 2026-04-20 https://www.recordingblogs.com/wiki/midi-registered-parameter-number-rpn NRPN = Non-Registered Parameter Number. RPN = Registered Parameter Number. Consider a scenario in which a MIDI device receives the following two messages. 0xB0 0x64 0x00 RPN, fine, pitch bend range 0xB0 0x65 0x00 RPN, coarse, ditto Both are MIDI controller messages for channel 0. The first message is 0x64, an RPN (fine, or lower bits) controller. The second value is 0x65, is an RPN (coarse, or higher bits) controller. The last byte of the two messages have the value 0x00, which tells the MIDI device that the range of the pitch bend will change. These values have been registered with the International MIDI Association to carry this kind of information. The two messages above tell the MIDI device that the pitch bend range is about to change. The MIDI device should then expect data entry or data increment / decrement messages that will define the new pitch range. To make a change to the pitch range, the MIDI device should receive messages similar to the two messages that follow. Suppose that the MIDI device receives the following messages. 0xB0 0x06 0x02 Data entry, coarse 0xB0 0x26 0x03 Data entry, fine Since the MIDI device is waiting for a pitch range change, it interprets the third byte of each message as follows: The coarse data entry is 0x02 -- the pitch wheel will move by +/-2 semitones. The fine data entry is 0x03 -- the pitch wheel will move by +/- 3 cents. The pitch shift can move two semitones and three cents down to 0x0000 or two semitones and three cents up to 0x3FFF. According to the MIDI protocol, the MIDI device should also receive the following two messages. 0xB0 0x64 0x7F 0xB0 0x65 0x7F The status byte 0xB0 shows that these two messages are MIDI controller messages for channel 0. They carry the controller values of 0x64 and 0x65 and mean RPN fine and coarse controllers. They have the values 0x7F which signifies the end of the controller sequence. To properly use the registered parameter number (RPN) controller, a MIDI device must receive a sequence of messages as follows: - Two controller messages with 0x64 and 0x65 and values that specify the RPN and define what is to follow. - The values of the third bytes of these two messages are combined into a 14-bit value by dropping the most significant bit of each byte. - The resulting value is registered with the International MIDI Association (IMA) and describes a specific purpose of what is to follow. - One or two data entry messages that specify what to do with the specific controller. - Data entry messages have controller values of 0x06 and 0x26 (coarse and fine). - The fine data entry message (0x26) can be omitted depending on the registered parameter number defined in the two controller messages above. - The values of the third bytes of the two data entry messages are combined in a 14-bit value. - The 14-bit value has significance depending on the type of controller being affected. - If the RPN is a pitch bend range, the value sent with the data entry messages is 0x0103 (which, when split into two 7-bit values, translates to 0x02 and 0x03) then the pitch bend will vary +/- two semitones and three cents. - Alternative to data entry controller messages, the MIDI device could also receive data button increment or data button decrement messages. - These messages are MIDI controller messages with controller values of 0x60 and 0x61 - Instead of specifying the exact value of the controller these messages increase or decrease respectively its current value. - The MIDI 1.0 specification recommends using Data Entry MSB and LSB instead of Data Increment/Decrement. - Two controller messages with controller numbers 0x64 and 0x65 and with values 0x7F and 0x7F signifying the end of this controller sequence. These two messages are optional, but are a good practice, as if not sent, the coarse and fine controller numbers would remain set forever in practice and a random data entry message could have unintended effects. An example using these controllers is provided below. Defined RPNs The following is a list of RPN values for the 14-bit values of the 0x64 and 0x65 controllers that have been registered with the IMA. The preceding zeroes have been left off. 0x00 – Pitch bend range 0x01 – Channle Fine tuning 0x02 – Channle Coarse tuning 0x03 – Tuning program change 0x04 – Tuning bank select 0x05 – Modulation depth range 0x7F - End of parameter setting, RPN null, disable RPN changes. Pitch bend range: The pitch bend RPN sequence adjusts the range of the MIDI pitch wheel message. It needs both data entry (coarse and fine) messages. Fine tuning: The fine tuning RPN sequence adjusts the tuning of a channel. It also needs both data entry messages. If the 14-bit value of the data entry messages is 0x2000, then tuning is central with A = 440 Hz. The full range (0x0000 to 0x3FFF of the 14-bit value) means two semitones. Thus, the tuning can move down one semitone or up one semitone and since one semitone is equal to 100 cents, since the distance between the center 0x2000 and each of the limits (up and down) is 8192 decimal, one single step increment is 100 / 8192 = 0.0122 (approximately) cents. The value 0x2001 for example would mean that A is 440 Hz plus 0.0122 cents, which is A = 440.0031 Hz. Coarse tuning: The coarse tuning RPNs use only the coarse data entry message to tune, with 0x20 representing central tuning of A = 440 Hz and with increments of whole semitones (e.g., 0x21 would be a whole semitone displacement up). Tuning program and tuning bank: The tuning program change and tuning bank change are not widely used. It is likely that these messages select tunings different than the equal tempered tuning. Modulation depth range: The modulation depth range is designed to affect the depth of a vibrato, but neither the maximum and minimum depth nor the default depth is set for General MIDI. Those are left up to the manufacturer. According to the MIDI Manufacturer Association, the message may even affect something else than a vibrato. The first three registered parameter numbers above (pitch bend range, fine tuning, and coarse tuning) are required by General MIDI 1. The modulation depth range was added in General MIDI 2. The remaining two parameter numbers were presumably defined later. NRPN: 0xB0 0x66 0x00 NRPN, fine, pitch bend range 0xB0 0x68 0x00 NRPN, coarse, ditto ================================================= https://www.2writers.com/eddie/TutNrpn.htm If you want to change the "pitch bend sensitivity" you would send the following control change messages: 101 0 (0x67 0x00) Coarse pitchbend range (MSB) 100 0 (0x66 0x00) Fine pitchbend range (LSB) 6 2 (0x06 0x02) Coarse data entry slider to set value = 2 38 0 (0x26 0x00) Fine data entry slider to set value = 0 The 101 and 100 indicate that a parameter is to be changed. The 0 in each indicates pitchbend. Pitch bend sensitivity is how much the currently sounding notes are affected by pitch bend. (See the description above.) ================================================= http://midi.teragonaudio.com/tutr/rpn.htm Assume the manufacturer chose 14000 (from the set 0 to 16383) for a parameter. - In binary, this is a 16-bit number: 0011 0110 1011 0000 - Strip off the two leading zeroes: 11 0110 1011 0000 - Get the next 7 bits: 11 0110 1 - Prepend a 0: 011 0110 1 - This value (109 or 0x6D) is the high bits of the parameter number. - Get the last 7 bits: 011 0000 - Prepend a 0: 0011 0000 - This value (48 or 0x30) is the low bits of the parameter number. - Send the NRPN coarse controller: 0xB0 0x63 0x6D - Send the NRPN fine controller: 0xB0 0x62 0x30 - Then send one or more of the following messages: - Data Entry Slider, coarse: 0xB0 0x06 0x64 (i.e. 100) - Data Button Increment: 0xB0 0x60 xxxx - Data Button Decrement: 0xB0 0x61 xxxx Cakewalk has an "Insert" menu: Bank/patch change Wave file Tempo change Meter/Key change Series of controllers Series of tempos # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/notes/bluez-alsa-notes.text ================================================ Rough HOWTO On Using Bluetooth Earbuds Chris Ahlstrom 2021-08-04 to 2021-08-04 This is how I got Bluez-ALSA working with aplay and MPD on my Debian Sid system. Apparently it will be in the upcoming Debian 12. It is not in Ubuntu yet, as far as I know, so will build it from source (later). Sites: https://github.com/Arkq/bluez-alsa https://wiki.debian.org/Bluetooth/Alsa https://wiki.archlinux.org/title/bluetooth_headset https://community.volumio.org/t/volumio-bluez-alsa-a2dp-bluetooth-support/5611 0. Background: First, I run MPD in user land, with this part relevant from ~/.mpdconf: audio_output { type "alsa" name "USB Audio Codec" device "hw:CODEC" # optional } The rest of the file is pretty stock, except the logs etc. are written to ~/.mpd. Renamed /etc/mpd.conf /etc/mpd.conf.disabled to keep it out of the way. The command "systemctl --user start mpd" is (eventually) called via the .xsession startup I use. Second, the stock install of ALSA is used, and Debian puts all of the configuration of ALSA in /etc/alsa/conf.d. Thus, /etc/asound.conf is unused and I renamed it to /etc/asound.conf. 1. Installed the Debian bluez-alsa-utils package. This install sets up the bluez-alsa service in systemd and adds "20-bluealsa.conf" to /etc/alsa/conf.d, among other things. A far more comprehensive setup than one finds via Google. 2. Created the file ~/.asoundrc with the following content, necessary to be able to use "aplay -D bluealsa my_sound_file.wav". defaults.bluealsa { service "org.bluealsa" interface "hci0" device "20:05:13:08:06:9B" profile "a2dp" delay 1000 } However, the bluealsa device doesn't show up in /proc/asound or in the "aplay -l" command. 3. Not sure these steps are necessary: a. Create /etc/bluetooth/audio.conf and add: [General] Class = 0x20041C Enable = Source,Sink,Media,Socket b. Update /etc/bluetooth/main.conf with: [General] Class = 0x20041C 4. Paired the bluetooth earbuds, fairly easy with blueman-manager or bluetoothctl. Had to make them "Trusted". 5. In alsamixer, F6 does not show any bluealsa device. Instead, arrowed down to the "enter device name..." entry, selected it, and typed in "bluealsa". Then the "BlueALSA" card is shown, with its A2DP mixer control. 6. Added this as the second output device in ~/.mpd.conf: audio_output { type "alsa" name "bluealsa" device "bluealsa" mixer_type "software" } 7. Restart the system, log in, reconnect the bluetooth earbuds, start my MPD front end and enjoy, muting the other output(s). # vim: sw=4 ts=4 wm=8 et ft=rc ================================================ FILE: contrib/notes/bvi.text ================================================ Cheat Sheet for the bvi Hex Editor Chris Ahlstrom 2026-04-22 to 2026-04-22 Key options: -R Read-only viewing. -f script Run a script with a series of "ex" commands. Search patterns: . Any character [] set of characters * zero or more occurrences of previous char or set The ^ and $ anchors cannot be used. Search explicitly: \n newline \r return \t tab \0 binary zero /pat, ?pat Find ASCII pattern. \hex, #hex Find hex string. Example: "\24240013" Insertion and deletion can be enabled by :set memmove Input commands: i I A r R Exit input/cmd: ESC Switch panes: TAB Cancel command: CTRL-C Command line mode: / ? \ # ! Exit command mode: ESC Delete/rubout intr: DEL COMMAND SUMMARY: :version Show version info <- ... -> Arrow keys move the cursor h j k l Same as arrow keys u Undo previous change ZZ Exit bvi, saving changes :q! Quit, discarding changes /text Search for text ^U ^D Scroll up or down Numbers may be typed as a prefix to some commands: Screen column ⎪ Byte of file G Scroll amount ^D ^U Repeat effect Most of the rest of the commands :w Write changed buffer to file :w! Write override read-only ("forced" write) :q, :q! Quit, and discard all changes :e file, :e! Edit file, reread file, discard changes :e #, :e! # Edit the alternate file, discard changes :w file, :w! file Write current buffer to file, force overwrite :sh Run the command as set with option "shell" :!cmd Run the command cmd from "shell", then return :n Edit next file in the argument list :f, ^G Show current filename, modified flag, There are additional items in the bvi(1) man page, such as bit operations. Many stock vi commands apply in some ways, depending which pane is active. # vim: sw=3 ts=3 wm=8 et ft=sh ================================================ FILE: contrib/notes/clang-macros-freebsd.text ================================================ #define _LP64 1 #define __ATOMIC_ACQUIRE 2 #define __ATOMIC_ACQ_REL 4 #define __ATOMIC_CONSUME 1 #define __ATOMIC_RELAXED 0 #define __ATOMIC_RELEASE 3 #define __ATOMIC_SEQ_CST 5 #define __BIGGEST_ALIGNMENT__ 16 #define __BITINT_MAXWIDTH__ 8388608 #define __BOOL_WIDTH__ 8 #define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__ #define __CHAR16_TYPE__ unsigned short #define __CHAR32_TYPE__ unsigned int #define __CHAR_BIT__ 8 #define __CLANG_ATOMIC_BOOL_LOCK_FREE 2 #define __CLANG_ATOMIC_CHAR16_T_LOCK_FREE 2 #define __CLANG_ATOMIC_CHAR32_T_LOCK_FREE 2 #define __CLANG_ATOMIC_CHAR_LOCK_FREE 2 #define __CLANG_ATOMIC_INT_LOCK_FREE 2 #define __CLANG_ATOMIC_LLONG_LOCK_FREE 2 #define __CLANG_ATOMIC_LONG_LOCK_FREE 2 #define __CLANG_ATOMIC_POINTER_LOCK_FREE 2 #define __CLANG_ATOMIC_SHORT_LOCK_FREE 2 #define __CLANG_ATOMIC_WCHAR_T_LOCK_FREE 2 #define __CONSTANT_CFSTRINGS__ 1 #define __DBL_DECIMAL_DIG__ 17 #define __DBL_DENORM_MIN__ 4.9406564584124654e-324 #define __DBL_DIG__ 15 #define __DBL_EPSILON__ 2.2204460492503131e-16 #define __DBL_HAS_DENORM__ 1 #define __DBL_HAS_INFINITY__ 1 #define __DBL_HAS_QUIET_NAN__ 1 #define __DBL_MANT_DIG__ 53 #define __DBL_MAX_10_EXP__ 308 #define __DBL_MAX_EXP__ 1024 #define __DBL_MAX__ 1.7976931348623157e+308 #define __DBL_MIN_10_EXP__ (-307) #define __DBL_MIN_EXP__ (-1021) #define __DBL_MIN__ 2.2250738585072014e-308 #define __DECIMAL_DIG__ __LDBL_DECIMAL_DIG__ #define __ELF__ 1 #define __FINITE_MATH_ONLY__ 0 #define __FLT16_DECIMAL_DIG__ 5 #define __FLT16_DENORM_MIN__ 5.9604644775390625e-8F16 #define __FLT16_DIG__ 3 #define __FLT16_EPSILON__ 9.765625e-4F16 #define __FLT16_HAS_DENORM__ 1 #define __FLT16_HAS_INFINITY__ 1 #define __FLT16_HAS_QUIET_NAN__ 1 #define __FLT16_MANT_DIG__ 11 #define __FLT16_MAX_10_EXP__ 4 #define __FLT16_MAX_EXP__ 16 #define __FLT16_MAX__ 6.5504e+4F16 #define __FLT16_MIN_10_EXP__ (-4) #define __FLT16_MIN_EXP__ (-13) #define __FLT16_MIN__ 6.103515625e-5F16 #define __FLT_DECIMAL_DIG__ 9 #define __FLT_DENORM_MIN__ 1.40129846e-45F #define __FLT_DIG__ 6 #define __FLT_EPSILON__ 1.19209290e-7F #define __FLT_HAS_DENORM__ 1 #define __FLT_HAS_INFINITY__ 1 #define __FLT_HAS_QUIET_NAN__ 1 #define __FLT_MANT_DIG__ 24 #define __FLT_MAX_10_EXP__ 38 #define __FLT_MAX_EXP__ 128 #define __FLT_MAX__ 3.40282347e+38F #define __FLT_MIN_10_EXP__ (-37) #define __FLT_MIN_EXP__ (-125) #define __FLT_MIN__ 1.17549435e-38F #define __FLT_RADIX__ 2 #define __FXSR__ 1 #define __FreeBSD__ 14 #define __FreeBSD_cc_version 1400006 #define __GCC_ASM_FLAG_OUTPUTS__ 1 #define __GCC_ATOMIC_BOOL_LOCK_FREE 2 #define __GCC_ATOMIC_CHAR16_T_LOCK_FREE 2 #define __GCC_ATOMIC_CHAR32_T_LOCK_FREE 2 #define __GCC_ATOMIC_CHAR_LOCK_FREE 2 #define __GCC_ATOMIC_INT_LOCK_FREE 2 #define __GCC_ATOMIC_LLONG_LOCK_FREE 2 #define __GCC_ATOMIC_LONG_LOCK_FREE 2 #define __GCC_ATOMIC_POINTER_LOCK_FREE 2 #define __GCC_ATOMIC_SHORT_LOCK_FREE 2 #define __GCC_ATOMIC_TEST_AND_SET_TRUEVAL 1 #define __GCC_ATOMIC_WCHAR_T_LOCK_FREE 2 #define __GCC_HAVE_DWARF2_CFI_ASM 1 #define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1 1 #define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_2 1 #define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_4 1 #define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8 1 #define __GNUC_MINOR__ 2 #define __GNUC_PATCHLEVEL__ 1 #define __GNUC_STDC_INLINE__ 1 #define __GNUC__ 4 #define __GXX_ABI_VERSION 1002 #define __INT16_C_SUFFIX__ #define __INT16_FMTd__ "hd" #define __INT16_FMTi__ "hi" #define __INT16_MAX__ 32767 #define __INT16_TYPE__ short #define __INT32_C_SUFFIX__ #define __INT32_FMTd__ "d" #define __INT32_FMTi__ "i" #define __INT32_MAX__ 2147483647 #define __INT32_TYPE__ int #define __INT64_C_SUFFIX__ L #define __INT64_FMTd__ "ld" #define __INT64_FMTi__ "li" #define __INT64_MAX__ 9223372036854775807L #define __INT64_TYPE__ long int #define __INT8_C_SUFFIX__ #define __INT8_FMTd__ "hhd" #define __INT8_FMTi__ "hhi" #define __INT8_MAX__ 127 #define __INT8_TYPE__ signed char #define __INTMAX_C_SUFFIX__ L #define __INTMAX_FMTd__ "ld" #define __INTMAX_FMTi__ "li" #define __INTMAX_MAX__ 9223372036854775807L #define __INTMAX_TYPE__ long int #define __INTMAX_WIDTH__ 64 #define __INTPTR_FMTd__ "ld" #define __INTPTR_FMTi__ "li" #define __INTPTR_MAX__ 9223372036854775807L #define __INTPTR_TYPE__ long int #define __INTPTR_WIDTH__ 64 #define __INT_FAST16_FMTd__ "hd" #define __INT_FAST16_FMTi__ "hi" #define __INT_FAST16_MAX__ 32767 #define __INT_FAST16_TYPE__ short #define __INT_FAST16_WIDTH__ 16 #define __INT_FAST32_FMTd__ "d" #define __INT_FAST32_FMTi__ "i" #define __INT_FAST32_MAX__ 2147483647 #define __INT_FAST32_TYPE__ int #define __INT_FAST32_WIDTH__ 32 #define __INT_FAST64_FMTd__ "ld" #define __INT_FAST64_FMTi__ "li" #define __INT_FAST64_MAX__ 9223372036854775807L #define __INT_FAST64_TYPE__ long int #define __INT_FAST64_WIDTH__ 64 #define __INT_FAST8_FMTd__ "hhd" #define __INT_FAST8_FMTi__ "hhi" #define __INT_FAST8_MAX__ 127 #define __INT_FAST8_TYPE__ signed char #define __INT_FAST8_WIDTH__ 8 #define __INT_LEAST16_FMTd__ "hd" #define __INT_LEAST16_FMTi__ "hi" #define __INT_LEAST16_MAX__ 32767 #define __INT_LEAST16_TYPE__ short #define __INT_LEAST16_WIDTH__ 16 #define __INT_LEAST32_FMTd__ "d" #define __INT_LEAST32_FMTi__ "i" #define __INT_LEAST32_MAX__ 2147483647 #define __INT_LEAST32_TYPE__ int #define __INT_LEAST32_WIDTH__ 32 #define __INT_LEAST64_FMTd__ "ld" #define __INT_LEAST64_FMTi__ "li" #define __INT_LEAST64_MAX__ 9223372036854775807L #define __INT_LEAST64_TYPE__ long int #define __INT_LEAST64_WIDTH__ 64 #define __INT_LEAST8_FMTd__ "hhd" #define __INT_LEAST8_FMTi__ "hhi" #define __INT_LEAST8_MAX__ 127 #define __INT_LEAST8_TYPE__ signed char #define __INT_LEAST8_WIDTH__ 8 #define __INT_MAX__ 2147483647 #define __INT_WIDTH__ 32 #define __KPRINTF_ATTRIBUTE__ 1 #define __LDBL_DECIMAL_DIG__ 21 #define __LDBL_DENORM_MIN__ 3.64519953188247460253e-4951L #define __LDBL_DIG__ 18 #define __LDBL_EPSILON__ 1.08420217248550443401e-19L #define __LDBL_HAS_DENORM__ 1 #define __LDBL_HAS_INFINITY__ 1 #define __LDBL_HAS_QUIET_NAN__ 1 #define __LDBL_MANT_DIG__ 64 #define __LDBL_MAX_10_EXP__ 4932 #define __LDBL_MAX_EXP__ 16384 #define __LDBL_MAX__ 1.18973149535723176502e+4932L #define __LDBL_MIN_10_EXP__ (-4931) #define __LDBL_MIN_EXP__ (-16381) #define __LDBL_MIN__ 3.36210314311209350626e-4932L #define __LITTLE_ENDIAN__ 1 #define __LLONG_WIDTH__ 64 #define __LONG_LONG_MAX__ 9223372036854775807LL #define __LONG_MAX__ 9223372036854775807L #define __LONG_WIDTH__ 64 #define __LP64__ 1 #define __MMX__ 1 #define __NO_INLINE__ 1 #define __NO_MATH_ERRNO__ 1 #define __NO_MATH_INLINES 1 #define __OBJC_BOOL_IS_BOOL 0 #define __OPENCL_MEMORY_SCOPE_ALL_SVM_DEVICES 3 #define __OPENCL_MEMORY_SCOPE_DEVICE 2 #define __OPENCL_MEMORY_SCOPE_SUB_GROUP 4 #define __OPENCL_MEMORY_SCOPE_WORK_GROUP 1 #define __OPENCL_MEMORY_SCOPE_WORK_ITEM 0 #define __ORDER_BIG_ENDIAN__ 4321 #define __ORDER_LITTLE_ENDIAN__ 1234 #define __ORDER_PDP_ENDIAN__ 3412 #define __POINTER_WIDTH__ 64 #define __PRAGMA_REDEFINE_EXTNAME 1 #define __PTRDIFF_FMTd__ "ld" #define __PTRDIFF_FMTi__ "li" #define __PTRDIFF_MAX__ 9223372036854775807L #define __PTRDIFF_TYPE__ long int #define __PTRDIFF_WIDTH__ 64 #define __REGISTER_PREFIX__ #define __SCHAR_MAX__ 127 #define __SEG_FS 1 #define __SEG_GS 1 #define __SHRT_MAX__ 32767 #define __SHRT_WIDTH__ 16 #define __SIG_ATOMIC_MAX__ 2147483647 #define __SIG_ATOMIC_WIDTH__ 32 #define __SIZEOF_DOUBLE__ 8 #define __SIZEOF_FLOAT__ 4 #define __SIZEOF_INT128__ 16 #define __SIZEOF_INT__ 4 #define __SIZEOF_LONG_DOUBLE__ 16 #define __SIZEOF_LONG_LONG__ 8 #define __SIZEOF_LONG__ 8 #define __SIZEOF_POINTER__ 8 #define __SIZEOF_PTRDIFF_T__ 8 #define __SIZEOF_SHORT__ 2 #define __SIZEOF_SIZE_T__ 8 #define __SIZEOF_WCHAR_T__ 4 #define __SIZEOF_WINT_T__ 4 #define __SIZE_FMTX__ "lX" #define __SIZE_FMTo__ "lo" #define __SIZE_FMTu__ "lu" #define __SIZE_FMTx__ "lx" #define __SIZE_MAX__ 18446744073709551615UL #define __SIZE_TYPE__ long unsigned int #define __SIZE_WIDTH__ 64 #define __SSE2_MATH__ 1 #define __SSE2__ 1 #define __SSE_MATH__ 1 #define __SSE__ 1 #define __STDC_HOSTED__ 1 #define __STDC_MB_MIGHT_NEQ_WC__ 1 #define __STDC_UTF_16__ 1 #define __STDC_UTF_32__ 1 #define __STDC_VERSION__ 201710L #define __STDC__ 1 #define __UINT16_C_SUFFIX__ #define __UINT16_FMTX__ "hX" #define __UINT16_FMTo__ "ho" #define __UINT16_FMTu__ "hu" #define __UINT16_FMTx__ "hx" #define __UINT16_MAX__ 65535 #define __UINT16_TYPE__ unsigned short #define __UINT32_C_SUFFIX__ U #define __UINT32_FMTX__ "X" #define __UINT32_FMTo__ "o" #define __UINT32_FMTu__ "u" #define __UINT32_FMTx__ "x" #define __UINT32_MAX__ 4294967295U #define __UINT32_TYPE__ unsigned int #define __UINT64_C_SUFFIX__ UL #define __UINT64_FMTX__ "lX" #define __UINT64_FMTo__ "lo" #define __UINT64_FMTu__ "lu" #define __UINT64_FMTx__ "lx" #define __UINT64_MAX__ 18446744073709551615UL #define __UINT64_TYPE__ long unsigned int #define __UINT8_C_SUFFIX__ #define __UINT8_FMTX__ "hhX" #define __UINT8_FMTo__ "hho" #define __UINT8_FMTu__ "hhu" #define __UINT8_FMTx__ "hhx" #define __UINT8_MAX__ 255 #define __UINT8_TYPE__ unsigned char #define __UINTMAX_C_SUFFIX__ UL #define __UINTMAX_FMTX__ "lX" #define __UINTMAX_FMTo__ "lo" #define __UINTMAX_FMTu__ "lu" #define __UINTMAX_FMTx__ "lx" #define __UINTMAX_MAX__ 18446744073709551615UL #define __UINTMAX_TYPE__ long unsigned int #define __UINTMAX_WIDTH__ 64 #define __UINTPTR_FMTX__ "lX" #define __UINTPTR_FMTo__ "lo" #define __UINTPTR_FMTu__ "lu" #define __UINTPTR_FMTx__ "lx" #define __UINTPTR_MAX__ 18446744073709551615UL #define __UINTPTR_TYPE__ long unsigned int #define __UINTPTR_WIDTH__ 64 #define __UINT_FAST16_FMTX__ "hX" #define __UINT_FAST16_FMTo__ "ho" #define __UINT_FAST16_FMTu__ "hu" #define __UINT_FAST16_FMTx__ "hx" #define __UINT_FAST16_MAX__ 65535 #define __UINT_FAST16_TYPE__ unsigned short #define __UINT_FAST32_FMTX__ "X" #define __UINT_FAST32_FMTo__ "o" #define __UINT_FAST32_FMTu__ "u" #define __UINT_FAST32_FMTx__ "x" #define __UINT_FAST32_MAX__ 4294967295U #define __UINT_FAST32_TYPE__ unsigned int #define __UINT_FAST64_FMTX__ "lX" #define __UINT_FAST64_FMTo__ "lo" #define __UINT_FAST64_FMTu__ "lu" #define __UINT_FAST64_FMTx__ "lx" #define __UINT_FAST64_MAX__ 18446744073709551615UL #define __UINT_FAST64_TYPE__ long unsigned int #define __UINT_FAST8_FMTX__ "hhX" #define __UINT_FAST8_FMTo__ "hho" #define __UINT_FAST8_FMTu__ "hhu" #define __UINT_FAST8_FMTx__ "hhx" #define __UINT_FAST8_MAX__ 255 #define __UINT_FAST8_TYPE__ unsigned char #define __UINT_LEAST16_FMTX__ "hX" #define __UINT_LEAST16_FMTo__ "ho" #define __UINT_LEAST16_FMTu__ "hu" #define __UINT_LEAST16_FMTx__ "hx" #define __UINT_LEAST16_MAX__ 65535 #define __UINT_LEAST16_TYPE__ unsigned short #define __UINT_LEAST32_FMTX__ "X" #define __UINT_LEAST32_FMTo__ "o" #define __UINT_LEAST32_FMTu__ "u" #define __UINT_LEAST32_FMTx__ "x" #define __UINT_LEAST32_MAX__ 4294967295U #define __UINT_LEAST32_TYPE__ unsigned int #define __UINT_LEAST64_FMTX__ "lX" #define __UINT_LEAST64_FMTo__ "lo" #define __UINT_LEAST64_FMTu__ "lu" #define __UINT_LEAST64_FMTx__ "lx" #define __UINT_LEAST64_MAX__ 18446744073709551615UL #define __UINT_LEAST64_TYPE__ long unsigned int #define __UINT_LEAST8_FMTX__ "hhX" #define __UINT_LEAST8_FMTo__ "hho" #define __UINT_LEAST8_FMTu__ "hhu" #define __UINT_LEAST8_FMTx__ "hhx" #define __UINT_LEAST8_MAX__ 255 #define __UINT_LEAST8_TYPE__ unsigned char #define __USER_LABEL_PREFIX__ #define __VERSION__ "FreeBSD Clang 16.0.6 (https://github.com/llvm/llvm-project.git llvmorg-16.0.6-0-g7cbf1a259152)" #define __WCHAR_MAX__ 2147483647 #define __WCHAR_TYPE__ int #define __WCHAR_WIDTH__ 32 #define __WINT_MAX__ 2147483647 #define __WINT_TYPE__ int #define __WINT_WIDTH__ 32 #define __amd64 1 #define __amd64__ 1 #define __clang__ 1 #define __clang_literal_encoding__ "UTF-8" #define __clang_major__ 16 #define __clang_minor__ 0 #define __clang_patchlevel__ 6 #define __clang_version__ "16.0.6 (https://github.com/llvm/llvm-project.git llvmorg-16.0.6-0-g7cbf1a259152)" #define __clang_wide_literal_encoding__ "UTF-32" #define __code_model_small__ 1 #define __k8 1 #define __k8__ 1 #define __llvm__ 1 #define __seg_fs __attribute__((address_space(257))) #define __seg_gs __attribute__((address_space(256))) #define __tune_k8__ 1 #define __unix 1 #define __unix__ 1 #define __x86_64 1 #define __x86_64__ 1 #define unix 1 ================================================ FILE: contrib/notes/drum_notes_gm_2.text ================================================ MIDI Drum Notes for GM and GM 2 Chris Ahlstrom 2021-05-22 to 2021-05-22 This list comes from: https://soundprogramming.net/file-formats/general-midi-drum-note-numbers/ General MIDI Drums (Ch 10): The numbers listed correspond to the MIDI note number for that drum sound. Drum sounds added in General MIDI Level 2 are tagged with "(GM2)". 27 High Q (GM2) 28 Slap (GM2) 29 Scratch Push (GM2) 30 Scratch Pull (GM2) 31 Sticks (GM2) 32 Square Click (GM2) 33 Metronome Click (GM2) 34 Metronome Bell (GM2) 35 Bass Drum 2 36 Bass Drum 1 37 Side Stick 38 Snare Drum 1 39 Hand Clap 40 Snare Drum 2 41 Low Tom 2 42 Closed Hi-hat 43 Low Tom 1 44 Pedal Hi-hat 45 Mid Tom 2 46 Open Hi-hat 47 Mid Tom 1 48 High Tom 2 49 Crash Cymbal 1 50 High Tom 1 51 Ride Cymbal 1 52 Chinese Cymbal 53 Ride Bell 54 Tambourine 55 Splash Cymbal 56 Cowbell 57 Crash Cymbal 2 58 Vibra Slap 59 Ride Cymbal 2 60 High Bongo 61 Low Bongo 62 Mute High Conga 63 Open High Conga 64 Low Conga 65 High Timbale 66 Low Timbale 67 High Agogo 68 Low Agogo 69 Cabasa 70 Maracas 71 Short Whistle 72 Long Whistle 73 Short Guiro 74 Long Guiro 75 Claves 76 High Wood Block 77 Low Wood Block 78 Mute Cuica 79 Open Cuica 80 Mute Triangle 81 Open Triangle 82 Shaker (GM2) 83 Jingle Bell (GM2) 84 Belltree (GM2) 85 Castanets (GM2) 86 Mute Surdo (GM2) 87 Open Surdo (GM2) # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/notes/event-from-bytes.text ================================================ SysEx Continuation: SysEx (System Exclusive) messages in MIDI can be continued across multiple packets, often with the use of a continuation byte (0xF7). This is necessary when the SysEx data exceeds the maximum length of a single MIDI message or when there's a need to interleave other MIDI messages within a SysEx stream. Here's a breakdown of how SysEx continuation works: 1. The Problem: Standard MIDI messages have a limited length, and SysEx messages can be quite long, especially when dealing with patch data or bulk dumps. Sometimes, a device might need to interrupt a large SysEx transfer with other MIDI messages (like note ons or control changes). 2. The Solution: SysEx Continuation (F7) A SysEx message starts with the status byte 0xF0 (Start). It's followed by the manufacturer's ID, device ID, and the actual data. The message ends with 0xF7 (End/Escape). To send a long SysEx message, it's split into smaller chunks. Each chunk, except the last one, ends with 0xF7, indicating that there is more data to follow. The receiving device recognizes these 0xF7 bytes as continuation markers and knows to expect the next chunk. 3. Interleaved Messages When a device needs to send other MIDI messages (like note or control changes) while a SysEx transfer is in progress, it can do so by sending the regular MIDI messages, then resuming the SysEx stream with the 0xF7 continuation byte. The receiver must be able to distinguish between the continuation byte and the end-of-SysEx byte, as both are 0xF7. 4. Escaped Events (F7 as Escape) The 0xF7 byte can also be used as an "escape" character to embed other MIDI messages (like realtime or common messages) within a SysEx stream. In this case, the 0xF7 byte is followed by a standard MIDI status byte (e.g., 0xF1, 0xF2), indicating an escaped message. This allows for embedding commands for other devices, or control messages for the same device, into a SysEx stream. In essence, the 0xF7 byte acts as a signal for either continuing a SysEx message or embedding other MIDI data within it, providing flexibility in how SysEx messages are structured and transmitted. ================================================ FILE: contrib/notes/freebsd.text ================================================ Notes on FreeBSD and Its Issues Chris Ahlstrom 2024-01-13 to 2024-01-13 FreeBSD comes with issues regarding ALSA and JACK. This file describes various things one needs to tackle to get Seq66 running on FreeBSD Release 13. Also see the FreeBSD section of the INSTALL file. We are still working through issues; once resolved, the corrective actions will be included in the Seq66 User Manual. Make sure that the following packages are installed (check with "pkg info"): - For GNU automake (currently has serious issues): - autoconf - autoconf-archive - autoconf-switch - automake - gmake - libtool - pkgconf - Qt 5: - qt5; one might have to run "pkg upgrade" to make sure that Qt5 will run properly in Seq66. - qmake - ALSA - alsa-lib - alsa-seq-server; run "kldload cuse" before running this server. All as root. - alsa-plugins - alsa-utils - a2jmidid - JACK - jackit - jack_umidi - jack-example-tools (for jack_lsp, etc.) - liblo for OSC and NSM support - Other utilities: - usbutils Because of the issues noted below, we use qmake in lieu of automake via the qbuild.sh script found in contrib/scripts: $ qbuild.sh $ vi make.log This builds the rtmidi-based internal Seq66 MIDI engine. This supports JACK and ALSA, with the former working better. The portmidi-based internal MIDI engine also uses ALSA; ALSA is a little more problematic than JACK. We have work to do! If using qjackctl to set up JACK, select the "oss" engine in that application. ALSA/MIDI is explicitly NOT SUPPORTED by that application, as noted in its Help / About. Consider setting the following in /etc/rc.conf: jackd_enable="YES" jackd_user = "ate" ??? jackd_rtprio="YES" jackd_args="-R -doss -r48000 -p1024 -n3 -w16 --capture /dev/dspN --playback /dev/dspN" We did not have to do this to get Seq66 to play to Qsynth using JACK. Seq66 in FreeBSD running JACK will show software MIDI ports like jack-keyboard and qsynth. However, we do not see any USB MIDI devices, even though snd_uaudio is loaded. Perhaps this is needed: $ jackd -r -d oss -r44100 -n2 -w16 -C/dev/dsp1.5 -P/dev/dsp1.6 & $ sleep 1 $ jack_umidi -d /dev/umidi0.0 -B & This apparently converts the umidi device to a TCP-based JACK instance. Loopback: "dd if=/dev/umidi0.0 of=/dev/umidi0.0 bs=1" USB Devices: usbconfig -u 0 -a 3 ... snd_uaudio apparently works in Ubuntu as well. Something to look into. Read snd_uaudio(4) on FreeBSD. Hmmm: $ fluidsynth /usr/local/share/generaluser-gs/GeneralUser_GS.sf2 & $ midicat -d -q midi/0 -q midithru/0 To try: Verify that the midi controller works: # ln -sf /dev/umidi*.0 /dev/midi $ fluidsynth -m oss -a oss path_to/some.sf2. Verify sound when operating the controller, which shows that the USB MIDI messages appear on /dev/midi. ALSA: $ ls /dev/*midi* # alsa-seq-server -d /dev/umidi1.0 $ ls /dev/*seq* /dev/sequencer0 Note that umidi is read/write with raw MIDI. playumidi: "playumidi" is a MIDI file player for USB MIDI devices on FreeBSD. It can control the FreeBSD-umidi devices (/dev/umidi*.*) via the "uaudio" driver, not needing the "/dev/sequencer" device or other software for listening to a MIDI file. Automake Issues: Currently having issues with Qt in the GNU automake instructions below. So do the following instead: - Make a shadow directory in the parent of the seq66 directory. - Change to it. - Run the new qbuild.sh script. It uses "sh" as the shell for portability. - Read the make.log file for success or failure. Although one user reported that "./configure" worked fine, on FreeBSD we had issues, even with the following commands: $ ./configure --with-alsa-prefix=/usr/local/lib --with-alsa-inc-prefix=/usr/local/include or $ ./configure --disable-alsatext $ sh -c 'gmake >make.log 2>&1' A raft of other issues have occurred with this method. For example, the QT_CXXFLAGS and QT_LIBS macros are empty! This causes spurious notes about "stopped in /tmp/xxxx" to be written to the generated Makefiles. # vim: sw=4 ts=4 wm=8 et ft=rc ================================================ FILE: contrib/notes/gcc-version.text ================================================ Setting the gcc and g++ version on Ubuntu and Debian Chris Ahlstrom 2019-04-20 to 2020-03-20 I had a problem using std::bind() that I could not figure out, with a construct the looked perfectly legal to me. My Ubuntu laptop was using gcc/g++ version 7. So I install gcc/g++ and wanted to make version 8 the default. The update-alternative program did not include gcc/g++! After some Googoo-fu and reading the following link, here are the instructions that work. http://charette.no-ip.com:81/programming/2011-12-24_GCCv47/ We need to let update-alternatives know we have two C/C++ compilers, create a record for each one, and then configure which one we want to use. This is done with the following steps. First, a quick check: $ ls /usr/bin/gcc* /usr/bin/gcc /usr/bin/gcc-ar /usr/bin/gcc-nm /usr/bin/gcc-ranlib /usr/bin/gcc-7 /usr/bin/gcc-ar-7 /usr/bin/gcc-nm-7 /usr/bin/gcc-ranlib-7 /usr/bin/gcc-8 /usr/bin/gcc-ar-8 /usr/bin/gcc-nm-8 /usr/bin/gcc-ranlib-8 $ ls /usr/bin/g++* /usr/bin/g++ /usr/bin/g++-7 /usr/bin/g++-8 $ ls -l /usr/bin/gcc /usr/bin/g++ lrwxrwxrwx 1 root root 5 Sep 17 2018 /usr/bin/g++ -> g++-7 lrwxrwxrwx 1 root root 5 Sep 17 2018 /usr/bin/gcc -> gcc-7 We will assume that changing the compilers also brings in the correct versions of the "ar", "nm", and "ranlib" programs. $ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 40 \ --slave /usr/bin/g++ g++ /usr/bin/g++-7 $ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 80 \ --slave /usr/bin/g++ g++ /usr/bin/g++-8 We give the newer versions of gcc/g++ a higher priority. The "install" parameters specify the master link, the name of the symlink in the "/etc/alternatives" directory, the path to the actual binary, and the priority of the link... a higher number is higher priority. Note the use of "--slave". This ensures when we change the configuration for gcc, that we automatically update the configuration for g++. This is important so the compilers aren not out-of-sync. From this point forward, the only thing required when switching compilers is this (relatively) simple command, run as root or via sudo: $ sudo update-alternatives --config gcc There are 2 choices for the alternative gcc (providing /usr/bin/gcc). Selection Path Priority Status ------------------------------------------------------------ * 0 /usr/bin/gcc-8 60 auto mode 1 /usr/bin/gcc-7 40 manual mode 2 /usr/bin/gcc-8 60 manual mode The default is now the version we want. $ gcc --version gcc (Ubuntu 8.2.0-1ubuntu2~18.04) 8.2.0 $ g++ --version g++ (Ubuntu 8.2.0-1ubuntu2~18.04) 8.2.0 As a final check: $ ls -l /usr/bin/gcc /usr/bin/g++ /etc/alternatives/gcc* /etc/alternatives/g++* lrwxrwxrwx 1 root root 14 Apr 20 05:16 /etc/alternatives/g++ -> /usr/bin/g++-8 lrwxrwxrwx 1 root root 14 Apr 20 05:16 /etc/alternatives/gcc -> /usr/bin/gcc-8 lrwxrwxrwx 1 root root 21 Apr 20 05:16 /usr/bin/g++ -> /etc/alternatives/g++ lrwxrwxrwx 1 root root 21 Apr 20 05:16 /usr/bin/gcc -> /etc/alternatives/gcc Now let us rebuild our program. Now, we had a lot of issues building this code on a Debian Sid laptop. The issue turned out to be some weird stuff going on between autoconf/automake and gcc/g++ 9. So we have modified the makefiles to try to use gcc/g++ 8 first. From configure.ac: AC_PROG_CC([gcc-8 gcc]) AC_PROG_CXX([g++-8 g++]) AC_PROG_CXXCPP([cpp-8 cpp]) # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/notes/install-directories.text ================================================ Installation directories for Seq66 in Linux and Windows Chris Ahlstrom 2023-05-26 to 2023-07-19 Linux: Let PREFIX = "/usr" or "/usr/local" for the base of the installation area. (Is there a base for a "~/.local"-based installation?) Let HOME = "/home/username". Header files: PREFIX/include/seq66-0.99/ and subdirectories Executable: PREFIX/bin/ Libraries: PREFIX/lib/seq66-0.00/ Man pages: PREFIX/man/man1/ Desktop files: PREFIX/share/applications/ Data/samples: PREFIX/share/seq66-0.99/ Icons/pixmaps: PREFIX/share/seq66-0.99/icons/hicolor// PREFIX/share/seq66-0.99/pixmaps/ PREFIX/share/pixmaps/ Documentation: PREFIX/share/doc/seq66-0.99/ including tutorial directory Configuration: HOME/.config/seq66/ (default location) Windows: Let PREFIX = "C:/Program Files" (64-bit) Let HOME = "C:/Users/username". Header files: Not installed on Windows Executable: PREFIX/Seq66 Libraries: PREFIX/Seq66 (C/C++, pthread, and Qt libraries only) Man pages: Not installed on Windows Desktop files: N/A Data/samples: PREFIX/Seq66/data Icons/pixmaps: To do. Documentation: PREFIX/Seq66/doc Configuration: HOME/AppData/Local/seq66 (default location) Lnk files: HOME/AppData/Roaming/Microsoft/Windows/Recent (seq66.lnk) Install/Un lnk: HOME/AppData/Roaming/Microsoft/Windows/Start Menu/ /Programs/Seq66/ (Seq66.lnk and Uninstall Seq66.lnk) # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/notes/key-maps-dump.text ================================================ key-maps-dump.text Chris Ahlstrom 2023-09-02 This file shows the maps in the keymap module. The key-code maps is an std::multimap, and the keyname map is a normal std::map. ====================== Key Code Map ==================== Key[ 0] = ev key 32 { 0x20 <-- code 0x00000020, ' Space', mod 0x 0 } Key[ 1] = ev key 33 { 0x21 <-- code 0x00000021, ' !', mod 0x2000000 } Key[ 2] = ev key 34 { 0x22 <-- code 0x00000022, ' "', mod 0x2000000 } Key[ 3] = ev key 35 { 0x23 <-- code 0x00000023, ' #', mod 0x2000000 } Key[ 4] = ev key 36 { 0x24 <-- code 0x00000024, ' $', mod 0x2000000 } Key[ 5] = ev key 37 { 0x25 <-- code 0x00000025, ' %', mod 0x2000000 } Key[ 6] = ev key 38 { 0x26 <-- code 0x00000026, ' &', mod 0x2000000 } Key[ 7] = ev key 39 { 0x27 <-- code 0x00000027, ' '', mod 0x2000000 } Key[ 8] = ev key 40 { 0x28 <-- code 0x00000028, ' (', mod 0x2000000 } Key[ 9] = ev key 41 { 0x29 <-- code 0x00000029, ' )', mod 0x2000000 } Key[ 10] = ev key 42 { 0x2a <-- code 0x0000002a, ' *', mod 0x2000000 } Key[ 11] = ev key 42 { 0xd0 <-- code 0x0000002a, ' KP_*', mod 0x20000000 } Key[ 12] = ev key 43 { 0x2b <-- code 0x0000002b, ' +', mod 0x2000000 } Key[ 13] = ev key 43 { 0xd1 <-- code 0x0000002b, ' KP_+', mod 0x20000000 } Key[ 14] = ev key 44 { 0x2c <-- code 0x0000002c, ' ,', mod 0x 0 } Key[ 15] = ev key 44 { 0xd2 <-- code 0x0000002c, ' KP_,', mod 0x20000000 } Key[ 16] = ev key 45 { 0x2d <-- code 0x0000002d, ' -', mod 0x 0 } Key[ 17] = ev key 45 { 0xd3 <-- code 0x0000002d, ' KP_-', mod 0x20000000 } Key[ 18] = ev key 46 { 0x2e <-- code 0x0000002e, ' .', mod 0x 0 } Key[ 19] = ev key 46 { 0xd4 <-- code 0x0000002e, ' KP_.', mod 0x22000000 } Key[ 20] = ev key 46 { 0xd9 <-- code 0x0000002e, ' KP_.', mod 0x20000000 } Key[ 21] = ev key 47 { 0x2f <-- code 0x0000002f, ' /', mod 0x 0 } Key[ 22] = ev key 47 { 0xd5 <-- code 0x0000002f, ' KP_/', mod 0x20000000 } Key[ 23] = ev key 48 { 0x30 <-- code 0x00000030, ' 0', mod 0x 0 } Key[ 24] = ev key 49 { 0x31 <-- code 0x00000031, ' 1', mod 0x 0 } Key[ 25] = ev key 50 { 0x32 <-- code 0x00000032, ' 2', mod 0x 0 } Key[ 26] = ev key 51 { 0x33 <-- code 0x00000033, ' 3', mod 0x 0 } Key[ 27] = ev key 52 { 0x34 <-- code 0x00000034, ' 4', mod 0x 0 } Key[ 28] = ev key 53 { 0x35 <-- code 0x00000035, ' 5', mod 0x 0 } Key[ 29] = ev key 54 { 0x36 <-- code 0x00000036, ' 6', mod 0x 0 } Key[ 30] = ev key 55 { 0x37 <-- code 0x00000037, ' 7', mod 0x 0 } Key[ 31] = ev key 56 { 0x38 <-- code 0x00000038, ' 8', mod 0x 0 } Key[ 32] = ev key 57 { 0x39 <-- code 0x00000039, ' 9', mod 0x 0 } Key[ 33] = ev key 58 { 0x3a <-- code 0x0000003a, ' :', mod 0x2000000 } Key[ 34] = ev key 59 { 0x3b <-- code 0x0000003b, ' ;', mod 0x 0 } Key[ 35] = ev key 60 { 0x3c <-- code 0x0000003c, ' <', mod 0x2000000 } Key[ 36] = ev key 61 { 0x3d <-- code 0x0000003d, ' =', mod 0x 0 } Key[ 37] = ev key 62 { 0x3e <-- code 0x0000003e, ' >', mod 0x2000000 } Key[ 38] = ev key 63 { 0x3f <-- code 0x0000003f, ' ?', mod 0x2000000 } Key[ 39] = ev key 64 { 0x40 <-- code 0x00000040, ' @', mod 0x2000000 } Key[ 40] = ev key 65 { 0x 1 <-- code 0x00000041, ' SOH', mod 0x4000000 } Key[ 41] = ev key 65 { 0x41 <-- code 0x00000041, ' A', mod 0x2000000 } Key[ 42] = ev key 65 { 0x61 <-- code 0x00000041, ' a', mod 0x 0 } Key[ 43] = ev key 66 { 0x 2 <-- code 0x00000042, ' STX', mod 0x4000000 } Key[ 44] = ev key 66 { 0x42 <-- code 0x00000042, ' B', mod 0x2000000 } Key[ 45] = ev key 66 { 0x62 <-- code 0x00000042, ' b', mod 0x 0 } Key[ 46] = ev key 67 { 0x 3 <-- code 0x00000043, ' ETX', mod 0x4000000 } Key[ 47] = ev key 67 { 0x43 <-- code 0x00000043, ' C', mod 0x2000000 } Key[ 48] = ev key 67 { 0x63 <-- code 0x00000043, ' c', mod 0x 0 } Key[ 49] = ev key 68 { 0x 4 <-- code 0x00000044, ' EOT', mod 0x4000000 } Key[ 50] = ev key 68 { 0x44 <-- code 0x00000044, ' D', mod 0x2000000 } Key[ 51] = ev key 68 { 0x64 <-- code 0x00000044, ' d', mod 0x 0 } Key[ 52] = ev key 69 { 0x 5 <-- code 0x00000045, ' ENQ', mod 0x4000000 } Key[ 53] = ev key 69 { 0x45 <-- code 0x00000045, ' E', mod 0x2000000 } Key[ 54] = ev key 69 { 0x65 <-- code 0x00000045, ' e', mod 0x 0 } Key[ 55] = ev key 70 { 0x 6 <-- code 0x00000046, ' ACK', mod 0x4000000 } Key[ 56] = ev key 70 { 0x46 <-- code 0x00000046, ' F', mod 0x2000000 } Key[ 57] = ev key 70 { 0x66 <-- code 0x00000046, ' f', mod 0x 0 } Key[ 58] = ev key 71 { 0x 7 <-- code 0x00000047, ' BEL', mod 0x4000000 } Key[ 59] = ev key 71 { 0x47 <-- code 0x00000047, ' G', mod 0x2000000 } Key[ 60] = ev key 71 { 0x67 <-- code 0x00000047, ' g', mod 0x 0 } Key[ 61] = ev key 72 { 0x 8 <-- code 0x00000048, ' BS', mod 0x4000000 } Key[ 62] = ev key 72 { 0x48 <-- code 0x00000048, ' H', mod 0x2000000 } Key[ 63] = ev key 72 { 0x68 <-- code 0x00000048, ' h', mod 0x 0 } Key[ 64] = ev key 73 { 0x 9 <-- code 0x00000049, ' HT', mod 0x4000000 } Key[ 65] = ev key 73 { 0x49 <-- code 0x00000049, ' I', mod 0x2000000 } Key[ 66] = ev key 73 { 0x69 <-- code 0x00000049, ' i', mod 0x 0 } Key[ 67] = ev key 74 { 0x a <-- code 0x0000004a, ' LF', mod 0x4000000 } Key[ 68] = ev key 74 { 0x4a <-- code 0x0000004a, ' J', mod 0x2000000 } Key[ 69] = ev key 74 { 0x6a <-- code 0x0000004a, ' j', mod 0x 0 } Key[ 70] = ev key 75 { 0x b <-- code 0x0000004b, ' VT', mod 0x4000000 } Key[ 71] = ev key 75 { 0x4b <-- code 0x0000004b, ' K', mod 0x2000000 } Key[ 72] = ev key 75 { 0x6b <-- code 0x0000004b, ' k', mod 0x 0 } Key[ 73] = ev key 76 { 0x c <-- code 0x0000004c, ' FF', mod 0x4000000 } Key[ 74] = ev key 76 { 0x4c <-- code 0x0000004c, ' L', mod 0x2000000 } Key[ 75] = ev key 76 { 0x6c <-- code 0x0000004c, ' l', mod 0x 0 } Key[ 76] = ev key 77 { 0x d <-- code 0x0000004d, ' CR', mod 0x4000000 } Key[ 77] = ev key 77 { 0x4d <-- code 0x0000004d, ' M', mod 0x2000000 } Key[ 78] = ev key 77 { 0x6d <-- code 0x0000004d, ' m', mod 0x 0 } Key[ 79] = ev key 78 { 0x e <-- code 0x0000004e, ' SO', mod 0x4000000 } Key[ 80] = ev key 78 { 0x4e <-- code 0x0000004e, ' N', mod 0x2000000 } Key[ 81] = ev key 78 { 0x6e <-- code 0x0000004e, ' n', mod 0x 0 } Key[ 82] = ev key 79 { 0x f <-- code 0x0000004f, ' SI', mod 0x4000000 } Key[ 83] = ev key 79 { 0x4f <-- code 0x0000004f, ' O', mod 0x2000000 } Key[ 84] = ev key 79 { 0x6f <-- code 0x0000004f, ' o', mod 0x 0 } Key[ 85] = ev key 80 { 0x10 <-- code 0x00000050, ' DLE', mod 0x4000000 } Key[ 86] = ev key 80 { 0x50 <-- code 0x00000050, ' P', mod 0x2000000 } Key[ 87] = ev key 80 { 0x70 <-- code 0x00000050, ' p', mod 0x 0 } Key[ 88] = ev key 81 { 0x11 <-- code 0x00000051, ' DC1', mod 0x4000000 } Key[ 89] = ev key 81 { 0x51 <-- code 0x00000051, ' Q', mod 0x2000000 } Key[ 90] = ev key 81 { 0x71 <-- code 0x00000051, ' q', mod 0x 0 } Key[ 91] = ev key 82 { 0x12 <-- code 0x00000052, ' DC2', mod 0x4000000 } Key[ 92] = ev key 82 { 0x52 <-- code 0x00000052, ' R', mod 0x2000000 } Key[ 93] = ev key 82 { 0x72 <-- code 0x00000052, ' r', mod 0x 0 } Key[ 94] = ev key 83 { 0x13 <-- code 0x00000053, ' DC3', mod 0x4000000 } Key[ 95] = ev key 83 { 0x53 <-- code 0x00000053, ' S', mod 0x2000000 } Key[ 96] = ev key 83 { 0x73 <-- code 0x00000053, ' s', mod 0x 0 } Key[ 97] = ev key 84 { 0x14 <-- code 0x00000054, ' DC4', mod 0x4000000 } Key[ 98] = ev key 84 { 0x54 <-- code 0x00000054, ' T', mod 0x2000000 } Key[ 99] = ev key 84 { 0x74 <-- code 0x00000054, ' t', mod 0x 0 } Key[100] = ev key 85 { 0x15 <-- code 0x00000055, ' NAK', mod 0x4000000 } Key[101] = ev key 85 { 0x55 <-- code 0x00000055, ' U', mod 0x2000000 } Key[102] = ev key 85 { 0x75 <-- code 0x00000055, ' u', mod 0x 0 } Key[103] = ev key 86 { 0x16 <-- code 0x00000056, ' SYN', mod 0x4000000 } Key[104] = ev key 86 { 0x56 <-- code 0x00000056, ' V', mod 0x2000000 } Key[105] = ev key 86 { 0x76 <-- code 0x00000056, ' v', mod 0x 0 } Key[106] = ev key 87 { 0x17 <-- code 0x00000057, ' ETB', mod 0x4000000 } Key[107] = ev key 87 { 0x57 <-- code 0x00000057, ' W', mod 0x2000000 } Key[108] = ev key 87 { 0x77 <-- code 0x00000057, ' w', mod 0x 0 } Key[109] = ev key 88 { 0x18 <-- code 0x00000058, ' CAN', mod 0x4000000 } Key[110] = ev key 88 { 0x58 <-- code 0x00000058, ' X', mod 0x2000000 } Key[111] = ev key 88 { 0x78 <-- code 0x00000058, ' x', mod 0x 0 } Key[112] = ev key 89 { 0x19 <-- code 0x00000059, ' EM', mod 0x4000000 } Key[113] = ev key 89 { 0x59 <-- code 0x00000059, ' Y', mod 0x2000000 } Key[114] = ev key 89 { 0x79 <-- code 0x00000059, ' y', mod 0x 0 } Key[115] = ev key 90 { 0x1a <-- code 0x0000005a, ' SUB', mod 0x4000000 } Key[116] = ev key 90 { 0x5a <-- code 0x0000005a, ' Z', mod 0x2000000 } Key[117] = ev key 90 { 0x7a <-- code 0x0000005a, ' z', mod 0x 0 } Key[118] = ev key 91 { 0x1b <-- code 0x0000005b, ' ESC', mod 0x4000000 } Key[119] = ev key 91 { 0x5b <-- code 0x0000005b, ' [', mod 0x 0 } Key[120] = ev key 92 { 0x1c <-- code 0x0000005c, ' FS', mod 0x4000000 } Key[121] = ev key 92 { 0x5c <-- code 0x0000005c, ' \', mod 0x 0 } Key[122] = ev key 93 { 0x1d <-- code 0x0000005d, ' GS', mod 0x4000000 } Key[123] = ev key 93 { 0x5d <-- code 0x0000005d, ' ]', mod 0x 0 } Key[124] = ev key 94 { 0x1e <-- code 0x0000005e, ' RS', mod 0x6000000 } Key[125] = ev key 94 { 0x5e <-- code 0x0000005e, ' ^', mod 0x2000000 } Key[126] = ev key 95 { 0x1f <-- code 0x0000005f, ' US', mod 0x6000000 } Key[127] = ev key 95 { 0x5f <-- code 0x0000005f, ' _', mod 0x2000000 } Key[128] = ev key 96 { 0x60 <-- code 0x00000060, ' `', mod 0x 0 } Key[129] = ev key 123 { 0x7b <-- code 0x0000007b, ' {', mod 0x2000000 } Key[130] = ev key 124 { 0x7c <-- code 0x0000007c, ' |', mod 0x2000000 } Key[131] = ev key 125 { 0x7d <-- code 0x0000007d, ' }', mod 0x2000000 } Key[132] = ev key 126 { 0x7e <-- code 0x0000007e, ' ~', mod 0x2000000 } Key[133] = ev key 127 { 0x7f <-- code 0x0000007f, ' DEL', mod 0x 0 } Key[134] = ev key 136 { 0x88 <-- code 0x00000088, ' 0x88', mod 0x 0 } Key[135] = ev key 137 { 0x89 <-- code 0x00000089, ' 0x89', mod 0x 0 } Key[136] = ev key 16777216 { 0x80 <-- code 0x01000000, ' Esc', mod 0x 0 } Key[137] = ev key 16777217 { 0x81 <-- code 0x01000001, ' Tab', mod 0x 0 } Key[138] = ev key 16777218 { 0x82 <-- code 0x01000002, ' BkTab', mod 0x2000000 } Key[139] = ev key 16777219 { 0x83 <-- code 0x01000003, ' BkSpace', mod 0x 0 } Key[140] = ev key 16777220 { 0x84 <-- code 0x01000004, ' Return', mod 0x 0 } Key[141] = ev key 16777221 { 0x85 <-- code 0x01000005, ' Enter', mod 0x20000000 } Key[142] = ev key 16777222 { 0x86 <-- code 0x01000006, ' Ins', mod 0x 0 } Key[143] = ev key 16777222 { 0xc0 <-- code 0x01000006, ' KP_Ins', mod 0x20000000 } Key[144] = ev key 16777223 { 0x87 <-- code 0x01000007, ' Del', mod 0x 0 } Key[145] = ev key 16777223 { 0xc1 <-- code 0x01000007, ' KP_Del', mod 0x20000000 } Key[146] = ev key 16777224 { 0xc2 <-- code 0x01000008, ' Pause', mod 0x2000000 } Key[147] = ev key 16777225 { 0xc3 <-- code 0x01000009, ' Print', mod 0x2000000 } Key[148] = ev key 16777226 { 0x8a <-- code 0x0100000a, ' SysReq', mod 0x 0 } Key[149] = ev key 16777227 { 0x8b <-- code 0x0100000b, ' Clear', mod 0x 0 } Key[150] = ev key 16777228 { 0x8c <-- code 0x0100000c, ' 0x8c', mod 0x 0 } Key[151] = ev key 16777229 { 0x8d <-- code 0x0100000d, ' 0x8d', mod 0x 0 } Key[152] = ev key 16777230 { 0x8e <-- code 0x0100000e, ' 0x8e', mod 0x 0 } Key[153] = ev key 16777231 { 0x8f <-- code 0x0100000f, ' 0x8f', mod 0x 0 } Key[154] = ev key 16777232 { 0x90 <-- code 0x01000010, ' Home', mod 0x 0 } Key[155] = ev key 16777232 { 0xc4 <-- code 0x01000010, ' KP_Home', mod 0x20000000 } Key[156] = ev key 16777233 { 0x91 <-- code 0x01000011, ' End', mod 0x 0 } Key[157] = ev key 16777233 { 0xc5 <-- code 0x01000011, ' KP_End', mod 0x20000000 } Key[158] = ev key 16777234 { 0x92 <-- code 0x01000012, ' Left', mod 0x 0 } Key[159] = ev key 16777234 { 0xc6 <-- code 0x01000012, ' KP_Left', mod 0x20000000 } Key[160] = ev key 16777235 { 0x93 <-- code 0x01000013, ' Up', mod 0x 0 } Key[161] = ev key 16777235 { 0xc7 <-- code 0x01000013, ' KP_Up', mod 0x20000000 } Key[162] = ev key 16777236 { 0x94 <-- code 0x01000014, ' Right', mod 0x 0 } Key[163] = ev key 16777236 { 0xc8 <-- code 0x01000014, ' KP_Right', mod 0x20000000 } Key[164] = ev key 16777237 { 0x95 <-- code 0x01000015, ' Down', mod 0x 0 } Key[165] = ev key 16777237 { 0xc9 <-- code 0x01000015, ' KP_Down', mod 0x20000000 } Key[166] = ev key 16777238 { 0x96 <-- code 0x01000016, ' PageUp', mod 0x 0 } Key[167] = ev key 16777238 { 0xca <-- code 0x01000016, 'KP_PageUp', mod 0x20000000 } Key[168] = ev key 16777239 { 0x97 <-- code 0x01000017, ' PageDn', mod 0x 0 } Key[169] = ev key 16777239 { 0xcb <-- code 0x01000017, 'KP_PageDn', mod 0x20000000 } Key[170] = ev key 16777248 { 0x98 <-- code 0x01000020, ' Shift_L', mod 0x2000000 } Key[171] = ev key 16777248 { 0xd7 <-- code 0x01000020, ' Shift_R', mod 0x2000000 } Key[172] = ev key 16777248 { 0xdb <-- code 0x01000020, ' Shift_Lr', mod 0x 0 } Key[173] = ev key 16777248 { 0xdc <-- code 0x01000020, ' Shift_Rr', mod 0x 0 } Key[174] = ev key 16777249 { 0x99 <-- code 0x01000021, ' Ctrl_L', mod 0x4000000 } Key[175] = ev key 16777249 { 0xd8 <-- code 0x01000021, ' Ctrl_R', mod 0x4000000 } Key[176] = ev key 16777249 { 0xdd <-- code 0x01000021, ' Ctrl_Lr', mod 0x 0 } Key[177] = ev key 16777249 { 0xde <-- code 0x01000021, ' Ctrl_Rr', mod 0x 0 } Key[178] = ev key 16777250 { 0x9a <-- code 0x01000022, ' Meta', mod 0x10000000 } Key[179] = ev key 16777251 { 0x9b <-- code 0x01000023, ' Alt_L', mod 0x8000000 } Key[180] = ev key 16777251 { 0xda <-- code 0x01000023, ' Alt_R', mod 0x40000000 } Key[181] = ev key 16777252 { 0x9c <-- code 0x01000024, ' CapsLk', mod 0x 0 } Key[182] = ev key 16777253 { 0x9d <-- code 0x01000025, ' NumLk', mod 0x 0 } Key[183] = ev key 16777254 { 0x9e <-- code 0x01000026, ' ScrlLk', mod 0x 0 } Key[184] = ev key 16777255 { 0x9f <-- code 0x01000027, ' 0x9f', mod 0x 0 } Key[185] = ev key 16777264 { 0xa0 <-- code 0x01000030, ' F1', mod 0x 0 } Key[186] = ev key 16777264 { 0xb4 <-- code 0x01000030, ' Sh_F1', mod 0x2000000 } Key[187] = ev key 16777265 { 0xa1 <-- code 0x01000031, ' F2', mod 0x 0 } Key[188] = ev key 16777265 { 0xb5 <-- code 0x01000031, ' Sh_F2', mod 0x2000000 } Key[189] = ev key 16777266 { 0xa2 <-- code 0x01000032, ' F3', mod 0x 0 } Key[190] = ev key 16777266 { 0xb6 <-- code 0x01000032, ' Sh_F3', mod 0x2000000 } Key[191] = ev key 16777267 { 0xa3 <-- code 0x01000033, ' F4', mod 0x 0 } Key[192] = ev key 16777267 { 0xb7 <-- code 0x01000033, ' Sh_F4', mod 0x2000000 } Key[193] = ev key 16777268 { 0xa4 <-- code 0x01000034, ' F5', mod 0x 0 } Key[194] = ev key 16777268 { 0xb8 <-- code 0x01000034, ' Sh_F5', mod 0x2000000 } Key[195] = ev key 16777269 { 0xa5 <-- code 0x01000035, ' F6', mod 0x 0 } Key[196] = ev key 16777269 { 0xb9 <-- code 0x01000035, ' Sh_F6', mod 0x2000000 } Key[197] = ev key 16777270 { 0xa6 <-- code 0x01000036, ' F7', mod 0x 0 } Key[198] = ev key 16777270 { 0xba <-- code 0x01000036, ' Sh_F7', mod 0x2000000 } Key[199] = ev key 16777271 { 0xa7 <-- code 0x01000037, ' F8', mod 0x 0 } Key[200] = ev key 16777271 { 0xbb <-- code 0x01000037, ' Sh_F8', mod 0x2000000 } Key[201] = ev key 16777272 { 0xa8 <-- code 0x01000038, ' F9', mod 0x 0 } Key[202] = ev key 16777272 { 0xbc <-- code 0x01000038, ' Sh_F9', mod 0x2000000 } Key[203] = ev key 16777273 { 0xa9 <-- code 0x01000039, ' F10', mod 0x 0 } Key[204] = ev key 16777273 { 0xbd <-- code 0x01000039, ' Sh_F10', mod 0x2000000 } Key[205] = ev key 16777274 { 0xaa <-- code 0x0100003a, ' F11', mod 0x 0 } Key[206] = ev key 16777274 { 0xbe <-- code 0x0100003a, ' Sh_F11', mod 0x2000000 } Key[207] = ev key 16777275 { 0xab <-- code 0x0100003b, ' F12', mod 0x 0 } Key[208] = ev key 16777275 { 0xbf <-- code 0x0100003b, ' Sh_F12', mod 0x2000000 } Key[209] = ev key 16777299 { 0xac <-- code 0x01000053, ' Super_L', mod 0x 0 } Key[210] = ev key 16777300 { 0xad <-- code 0x01000054, ' Super_R', mod 0x 0 } Key[211] = ev key 16777301 { 0xae <-- code 0x01000055, ' Menu', mod 0x 0 } Key[212] = ev key 16777302 { 0xaf <-- code 0x01000056, ' Hyper_L', mod 0x 0 } Key[213] = ev key 16777303 { 0xb0 <-- code 0x01000057, ' Hyper_R', mod 0x 0 } Key[214] = ev key 16777304 { 0xb1 <-- code 0x01000058, ' Help', mod 0x 0 } Key[215] = ev key 16777305 { 0xb2 <-- code 0x01000059, ' Dir_L', mod 0x 0 } Key[216] = ev key 16777312 { 0xb3 <-- code 0x01000060, ' Dir_R', mod 0x 0 } Key[217] = ev key 16777369 { 0xcc <-- code 0x01000099, ' KP_Begin', mod 0x 0 } Key[218] = ev key 16777369 { 0xcd <-- code 0x01000099, ' 0xcd', mod 0x 0 } Key[219] = ev key 16777369 { 0xce <-- code 0x01000099, ' 0xce', mod 0x 0 } Key[220] = ev key 16777369 { 0xcf <-- code 0x01000099, ' 0xcf', mod 0x 0 } Key[221] = ev key 16777369 { 0xd6 <-- code 0x01000099, ' 0xd6', mod 0x 0 } Key[222] = ev key 16777369 { 0xdf <-- code 0x01000099, ' Quit', mod 0x 0 } Key[223] = ev key 16777369 { 0xe0 <-- code 0x01000099, ' 0xe0', mod 0x 0 } Key[224] = ev key 16777369 { 0xe1 <-- code 0x01000099, ' 0xe1', mod 0x 0 } Key[225] = ev key 16777369 { 0xe2 <-- code 0x01000099, ' 0xe2', mod 0x 0 } Key[226] = ev key 16777369 { 0xe3 <-- code 0x01000099, ' 0xe3', mod 0x 0 } Key[227] = ev key 16777369 { 0xe4 <-- code 0x01000099, ' 0xe4', mod 0x 0 } Key[228] = ev key 16777369 { 0xe5 <-- code 0x01000099, ' 0xe5', mod 0x 0 } Key[229] = ev key 16777369 { 0xe6 <-- code 0x01000099, ' 0xe6', mod 0x 0 } Key[230] = ev key 16777369 { 0xe7 <-- code 0x01000099, ' 0xe7', mod 0x 0 } Key[231] = ev key 16777369 { 0xe8 <-- code 0x01000099, ' 0xe8', mod 0x 0 } Key[232] = ev key 16777369 { 0xe9 <-- code 0x01000099, ' 0xe9', mod 0x 0 } Key[233] = ev key 16777369 { 0xea <-- code 0x01000099, ' 0xea', mod 0x 0 } Key[234] = ev key 16777369 { 0xeb <-- code 0x01000099, ' 0xeb', mod 0x 0 } Key[235] = ev key 16777369 { 0xec <-- code 0x01000099, ' 0xec', mod 0x 0 } Key[236] = ev key 16777369 { 0xed <-- code 0x01000099, ' 0xed', mod 0x 0 } Key[237] = ev key 16777369 { 0xee <-- code 0x01000099, ' 0xee', mod 0x 0 } Key[238] = ev key 16777369 { 0xef <-- code 0x01000099, ' 0xef', mod 0x 0 } Key[239] = ev key 16777369 { 0xf0 <-- code 0x01000099, ' 0xf0', mod 0x 0 } Key[240] = ev key 16777369 { 0xf1 <-- code 0x01000099, ' 0xf1', mod 0x 0 } Key[241] = ev key 16777369 { 0xf2 <-- code 0x01000099, ' 0xf2', mod 0x 0 } Key[242] = ev key 16777369 { 0xf3 <-- code 0x01000099, ' 0xf3', mod 0x 0 } Key[243] = ev key 16777369 { 0xf4 <-- code 0x01000099, ' 0xf4', mod 0x 0 } Key[244] = ev key 16777369 { 0xf5 <-- code 0x01000099, ' 0xf5', mod 0x 0 } Key[245] = ev key 16777369 { 0xf6 <-- code 0x01000099, ' 0xf6', mod 0x 0 } Key[246] = ev key 16777369 { 0xf7 <-- code 0x01000099, ' 0xf7', mod 0x 0 } Key[247] = ev key 16777369 { 0xf8 <-- code 0x01000099, ' 0xf8', mod 0x 0 } Key[248] = ev key 16777369 { 0xf9 <-- code 0x01000099, ' 0xf9', mod 0x 0 } Key[249] = ev key 16777369 { 0xfa <-- code 0x01000099, ' 0xfa', mod 0x 0 } Key[250] = ev key 16777369 { 0xfb <-- code 0x01000099, ' 0xfb', mod 0x 0 } Key[251] = ev key 16777369 { 0xfc <-- code 0x01000099, ' 0xfc', mod 0x 0 } Key[252] = ev key 16777369 { 0xfd <-- code 0x01000099, ' 0xfd', mod 0x 0 } Key[253] = ev key 16777369 { 0xfe <-- code 0x01000099, ' 0xfe', mod 0x 0 } keymap size = 254 ====================== Key Name Map ==================== Name[ 0] = ! # 33 (0x21) Name[ 1] = " # 34 (0x22) Name[ 2] = # # 35 (0x23) Name[ 3] = $ # 36 (0x24) Name[ 4] = % # 37 (0x25) Name[ 5] = & # 38 (0x26) Name[ 6] = ' # 39 (0x27) Name[ 7] = ( # 40 (0x28) Name[ 8] = ) # 41 (0x29) Name[ 9] = * # 42 (0x2a) Name[ 10] = + # 43 (0x2b) Name[ 11] = , # 44 (0x2c) Name[ 12] = - # 45 (0x2d) Name[ 13] = . # 46 (0x2e) Name[ 14] = / # 47 (0x2f) Name[ 15] = 0 # 48 (0x30) Name[ 16] = 0x88 #136 (0x88) Name[ 17] = 0x89 #137 (0x89) Name[ 18] = 0x8c #140 (0x8c) Name[ 19] = 0x8d #141 (0x8d) Name[ 20] = 0x8e #142 (0x8e) Name[ 21] = 0x8f #143 (0x8f) Name[ 22] = 0x9f #159 (0x9f) Name[ 23] = 0xcd #205 (0xcd) Name[ 24] = 0xce #206 (0xce) Name[ 25] = 0xcf #207 (0xcf) Name[ 26] = 0xd6 #214 (0xd6) Name[ 27] = 0xe0 #224 (0xe0) Name[ 28] = 0xe1 #225 (0xe1) Name[ 29] = 0xe2 #226 (0xe2) Name[ 30] = 0xe3 #227 (0xe3) Name[ 31] = 0xe4 #228 (0xe4) Name[ 32] = 0xe5 #229 (0xe5) Name[ 33] = 0xe6 #230 (0xe6) Name[ 34] = 0xe7 #231 (0xe7) Name[ 35] = 0xe8 #232 (0xe8) Name[ 36] = 0xe9 #233 (0xe9) Name[ 37] = 0xea #234 (0xea) Name[ 38] = 0xeb #235 (0xeb) Name[ 39] = 0xec #236 (0xec) Name[ 40] = 0xed #237 (0xed) Name[ 41] = 0xee #238 (0xee) Name[ 42] = 0xef #239 (0xef) Name[ 43] = 0xf0 #240 (0xf0) Name[ 44] = 0xf1 #241 (0xf1) Name[ 45] = 0xf2 #242 (0xf2) Name[ 46] = 0xf3 #243 (0xf3) Name[ 47] = 0xf4 #244 (0xf4) Name[ 48] = 0xf5 #245 (0xf5) Name[ 49] = 0xf6 #246 (0xf6) Name[ 50] = 0xf7 #247 (0xf7) Name[ 51] = 0xf8 #248 (0xf8) Name[ 52] = 0xf9 #249 (0xf9) Name[ 53] = 0xfa #250 (0xfa) Name[ 54] = 0xfb #251 (0xfb) Name[ 55] = 0xfc #252 (0xfc) Name[ 56] = 0xfd #253 (0xfd) Name[ 57] = 0xfe #254 (0xfe) Name[ 58] = 1 # 49 (0x31) Name[ 59] = 2 # 50 (0x32) Name[ 60] = 3 # 51 (0x33) Name[ 61] = 4 # 52 (0x34) Name[ 62] = 5 # 53 (0x35) Name[ 63] = 6 # 54 (0x36) Name[ 64] = 7 # 55 (0x37) Name[ 65] = 8 # 56 (0x38) Name[ 66] = 9 # 57 (0x39) Name[ 67] = : # 58 (0x3a) Name[ 68] = ; # 59 (0x3b) Name[ 69] = < # 60 (0x3c) Name[ 70] = = # 61 (0x3d) Name[ 71] = > # 62 (0x3e) Name[ 72] = ? # 63 (0x3f) Name[ 73] = @ # 64 (0x40) Name[ 74] = A # 65 (0x41) Name[ 75] = ACK # 6 (0x 6) Name[ 76] = Alt_L #155 (0x9b) Name[ 77] = Alt_R #218 (0xda) Name[ 78] = B # 66 (0x42) Name[ 79] = BEL # 7 (0x 7) Name[ 80] = BS # 8 (0x 8) Name[ 81] = BkSpace #131 (0x83) Name[ 82] = BkTab #130 (0x82) Name[ 83] = C # 67 (0x43) Name[ 84] = CAN # 24 (0x18) Name[ 85] = CR # 13 (0x d) Name[ 86] = CapsLk #156 (0x9c) Name[ 87] = Clear #139 (0x8b) Name[ 88] = Ctrl_L #153 (0x99) Name[ 89] = Ctrl_Lr #221 (0xdd) Name[ 90] = Ctrl_R #216 (0xd8) Name[ 91] = Ctrl_Rr #222 (0xde) Name[ 92] = D # 68 (0x44) Name[ 93] = DC1 # 17 (0x11) Name[ 94] = DC2 # 18 (0x12) Name[ 95] = DC3 # 19 (0x13) Name[ 96] = DC4 # 20 (0x14) Name[ 97] = DEL #127 (0x7f) Name[ 98] = DLE # 16 (0x10) Name[ 99] = Del #135 (0x87) Name[100] = Dir_L #178 (0xb2) Name[101] = Dir_R #179 (0xb3) Name[102] = Down #149 (0x95) Name[103] = E # 69 (0x45) Name[104] = EM # 25 (0x19) Name[105] = ENQ # 5 (0x 5) Name[106] = EOT # 4 (0x 4) Name[107] = ESC # 27 (0x1b) Name[108] = ETB # 23 (0x17) Name[109] = ETX # 3 (0x 3) Name[110] = End #145 (0x91) Name[111] = Enter #133 (0x85) Name[112] = Esc #128 (0x80) Name[113] = F # 70 (0x46) Name[114] = F1 #160 (0xa0) Name[115] = F10 #169 (0xa9) Name[116] = F11 #170 (0xaa) Name[117] = F12 #171 (0xab) Name[118] = F2 #161 (0xa1) Name[119] = F3 #162 (0xa2) Name[120] = F4 #163 (0xa3) Name[121] = F5 #164 (0xa4) Name[122] = F6 #165 (0xa5) Name[123] = F7 #166 (0xa6) Name[124] = F8 #167 (0xa7) Name[125] = F9 #168 (0xa8) Name[126] = FF # 12 (0x c) Name[127] = FS # 28 (0x1c) Name[128] = G # 71 (0x47) Name[129] = GS # 29 (0x1d) Name[130] = H # 72 (0x48) Name[131] = HT # 9 (0x 9) Name[132] = Help #177 (0xb1) Name[133] = Home #144 (0x90) Name[134] = Hyper_L #175 (0xaf) Name[135] = Hyper_R #176 (0xb0) Name[136] = I # 73 (0x49) Name[137] = Ins #134 (0x86) Name[138] = J # 74 (0x4a) Name[139] = K # 75 (0x4b) Name[140] = KP_* #208 (0xd0) Name[141] = KP_+ #209 (0xd1) Name[142] = KP_, #210 (0xd2) Name[143] = KP_- #211 (0xd3) Name[144] = KP_. #212 (0xd4) Name[145] = KP_/ #213 (0xd5) Name[146] = KP_Begin #204 (0xcc) Name[147] = KP_Del #193 (0xc1) Name[148] = KP_Down #201 (0xc9) Name[149] = KP_End #197 (0xc5) Name[150] = KP_Home #196 (0xc4) Name[151] = KP_Ins #192 (0xc0) Name[152] = KP_Left #198 (0xc6) Name[153] = KP_PageDn #203 (0xcb) Name[154] = KP_PageUp #202 (0xca) Name[155] = KP_Right #200 (0xc8) Name[156] = KP_Up #199 (0xc7) Name[157] = L # 76 (0x4c) Name[158] = LF # 10 (0x a) Name[159] = Left #146 (0x92) Name[160] = M # 77 (0x4d) Name[161] = Menu #174 (0xae) Name[162] = Meta #154 (0x9a) Name[163] = N # 78 (0x4e) Name[164] = NAK # 21 (0x15) Name[165] = NumLk #157 (0x9d) Name[166] = O # 79 (0x4f) Name[167] = P # 80 (0x50) Name[168] = PageDn #151 (0x97) Name[169] = PageUp #150 (0x96) Name[170] = Pause #194 (0xc2) Name[171] = Print #195 (0xc3) Name[172] = Q # 81 (0x51) Name[173] = Quit #223 (0xdf) Name[174] = R # 82 (0x52) Name[175] = RS # 30 (0x1e) Name[176] = Return #132 (0x84) Name[177] = Right #148 (0x94) Name[178] = S # 83 (0x53) Name[179] = SI # 15 (0x f) Name[180] = SO # 14 (0x e) Name[181] = SOH # 1 (0x 1) Name[182] = STX # 2 (0x 2) Name[183] = SUB # 26 (0x1a) Name[184] = SYN # 22 (0x16) Name[185] = ScrlLk #158 (0x9e) Name[186] = Sh_F1 #180 (0xb4) Name[187] = Sh_F10 #189 (0xbd) Name[188] = Sh_F11 #190 (0xbe) Name[189] = Sh_F12 #191 (0xbf) Name[190] = Sh_F2 #181 (0xb5) Name[191] = Sh_F3 #182 (0xb6) Name[192] = Sh_F4 #183 (0xb7) Name[193] = Sh_F5 #184 (0xb8) Name[194] = Sh_F6 #185 (0xb9) Name[195] = Sh_F7 #186 (0xba) Name[196] = Sh_F8 #187 (0xbb) Name[197] = Sh_F9 #188 (0xbc) Name[198] = Shift_L #152 (0x98) Name[199] = Shift_Lr #219 (0xdb) Name[200] = Shift_R #215 (0xd7) Name[201] = Shift_Rr #220 (0xdc) Name[202] = Space # 32 (0x20) Name[203] = Super_L #172 (0xac) Name[204] = Super_R #173 (0xad) Name[205] = SysReq #138 (0x8a) Name[206] = T # 84 (0x54) Name[207] = Tab #129 (0x81) Name[208] = U # 85 (0x55) Name[209] = US # 31 (0x1f) Name[210] = Up #147 (0x93) Name[211] = V # 86 (0x56) Name[212] = VT # 11 (0x b) Name[213] = W # 87 (0x57) Name[214] = X # 88 (0x58) Name[215] = Y # 89 (0x59) Name[216] = Z # 90 (0x5a) Name[217] = [ # 91 (0x5b) Name[218] = \ # 92 (0x5c) Name[219] = ] # 93 (0x5d) Name[220] = ^ # 94 (0x5e) Name[221] = _ # 95 (0x5f) Name[222] = ` # 96 (0x60) Name[223] = a # 97 (0x61) Name[224] = b # 98 (0x62) Name[225] = c # 99 (0x63) Name[226] = d #100 (0x64) Name[227] = e #101 (0x65) Name[228] = f #102 (0x66) Name[229] = g #103 (0x67) Name[230] = h #104 (0x68) Name[231] = i #105 (0x69) Name[232] = j #106 (0x6a) Name[233] = k #107 (0x6b) Name[234] = l #108 (0x6c) Name[235] = m #109 (0x6d) Name[236] = n #110 (0x6e) Name[237] = o #111 (0x6f) Name[238] = p #112 (0x70) Name[239] = q #113 (0x71) Name[240] = r #114 (0x72) Name[241] = s #115 (0x73) Name[242] = t #116 (0x74) Name[243] = u #117 (0x75) Name[244] = v #118 (0x76) Name[245] = w #119 (0x77) Name[246] = x #120 (0x78) Name[247] = y #121 (0x79) Name[248] = z #122 (0x7a) Name[249] = { #123 (0x7b) Name[250] = | #124 (0x7c) Name[251] = } #125 (0x7d) Name[252] = ~ #126 (0x7e) keyname size = 253 Duplicate mute slot # 43 : '0xe8' [seq66] Key controls: Error at line 232 ordinal 0xe8 key '0xe8' control 'Visibility' code 43 [seq66] Controls: 145 keys; 435 MIDI in; 32 automation displays; 11 macros # vim: sw=4 ts=4 wm=4 et ft=rc ================================================ FILE: contrib/notes/key-names.text ================================================ Key Names for Sequencer66 Chris Ahlstrom 2018-12-04 to 2018-12-04 This file lists all of the legal (supported) key names that can be used to control operations. However, note that the keys that start with "Null" are not accessible. We will eventually fill them all in. Currenty missing are "Alt_L" and "Alt_R". NUL Space @ ` Esc F1 KP_Ins Null_e0 SOH ! A a Tab F2 KP_Del Null_e1 STX " B b BkTab F3 Pause Null_e2 ETX # C c BkSpace F4 Print Null_e3 EOT $ D d Return F5 KP_Home Null_e4 ENQ % E e Enter F6 KP_End Null_e5 ACK & F f Ins F7 KP_Left Null_e6 BEL ' G g Del F8 KP_Up Null_e7 BS ( H h Pause F9 KP_Right Null_e8 HT ) I i Print F10 KP_Down Null_e9 LF * J j SysReq F11 KP_PageUp Null_ea VT + K k Clear F12 KP_PageDn Null_eb FF , L l Null_8c Super_L Null_cc Null_ec CR - M m Null_8d Super_R Null_cd Null_ed SO . N n Null_8e Menu Null_ce Null_ee SI / O o Null_8f Hyper_L Null_cf Null_ef DLE 0 P p Home Hyper_R KP_* Null_f0 DC1 1 Q q End Help KP_+ Null_f1 DC2 2 R r Left Dir_L KP_, Null_f2 DC3 3 S s Up Dir_R KP_- Null_f3 DC4 4 T t Right Sh_F1 KP_. Null_f4 NAK 5 U u Down Sh_F2 KP_/ Null_f5 SYN 6 V v PageUp Sh_F3 Null_d6 Null_f6 ETB 7 W w PageDn Sh_F4 Shift Null_f7 CAN 8 X x Shift Sh_F5 Control Null_f8 EM 9 Y y Ctrl Sh_F6 Super Null_f9 SUB : Z z Meta Sh_F7 Alt Null_fa ESC ; [ { Alt Sh_F8 CapsLk Null_fb FS < \\ | CapsLk Sh_F9 Null_dc Null_fc GS = ] } NumLk Sh_F10 Null_dd Null_fd RS > ^ ~ ScrlLk Sh_F11 Null_de Null_fe US ? _ DEL Null_9f Sh_F12 Null_df Null_ff # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/notes/launchpad.text ================================================ Quick Reference for Launchpad Mini Chris Ahlstrom 2020-08-03 to 2023-04-18 Some of this information was adopted from the PDF file launchpad-programmers-reference.pdf. That document notes that a Launchpad message is 3 bytes, and is of type Note Off (80h), Note On (90h), or a controller change (B0h). However, on our Mini, we do not receive not offs (in ALSA)... we receive Note Ons with velocity 0. The Launchpad Mini has a top row of circular buttons numbered from 1 to 8. The next 8 rows start with 8 unlabeled square buttons with a circular button on the right, labelled with letters A through H. The top row emits 0xB0 xxx 0x7f on press, and 0xB0 xxx 0x00 on release, where: 0xB0 Control Change on channel 0 (i.e. channel 1). xxx Ranges from 0x68 (104) to 0x6f (111) which are in the range of undefined MIDI controllers. The other buttons also issue 0x90 xxx 0x7f on press, and 0x90 xxx 0x0 on release, where: 0x90 Note On message on channel 0 (i.e. channel 1). xxx The hex or decimal value of the note, as shown by the two-digit hex values shown below (the decimals are shown in the launchpad-mini.ods file in the doc directory. X-Y Key Layout (mapping mode 1): 68h 69h 6ah 6bh 6ch 6dh 6eh 6fh : B0h (1) (2) (3) (4) (5) (6) (7) (8) 90h [00h] [01h] [02h] [03h] [04h] [05h] [06h] [07h] (A) : 08h [10h] [11h] [12h] [13h] [14h] [15h] [16h] [17h] (B) : 18h [20h] [21h] [22h] [23h] [24h] [25h] [26h] [27h] (C) : 28h [30h] [31h] [32h] [33h] [34h] [35h] [36h] [37h] (D) : 38h [40h] [41h] [42h] [43h] [44h] [45h] [46h] [47h] (E) : 48h [50h] [51h] [52h] [53h] [54h] [55h] [56h] [57h] (F) : 58h [60h] [61h] [62h] [63h] [64h] [65h] [66h] [67h] (G) : 68h [70h] [71h] [72h] [73h] [74h] [75h] [76h] [77h] (H) : 78h Drum Rack Key Layout (mapping mode 2): (1) (2) (3) (4) (5) (6) (7) (8) [40h] [41h] [42h] [43h] [60h] [61h] [62h] [63h] (A) : 64h [3ch] [3dh] [3eh] [3fh] [5ch] [5dh] [5eh] [5fh] (B) : 65h [38h] [39h] [3ah] [3bh] [58h] [59h] [5ah] [5bh] (C) : 66h [34h] [35h] [36h] [37h] [54h] [55h] [56h] [57h] (D) : 67h [30h] [31h] [32h] [33h] [50h] [51h] [52h] [53h] (E) : 68h [2ch] [2dh] [2eh] [2fh] [4ch] [4dh] [4eh] [4fh] (F) : 69h [28h] [29h] [2ah] [2bh] [48h] [49h] [4ah] [4bh] (G) : 6ah [24h] [25h] [26h] [27h] [44h] [45h] [46h] [47h] (H) : 6bh Set Grid LED: 90h key vel (vel = 00GGCKRR) "key" is a value given in the active of the two layouts shown above. The "vel" bits are: GG for Green brightness, C to clear the LED setting of the other buffer, K to copy the data to both buffers, and RR for Red brightness. Brightness values range from 0 (Off) to 3 (Full). Hex MSB LSB Color Brightness 00GG CKRR Decimal Vel 0Ch 0000 1100 Off Off 12 0Dh 0000 1101 Red Low 13 0Eh 0000 1110 Red Medium 14 0Fh 0000 1111 Red Full 15 1Ch 0001 1100 Green Low 28 1Dh 0001 1101 Amber Low 29 2Ch 0010 1100 Green Medium 44 2Eh 0010 1110 Amber Medium 46 3Ch 0011 1100 Green Full 60 3Eh 0011 1110 Yellow Full 62 3Fh 0011 1111 Amber Full 63 Reset: B0h 00h 00h Mapping Mode: B0h 00h 01h or 02h Double-buffer: B0h 00h 20h-3dh (bits: 01CFU0D) Automap/Live LEDs: x C = Copy. Copy LED states from displayed buffer to updating buffer. F = Flash. Continually flip displayed buffer to make LEDS flash. U = Update. Set buffer 0 or 1 as the updating buffer. D = Display. Set buffer 0 or 1 as the displaying buffer. The default state is no Flash; Updating = Displaying = 0; LED data displayed instantly. This message also resets the flash timer. All LEDS On: B0h 00h 7dh-7fh (low, medium, and full brightness) This command resets all other (see the Reset message). LED Duty Cycle: B0h 1e-1fh ND N = numerator = 0 to 0fh + 1 D = denominator = 0 to 0fh + 3 If N < 9, send B0h 1eh 10h x N-1 + D-3 Otherwise send B0h 1fh 10h x N-9 + D-3 Automap/Live LEDs: B0h, 68h-6fh data Rapid LED Update: 92h vel1 vel2 Grid Button Out: 90h key vel (vel = 127 on press, 0 on release) Auto/Live Button: B0h 68h-6fh vel (vel = 127 on press, 0 on release) More information can be found in the PDF. For Seq66, we want to start with the basic keys, mapped to the top row of circular buttons (very tentative): Song* Keep* Group* Panic* Stop Pause Play Record Queue Learn ???* 68h 69h 6ah 6bh 6ch 6dh 6eh 6fh 104 105 106 107 108 109 110 111 * means not yet supported, but see the latest LaunchPad 'ctrl' files in the data/linux directory. X-Y Key Layout (decimal version): 104 105 106 107 108 109 110 111 : B0h (1) (2) (3) (4) (5) (6) (7) (8) 90h [00 ] [01 ] [02 ] [03 ] [04 ] [05 ] [06 ] [07 ] (A) : 08 [16 ] [17 ] [18 ] [19 ] [20 ] [21 ] [22 ] [23 ] (B) : 24 [32 ] [33 ] [34 ] [35 ] [36 ] [37 ] [38 ] [39 ] (C) : 40 [48 ] [49 ] [50 ] [51 ] [52 ] [53 ] [54 ] [55 ] (D) : 56 [64 ] [65 ] [66 ] [67 ] [68 ] [69 ] [70 ] [71 ] (E) : 72 [80 ] [81 ] [82 ] [83 ] [84 ] [85 ] [86 ] [87 ] (F) : 88 [96 ] [97 ] [98 ] [99 ] [100] [101] [102] [103] (G) : 104 [112] [113] [114] [115] [116] [117] [118] [119] (H) : 120 # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/notes/midi-control-in.text ================================================ midi-control-in.text Chris Ahlstrom 2023-09-02 This file shows the key assignments (with a modified 'ctrl' file). [seq66] 32 loop-control lines [seq66] 32 mute-group-control lines [seq66] 81 automation-control lines [seq66] Key container size: 145 [seq66] Index Key Name Category Action Slot/Code [seq66] [ 0] (0x 8) BS Auto Toggle 12/12 'Solo' [seq66] [ 1] (0x20) Space Auto Toggle 27/27 'Start' [seq66] [ 2] (0x21) ! Mute Toggle 83/ 0 'Mute 0' [seq66] [ 3] (0x23) # Mute Toggle 83/ 8 'Mute 8' [seq66] [ 4] (0x24) $ Mute Toggle 83/12 'Mute 12' [seq66] [ 5] (0x25) % Mute Toggle 83/16 'Mute 16' [seq66] [ 6] (0x26) & Mute Toggle 83/24 'Mute 24' [seq66] [ 7] (0x27) ' Auto Toggle 0/ 0 'BPM Up' [seq66] [ 8] (0x2a) * Mute Toggle 83/28 'Mute 28' [seq66] [ 9] (0x2c) , Loop Toggle 82/31 'Loop 31' [seq66] [ 10] (0x2d) - Auto Toggle 37/37 'Event Edit' [seq66] [ 11] (0x2e) . Auto Toggle 10/10 'Playback' [seq66] [ 12] (0x2f) / Auto Toggle 33/33 'Slot Shift' [seq66] [ 13] (0x30) 0 Auto Toggle 34/34 'Mutes Clear' [seq66] [ 14] (0x31) 1 Loop Toggle 82/ 0 'Loop 0' [seq66] [ 15] (0x32) 2 Loop Toggle 82/ 4 'Loop 4' [seq66] [ 16] (0x33) 3 Loop Toggle 82/ 8 'Loop 8' [seq66] [ 17] (0x34) 4 Loop Toggle 82/12 'Loop 12' [seq66] [ 18] (0x35) 5 Loop Toggle 82/16 'Loop 16' [seq66] [ 19] (0x36) 6 Loop Toggle 82/20 'Loop 20' [seq66] [ 20] (0x37) 7 Loop Toggle 82/24 'Loop 24' [seq66] [ 21] (0x38) 8 Loop Toggle 82/28 'Loop 28' [seq66] [ 22] (0x3b) ; Auto Toggle 1/ 1 'BPM Dn' [seq66] [ 23] (0x3c) < Mute Toggle 83/31 'Mute 31' [seq66] [ 24] (0x3d) = Auto Toggle 36/36 'Loop Edit' [seq66] [ 25] (0x3e) > Auto Toggle 43/43 'Visibility' [seq66] [ 26] (0x40) @ Mute Toggle 83/ 4 'Mute 4' [seq66] [ 27] (0x41) A Mute Toggle 83/ 2 'Mute 2' [seq66] [ 28] (0x42) B Mute Toggle 83/19 'Mute 19' [seq66] [ 29] (0x43) C Mute Toggle 83/11 'Mute 11' [seq66] [ 30] (0x44) D Mute Toggle 83/10 'Mute 10' [seq66] [ 31] (0x45) E Mute Toggle 83/ 9 'Mute 9' [seq66] [ 32] (0x46) F Mute Toggle 83/14 'Mute 14' [seq66] [ 33] (0x47) G Mute Toggle 83/18 'Mute 18' [seq66] [ 34] (0x48) H Mute Toggle 83/22 'Mute 22' [seq66] [ 35] (0x49) I Mute Toggle 83/29 'Mute 29' [seq66] [ 36] (0x4a) J Mute Toggle 83/26 'Mute 26' [seq66] [ 37] (0x4b) K Mute Toggle 83/30 'Mute 30' [seq66] [ 38] (0x4d) M Mute Toggle 83/27 'Mute 27' [seq66] [ 39] (0x4e) N Mute Toggle 83/23 'Mute 23' [seq66] [ 40] (0x50) P Auto Toggle 11/11 'Song Record' [seq66] [ 41] (0x51) Q Mute Toggle 83/ 1 'Mute 1' [seq66] [ 42] (0x52) R Mute Toggle 83/13 'Mute 13' [seq66] [ 43] (0x53) S Mute Toggle 83/ 6 'Mute 6' [seq66] [ 44] (0x54) T Mute Toggle 83/17 'Mute 17' [seq66] [ 45] (0x55) U Mute Toggle 83/25 'Mute 25' [seq66] [ 46] (0x56) V Mute Toggle 83/15 'Mute 15' [seq66] [ 47] (0x57) W Mute Toggle 83/ 5 'Mute 5' [seq66] [ 48] (0x58) X Mute Toggle 83/ 7 'Mute 7' [seq66] [ 49] (0x59) Y Mute Toggle 83/21 'Mute 21' [seq66] [ 50] (0x5a) Z Mute Toggle 83/ 3 'Mute 3' [seq66] [ 51] (0x5b) [ Auto Toggle 3/ 3 'Set Dn' [seq66] [ 52] (0x5c) \ Auto Toggle 32/32 'Keep Queue' [seq66] [ 53] (0x5d) ] Auto Toggle 2/ 2 'Set Up' [seq66] [ 54] (0x5e) ^ Mute Toggle 83/20 'Mute 20' [seq66] [ 55] (0x60) ` Auto Toggle 7/ 7 'Group Mute' [seq66] [ 56] (0x61) a Loop Toggle 82/ 2 'Loop 2' [seq66] [ 57] (0x62) b Loop Toggle 82/19 'Loop 19' [seq66] [ 58] (0x63) c Loop Toggle 82/11 'Loop 11' [seq66] [ 59] (0x64) d Loop Toggle 82/10 'Loop 10' [seq66] [ 60] (0x65) e Loop Toggle 82/ 9 'Loop 9' [seq66] [ 61] (0x66) f Loop Toggle 82/14 'Loop 14' [seq66] [ 62] (0x67) g Loop Toggle 82/18 'Loop 18' [seq66] [ 63] (0x68) h Loop Toggle 82/22 'Loop 22' [seq66] [ 64] (0x69) i Loop Toggle 82/29 'Loop 29' [seq66] [ 65] (0x6a) j Loop Toggle 82/26 'Loop 26' [seq66] [ 66] (0x6b) k Loop Toggle 82/30 'Loop 30' [seq66] [ 67] (0x6c) l Auto Toggle 8/ 8 'Group Learn' [seq66] [ 68] (0x6d) m Loop Toggle 82/27 'Loop 27' [seq66] [ 69] (0x6e) n Loop Toggle 82/23 'Loop 23' [seq66] [ 70] (0x6f) o Auto Toggle 6/ 6 'Queue' [seq66] [ 71] (0x71) q Loop Toggle 82/ 1 'Loop 1' [seq66] [ 72] (0x72) r Loop Toggle 82/13 'Loop 13' [seq66] [ 73] (0x73) s Loop Toggle 82/ 6 'Loop 6' [seq66] [ 74] (0x74) t Loop Toggle 82/17 'Loop 17' [seq66] [ 75] (0x75) u Loop Toggle 82/25 'Loop 25' [seq66] [ 76] (0x76) v Loop Toggle 82/15 'Loop 15' [seq66] [ 77] (0x77) w Loop Toggle 82/ 5 'Loop 5' [seq66] [ 78] (0x78) x Loop Toggle 82/ 7 'Loop 7' [seq66] [ 79] (0x79) y Loop Toggle 82/21 'Loop 21' [seq66] [ 80] (0x7a) z Loop Toggle 82/ 3 'Loop 3' [seq66] [ 81] (0x7c) | Auto Toggle 20/20 'One-shot' [seq66] [ 82] (0x7e) ~ Auto Toggle 42/42 'Panic' [seq66] [ 83] (0x80) Esc Auto Toggle 28/28 'Stop' [seq66] [ 84] (0x86) Ins Auto Toggle 5/ 5 'Snapshot' [seq66] [ 85] (0x8c) 0x8c Auto Toggle 77/77 'Sets Normal' [seq66] [ 86] (0x8d) 0x8d Auto Toggle 78/78 'Sets Auto' [seq66] [ 87] (0x8e) 0x8e Auto Toggle 79/79 'Sets Additive' [seq66] [ 88] (0x8f) 0x8f Auto Toggle 80/80 'All Sets' [seq66] [ 89] (0x90) Home Auto Toggle 9/ 9 'Playing Set' [seq66] [ 90] (0x96) PageUp Auto Toggle 14/14 'BPM Page Up' [seq66] [ 91] (0x97) PageDn Auto Toggle 15/15 'BPM Page Dn' [seq66] [ 92] (0xa0) F1 Auto Toggle 23/23 'Top' [seq66] [ 93] (0xa1) F2 Auto Toggle 24/24 'Play List' [seq66] [ 94] (0xa2) F3 Auto Toggle 25/25 'Play Song' [seq66] [ 95] (0xa3) F4 Auto Toggle 41/41 'Follow JACK' [seq66] [ 96] (0xa4) F5 Auto Toggle 22/22 'Rewind' [seq66] [ 97] (0xa5) F6 Auto Toggle 21/21 'FF' [seq66] [ 98] (0xa6) F7 Auto Toggle 31/31 'Song Pos' [seq66] [ 99] (0xa7) F8 Auto Toggle 30/30 'Toggle Mute' [seq66] [100] (0xa8) F9 Auto Toggle 26/26 'Tap BPM' [seq66] [101] (0xa9) F10 Auto Toggle 38/38 'Song Mode' [seq66] [102] (0xaa) F11 Auto Toggle 39/39 'Toggle JACK' [seq66] [103] (0xab) F12 Auto Toggle 40/40 'Menu Mode' [seq66] [104] (0xb4) Sh_F1 Auto Toggle 49/49 'Record Overdub' [seq66] [105] (0xb5) Sh_F2 Auto Toggle 50/50 'Record Overwrite' [seq66] [106] (0xb6) Sh_F3 Auto Toggle 51/51 'Record Expand' [seq66] [107] (0xb7) Sh_F4 Auto Toggle 52/52 'Record Oneshot' [seq66] [108] (0xb8) Sh_F5 Auto Toggle 53/53 'Grid Loop' [seq66] [109] (0xb9) Sh_F6 Auto Toggle 54/54 'Grid Record' [seq66] [110] (0xba) Sh_F7 Auto Toggle 55/55 'Grid Copy' [seq66] [111] (0xbb) Sh_F8 Auto Toggle 56/56 'Grid Paste' [seq66] [112] (0xbc) Sh_F9 Auto Toggle 57/57 'Grid Clear' [seq66] [113] (0xbd) Sh_F10 Auto Toggle 58/58 'Grid Delete' [seq66] [114] (0xbe) Sh_F11 Auto Toggle 59/59 'Grid Thru' [seq66] [115] (0xbf) Sh_F12 Auto Toggle 60/60 'Grid Solo' [seq66] [116] (0xc0) KP_Ins Auto Toggle 29/29 'Loop L/R' [seq66] [117] (0xc4) KP_Home Auto Toggle 4/ 4 'Replace' [seq66] [118] (0xd0) KP_* Auto Toggle 17/17 'Record Style' [seq66] [119] (0xd1) KP_+ Auto Toggle 19/19 'Reset Sets' [seq66] [120] (0xd3) KP_- Auto Toggle 18/18 'Quan Record' [seq66] [121] (0xd4) KP_. Auto Toggle 16/16 'Set Set' [seq66] [122] (0xd5) KP_/ Auto Toggle 13/13 'Thru' [seq66] [123] (0xdf) Quit Auto Toggle 35/35 'Quit' [seq66] [124] (0xe0) 0xe0 Auto Toggle 61/61 'Grid Cut' [seq66] [125] (0xe1) 0xe1 Auto Toggle 62/62 'Grid Double' [seq66] [126] (0xe2) 0xe2 Auto Toggle 63/63 'Q None' [seq66] [127] (0xe3) 0xe3 Auto Toggle 64/64 'Q Full' [seq66] [128] (0xe4) 0xe4 Auto Toggle 65/65 'Q Tighten' [seq66] [129] (0xe5) 0xe5 Auto Toggle 66/66 'Randomize' [seq66] [130] (0xe6) 0xe6 Auto Toggle 67/67 'Jitter' [seq66] [131] (0xe7) 0xe7 Auto Toggle 68/68 'Note-map' [seq66] [132] (0xe8) 0xe8 Auto Toggle 69/69 'BBT/HMS' [seq66] [133] (0xe9) 0xe9 Auto Toggle 70/70 'LR Loop' [seq66] [134] (0xea) 0xea Auto Toggle 71/71 'Undo Record' [seq66] [135] (0xeb) 0xeb Auto Toggle 72/72 'Redo Record' [seq66] [136] (0xec) 0xec Auto Toggle 73/73 'Transpose Song' [seq66] [137] (0xed) 0xed Auto Toggle 74/74 'Copy Set' [seq66] [138] (0xee) 0xee Auto Toggle 75/75 'Paste Set' [seq66] [139] (0xef) 0xef Auto Toggle 76/76 'Toggle Tracks' [seq66] [140] (0xfa) 0xfa Auto Toggle 44/44 'Save Session' [seq66] [141] (0xfb) 0xfb Auto Toggle 45/45 'Reserved 45' [seq66] [142] (0xfc) 0xfc Auto Toggle 46/46 'Reserved 46' [seq66] [143] (0xfd) 0xfd Auto Toggle 47/47 'Reserved 47' [seq66] [144] (0xfe) 0xfe Auto Toggle 48/48 'Reserved 48' [seq66] Controls: 145 keys; 435 MIDI in; 32 automation displays; 11 macros [seq66] Event key #0x1000004 mod None ' ' release: scan 0x24 key 0xff0d ord 0x84 [seq66] Event key #0x1000053 mod None 'Super_L' press: scan 0x85 key 0xffeb ord 0xac # vim: sw=4 ts=4 wm=4 et ft=rc ================================================ FILE: contrib/notes/midi-messages.text ================================================ Summary of MIDI Messages Chris Ahlstrom 2025-10-12 to 2025-10-12 - Channel Voice Messages - Note Off 0x8n - Note on 0x9n - Aftertouch (Polyphonic Key Pressure) 0xAn - Control Change 0xBn - Bank Select - (and many more) - Program Change (Patch Change) 0xCn - Channel Pressure 0xDn - Pitch Bend Change 0xEn - Selects Channel Mode 0xB 1xxx ? - System Common Messages ???? 0xF0 0sss - MTC Quarter Frame 0xF1 - Song Select 0xF2 - Song Position Pointer 0xF3 - Tune Request (Tune Select) 0xF6 - End Of Exclusive (EOX) 0xF7 - System Real Time Message 0xF0 1ttt - Timing Clock - Undefined (ttt = 001) - Start - Continue - Stop - Undefined (ttt = 101) - Active Sensing - System Reset - System Exclusive Message 0xF0 - Non-Realtime SysEx Header OxF0 7E - Sample Data Header 0xF0 7E id 01 ... F7 - Sample Data Packet 0xF0 7E id 02 ... F7 - Sample Dump Request 0xF0 7E id 03 ... F7 - MIDI Time Code (cueing setup msgs) 0xF0 7E id 04 ... F7 - Sample Dump Extensions 0xF0 7E id 05 ... F7 - Loop Point Transmission 0xF0 7E id 05 01 ... F7 17 bytes - Loop Points Request 0xF0 7E id 05 02 ... F7 10 bytes - General Information 0xF0 7E id 06 - Device Inquiry 0xF0 7E id 06 01 F7 - Device Inquiry Response 0xF0 7E id 06 02 ... F7 - File Dump 0xF0 7E id 07 - Header 0xF0 7E id 07 01 ... F7 - Data Packet 0xF0 7E id 07 02 ... F7 - Request 0xF0 7E id 07 03 ... F7 - Generic Handshaking Messages 0xF0 7E id subid pp F7 - EOF 0xF0 7E id 7B pp F7 - WAIT 0xF0 7E id 7C pp F7 - CANCEL 0xF0 7E id 7D pp F7 - NAK 0xF0 7E id 7E pp F7 - ACK 0xF0 7E id 7F pp F7 - MIDI Tuning Standard 0xF0 7E id 08 ... F7 - Bulk Tuning Dump Request 0xF0 7E id 08 00 tt F7 - Bulk Tuning Dump Reply 0xF0 7E id 08 01 tt ... F7 - Single-note Tuning Change 0xF0 7E id 08 02 tt ... F7 - General MIDI System (0xF0 7E id 09) - GM On 0xF0 7E id 09 01 F7 - GM Off 0xF0 7E id 09 02 F7 - Realtime SysEx Header (OxF0 7F) - Full Message 0xF0 7F id 01 01 ... F7 - Bar Marker 0xF0 7F id 03 01 ... F7 - Meta Messages 0xFF type length bytes Messages that are Typically not present in a MIDI File: - System Realtime Messages (timing clock, start, continue, stop, active sensing, and system reset). - System Common Message (MTC quarter frame, song select, song position point, tune request, EOX. # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/notes/midi-override-options.text ================================================ Potential Seq66 Options to Override by SeqSpec Data Chris Ahlstrom 2025-06-12 to 2025-06-12 Consider a new SeqSpec to hold, in MIDI file, options that need to override the 'rc' file. A MIDI file that requires ... - "Record by buss". - "Buss override". - "JACK transport". - "Sets mode". - Activating the metronome. But how would these options be established without a restart? # vim: sw=4 ts=4 wm=8 et ft=rc ================================================ FILE: contrib/notes/msys2-packages.text ================================================ Package to Install for an MSYS2 Build Chris Ahlstrom 2024-10-25 pacman -Syuu pacman -S base-devel git wget p7zip pacman -S mingw-w64-x86_64-toolchain pacman -S mingw-w64-x86_64-qt5-tools pacman -S mingw-w64-x86_64-qt5 pacman -S mingw-w64-x86_64-autotools pacman -S automake-wrapper pacman -S mingw-w64-x86_64-qmake pacman -S mingw-w64-x86_64-qt5-base pacman -S mingw-w64-x86_64-gcc # vim: sw=4 ts=4 wm=4 et ft=rc ================================================ FILE: contrib/notes/pattern-fix-tests.text ================================================ Thorough Testing of the Pattern-Fix Dialog Chris Ahlstrom 2024-11-09 to 2025-01-08 The test file for pattern-fix is contrib/midi/test.midi. We change the "Test Pattern" notes to a velocity of all 74 except for 127 with the first and last notes, and center notes of velocity 124 or 4. Length Change: o None. o Measures. o Integer greater. - Increases the measure count. - Spreads out (upscales) the notes to fill the increase. - Preserves note length if checked. If not checked, the lengths of the notes increase proportionately. - One must tab out of the Measures field before able to click the Set button. - Once Set is clicked, the modification indicator (asterisk) shows up in the pattern editor and in the main window, and there will be a prompt for saving at exit. - The pattern editor immediately shows these changes. - However, the Reset button does not undo the modify flag. - Reset works. - The Expand effect box is checked. - L an R markers are unchanged. - END move to the end of the new pattern length. - The resultant length is shown as reduced by 3 pulses (Seq66 shortens notes slightly for read-ability). o Integer lesser. - Decreases the measure count. - Compresses the notes to fill the smaller measure count. - Preserves note length if checked. If not checked, the lengths of the notes decrease. For this case, preserving note length is generally not desirable. - The Shrink effect box is checked. - L an R markers are unchanged. - END move to the end of the new pattern length. - If "Preserve note length" is set, the last note might bleed across the end of the last measure. END shold be adjusted properly as well. o Float greater. - Similar to "integer greater", but rounds up to the nearest full measure. - Spreads out the notes to expand by the given factor, not the final measure size. Thus, the notes might not reach to the end of the pattern. - Changes the Scale Factor to match after tabbing out of the field. - Sets the Expand effect. o Float lesser. - Similar to "integer lesser", but rounds up to the nearest full measure. - Compresses the notes to compress by the given fraction, not the final measure size. - Changes the Scale Factor. - Sets Shrink. - For scaling, best to turn off "Preserve note length", otherwise notes overlap. o "Time sig" greater. - 6/4. - Keeps the number of measures the same, but expands the number of beats and the length of the pattern. - Tabbing out immediately sets Time Sig. - The END marker is set appropriately. - The dropdowns in the main pattern editor windows reflect the new time signature. o "Time sig" lesser. - 3/4. - Tabbing out should not set Shrink. - The pattern is scaled by 0.75. - The measures stay the same. - The time-sig numerator and denominator are set correctly. - BUGS galore: - The END marker, though is set to 3:1:0. The length of the pattern stays the same. - The Log Timesig button remains at 4/4. Clicking it royally screws up the notes and seqroll. - The dropdowns in the main pattern editor windows reflect the new time signature. - 3/8. Identitical except the time-sig properly shows 3 and 8. Note: changing to 3/4 in the pattern editor handles the seqroll seqtime bars and time signature properly, but it messes up the notes; it does not change the R marker position. It should not move the notes at all. o Scale factor. o Greaater than one. o One. o Less than one. - Almost identical to the Measure change. Just a different way to specify it. o Other fixes. Only one fix can be done at a time. o Align left and align right. - Set check-marks the Shift effect. o Reverse measures. - Set reverses the whole pattern. - Set check-marks Reverse. o Reverse in place. - Set reverses the pattern in place. That is, the last note ends up at the time of the first note, and vice versa. - Set check-marks Reverse. o Preserve note length. (A factor in the tests above and below.) o Alteration. o Click None and the real choices unchecks or check-marks the Alteration effect. o None. o Tight Q ticks. (With various ranges.) o Full Q ticks. (With various ranges.) o Random amplitude. (With various ranges.) o Time/tick jitter. (With various ranges.) o Note-map. To do: 1. Add an rcsettings::notemap_filespec (string basename) overload to construct the name if it does not have a path.. 2. Add qpatternfix::open_note_mapper() similar to the performer version, but that opens the file and creates a temporary notemapper. Or extend performer to do that. 3. We need a version of the above in the notemapper module. 3. Pass it to sequence::fix_pattern() or sequence::repitch() somehow. Also need a reverse repitch flag. Also see smanager::open_note_mapper() and qseframe::repitch_all(). o Loading alternate note-map. o Repitching. o Rev Note-map o Set. o Reset. o Note that Reset will undo any changes made in the pattern editor while the Pattern-Fix Editor is up. = = = = = = = = = = = What does it mean to convert a 4/4 pattern to 3/4? Original pattern: 4 ||========== | | |========== || - || |========== | | || 4 || | |========== | || Notes left in place: 3 ||========== | | ||========= | - || |========== | || |. . . 4 || | |========== || | Notes compressed: 3 ||======= | | =======|| - || ========= | || 4 || | ========= || https://music.stackexchange.com/questions/46058/any-pointers-on-how-to-convert-4-4-to-3-4 Current behavior in seqframe: track().set_beats_per_bar(bpb = 3, true) m_time_beats_per_measure = 3 [unsigned short!] get_measures() --> get_measures(0) um = unit_measure() --> unchanged m_unit_measure = 768 len = get_length() --> 1536 (2 measures x 768 per measure) measures = len / um = 2 THEREFORE, measures does not change at this point! modify() track().apply_length(bpb, 0, 0, user_change = false) set_beats_per_bar(3, false) this time; no changes at all here unit_measure(reset = true) m_unit_measure = measures_to_ticks(measures = 1) --> 576 measures = get_measures(0) --> len = 1536 --> measures = 3 result = set_length( seq66::measures_to_ticks(3,192,4,3) --> 1728) m_length = 1728 . . . set length for events and triggers verify_and_link() Original 4 b/bar * 192 t/bar * 2 m = = = = = = = = = = = Side note: ~automutex() is called before returning a value, so the value needs to be a LOCAL VARIABLE ================================================ FILE: contrib/notes/perf-callbacks.text ================================================ Summary of performer::callbacks usages Chris Ahlstrom 2024-12-21 to 2024-12-21 First, a summary of the performer::change values: no. Do not set the modify-flag. yes. Do set the modify-flag. recreate. Recreate the user-interface(s). removed. Change was a removal; more specific than yes. signal. Could alter the UI from a different thread. Possible additions: recording. Refresh only qseqroll and update modify flag. Callbacks index numbers: group_learn. Group-learn turned on. group_learn_complete. Group-learn turned off. mutes_change. Change in the mute-state. set_change. Change in the active screen-set. sequence_change. New, deleted, or pasted pattern. automation_change. A start or stop control occurred. ui_change. Indicates a user-interface action. trigger_change. A trigger changed pattern muting. resolution_change. A change in PPQN or BPM. song_change. A different MIDI tune was loaded. Callbacks: TODO: performer "notify->on" search bool on_group_learn (bool learning) Calls: performer::group_learn() bool on_group_learn_complete (const keystroke & k, bool good) Calls: performer::group_learn_complete() bool on_mutes_change (mutegroup::number, performer::change) Calls: performer::group_learn_complete() performer::set_mutes() performer::clear_mutes() performer::learn_mutes() [why not on group learn?] performer::apply_mutes() performer::unapply_mutes() performer::toggle_mutes() performer::toggle_active_mutes() performer::select_and_mute_group() performer::read_midi_file() bool on_set_change (screenset::number, performer::change) Calls: performer::new_sequence() performer::ui_change_set_bus() performer::screenset_name() Responses: bool on_sequence_change (seq::number, performer::change) Calls: performer::new_sequence() performer::remove_sequence(): change::recreate performer::merge_sequence(): change::recreate performer::log_current_tempo(): [disabled] performer::set_playing_screenset(): change::signal performer::paste_to_playscreen(): change::yes performer::remove_set(): change::removed performer::clear_set(): change::removed (?) performer::swap_sets(): change::??? performer::sequence_inbus_setup(): change::recreate, change::no performer::set_midi_bus(): [disabled, see sequence] performer::set_midi_in_bus(): [disabled, see sequence] performer::set_midi_channel(): [disabled, see sequence] performer::set_sequence_name(): change::recreate performer::convert_to_smf_0(): change::recreate performer::loop_control(): change::no sequence::modify(true): notify_change(yes, no)..... sequence::add_note(): perf()->notify_seq..... (!!!) sequence::add_event(): modify(true) sequence::set_recording_style(): notify_change(false)..... qseqeventframe::slot_save() change::? Responses: bool on_automation_change (automation::slot) Calls: performer::start_playing(): slot::start performer::stop_playing(): slot::stop performer::play_count_in(): slot::start performer:: MANY MORE automation functions bool on_ui_change (seq::number) Calls: performer:: NONE (!!!) bool on_trigger_change (seq::number, performer::change) Calls: performer::clear_triggers(): change::??? performer::cut_triggers(): change::??? performer::delete_triggers(): change::??? performer::offset_triggers(): change::??? performer::move_triggers(): change::??? performer::move_triggers(): change::???, seq::all() #if defined USE_INTERSECT_FUNCTIONS performer::intersect_triggers(): change::??? #endif performer::add_trigger(): change::??? performer::delete_trigger(): change::??? performer::transpose_trigger(): change::??? performer::add_or_delete_trigger(): change::??? performer::split_trigger(): change::??? performer::grow_trigger(): change::??? performer::move_trigger(): change::??? performer::paste_trigger(): change::??? performer::paste_or_split_trigger(): change::??? performer::set_ctrl_status(): change::no performer::replace_for_solo(): change::no sequence::play(): notify_trigger(no)..... sequence::set_recording(): notify_trigger() bool on_resolution_change Calls: performer::set_ppqn() performer::change_ppqn() [of all patterns] performer::jack_set_beats_per_minute() bool on_song_action (bool, playlist::action) #if defined USE_ON_SIGNAL_ACTION bool on_signal_action (bool, playlist::action) #endif # vim: sw=4 ts=4 wm=8 et ft=rc ================================================ FILE: contrib/notes/performance.text ================================================ Sequencer64: Snapshot: Press: Save current state; let user change playscreen arming. Release: Restore the saved state. Idea: Can we make restoration cued? Mute-Groups: Learn mode: Press button, Ctrl-L, or get MIDI glearn control. Save group: According to selected group number, save current playscreen into group. Apply group: According to event, apply the desired mute group to the playscreen. Disable: Reverse apostrophe Enable: Apostrophe Clear: Remove all mute-groups. They won't be saved to the MIDI file. If a change, the user should be a prompt to save the MIDI file. What about the 'rc' file? The cleared mute-groups are save. Reload: Reload the mute-groups from the 'rc' file. Muting: Toggle: Toggles arming on all patterns? Or all patterns in playscreen? Mute all: Mute all patterns (in the playscreen?) Unmute all: Unmute all patterns (in the playscreen?) Song Mode: How does it interact with mute groups? Override them? Home key: Set playing screenset. We don't enable a screenset just because it is in view. That would cause confusion. We select a screenset, and when we get to the one we want, we can then enable it. Processes: learn_toggle() shift_lock() for all seqs in viewscreen, draw_sequence_on_pixmap(); requires getting seq pointers by seq number; required iterating through seq events. set_song_mode() from various interfaces set_screenset()/set_playscreen() and update_screenset() set_screenset()/set_viewscreen() and update_screenset() Note that multiwid would require multi-view and multi-play screensets! m_mute_group[1024]: any_group_unmutes() select_group_mute() set_ & get_group_mute_state() [deprecated] load_ & save_mute_group() [partial deprec?] set_and_copy_mute_group() clear_mute_groups() m_mute_group_rc[1024]: load_ & save_mute_group() [partial deprec?] m_armed_statuses[1024]: toggle_playing_tracks() m_sequence_state[1024]: save_playing_state() [snapshot] restore_playing_state() [snapshot] m_screenet_state[32]: unqueue_sequences() [one seq] save_current_screenset() clear_current_screenset() m_tracks_mute_state[32]: set_and_copy_mute_group() mute_group_tracks() set_playing_screenset() [MIDI control play ss] sequence_playing_change() Sequencer66 Ideas: Triggers: Move performer::selected_trigger() into triggers. That class would need access to performer to get the sequence pointer. Same for: select_triggers_in_range() select_trigger() unselect_all_triggers() Undo/Redo: Can we make classes for these? For drawing the various representations of each sequence in a set, provide one setmapper function that accepts a functor "void f(int seqnumber)". Also need this functionality for a single sequence, all sequences, multiple sets, and all sets. Mute group toggle in addition to just set. Will generally adhere to the above. And simplify the handling of mute groups. We want to support the concept of playscreen vs viewscreen. The playscreen would play even in not in view in the GUI. The visible screen, the viewscreen, would not necessarily be playing. The user could scroll to the desired screen, do a bunch of stuff to it, without affecting playing (in Live mode). Auto-play: When a screenset comes into view, it is automatically queued for playing. All-play: Turn on the playing of all sets. PPQN Life Cycle: Startup: smanager::set_configuration_defaults(): rc() and usr().set_defaults(): usersettings::m_midi_ppqn = 192; m_file_ppqn = 0; smanager::main_settings: cmdlineopts::parse_command_line_options(): smanager::main_settings: cmdlineopts::parse_options_files(): usersettings::m_midi_ppqn = value from "usr" file: 0 or legal value, validated in usrsettings::midi_ppqn(). cmdlineopts::parse_command_line_options() again to override the files. Store the MIDI filename provided on the command-line, if any. smanager::create_performer(): choose_ppqn(-1) --> 0 --> 192 performer set up with m_ppqn = 192 performer::get_settings() from "rc" and "usr" performer launched with ppqn = 192 * master buss created with ppqn = 192, bpm = 120 * master buss initialized via api_init() with ppqn = 192, bpm = 120 I/O threads launched in performer. MIDI File Loaded at Startup, qseq66.usr specifies 0 for default PPQN: smanager::open_midi_file(): leads to midifile being created with PPQN = 0, which then gets changed when the PPQN is read from the file; this turns off ppqn scaling and sets m_ppqn = m_file_ppqn in midifile. Then this PPQN is passed to performer::set_ppqn(). Can this be done later, to change_ppqn()? After parsing, set usr().file_ppqn() to hold the value, and call performer::change_ppqn(). midi_alsa_info and whole rtmidi API: Combine api_set_ppqn() and api_set_beats_per_minute() if feasible. ================================================ FILE: contrib/notes/pipewire.text ================================================ Pipewire Chris Ahlstrom 2026-01-17 to 2026-01-22 How to set up to use Pipewire with Seq66 ALSA and JACK support. Possible tools to install: pipewire pipewire-docs pipewire-pulse pipewire-audio [Arch] pipewire-audio-client-libraries [Debian, "replaces" ALSA, JACK] libspa-0.2-jack pipewire-alsa pipewire-jack pipewire-jack-client wireplumber wpctl pw-dump pwvucontrol pipemixer wiremax helvum qpwgraph $ systemctl --user daemon-reload # check for new service files $ systemctl --user --now disable pulseaudio.service pulseaudio.socket $ systemctl --user --now enable pipewire pipewire-pulse $ systemctl --user --now enable wireplumber.service $ systemctl --user restart wireplumber pipewire pipewire-pulse See if this command shows "Server Name: PulseAudio (on PipeWire 1.4.9)": $ pactl info This command shows the Clients and Audio/Video devices: $ wpctl status This command shows a long set of JSON output describing the configuration: $ pw-dump Make it permanent: $ systemctl --user mask pulseaudio $ pactl info | grep '^Service Name' Running Seq66 with JACK and Pipewire: $ pw-jack qseq66 --jack & This command modifies LD_LIBRARY_PATH so that the application will load the PipeWire implementation of JACK client libraries instead of those of JACK; JACK clients are redirected to PipeWire. If the PipeWire implementation of JACK has been installed as a system-wide replacement for JACK, the system already behaves in that way; pw-jack is not needed. $ ps ax|grep pipe and wire /usr/bin/pipewire /usr/bin/pipewire -c filter-chain.conf /usr/bin/pipewire-pulse /usr/bin/wireplumber Directories (varies by Linux version): /usr/share/doc/pipewire/examples /usr/share/pipewire /usr/share/wireplumber /etc/wireplumber [Arch] ~/.config/wireplumber ~/.local/state/wireplumber Arch Linux: PipeWire provides an initial set of configuration files in /usr/share/pipewire. Do not edit these files; package updates will overwrite your changes. To configure PipeWire, copy files from /usr/share/pipewire to the alternate system-wide location /etc/pipewire, or to the user location ~/.config/pipewire. An equally named file in a directory with a higher precedence makes the analogous files ignored. Audio: install pipewire-audio. Depending on the type of audio clients, might need to take some extra steps. May need to install additional firmware for your audio device, ALSA clients: Install pipewire-alsa (and remove pulseaudio-alsa) to route all applications using the ALSA API through PipeWire. PulseAudio clients: Install pipewire-pulse. It replaces pulseaudio and pulseaudio-bluetooth. Reboot, re-login or stop pulseaudio.service and start the pipewire-pulse.service user unit to see the effect. See the reference below for the usage of pactl. JACK clients: Install pipewire-jack for JACK support. rncbc from Reddit: Q. Is there a way to cleanly switch from pipewire-jack to "just jack" ? The goal here is to be able to be able to reserve my critical sound interface for critical jobs when needed without crushing my CPU under load when i just want to chill, without sacrificing jack's flexibility when it comes to ease of use. A. rncbc: There's 2 options here. a. You already trust jack for most if not all of your production: 1. Do not uninstall JACK. 2. Keep or install pipewire-jack as usual; 3. Edit and comment out all the lines in "/etc/ld.so.conf.d/pipewire-jack.conf" (or the equivalent) 4. While not having jackd(bus) up and running, as for actual production, run "pw-jack your-jack-app"; jack-app will then join the pipewire-jack graph. 5. Thus, one can choose between straight JACK and pipewire-jack. b. You have no clue about JACK but want to try the various "jack-apps": 1. Uninstall JACK if desired, or leave it all as is. 2. Install pipewire-jack or the equivalent for your distro (e.g. pipewire-libjack). 3. All desktop and jack-apps will join the party. 4. You might complain that some jack-apps do not work quite as they should; if so, there is always option a. At the Reddit site noted below jason_gate describes switching between JACK and Pipewire. The steps are summarized here: a. Stop pipewire{.socket,.service}, pipewire-pulse{.socket,.service}, and wireplumber using "systemctl --user". Then, using the Arch optional systemd wrapper service for JACK, start "jack@myjack.service" in a similar manner. b. For the opposite setup, stop jack using systemctl and start pipewire using systemctl. References: https://wiki.archlinux.org/title/PipeWire https://wiki.debian.org/PipeWire https://www.reddit.com/r/linuxaudio/comments/ 1fi4qd1/pipewirejack_or_just_jack/ # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: contrib/notes/program-banks.text ================================================ Chris Ahlstrom 2025-06-10 to 2025-07-08 Issue #138 How about each seq66 pattern would always update/save the last received `program change + MSB + LSB`-values at the beginning. Maybe this could be a default feature-toggle? This would make the above mentioned manual steps (4x16=64) redundant. There are two styles of MIDI commands used to select banks CC 0 and CC 32. [1] Here, n is the channel number, bb is a bank number, and pp is a program/patch number. The symbol lsb is the least significant byte of the bank number and msb is the most significant byte. 0xBn 0x00 bb + 0xCn pp 0xBn 0x00 msb + 0xBn 0x20 lsb + 0xCn pp These two style are synthesizer-dependent, so how to know which one to use? The obvious way is to have the synth sent a bank/patch change and see if 0xBn or 0xCn comes after the first 0xBn. Another way is to have the user set this for each MIDI instrument. [1] The CCs from 0 to 13 provide coarse settings for various items, while the CCs from 32 to 45 provide the "fine" settings. Perhaps a CC editor dialog is in order. Perhaps also include NRPN coarse/fine and RPN coarse/fine entries as well? However, there are too many CCs etc to handle this well. Instead, version 0.99.21 of Seq66 has been updated to make recording CCs a bit easier. MIDI event sorting now uses std::stable\_sort(), and event-ranking gives CC and Program Changes the same priority, to preserve the order of these incoming events. # vim: sw=3 ts=3 wm=8 et ft=sh nowrap ================================================ FILE: contrib/notes/q-hierarchy.text ================================================ The Hierarchy of Qt GUI Classes Chris Ahlstrom 2021-12-26 to 2021-12-26 This file simply shows the Seq66 hierarchy of GUI classes. The asterisks means that qbase is the most basic class in that hierarchy. This set of classes includes mainly those that draw directly onto a "canvas". qclocklayout : QWidget qlfoframe : QFrame qinputcheckbox : QWidget qliveframeex : QWidget qlooputton : qslotbutton : QPushButton * qperfbase : qeditbase : qbase qperfeditex : QWidget qperfeditframe64 : QFrame * qperfnames : QWidget, qperfbase ... * qperfroll : QWidget, qperfbase ... * qseqbase : qeditbase : qbase * qseqdata : QWidget, qseqbase ..., performer::callbacks * qseqroll : QWidget, qseqbase ... qseqeditex : QWidget * qseqeditframe64 : qseqframe, performer::callbacks * qseqframe : QFrame, qbase * qseqkeys : QWidget, qseqbase, ... * qseqtime : QWidget, qseqbase, ... * qstriggereditor : QWidget, qseqbase, ... qslivegrid : qlivebase : QFrame qsmaintime : QWidget qseqeventframe : QFrame, performer::callbacks qsmainwnd : QMainWindow, performer::callbacks qsetmaster : QFrame, performer::callbacks qmutemaster : QFrame, performer::callbacks # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/notes/qw-az-keys.text ================================================ Keys Emitted by Qw(Az)erty Keyboard Chris Ahlstrom 2021-08-04 to 2021-12-12 QWERTY AZERTY x Key '`' 0x60: Key 'Null_ff' text = '²' scan code = 0x31; key code = 0xb2 * Key '1' 0x31: Key '&' 0x26: text = '&' scan code = 0xa; key code = 0x26 * Key 'q' 0x71: Key 'a' 0x61: text = 'a' scan code = 0x18; key code = 0x61 * Key 'a' 0x61: Key 'q' 0x71: text = 'q' scan code = 0x26; key code = 0x71 * Key 'z' 0x7a: Key 'w' 0x77: text = 'w' scan code = 0x34; key code = 0x77 x Key '2' 0x32: Key 'Null_ff' text = 'é' scan code = 0xb; key code = 0xe9 * Key 'w' 0x77: Key 'z' 0x7a: text = 'z' scan code = 0x19; key code = 0x7a Key 's' 0x73: Key 's' 0x73: text = 's' scan code = 0x27; key code = 0x73 Key 'x' 0x78: Key 'x' 0x78: text = 'x' scan code = 0x35; key code = 0x78 * Key '3' 0x33: Key '"' 0x22: text = '"' scan code = 0xc; key code = 0x22 Key 'e' 0x65: Key 'e' 0x65: text = 'e' scan code = 0x1a; key code = 0x65 Key 'd' 0x64: Key 'd' 0x64: text = 'd' scan code = 0x28; key code = 0x64 Key 'c' 0x63: Key 'c' 0x63: text = 'c' scan code = 0x36; key code = 0x63 * Key '4' 0x34: Key ''' 0x27: text = ''' scan code = 0xd; key code = 0x27 Key 'r' 0x72: Key 'r' 0x72: text = 'r' scan code = 0x1b; key code = 0x72 Key 'f' 0x66: Key 'f' 0x66: text = 'f' scan code = 0x29; key code = 0x66 Key 'v' 0x76: Key 'v' 0x76: text = 'v' scan code = 0x37; key code = 0x76 * Key '5' 0x35: Key '(' 0x28: text = '(' scan code = 0xe; key code = 0x28 Key 't' 0x74: Key 't' 0x74: text = 't' scan code = 0x1c; key code = 0x74 Key 'g' 0x67: Key 'g' 0x67: text = 'g' scan code = 0x2a; key code = 0x67 Key 'b' 0x62: Key 'b' 0x62: text = 'b' scan code = 0x38; key code = 0x62 * Key '6' 0x36: Key '-' 0x2d: text = '-' scan code = 0xf; key code = 0x2d Key 'y' 0x79: Key 'y' 0x79: text = 'y' scan code = 0x1d; key code = 0x79 Key 'h' 0x68: Key 'h' 0x68: text = 'h' scan code = 0x2b; key code = 0x68 Key 'n' 0x6e: Key 'n' 0x6e: text = 'n' scan code = 0x39; key code = 0x6e x Key '7' 0x37: Key 'Null_ff' text = 'è' scan code = 0x10; key code = 0xe8 Key 'u' 0x75: Key 'u' 0x75: text = 'u' scan code = 0x1e; key code = 0x75 Key 'j' 0x6a: Key 'j' 0x6a: text = 'j' scan code = 0x2c; key code = 0x6a Key 'm' 0x6d: Key ',' 0x2c: text = ',' scan code = 0x3a; key code = 0x2c * Key '8' 0x38: Key '`' 0x60: text = '_' scan code = 0x11; key code = 0x5f Key 'i' 0x69: Key 'i' 0x69: text = 'i' scan code = 0x1f; key code = 0x69 Key 'k' 0x6b: Key 'k' 0x6b: text = 'k' scan code = 0x2d; key code = 0x6b * Key ',' 0x2c: Key ';' 0x3b: text = ';' scan code = 0x3b; key code = 0x3b x Key '9' 0x39: Key 'Null_ff' text = 'ç' scan code = 0x12; key code = 0xe7 Key 'o' 0x6f: Key 'o' 0x6f: text = 'o' scan code = 0x20; key code = 0x6f Key 'l' 0x6c: Key 'l' 0x6c: text = 'l' scan code = 0x2e; key code = 0x6c * Key '.' 0x2e: Key ':' 0x3a: text = ':' scan code = 0x3c; key code = 0x3a x Key '0' 0x30: Key 'Null_ff' text = 'à' scan code = 0x13; key code = 0xe0 Key 'p' 0x70: Key 'p' 0x70: text = 'p' scan code = 0x21; key code = 0x70 * Key ';' 0x3b: Key 'm' 0x6d: text = 'm' scan code = 0x2f; key code = 0x6d * Key '/' 0x2f: Key '!' 0x21: text = '!' scan code = 0x3d; key code = 0x21 * Key '-' 0x2d: Key ')' 0x29: text = ')' scan code = 0x14; key code = 0x29 ? Key '[' 0x5b: Key 'Null_ff' text = '' scan code = 0x22; key code = 0xfe52 x Key ''' 0x27: Key 'Null_ff' text = 'ù' scan code = 0x30; key code = 0xf9 Key '=' 0x3d: Key '=' 0x3d: text = '=' scan code = 0x15; key code = 0x3d * Key ']' 0x5d: Key '$' 0x24: text = '$' scan code = 0x23; key code = 0x24 * Key '\' 0x5c: Key '+' 0x2b: text = '*' scan code = 0x33; key code = 0x2a QWERTY AZERTY (shifted) Key '~' 0x7e: Key '~' 0x7e: text = '~' scan code = 0x31; key code = 0x7e *S Key '!' 0x21: Key '1' 0x31: text = '1' scan code = 0xa; key code = 0x31 * Key 'Q' 0x51: Key 'A' 0x41: text = 'A' scan code = 0x18; key code = 0x41 * Key 'A' 0x41: Key 'Q' 0x51: text = 'Q' scan code = 0x26; key code = 0x51 * Key 'Z' 0x5a: Key 'W' 0x57: text = 'W' scan code = 0x34; key code = 0x57 *S Key '@' 0x40: Key '2' 0x32: text = '2' scan code = 0xb; key code = 0x32 * Key 'W' 0x57: Key 'Z' 0x5a: text = 'Z' scan code = 0x19; key code = 0x5a Key 'S' 0x53: Key 'S' 0x53: text = 'S' scan code = 0x27; key code = 0x53 Key 'X' 0x58: Key 'X' 0x58: text = 'X' scan code = 0x35; key code = 0x58 *S Key '#' 0x23: Key '3' 0x33: text = '3' scan code = 0xc; key code = 0x33 Key 'E' 0x45: Key 'E' 0x45: text = 'E' scan code = 0x1a; key code = 0x45 Key 'D' 0x44: Key 'D' 0x44: text = 'D' scan code = 0x28; key code = 0x44 Key 'C' 0x43: Key 'C' 0x43: text = 'C' scan code = 0x36; key code = 0x43 *S Key '$' 0x24: Key '4' 0x34: text = '4' scan code = 0xd; key code = 0x34 Key 'R' 0x52: Key 'R' 0x52: text = 'R' scan code = 0x1b; key code = 0x52 Key 'F' 0x46: Key 'F' 0x46: text = 'F' scan code = 0x29; key code = 0x46 Key 'V' 0x56: Key 'V' 0x56: text = 'V' scan code = 0x37; key code = 0x56 *S Key '%' 0x25: Key '5' 0x35: text = '5' scan code = 0xe; key code = 0x35 Key 'T' 0x54: Key 'T' 0x54: text = 'T' scan code = 0x1c; key code = 0x54 Key 'G' 0x47: Key 'G' 0x47: text = 'G' scan code = 0x2a; key code = 0x47 Key 'B' 0x42: Key 'B' 0x42: text = 'B' scan code = 0x38; key code = 0x42 *S Key '^' 0x5e: Key '6' 0x36: text = '6' scan code = 0xf; key code = 0x36 Key 'Y' 0x59: Key 'Y' 0x59: text = 'Y' scan code = 0x1d; key code = 0x59 Key 'H' 0x48: Key 'H' 0x48: text = 'H' scan code = 0x2b; key code = 0x48 Key 'N' 0x4e: Key 'N' 0x4e: text = 'N' scan code = 0x39; key code = 0x4e *S Key '&' 0x26: Key '7' 0x37: text = '7' scan code = 0x10; key code = 0x37 Key 'U' 0x55: Key 'U' 0x55: text = 'U' scan code = 0x1e; key code = 0x55 Key 'J' 0x4a: Key 'J' 0x4a: text = 'J' scan code = 0x2c; key code = 0x4a Key 'M' 0x4d: Key '?' 0x3f: text = '?' scan code = 0x3a; key code = 0x3f *S Key '*' 0x2a: Key '8' 0x38: text = '8' scan code = 0x11; key code = 0x38 Key 'I' 0x49: Key 'I' 0x49: text = 'I' scan code = 0x1f; key code = 0x49 Key 'K' 0x4b: Key 'K' 0x4b: text = 'K' scan code = 0x2d; key code = 0x4b * Key '<' 0x3c: Key '/' 0x2f: text = '.' scan code = 0x3b; key code = 0x2e *? Key '(' 0x28: Key '9' 0x39: text = '9' scan code = 0x12; key code = 0x39 Key 'O' 0x4f: Key 'O' 0x4f: text = 'O' scan code = 0x20; key code = 0x4f Key 'L' 0x4c: Key 'L' 0x4c: text = 'L' scan code = 0x2e; key code = 0x4c * Key '>' 0x3e: Key '0' 0x30: text = '/' scan code = 0x3c; key code = 0x2f * Key ')' 0x29: Key '0' 0x30: text = '0' scan code = 0x13; key code = 0x30 Key 'P' 0x50: Key 'P' 0x50: text = 'P' scan code = 0x21; key code = 0x50 * Key ':' 0x3a: Key 'M' 0x4d: text = 'M' scan code = 0x2f; key code = 0x4d x Key '?' 0x3f: Key 'Null_ff' text = '§' scan code = 0x3d; key code = 0xa7 x Key '_' 0x5f: Key 'Null_ff' text = '°' scan code = 0x14; key code = 0xb0 ? Key '{' 0x7b: Key 'Null_ff' text = '' scan code = 0x22; key code = 0xfe57 x Key '"' 0x22: Key '%' 0x25: text = '%' scan code = 0x30; key code = 0x25 Key '+' 0x2b: Key '+' 0x2b: text = '+' scan code = 0x15; key code = 0x2b x Key '}' 0x7d: Key 'Null_ff' text = '£' scan code = 0x23; key code = 0xa3 x Key '|' 0x7c: Key 'Null_ff' text = 'µ' scan code = 0x33; key code = 0xb5 Key 'Return' 0x84: text = ' ' scan code = 0x24; key code = 0xff0d Key 'Super_L' 0xac: text = '' scan code = 0x85; key code = 0xffeb * Change the mapping in the simple way. x Use an extended code. S Shift is the inverse of QWERTY handling. ? Not sure yet. For keyboard-layout = azerty, disable use of auto-shift. Or make it an option, use-auto-shift = false, defaulting to true // Column 1 is the event->key() value. // The keycode is the event->nativeVirtualKey(). // // Letters LC: note the the keycode is the "real" value Key #0x41 Press 'a' scan = 0x18; keycode = 0x61 ordinal 97 Key #0x42 Press 'b' scan = 0x38; keycode = 0x62 ordinal 98 Key #0x43 Press 'c' scan = 0x36; keycode = 0x63 ordinal 99 . . . Key #0x5a Press 'z' scan = 0x19; keycode = 0x7a ordinal 122 // Letters UC Key #0x41 Press 'A' scan = 0x18; keycode = 0x41 ordinal 65 Key #0x42 Press 'B' scan = 0x38; keycode = 0x42 ordinal 66 Key #0x43 Press 'C' scan = 0x36; keycode = 0x43 ordinal 67 . . . Key #0x5a Press 'Z' scan = 0x19; keycode = 0x5a ordinal 90 // Extras letters Key #0xc0 Press 'à' scan = 0x13; keycode = 0xe0 ordinal 4294967295 Key #0xc7 Press 'ç' scan = 0x12; keycode = 0xe7 ordinal 4294967295 Key #0xc8 Press 'è' scan = 0x10; keycode = 0xe8 ordinal 4294967295 Key #0xc9 Press 'é' scan = 0xb; keycode = 0xe9 ordinal 4294967295 Key #0xd9 Press 'ù' scan = 0x30; keycode = 0xf9 ordinal 4294967295 // Numbers: Available by pressing Shift + key Key #0x30 Press '0' scan = 0x13; keycode = 0x30 ordinal 48 Key #0x31 Press '1' scan = 0xa; keycode = 0x31 ordinal 49 Key #0x32 Press '2' scan = 0xb; keycode = 0x32 ordinal 50 . . . Key #0x39 Press '9' scan = 0x12; keycode = 0x39 ordinal 57 // Symbols // Line ends with * when symbol is available by pressing Shift + key Key #0x26 Press '&' scan = 0xa; keycode = 0x26 ordinal 38 Key #0x22 Press '"' scan = 0xc; keycode = 0x22 ordinal 34 Key #0x27 Press ''' scan = 0xd; keycode = 0x27 ordinal 39 Key #0x28 Press '(' scan = 0xe; keycode = 0x28 ordinal 40 Key #0x2d Press '-' scan = 0xf; keycode = 0x2d ordinal 45 Key #0x5f Press '_' scan = 0x11; keycode = 0x5f ordinal 96 Key #0x29 Press ')' scan = 0x14; keycode = 0x29 ordinal 41 Key #0x3d Press '=' scan = 0x15; keycode = 0x3d ordinal 61 Key #0x1001252 Press '^' scan = 0x22; keycode = 0xfe52 ordinal 4294967295 ???? Key #0x24 Press '$' scan = 0x23; keycode = 0x24 ordinal 36 Key #0x2a Press '*' scan = 0x33; keycode = 0x2a ordinal 43 Key #0x2c Press ',' scan = 0x3a; keycode = 0x2c ordinal 44 Key #0x3b Press ';' scan = 0x3b; keycode = 0x3b ordinal 59 Key #0x3a Press ':' scan = 0x3c; keycode = 0x3a ordinal 58 Key #0x21 Press '!' scan = 0x3d; keycode = 0x21 ordinal 33 Key #0xb0 Press '°' scan = 0x14; keycode = 0xb0 ordinal 176 * Key #0x2b Press '+' scan = 0x15; keycode = 0x2b ordinal 43 * Key #0x1001257 Press '¨' scan = 0x22; keycode = 0xfe57 ordinal 4294967295 * Key #0xa3 Press '£' scan = 0x23; keycode = 0xa3 ordinal 163 * Key #0x25 Press '%' scan = 0x30; keycode = 0x25 ordinal 37 * Key #0x39c Press 'µ' scan = 0x33; keycode = 0xb5 ordinal 4294967295 * Key #0x3f Press '?' scan = 0x3a; keycode = 0x3f ordinal 63 * Key #0x2e Press '.' scan = 0x3b; keycode = 0x2e ordinal 47 * Key #0x2f Press '/' scan = 0x3c; keycode = 0x2f ordinal 48 * Key #0xa7 Press '§' scan = 0x3d; keycode = 0xa7 ordinal 167 * Key #0x3c Press '<' scan = 0x5e; keycode = 0x3c ordinal 60 Key #0x3e Press '>' scan = 0x5e; keycode = 0x3e ordinal 62 * Key #0xb2 Press '²' scan = 0x31; keycode = 0xb2 ordinal 178 // Extras symbols // Available by pressing AltR + key Key #0x7e Press '~' scan = 0xb; keycode = 0x7e ordinal 126 Key #0x23 Press '#' scan = 0xc; keycode = 0x23 ordinal 35 Key #0x7b Press '{' scan = 0xd; keycode = 0x7b ordinal 123 Key #0x5b Press '[' scan = 0xe; keycode = 0x5b ordinal 28 Key #0x7c Press '|' scan = 0xf; keycode = 0x7c ordinal 124 Key #0x60 Press '`' scan = 0x10; keycode = 0x60 ordinal 96 Key #0x5c Press '\' scan = 0x11; keycode = 0x5c ordinal 29 Key #0x5e Press '^' scan = 0x12; keycode = 0x5e ordinal 31 Key #0x40 Press '@' scan = 0x13; keycode = 0x40 ordinal 64 Key #0x5d Press ']' scan = 0x14; keycode = 0x5d ordinal 30 Key #0x7d Press '}' scan = 0x15; keycode = 0x7d ordinal 125 Key #0xa4 Press '¤' scan = 0x23; keycode = 0xa4 ordinal 4294967295 Key #0x20ac Press '€' scan = 0x1a; keycode = 0x20ac ordinal 4294967295 // F keys Key #0x1000030 Press 'F1' scan = 0x43; keycode = 0xffbe ordinal 160 Key #0x1000031 Press 'F2' scan = 0x44; keycode = 0xffbf ordinal 161 Key #0x1000032 Press 'F3' scan = 0x45; keycode = 0xffc0 ordinal 162 Key #0x1000033 Press 'F4' scan = 0x46; keycode = 0xffc1 ordinal 183 Key #0x1000034 Press 'F5' scan = 0x47; keycode = 0xffc2 ordinal 164 Key #0x1000035 Press 'F6' scan = 0x48; keycode = 0xffc3 ordinal 165 Key #0x1000036 Press 'F7' scan = 0x49; keycode = 0xffc4 ordinal 166 Key #0x1000037 Press 'F8' scan = 0x4a; keycode = 0xffc5 ordinal 187 Key #0x1000038 Press 'F9' scan = 0x4b; keycode = 0xffc6 ordinal 168 Key #0x1000039 Press 'F10' scan = 0x4c; keycode = 0xffc7 ordinal 169 Key #0x100003a Press 'F11' scan = 0x5f; keycode = 0xffc8 ordinal 170 Key #0x100003b Press 'F12' scan = 0x60; keycode = 0xffc9 ordinal 171 // Basic Special keys Key #0x20 Press 'Space' scan = 0x41; keycode = 0x20 ordinal 32 Key #0x1000000 Press 'Esc' scan = 0x9; keycode = 0xff1b ordinal 128 Key #0x1000001 Press 'Tab' scan = 0x17; keycode = 0xff09 ordinal 129 Key #0x1000024 Press 'CapsLock' scan = 0x42; keycode = 0xffe5 ordinal 156 Key #0x1000020 Press 'ShiftL' scan = 0x32; keycode = 0xffe1 ordinal 152 Key #0x1000020 Release 'ShiftL' scan = 0x32; keycode = 0xffe1 ordinal 153 Key #0x1000020 Press 'ShiftR' scan = 0x3e; keycode = 0xffe2 ordinal 152 Key #0x1000020 Release 'ShiftR' scan = 0x3e; keycode = 0xffe2 ordinal 153 Key #0x1000021 Press 'CtrlL' scan = 0x25; keycode = 0xffe3 ordinal 153 Key #0x1000021 Release 'CtrlL' scan = 0x25; keycode = 0xffe3 ordinal 154 Key #0x1000021 Press 'CtrlR' scan = 0x69; keycode = 0xffe4 ordinal 153 Key #0x1000021 Release 'CtrlR' scan = 0x69; keycode = 0xffe4 ordinal 154 Key #0x1000023 Press 'AltL' scan = 0x40; keycode = 0xffe9 ordinal 155 Key #0x1001103 Press 'AltR' scan = 0x6c; keycode = 0xfe03 ordinal 4294967295 Key #0x1000022 Press 'SuperR' scan = 0x86; keycode = 0xffec ordinal 154 Key #0x1000055 Press 'Meta' scan = 0x87; keycode = 0xff67 ordinal 174 Key #0x1000003 Press 'Backspace' scan = 0x16; keycode = 0xff08 ordinal 131 - Enter/Return returns the following, and no "Key#..." displayed. scan = 0x24; keycode = 0xff0d ordinal 132 - SuperL opens the Gnome menu and doesn't display anything // Extra special keys Key #0x1000026 Press 'ScrollLock' scan = 0x4e; keycode = 0xff14 ordinal 158 Key #0x1000008 Press 'PauseBreak' scan = 0x7f; keycode = 0xff13 ordinal 136 Key #0x1000006 Press 'Insert' scan = 0x76; keycode = 0xff63 ordinal 134 Key #0x1000007 Press 'Delete' scan = 0x77; keycode = 0xffff ordinal 135 Key #0x1000010 Press 'Home' scan = 0x6e; keycode = 0xff50 ordinal 144 Key #0x1000011 Press 'End' scan = 0x73; keycode = 0xff57 ordinal 145 Key #0x1000016 Press 'PageUp' scan = 0x70; keycode = 0xff55 ordinal 150 Key #0x1000017 Press 'PageDown' scan = 0x75; keycode = 0xff56 ordinal 151 - PrintScreen doesn't display anything // Arrows Key #0x1000013 Press 'Up' scan = 0x6f; keycode = 0xff52 ordinal 147 Key #0x1000015 Press 'Down' scan = 0x74; keycode = 0xff54 ordinal 149 Key #0x1000012 Press 'Left' scan = 0x71; keycode = 0xff51 ordinal 146 Key #0x1000014 Press 'Right' scan = 0x72; keycode = 0xff53 ordinal 148 // NumPad Key #0x31 Press '1' scan = 0x57; keycode = 0xffb1 ordinal 49 Key #0x32 Press '2' scan = 0x58; keycode = 0xffb2 ordinal 50 Key #0x33 Press '3' scan = 0x59; keycode = 0xffb3 ordinal 51 Key #0x34 Press '4' scan = 0x53; keycode = 0xffb4 ordinal 52 Key #0x35 Press '5' scan = 0x54; keycode = 0xffb5 ordinal 53 Key #0x36 Press '6' scan = 0x55; keycode = 0xffb6 ordinal 54 Key #0x37 Press '7' scan = 0x4f; keycode = 0xffb7 ordinal 55 Key #0x38 Press '8' scan = 0x50; keycode = 0xffb8 ordinal 56 Key #0x39 Press '9' scan = 0x51; keycode = 0xffb9 ordinal 57 Key #0x30 Press '0' scan = 0x5a; keycode = 0xffb0 ordinal 48 Key #0x2e Press '.' scan = 0x5b; keycode = 0xffae ordinal 47 Key #0x2f Press '/' scan = 0x6a; keycode = 0xffaf ordinal 213 Key #0x2a Press '*' scan = 0x3f; keycode = 0xffaa ordinal 208 Key #0x2d Press '-' scan = 0x52; keycode = 0xffad ordinal 211 Key #0x2b Press '+' scan = 0x56; keycode = 0xffab ordinal 209 Key #0x1000025 Press 'NumLock' scan = 0x4d; keycode = 0xff7f ordinal 157 Key #0x1000010 Press 'Home' scan = 0x4f; keycode = 0xff95 ordinal 145 Key #0x1000011 Press 'End' scan = 0x57; keycode = 0xff9c ordinal 146 Key #0x1000016 Press 'PageUp' scan = 0x51; keycode = 0xff9a ordinal 151 Key #0x1000017 Press 'PageDown' scan = 0x59; keycode = 0xff9b ordinal 152 Key #0x1000006 Press 'Insert' scan = 0x5a; keycode = 0xff9e ordinal 134 Key #0x1000007 Press 'Del' scan = 0x5b; keycode = 0xff9f ordinal 136 Key #0x1000013 Press 'Up' scan = 0x50; keycode = 0xff97 ordinal 147 Key #0x1000015 Press 'Down' scan = 0x58; keycode = 0xff99 ordinal 149 Key #0x1000012 Press 'Left' scan = 0x53; keycode = 0xff96 ordinal 147 Key #0x1000014 Press 'Right' scan = 0x55; keycode = 0xff98 ordinal 148 - Keypad-Enter returns scan = 0x68; keycode = 0xff8d ordinal 133", no Key#... displayed. # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/notes/rearrange-test.text ================================================ rearrange-test.text Chris Ahlstrom 2025-07-03 https://midi.org/community/midi-specifications/reordering-simultaneous-events Reordering of events can be problematic. It is not recommended. Your specific example of a Note Off and a Note One can cause a problem, although not in all cases. Sometimes a synthesizer will use this order in a meaningful way. For example, in Mono Mode, many synthesizers will perform a legato transition if they receive a new Note On before receiving a Note Off for the previous note. Another example would be a sophisticated piano, such as those that use 3 key sensors, which might use that same order to signify when a key is returned only half way up then restruck such that the damper does not touch the string between hammer strikes. In either of those examples, switching the order of events changes the response of the receiver. https://rnhart.net/midi/attachments/re-arrange-test.zip By the way, I just did some tests by connecting MIDI player software I have on my computer to MIDI monitor software MIDI-OX using a virtual MIDI cable loopMIDI, then checking the message order shown in the MIDI-OX monitor window. It looks like about half of the MIDI players I have move note ends before note starts if they happen at the same time position in the MIDI file. So some other MIDI software developers have also considered this a good idea. # vim: sw=4 ts=4 wm=4 et ft=rc ================================================ FILE: contrib/notes/scales-key-chord-handling.text ================================================ Drawing Note Bars and Disabling Off-Chord or Off-Scale Note Painting Chris Ahlstrom 2025-10-12 to 2025-10-12 We would like to adapt to issue #141 as noted below. Seq66 already has parameters for randomizing note pitches according to the current musical scale for the pattern, and we added a pitch-randomization function to the eventlist. The notes adhere to the currently selected musical scale. - Re issue #141 Feature request: harmonySEQ-style integration / plugin pattern editors: - Add pattern editor grid-line coloring, similar to that for scales, to reflect the current key and current chord. - Add a setting so that drawn notes can be added only on the current scale. - Add a setting so that drawn notes can be added only on the current chord. - Add [brushes] chord to the palette. - Related notes: - The user can use the following settings to achieve the desired chunkiness of the drawn pattern. - Vertical zoom - Horizontal zoom - Note-length setting - Note snap setting - Could add a starting horizontal and vertical zoom to the 'usr' configuration file. - Scale drawing takes priority over chord drawing. Background sequence coloring: If a background sequence is active and is not the current sequence, then it is drawn using backseq_brush(). Scale coloring: If a scale is active, then scales_policy() is called to determine if the note is part of the current key + scale combination. If not, then the note bar is drawn using scale_paint(). Chord painting: If a chord is active, then the offset for the base note is 0, and the offsets for the rest of the note are looked up in the chord table. For all offsets, a note is drawn at base + offset. Note that some chords have offsets >= 12. Chord note bars: How to do it? 1. Get the "base" note from the key setting. This ranges from 0 (C) to 11 (B). 2. When drawing the horizontal lines (from 0 to 127), for each note, first get the mod value of that note. 3. Look for that mod value in the list. If found, the bar can be drawn. 4. If the list has a value of 12 or greater, WHAT TO DO? One possibility is to deduct 12 before checking the mod value. # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/notes/session-mgrs.text ================================================ Notes for RaySession and Agordejo Session Managers Chris Ahlstrom 2025-01-15 to 2025-01-18 This document provides a brief, startup description of using raysession and agordejo. Both use the Non/New Session Manager (NSM) protocol, more or less. Descriptions of session control via signals, a JACK session, or NSM can be found in the *Session Management* section of the Seq66 user manual. Note that these two session managers, raysession and agordejo, can be started, along with JACK, via the contrib/scripts/jackctl script. Also included is our steps to get a new setup without a session manager, and then run Seq66 with one. ============= Ray Session ============= 1. Start JACK server. 2. RaySession includes its own implementation of the NSM server. 3. Run this command to use a more hidden session directory instead of "~/Ray Network Sessions": $ raysession --session-root /home/$USER/.local/share/ray & Note: After exit, the configuration files for RaySession itself are in /home/$USER/.config/RaySession/RaySession.conf etc. Also, the /home/$USER/.local/share/ray/... session directories do not quite follow NSM convention. 4. Use "With JACK patch memory". Ok. This yields a session with the "JACK Connections" client running. 5. Add Seq66 (qseq66), qsynth, etc. using the "+ Application" button. [ We see Jack Connections twice, agordejo, Qtractor, Seq66, but not Qsynth. WHY? Can add our jackctl script, but it comes up as a Ray-Hack with need to configure ] ============= Agordejo ============= 1. Start JACK server. Might be able to do some minimal work with ALSA. 2. Agordejo includes an implementation of the NSM server. 3. No need to set --session-root; the default is /home/$USER/.local/share/nsm, but we can do it anyway. The next section shows how to start from scratch by running Seq66 without a session manager and getting the basic setup to work. ============= First Run ============= 1. Make sure NO session manager is running. Make sure JACK is not active. 2. Remove or move aside all qseq66.* files in ~/.config/seq66. 3. Plug in devices: AKAI MPK Mini Play, Korg nanoKEY2, Alesis Q25, Launchpad Mini, etc. 4. Start software synthesizers such as Yoshimi in ALSA mode. 5. $ jackctl --start --a2j Seq66 provides this script in contrib/scripts/jackctl. This starts JACK and a2jmidi. See the script for details. 6. Start software synthesizers such as Qsynth in JACK mode. 7. Optional: Start QjackCtl 1. Setup / Misc / Start JACK audio... Disable 1. Setup / Misc / Stop JACK audio... Disable 1. xxx 1. xxx 1. xxx 1. Optionally the Graph view. 8. $ qseq66 --jack 9. Verify the setup. See the "Setup" section below. 10. Make the following additional settings: 1. Clock tab: enable "MIDI Ctrl Out" and select the Launchpad Mini. 2. Input tab: Enable all inputs except "Midi Through Port-0". 3. Click the "Restart Seq66!" button. 4. Input tab: enable "MIDI Ctrl In" and select the Launchpad Mini. 5. JACK tab: Disable "JACK auto-connect". We need the session manager to set up the connections. 5. Session tab: Change the name of the 'ctrl' file to "launchpad.ctrl". 6. Click the "Restart Seq66!" button. Verify by stopping qseq66 and restarting it. Fix any wrong settings and try again. 11. Copy "data/linux/qseq66-lp-mini-alt.ctrl" to "~/.config/seq66/launchpad.ctrl". 12. Edit all the configuration files to see that they are correct. Keep fixing and rerunning qseq66 until it all works. Note that qseq66 can be run with or without the option to "auto-connect" qseq66 to existing JACK ports. ============= NSM Run ============= Once all the setup issues are complete, one can run a session manager. We will use Agordejo here. Also, we will use the script jackctl, found in contrib/scripts, to set up JACK, start the a2jmidid daemon to expose hardware USB MIDI devices to JACK, and start the session manager. (It can also start up a software synthesizer, but we want the session manager to do that.) The steps might differ slighly depending on the host computer setup. The script requires that jackdbus be running in order to use the jack_control command. We assume the session configuration directory (step 1.4 below) is not yet present. 1. Run the following command (assuming jackctl is in the PATH); it performs the following steps, with short sleeps to ensure startup: $ jackctl --start --a2j --session 1. jack_control start 2. jack_control dps period 128 3. a2jmidid --export-hw -u & 4. agordejo --session-root /home/$USER/.local/share/nsm & 2. Make sure that any MIDI applications (e.g. Qsynth) are set up for JACK. Close them once set up. 3. xx. To close out the session: 1. Save and Close the session, then quit Agordejo. This should also close qseq66 and qsynth. 2. Run the following command. $ jackctl --stop --a2j ============= Setup ============= Clock: Midi Through Port-0 Launchpad Mini MIDI 1 nanoKEY2 nanoKEY2 _ CTRL Q25 MIDI 1 MPK mini Play mk3 MIDI 1 a2j:yoshimi input fluidsynth-midi:midi_00 x MIDI I/O Port Maps Input: Midi Through Port-0 Launchpad Mini MIDI 1 nanoKEY2 nanoKEY2 _ CTRL Q25 MIDI 1 MPK mini Play mk3 MIDI 1 # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/notes/snapshots.text ================================================ Snapshot and Other Storage Objects Chris Ahlstrom 2023-11-14 to 2023-11-14 This file reviews how various pattern scenarios are saved. A. Pattern Clipboards 1. Whole-pattern. This is an internal sequence owned by performer. Filled by calls to sequence::partial_assign(). Used for copy/cut/paste/merge/channelize. 2. Event-list. This is a static eventlist owned by sequence. Used in copy_selected(), paste_selected(), merge_events(), B. set_ctrl_status() [seq snapshot storage]: Handles the following, enumeration value in automation::ctrlstatus: - replace Is this a queued-replace/queued-solo? - snapshot - queue - keep_queue - oneshot - learn 1. Automation functions in performer. The enumeration values are from automation::slot. The default key mappings are set in the keycontainer class. - automation_replace(): mod_replace (Key_Home) - automation_snapshot(): mod_snapshot (Ins) - automation_queue(): mod_queue (o) - automation_solo(): solo (BS) queue + replace - automation_oneshot(): oneshot (|) - automation_keep_queue(): keep_queue (\) - All it does is add/remove this status to/from midicontrolin and call display_ctrl_status(). 2. set_ctrl_status (action a, ctrlstatus::snapshot) - Calls either save_snapshot() or restore_snapshot(). These calls proceed from performer to setmapper to screenset to seq. Each active sequence (seq) saves/restores the armed() value to/from a snapshot_status value. [(Inactive patterns always save false -- is it worth optimizing them out?] [Also used separately is clear_snapshot().] 3. set_ctrl_status (action a, ctrlstatus::queue || keepqueue) - Does not do anything yet for an on action. - For an off action, calls unset_queued_replace(). Clears the queued-replace (queued solo???) and queue. Clears the snapshot [clear_snapshot]. - Removes the status from midicontrolin. - Also see performer::sequence_playing_toggle(), set_playing_screenset(), and reset_playset(). 4. set_ctrl_status (action a, ctrlstatus::queue || replace [i.e. solo]) - See above. 5. set_ctrl_status (action a, ctrlstatus::replace) 6. set_ctrl_status (action a, ctrlstatus::oneshot) 7. set_ctrl_status (action a, ctrlstatus::learn) - All these do is add/remove the status to/from midicontrolin and call display_ctrl_status(). C. Metronome D. Play-set. The playset is an object that hold a list of screensets and an array of pointers to all the sequences that need to be playing. Most often this is merely the patterns in the grid showing in the main window, but it can be augumented with patterns from other screenset via configuration options. E. Soloing Revisited (note the table near the top) Current calls: - automation_replace(): set_ctrl_status() for mod_replace, replace - automation_snapshot(): set_ctrl_status() for mod_snapshot, snapshot - automation_queue(): set_ctrl_status() for mod_queue, queue - automation_solo(): toggle_ or set_ctrl_status(), solo = queue+replace - automation_oneshot(): set_ctrl_status() for mod_oneshot, queue - automation_keep_queue(): toggle_ or set_ctrl_status() for keep_queue F. Replace (with Solo not enabled) - ctrlstatus::queue and ctrlstatus::replace OR'ed together - Calls saved_queued(seqno) via performer, setmapper, play screenset. For each active seq, it is set queued if armed or has seqno. - Then performer::unqueue_sequences(seqno) to setmapper to play screenset does unqueue(seqno): if it is seqno, and not armed, toggle queued; else is queued, toggled queue on (in sequence, not seq!!!) Sets queued-tick and off-from-snap. Set queued-replace-slot to seqno. - But nothing happens. # vim: sw=4 ts=4 wm=8 et ft=rc ================================================ FILE: contrib/notes/styling.text ================================================ Ways to Color Seq66 Chris Ahlstrom 2021-08-04 to 2021-12-12 1. Desktop theme: Select a desktop theme supported by Qt/KDE. Gtk themes can also work. On our main development laptop, selecting the Gtk support in qt5ct will cause very long delays (30 seconds!) in opening many applications, including Seq66. 2. qseq66.usr: 1. In "[user-interface-settings]", set the "grid_style" items to 0 for the old-style of flat, fully-colored button, or set to 3 for the new style using the theme's push-button style, and foreground/background coloring from the internal palette only for the progress box in the center. Warning: the old-style is deprecated and will eventually go away (unless there is a clamor for it). 2. In the "[user-ui-tweaks]" section, change the empty style-sheet filename: style-sheet = "qseq66.qss" which can affect only certain types of Qt widget. Style-sheets do not affect the text that Seq66 draws on the buttons nor the lines drawn in the pattern-progress box, because Seq66 chooses these colors from a 'palette' file. Some useful or "interesting" style tweaks can be found in the "data/samples" directory. 3. qseq66.palette: The palette controls the coloring of the backgrounds, lines, text, notes, selection boxes, and selected pattern colors. In addition, it can alter the background brush style for notes, scales, and the background pattern. 1. In the "data" directory, the following palette files are available: ./linux/qseq66.palette ./linux/qseq66-alt-gray.palette ./linux/qseq66-gray.palette ./samples/qseq66-sample.palette ./win/qpseq66.palette 2. To create your own palette: 1. In Seq66, go to Edit / Preferences / Display. Edit the "Palette File Base Name" as desired, then press the "Save Current Palette" button. The file should then be available in your Seq66 configuration directory. 2. Also check the "Use Palette File" option. The named palette file will be saved in the 'rc' file, section "[palette-file]". 3. In your Seq66 configuration directory, edit the 'palette' file to change the colors. Also change the color name appropriately if editing colors in the "[palette]" section, which apply to the pattern slots. 4. In your Seq66 configuration directory, edit the 'rc' file to include the 'palette' file and make it active: [palette-file] 1 # palette_active "qseq66-sample.palette" Color notes: The 32 "[palette]" colors are used for coloring the patterns. The pen-color (left column) is used for drawing the progress box outline and the note lines inside of it. The back-color (right column) is used for drawing the background of the progress box. "Label" is used for the text in the loop buttons. There is no apparent way to modify the text and line colors used via a style-sheet. Also, the color of the text in an empty button is dependent on the theme. It can be altered, though, via a Qt style-sheet as noted earlier. Gradient in style-sheets: Gradient is handled in a box, and parameters are in percent (0.0 to 1.0): (0,0) ----------- | | | | | | ----------- (1,1) The range of the gradient [which is allowed to fall outside of (0,0) to (1.1)] is set by "stops". Given the (x,y) range, each stop represents a change in color at the percentage range given by that stop. background: qlineargradient ( x1:0, y1:0, // starting point x2:1, y2:1, // ending point stop:0 white, // start a white stop:0.5 gray, // transition to gray halfway through stop:1 green // finish at green ) # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/notes/win-virtual-keys.text ================================================ https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes The following table shows the symbolic constant names, hexadecimal values, and mouse orboard equivalents for the virtual-key codes used by the system. The codes are listed in numeric order. Notes: [1] Used for miscellaneous characters; it can vary by keyboard. [2] The <>s on the US standardboard, or the \\| on the non-US 102-keyboard. [3] Used to pass Unicode characters as if they were strokes. The VK_PACKET is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more information, see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP. - 0xE8 Unassigned Constant Value Description VK_LBUTTON 0x01 Left mouse button VK_RBUTTON 0x02 Right mouse button VK_CANCEL 0x03 Control-break processing VK_MBUTTON 0x04 Middle mouse button (three-button mouse) VK_XBUTTON1 0x05 X1 mouse button VK_XBUTTON2 0x06 X2 mouse button - 0x07 Undefined VK_BACK 0x08 BACKSPACE VK_TAB 0x09 TAB - 0x0A-0B Reserved VK_CLEAR 0x0C CLEAR VK_RETURN 0x0D ENTER - 0x0E-0F Undefined VK_SHIFT 0x10 SHIFT VK_CONTROL 0x11 CTRL VK_MENU 0x12 ALT VK_PAUSE 0x13 PAUSE VK_CAPITAL 0x14 CAPS LOCK VK_KANA 0x15 IME Kana mode VK_HANGUEL 0x15 IME Hanguel mode (compatibility; use VK_HANGUL) VK_HANGUL 0x15 IME Hangul mode VK_IME_ON 0x16 IME On VK_JUNJA 0x17 IME Junja mode VK_FINAL 0x18 IME final mode VK_HANJA 0x19 IME Hanja mode VK_KANJI 0x19 IME Kanji mode VK_IME_OFF 0x1A IME Off VK_ESCAPE 0x1B ESC VK_CONVERT 0x1C IME convert VK_NONCONVERT 0x1D IME nonconvert VK_ACCEPT 0x1E IME accept VK_MODECHANGE 0x1F IME mode change request VK_SPACE 0x20 SPACEBAR VK_PRIOR 0x21 PAGE UP VK_NEXT 0x22 PAGE DOWN VK_END 0x23 END VK_HOME 0x24 HOME VK_LEFT 0x25 LEFT ARROW VK_UP 0x26 UP ARROW VK_RIGHT 0x27 RIGHT ARROW VK_DOWN 0x28 DOWN ARROW VK_SELECT 0x29 SELECT VK_PRINT 0x2A PRINT VK_EXECUTE 0x2B EXECUTE VK_SNAPSHOT 0x2C PRINT SCREEN VK_INSERT 0x2D INS VK_DELETE 0x2E DEL VK_HELP 0x2F HELP VK_0x30 0x30 0 VK_0x31 0x31 1 VK_0x32 0x32 2 VK_0x33 0x33 3 VK_0x34 0x34 4 VK_0x35 0x35 5 VK_0x36 0x36 6 VK_0x37 0x37 7 VK_0x38 0x38 8 VK_0x39 0x39 9 - 0x3A-40 Undefined VK_0x41 0x41 A VK_0x42 0x42 B VK_0x43 0x43 C VK_0x44 0x44 D VK_0x45 0x45 E VK_0x46 0x46 F VK_0x47 0x47 G VK_0x48 0x48 H VK_0x49 0x49 I VK_0x4A 0x4A J VK_0x4B 0x4B K VK_0x4C 0x4C L VK_0x4D 0x4D M VK_0x4E 0x4E N VK_0x4F 0x4F O VK_0x50 0x50 P VK_0x51 0x51 Q VK_0x52 0x52 R VK_0x53 0x53 S VK_0x54 0x54 T VK_0x55 0x55 U VK_0x56 0x56 V VK_0x57 0x57 W VK_0x58 0x58 X VK_0x59 0x59 Y VK_0x5A 0x5A Z VK_LWIN 0x5B Left Windows VK_RWIN 0x5C Right Windows VK_APPS 0x5D Applications - 0x5E Reserved VK_SLEEP 0x5F Computer Sleep VK_NUMPAD0 0x60 Numericpad 0 VK_NUMPAD1 0x61 Numericpad 1 VK_NUMPAD2 0x62 Numericpad 2 VK_NUMPAD3 0x63 Numericpad 3 VK_NUMPAD4 0x64 Numericpad 4 VK_NUMPAD5 0x65 Numericpad 5 VK_NUMPAD6 0x66 Numericpad 6 VK_NUMPAD7 0x67 Numericpad 7 VK_NUMPAD8 0x68 Numericpad 8 VK_NUMPAD9 0x69 Numericpad 9 VK_MULTIPLY 0x6A Multiply VK_ADD 0x6B Add VK_SEPARATOR 0x6C Separator VK_SUBTRACT 0x6D Subtract VK_DECIMAL 0x6E Decimal VK_DIVIDE 0x6F Divide VK_F1 0x70 F1 VK_F2 0x71 F2 VK_F3 0x72 F3 VK_F4 0x73 F4 VK_F5 0x74 F5 VK_F6 0x75 F6 VK_F7 0x76 F7 VK_F8 0x77 F8 VK_F9 0x78 F9 VK_F10 0x79 F10 VK_F11 0x7A F11 VK_F12 0x7B F12 VK_F13 0x7C F13 VK_F14 0x7D F14 VK_F15 0x7E F15 VK_F16 0x7F F16 VK_F17 0x80 F17 VK_F18 0x81 F18 VK_F19 0x82 F19 VK_F20 0x83 F20 VK_F21 0x84 F21 VK_F22 0x85 F22 VK_F23 0x86 F23 VK_F24 0x87 F24 - 0x88-8F Unassigned VK_NUMLOCK 0x90 NUM LOCK VK_SCROLL 0x91 SCROLL LOCK - 0x92-96 OEM specific - 0x97-9F Unassigned VK_LSHIFT 0xA0 Left SHIFT VK_RSHIFT 0xA1 Right SHIFT VK_LCONTROL 0xA2 Left CONTROL VK_RCONTROL 0xA3 Right CONTROL VK_LMENU 0xA4 Left ALT VK_RMENU 0xA5 Right ALT VK_BROWSER_BACK 0xA6 Browser Back VK_BROWSER_FORWARD 0xA7 Browser Forward VK_BROWSER_REFRESH 0xA8 Browser Refresh VK_BROWSER_STOP 0xA9 Browser Stop VK_BROWSER_SEARCH 0xAA Browser Search VK_BROWSER_FAVORITES 0xAB Browser Favorites VK_BROWSER_HOME 0xAC Browser Start and Home VK_VOLUME_MUTE 0xAD Volume Mute VK_VOLUME_DOWN 0xAE Volume Down VK_VOLUME_UP 0xAF Volume Up VK_MEDIA_NEXT_TRACK 0xB0 Next Track VK_MEDIA_PREV_TRACK 0xB1 Previous Track VK_MEDIA_STOP 0xB2 Stop Media VK_MEDIA_PLAY_PAUSE 0xB3 Play/Pause Media VK_LAUNCH_MAIL 0xB4 Start Mail VK_LAUNCH_MEDIA_SELECT 0xB5 Select Media VK_LAUNCH_APP1 0xB6 Start Application 1 VK_LAUNCH_APP2 0xB7 Start Application 2 - 0xB8-B9 Reserved VK_OEM_1 0xBA [1] US standardboard ';:' VK_OEM_PLUS 0xBB Any country/region '+' VK_OEM_COMMA 0xBC Any country/region ',' VK_OEM_MINUS 0xBD Any country/region '-' VK_OEM_PERIOD 0xBE Any country/region '.' VK_OEM_2 0xBF [1] US standardboard '/?' VK_OEM_3 0xC0 [1] US standardboard '`~' - 0xC1-D7 Reserved - 0xD8-DA Unassigned VK_OEM_4 0xDB [1] US standardboard '[{' VK_OEM_5 0xDC [1] US standardboard '\|' VK_OEM_6 0xDD [1] US standardboard ']}' VK_OEM_7 0xDE [1] US standardboard 'single-/double-quote' VK_OEM_8 0xDF [1] None - 0xE0 Reserved VK_OEM_101 0xE1 OEM specific VK_OEM_102 0xE2 [2] - 0xE3-E4 OEM specific VK_PROCESSKEY 0xE5 IME PROCESS - 0xE6 OEM specific VK_PACKET 0xE7 [3] - 0xE8 Unassigned 0 0xE9-F5 OEM specific VK_ATTN 0xF6 Attn VK_CRSEL 0xF7 CrSel VK_EXSEL 0xF8 ExSel VK_EREOF 0xF9 Erase EOF VK_PLAY 0xFA Play VK_ZOOM 0xFB Zoom VK_NONAME 0xFC Reserved VK_PA1 0xFD PA1 VK_OEM_CLEAR 0xFE Clear ================================================ FILE: contrib/notes/windows-port-midi.text ================================================ A Summary of Win32 Multimedia Functions, Structures, MIDI Mapper, and PortMidi Chris Ahlstrom 2020-07-05 to 2020-07-11 MMRESULT midiOutOpen ( LPHMIDIOUT phmo, UINT uDeviceID, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen ) phmo: Pointer to an HMIDIOUT handle. This location is filled with a handle identifying the opened MIDI output device. uDeviceID: Identifier of the MIDI output device that is to be opened. This value ranges from 0 on up to the number of devices minus 1. MIDIMAPPER is a -1 value, but is a legal device identifier. dwCallback: Pointer to a callback, event handle, thread identifier, or a handle of a window or thread called during MIDI playback to process messages related to the progress of the playback. If no callback is desired, specify NULL. See the prototype for MidiOutProc() below. dwInstance: User instance data passed to the callback. This parameter is not used with window callbacks or threads. fdwOpen: Callback flag for opening the device. See below. Return values: MMSYSERR_NOERROR Successful. Same as PortMidi pmNoError enum value. MIDIERR_NODEVICE No MIDI port found. Occurs only when the mapper is opened. MMSYSERR_ALLOCATED The specified resource is already allocated. MMSYSERR_BADDEVICEID The specified device identifier is out of range. MMSYSERR_INVALPARAM The specified pointer or structure is invalid. MMSYSERR_NOMEM The system is unable to allocate or lock memory. If a function is chosen to receive callback information, the following messages are sent to the function to indicate the progress of MIDI output: MOM_OPEN, MOM_CLOSE, and MOM_DONE. MMRESULT midiInOpen ( LPHMIDIIN phmi, UINT uDeviceID, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen ) The parameters are similar to midiOutOpen(). The callback flags are the same, plus the following: MIDI_IO_STATUS When this parameter also specifies CALLBACK_FUNCTION, MIM_MOREDATA messages are sent to the callback function as well as MIM_DATA messages. Or, if this parameter also specifies CALLBACK_WINDOW, MM_MIM_MOREDATA messages are sent to the window as well as MM_MIM_DATA messages. This flag does not affect event or thread callbacks. The return values are the same, with the following addition: MMSYSERR_INVALFLAG The flags specified by dwFlags are invalid. If a function is chosen to receive callback information, the following messages are sent to the function to indicate the progress of MIDI input: MIM_OPEN, MIM_CLOSE, MIM_DATA, MIM_LONGDATA, MIM_ERROR, MIM_LONGERROR, and MIM_MOREDATA. Callback functions: void CALLBACK MidiOutProc // Do not call multimedia functions here! ( HMIDIOUT hmo, // Handle to the MIDI device. UINT wMsg, // MIDI output message code (MOM_xxx above). DWORD_PTR dwInstance, // Pointer to the instance data. DWORD_PTR dwParam1, // A message parameter. DWORD_PTR dwParam2 // A message parameter. ) void CALLBACK MidiInProc // Do not call multimedia functions here! ( HMIDIOUT hmo, // Handle to the MIDI device. UINT wMsg, // MIDI output message code (MOM_xxx above). DWORD_PTR dwInstance, // Pointer to the instance data. DWORD_PTR dwParam1, // A message parameter. DWORD_PTR dwParam2 // A message parameter. ) Callback flags: CALLBACK_EVENT The dwCallback parameter is an event handle. This callback mechanism is for output only. CALLBACK_FUNCTION The dwCallback parameter is a callback function address. CALLBACK_NULL There is no callback mechanism. The default setting. CALLBACK_THREAD The dwCallback parameter is a thread identifier. CALLBACK_WINDOW The dwCallback parameter is a window handle. Enumeration functions: MMRESULT midiOutGetDevCaps ( UINT uDeviceID, // MIDIMAPPER or 0, 1, .... LPMIDIOUTCAPS pmoc, // Pointer to a capabilities structure. UINT cbmoc // The size of the structure, can be 0. ) MMRESULT midiOutGetDevCaps ( UINT uDeviceID, // MIDIMAPPER or 0, 1, .... LPMIDIOUTCAPS pmoc, // Pointer to a capabilities structure. UINT cbmoc // The size of the structure, can be 0. ) Note that uDeviceID can also be a properly-cast device handle! UINT midiOutGetNumDevs () MMRESULT midiOutClose (HMIDIOUT hmo) UINT midiInGetNumDevs () MMRESULT midiInClose (HMIDIOUT hmi) Additional return codes: MIDIERR_STILLPLAYING Buffers are still in the queue. MMSYSERR_INVALHANDLE The specified device handle is invalid. MIDI Mapper: Channel Map: A channel map redirects channel messages (e.g. Note On) to a destination channel on a destination output device, with an optional patch map to modify the message. MIDI system messages are sent to all MIDI output devices listed in a channel map. Patch Map: A patch map affects Program Change and Volume Controller messages. It provides a destination program-change value, a volume scaler, and an optional Key Map (not discussed here). There are 16 entries in the Channel Map, each pointing to a line in a Patch Map (up to 16 total Patch Maps), and each cell points to a Key Map, one possible for every entry of every patch map. Based on the Microsoft documentation, it would seem that the MIDI Mapper is an output device. That is, a sequencer will send MIDI to it in order to do playback. Coolsoft notes follow. Windows XP came bundled with MIDIMapper (device 0) and the Microsoft GS Wavetable Synth (MGWS, device 1), so by default we have MIDI Out to 0 --> MIDIMapper --> MWGS --> audio. Virtual MIDI Synth disables MIDI Mapper settings on Win 8 (Win 10?) The instructions on the VirtualMidiSynth website [http://coolsoft.altervista.org/en/virtualmidisynth/faq#faq12], specifically under the Default MIDI output device configuration section, explain how to re-enable the default MIDI synth. Folders to delete: C:/Windows/System32/VirtualMIDISynth C:/Windows/SysWOW64/VirtualMIDISynt (only on 64bit systems) Registry keys to delete: HKEY_LOCAL_MACHINE/SOFTWARE/CoolSoft VirtualMIDISynth HKEY_LOCAL_MACHINE/SOFTWARE/Wow6432Node/CoolSoft VirtualMIDISynth (only on 64bit systems) Registry keys to edit: These keys contain the registered multimedia drivers on the system. To reset the registry to default state, set all midi* subkeys (midi, midi1, midi2, ..., midi9) values to wdmaud.drv. HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Drivers32 HKEY_LOCAL_MACHINE/SOFTWARE/Wow6432Node/Microsoft/Windows NT/CurrentVersion/Drivers32 (only on 64bit systems) Default MIDI output device configurations: The following keys contains the default MIDI device configured for MidiMapper (up to Windows 7 included) and Windows Media Player. NOTE: if WMP keys are missing they will be recreated at next Windows Media Player configuration change / close / reopen. HKEY_CURRENT_USER/Software/Microsoft/Windows/CurrentVersion/Multimedia/MIDIMap --> szPname = Microsoft GS Wavetable Synth (up to Windows 7 included) HKEY_CURRENT_USER/Software/Microsoft/ActiveMovie/devenum{4EFE2452-168A-11D1-BC76-00C04FB9453B}/Default MidiOut Device --> MidiOutId = FFFFFFFF HKEY_CURRENT_USER/Software/Microsoft/ActiveMovie/devenum 64-bit{4EFE2452-168A-11D1-BC76-00C04FB9453B}/Default MidiOut Device --> MidiOutId = FFFFFFFF (only on 64bit systems) See the script in contrib/scripts/windows/VMS_fixes.reg. PortMidi: PmDefaults is a stand-along application used to set preferences for MIDI input and output devices. The preferences are read by the PortMidi library when it is initialized, and the application can easily open the devices set by PmDefaults. In this way, PortMidi applications can run without any special code to offer device selection. Of course, advanced programs like Audacity can implement their own self-contained selection and preferences, ignoring preferences set by PmDefaults. To use PmDefaults, you must install it. Note that you must have the Java runtime system installed since PmDefaults is written in Java. For Seq66, we have removed the Java dependency, it was used for configuration, and Seq66 has its own configuration files and format. When you run PmDefaults, you will see pull-down lists for Input and Output devices reported by your system to PortMidi. If you add or remove a device, for example by plugging/unplugging a USB interface or starting software that creates a virtual port, you might want to press the Refresh Device Lists button to update the choices. Select the input and output device(s) you wish to use and click the Update Preferences button. You can test devices: input from the selected input device causes the little indicator light to the right of the input selection to light up. To send a note-on to the selected output, press the Test button to the right of your selection. To set preferences to your current selections and make them visible to PortMidi applications, click the Update Preferences button. Once preferences are set with Update Preferences, you can exit PmDefaults, and the preferences will remain in effect until you change them. If you leave PmDefaults running, note that ports will be open (see the previous paragraph on testing) and that may prevent an application from opening a device. You should click on the Close/Release Ports button to close the devices held by PmDefaults before you try to open them with another application. # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: contrib/notes/zoom.text ================================================ Horizontal zoom in Seq66 is basically the number of ticks represented by one pixel. It ranges from 1 to 1024 by powers of 2, and the default zoom is 2. For high PPQN, the zoom needs to be high so that the piano roll is not compressed. The notation for zoom in the GUI is "1:t", where t is the number of ticks per 1 pixel. That is "one pixel : t ticks". For the horizontal zoom keys, z, 0, and Z: - "z" zooms out, making the measures smaller, more compressed. It increases the "t" (number of ticks) in a pixel. - "0" resets to the default zoom or the initial zoom for the given PPQN. IT NEEDS FIXING. - "Z" zooms in, making the measures larger, more expanded. It decreases "t". Resetting the zoom: Here is the current code sequence: - Press "0" and handle Key_0. - qseqeditframe64::reset_zoom() zprevious = qseqframe::zoom() = zoomer::zoom() result = qseqframe::reset_zoom(0) result = qeditbase::reset_zoom(0) result = zoomer::reset_zoom(0) result = zoom::initialize() index = log2_power_of_2(m_initial_zoom = 2) !! qseqroll::reset_zoom(0) result = qseqbase::reset_zoom(0) result = zoomer::reset_zoom(0) ETC. qeditbase::reset_zoom(0) [for seqtime] qeditbase::reset_zoom(0) [for seqdata] qeditbase::reset_zoom(0) [for seqevent] qseqeditframe64::adjust_for_zoom() znew = zoom() calculates dropdown index and scrolls x as needed !! Can we set this properly for adapting the zoom to the PPQN? Calls to zoomer ctor: - Seq66 startup: Here, the call is zoomer(2400, 16, scale=32) - qperfeditframe64 --> qperfnames; qperfbase; qeditbase - qperfeditframe64 --> qperftime; qperfbase; qeditbase - qperfeditframe64 --> qperfroll; qperfbase; qeditbase - Pattern editor window opening: - qseqeditframe64 --> qseqframe; qeditbase; zoomer(2400, 2, 0) !! Here, qseqframe calls qeditbase with p, c_default_seq_zoom. We NEED an initialzoom parameter for qseqframe! BUT DO WE? The frame itself does not have zoomable elements. - qseqeditframe64 --> initialize_panels() --> qseqkeys; qseqbase; qeditbase; zoomer(2400, 2, 1) !!! Here, qseqkeys calls qseqbase with c_default_seq_zoom and snap. We NEED an initialzoom parameter for qseqkeys as called in initialize_panels! - qseqeditframe64 --> initialize_panels() --> qseqtime; qseqbase; qeditbase; zoomer(2400, 2, 1) !!! qseqtime already has a zoom parameter, but is zoom(), which returns true. We need to move adapt-zoom code to the top of initialize_panels(), at least. Compare to what qperfeditframe does. Another option is having the adapt-zoom code modify the zoom of all the panels. - qseqeditframe64 --> initialize_panels() --> qseqroll; qseqbase; qeditbase; zoomer(2400, 2, 1) !!! Same as qseqtime!!! - qseqeditframe64 --> initialize_panels() --> qseqdata; qseqbase; qeditbase; zoomer(2400, 2, 1) !!! Same as qseqtime!!! - qseqeditframe64 --> initialize_panels() --> qstriggereditor; qseqbase; qeditbase; zoomer(2400, 2, 1) !!! Same as qseqtime!!! = = = qseqeditframe64 (): #876 int zoom = usr().zoom(); if (usr().adapt_zoom()) zoom = zoom_power_of_2(ppqn); set_zoom(zoom); change_ppqn() has the same sequence, but also modifies snap and note length! PPQN class members: jack: - set_ppqn() masterbus: - set_ppqn() midifile: - ppqn() - file_ppqn() - if usr().use_file_ppqn() then set performer::file_ppqn(), ppqn(), and turn of PPQN scaling. performer: - ppqn() returns either m_ppqn or m_file_ppqn. It should not be in possession of file PPQN, let usrsettings do it. - change_ppqn(). This is fine. - file_ppqn(). - jack_set_ppqn() - set_ppqn(). sequence: - change_ppqn(). Rescales. - m_ppqn, get_ppqn(). usrsettings: - c_base_ppqn = 192 - c_use_default_ppqn = -1 - c_use_file_ppqn = 0 [int !!!!] - base_ppqn() - default_ppqn() - use_default_ppqn() [int !!!!] - midi_ppqn() [either default PPQN or the file PPQN) - use_file_ppqn() [bool !!!!] - file_ppqn() - adapt_zoom () return midi_ppqn() != c_base_ppqn ================================================ FILE: contrib/scripts/Jack ================================================ #!/bin/bash #----------------------------------------------------------------------------- ## # \file Jack # \library bin # \author Chris Ahlstrom # \date 2015-03-15 # \update 2015-07-19 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Jack is a script to turn a JACK session on and off for setups that do # not have all sound running through JACK. For example, my laptop # generally runs the mpd daemon, streaming music, when I browse, do # newsgroups, and write code. So I need a special setup when I want to # work with music through JACK. # # This script generally assume that you have a ~/bin directory, and that # it is part of your PATH. # # NOTE: # # A BETTER alternative is data/linux/jackctl, which is installed via # "make install". # #----------------------------------------------------------------------------- MyJackVersion="0.1 (2015-07-19)" #----------------------------------------------------------------------------- # Install the standard functions for this script. #----------------------------------------------------------------------------- . binfuncs #----------------------------------------------------------------------------- # Set up control variables and other variables for this script. If some # options never apply to your setup, you can hard-wire them here. You can # find the names of your audio devices using the command-line "aplay -l". # Note that the JackSettings below apply to the "alsa" back-end only. #----------------------------------------------------------------------------- MyScript="Jack" SetJack="on" # either "on" or "off" DoHelp="no" # --help: either "no" or "yes" DoVersion="no" # --version DoMpd="yes" # --mpd, --no-mpd UseQjackCtrl="no" # --qjack, --qj DoConky="yes" # no option, hard-wired BackEnd="alsa" # adjust to your preference HwChannel="PCH" # modify to fit your system. HwRate="48000" # adjust to your preference PortMax="1024" # adjust to your preference, e.g. 256 Periods="3" # normal value is 2 AlsaMidi="raw" # or "seq"; see jackd man page JackSettings="-r$HwRate -p$PortMax -n$Periods -X$AlsaMidi -D -Chw:$HwChannel -Phw:$HwChannel" Error_StartRangeHere=100 #------------------------------------------------------------------------------ # Brute-force options loop #------------------------------------------------------------------------------ if [ $# -ge 1 ] ; then while [ "$1" != "" ] ; do case "$1" in --log) shift setlog "$1" ;; --new-log) shift setlog "$1" rm -f "$LogFileName" ;; --on | on) SetJack="on" ;; --off | off) SetJack="off" ;; --mpd | mpd) DoMpd="yes" ;; --no-mpd | no-mpd) DoMpd="no" ;; --qjack | --qj | --qjackctl) UseQjackCtrl="yes" ;; --version) DoVersion="yes" ;; --help) DoHelp="yes" ;; *) die $Error_BadOption "$MyScript" "$ErrorMsg_BadOption" "$1" ;; esac shift done fi #------------------------------------------------------------------------------ # Version information or help #------------------------------------------------------------------------------ if [ $DoVersion == "yes" ] ; then echo "Version $MyJackVersion" exit 0 fi if [ $DoHelp == "yes" ] ; then cat << E_O_F Jack v. $MyJackVersion The Jack script turns JACK on or turns JACK off, and also provides support for controlling other processes that are needed for audio work, or that interfere with JACK. Steps: - Calls binfuncs to set up convenience functions for the Jack script. - Use systemd's 'systemctl' to stop 'mpd.socket' and 'mpd.service', to free up audio for JACK to use. This requires running as root or setting yourself up in /etc/sudoers.d/systemctl-doers. - Start JACK (and optionally, QJackCtrl). Options: --on or on Turn Jack on. This is the default operation. --off or off Turn Jack off. --mpd or mpd Manage MPD. This is the default. --no-mpd, no-mpd Do not manage MPD. Change the 'DoMpd' variable to 'no' in this script if you don't have MPD installed. --qjack Use QJackCtl to control JACK. Otherwise, the 'jackd' command-line appliation is used. --log file Log error messages to a file. Messages are appended to the existing file. --new-log file Log error messages to a file, but delete the old log first. --help Show this help banner. --version Show the version information. E_O_F exit 0 fi #------------------------------------------------------------------------------ # Jack "on" # # First do MPD control (from mpdctl script). We also kill conky since we # have a script that monitors MPD. # #------------------------------------------------------------------------------ if [ $SetJack == "on" ] ; then msg "$MyScript" "Jack on" if [ $DoMpd == "yes" ] ; then sudo /bin/systemctl stop mpd.socket sudo /bin/systemctl stop mpd.service if [ $DoConky == "yes" ] ; then killall conky conky & 2> /dev/null fi fi if [ $UseQjackCtrl == "yes" ] ; then #------------------------------------------------------------------------ # # Run qjackctl and start jack, with this setting in ~/.jackdrc or # ~/.config/rncbc.org/QjackCtl.conf: # # /usr/bin/jackd -dalsa -dhw:0 -r48000 -p1024 -n2 # # Can also add --active-patchbay= # #------------------------------------------------------------------------ msg "$MyScript" "Starting QJackCtl and JACK with 'qjackctl --start'" qjackctl --start & sleep 2 else #------------------------------------------------------------------------ # From .jackdrc on ASUS laptop # /usr/bin/jackd -dalsa -r48000 -p1024 -n3 -Xraw -D -Chw:PCH -Phw:PCH # /usr/bin/jackd -dalsa -dhw:0 -r48000 -p1024 -n2 & #------------------------------------------------------------------------ msg "$MyScript" "/usr/bin/jackd -d$BackEnd $JackSettings" /usr/bin/jackd -d$BackEnd $JackSettings & sleep 1 fi else msg "$MyScript" "Jack off" if [ $UseQjackCtrl == "yes" ] ; then msg "$MyScript" "Killing qjackctl..." killall qjackctl sleep 1 fi msg "$MyScript" "Killing jackd..." killall jackd sleep 1 if [ $DoMpd == "yes" ] ; then sudo /bin/systemctl start mpd.socket sudo /bin/systemctl start mpd.service sleep 1 if [ $DoConky == "yes" ] ; then conky -c /home/ahlstrom/.conky2rc & 2> /dev/null fi fi fi # SetJack #------------------------------------------------------------------------------ # vim: ts=3 sw=3 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/README ================================================ Contributed Scripts Directory for Seq66 Chris Ahlstrom 2021-04-17 to 2023-03-26 This directory contains scripts that may be useful. Not all of them will work without some system setup. binfuncs Defines some bash functions that may be useful. bluejack Meant to send JACK audio to Bluetooth via "bluealsa". Painful. debug Provides a way to run the cgdb debugger via libtool. htmldoc Builds seq66.html from the TeX files of the user-manual. Needs pandoc. Jack Stops mpd and starts JACK; uses binfuncs. No longer used. make-checkout Checks out Makefile.ins to preserve the canonical makes. mutetest Runs a test of mute groups using Yoshimi. naming Fixes up the names of MP3 obtained via youtube-dl. ordercp Copies files while preserving their name ordering a la 'ls'. qbuild Rebuilds portmidi or rtmidi Seq66 using qmake on Linux. qtctrun Runs an application using the qt5ct platform theming. reconf Reconstructs the GNU Autotools project files for Seq66. seq66-nsm-proxy A script to try to run Seq66 via nsm-proxy. No guarantees. seq66.sed Sed script to convert Seq64 names to Seq66 names. No longer needed. session A new version of the Jack script above. Mostly of tutorial value. strap_functions Provides the functions used by the bootstrap script. timid Starts Timidity++ using JACK/ALSA, also will kill it. ystart Starts Yoshimi with a GM state file from yoshimi-cookbook. windows/VMS_fixes Fixes the Registry for the MIDI Mapper. Use at your own risk. Also see some scripts installed in data/linux/jack. # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: contrib/scripts/alsa.m4 ================================================ dnl Configure Paths for Alsa dnl Some modifications by Richard Boulton dnl Christopher Lansdown dnl Jaroslav Kysela dnl Last modification: $Id: alsa.m4,v 1.24 2004/09/15 18:48:07 tiwai Exp $ dnl AM_PATH_ALSA([MINIMUM-VERSION [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) dnl Test for libasound, and define ALSA_CFLAGS and ALSA_LIBS as appropriate. dnl enables arguments --with-alsa-prefix= dnl --with-alsa-enc-prefix= dnl --disable-alsatest dnl dnl For backwards compatibility, if ACTION_IF_NOT_FOUND is not specified, dnl and the alsa libraries are not found, a fatal AC_MSG_ERROR() will result. dnl AC_DEFUN([AM_PATH_ALSA], [dnl Save the original CFLAGS, LDFLAGS, and LIBS alsa_save_CFLAGS="$CFLAGS" alsa_save_LDFLAGS="$LDFLAGS" alsa_save_LIBS="$LIBS" alsa_found=yes dnl dnl Get the cflags and libraries for alsa dnl AC_ARG_WITH(alsa-prefix, [ --with-alsa-prefix=PFX Prefix where Alsa library is installed(optional)], [alsa_prefix="$withval"], [alsa_prefix=""]) AC_ARG_WITH(alsa-inc-prefix, [ --with-alsa-inc-prefix=PFX Prefix where include libraries are (optional)], [alsa_inc_prefix="$withval"], [alsa_inc_prefix=""]) dnl FIXME: this is not yet implemented AC_ARG_ENABLE(alsatest, [ --disable-alsatest Do not try to compile and run a test Alsa program], [enable_alsatest="$enableval"], [enable_alsatest=yes]) dnl Add any special include directories AC_MSG_CHECKING(for ALSA CFLAGS) if test "$alsa_inc_prefix" != "" ; then ALSA_CFLAGS="$ALSA_CFLAGS -I$alsa_inc_prefix" CFLAGS="$CFLAGS -I$alsa_inc_prefix" fi AC_MSG_RESULT($ALSA_CFLAGS) dnl add any special lib dirs AC_MSG_CHECKING(for ALSA LDFLAGS) if test "$alsa_prefix" != "" ; then ALSA_LIBS="$ALSA_LIBS -L$alsa_prefix" LDFLAGS="$LDFLAGS $ALSA_LIBS" fi dnl add the alsa library ALSA_LIBS="$ALSA_LIBS -lasound -lm -ldl -lpthread" LIBS="$ALSA_LIBS $LIBS" AC_MSG_RESULT($ALSA_LIBS) dnl Check for a working version of libasound that is of the right version. if test "x$enable_alsatest" = "xyes"; then min_alsa_version=ifelse([$1], ,0.1.1,$1) AC_MSG_CHECKING(for libasound headers version >= $min_alsa_version) no_alsa="" alsa_min_major_version=`echo $min_alsa_version | \ sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` alsa_min_minor_version=`echo $min_alsa_version | \ sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` alsa_min_micro_version=`echo $min_alsa_version | \ sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` AC_LANG_SAVE AC_LANG_C AC_TRY_COMPILE([ #include ], [ /* ensure backward compatibility */ #if !defined(SND_LIB_MAJOR) && defined(SOUNDLIB_VERSION_MAJOR) #define SND_LIB_MAJOR SOUNDLIB_VERSION_MAJOR #endif #if !defined(SND_LIB_MINOR) && defined(SOUNDLIB_VERSION_MINOR) #define SND_LIB_MINOR SOUNDLIB_VERSION_MINOR #endif #if !defined(SND_LIB_SUBMINOR) && defined(SOUNDLIB_VERSION_SUBMINOR) #define SND_LIB_SUBMINOR SOUNDLIB_VERSION_SUBMINOR #endif # if(SND_LIB_MAJOR > $alsa_min_major_version) exit(0); # else # if(SND_LIB_MAJOR < $alsa_min_major_version) # error not present # endif # if(SND_LIB_MINOR > $alsa_min_minor_version) exit(0); # else # if(SND_LIB_MINOR < $alsa_min_minor_version) # error not present # endif # if(SND_LIB_SUBMINOR < $alsa_min_micro_version) # error not present # endif # endif # endif exit(0); ], [AC_MSG_RESULT(found.)], [AC_MSG_RESULT(not present.) ifelse([$3], , [AC_MSG_ERROR(Sufficiently new version of libasound not found.)]) alsa_found=no] ) AC_LANG_RESTORE fi dnl Now that we know that we have the right version, let's see if we have the library and not just the headers. if test "x$enable_alsatest" = "xyes"; then AC_CHECK_LIB([asound], [snd_ctl_open],, [ifelse([$3], , [AC_MSG_ERROR(No linkable libasound was found.)]) alsa_found=no] ) fi if test "x$alsa_found" = "xyes" ; then ifelse([$2], , :, [$2]) LIBS=`echo $LIBS | sed 's/-lasound//g'` LIBS=`echo $LIBS | sed 's/ //'` LIBS="-lasound $LIBS" fi if test "x$alsa_found" = "xno" ; then ifelse([$3], , :, [$3]) CFLAGS="$alsa_save_CFLAGS" LDFLAGS="$alsa_save_LDFLAGS" LIBS="$alsa_save_LIBS" ALSA_CFLAGS="" ALSA_LIBS="" fi dnl That should be it. Now just export out symbols: AC_SUBST(ALSA_CFLAGS) AC_SUBST(ALSA_LIBS) ]) ================================================ FILE: contrib/scripts/audio ================================================ #!/bin/bash # # 2024-10-10 to 2024-12-23 # # Starts/stops mpd, ncmpcpp, and alsamixer. Key bindings can be made: # # Super-A: Start them all. Also creates a temp-file to flag it. # Super-2: Stop them all. Deletes the tempfile. # # The MPD configuration is in ~/.config/mpd/mpd.conf. The setup currently # supports either of these "alsa" outputs: "USB Audio CODEC" (Behringer # UCA202) or "iStore Audio" (a cheap USB-to-headphone jack). We use # pavucontrol to make the one we're using the default. # # We also set up .xbindkeysrc to use keystrokes to control amixer. # The device name is either "CODEC" or "Audio". # # Note that the terminal used is currently urxvt. # DOSTART=yes DOSTOP=no DOMIXER=no DONCMPCPP=no if [ "$1" == "--stop" ] ; then DOSTART="no" DOSTOP="yes" fi if [ "$1" == "--mixer" ] ; then DOSTART="no" DOSTOP="no" DOMIXER="yes" fi if [ "$1" == "--ncmpcpp" ] ; then DOSTART="no" DOSTOP="no" DONCMPCPP="yes" fi if [ "$DOSTART" == "yes" ] ; then if ! test -e /tmp/audio ; then echo "Starting mpd, ncmpcpp, and alsamixer..." systemctl --user start mpd /usr/bin/urxvt -geometry 80x20+130+24 -e ncmpcpp & /usr/bin/urxvt -geometry 80x20+800+24 -e alsamixer & touch /tmp/audio fi fi if [ "$DOSTOP" == "yes" ] ; then echo "Stopping mpd, ncmpcpp, and alsamixer..." systemctl --user stop mpd killall ncmpcpp killall alsamixer rm -f /tmp/audio fi if [ "$DOIXER" == "yes" ] ; then echo "Starting alsamixer..." /usr/bin/urxvt -geometry 80x20+800+24 -e alsamixer & fi if [ "$DONCMPCPP" == "yes" ] ; then echo "Starting ncmpcpp..." /usr/bin/urxvt -geometry 80x20+130+24 -e ncmpcpp & fi # vim: ts=3 sw=3 et ft=sh ================================================ FILE: contrib/scripts/binfuncs ================================================ #!/bin/bash #------------------------------------------------------------------------------ ## # \file binfunc # \library bin # \author Chris Ahlstrom # \date 2015-03-15 to 2019-09-27 # \version $Revision$ # \license GNU GPLv2 or above # # This script provides standard functions for my ~/bin scripts. # An optional external shell variable, LogFileName, if defined, is used # as the location to dump the log. # #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ # Provide a sane environment, just in case it is needed. #------------------------------------------------------------------------------ LANG=C export LANG export BinEditDate="2019-09-27" #------------------------------------------------------------------------------ # Global variables and standard error codes for our scripts #------------------------------------------------------------------------------ LogFileName="" ErrorBadOption=1 ErrorMsgBadOption="Unsupported option" ErrorLogFile=2 ErrorMsgLogFile="Please specify a --log name." ErrorMsgBadLogFile="Please specify a legal (no hyphen) --log name." #------------------------------------------------------------------------------ # die $exitcode $project $errormessage ... #------------------------------------------------------------------------------ function die() { ExitCode=$1 CurrProject=$2 Message="? $3" shift 3 while [ "$1" != "" ] ; do Message+=" " Message+="$1" Message+=$'\n' shift done if [ "$LogFileName" != "" ] ; then echo "$Message" >> $LogFileName fi cat << E_O_F $Message Run this script with the --help option for more information." E_O_F exit $ExitCode } #------------------------------------------------------------------------------ # msg $project $errormessage ... #------------------------------------------------------------------------------ function msg() { CurrProject=$1 Message="* $2" shift 2 while [ "$1" != "" ] ; do Message+=" " Message+="$1" Message+=$'\n' shift done if [ "$LogFileName" != "" ] ; then echo "$Message" >> $LogFileName fi echo "" echo "$Message" } #------------------------------------------------------------------------------ # setlog $filename #------------------------------------------------------------------------------ function setlog() { if [ "$1" == "" ] ; then die $ErrorLogFile "$MyScript" "$ErrorMsgLogFile" else export LogFileName="$1" fi SUBSTR="${1:0:1}" if [ $SUBSTR == "-" ] ; then die $ErrorLogFile "$MyScript" "$ErrorMsgBadLogFile" fi } # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: contrib/scripts/bluejack ================================================ #!/bin/bash # #****************************************************************************** # bluejack #------------------------------------------------------------------------------ ## # \file bluejack # \library Home/Audio # \author Chris Ahlstrom # \date 2021-08-05 # \update 2021-08-05 # \version $Revision$ # # /usr/bin/jackd -r -p128 -dalsa -dbluealsa -r44100 -p256 -n4 -s -S -P -o2 # # https://github.com/Arkq/bluez-alsa/wiki/Using-BlueALSA-with-the-JACK-Audio-Connection-Kit # # Possibly the simplest example is to create a jack sink using the bluealsa default PCM: # # zita-j2a -j bluealsa -d bluealsa -p 1024 -n 3 -c 2 -L # # -j bluealsa: Use the name "bluealsa" for the sink. This is the name JACK # clients use to send audio to the sink. # -d bluealsa: Use the alsa device "bluealsa". This name is predefined by # the bluez-alsa installation, and used in this way selects the most # recently connected A2DP playback device. # -p 1024: Use an ALSA period size of 1024 frames. # -n 3: Use an ALSA buffer of 3 periods. A smaller buffer will certainly # result in constant underruns. # -c 2: Use 2 channels. `zita-j2a` will create 2 jack ports, # `bluealsa:playback_1` and `bluealsa:playback_2`. # -L: Use S16_LE samples. Without this option `zita-j2a` may convert to # 32-bit samples only for the ALSA `plug` plugin to convert them back # to 16-bit. # # The command line arguments for alsa_out are the same as for zita-j2a except that -L # is not supported. # # alsa_out -j bt_headphones -d bluealsa:XX:XX:XX:XX:XX:XX -c 2 -p 4096 -n 3 # # alsa_out -p 10000 -d bluealsa:HCI=hci0,DEV="20:05:13:08:06:9B",profile=a2dp # # There is a bug in alsa_out that causes a buffer overflow if the device name is too # long. The longest device name allowed is 29 characters. Fortunately the form used # here is only 26 characters. # # jackd -r -d alsa -P bluealsa -n 3 -S -o 2 # # -R: Realtime. Bluetooth audio is high-latency, so no need for realtime. # -r: Do not request real-time scheduling (optional - will also work without # this) # # -d alsa: Use the ALSA backend # -P bluealsa: Use the most-recently connected BlueALSA playback device. Or # choose a specific device with `-P bluealsa:XX:XX:XX:XX:XX:XX` # -n 3: Use an ALSA buffer of 3 periods. A smaller buffer will produce underruns. # -o 2: Create 2 output channels # -S: Use S16_LE sample format # # Clients can then send to the BlueALSA device with # # JACK_PLAY_CONNECT_TO=system:playback_%d jack-play test_sounds.wav # # .asoundrc: # # It is possible to remove the ALSA plug plugin from the audio processing chain, # and instead rely on JACK's own audio processing features. To do this define # PCMs in our ~/.asoundrc file. # # pcm.bt-headphones { # type bluealsa # device XX:XX:XX:XX:XX:XX # profile a2dp # } # # ctl.bt-headphones { # type bluealsa # } # #------------------------------------------------------------------------------ /usr/bin/jackd -r -d alsa -P bluealsa -r44100 -n 3 -S -o 2 alsa_out -j bluealsa -p 4096 -d bluealsa:20:05:13:08:06:9B #------------------------------------------------------------------------------ # vim: ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/build_debug_code.bat ================================================ @echo off :: ************************************************************************** :: Seq66 Windows Build-Debug Package :: -------------------------------------------------------------------------- :: :: \file build_debug_code.bat :: \library Seq66 for Windows :: \author Chris Ahlstrom :: \date 2021-12-09 :: \update 2023-05-20 :: \license $XPC_SUITE_GPL_LICENSE$ :: :: This script sets up and creates a debug build of Seq66 for :: Windows. We needed it to figure out why Seq66 uses less CPU when :: playing than when not playing, and uses a high amount of CPU when :: not playing. And using Qt Creator's debugger doesn't cut it. :: We won't repeat any discussion found in the build_release_package :: batch file. :: :: Requirements: :: :: 1. A working build setup as specified in the build_release_package :: script, minus 7-zip. :: 2. Make sure Git Bash is installed. The DOS command line does not :: work well with gdb. :: :: Build Instructions: :: :: 1. Before running this script, modify the environment variables below :: in this batch file for your specific setup, including :: PROJECT_VERSION, PROJECT_DRIVE, and PROJECT_BASE. :: 2. Create a "shadow" directory at the same level as "seq66", change :: to it in a DOS console, and run :: "..\seq66\nsis\build_debug_code.bat". :: d. Or cd to the seq66\nsis directory and try :: "build_release_package.bat > build.log 2>&1" :: 6. The result is..... :: It is found in seq66/../seq66-debug/Seq66qt5. A log file is :: made in seq66/../seq66-debug/make.log, which can be checked :: for build warnings and errors. :: :: This batch file completely removes the old Windows seq66-release directory :: and re-does everything. :: :: Debug Instructions: :: :: 1. In a Git Bash window, change to the seq66-debug/Seq66qt5/debug :: directory. :: 2. Run "gdb qpseq66.exe". :: 3. When desired, type Ctrl-C to pause the application. :: 4. Show the threads with "info threads". :: 5. Got to the desired thread using "thread 8" (for example). :: :: x. After making a change to a source-code file: :: a. Delete the existing qpseq66.exe; the make process somehow :: won't rebuild it if it exists! :: b. Go to seq66-debug and run "mingw32-make" (again). :: c. Now the new version of qpseq66.exe. :: :: :: See the set of variable immediately below. :: ::--------------------------------------------------------------------------- set PROJECT_VERSION=0.99.5 set PROJECT_DATE=2023-05-20 set PROJECT_DRIVE=C: set PROJECT_BITS=32 set PROJECT_BASE=\Users\Chris\Documents\Home :: The project directory depends upon running this file from the nsis :: directory or from the shadow directory at the same level as "seq66". set PROJECT_REL_ROOT=..\seq66 set PROJECT_PRO=seq66.pro set SHADOW_DIR=seq66-debug set APP_DIR=Seq66qt5 set DEBUG_DIR=%APP_DIR%\debug set CONFIG_SET="CONFIG += debug" set AUX_DIR=data set DOC_DIR=data\share\doc :: C: %PROJECT_DRIVE% :: cd \Users\Chris\Documents\Home cd %PROJECT_BASE% del /S /Q %SHADOW_DIR% > NUL rmdir %SHADOW_DIR% mkdir %SHADOW_DIR% echo Creating Qt shadow directory %SHADOW_DIR% ... cd %SHADOW_DIR% :: qmake -makefile -recursive "CONFIG += debug" ..\seq66\seq66.pro echo qmake -makefile -recursive %CONFIG_SET% %PROJECT_REL_ROOT%\%PROJECT_PRO% echo mingw%PROJECT_BITS%-make (output to make.log) qmake -makefile -recursive %CONFIG_SET% %PROJECT_REL_ROOT%\%PROJECT_PRO% mingw32-make > make.log 2>&1 if ERRORLEVEL 1 goto builderror :: windeployqt Seq66qt5\debug echo windeployqt %DEBUG_DIR% windeployqt %DEBUG_DIR% goto done :builderror echo mingw32-make failed, aborting! Check make.log for errors. goto ender :done :: vim: ts=4 sw=4 ft=dosbatch fileformat=dos ================================================ FILE: contrib/scripts/compositor ================================================ #!/bin/bash ## # \file compositor # \library Any project # \author Chris Ahlstrom # \date 2024-10-17 # \update 2026-03-11 # # Xorg composite manager: compton older than picom. # # COMPT_EXECUTABLE="compton" or "picom" # # compton options: # # -b = daemon # -c = shadow # -C = no dock shadow # -G = no drag-n-drop shadow # -f = fading # -m = menu opacity # -i = inactive opacity # -e = frame opacity # # See ~/.config/picom.conf for the options. Also built from source now. # # /usr/bin/picom --daemon --shadow --fading --inactive-opacity=0.50 \ # --active-opacity=0.75 --frame-opacity=0.90 --menu-opacity \ # --config /dev/null # #------------------------------------------------------------------------------ READY="no" COMPOSITOR="picom" if [ "$1" == "--help" ] || [ "$1" == "help" ] ; then echo "compositor options: --stop, --compton; default is to run picom." exit 1 cat << E_O_F Usage v. 2024-12-02 compositor [--start] [--stop] [--restart] This script can stop $COMPOSITOR, or restart it in the same as in an .xsession script. Options: --start Start $COMPOSITOR. This is the default action. --stop Stop $COMPOSITOR. --kill Stop $COMPOSITOR. --restart Stop $COMPOSITOR, then restart it. --compton Use compton as the compositor. --picom Use picom as the compositor. Edit the COMPOSITOR variable in the compositor script to change the default compositor. E_O_F fi if [ "$1" == "--compton" ] ; then COMPOSITOR="compton" fi if [ "$1" == "--picom" ] ; then COMPOSITOR="picom" fi if [ "$1" == "--stop" ] || [ "$1" == "--kill" ] ; then echo "Stopping the $COMPOSITOR compositor..." killall $COMPOSITOR &> /dev/null exit 0 fi # Stop the compositor, then fall through to restart it. if [ "$1" == "--restart" ] ; then echo "Restarting the $COMPOSITOR compositor..." killall $COMPOSITOR &> /dev/null fi if [ "$COMPOSITOR" == "compton" ] ; then echo "Running $COMPOSITOR..." /usr/bin/$COMPOSITOR -b -cCGf --active-opacity 0.95 -m 1.0 -i 0.95 -e 0.9 \ --no-fading-openclose --sw-opti & sleep 0.25 exit 0 fi if [ "$COMPOSITOR" == "picom" ] ; then /usr/local/bin/$COMPOSITOR --daemon --log-file ~/.config/picom.log & sleep 0.25 exit 0 fi fi # vim: ts=3 sw=3 et ft=sh ================================================ FILE: contrib/scripts/configure-clang ================================================ #!/bin/bash #------------------------------------------------------------------------------ ## # \file configure-clang # \library bin # \author Chris Ahlstrom # \date 2023-12-06 to 2023-12-16 # \version $Revision$ # \license GNU GPLv2 or above # # This script tries to set up got building Seq66 with clang. # #------------------------------------------------------------------------------ # # export CFLAGS=" -flto -std=gnu99 " # export LDFLAGS=" -flto -fuse-ld=gold " # export RANLIB=llvm-ranlib # export QMAKESPEC="linux-llvm" # export QMAKESPEC="linux-clang" # # It's better to use the generic names, even the user must add soft-links to # the desired version. # # CLANGVER= # CC="clang$CLANGVER" CXX="clang++$CLANGVER" ./configure $* CC=clang CXX=clang++ ./configure $* if [ $? != 0 ] ; then echo "CC=clang CXX=clang++ ./configure $* failed" else echo "CC=clang CXX=clang++ ./configure $* is complete" fi #****************************************************************************** # qbuild (Seq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/conk ================================================ #!/bin/bash # #****************************************************************************** # Conk #------------------------------------------------------------------------------ ## # \file Conk # \library Home/Audio # \author Chris Ahlstrom # \date 2015-07-18 # \update 2026-03-11 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This script provides a way to control two instances of conky in a # manner pretty specific to my setup. # # This file can stop conky, or restart it, according to my # configuration. # #------------------------------------------------------------------------------ ConkyStatus="on" DoHelp="no" if [ $# -ge 1 ] ; then while [ "$1" != "" ] ; do case "$1" in --start | on) ConkyStatus="on" ;; --stop | off) ConkyStatus="off" ;; help) DoHelp="yes" ;; esac shift done fi if [ "$DoHelp" == "yes" ] ; then cat << E_O_F Usage: conk [options] Starts or stops two instances of conky as per my .xsession file. Stopping conky is useful when mpd is killed, and to get more room. Options: stop | out Stop all conky instances. start | on Restart the conky instances. This is the default action. help Show this text. E_O_F elif [ "$ConkyStatus" == "off" ] ; then killall conky > /dev/null else killall conky > /dev/null /usr/bin/conky -c /home/ahlstrom/.config/personal/conky/conkyrc 2> /dev/null & sleep 1 /usr/bin/conky -c /home/ahlstrom/.config/personal/conky/conky2rc 2> /dev/null & fi #------------------------------------------------------------------------------ # vim: ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/debug ================================================ #!/bin/sh # # This script runs libtool in order to execute a debugger. For our projects, # the setup must avoid the --disable-share option. So do this for our project: # # First edit the MYDEBUGGER macro to use your favorite debugger. # # $ ./bootstrap --full-clean [just to be sare] # $ ./bootstaps [just create the configure script] # $ ./configure --enable-debug [can add "=gdb" if desired] MYDEBUGGER=cgdb libtool --mode=execute $MYDEBUGGER $* #****************************************************************************** # debug #------------------------------------------------------------------------------ # vim: ts=3 sw=3 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/dot-xbindkeysrc ================================================ ########################### # xbindkeys configuration # ########################### # # Microsoft Arc Keyboard and much more # # Chris Ahlstrom # 2001-01-01 to 2024-12-23 # # Version: 0.2.0 # # Note: This setup requires the installation of xbindkeys, alsa-utils, # mrxvt, mpc, iceweasel, and some other odds and ends # # A list of keys is in /usr/include/X11/keysym.h and in # /usr/include/X11/keysymdef.h; The XK_ is not needed. # # List of modifier (on my keyboard): # # Control, Shift, Mod1 (Alt), Mod2 (NumLock), # Mod3 (CapsLock), Mod4, Mod5 (Scroll). # # Another way to specify a key is to use 'xev' and set the keycode with c:nnn # or the modifier with m:nnn where nnn is the keycode or the state returned by # xev # # We no longer care about idiot buttons, and want to enable only buttons found # on most keyboards. Some desktop environments or window managers may enable # them anyway. # # Symbology: # # m:0xyy Modifier key # c:nnn keysym value # # keystate_numlock = enable # keystate_scrolllock = enable # keystate_capslock = enable # # DISABLED. # Show bound keys file # "xbindkeys_show &" # control+shift + q # # These do not work: # # export ALSAOUTPUT="CODEC" # export ALSAOUTPUT="Audio" # Volume Down: Microsoft Arc keyboard's volume-down button: "amixer -c Audio -- sset PCM playback 5%- > /dev/null &" m:0x0 + c:122 NoSymbol # Volume Up: Microsoft Arc keyboard's volume-up button: "amixer -c Audio -- sset PCM playback 5%+ > /dev/null &" m:0x0 + c:123 NoSymbol # Volume Down: Tux- - "amixer -c Audio -- sset PCM playback 5%- > /dev/null &" m:0x40 + c:20 NoSymbol # Volume Down: Ctrl-Shift- - "amixer -c PCH -- sset Master playback 5%- > /dev/null &" m:0x5 + c:20 Control+Shift + minus # Volume Up: Tux- = (+) "amixer -c Audio -- sset PCM playback 5%+ > /dev/null &" m:0x40 + c:21 NoSymbol # Volume Up: Ctrl-Shift- + "amixer -c PCH -- sset Master playback 5%+ > /dev/null &" m:0x5 + c:21 Control+Shift + equal # Mute/Unmute: Tux-; (Tux is the frickin' "Windows" key) # # "amixer -c Audio -- sset PCM toggle > /dev/null &" # m:0x40 + c:19 # NoSymbol # Mute/Unmute: Microsoft Arc keyboard's mute button, works on some other # keyboards as well: "amixer -c Audio -- sset PCM toggle > /dev/null &" m:0x0 + c:121 NoSymbol # On the Microsoft Arc keyboard, the F7 to F12 keys are accessed using an # "Fn" key on keys F1 to F6. Somehow, this does not work with the Tux key # pressed, so we just put dummy assignments in here for now. # # gcalctool: Tux-F7 to F12 # "gcalctool" # m:0x40 + c:96 # NoSymbol # Play: Tux-/ # # "localhost" or "/var/run/mpd/socket" # #"mpc -h /var/run/mpd/socket play > /dev/null" #"mpc -h /var/run/mpd/socket stop > /dev/null" #"mpc -h /var/run/mpd/socket prev > /dev/null" #"mpc -h /var/run/mpd/socket next > /dev/null" "mpc play > /dev/null" m:0x40 + c:61 NoSymbol # Stop: Tux-\ "mpc stop > /dev/null" m:0x40 + c:51 NoSymbol # Prev: Tux-[ "mpc prev > /dev/null" m:0x40 + c:34 NoSymbol # Next: Tux-] "mpc next > /dev/null" m:0x40 + c:35 NoSymbol # Prev: Tux-, "mpc cdprev > /dev/null" m:0x40 + c:59 NoSymbol # Next: Tux-. "mpc next > /dev/null" m:0x40 + c:60 NoSymbol # DISABLED. We use this key for tmux. # gcalctool: Tux-` # # "gcalctool" # m:0x40 + c:49 # NoSymbol # mpc: Tux-; # # Used elsewhere in this file. # "mpc > /dev/null" m:0x40 + c:47 NoSymbol # mpc: Tux-' (single-quote) # # "mpc -h /var/run/mpd/socket clear > /dev/null" # m:0x40 + c:48 # NoSymbol # Additional keys, currently set up in XFce. The following settings try to # perform the same functions, with the same keys, in other window managers. # Desktop menu: Tux-m # # "xfdesktop --menu" # m:0x40 + m # NoSymbol # # Desktop reload: Tux-z # # "xfdesktop --reload" # m:0x40 + m # NoSymbol # Roxterm: Tux-r # # "TMUX= /usr/bin/roxterm --geometry=82x54 &" # m:0x40 + r # NoSymbol # Browser (Firefox): Tux-b # HANDLED by window manager # # "/usr/bin/firefox &" # m:0x40 + b # NoSymbol # Google Chrome browser: Tux-g # HANDLED by window manager # "/usr/bin/google-chrome-beta &" # "/usr/bin/chromium-browser --enable-plugins &" # # Chromium # HANDLED by window manager # "/usr/bin/google-chrome-beta &" # m:0x40 + g # NoSymbol # Illumination of keyboard: Tux-i "/home/ahlstrom/bin/kbd-illum --toggle" m:0x40 + i NoSymbol # Emulator (VirtualBox): Tux-e "/usr/bin/virtualbox &" m:0x40 + e NoSymbol # HANDLED by window manager # Audio (gmpc): Tux-a # "/usr/bin/xfce4-terminal --geometry=124x24+130+60 -e /usr/bin/ncmpcpp &" # m:0x40 + a # NoSymbol # HANDLED by window manager # Window (xfce4-terminal): Tux-w # "/usr/bin/xfce4-terminal --geometry 82x44 &" # HANDLED by window manager # "/usr/bin/Terminal --geometry 82x44 &" # m:0x40 + w # NoSymbol # HANDLED by window manager # Tmux window (xfce4-terminal): Tux-t # "/usr/bin/xfce4-terminal --geometry 82x44 -e /home/ahlstrom/bin/tmux2 &" # HANDLED by window manager # "/usr/bin/Terminal --geometry 82x44 -e /home/ahlstrom/bin/tmux2 &" # m:0x40 + t # NoSymbol # HANDLED by window manager # Screen lock: Tux-1 (!) # "/home/ahlstrom/bin/lock-desktop &" # m:0x40 + c:10 # Mod4 + 1 # HANDLED by window manager # Screen lock: Tux-2 (@) # "/home/ahlstrom/bin/lock-desktop &" # m:0x40 + c:10 # Mod4 + 2 # vim: ft=sh # # End of xbindkeys configuration ================================================ FILE: contrib/scripts/gdarkseq66 ================================================ #!/bin/sh # # Use dark coloring on qseq66, as configured in qt5ct. QT_QPA_PLATFORMTHEME=gtk2 qseq66 # vim: ts=3 sw=3 et ft=sh ================================================ FILE: contrib/scripts/grayscale.sh ================================================ #!/bin/bash # # Chris Ahlstrom # 2025-01-05 to 2025-01-05 # # Adapted from https://github.com/bubbleguuum/toggle-monitor-grayscale # # Should also work with compositor=compton, untested. # #----------------------------------------------------------------------- compositor=picom function usage { bin=$(basename $0) cat << E_O_F Usage: grayscale [options] Toggle monitors between color and grayscale mode via various mechanisms. $bin [ $compositor | nvidia | ddc | auto] $bin $compositor [$compositor args] $bin nvidia [nv mon]" $bin ddc [ddc mon]" $compositor: Use a GLX shader to set grayscale. nvidia: Use NVIDIA proprietary driver Digital Vibrance setting to set grayscale. ddc: Use the DDC/CI monitor protocol to set the monitor saturation to 0 (grayscale) if supported by the monitor. auto: Use $compositor if running, otherwise nvidia if available, otherwise ddc if available. $compositor args: in $compositor mode, optional $compositor parameters: nv mon: In nvidia mode, 'mon' is an optional monitor name as enumerated by xrandr. If unspecified, apply to all monitors managed by the NVIDIA driver. ddc mon: In DDC mode, use optional ddcutil options to identify the monitor. See 'man ddcutil'. If unspecified, apply to the first monitor detected by ddcutil. If invoked with no argument, auto is used." E_O_F exit 0 } function toggle_nvidia { dpy=$1 value=$(nvidia-settings -t -q DigitalVibrance) desaturate_value=-1024 # Set a value in the ]-1024..0[ range to desaturate colors instead # of full grayscale; -1024 => full grayscale. if (( value == $desaturate_value )); then value=0 toggle_mode="color" else value=$desaturate_value toggle_mode="grayscale" fi if [ -n "$dpy" ]; then param="[DPY:$dpy]/DigitalVibrance" else param="DigitalVibrance" fi nvidia-settings -a ${param}=${value} > /dev/null } function toggle_compositor { if $compositor --help | grep legacy-backends > /dev/null; then use_experimental_backends=1; grep_string="window-shader-fg" else use_experimental_backends=0; grep_string="glx-fshader-win" fi if pgrep -a -x $compositor | grep $grep_string > /dev/null; then pkill -x $compositor sleep 1 $compositor $* -b toggle_mode="color" else pkill -x $compositor sleep 1 if (( $use_experimental_backends == 1 )); then tmpfile=$(mktemp) trap 'rm -f "${tmpfile}"' EXIT cat > ${tmpfile} << EOF #version 330 in vec2 texcoord; uniform sampler2D tex; uniform float opacity; vec4 default_post_processing(vec4 c); vec4 window_shader() { vec4 c = default_post_processing(texelFetch(tex, ivec2(texcoord), 0)); float y = dot(c.rgb, vec3(0.2126, 0.7152, 0.0722)); c = opacity*vec4(y, y, y, c.a); return c; } EOF $compositor $* -b --backend glx --window-shader-fg ${tmpfile} 2> /dev/null else shader='uniform sampler2D tex; uniform float opacity; void main() { vec4 c = texture2D(tex, gl_TexCoord[0].xy); float y = dot(c.rgb, vec3(0.2126, 0.7152, 0.0722)); gl_FragColor = opacity*vec4(y, y, y, c.a); }' $compositor $* -b --backend glx --glx-fshader-win "${shader}" 2> /dev/null fi toggle_mode="grayscale" fi } function toggle_ddc { out=($(ddcutil $* getvcp 8a -t)) if (( $? != 0 )); then echo "ddc: this monitor does not support saturation control" exit 1 fi # out array: # # VCP 8A C 100 200 # | | # cur max if (( ${#out[@]} != 5 )); then echo "ddc: unexpected output getting current saturation state" exit 1 fi cur_saturation=${out[3]} max_saturation=${out[4]} # Set a value in ]0..max/2[ range to desaturate colors instead of # full grayscale; # 0 => full grayscale. desaturate_value=0 if (( cur_saturation == desaturate_value )); then new_saturation=$(( max_saturation / 2 )) # nominal saturation toggle_mode="color" else new_saturation=$desaturate_value toggle_mode="grayscale" fi ddcutil $* setvcp 8a $new_saturation } mode=$1 case $mode in --help | -h) usage ;; $compositor) if ! pgrep -x $compositor > /dev/null; then echo "$compositor is not running" exit 1 fi ;; nvidia) if ! which nvidia-settings &> /dev/null; then echo "nvidia-settings is not installed" exit 1 fi ;; ddc) if ! which ddcutil &> /dev/null; then echo "ddcutil is not installed" exit 1 fi ;; *) [ -z "$mode" ] && mode=auto if [ "$mode" = "auto" ]; then if pgrep -x $compositor > /dev/null; then mode=$compositor elif which nvidia-settings &> /dev/null; then mode=nvidia elif which ddcutil &> /dev/null; then mode=ddc else echo "Neither $compositor is running, nor is nvidia-settings" echo "installed, nor is ddcutil installed" exit 1 fi else usage fi esac # Pass eventual remaining arguments to toggle_* function if (( $# > 0 )); then shift fi if [ "$mode" = "nvidia" ]; then toggle_nvidia $* elif [ "$mode" = "$compositor" ]; then toggle_compositor $* else toggle_ddc $* fi if (( $? == 0 )); then echo "$mode: set to $toggle_mode" else echo "$mode: toggle failed" fi #****************************************************************************** # grayscale.sh #------------------------------------------------------------------------------ # vim: ts=4 sw=4 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/gvo ================================================ #!/bin/bash # # 2026-04-01 to 2026-04-01 # # Runs vim -O in a larger terminal. Under tmux, sometimes one cannot # get gvim to run from in a terminal. # /usr/bin/urxvt -geometry 164x56 -e gvim -O $1 $2 & # vim: ts=3 sw=3 et ft=sh ================================================ FILE: contrib/scripts/htmldoc ================================================ #!/bin/sh #------------------------------------------------------------------------------ ## # \file htmldoc # \library scripts # \author Chris Ahlstrom # \date 2021-03-10 to 2021-10-12 # \version $Revision$ # \license GNU GPLv2 or above # # This script, when run from the doc/latex/tex directory, generates a # quick-and-dirty single-page HTML version of the user's manual. # #------------------------------------------------------------------------------ pandoc -f latex -t html --extract-media=images -o seq66.html seq66-user-manual.tex #****************************************************************************** # htmldoc #------------------------------------------------------------------------------ # vim: ts=3 sw=3 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/make-checkout ================================================ #!/bin/sh #------------------------------------------------------------------------------ ## # \file make-checkout # \library bin # \author Chris Ahlstrom # \date 2020-10-10 to 2022-06-27 # \version $Revision$ # \license GNU GPLv2 or above # # This script removes the changed or deleted status of the configure and # Makefile.in files that can happen when the project is built on a different # system. Not a big fan of this Makefile.in crap, though I generally like # GNU Autotools. # #------------------------------------------------------------------------------ git checkout Makefile.in git checkout Seq66cli/Makefile.in git checkout Seq66qt5/Makefile.in git checkout configure git checkout aux-files/compile git checkout aux-files/depcomp git checkout aux-files/ltmain.sh git checkout aux-files/missing git checkout data/Makefile.in git checkout doc/Makefile.in git checkout data/share/doc/seq66-user-manual.pdf # git checkout doc/dox/Makefile.in git checkout doc/latex/Makefile.in git checkout doc/latex/tex/Makefile.in git checkout include/config.h.in git checkout libseq66/Makefile.in git checkout libseq66/include/Makefile.in git checkout libseq66/src/Makefile.in git checkout libsessions/Makefile.in git checkout libsessions/include/Makefile.in git checkout libsessions/src/Makefile.in git checkout m4/Makefile.in git checkout man/Makefile.in git checkout resources/pixmaps/Makefile.in git checkout seq_portmidi/Makefile.in git checkout seq_portmidi/include/Makefile.in git checkout seq_portmidi/src/Makefile.in git checkout seq_qt5/Makefile.in git checkout seq_qt5/forms/Makefile.in git checkout seq_qt5/include/Makefile.in git checkout seq_qt5/src/Makefile.in git checkout seq_rtmidi/Makefile.in git checkout seq_rtmidi/include/Makefile.in git checkout seq_rtmidi/src/Makefile.in #****************************************************************************** # make-checkout (Seq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/make-qt5-links ================================================ #!/bin/sh #------------------------------------------------------------------------------ ## # \file make-qt5-links # \library bin # \author Chris Ahlstrom, Fi3 # \date 2021-11-22 to 2021-11-23 # \version $Revision$ # \license GNU GPLv2 or above # # This script creates the links needed to adapt OpenSUSE and Fedora to # use qt5 for qmake, rcc, moc, etc. Thanks to Seq66 users sivejc and fi3. # # One might want to add the PATH update to one's profile/bashrc. # #------------------------------------------------------------------------------ mkdir -p ~/.local/bin/qt ln -s /bin/qmake-qt5 ~/.local/bin/qmake ln -s /bin/rcc-qt5 ~/.local/bin/rcc ln -s /bin/uic-qt5 ~/.local/bin/uic ln -s /bin/lrelease-qt5 ~/.local/bin/lrelease ln -s /bin/lupdate-qt5 ~/.local/bin/lupdate ln -s /bin/moc-qt5 ~/.local/bin/moc export PATH=$PATH:~/.local/bin/qt #****************************************************************************** # make-qt5-links (Seq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/mutetest ================================================ #!/bin/sh # # YOSHPATH="/usr/bin" YOSHPATH="/usr/local/bin" ROOTPATH="/home/ahlstrom/Home/ca/mls/git/" CFGPATH="$ROOTPATH/yoshimi-cookbook/sequencer64/b4uacuse" SEQPATH="$ROOTPATH/seq66/" BINPATH="$SEQPATH/Seq66qt5/" $YOSHPATH/yoshimi $1 --state=$CFGPATH/yoshimi-b4uacuse-gm.state &> /dev/null & aplaymidi -l $BINPATH/qseq66 -b 1 $SEQPATH/contrib/midi/mute-song.midi ================================================ FILE: contrib/scripts/naming ================================================ #!/bin/bash # # This script fixes up the names of MP3 files to shorten them, change spaces # to underscores, etc. If an argument is provided, it is removed. For # example: # # $ naming Frank_Zappa # # will removed "Frank_Zappa-" or "Frank_Zappa_" from the beginning of the # filename, which is useful when the tune is already in a directory named # "Frank_Zappa". rename "s/ /_/g" * rename "s/_-_/-/" * rename "s/_\(/-/" * rename "s/\(//" * rename "s/\)//" * rename "s/_\[/-/" * rename "s/]//" * rename "s/'//" * rename "s/,//" * rename "s/#//" * rename "s/://" * rename "s/_&_/_and_/g" * rename "s/Mr\._/Mr_/g" * rename "s/_\.mp3/.mp3/" * rename "s/-\.mp3/.mp3/" * if [ "$1" != "" ] ; then rename "s/$1-//" * rename "s/$1_//" * fi chmod -x *.mp3 &> /dev/null ls --color=always ================================================ FILE: contrib/scripts/notemapgen.py ================================================ #!/usr/bin/python3 # #***************************************************************************** ## # \file notemapgen.py # \author Chris Ahlstrom # \updates 2025-01-10 # \version $Revision$ # # Generates a simple notemap for testing. # #***************************************************************************** # Python modules import os import string import sys #***************************************************************************** # __main__ #----------------------------------------------------------------------------- file = open("test.notemap", "w") file.write("Test notemap file: " + file.name + "\n\n") for x in range(12, 128) : file.write("[Drum " + str(x) + "]\n\n") file.write("dev-name = \"Dev note " + str(x) + "\"\n") file.write("gm-name = \"GM note " + str(x - 12) + "\"\n") file.write("dev-note = " + str(x) + "\n") file.write("gm-note = " + str(x - 12) + "\n\n") file.write("# vim: sw=4 ts=4 wm=4 et ft=dosini") file.close() #***************************************************************************** # End of notemapgen.py #----------------------------------------------------------------------------- # vim: ts=4 sw=4 et ft=python #----------------------------------------------------------------------------- ================================================ FILE: contrib/scripts/ordercp ================================================ #!/bin/bash # # cd MusicToMove/ # # find . -print0 | sort -zr | xargs -0 cp --parents # --target-directory=/media/disk/MUSIC/FoldersMoved # # -print0, sort -z, and xargs -0 must all be specified togethern. # # Usage of this script: # # $ cd /opt/mp3/music/dongle (i.e folder_containing_all_music_folders) # $ ordercp /media/usb/b/1 find . -print0 | sort -z | xargs -0 cp --parents --target-directory=$1 &> /dev/null ================================================ FILE: contrib/scripts/qbuild-bash ================================================ #!/bin/bash #------------------------------------------------------------------------------ ## # \file qbuild # \library bin # \author Chris Ahlstrom # \date 2020-09-27 to 2023-12-24 # \version $Revision$ # \license GNU GPLv2 or above # # This script runs qmake and make, and should be run from a Qt shadow # directory. Look at the Bash variables below and make any changes needed for # your setup. # #------------------------------------------------------------------------------ BUILDTYPE="release" ENGINE="" DOBUILD="yes" QMAKER="qmake" MAKER="make" MAKEOPTIONS="-j 8" MAKELOG="make.log" QOPTIONS="-makefile -recursive -Wall" PROJFILE="../seq66/seq66.pro" #****************************************************************************** # Help #------------------------------------------------------------------------------ if [ "$1" == "--help" ] ; then cat << E_O_F The qbuild script makes it easier to remember how to build Qt shadow builds for Seq66, to generate either qpseq66 (portmidi) or qseq66 (rtmidi). The default is to build 'qpseq66' for release. The build can be modified by adding the flags 'debug' and 'rtmidi'. cd .. (parent directory of seq66) mkdir shadow-rtmidi (if not already made) cd shadow rm -rf * (removed old junk; CAREFUL!) qbuild debug rtmidi vi make.log This must be run from a shadow directory. The build output is written to make.log. Other options are '--help' and '--dry-run'. Please note that this script is Bash-specific and *deprecated*. Use the qbuild.sh script instead. E_O_F exit 1 fi if [ $# -ge 1 ] ; then while [ "$1" != "" ] ; do case "$1" in dry | --dry-run) DOBUILD="no" ;; debug | --debug) BUILDTYPE="debug" ;; rtmidi | rt | --rtmidi) ENGINE="rtmidi" ;; *) echo "? Unsupported qbuild option; --help for more information" exit 1 ;; esac shift done fi if [ "$DOBUILD" == "yes" ] ; then rm -f $MAKELOG date > $MAKELOG echo " " &>> $MAKELOG echo " $QMAKER $QOPTIONS \""CONFIG += $BUILDTYPE $ENGINE\"" $PROJFILE" &>> $MAKELOG echo " " &>> $MAKELOG $QMAKER $QOPTIONS "CONFIG += $BUILDTYPE $ENGINE" $PROJFILE &>> $MAKELOG echo " " &>> $MAKELOG echo " $MAKER $MAKEOPTIONS &> $MAKELOG" &>> $MAKELOG echo " " &>> $MAKELOG $MAKER $MAKEOPTIONS &>> $MAKELOG echo "Read $MAKELOG for build details." else echo "Commands to be run:" echo " rm -f $MAKELOG" echo " $QMAKER $QOPTIONS \""CONFIG += $BUILDTYPE $ENGINE\"" $PROJFILE" echo " $MAKER $MAKEOPTIONS &> $MAKELOG" echo " $MAKELOG will contain the build details." fi #****************************************************************************** # qbuild (Seq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/qbuild.sh ================================================ #!/bin/sh #------------------------------------------------------------------------------ ## # \file qbuild.sh # \library bin # \author Chris Ahlstrom # \date 2023-12-23 to 2023-08-05 # \version $Revision$ # \license GNU GPLv2 or above # # This script runs qmake and make, and should be run from a Qt shadow # directory. Look at the variables below and make any changes needed for # your setup. # #------------------------------------------------------------------------------ BUILDTYPE="release" ENGINE="rtmidi" DOBUILD="yes" QMAKER="qmake" MAKER="make" CPUCOUNT="8" MAKEOPTIONS="-j 8" MAKELOG="make.log" QOPTIONS="-makefile -recursive -Wall" PROJFILE="../seq66/seq66.pro" PROJFILE2="seq66.pro" #****************************************************************************** # Help #------------------------------------------------------------------------------ if test "$1" = "--help" -o "$1" = "help" ; then cat << E_O_F This script makes it easy to build Qt shadow builds for Seq66, to generate either qpseq66 (portmidi) or qseq66 (rtmidi). The default is to build 'qseq66' as a release using the rtmidi engine. The build can be modified by adding the flags 'debug' or 'portmidi' (which generates 'qpseq66'). cd .. (parent directory of seq66) mkdir shadow-rtmidi (if not already made) cd shadow-rtmidi rm -rf * (removed old junk; CAREFUL!) qbuild.sh (could add 'debug rtmidi', etc.) vi make.log This script must be run from a shadow directory. The build output is written to make.log. Other options are '--help' and '--dry-run'. Note that, if any Qt 'pro' file in the project has changed, one should first remove all files from the shadow directory (CAUTION!!!) before rebuilding. Options: --help Show this help text. --cpus n Use n cores. The default is 8. --dry-run Do not build, just show the steps --debug Enable a debug build. --portmidi Build using Seq66's internal portmidi engine. --rtmidi Build using Seq66's internal rtmidi engine [default]. E_O_F exit 1 fi if test $# -ge 1 ; then while [ "$1" != "" ] ; do case "$1" in cpus | --cpus) shift CPUCOUNT=="$1" MAKEOPTIONS="-j $CPUCOUNT" ;; dry | --dry-run) DOBUILD="no" ;; debug | --debug) BUILDTYPE="debug" ;; portmidi | --portmidi) ENGINE="portmidi" ;; rtmidi | rt | --rtmidi) ENGINE="rtmidi" ;; *) echo "? Unsupported qbuild option; --help for more information" exit 1 ;; esac shift done fi if test "$DOBUILD" = "yes" ; then rm -f $MAKELOG date > $MAKELOG echo " " >> $MAKELOG 2>&1 echo " $QMAKER $QOPTIONS \""CONFIG += $BUILDTYPE $ENGINE\"" $PROJFILE" >> $MAKELOG 2>&1 echo " " >> $MAKELOG echo " $QMAKER $QOPTIONS \""CONFIG += $BUILDTYPE $ENGINE\"" $PROJFILE2..." $QMAKER $QOPTIONS "CONFIG += $BUILDTYPE $ENGINE" $PROJFILE >> $MAKELOG 2>&1 echo " " >> $MAKELOG echo " $MAKER $MAKEOPTIONS > $MAKELOG" >> $MAKELOG 2>&1 echo " " >> $MAKELOG echo " $MAKER $MAKEOPTIONS > $MAKELOG ..." $MAKER $MAKEOPTIONS >> $MAKELOG 2>&1 echo "Read $MAKELOG for build errors or warnings." else echo "Commands to be run:" echo " rm -f $MAKELOG" echo " $QMAKER $QOPTIONS \""CONFIG += $BUILDTYPE $ENGINE\"" $PROJFILE" echo " $MAKER $MAKEOPTIONS > $MAKELOG" echo " $MAKELOG will contain the build details." fi #****************************************************************************** # qbuild.sh (Seq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/qbuildwin.sh ================================================ #!/bin/sh #------------------------------------------------------------------------------ # \file qbuildwin.sh # \library bin # \author Chris Ahlstrom # \date 2023-12-23 to 2024-10-19 # \version $Revision$ # \license GNU GPLv2 or above # # This script runs qmake and make, and should be run from a Qt shadow # directory. Look at the variables below and make any changes needed for # your setup. # # This version is preset for Windows builds in Msys2. # # Also see: # # https://www.kienlab.com/posts/windows-qt5-deploy-static-linking-msys2/ # #------------------------------------------------------------------------------ BUILDTYPE="release" ENGINE="portmidi" DOBUILD="yes" QMAKER="qmake" MAKER="make" CPUCOUNT="4" MAKEOPTIONS="-j 4" MAKELOG="make.log" QOPTIONS="-makefile -recursive -Wall" PROJFILE="../seq66/seq66.pro" PROJFILE2="seq66.pro" #****************************************************************************** # Help #------------------------------------------------------------------------------ if test "$1" = "--help" -o "$1" = "help" ; then cat << E_O_F This script makes it easy to build Qt shadow builds for Seq66, to generate either qpseq66 (portmidi) or qseq66 (rtmidi). The default is to build 'qseq66' as a release uisng the rtmidi engine. The build can be modified by adding the flags 'debug' or 'portmidi' (which generates 'qpseq66'). cd .. (parent directory of seq66) mkdir shadow-rtmidi (if not already made) cd shadow-rtmidi rm -rf * (removed old junk; CAREFUL!) qbuild.sh (could add 'debug rtmidi', etc.) vi make.log This script must be run from a shadow directory. The build output is written to make.log. Other options are '--help' and '--dry-run'. Note that, if any Qt 'pro' file in the project has changed, one should first remove all files from the shadow directory (CAUTION!!!) before rebuilding. Options: --help Show this help text. --cpus n Use n cores. The default is 8. --dry-run Do not build, just show the steps --debug Enable a debug build. --portmidi Build using Seq66's internal portmidi engine. --rtmidi Build using Seq66's internal rtmidi engine [default]. E_O_F exit 1 fi if test $# -ge 1 ; then while [ "$1" != "" ] ; do case "$1" in cpus | --cpus) shift CPUCOUNT=="$1" MAKEOPTIONS="-j $CPUCOUNT" ;; dry | --dry-run) DOBUILD="no" ;; debug | --debug) BUILDTYPE="debug" ;; portmidi | --portmidi) ENGINE="portmidi" ;; rtmidi | rt | --rtmidi) ENGINE="rtmidi" ;; *) echo "? Unsupported qbuild option; --help for more information" exit 1 ;; esac shift done fi if test "$DOBUILD" = "yes" ; then rm -f $MAKELOG date > $MAKELOG echo " " >> $MAKELOG 2>&1 echo " $QMAKER $QOPTIONS \""CONFIG += $BUILDTYPE $ENGINE\"" $PROJFILE" >> $MAKELOG 2>&1 echo " " >> $MAKELOG echo " $QMAKER $QOPTIONS \""CONFIG += $BUILDTYPE $ENGINE\"" $PROJFILE2..." $QMAKER $QOPTIONS "CONFIG += $BUILDTYPE $ENGINE" $PROJFILE >> $MAKELOG 2>&1 echo " " >> $MAKELOG echo " $MAKER $MAKEOPTIONS > $MAKELOG" >> $MAKELOG 2>&1 echo " " >> $MAKELOG echo " $MAKER $MAKEOPTIONS > $MAKELOG ..." $MAKER $MAKEOPTIONS >> $MAKELOG 2>&1 echo "Read $MAKELOG for build errors or warnings." else echo "Commands to be run:" echo " rm -f $MAKELOG" echo " $QMAKER $QOPTIONS \""CONFIG += $BUILDTYPE $ENGINE\"" $PROJFILE" echo " $MAKER $MAKEOPTIONS > $MAKELOG" echo " $MAKELOG will contain the build details." fi #****************************************************************************** # qbuild.sh (Seq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/qtctrun ================================================ #!/bin/bash # # Run an application using Qt coloring as configured in qt5ct. QTXCT="none" type qt6ct &> /dev/null if [ $? == 0 ] ; then QTXCT="qt6ct" else type qt5ct &> /dev/null if [ $? == 0 ] ; then QTXCT="qt5ct" fi fi if [ "$QTXCT" != "none" ] ; then QT_QPA_PLATFORMTHEME=$QTXCT $* & else echo "No qt6ct or qt5ct application is installed." exit 1 fi ================================================ FILE: contrib/scripts/qtests ================================================ #!/bin/bash # #****************************************************************************** # qtests (Seq66) #------------------------------------------------------------------------------ ## # \file qtests # \library Seq66 # \author Chris Ahlstrom # \date 2021-12-14 # \update 2021-12-14 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module serves to implement some of the test documented in # contrib/tests/4x4/README. See that file for issues and to-dos. # #------------------------------------------------------------------------------ GITPATH="$HOME/Home/ca/mls/git" PROJPATH="$GITPATH/seq66" EXEDIR="Seq66qt5" DEBUGPATH="$GITPATH/shadow-seq66-rtmidi" DEBUGNAME="qseq66" EXENAME="qseq66" EXECUTABLE="$PROJPATH/$EXEDIR/$EXENAME" DEBUGABLE="$DEBUGPATH/$EXEDIR/$DEBUGNAME" TESTFOLDER="contrib/tests/4x4" # TESTCONFIGS="$HOME/.config/seq66/4x4" TESTFILES="$PROJPATH/$TESTFOLDER" TESTCOMMAND="$EXECUTABLE --home $TESTFILES $TESTFILES/buff.midi" # A very long command with all the directories expanded! # # echo "$TESTCOMMAND" # exec $TESTCOMMAND & $TESTCOMMAND & #****************************************************************************** # qtests (Seq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/reconf ================================================ #!/bin/bash #------------------------------------------------------------------------------ ## # \file reconf # \library bin # \author Chris Ahlstrom # \date 2020-11-25 to 2022-01-07 # \version $Revision$ # \license GNU GPLv2 or above # # This script can be run when make fails due to local variations on the # versions of the GNU autotools. # # Not sure why version numbers are used in GNU autotools. # #------------------------------------------------------------------------------ libtoolize --force aclocal autoheader automake --force-missing --add-missing autoconf #****************************************************************************** # reconf (Seq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/recordpa ================================================ #!/bin/bash # #****************************************************************************** # recordpa #------------------------------------------------------------------------------ ## # \file recordpa # \library Any project # \author Chris Ahlstrom # \date 2022-09-14 # \update 2022-09-24 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # The above is modified by the following to remove even the mild GPL # restrictions: # # Use this script in any manner whatsoever. You don't even need to give # me any credit. However, keep in mind the value of the GPL in keeping # software and its descendant modifications available to the community # for all time. # # Simply records the output of our USB audio device as received through # PulseAudio. # # parecord is equivalent to "pacat -r --file-format". Refer to the pacat(1) # man page for pacat's flags. To get the sources for audio record. # # $ pactl list sources short # # The following two lines produce the same result. # # $ parecord -d alsa_input.pci-0000_00_1f.3.analog-stereo speech.flac # $ parecord -d 1 speech.flac # #------------------------------------------------------------------------------ if [ "$1" == "--list" ] ; then pactl list sources short elif [ "$1" == "--help" ] ; then cat << E_O_F Usage: recordpa --help recordpa --list recordpa x.wav record x.wav E_O_F elif [ "$2" == "" ] ; then parecord --channels=1 --record --device=alsa_output.usb-Burr-Brown_from_TI_USB_Audio_CODEC-00.analog-stereo.monitor $1 else parecord --channels=1 --record --device=$1 $2 fi #****************************************************************************** # recordpa #------------------------------------------------------------------------------ # vim: ts=3 sw=3 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/release ================================================ #!/bin/bash #------------------------------------------------------------------------------ ## # \file release # \library bin # \author Chris Ahlstrom # \date 2025-02-02 to 2025-02-03 # \version $Revision$ # \license GNU GPLv2 or above # # This script provides help in remembering what to edit for a new # release. # #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ # Provide a sane environment, just in case it is needed. #------------------------------------------------------------------------------ LANG=C export LANG export ReleaseEditDate="2025-02-02" echo "Calling vi(m) to edit a number of files before final wip check-in." vi configure.ac VERSION README.md NEWS RELNOTES \ include/qt/portmidi/seq66-config.h include/qt/rtmidi/seq66-config.h \ data/license.text data/readme.text data/readme.windows \ doc/latex/tex/seq66-user-manual.tex \ nsis/Seq66Setup.nsi nsis/Seq66Constants.nsh nsis/build_release_package.bat cat << E_O_F Run the following commands: git commit -a -m "Prep for releasing wip 0.99.18 to master." git checkout master git merge wip rm (any junk files in contrib/midi and data/midi) ./bootstrap --full-clean ./bootstrap -er make -j 8 &> make.log ./Seq66qt/qseq66 for a smoke test push doc/latex make popd sudo make install Then do the documented GitHub and Windows process. E_O_F # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: contrib/scripts/seq66-nsm-proxy ================================================ #!/bin/bash # #****************************************************************************** # seq66-nsm-proxy #------------------------------------------------------------------------------ ## # \file seq66-nsm-proxy # \library Seq66 # \author Chris Ahlstrom # \date 2020-10-03 # \update 2020-10-03 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # IN PROGRESS # #------------------------------------------------------------------------------ MIDIDIR="$HOME/.local/share/seq24/blank.midi" MIDIBLANK="$NSM_CLIENT_ID.midi" if [ -z "$NSM_SESSION_NAME" ]; then echo "This script must be run under nsm-proxy." exit 0 fi trap 'kill -TERM $PID' TERM INT if ! [ -a "$MIDIBLANK" ]; then echo "Creating empty midi file..." cp "$MIDIDIR" "$MIDIBLANK" fi # qseq66 "$MIDIBLANK" & exec qseq66 "$MIDIBLANK" PID=$! wait $PID trap - TERM INT wait $PID EXIT_STATUS=$? #****************************************************************************** # seq66-nsm-proxy #------------------------------------------------------------------------------ # vim: ts=3 sw=3 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/seq66.sed ================================================ #!/usr/bin/sed -i -f # ## \file seq66.sed # # Quick fixes to some stock changes in going from seq64 to seq66. # # \library seq66 application # \author Chris Ahlstrom # \date 2018-11-09 # \updates 2019-03-09 # \license GNU GPLv2 or above # # Execution: # # sed -i -f seq66.sed *.cpp *.c *.h *.hpp # # Order is important, the longer matches should be hit first. s/seq24\/sequencer64/seq66/g s/seq24/seq66/g s/seq64/seq66/g s/SEQ64/SEQ66/g s/sequencer64/seq66/g s/Sequencer64/Seq66/g s/SEQUENCER64/SEQ66/g s/Seq24 team; modifications by // # s/seq66\/seq66/seq66/g # File-name changes s/"easy_macros.h" /"basic_macros.hpp"/g s/"basic_macros.h" /"basic_macros.hpp"/g s/CharVector/bytes/g s/easy_macros/basic_macros/g s/EASY_MACROS/BASIC_MACROS/g s/EventStack/eventstack/g s/file_functions/filefunctions/g s/FILE_FUNCTIONS/FILEFUNCTIONS/g s/midibyte\.hpp/midibytes.hpp/g s/MIDIBYTE/MIDIBYTES/g s/midi_container/midi_vector_base/g s/MIDI_CONTAINER/MIDI_VECTOR_BASE/g s/optionsfile/rcfile/g s/OPTIONSFILE/RCFILE/g s/palette_pair_t/pair/g s/perform/performer/g s/PERFORM/PERFORMER/g s/performerance/performance/g s/platform_macros/seq66_platform_macros/g s/ PLATFORM_/ SEQ66_PLATFORM_/g s/rc_settings/rcsettings/g s/RC_SETTINGS/RCSETTINGS/g s/SysexContainer/sysex/g s/userfile/usrfile/g s/USERFILE/USRFILE/g s/user_instrument/usrinstrument/g s/USER_INSTRUMENT/USRINSTRUMENT/g s/user_midi_bus/usrmidibus/g s/USER_MIDI_BUS/USRMIDIBUS/g s/user_settings/usrsettings/g s/USERSETTINGS/USRSETTINGS/g #------------------------------------------------------------------------------- # Shorten long lines having nothing but spaces, that end in * #------------------------------------------------------------------------------- # s/ *\*$/ */ #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # End of seq66.sed #------------------------------------------------------------------------------- # vim: ts=3 sw=3 et ft=sed #------------------------------------------------------------------------------- ================================================ FILE: contrib/scripts/session ================================================ #!/bin/bash # #****************************************************************************** # session #------------------------------------------------------------------------------ ## # \file session # \library Home/Audio # \author Chris Ahlstrom # \date 2014-03-18 # \update 2019-09-28 # \version $Revision$ # # This script provides a way to make a pretty specific connection setup # on Linux using JACK. # # See the file seq-session-layout.odg (LibreOffice Draw file). # # This provides an extended version of seq-session that can also # stop/start the mpd and jackd daemons. # # Dependencies: # # - sudo and the text noted below in a file in /etc/sudoers.d # - jackd and jack_connect # - aplay (for listing devices) # - a2jmidid # - zita-j2a # - qjackctl # - yoshimi (software synthesizer) # - qseq66 (MIDI loop application) # # systemctl-doers: # # Create this file in /etc/sudoers.d, with the following line of text: # # myusername myhostname =NOPASSWD: # /bin/systemctl stop mpd,/bin/systemctl start mpd # # This is actually one line of text. It is specific to Debian Sid # (unstable) running systemd. It will differ between systems. # #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ # Adjustable variables #------------------------------------------------------------------------------ SEQ66="qseq66" REDHAT_START_MPD="systemctl start mpd" REDHAT_STOP_MPD="systemctl stop mpd" UBUNTU_START_MPD="service mpd start" UBUNTU_STOP_MPD="service mpd stop" START_MPD="$UBUNTU_START_MPD" STOP_MPD="$UBUNTU_STOP_MPD" TEST_SONG="$HOME/Home/Audio/Seq24/click_4_4.midi" # YOSHIMI_STATE="$HOME/.config/yoshimi/yoshimi.state" YOSHIMI_STATE="$HOME/Home/ca/mls/git/yoshimi-cookbook/sequencer64/b4uacuse/yoshimi-b4uacuse-gm.state" #------------------------------------------------------------------------------ # State variables #------------------------------------------------------------------------------ DO_SETUP="yes" DO_TEARDO_WN="no" DO_HELP="no" DO_ADVICE="yes" DO_QJACKCTL="yes" DO_A2JMIDID="yes" DO_ZITAJ2A="no" DO_SEQ66="yes" DO_QSYNTH="no" DO_YOSHIMI="yes" DO_YOSH_CONNECT="no" DO_MIDI_CONNECT="no" DO_SKIP_CONNECTS="yes" DO_MANUAL_PORTS="yes" #------------------------------------------------------------------------------ # die $exitcode $project $errormessage #------------------------------------------------------------------------------ # # Unfortunately, we need to figure out to get failure data from a daemon. # #------------------------------------------------------------------------------ function die() { EXITCODE=$1 CURPROJECT=$2 MESSAGE="? [$CURPROJECT] $3" MESSAGE+=$'\n' shift 3 while [ "$1" != "" ] ; do MESSAGE+=" " MESSAGE+="$1" MESSAGE+=$'\n' shift done if [ "$DO_LOG" == "yes" ] && [ "$LOGFILENAME" != "" ] ; then echo "$MESSAGE" >> $LOGFILENAME fi echo "$MESSAGE" echo "Run this script with the --help option for more information." echo "Will stop all processes already started." echo "" DO_TEARDO_WN="yes" exit $EXITCODE } #------------------------------------------------------------------------------ # USB Audio detection #------------------------------------------------------------------------------ # # Figures out the device numbers for our USB audio device. Might also # search for "CODEC", instead of "USB". Depends on the system. # # HWUSB="hw:2" # #------------------------------------------------------------------------------ MAINHWUSB=$(aplay -l | grep USB | awk '{ print substr($2, 0, 1) }') if [ "$MAINHWUSB" == "" ] ; then DO_ZITAJ2A="no" echo "Warning: No USB audio device plugged in." else SUBHWUSB=$(aplay -l | grep USB | awk '{ print substr($8, 0, 1) }') export HWUSB="hw:$MAINHWUSB" # HWUSB="hw:$MAINHWUSB,$SUBHWUSB" fi #------------------------------------------------------------------------------ # Options-processing loop #------------------------------------------------------------------------------ if [ $# -ge 1 ] ; then while [ "$1" != "" ] ; do case "$1" in --setup | -s | --start) DO_SETUP="yes" DO_TEARDO_WN="no" ;; --teardown | -t | --stop) DO_SETUP="no" DO_TEARDO_WN="yes" DO_ADVICE="no" ;; --no-advice | --na) DO_ADVICE="no" ;; --no-connects) DO_SKIP_CONNECTS="yes" ;; --manual) DO_MANUAL="yes" ;; --auto) DO_MANUAL="no" ;; --j2a) DO_ZITAJ2A="yes" shift if [ "$1" != "" ] ; then export HWUSB="$1" fi ;; --no-j2a) DO_ZITAJ2A="no" ;; --song) shift if [ "$1" != "" ] ; then TEST_SONG="$1" fi ;; --state) shift if [ "$1" != "" ] ; then YOSHIMI_STATE="$1" fi ;; --help) DO_HELP="yes" DO_SETUP="no" DO_ADVICE="no" ;; esac shift done fi if [ "$DO_SETUP" == "yes" ] ; then #------------------------------------------------------------------------------ # Stop MPD and start JACK #------------------------------------------------------------------------------ sudo $UBUNTU_STOP_MPD #------------------------------------------------------------------------------ # QJackCtl #------------------------------------------------------------------------------ # # Run qjackctl and start jack, with this setting in ~/.jackdrc or # ~/.config/rncbc.org/QjackCtl.conf: # # /usr/bin/jackd -dalsa -dhw:0 -r48000 -p1024 -n2 # # Can also add --active-patchbay= # #------------------------------------------------------------------------------ if [ "$DO_QJACKCTL" == "yes" ] ; then echo "Starting QJackCtl/JACK: 'qjackctl --start'" qjackctl --start & sleep 2 fi #------------------------------------------------------------------------------ # a2jmidid #------------------------------------------------------------------------------ # # Start the ALSA-to-JACK bridge, bridging the hardware ports as well as # the software ports. # # An alternative is a2jmidi_bridge, which creates only one MIDI capture # (readable) output port and a writable ALSA playback client. # #------------------------------------------------------------------------------ if [ "$DO_A2JMIDID" == "yes" ] ; then echo "Starting ALSA-to-JACK bridge: 'a2jmidid --export-hw'" a2jmidid --export-hw & sleep 2 fi #------------------------------------------------------------------------------ # zita-j2a #------------------------------------------------------------------------------ # # Create a writable audio client/port accessible from JACK, to support # audio output to the USB audio device. Note that we'll need a way to # detect the correct hw port for this device, which is named "CODEC" on # my Linux box. # #------------------------------------------------------------------------------ if [ "$DO_ZITAJ2A" == "yes" ] ; then echo "Starting Zira JACK-to-ALSA bridge: 'zita-j2a -d $HWUSB'" if [ "$HWUSB" == "" ] ; then echo "? Need to export HWUSB=hw:X, use 'aplay -l' to find X." else zita-j2a -d $HWUSB & sleep 2 fi fi #------------------------------------------------------------------------------ # qseq66 #------------------------------------------------------------------------------ # # With qseq66.rc containing: # # [manual-ports] # 1 # # or using the --manual-ports option on the command line. # #------------------------------------------------------------------------------ if [ "$DO_SEQ66" == "yes" ] ; then echo "Starting sequencer with song '$TEST_SONG':" if [ "$DO_MANUAL" == "yes" ] ; then echo " $SEQ66 --manual-ports ..." $SEQ66 --manual-ports "$TEST_SONG" & else echo " $SEQ66 --auto-ports ..." $SEQ66 --auto-ports "$TEST_SONG" & fi sleep 2 fi #------------------------------------------------------------------------------ # yoshimi and jack_connect #------------------------------------------------------------------------------ # # Start yoshimi and bug out. Starts it with options to use JACK MIDI # input, JACK audio output, and to auto-connect to JACK audio. # #------------------------------------------------------------------------------ if [ "$DO_YOSHIMI" == "yes" ] ; then if [ "$DO_ZITAJ2A" == "yes" ] ; then if [ "$DO_YOSH_CONNECT" == yes ] ; then echo "Starting yoshimi soft synth:" echo " yoshimi -j -J --state=$YOSHIMI_STATE" yoshimi -j -J --state=$YOSHIMI_STATE & sleep 2 echo "jack_connect yoshimi:left zita-j2a:playback_1" if [ "$DO_SKIP_CONNECTS" == "no" ] ; then jack_connect yoshimi:left zita-j2a:playback_1 else echo "... skipped." fi echo "jack_connect yoshimi:right zita-j2a:playback_2" if [ "$DO_SKIPCONNECTS" == "no" ] ; then jack_connect yoshimi:right zita-j2a:playback_2 else echo "... skipped." fi else echo "Starting yoshimi synthesizer, unconnected to audio:" echo " yoshimi -j -J --state=$YOSHIMI_STATE" yoshimi -j -J --state=$YOSHIMI_STATE & fi else if [ "$DO_YOSH_CONNECT" == yes ] ; then echo "Starting yoshimi synthesizer, output to default audio:" echo " yoshimi -j -J -K --state=$YOSHIMI_STATE" yoshimi -j -J -K --state=$YOSHIMI_STATE & sleep 2 echo "jack_connect yoshimi:left system:playback_1" if [ "$DO_SKIP_CONNECTS" == "no" ] ; then jack_connect yoshimi:left system:playback_1 else echo "... skipped." fi sleep 1 echo "jack_connect yoshimi:right system:playback_2" if [ "$DO_SKIP_CONNECTS" == "no" ] ; then jack_connect yoshimi:right system:playback_2 else echo "... skipped." fi else echo "Starting yoshimi synthesizer, unconnected to audio:" echo " yoshimi -j -J --state=$YOSHIMI_STATE" yoshimi -j -J --state=$YOSHIMI_STATE & fi fi fi #------------------------------------------------------------------------------ # Other #------------------------------------------------------------------------------ # # Other connections for setup. The names of JACK ports can be found # using the jack_lsp command. Unfortunately, the exact names can # change, and thus the connection commands can therefore FAIL. # # jack_connect "a2j:qseq66 [131] (capture): [1] qseq66 1" "yoshimi:midi in" # jack_connect "a2j:nanoKEY2 [20] (capture): nanoKEY2 MIDI 1" # "a2j:qseq66 [131] (playback): qseq66 in" # #------------------------------------------------------------------------------ if [ "$DO_MIDI_CONNECT" == yes ] ; then sleep 2 NANOKEYOUT=$(jack_lsp | grep "nanoKEY2.*capture") SEQ66IN=$(jack_lsp | grep "$SEQ66.*playback") SEQ66OUT=$(jack_lsp | grep "$SEQ66.*capture.* 1$") echo "jack_connect '$SEQ66OUT' output to" echo " 'yoshimi:midi in'" if [ "$DO_SKIP_CONNECTS" == "no" ] ; then jack_connect "$SEQ66OUT" "yoshimi:midi in" else echo "... skipped." fi echo "jack_connect '$NANOKEYOUT' to" echo " '$SEQ66IN'" if [ "$DO_SKIP_CONNECTS" == "no" ] ; then jack_connect "$NANOKEYOUT" "$SEQ66IN" else echo "... skipped." fi fi fi if [ "$DO_TEARDO_WN" == "yes" ] ; then echo "Killing yoshimi..." killall yoshimi sleep 1 echo "Killing $SEQ66..." killall $SEQ66 sleep 1 echo "Killing a2jmidid..." killall a2jmidid sleep 1 echo "Killing zita-j2a..." killall zita-j2a sleep 1 echo "Killing qjackctl..." killall qjackctl sleep 1 echo "Killing jackd..." killall jackd echo "Restarting mpd..." sudo systemctl start mpd fi if [ "$DO_HELP" == "yes" ] ; then cat << E_O_F seq-session v 0.1 2014-09-28 Usage: seq-session [ options | --help] This script helps you set up for doing MIDI work using JACK, Seq66, and the Yoshimi software synthesizer. Options: --setup, The default, this option starts all the applications, with --start, -s pauses in between to allow settling. --teardown, This option stop all of the applications in reverse order. --stop, -t --auto Use the auto-connect ports feature of $SEQ66. --manual Use the manual (virtual ports) feature of $SEQ66. --j2a [hw:x] Start a writable client to get access to another audio output device. This is now the default, and the hardware device is detected --state file Open the file as the Yoshimi state file. The default value is '$YOSHIMI_STATE'. --no-advice Don't show the 'advice' after running the script. --no-connects Don't make connections. Useful for debugging. --help Show this help banner and the advice. E_O_F fi if [ "$DO_ADVICE" == "yes" ] ; then cat << E_O_F Check or make the following settings; jack_connect makes most of them: - Verify that Yoshimi, Seq66, QJackCtl, and JACK are running. - In QJackCtl's Audio tab, verify that Yoshimi is connected to "System" or, for USB audio output, "Zita-j2a". - In QJackCtl's MIDI tab, connect "$SEQ66 [131](capture): [1] $SEQ66 1" to "yoshimi: midi in", if not already connected by jack_connect. - In Seq66, open the file "~/Home/Audio/Seq24/click_4_4.midi". Right-click on the "Updown Clicks" pattern and edit it. Verify that the output bus is set to [1] $SEQ66 1, and that the MIDI channel is 2. Turn on the "sequence dumps to MIDI buss" button. - In Yoshimi, do Instrument / Show Instrument Bank, and select Drums. Then select "Drums Kit1b" or "CR78 Combo". Close that dialog; set Midi Chan to 2. (One can also save the state of Yoshimi in a file with the same base-name as the MIDI sequence you create, and open that state file to achieve the settings very quickly.) - In Seq66, verify that "Updown Clicks" is selected. Click the play button and verify that the pattern plays. - In the QJackCtls MIDI tab, connect keyboard to "a2j / $SEQ66 [131](playback): $SEQ66 in". In Seq66, edit the "Keyboard" pattern (empty) and make sure that the output bus is set to [1] $SEQ66 1, and that the MIDI channel is 1. In Yoshimi, set Part 2 to MIDI channel 1 and pick the desired patch from the desired instrument bank. Make sure the part is "Enabled". Now you should be able to play along with the beat. - Open a mixer application (e.g. XFce4-mixer), and make sure the audio output (default or USB audio) is turned up enough to hear. E_O_F fi #------------------------------------------------------------------------------ # vim: ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/strap_functions ================================================ #!/bin/bash # #****************************************************************************** # strap_functions (project) #------------------------------------------------------------------------------ ## # \file strap_functions # \library Any project # \author Chris Ahlstrom # \date 2018-11-09 # \update 2021-10-13 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # The above is modified by the following to remove even the mild GPL # restrictions: # # Use this script in any manner whatsoever. You don't even need to give # me any credit. However, keep in mind the value of the GPL in keeping # software and its descendant modifications available to the community # for all time. # # This module provides some bootstrap- and build-related functions the # top-level bootstrap script can use. It does a much more thorough job # of cleaning out junk/derived files. # #------------------------------------------------------------------------------ #****************************************************************************** # Provide a sane environment, just in case it is needed. #------------------------------------------------------------------------------ LANG=C export LANG CYGWIN=binmode export CYGWIN #****************************************************************************** # Known exit codes #------------------------------------------------------------------------------ EXIT_ERROR_GENERIC=1 EXIT_ERROR_NO_SUCH_OPTION=2 EXIT_ERROR_LOG=3 EXIT_ERROR_NO_SUCH_COMMAND=4 EXIT_ERROR_NO_SCRIPTS_DIR=5 EXIT_ERROR_AUTOCONF_VERSION=6 EXIT_ERROR_AUTOCONF_VERSION_2=7 EXIT_ERROR_AUTOCONF_VERSION_3=8 EXIT_ERROR_GETTEXT=9 EXIT_ERROR_WGET=10 EXIT_ERROR_BOOTSTRAP_NOT_RUN=11 EXIT_ERROR_HELP=12 EXIT_ERROR_NO_SUBIDR=13 EXIT_ERROR_TEST_ONLY=14 EXIT_ERROR_NO_BUILDDIR=15 EXIT_ERROR_NO_PROJECT=16 EXIT_ERROR_NO_LOGNAME=17 EXIT_ERROR_FAILED=18 EXIT_ERROR_NO_SUCH_FILE=19 EXIT_ERROR_TEST_FAILED=20 EXIT_ERROR_NO_BFUNCTIONS=255 #****************************************************************************** # die $EXITCODE $PROJECT $ERRORMESSAGE ... #------------------------------------------------------------------------------ # # Emits the $PROJECT name provided, the $ERRORMESSAGE, and any other # parameters provided. Then it exits with the value of $EXITCODE. # # For the bash shell, the exit codes range from 0 to 255. If a larger # value is provided, bash changes it modulo 256. In other words, the # exit code is a single unsigned byte. # #------------------------------------------------------------------------------ function die() { EXITCODE=$1 CURPROJECT=$2 MESSAGE="? [$CURPROJECT] $3" MESSAGE+=$'\n' shift 3 while [ "$1" != "" ] ; do MESSAGE+=" " MESSAGE+="$1" MESSAGE+=$'\n' shift done if [ "$DOLOG" == "yes" ] && [ "$LOGFILENAME" != "" ] ; then echo "$MESSAGE" >> $LOGFILENAME fi echo "$MESSAGE" echo "Run this script with the --help option for more information." echo "" exit $EXITCODE } #****************************************************************************** # warn $EXITCODE $PROJECT $ERRORMESSAGE ... #------------------------------------------------------------------------------ # # Just like die(), except it returns the exit code instead of exiting. # #------------------------------------------------------------------------------ function warn() { EXITCODE=$1 CURPROJECT=$2 MESSAGE="? [$CURPROJECT] $3" MESSAGE+=$'\n' shift 3 while [ "$1" != "" ] ; do MESSAGE+=" " MESSAGE+="$1" MESSAGE+=$'\n' shift done if [ "$DOLOG" == "yes" ] && [ "$LOGFILENAME" != "" ] ; then echo "$MESSAGE" >> $LOGFILENAME fi echo "$MESSAGE" echo "Run this script with the --help option for more information." echo "" return $EXITCODE } #****************************************************************************** # checkdir $SUBDIR #------------------------------------------------------------------------------ checkdir() { if [ ! -d "$1" ] ; then warn $EXIT_ERROR_NO_SUBDIR "checkdir()" \ "COULD NOT FIND THE '$1' DIRECTORY." "$\n" \ "Please run this script within the project directory hierarchy" return $EXIT_ERROR_NO_SUBDIR else return 0 fi } #****************************************************************************** # log $PROJECT $MESSAGE ... #------------------------------------------------------------------------------ function log() { CURPROJECT=$1 MESSAGE="[$CURPROJECT] $2" MESSAGE+=$'\n' shift 2 while [ "$1" != "" ] ; do MESSAGE+=" " MESSAGE+="$1" MESSAGE+=$'\n' shift done if [ $DOLOG == "yes" ] && [ $LOGFILENAME != "" ] ; then echo "$MESSAGE" >> $LOGFILENAME else DOLOG="no" fi echo "$MESSAGE" } #****************************************************************************** # checklog #------------------------------------------------------------------------------ function checklog() { if [ "$DOLOG" == "yes" ] ; then if [ "$LOGFILENAME" == "" ] ; then die $EXIT_ERROR_LOG "LOG" \ "? Missing name for the --log option." else echo "* Logging main commands to $LOGFILENAME..." if [ ! -f $LOGFILENAME ] ; then touch $LOGFILENAME if [ $? != 0 ] ; then die $EXIT_ERROR_LOG "LOG" \ "? Creating log-file $LOGFILENAME failed, aborting!" else log "project bootstrap script" "`date`" fi fi fi fi } #****************************************************************************** # run_cmd $NAME_OF_PROGRAM #------------------------------------------------------------------------------ run_cmd() { if [ "$DOBOOTSTRAP" == "yes" ] ; then echo "* Running project build command $*" if [ -x /usr/bin/$1 ] ; then if ! $* ; then die $EXIT_ERROR_NO_SUCH_COMMAND "run_cmd" \ "The bootstrap command failed!" fi else die $EXIT_ERROR_NO_SUCH_COMMAND "run_cmd" \ "Program $1 does not exist, exiting script!" fi fi } #****************************************************************************** # clean_tree #------------------------------------------------------------------------------ # # Cleans out the whole project tree of garbage. Currently unused. See the # usage of DOCLEAN and DOFULLCLEAN in the bootstrap script. # # MIGHT NOT BE CALLED! # #------------------------------------------------------------------------------ function clean_tree() { if [ $? == 0 ] ; then clean_tempfiles clean_gnufiles clean_debfiles clean_doxfiles clean_winfiles clean_testapps clean_m4 # The following remnant from an in-source build can screw up an # out-of-source (e.g. debug) build if it remains around. rm -f include/seq66-config.h fi } #****************************************************************************** # clean_m4 #------------------------------------------------------------------------------ # # Script helper function for cleaning out auto-copied m4 files # #------------------------------------------------------------------------------ function clean_m4() { rm -f m4/codeset.m4 rm -f m4/gettext.m4 rm -f m4/glibc2.m4 rm -f m4/glibc21.m4 rm -f m4/iconv.m4 rm -f m4/intdiv0.m4 rm -f m4/intl.m4 rm -f m4/intldir.m4 rm -f m4/intmax.m4 rm -f m4/inttypes-pri.m4 rm -f m4/inttypes_h.m4 rm -f m4/lcmessage.m4 rm -f m4/lib-ld.m4 rm -f m4/lib-link.m4 rm -f m4/lib-prefix.m4 rm -f m4/libtool.m4 rm -f m4/lock.m4 rm -f m4/longdouble.m4 rm -f m4/longlong.m4 rm -f m4/lt*.m4 rm -f m4/nls.m4 rm -f m4/po.m4 rm -f m4/printf-posix.m4 rm -f m4/progtest.m4 rm -f m4/size_max.m4 rm -f m4/stdint_h.m4 rm -f m4/uintmax_t.m4 rm -f m4/ulonglong.m4 rm -f m4/visibility.m4 rm -f m4/wchar_t.m4 rm -f m4/wint_t.m4 rm -f m4/xsize.m4 rm -f m4/Makefile m4/*.m4~ } #****************************************************************************** # clean_by_ext $EXTLIST #------------------------------------------------------------------------------ # # Cleans out the whole project tree of the obvious garbage, # determined by file extension. # #------------------------------------------------------------------------------ function clean_by_ext() { for EXT in $1 do find . -iname "*.$EXT" -exec rm -f '{}' \; 2> /dev/null done } #****************************************************************************** # clean_tempfiles #------------------------------------------------------------------------------ # # Cleans out the whole project tree of the obvious garbage, # determined by file name. This function covers temporary files not # covered by other cleaning functions. # #------------------------------------------------------------------------------ function clean_tempfiles() { local TEMPFILES="core vgcore dox-stamp bootstrap.stamp" echo "* Cleaning temp-files..." for FILE in $TEMPFILES do find . -iname $FILE -exec rm -f '{}' \; 2> /dev/null done # vim swap-files, backup files, my own temp files, log files # Added the hash-tag and am--include-marker files which weirdly appear # sometimes. find . -name "#" -exec rm -f '{}' \; 2> /dev/null find . -name "am--include-marker" -exec rm -f '{}' \; 2> /dev/null find . -name "out.*" -exec rm -f '{}' \; 2> /dev/null find . -name "*.swp" -exec rm -f '{}' \; 2> /dev/null find . -name ".*.swp" -exec rm -f '{}' \; 2> /dev/null find . -name "*.t" -exec rm -f '{}' \; 2> /dev/null find . -name "*.hexer" -exec rm -f '{}' \; 2> /dev/null find . -name "*~" -exec rm -f '{}' \; 2> /dev/null find . -name "make.log" -exec rm -f '{}' \; 2> /dev/null clean_by_ext "bak t tmp" find . -name "tmwrk*" -exec rm -rf '{}' \; 2> /dev/null find . -name "tmpcvs*" -exec rm -rf '{}' \; 2> /dev/null find . -name "tmp" -exec rm -rf '{}' \; 2> /dev/null rm -f doc/dox/config.log rm -f config.log # find . -name "safety" -exec rm -rf '{}' \; 2> /dev/null } #****************************************************************************** # clean_cfgfiles #------------------------------------------------------------------------------ # # Cleans out the project tree of GNU configure files. Used for a # full cleaning. The aux-files are now kept around for the configure # script, as well as configure and depcomp. # # rm -rf aux-files # #------------------------------------------------------------------------------ function clean_cfgfiles() { local CFGFILES="libtool Makefile.in ltmain.sh" echo "* Cleaning configure-related files..." for FILE in $CFGFILES do find . -name $FILE -exec rm -f '{}' \; 2> /dev/null done } #****************************************************************************** # clean_gnufiles #------------------------------------------------------------------------------ # # Cleans out the whole project tree of GNU droppings. # # libtoolize files (these may be links). But careful, though. We need # the following for "make dist": # # config.guess config.sub depcomp install-sh ltmain.sh missing # texinfo.tex # #------------------------------------------------------------------------------ function clean_gnufiles() { local GNUFILES="configure.scan _configs.sed \ aclocal.m4 acinclude.m4 .dirstamp Makefile stamp-h1 \ config.lt config.status" rm -f config.guess config.sub rm -f config.guess.* config.sub.* echo "* Cleaning GNU files..." for FILE in $GNUFILES do find . -name $FILE -exec rm -f '{}' \; 2> /dev/null done local GNUEXTS="a la o lo Po so" for EXT in $GNUEXTS do find . -iname "*.$EXT" -exec rm -f '{}' \; 2> /dev/null done local GNUDIRS="autom4te.cache .deps .libs" # i18n for DIR in $GNUDIRS do find . -name $DIR -exec rm -rf '{}' \; 2> /dev/null done } #****************************************************************************** # clean_debfiles #------------------------------------------------------------------------------ # # Cleans out the whole project tree of Debian-related files. # # We could run "dh_clean" instead. # #------------------------------------------------------------------------------ function clean_debfiles() { # local DEBFILES="debian/files debian/*.log debian/*-dev" # # echo "* Cleaning Debian files..." # echo "! clean_debfiles() needs work, just printing for now" # for FILE in $DEBFILES # do # find . -path "*.$FILE" # -exec rm -f '{}' \; 2> /dev/null # done # debian/rules clean # These come from using the control-split file on a trial basis. # rm -f debian/libseq66-dev.substvars # rm -rf debian/libseq66-dev/ # rm -f debian/libseq66.substvars # rm -rf debian/libseq66/ # rm -f debian/seq-gtkmm2-dev.substvars # rm -rf debian/seq-gtkmm2-dev/ # rm -f debian/seq-gtkmm2.substvars # rm -rf debian/seq-gtkmm2/ # rm -f debian/seq-rtmidi-dev.substvars # rm -rf debian/seq-rtmidi-dev/ # rm -f debian/seq-rtmidi.substvars # rm -rf debian/seq-rtmidi/ echo "clean-debfiles() not yet ready." } #****************************************************************************** # clean_doxfiles #------------------------------------------------------------------------------ # # Cleans out the whole project tree of Doxygen-related files. Be # careful, as this function removes directories named "html" and # "latex". If you have actual need for such directories, rename them # (e.g. "HTML" and "LaTeX"). # # This, of course, is a Chris convention. :-) # # PDFs are also removed; we will always derive them from Doxygen files. # #------------------------------------------------------------------------------ function clean_doxfiles() { # echo "* Cleaning Doxygen files and PDFs..." # find . -name "html" -exec rm -rf '{}' \; 2> /dev/null # find . -name "latex" -exec rm -rf '{}' \; 2> /dev/null # find . -name "pdf" -exec rm -rf '{}' \; 2> /dev/null # clean_by_ext "pdf" echo "clean-doxfiles() not yet ready." } #****************************************************************************** # clean_qt5files #------------------------------------------------------------------------------ function clean_qt5files() { echo "* Cleaning Qt 5 build artifacts..." find . -name "*.moc.cpp" -exec rm -f '{}' \; 2> /dev/null find . -name "*.ui.h" -exec rm -f '{}' \; 2> /dev/null find . -name "ui_*.h" -exec rm -f '{}' \; 2> /dev/null } #****************************************************************************** # clean_testapps #------------------------------------------------------------------------------ # # Cleans out the whole project tree of the known unit-test and # integration test applications. # #------------------------------------------------------------------------------ function clean_testapps() { TESTAPPS="" echo "* Cleaning the test applications..." for FILE in $TESTAPPS do find . -name "*.$FILE" -exec rm -f '{}' \; 2> /dev/null done local WINFILES="*.exe" for FILE in $WINFILES do find . -name "*.$FILE" -exec rm -f '{}' \; 2> /dev/null done } #****************************************************************************** # clean_project $SUBDIR #------------------------------------------------------------------------------ # # Script helper function for cleaning out auto-copied m4 files # #------------------------------------------------------------------------------ function clean_project() { if [ -d $1 ] ; then pushd $1 make clean clean_m4 rm -rf debian/lib$1 rm -f install-sh mkinstalldirs missing rm -rf config rm -f include/stamp-h.in rm -f include/stamp-h1 rm -f include/config.h include/config.h.in include/seq66-config.h popd else die $EXIT_ERROR_NO_SUBDIR "clean_project()" \ "? Subirectory $1 does not exist" fi } #****************************************************************************** # save_config_make $SUBDIR #------------------------------------------------------------------------------ # # Preserves the top-level configure.ac and Makefile.am files. Also # creates a default tarball for additional safety. # #------------------------------------------------------------------------------ function save_config_make() { cp configure.ac contrib/po/configure.ac.fresh cp Makefile.am contrib/po/Makefile.am.fresh cp configure.ac contrib/automake/configure.ac cp Makefile.am contrib/automake/Makefile.am pushd .. ./pack popd } #****************************************************************************** # strap_functions (project) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/timid ================================================ #!/bin/bash #------------------------------------------------------------------------------ ## # \file timid # \library bin # \author Chris Ahlstrom # \date 2021-04-19 to 2023-04-18 # \version $Revision$ # \license GNU GPLv2 or above # # This script runs timidity, so one can avoid having to deal with timidity as # a service. Starts it under ALSA as a daemon, not a service. # # -Od Output to audio device (default). # -Os Output to ALSA. # -Oj Output to JACK # -iA Launch as an ALSA sequencer client # --background Start as a daemon. # # See https://wiki.debian.org/AlsaMidi # #------------------------------------------------------------------------------ OPTIONS="" if [ "$1" == "kill" ] ; then killall timidity sleep 1 aplaymidi -l else if [ "$1" == "jack" ] ; then OPTIONS="-Oj" elif [ "$1" == "alsa" ] ; then OPTIONS="-Os -iA" fi timidity $OPTIONS --background echo "Waiting 3 seconds to settle..." sleep 3 aplaymidi -l fi #****************************************************************************** # qbuild (Seq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 wm=4 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: contrib/scripts/vd ================================================ #!/bin/bash # # 2026-04-01 to 2026-04-15 # # Runs vim -O in a larger terminal. Under tmux, sometimes one cannot # get gvim to run from in a terminal. # /usr/bin/urxvt -geometry 164x56 -e vim -O -d $1 $2 & # vim: ts=3 sw=3 et ft=sh ================================================ FILE: contrib/scripts/vo ================================================ #!/bin/bash # # 2026-04-01 to 2026-04-01 # # Runs vim -O in a larger terminal. Under tmux, sometimes one cannot # get gvim to run from in a terminal. # /usr/bin/urxvt -geometry 164x56 -e vim -O $1 $2 & # vim: ts=3 sw=3 et ft=sh ================================================ FILE: contrib/scripts/windows/VMS_fixes.reg ================================================ Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Drivers32] "midi"="wdmaud.drv" "midi1"=- "midi2"=- "midi3"=- "midi4"=- "midi5"=- "midi6"=- "midi7"=- "midi8"=- "midi9"=- [HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Drivers32] "midi"="wdmaud.drv" "midi1"=- "midi2"=- "midi3"=- "midi4"=- "midi5"=- "midi6"=- "midi7"=- "midi8"=- "midi9"=- [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Multimedia\MIDIMap] "szPname"="Microsoft GS Wavetable Synth" [HKEY_CURRENT_USER\Software\Microsoft\ActiveMovie\devenum\{4EFE2452-168A-11D1-BC76-00C04FB9453B}\Default MidiOut Device] "MidiOutId"="FFFFFFFF" [HKEY_CURRENT_USER\Software\Microsoft\ActiveMovie\devenum 64-bit\{4EFE2452-168A-11D1-BC76-00C04FB9453B}\Default MidiOut Device] "MidiOutId"="FFFFFFFF" ================================================ FILE: contrib/scripts/ystart ================================================ #!/bin/bash # # Date 2020-11-25 # Updated 2025-05-29 # # YOSHPATH="/usr/bin" # # Starts Yoshimi with our oft-used GM patch setup for Yoshimi. # Adjust the variables below for your setup. YOSHPATH="/usr/bin" YOSHIMI="yoshimi" OPTIONS="" REPOPATH="Home/ca/mls/git" CFGPATH="" DEFCFGPATH="$HOME/$REPOPATH/yoshimi-cookbook/sequencer64/b4uacuse" DOSTATE="no" USECODEC="no" STATEFILE="yoshimi-b4uacuse-gm.state" STATEARG="" # USELATEST="no" if [ "$1" == "--help" ] || [ "$1" == "help" ] ; then cat << E_O_F Usage v. 2025-05-29 ystart [ options ] Start the Yoshimi software synthesizer. Options: --alsa Use ALSA for both MIDI and audio. --jack Use JACK for both MIDI and audio. --ja Use JACK for MIDI and ALSA for audio. --codec Use ALSA for both, but use the CODEC device. --cfgpath dir Set the Yoshimi configuration directory. --b4 Set the configuration directory to: $DEFCFGPATH and use the state file $STATEFILE. Default command: $YOSHPATH/$YOSHIMI $OPTIONS E_O_F exit 0 else while [ "$1" != "" ] ; do case "$1" in --alsa) OPTIONS="--alsa-midi --alsa-audio" ;; --jack) OPTIONS="--jack-midi --jack-audio --autostart-jack" ;; --ja) OPTIONS="--jack-midi --alsa-audio --autostart-jack" ;; --codec) OPTIONS="--alsa-midi --alsa-audio=CODEC" ;; --cfgpath) shift CFGPATH="$1" ;; --b4) shift CFGPATH="$DEFCFGPATH" DOSTATE="yes" ;; *) echo "? Unsupported option; --help for more information" exit 1 ;; esac shift done fi # if [ "USELATEST" == "yes" ] ; then # YOSHPATH="/usr/local/bin" # YOSHIMI="yoshimi-1.7.2rc1" # shift # fi if [ "$DOSTATE" == "yes" ] ; then STATEARG="--state=$CFGPATH/$STATEFILE" echo "Running $YOSHPATH/$YOSHIMI $OPTIONS $STATEARG" exec $YOSHPATH/$YOSHIMI $OPTIONS --state=$STATEARG # & else exec $YOSHPATH/$YOSHIMI $OPTIONS # & fi ================================================ FILE: contrib/tests/4x4/README ================================================ The 4x4 Test Chris Ahlstrom 2021-12-13 to 2022-01-02 The 4x4 test is meant to test the following things: - The usages of a 4x4 grid, without and with swapping the pattern rows and columns. - Port-mapping, both for MIDI instrument I/O and for MIDI control and display. - The handling of various grid modes. By taking an existing file with a lot of empty patterns and numerous sets, along with trying to make some changes to that file, there were some bugs that were found and fixed, including errors in the handling of --help, extended automation keys for grid modes, setting set 0 when opening a MIDI file, expanding a pattern when merging a longer one into it; and pattern access in sets > 0. Steps: - Run the following command in order to create a new configuration directory to hold the 4x4 test configuration. Then exit Seq66 and verify that the configuration files are created in that directory: $ qseq66 --home ~/.config/seq66/4x4 The files created are qseq66.ctrl, qseq66.mutes, qseq66.playlist qseq66.usr, qseq66.drums, qseq66.palette, and, last, but not least, qseq66.rc. This command has to be run every time the tests are performed, because we currently do not have a mechanism to save the changed "home" configuration setting. Maybe we need a "registry" :-D. But see below. - Copy the configuration files in contrib/tests/4x4 to that directory. The file buff.midi will be used later, and does not need to be copied. - As an alternative, copy "data/samples/session.rc" to the default home configuration directory, and edit it to suit the existing file system. Then run Seq66 using the test option "--inspect 4x4". Port-mapping: On this system, for this test, we run ALSA, with yoshimi starting first and Qsynth starting second. We also use the Launchpad Mini as controller and display. # vim: sw=3 ts=3 wm=8 et ft=sh ================================================ FILE: contrib/tests/4x4/darkfix.qss ================================================ /* * \file textfix.qss * \library seq66 application * \author Chris Ahlstrom * \date 2021-10-20 * \updates 2022-01-02 * \license Public domain * * Provides a sample Qt style sheet for Seq66. Makes the text of disabled * elements easier to see in some light themes. */ QLabel { color : cornflowerblue; } QLabel:disabled { color: #F0A080; } QLineEdit { color : cornflowerblue; } QLineEdit:disabled { color: #F0A080; } QTextEdit { color : cornflowerblue; } QTextEdit:disabled { color: #F0A080; } QCheckBox { color : cornflowerblue; } QCheckBox:disabled { color: #F0A080; } QPushButton { color : cornflowerblue; } QPushButton:disabled { color: #F0A080; } QRadioButton { color : cornflowerblue; } QRadioButton:disabled { color: #F0A080; } QTabWidget { color : cornflowerblue; } QTabWidget:disabled { color: #F0A080; } QDoubleSpinBox { color : cornflowerblue; } QDoubleSpinBox:disabled { color: #F0A080; } /* * alternate-background-color : cornflowerblue; */ QScrollBar { background-color : #805040; } QScrollBar:handle { background-color : cornflowerblue; } QSpinBox { color : cornflowerblue; } QSpinBox:disabled { color: #F0A080; } QComboBox { color : cornflowerblue; } QComboBox:disabled { color: #F0A080; } /* * vim: sw=4 ts=4 wm=4 et ft=css */ ================================================ FILE: contrib/tests/4x4/qseq66-lp-mini-4x4.ctrl ================================================ # Seq66 0.90.5 (and above) MIDI control configuration file # # /home/user/.config/seq66/qseq66-lp-mini-8x8.ctrl # Written on 2020-08-07 09:58:39 # # This file holds the MIDI control configuration for Seq66. It follows # the format of the 'rc' configuration file, but is stored separately for # flexibility. It is always stored in the main configuration directory. # To use this file, replace the [midi-control] section in the 'rc' file, # and its contents with a [midi-control-file] tag, and simply add the # basename (e.g. nanomap.ctrl) on a separate line. [Seq66] config-type = "ctrl" version = 5 # [comments] holds the user's documentation for this control file. # Lines starting with '#' and '[' are ignored. Blank lines are ignored; # add an empty line by adding a space character to the line. [comments] This file is the 8 x 8 version of the Launchpad Mini control file for Seq66. It is NOT READY. This file was created by copying the default qseq66.ctrl file and changing the MIDI control section to the values found in the sequencer64-laumchpad.rc file. This section provides for controlling the following actions: o Patterns 0 to 31. Patterns can be toggled by sending Note Ons on on channel 1. Observe that the Toggle entries are marked active. This works with the Launchpad Mini. o Patterns 0 to 31 alternative. Patterns can be turned on by sending Note Ons on on channel 1, and turned off by sending Note Offs. Make the Toggle entries inactive, and the On and Off entries active to achieve this setup. o Muting 32 to 64 (mute groups 0 to 31). o Automation control 64 to 73. This covers only part of the actions that can be controlled via MIDI in Seq66; we still need to upgrade the rest once we get familiar with our new LaunchPad Mini. The default setup for the LaunchPad Mini is shown in the "Launchpad programmer's reference" file (launchpad-programmers-reference.pdf). Since, by default, the Mini uses Note Ons of velocity 0 instead of Note Offs, we set the data range from 1 to 127 instead of 0 to 127. For MIDI output to the Launchpad Mini, we make the following color mappings: Arm Mute Queue Deleted/Empty Green Red Yellow Off 60 15 62 12 A sample line. Just need to fill in the key (note value) needed for this row and set the pattern number at the left: 2 [1 0 0x90 key 60] [1 0 0x90 key 15] [1 0 0x90 key 62] [1 0 0x90 key 12] The Play, Pause, and Stop controls are all mapped to the top left button (#1) on the Mini, with states of off, green (play), red (stop), and yellow (pause). [midi-control-settings] # The control-buss value should range from 0 to the maximum buss available on # your system. If set, then only that buss will be allowed to provide MIDI # control. A value of 255 or 0xff means any buss can. The 'midi-enabled' flag # applies to the MIDI controls; keystrokes are always enabled. control-buss = 6 midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 keyboard-layout = qwerty # This style of control stanza incorporates key control as well. # The leftmost number on each line here is the pattern number (e.g. # 0 to 31); the group number, same range, for up to 32 groups; or it # it is an automation control number, again a similar range. # This internal MIDI control number is followed by three groups of # bracketed numbers, each providing three different type of control: # # Normal: [toggle] [on] [off] # Increment/Decr: [increment] [increment] [decrement] # Playback: [pause] [start] [stop] # Playlist/Song: [by-value] [next] [previous] # # In each group, there are six numbers: # # [on/off invert status d0 d1min d1max] # # 'on/off' enables/disables (1/0) the control for the pattern; # 'invert' (1/0) causes the opposite, but not all support this, and # all keystroke-releases set invert to true; 'status' is the MIDI # event to match (channel is NOT ignored), and if set to 0x00, the # control is disabled; 'd0' is the first data value, e.g. if status # is 0x90 (Note On), d0 represents the note number; d1min to d1max # is the range of data values detected, e.g. for a Note On, 1 to 127 # indicate that any non-zero velocity will invoke the control. # Hex values can be used; precede with '0x'. # # ------------------------- Loop, group, or automation-slot number # | ---------------------- Name of the key (see the key map) # | | # | | ---------------- Inverse # | | | -------------- MIDI status/event byte (e.g. Note On) # | | | | ------------ d0: Data 1 (e.g. Note number) # | | | | | ---------- d1max: Data 2 min (e.g. Note velocity) # | | | | | | -------- d1min: Data 2 max # | | | | | | | # v v v v v v v # 0 "F1" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Toggle On Off [loop-control] 0 "1" [ 0 0x90 0 1 127 ] [ 0 0x90 0 1 127 ] [ 0 0x80 0 1 127 ] 1 "q" [ 0 0x90 16 1 127 ] [ 0 0x90 16 1 127 ] [ 0 0x80 16 1 127 ] 2 "a" [ 0 0x90 32 1 127 ] [ 0 0x90 32 1 127 ] [ 0 0x80 32 1 127 ] 3 "z" [ 0 0x90 48 1 127 ] [ 0 0x90 48 1 127 ] [ 0 0x80 48 1 127 ] 8 "2" [ 0 0x90 1 1 127 ] [ 0 0x90 1 1 127 ] [ 0 0x80 1 1 127 ] 9 "w" [ 0 0x90 17 1 127 ] [ 0 0x90 17 1 127 ] [ 0 0x80 17 1 127 ] 10 "s" [ 0 0x90 33 1 127 ] [ 0 0x90 33 1 127 ] [ 0 0x80 33 1 127 ] 11 "x" [ 0 0x90 49 1 127 ] [ 0 0x90 49 1 127 ] [ 0 0x80 49 1 127 ] 16 "3" [ 0 0x90 2 1 127 ] [ 0 0x90 2 1 127 ] [ 0 0x80 2 1 127 ] 17 "e" [ 0 0x90 18 1 127 ] [ 0 0x90 18 1 127 ] [ 0 0x80 18 1 127 ] 18 "d" [ 0 0x90 34 1 127 ] [ 0 0x90 34 1 127 ] [ 0 0x80 34 1 127 ] 19 "c" [ 0 0x90 50 1 127 ] [ 0 0x90 50 1 127 ] [ 0 0x80 50 1 127 ] 24 "4" [ 0 0x90 3 1 127 ] [ 0 0x90 3 1 127 ] [ 0 0x80 3 1 127 ] 25 "r" [ 0 0x90 19 1 127 ] [ 0 0x90 19 1 127 ] [ 0 0x80 19 1 127 ] 26 "f" [ 0 0x90 35 1 127 ] [ 0 0x90 35 1 127 ] [ 0 0x80 35 1 127 ] 27 "v" [ 0 0x90 51 1 127 ] [ 0 0x90 51 1 127 ] [ 0 0x80 51 1 127 ] 32 "5" [ 0 0x90 4 1 127 ] [ 0 0x90 4 1 127 ] [ 0 0x80 4 1 127 ] 33 "t" [ 0 0x90 20 1 127 ] [ 0 0x90 20 1 127 ] [ 0 0x80 20 1 127 ] 34 "g" [ 0 0x90 36 1 127 ] [ 0 0x90 36 1 127 ] [ 0 0x80 36 1 127 ] 35 "b" [ 0 0x90 52 1 127 ] [ 0 0x90 52 1 127 ] [ 0 0x80 52 1 127 ] 40 "6" [ 0 0x90 5 1 127 ] [ 0 0x90 5 1 127 ] [ 0 0x80 5 1 127 ] 41 "y" [ 0 0x90 21 1 127 ] [ 0 0x90 21 1 127 ] [ 0 0x80 21 1 127 ] 42 "h" [ 0 0x90 37 1 127 ] [ 0 0x90 37 1 127 ] [ 0 0x80 37 1 127 ] 43 "n" [ 0 0x90 53 1 127 ] [ 0 0x90 53 1 127 ] [ 0 0x80 53 1 127 ] 48 "7" [ 0 0x90 6 1 127 ] [ 0 0x90 6 1 127 ] [ 0 0x80 6 1 127 ] 49 "u" [ 0 0x90 22 1 127 ] [ 0 0x90 22 1 127 ] [ 0 0x80 22 1 127 ] 50 "j" [ 0 0x90 38 1 127 ] [ 0 0x90 38 1 127 ] [ 0 0x80 38 1 127 ] 51 "m" [ 0 0x90 54 1 127 ] [ 0 0x90 54 1 127 ] [ 0 0x80 54 1 127 ] 56 "8" [ 0 0x90 7 1 127 ] [ 0 0x90 7 1 127 ] [ 0 0x80 7 1 127 ] 57 "i" [ 0 0x90 23 1 127 ] [ 0 0x90 23 1 127 ] [ 0 0x80 23 1 127 ] 58 "k" [ 0 0x90 39 1 127 ] [ 0 0x90 39 1 127 ] [ 0 0x80 39 1 127 ] 59 "," [ 0 0x90 55 1 127 ] [ 0 0x90 55 1 127 ] [ 0 0x80 55 1 127 ] # --------------- 4 "~" [ 0 0x90 64 1 127 ] [ 0 0x90 64 1 127 ] [ 0 0x80 64 1 127 ] 5 "~" [ 0 0x90 80 1 127 ] [ 0 0x90 80 1 127 ] [ 0 0x80 80 1 127 ] 6 "~" [ 0 0x90 96 1 127 ] [ 0 0x90 96 1 127 ] [ 0 0x80 96 1 127 ] 7 "~" [ 0 0x90 112 1 127 ] [ 0 0x90 112 1 127 ] [ 0 0x80 112 1 127 ] 12 "~" [ 0 0x90 65 1 127 ] [ 0 0x90 65 1 127 ] [ 0 0x80 65 1 127 ] 13 "~" [ 0 0x90 81 1 127 ] [ 0 0x90 81 1 127 ] [ 0 0x80 81 1 127 ] 14 "~" [ 0 0x90 97 1 127 ] [ 0 0x90 97 1 127 ] [ 0 0x80 97 1 127 ] 15 "~" [ 0 0x90 113 1 127 ] [ 0 0x90 113 1 127 ] [ 0 0x80 113 1 127 ] 20 "~" [ 0 0x90 66 1 127 ] [ 0 0x90 66 1 127 ] [ 0 0x80 66 1 127 ] 21 "~" [ 0 0x90 82 1 127 ] [ 0 0x90 82 1 127 ] [ 0 0x80 82 1 127 ] 22 "~" [ 0 0x90 98 1 127 ] [ 0 0x90 98 1 127 ] [ 0 0x80 98 1 127 ] 23 "~" [ 0 0x90 114 1 127 ] [ 0 0x90 114 1 127 ] [ 0 0x80 114 1 127 ] 28 "~" [ 0 0x90 67 1 127 ] [ 0 0x90 67 1 127 ] [ 0 0x80 67 1 127 ] 29 "~" [ 0 0x90 83 1 127 ] [ 0 0x90 83 1 127 ] [ 0 0x80 83 1 127 ] 30 "~" [ 0 0x90 99 1 127 ] [ 0 0x90 99 1 127 ] [ 0 0x80 99 1 127 ] 31 "~" [ 0 0x90 115 1 127 ] [ 0 0x90 115 1 127 ] [ 0 0x80 115 1 127 ] 36 "~" [ 0 0x90 68 1 127 ] [ 0 0x90 68 1 127 ] [ 0 0x80 68 1 127 ] 37 "~" [ 0 0x90 84 1 127 ] [ 0 0x90 84 1 127 ] [ 0 0x80 84 1 127 ] 38 "~" [ 0 0x90 100 1 127 ] [ 0 0x90 100 1 127 ] [ 0 0x80 100 1 127 ] 39 "~" [ 0 0x90 116 1 127 ] [ 0 0x90 116 1 127 ] [ 0 0x80 116 1 127 ] 44 "~" [ 0 0x90 69 1 127 ] [ 0 0x90 69 1 127 ] [ 0 0x80 69 1 127 ] 45 "~" [ 0 0x90 85 1 127 ] [ 0 0x90 85 1 127 ] [ 0 0x80 85 1 127 ] 46 "~" [ 0 0x90 101 1 127 ] [ 0 0x90 101 1 127 ] [ 0 0x80 101 1 127 ] 47 "~" [ 0 0x90 117 1 127 ] [ 0 0x90 117 1 127 ] [ 0 0x80 117 1 127 ] 52 "~" [ 0 0x90 70 1 127 ] [ 0 0x90 70 1 127 ] [ 0 0x80 70 1 127 ] 53 "~" [ 0 0x90 86 1 127 ] [ 0 0x90 86 1 127 ] [ 0 0x80 86 1 127 ] 54 "~" [ 0 0x90 102 1 127 ] [ 0 0x90 102 1 127 ] [ 0 0x80 102 1 127 ] 55 "~" [ 0 0x90 118 1 127 ] [ 0 0x90 118 1 127 ] [ 0 0x80 118 1 127 ] 60 "~" [ 0 0x90 71 1 127 ] [ 0 0x90 71 1 127 ] [ 0 0x80 71 1 127 ] 61 "~" [ 0 0x90 87 1 127 ] [ 0 0x90 87 1 127 ] [ 0 0x80 87 1 127 ] 62 "~" [ 0 0x90 103 1 127 ] [ 0 0x90 103 1 127 ] [ 0 0x80 103 1 127 ] 63 "~" [ 0 0x90 119 1 127 ] [ 0 0x90 119 1 127 ] [ 0 0x80 119 1 127 ] [mute-group-control 0 "!" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 0 1 "Q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 1 2 "A" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 2 3 "Z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 3 4 "@" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 4 5 "W" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 5 6 "S" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 6 7 "X" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 7 8 "#" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 8 9 "E" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 9 10 "D" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 10 11 "C" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 11 12 "$" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 12 13 "R" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 13 14 "F" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 14 15 "V" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 15 16 "%" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 16 17 "T" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 17 18 "G" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 18 19 "B" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 19 20 "^" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 20 21 "Y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 21 22 "H" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 22 23 "N" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 23 24 "&" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 24 25 "U" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 25 26 "J" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 26 27 "M" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 27 28 "*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 28 29 "I" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 29 30 "K" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 30 31 "<" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 31 [automation-control] 0 "'" [ 0 0x00 0 0 0 ] [ 0 0xb0 104 127 127 ] [ 0 0xb0 104 127 127 ] # BPM Up 1 ";" [ 0 0x00 0 0 0 ] [ 0 0xb0 105 127 127 ] [ 0 0xb0 105 127 127 ] # BPM Dn 2 "]" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Set Up 3 "[" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Set Dn 4 "KP_Home" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Replace 5 "Ins" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Snapshot 6 "o" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Queue 7 "`" [ 0 0x00 0 0 0 ] [ 0 0x90 8 1 127 ] [ 0 0x80 8 1 127 ] # Group Mute 8 "l" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Group Learn 9 "Home" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Playing Set 10 "." [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Playback 11 "P" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Record 12 "BkSpace" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Solo 13 "KP_/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Thru 14 "PageUp" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Up 15 "PageDn" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Dn 16 "KP_." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Set 17 "KP_*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record 18 "KP_-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quan Record 19 "KP_+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reset Seq 20 "|" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # One-shot 21 "F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # FF 22 "F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Rewind 23 "F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Top 24 "F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play List 25 "F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play Song 26 "F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Tap BPM 27 "Space" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Start 28 "Esc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Stop 29 "KP_Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Snapshot_2 30 "F8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Mute 31 "F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Pos 32 "\" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Keep Queue 33 "/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Slot Shift 34 "0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mutes Clear 35 "Null_f1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 35 36 "=" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop Edit 37 "-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Event Edit 38 "F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Mode 39 "F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle JACK 40 "F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Menu Mode 41 "F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Follow JACK 42 "~" [ 0 0x00 0 0 0 ] [ 0 0xb0 104 1 127 ] [ 0 0x00 0 0 0 ] # Panic 43 "0xf9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Visibility 44 "0xfa" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 44 45 "0xfb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 45 46 "0xfc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 47 "0xfd" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 48 "0xfe" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 48 [midi-control-out-settings] set-size = 32 output-buss = 5 midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 [midi-control-out] # --------------------- Pattern number (as applicable) # | ---------------- MIDI status+channel (e.g. Note On) # | | ------------ data 1 (e.g. note number) # | | | ---------- data 2 (e.g. velocity) # | | | | # v v v v # 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0] # Arm Mute Queue Delete # # This is a change (2021-02-10) from version 1 of this file. # A test of the status/event byte determines the enabled status, # and channel is incorporated into the status. Much cleaner! # The order of the lines that follow must must be preserved. 0 [ 0x90 0 60 ] [ 0x90 0 15 ] [ 0x90 0 62 ] [ 0x90 0 12 ] 1 [ 0x90 16 60 ] [ 0x90 16 15 ] [ 0x90 16 62 ] [ 0x90 16 12 ] 2 [ 0x90 32 60 ] [ 0x90 32 15 ] [ 0x90 32 62 ] [ 0x90 32 12 ] 3 [ 0x90 48 60 ] [ 0x90 48 15 ] [ 0x90 48 62 ] [ 0x90 48 12 ] 4 [ 0x90 64 60 ] [ 0x90 64 15 ] [ 0x90 64 62 ] [ 0x90 64 12 ] 5 [ 0x90 80 60 ] [ 0x90 80 15 ] [ 0x90 80 62 ] [ 0x90 80 12 ] 6 [ 0x90 96 60 ] [ 0x90 96 15 ] [ 0x90 96 62 ] [ 0x90 96 12 ] 7 [ 0x90 112 60 ] [ 0x90 112 15 ] [ 0x90 112 62 ] [ 0x90 112 12 ] 8 [ 0x90 1 60 ] [ 0x90 1 15 ] [ 0x90 1 62 ] [ 0x90 1 12 ] 9 [ 0x90 17 60 ] [ 0x90 17 15 ] [ 0x90 17 62 ] [ 0x90 17 12 ] 10 [ 0x90 33 60 ] [ 0x90 33 15 ] [ 0x90 33 62 ] [ 0x90 33 12 ] 11 [ 0x90 49 60 ] [ 0x90 49 15 ] [ 0x90 49 62 ] [ 0x90 49 12 ] 12 [ 0x90 65 60 ] [ 0x90 65 15 ] [ 0x90 65 62 ] [ 0x90 65 12 ] 13 [ 0x90 81 60 ] [ 0x90 81 15 ] [ 0x90 81 62 ] [ 0x90 81 12 ] 14 [ 0x90 97 60 ] [ 0x90 97 15 ] [ 0x90 97 62 ] [ 0x90 97 12 ] 15 [ 0x90 113 60 ] [ 0x90 113 15 ] [ 0x90 113 62 ] [ 0x90 113 12 ] 16 [ 0x90 2 60 ] [ 0x90 2 15 ] [ 0x90 2 62 ] [ 0x90 2 12 ] 17 [ 0x90 18 60 ] [ 0x90 18 15 ] [ 0x90 18 62 ] [ 0x90 18 12 ] 18 [ 0x90 34 60 ] [ 0x90 34 15 ] [ 0x90 34 62 ] [ 0x90 34 12 ] 19 [ 0x90 50 60 ] [ 0x90 50 15 ] [ 0x90 50 62 ] [ 0x90 50 12 ] 20 [ 0x90 66 60 ] [ 0x90 66 15 ] [ 0x90 66 62 ] [ 0x90 66 12 ] 21 [ 0x90 82 60 ] [ 0x90 82 15 ] [ 0x90 82 62 ] [ 0x90 82 12 ] 22 [ 0x90 98 60 ] [ 0x90 98 15 ] [ 0x90 98 62 ] [ 0x90 98 12 ] 23 [ 0x90 114 60 ] [ 0x90 114 15 ] [ 0x90 114 62 ] [ 0x90 114 12 ] 24 [ 0x90 3 60 ] [ 0x90 3 15 ] [ 0x90 3 62 ] [ 0x90 3 12 ] 25 [ 0x90 19 60 ] [ 0x90 19 15 ] [ 0x90 19 62 ] [ 0x90 19 12 ] 26 [ 0x90 35 60 ] [ 0x90 35 15 ] [ 0x90 35 62 ] [ 0x90 35 12 ] 27 [ 0x90 51 60 ] [ 0x90 51 15 ] [ 0x90 51 62 ] [ 0x90 51 12 ] 28 [ 0x90 67 60 ] [ 0x90 67 15 ] [ 0x90 67 62 ] [ 0x90 67 12 ] 29 [ 0x90 83 60 ] [ 0x90 83 15 ] [ 0x90 83 62 ] [ 0x90 83 12 ] 30 [ 0x90 99 60 ] [ 0x90 99 15 ] [ 0x90 99 62 ] [ 0x90 99 12 ] 31 [ 0x90 115 60 ] [ 0x90 115 15 ] [ 0x90 115 62 ] [ 0x90 115 12 ] 32 [ 0x90 4 60 ] [ 0x90 4 15 ] [ 0x90 4 62 ] [ 0x90 4 12 ] 33 [ 0x90 20 60 ] [ 0x90 20 15 ] [ 0x90 20 62 ] [ 0x90 20 12 ] 34 [ 0x90 36 60 ] [ 0x90 36 15 ] [ 0x90 36 62 ] [ 0x90 36 12 ] 35 [ 0x90 52 60 ] [ 0x90 52 15 ] [ 0x90 52 62 ] [ 0x90 52 12 ] 36 [ 0x90 68 60 ] [ 0x90 68 15 ] [ 0x90 68 62 ] [ 0x90 68 12 ] 37 [ 0x90 84 60 ] [ 0x90 84 15 ] [ 0x90 84 62 ] [ 0x90 84 12 ] 38 [ 0x90 100 60 ] [ 0x90 100 15 ] [ 0x90 100 62 ] [ 0x90 100 12 ] 39 [ 0x90 116 60 ] [ 0x90 116 15 ] [ 0x90 116 62 ] [ 0x90 116 12 ] 40 [ 0x90 5 60 ] [ 0x90 5 15 ] [ 0x90 5 62 ] [ 0x90 5 12 ] 41 [ 0x90 21 60 ] [ 0x90 21 15 ] [ 0x90 21 62 ] [ 0x90 21 12 ] 42 [ 0x90 37 60 ] [ 0x90 37 15 ] [ 0x90 37 62 ] [ 0x90 37 12 ] 43 [ 0x90 53 60 ] [ 0x90 53 15 ] [ 0x90 53 62 ] [ 0x90 53 12 ] 44 [ 0x90 69 60 ] [ 0x90 69 15 ] [ 0x90 69 62 ] [ 0x90 69 12 ] 45 [ 0x90 85 60 ] [ 0x90 85 15 ] [ 0x90 85 62 ] [ 0x90 85 12 ] 46 [ 0x90 101 60 ] [ 0x90 101 15 ] [ 0x90 101 62 ] [ 0x90 101 12 ] 47 [ 0x90 117 60 ] [ 0x90 117 15 ] [ 0x90 117 62 ] [ 0x90 117 12 ] 48 [ 0x90 6 60 ] [ 0x90 6 15 ] [ 0x90 6 62 ] [ 0x90 6 12 ] 49 [ 0x90 22 60 ] [ 0x90 22 15 ] [ 0x90 22 62 ] [ 0x90 22 12 ] 50 [ 0x90 38 60 ] [ 0x90 38 15 ] [ 0x90 38 62 ] [ 0x90 38 12 ] 51 [ 0x90 54 60 ] [ 0x90 54 15 ] [ 0x90 54 62 ] [ 0x90 54 12 ] 52 [ 0x90 70 60 ] [ 0x90 70 15 ] [ 0x90 70 62 ] [ 0x90 70 12 ] 53 [ 0x90 86 60 ] [ 0x90 86 15 ] [ 0x90 86 62 ] [ 0x90 86 12 ] 54 [ 0x90 102 60 ] [ 0x90 102 15 ] [ 0x90 102 62 ] [ 0x90 102 12 ] 55 [ 0x90 118 60 ] [ 0x90 118 15 ] [ 0x90 118 62 ] [ 0x90 118 12 ] 56 [ 0x90 7 60 ] [ 0x90 7 15 ] [ 0x90 7 62 ] [ 0x90 7 12 ] 57 [ 0x90 23 60 ] [ 0x90 23 15 ] [ 0x90 23 62 ] [ 0x90 23 12 ] 58 [ 0x90 39 60 ] [ 0x90 39 15 ] [ 0x90 39 62 ] [ 0x90 39 12 ] 59 [ 0x90 55 60 ] [ 0x90 55 15 ] [ 0x90 55 62 ] [ 0x90 55 12 ] 60 [ 0x90 71 60 ] [ 0x90 71 15 ] [ 0x90 71 62 ] [ 0x90 71 12 ] 61 [ 0x90 87 60 ] [ 0x90 87 15 ] [ 0x90 87 62 ] [ 0x90 87 12 ] 62 [ 0x90 103 60 ] [ 0x90 103 15 ] [ 0x90 103 62 ] [ 0x90 103 12 ] 63 [ 0x90 119 60 ] [ 0x90 119 15 ] [ 0x90 119 62 ] [ 0x90 119 12 ] [mute-control-out] # The format of the mute and automation output events is simpler: # # ---------------------- mute-group number # | ------------------ MIDI status+channel (e.g. Note On) # | | -------------- data 1 (e.g. note number) # | | | ------------ data 2 (e.g. velocity) # | | | | # v v v v # 1 [0x00 0 0 ] [0x00 0 0] [0x00 0 0] # On Off Empty (dark) # # The mute-controls have an additional stanza for non-populated # ("deleted") mute-groups. 0 [ 0x90 64 60 ] [ 0x90 64 15 ] [ 0x90 64 12 ] 1 [ 0x90 80 60 ] [ 0x90 80 15 ] [ 0x90 80 12 ] 2 [ 0x90 96 60 ] [ 0x90 96 15 ] [ 0x90 96 12 ] 3 [ 0x90 112 60 ] [ 0x90 112 15 ] [ 0x90 112 12 ] 4 [ 0x90 65 60 ] [ 0x90 65 15 ] [ 0x90 65 12 ] 5 [ 0x90 81 60 ] [ 0x90 81 15 ] [ 0x90 81 12 ] 6 [ 0x90 97 60 ] [ 0x90 97 15 ] [ 0x90 97 12 ] 7 [ 0x90 113 60 ] [ 0x90 113 15 ] [ 0x90 113 12 ] 8 [ 0x90 66 60 ] [ 0x90 66 15 ] [ 0x90 66 12 ] 9 [ 0x90 82 60 ] [ 0x90 82 15 ] [ 0x90 82 12 ] 10 [ 0x90 98 60 ] [ 0x90 98 15 ] [ 0x90 98 12 ] 11 [ 0x90 114 60 ] [ 0x90 114 15 ] [ 0x90 114 12 ] 12 [ 0x90 67 60 ] [ 0x90 67 15 ] [ 0x90 67 12 ] 13 [ 0x90 83 60 ] [ 0x90 83 15 ] [ 0x90 83 12 ] 14 [ 0x90 99 60 ] [ 0x90 99 15 ] [ 0x90 99 12 ] 15 [ 0x90 115 60 ] [ 0x90 115 15 ] [ 0x90 115 12 ] 16 [ 0x90 68 60 ] [ 0x90 68 15 ] [ 0x90 68 12 ] 17 [ 0x90 84 60 ] [ 0x90 84 15 ] [ 0x90 84 12 ] 18 [ 0x90 100 60 ] [ 0x90 100 15 ] [ 0x90 100 12 ] 19 [ 0x90 116 60 ] [ 0x90 116 15 ] [ 0x90 116 12 ] 20 [ 0x90 69 60 ] [ 0x90 69 15 ] [ 0x90 69 12 ] 21 [ 0x90 85 60 ] [ 0x90 85 15 ] [ 0x90 85 12 ] 22 [ 0x90 101 60 ] [ 0x90 101 15 ] [ 0x90 101 12 ] 23 [ 0x90 117 60 ] [ 0x90 117 15 ] [ 0x90 117 12 ] 24 [ 0x90 70 60 ] [ 0x90 70 15 ] [ 0x90 70 12 ] 25 [ 0x90 86 60 ] [ 0x90 86 15 ] [ 0x90 86 12 ] 26 [ 0x90 102 60 ] [ 0x90 102 15 ] [ 0x90 102 12 ] 27 [ 0x90 118 60 ] [ 0x90 118 15 ] [ 0x90 118 12 ] 28 [ 0x90 71 60 ] [ 0x90 71 15 ] [ 0x90 71 12 ] 29 [ 0x90 87 60 ] [ 0x90 87 15 ] [ 0x90 87 12 ] 30 [ 0x90 103 60 ] [ 0x90 103 15 ] [ 0x90 103 12 ] 31 [ 0x90 119 60 ] [ 0x90 119 15 ] [ 0x90 119 12 ] [automation-control-out] # This format is similar to the [mute-control-out] format, but # the first number is an active-flag, not an index number. # The stanzas are on/off/inactive, except for 'snap', which is # store/restore/inactive. 1 [ 0xb0 104 60 ] [ 0xb0 104 15 ] [ 0xb0 104 12 ] # Panic 1 [ 0xb0 105 60 ] [ 0xb0 105 15 ] [ 0xb0 105 12 ] # Stop 1 [ 0xb0 106 60 ] [ 0xb0 106 15 ] [ 0xb0 106 12 ] # Pause 1 [ 0xb0 107 60 ] [ 0xb0 107 15 ] [ 0xb0 107 12 ] # Play 1 [ 0xb0 108 60 ] [ 0xb0 108 15 ] [ 0xb0 108 12 ] # Toggle_mutes 1 [ 0xb0 109 60 ] [ 0xb0 109 15 ] [ 0xb0 109 12 ] # Song_record 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Slot_shift 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Free 1 [ 0x90 8 60 ] [ 0x90 8 15 ] [ 0x90 8 12 ] # Queue 1 [ 0x90 24 60 ] [ 0x90 24 15 ] [ 0x90 24 12 ] # Oneshot 1 [ 0x90 40 60 ] [ 0x90 40 15 ] [ 0x90 40 12 ] # Replace 1 [ 0x90 56 60 ] [ 0x90 56 15 ] [ 0x90 56 12 ] # Snap 1 [ 0x90 72 60 ] [ 0x90 72 15 ] [ 0x90 72 12 ] # Song 1 [ 0x90 88 60 ] [ 0x90 88 15 ] [ 0x90 88 12 ] # Learn 1 [ 0x90 104 60 ] [ 0x90 104 15 ] [ 0x90 104 12 ] # BPM_Up 1 [ 0x90 120 60 ] [ 0x90 120 15 ] [ 0x90 120 12 ] # BPM_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Dn 1 [ 0xb0 110 60 ] [ 0xb0 110 15 ] [ 0xb0 110 12 ] # Set_Up 1 [ 0xb0 111 60 ] [ 0xb0 111 15 ] [ 0xb0 111 12 ] # Set_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Tap_BPM 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Quit 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Visibility 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_2 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_3 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_4 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_5 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_6 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_7 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_8 # End of /home/user/.config/seq66/qseq66-lp-mini-8x8.ctrl # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: contrib/tests/4x4/qseq66.ctrl ================================================ # Seq66 0.98.3 MIDI control configuration file # # /home/ahlstrom/Home/ca/mls/git/seq66/contrib/tests/4x4/qseq66.ctrl # Written 2022-01-02 11:51:02 # # Sets up MIDI I/O control. The format is like the 'rc' file. To use it, set it # active in the 'rc' [midi-control-file] section. It adds loop, mute, & # automation buttons, MIDI display, new settings, and macros. [Seq66] config-type = "ctrl" version = 6 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] This control ('ctrl') file is meant for the 4x4 test. See the file "tests/4x4/qseq66.rc" for the full context. [midi-control-settings] # Input settings to control Seq66. 'control-buss' ranges from 0 to the highest # system input buss. If set, that buss can send MIDI control. 255 (0xFF) means # any enabled input device can send control. ALSA provides an extra 'announce' # buss, altering port numbering vice JACK. If port-mapping is enabled, the port # nick-name can be provided. # # 'midi-enabled' applies to the MIDI controls; keystroke controls are always # enabled. Supported keyboard layouts are 'qwerty' (default), 'qwertz', and # 'azerty'. AZERTY turns off auto-shift for group-learn. drop-empty-controls = false control-buss = "0xFF" midi-enabled = false button-offset = 0 button-rows = 4 button-columns = 8 keyboard-layout = qwerty # A control stanza incorporates key control and MIDI. Keys support 'toggle', and # key-release is 'invert'. The leftmost number on each line is the loop number # (0 to 31), the mutes number (same range), or an automation number. 3 groups of # of bracketed numbers follow, each providing a type of control: # # Normal: [toggle] [on] [off] # Increment/Decr: [increment] [increment] [decrement] # Playback: [pause] [start] [stop] # Playlist/Song: [by-value] [next] [previous] # # In each group, there are 5 numbers: # # [invert status d0 d1min d1max] # # A valid status (> 0x00) enables the control; 'invert' (1/0) inverts the, # the action, but not all support this. 'status' is the MIDI event to match # (channel is NOT ignored); 'd0' is the first data value (eg. if 0x90, Note On, # d0 is the note number; d1min to d1max is the range of d1 values detectable. # Hex values can be used; precede with '0x'. # # ------------------------ Loop/group/automation-slot number # | -------------------- Name of key (see the key map) # | | -------------- Inverse # | | | ---------- MIDI status/event byte (eg. Note On) # | | | | ------- d0: Data 1 (eg. Note number) # | | | | | ----- d1max: Data 2 min (eg. Note velocity) # | | | | | | -- d1min: Data 2 max # | | | | | | | # v v v v v v v # 0 "F1" [0 0x90 0 1 127] [0 0x00 0 0 0] [0 0x00 0 0 0] # Toggle On Off # # MIDI controls often send a Note On upon a press and a Note Off on release. # To use a control as a toggle, define only the Toggle stanza. For the control # to act only while held, define the On and Off stanzas with appropriate # statuses for press-and-release. [loop-control] 0 "1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 0 1 "q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 1 2 "a" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 2 3 "z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 3 4 "2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 4 5 "w" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 5 6 "s" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 6 7 "x" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 7 8 "3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 8 9 "e" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 9 10 "d" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 10 11 "c" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 11 12 "4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 12 13 "r" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 13 14 "f" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 14 15 "v" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 15 16 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 16 17 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 17 18 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 18 19 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 19 20 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 20 21 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 21 22 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 22 23 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 23 24 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 24 25 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 25 26 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 26 27 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 27 28 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 28 29 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 29 30 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 30 31 "Blank" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 31 [mute-group-control] 0 "!" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 0 1 "Q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 1 2 "A" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 2 3 "Z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 3 4 "@" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 4 5 "W" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 5 6 "S" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 6 7 "X" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 7 8 "#" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 8 9 "E" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 9 10 "D" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 10 11 "C" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 11 12 "$" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 12 13 "R" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 13 14 "F" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 14 15 "V" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 15 16 "%" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 16 17 "T" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 17 18 "G" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 18 19 "B" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 19 20 "^" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 20 21 "Y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 21 22 "H" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 22 23 "N" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 23 24 "&" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 24 25 "U" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 25 26 "J" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 26 27 "M" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 27 28 "*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 28 29 "I" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 29 30 "K" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 30 31 "<" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 31 [automation-control] 0 "'" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Up 1 ";" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Dn 2 "]" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Up 3 "[" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Dn 4 "KP_Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Replace 5 "Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Snapshot 6 "o" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Queue 7 "`" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Mute 8 "l" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Learn 9 "Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playing Set 10 "." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playback 11 "P" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Record 12 "BkSpace" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Solo 13 "LF" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Thru 14 "PageUp" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Up 15 "PageDn" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Dn 16 "KP_." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Set 17 "KP_*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop Mode 18 "KP_-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quan Record 19 "KP_+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reset Sets 20 "|" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # One-shot 21 "F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # FF 22 "F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Rewind 23 "F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Top 24 "F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play List 25 "F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play Song 26 "F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Tap BPM 27 "Space" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Start 28 "Esc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Stop 29 "KP_Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 29 30 "F8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Mute 31 "F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Pos 32 "\" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Keep Queue 33 "/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Slot Shift 34 "0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mutes Clear 35 "Quit" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quit 36 "=" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop Edit 37 "-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Event Edit 38 "F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Mode 39 "F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle JACK 40 "F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Menu Mode 41 "F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Follow JACK 42 "~" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Panic 43 "0xf9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Visibility 44 "0xfa" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Save Session 45 "0xfb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 45 46 "0xfc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 46 47 "0xfd" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 48 "0xfe" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 48 49 "Sh_F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Overdub 50 "Sh_F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Overwrite 51 "0xe0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Expand 52 "0xe1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Oneshot 53 "Sh_F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 54 "Sh_F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record 55 "Sh_F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Copy 56 "Sh_F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Paste 57 "Sh_F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Clear 58 "Sh_F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Delete 59 "Sh_F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Thru 60 "Sh_F8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Solo 61 "Sh_F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Cut 62 "Sh_F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Double 63 "5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q None 64 "t" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q Full 65 "g" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q Tighten 66 "b" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q Random 67 "6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q Jitter 68 "0xe7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 68 69 "y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BBT/HMS 70 "h" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # LR Loop 71 "0xea" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Undo Record 72 "0xeb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Redo Record 73 "n" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Transpose Song 74 "7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Copy Set 75 "u" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Paste Set 76 "j" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Tracks 77 "8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Normal 78 "i" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Auto 79 "k" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Additive 80 "," [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # All Sets [midi-control-out-settings] set-size = 32 output-buss = "0xFF" midi-enabled = false button-offset = 0 button-rows = 4 button-columns = 8 [midi-control-out] # ---------------- Pattern or device-button number) # | ----------- MIDI status+channel (eg. Note On) # | | ------- data 1 (eg. note number) # | | | ----- data 2 (eg. velocity) # | | | | # v v v v # 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0] # Arm Mute Queue Delete # # A test of the status byte determines the enabled status, and channel is # included in the status. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [mute-control-out] # The format of the mute and automation output events is similar: # # ----------------- mute-group number # | ------------- MIDI status+channel (eg. Note On) # | | --------- data 1 (eg. note number) # | | | ------- data 2 (eg. velocity) # | | | | # v v v v # 1 [0x00 0 0 ] [0x00 0 0] [0x00 0 0] # On Off Empty (dark) # # The mute-controls have an additional stanza for non-populated ("deleted") # mute-groups. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [automation-control-out] # This format is similar to [mute-control-out], but the first number is an # active-flag, not an index number. The stanzas are are on / off / inactive, # except for 'snap', which is store / restore / inactive. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Panic 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Stop 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Pause 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Toggle_mutes 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_record 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Slot_shift 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Free 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Queue 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # One-shot 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Replace 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Snap 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Learn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Tap_BPM 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Quit 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Visibility 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_2 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_3 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_4 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_5 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_6 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_7 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_8 [macro-control-out] # This format is 'macroname = [ hex bytes | macro-references]'. Macro references # are macro-names preceded by a '$'. Some values should always be defined, even # if empty: footer, header, reset, startup, and shutdown. footer = header = reset = shutdown = startup = # End of /home/ahlstrom/Home/ca/mls/git/seq66/contrib/tests/4x4/qseq66.ctrl # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: contrib/tests/4x4/qseq66.drums ================================================ # Seq66 0.98.1 note-mapper ('drums') configuration file # # /home/user/.config/seq66/4x4/qseq66.drums # Written 2021-12-12 11:19:29 # # This file resembles the files generated by 'midicvtpp', modified # for Seq66: # # midicvtpp --csv-drum GM_DD-11_Drums.csv --output ddrums.ini # # This file can convert the percussion of non-GM devices to GM, as # closely as possible. Although it is for drums, it can be used # for other note-mappings. [Seq66] config-type = "drums" version = 0 # [comments] holds user documentation for this file. The first empty, # hash-commented, or tag line ends the comment. [comments] Currently no testing is in place for the note-mapper. Too esoteric :-D # Drum/note-mapping configuration for Seq66, stored in the HOME # configuration directory. To use this file, add this file-name to # '[note-mapper]' section of the 'rc' file. There's no user-interface # for this file. The main values are: # # map-type: drum, patch, or multi; indicates the mapping to do. # gm-channel: Indicates the channel (1-16) applied to convert notes. # reverse: true or false; map in the opposite direction if true. [notemap-flags] map-type = "" gm-channel = 1 reverse = false # The drum section: # # [Drum 35]. Marks a GM drum-change section, one per instrument. # # gm-name GM name for the drum assigned to the input note. # gm-note Input note number, same as the section number. # dev-name The device's name for the drum. # dev-note GM MIDI note whose GM sound best matches the sound of # dev-name. The gm-note value is converted to the dev-note # value, unless reverse mapping is activated. The actual GM # drum sound might not match what the MIDI hardware puts out. # This is a sample. See 'data/samples/GM_DD-11.drums' for a full example. [Drum 36] dev-name = "Bass Drum Gated Reverb" gm-name = "Bass Drum 1" dev-note = 36 gm-note = 35 # End of /home/user/.config/seq66/4x4/qseq66.drums # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: contrib/tests/4x4/qseq66.mutes ================================================ # Seq66 0.98.3 mute-groups configuration file # # /home/ahlstrom/Home/ca/mls/git/seq66/contrib/tests/4x4/qseq66.mutes # Written 2022-01-02 11:51:02 # # Used in the [mute-group-file] section of the 'rc' file, making it easier to # multiple mute groups. To use this file, specify it in [mute-group-file] file # and set 'active = true'. [Seq66] config-type = "mutes" version = 0 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] Currently no testing is in place for the mutes. Soon! # load-mute-groups: set to 'none', or 'mutes' to load from the 'mutes' file, # 'midi' to load from the song, or 'both' to try to to read from 'mutes' first, # then the 'midi' file. # # save-mutes-to: 'both' writes the mutes to the 'mutes' and MIDI file; 'midi' # writes only to the MIDI file; and the mutes only to the 'mutes' file. # # strip-empty: If true, all-zero mute-groups are not written to the MIDI file. # # mute-group-rows and mute-group-columns: Specifies the size of the grid. Keep # these values at 4 and 8; mute-group-count is only for sanity-checking. # # groups-format: 'binary' means write mutes as 0 or 1; 'hex' means write them as # hexadecimal numbers (e.g. 0xff), useful for larger set sizes. # # mute-group-selected: if 0 to 31, and mutes are available either from this file # or from the MIDI file, then this mute-group is applied at startup; useful in # restoring a session. Set to -1 to disable. # # toggle-active-only: when a mute-group is toggled off, all patterns, even those # outside the mute-group, are muted. If this flag is set, only patterns in the # mute-group are muted. Patterns unmuted directly by the user remain unmuted. [mute-group-flags] load-mute-groups = both save-mutes-to = both strip-empty = true mute-group-rows = 4 mute-group-columns = 8 mute-group-count = 32 mute-group-selected = -1 groups-format = binary toggle-active-only = false [mute-groups] # Mute-group values are saved in the 'mutes' file, even if all zeroes. They can # be stripped out of the MIDI file by 'strip-empty-mutes'. A hex value indicates # a bit-mask, not a single bit. An quoted group name can be placed at the end # of the line. 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 ] "Group 0" 1 [ 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 ] "Group 1" 2 [ 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 ] "Group 2" 3 [ 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 ] "Group 3" 4 [ 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 ] "Group 4" 5 [ 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 ] "Group 5" 6 [ 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 ] "Group 6" 7 [ 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 ] "Group 7" 8 [ 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 ] "Group 8" 9 [ 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 ] "Group 9" 10 [ 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 ] "Group 10" 11 [ 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 ] "Group 11" 12 [ 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 ] "Group 12" 13 [ 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 ] "Group 13" 14 [ 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 ] "Group 14" 15 [ 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 ] "Group 15" 16 [ 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 ] "Group 16" 17 [ 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 ] "Group 17" 18 [ 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 ] "Group 18" 19 [ 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 ] "Group 19" 20 [ 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 ] "Group 20" 21 [ 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 ] "Group 21" 22 [ 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 ] "Group 22" 23 [ 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 ] "Group 23" 24 [ 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 ] "Group 24" 25 [ 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 ] "Group 25" 26 [ 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 ] "Group 26" 27 [ 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 ] "Group 27" 28 [ 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 ] "Group 28" 29 [ 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 ] "Group 29" 30 [ 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 ] "Group 30" 31 [ 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 ] "Group 31" # End of /home/ahlstrom/Home/ca/mls/git/seq66/contrib/tests/4x4/qseq66.mutes # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: contrib/tests/4x4/qseq66.playlist ================================================ # Seq66 0.98.1 playlist configuration file # # /home/user/.config/seq66/4x4/qseq66.playlist # Written 2021-12-12 11:19:29 # # This file holds multiple playlists, with one or more [playlist] # sections. Each has a user-specified number for sorting and MIDI # control, ranging from 0 to 127. Next comes a quoted name for this # list, followed by the quoted name of the song folder using the UNIX # separator ('/'). It should be accessible wherever Seq66 is run. # # Next is a list of tunes, each starting with a MIDI control number # and the quoted name of the MIDI file, sorted by the control number. # They can be simple 'base.midi' file-names; the playlist directory # is prepended to access the song. If the file-name has a path, that # will be used. [Seq66] config-type = "playlist" version = 1 # [comments] holds user documentation for this file. The first empty, # hash-commented, or tag line ends the comment. [comments] Currently no testing is in place for the playlist. [playlist-options] unmute-new-song = false deep-verify = false # First provide the playlist settings, its default storage folder, # and then list each tune with its control number. The playlist # number is arbitrary but unique. 0 to 127 recommended for use with # the MIDI playlist control. Similar for the tune numbers. Each # tune can include a path; it overrides the base directory. [playlist] # This is a NON-FUNCTIONAL playlist SAMPLE. Please see one of the # sample playlist files shipped with Seq66. # End of /home/user/.config/seq66/4x4/qseq66.playlist # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: contrib/tests/4x4/qseq66.rc ================================================ # Seq66 0.99.11 main ('rc') configuration file # # /home/ahlstrom/Home/ca/mls/git/seq66/contrib/tests/4x4/qseq66.rc # Written 2023-11-05 09:21:52 # # This file holds the main configuration for Seq66. It no longer follows the # format of the seq24rc configuration file. # # 'version' is set by Seq66; it is used to detect older configuration files, # which are upgraded to the new version when saved. # # 'quiet' suppresses start-up error messages. Useful when they are not # relevant. There's no --quiet command-line option yet. It's NOT the opposite # of 'verbose'. # # 'verbose' is temporary, same as --verbose; it's set to false at exit. # # 'sets-mode' affects set muting when moving to the next set. 'normal' leaves # the next set muted. 'auto-arm' unmutes it. 'additive' keeps the previous set # armed when moving to the next set. 'all-sets' arms all sets at once. # # 'port-naming': 'short', 'pair', or 'long'. If 'short', the device name is # shown. If it is generic, the client name is added for clarity. If 'pair', # the client:port number is prepended. If 'long', the full set of name items # is shown. If port-mapping is active (now the default), this does not apply. # # 'init-disabled-ports' is experimental. It tries live toggle of port state. [Seq66] config-type = "rc" version = 3 quiet = false verbose = false sets-mode = normal port-naming = short init-disabled-ports = false # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] This file and it's included configurations are meant for a test of the following usage patterns: 4x4 patterns Port mapping Added automation controls For this test configuration, we move the "Loop" keystrokes for patterns 16 to 31 to other automation functions. The "Mute" keystrokes (i.e. the shifted keys) remain the same. To enable this configuration first run the following command (Linux): $ qseq66 --home ~/.config/seq66/4x4 Once the 4x4 directory exists, copy the test configuration: $ cp contrib/tests/4x4/* ~/.config/seq66/4x4 An alternative is to do the test in place by copying the "session.rc" file to ~/.config/seq66 and point the "4x4" config variable to the full-path to contrib/tests/4x4. Then run "qseq66 --inspect 4x4". Let the testing begin! # Provides a flag and file-name for MIDI-control I/O settings. '""' means # no 'ctrl' file. If none, default keystrokes are used, with no MIDI control. # Note that all configuration files are stored in the "home" configuration # directory; any paths in the file-names are stripped. [midi-control-file] active = true name = "qseq66.ctrl" # Provides a flag and file-name for mute-groups settings. '""' means no # 'mutes' file. If none, there are no mute groups, unless the MIDI file # contains some. [mute-group-file] active = false name = "qseq66.mutes" # Provides a flag and file-name for 'user' settings. '""' means no 'usr' # file. If none, there are no special user settings. Using no 'usr' file # should be considered experimental. [usr-file] active = true name = "qseq66.usr" # Provides a flag and play-list file. If no list, use '""' and set active # = false. Use the extension '.playlist'. Even if not active, the play-list # file is read. 'base-directory' sets the directory holding all MIDI files # in all play-lists, useful when copying play-lists/tunes from one place to # another; it preserves sub-directories (e.g. in creating an NSM session). [playlist] active = false name = "qseq66.playlist" base-directory = "" # Provides a flag and file-name for note-mapping. '""' means no 'drums' file. # This file is used when the user invokes the note-conversion operation in # the pattern editor of a transposable pattern. Make the pattern temporarily # transposable to allow this operation. [note-mapper] active = false name = "qseq66.drums" # Provides a flag and a file-name to allow modifying the palette using the file # specified. Use '""' to indicate no 'palette' file. If none or not active, # the internal palette is used. [palette-file] active = false name = "qseq66.palette" # If specified, a style-sheet (e.g. 'qseq66.qss') is applied at startup. # This file must be located in Seq66's "home" directory now. # Note that style-sheet specification has been moved from the 'usr' file. [style-sheet-file] active = true name = "darkfix.qss" # Defines features of MIDI meta-event handling. Tempo events occur in the first # track (pattern 0), but one can move tempo elsewhere. It changes where tempo # events are recorded. The default is 0, the maximum is 1023. A pattern must # exist at this number. [midi-meta-events] tempo-track = 0 # Set to true to create virtual ALSA/JACK I/O ports and not auto-connect # to other clients. It allows up to 48 output or input ports (defaults to 8 # and 4). Keep it false to auto-connect Seq66 to real ALSA/JACK MIDI ports. # Set 'auto-enable' to enable all virtual ports automatically. [manual-ports] virtual-ports = false auto-enable = false output-port-count = 8 input-port-count = 4 # These MIDI ports are for input and control. JACK's view: these are # 'playback' devices. The first number is the bus, the second number is the # input status, disabled (0) or enabled (1). The item in quotes is the full # input bus name. The type of port depends on the 'virtual-ports' setting. [midi-input] 2 # number of MIDI input (or control) buses 0 1 "[0] 0:1 system:ALSA Announce" 1 0 "[1] 14:0 Midi Through Port-0" # This table is similar to the [midi-clock-map] section, but the values are # different. -2 = unavailable; 0 = not inputing; 1 = enabled for inputing. [midi-input-map] 1 # map is active 0 1 "ALSA Announce" 1 0 "Midi Through Port-0" # These MIDI ports are for output, playback, and display. JACK's view: these # are 'capture' devices. The first line shows the count of output ports. # Each line shows the bus number and clock status of that bus: # # -2 = Output port is not present on the system (unavailable). # -1 = Output port is disabled. # 0 = MIDI Clock is off. Output port is enabled. # 1 = MIDI Clock on; Song Position and MIDI Continue are sent. # 2 = MIDI Clock Modulo. # # With Clock Modulo, clocking doesn't begin until song position reaches the # start-modulo value [midi-clock-mod-ticks]. Ports that are unavailable # (because another port, e.g. Windows MIDI Mapper, has exclusive access to # the device) are displayed ghosted. The type of port depends on the # 'virtual-ports' setting. [midi-clock] 1 # number of MIDI clocks (output/display buses) 0 0 "[0] 14:0 Midi Through Port-0" # Patterns use bus numbers, not names. This table provides virtual bus numbers # that match real devices and can be stored in each pattern. The bus number # is looked up in this table, the port nick-name is retrieved, and the true # bus number is obtained and used. Thus, if the ports change order in the MIDI # system, the pattern will use the proper port. The short nick-names work in # ALSA or JACK (a2jmidid bridge). [midi-clock-map] 1 # map is active 0 0 "Midi Through Port-0" # 'ticks' provides the Song Position (16th notes) at which clocking begins if # the bus is set to MIDI Clock Mod setting. 'record-by-channel' allows the # master MIDI bus to record/filter incoming MIDI data by channel, adding each # new MIDI event to the pattern that is set to that channel. Option adopted # from the Seq32 project at GitHub. [midi-clock-mod-ticks] ticks = 64 record-by-channel = false # Set to true to have Seq66 ignore port names defined in the 'usr' file. Use # this option to to see the system ports as detected by ALSA/JACK. [reveal-ports] show-system-ports = false # This section sets up a metronome that can be activated from the main live # grid. It consists of a 'main' note on the first beat, then 'sub' notes on # the rest of the beats. The patch/program, note value, velocity, and # fraction length relative to the beat width (can be specified. The length # ranges from about 0.125 (one-eight) to 1.0 (the same length as the beat # width) to 2.0). [metronome] output-buss = 0 output-channel = 9 beats-per-bar = 4 beat-width = 4 main-patch = 0 main-note = 75 main-note-velocity = 96 main-note-length = 0 sub-patch = 0 sub-note = 76 sub-note-velocity = 84 sub-note-length = 0 count-in-active = false count-in-measures = 1 count-in-recording = false recording-buss = 0 recording-measures = 0 thru-buss = 0 thru-channel = 0 # Sets mouse usage for drawing/editing patterns. 'Fruity' mode is NOT in # Seq66. Other settings are available: 'snap-split' enables splitting # song-editor triggers at a snap position instead of in its middle. Split is # done by a middle-click or ctrl-left click. 'double-click-edit' allows double- # click on a slot to open it in a pattern editor. Set it to false if # you don't like how it works. [interaction-method] snap-split = false double-click-edit = true # transport-type enables synchronizing with JACK Transport. Values: # none: No JACK Transport in use. # slave: Use JACK Transport as Slave. # master: Attempt to serve as JACK Transport Master. # conditional: Serve as JACK master if no JACK master exists. # # song-start-mode playback is either Live, Song, or Auto: # live: Muting & unmuting of loops in the main window. # song: Playback uses Song (performance) editor data. # auto: If the loaded tune has song triggers, use Song mode. # # jack-midi sets/unsets JACK MIDI, separate from JACK transport. # jack-auto-connect sets connecting to JACK ports found. Default = true; use # false to have a session manager make the connections. # jack-use-offset attempts to calculate timestamp offsets to improve accuracy # at high-buffer sizes. Still a work in progress. # jack-buffer-size allows for changing the frame-count, a power of 2. [jack-transport] transport-type = none song-start-mode = auto jack-midi = false jack-auto-connect = true jack-use-offset = false jack-buffer-size = 0 # 'auto-save-rc' sets automatic saving of the 'rc' and other files. If set, # many command-line settings are saved to configuration files. # # 'old-triggers' saves triggers in a format compatible with Seq24. Otherwise, # triggers are saved with an additional 'transpose' setting. The old-mutes # value, if true, saves mute-groups as long values (!) instead of bytes. [auto-option-save] auto-save-rc = true save-old-triggers = false save-old-mutes = false # Specifies the last-used/currently-active directory. [last-used-dir] "/home/ahlstrom/Home/ca/mls/git/seq66/contrib/tests/4x4" # The most recently-loaded MIDI files. 'full-paths' = true means to show the # full file-path in the menu. The most recent file (top of list) can be loaded # via 'load-most-recent' at startup. [recent-files] full-paths = false load-most-recent = true count = 1 "/home/ahlstrom/Home/ca/mls/git/seq66/contrib/tests/4x4/buff.midi" # End of /home/ahlstrom/Home/ca/mls/git/seq66/contrib/tests/4x4/qseq66.rc # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: contrib/tests/4x4/qseq66.usr ================================================ # Seq66 0.99.11 user ('usr') configuration file # # /home/ahlstrom/Home/ca/mls/git/seq66/contrib/tests/4x4/qseq66.usr # Written 2023-11-05 09:21:19 # # 'usr' file. Edit it and place it in ~/.config/seq66. It allows naming each # MIDI bus/port, channel, and control code. [Seq66] config-type = "usr" version = 12 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] This file is part of the 4x4 test. It sets the 4x4 arrangement. # [user-midi-bus-definitions] # # 1. Define instruments and their control-code names, as applicable. # 2. Define MIDI busses, names, and the instruments on each channel. # # Channels are counted from 0-15, not 1-16. Instruments not set here are set # to -1 and are GM (General MIDI). These labels are shown in MIDI Clocks, # Inputs, the pattern editor buss, channel, and event drop-downs. To disable # entries, set counts to 0. [user-midi-bus-definitions] 0 # number of user-defined MIDI busses # In these MIDI instrument definitions, active (supported by the instrument) # controller numbers are paired with the (optional) name of the controller. [user-instrument-definitions] 0 # instrument list count # [user-interface-settings] # # Configures some user-interface items. Also see [user-ui-tweaks]. # For window styling, use Qt style-sheets as specified in the 'rc' file. # # 'swap-coordinates' swaps numbering so pattern numbers vary fastest by column # instead of rows. This setting applies to the live grid, mute-group buttons, # and set-buttons. # # 'mainwnd-rows' and 'mainwnd-columns' (option '-o sets=RxC') specify # rows/columns in the main grid. R ranges from 4 to 8, C from 4 to 12. # Values other than 4x8 have not been tested thoroughly. # # 'mainwnd-spacing' is for grid buttons; from 0 to 16, default = 2. # # 'default-zoom' is the initial zoom for piano rolls. From 1 to 512, default # = 2. Larger PPQNs require larger zoom to look good. Seq66 adapts the zoom to # the PPQN if set to 0. The unit of zoom is ticks/pixel. # # 'global-seq-feature' applies the key, scale, and background pattern to all # patterns versus separately to each. If all, these values are stored in the # MIDI file in the global SeqSpec versus in each track. # # 'progress-bar-thick specifies a thicker progress bar. Default is 2 pixels, # 1 pixel if set to false. Also affects the slot box border and the boldness # of the slot font. # # 'follow-progress specifies the default for following progress in the piano # rolls. Each window has a button to toggle following progess # # 'inverse-colors' (option -K/--inverse) specifies use of an inverse color # palette. Palettes are for Seq66 drawing areas, not for Qt widgets. # Normal/inverse palettes can be reconfigured via a 'palette' file. # # 'time-fg-color' and 'time-bg-color' override the default colors for ticks # and time displays (lime on black). 'default' keeps the defaults. 'normal' # uses the theme color. # # 'dark-theme' specifies that a dark theme is active, so that some colors # (e.g. grid-slot text) are inverted. # # 'window-redraw-rate' specifies the base window redraw rate for all windows. # From 10 to 100; default = 40 ms (25 ms for Windows). # # Window-scale (option '-o scale=m.n[xp.q]') specifies scaling the main # window at startup. Defaults to 1.0 x 1.0. If between 0.5 and 3.0, it # changes the size of the main window proportionately. # # 'enable-learn-confirmation' can be set to false to disable the prompt that # the mute-group learn action succeeded. Can be annoying. [user-interface-settings] swap-coordinates = false mainwnd-rows = 4 mainwnd-columns = 4 mainwnd-spacing = 2 default-zoom = 2 global-seq-feature = true progress-bar-thick = false follow-progress = false inverse-colors = false time-fg-color = "?" time-bg-color = "?" dark-theme = false window-redraw-rate = 40 window-scale = 1 window-scale-y = 1 enable-learn-confirmation = false # Seq66 separates file PPQN from the Seq66 PPQN. 'default-ppqn' specifies the # Seq66 PPQN, from 32 to 19200, default = 192. 'use-file-ppqn' (recommended) # indicates to use file PPQN. [user-midi-ppqn] default-ppqn = 192 use-file-ppqn = true # This section specifies the default values to use to jitter the MIDI event # time-stamps and randomize event amplitudes (e.g. velocity for notes). The # range of jitter is 1/j times the current snap value. [user-randomization] jitter-divisor = 8 amplitude = 8 # [user-midi-settings] # # Specifies MIDI-specific variables. -1 means the value isn't used. # # Item Default Range # 'convert-to-smf-1': true true/false. # 'beats-per-bar': 4 1 to 32. # 'beats-per-minute': 120.0 2.0 to 600.0. # 'beat-width': 4 1 to 32. # 'buss-override': -1 (none) -1 to 48. # 'velocity-override': -1 (Free) -1 to 127. # 'bpm-precision': 0 0 to 2. # 'bpm-step-increment': 1.0 0.01 to 25.0. # 'bpm-page-increment': 1.0 0.01 to 25.0. # 'bpm-minimum': 0.0 127.0 # 'bpm-maximum': 0.0 127.0 # # 'convert-to-smf-1' controls if SMF 0 files are split into SMF 1 when read. # 'buss-override' sets the output port for all patterns, for testing, etc. # This value will be saved if you save the MIDI file!!! # 'velocity-override' controls adding notes in the pattern editor; see the # 'Vol' button. -1 ('Free'), preserves incoming velocity. # 'bpm-precision' (spinner and MIDI control) is 0, 1, or 2. # 'bpm-step-increment' affects the spinner and MIDI control. For 1 decimal, # 0.1 is good. For 2, 0.01 is good, 0.05 is faster. Set 'bpm-page-increment' # larger than the step-increment; used with the Page-Up/Page-Down keys in the # spinner. BPM minimum/maximum sets the range in tempo graphing; defaults to # 0.0 to 127.0. Decrease it for a magnified view of tempo. [user-midi-settings] convert-to-smf-1 = true beats-per-bar = 4 beats-per-minute = 120 beat-width = 4 buss-override = -1 velocity-override = -1 bpm-precision = 0 bpm-step-increment = 1 bpm-page-increment = 10 bpm-minimum = 2 bpm-maximum = 600 # [user-options] # # These settings specify some -o or --option switch values. 'daemonize' in # seq66cli indicates that it should run as a service. 'log' specifies a log- # file redirecting output from standard output/error. If no path in the name, # the log is stored in the configuration directory. For no log-file, use # "none" or "". On the command line: '-o log=filename.log'. [user-options] daemonize = false log = "/home/ahlstrom/Home/ca/mls/git/seq66/contrib/tests/4x4/4x4.log" pdf-viewer = "?" browser = "?" # [user-ui-tweaks] # # key-height specifies the initial height (before vertical zoom) of pattern # editor keys. Defaults to 10 pixels, ranges from 6 to 32. # # key-view specifies the default for showing labels for each key: # 'octave-letters' (default), 'even_letters', 'all-letters', # 'even-numbers', and 'all-numbers'. # # note-resume causes notes-in-progress to resume when the pattern toggles on. # # Note that style-sheet specification has been moved to the 'rc' file with # all the rest of the files. # # A fingerprint is a condensation of note events in a long track, to reduce # the time drawing the pattern in the buttons. Ranges from 32 (default) to # 128. 0 = don't use a fingerprint. # # progress-box-width and -height settings change the scaled size of the # progress box in the live-grid buttons. Width ranges from 0.50 to 1.0, and # the height from 0.10 to 1.0. If either is 'default', defaults (0.8 x 0.3) # are used. progress-box-shown controls if the boxes are shown at all. # # progress-note-min and progress-note-max set the progress-box note range so # that notes aren't centered in the box, but shown at their position by pitch. # # lock-main-window prevents the accidental change of size of the main # window. [user-ui-tweaks] key-height = 10 key-view = octave-letters note-resume = false fingerprint-size = 32 progress-box-width = 0.8 progress-box-height = 0.4 progress-box-shown = false progress-note-min = 0 progress-note-max = 127 lock-main-window = false # [user-session] # # The session manager to use, if any. 'session' is 'none' (default), 'nsm' # (Non/New Session Manager), or 'jack'. 'url' can be set to the value set by # nsmd when run by command-line. Set 'url' if running nsmd stand-alone; use # the --osc-port number. Seq66 detects if started in NSM. The visibility flag # is used only by NSM to restore visibility. [user-session] session = jack url = "" visibility = true # [pattern-editor] # # Settings for play/record for a new pattern. A new pattern is 'Untitled' # and has no events. These settings save time in live recording. Valid # record-style values: 'merge' (overdub), 'overwrite', 'expand', 'one-shot', # and 'one-shot-reset'. 'wrap-around' allows recorded notes to wrap to the # pattern start. 'escape-pattern' allows the Esc key to close the pattern # editor if not playing or in paint mode. Currently 'notemap' and quantizing # are mutually exclusive. [pattern-editor] escape-pattern = false armed = false thru = false record = false tighten = false qrecord = false notemap = false record-style = merge wrap-around = false # End of /home/ahlstrom/Home/ca/mls/git/seq66/contrib/tests/4x4/qseq66.usr # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: contrib/tests/4x4/synthstart ================================================ #!/bin/bash # # Date 2022-01-02 # Updated 2022-01-02 # # YOSHPATH="/usr/bin" # # Starts Yoshimi with our oft-used GM patch setup for Yoshimi, and Qsynth. # Meant to be done under ALSA. Make sure Qsynth is set for ALSA. # Yoshimi is kind of flaky on our system when run in the background, so we run # it second. YOSHPATH="/usr/bin" YOSHIMI="yoshimi" OPTIONS="-aA" REPOPATH="Home/ca/mls/git" CFGPATH="$HOME/$REPOPATH/yoshimi-cookbook/sequencer64/b4uacuse" if [ "$1" == "latest" ] ; then YOSHPATH="/usr/local/bin" YOSHIMI="yoshimi-1.7.2rc1" shift fi echo "Running QSynth..." qsynth & sleep 2 echo "Running $YOSHPATH/$YOSHIMI $OPTIONS --state=$CFGPATH/yoshimi-b4uacuse-gm.state..." exec $YOSHPATH/$YOSHIMI $OPTIONS --state=$CFGPATH/yoshimi-b4uacuse-gm.state # vim: ts=3 sw=3 wm=4 et ft=sh ================================================ FILE: contrib/valgrind/fontconfig.supp ================================================ # -*- tab-width: 3; indent-tabs-mode: nil -*- { Memcheck:Addr4 fun:FcConfigFileExists } ================================================ FILE: contrib/valgrind/glibc.supp ================================================ # -*- tab-width: 3; indent-tabs-mode: nil -*- { Memcheck:Cond fun:__GI___strcasecmp_l } { Memcheck:Value8 fun:__GI___strcasecmp_l } { Memcheck:Addr8 fun:__strspn_sse42 } { Memcheck:Cond fun:__strspn_sse42 } ================================================ FILE: contrib/valgrind/helgrind-test.sh ================================================ #!/bin/sh # # Additional options: # # --suppressions=contrib/valgrind/seq66.supp # --leak-resolution=high SCRIPTDIR="../seq66/contrib/valgrind" TOOL="--tool=helgrind" CALLERS="--num-callers=50" SUPPRESS="--suppressions=$SCRIPTDIR/kde.supp" QTGLIB="export QT_NO_GLIB=1" LOCKTRACK="--track-lockorders=no" HELGRIND_OPTS="$TOOL $CALLERS $LOCKTRACK $SUPPRESS" echo "$QTGLIB valgrind $HELGRIND_OPTS --log-file=helgrind-qt.log $* . . ." $QTGLIB valgrind $HELGRIND_OPTS --log-file=helgrind-qt.log $* # vim: ts=3 sw=3 wm=4 et ft=sh ================================================ FILE: contrib/valgrind/kde.supp ================================================ # See https://www.kdab.com/~dfaure/helgrind.html: # # $ cd ~ # $ git clone git://anongit.kde.org/kde-dev-scripts # $ export VALGRIND_OPTS="--num-callers=50 \ # --suppressions=$HOME/kde-dev-scripts/kde.supp" # # alias helgrind="QT_NO_GLIB=1 valgrind --tool=helgrind --track-lockorders=no" # # Some valgrind suppressions handy for ignoring stuff we don't care # about when valgrinding kde applications # # Library paths and versions from debian unstable, YMMV # # # ld.so errors # { strchr/decompose_rpath/_dl_map_object MemCheck:Cond fun:strchr fun:decompose_rpath fun:_dl_map_object } { strlen/libc/_dl_catch_error MemCheck:Cond fun:strlen fun:_dl_open obj:*libdl-2*.so fun:_dl_catch_error* } { strchr/libc/_dl_catch_error MemCheck:Cond fun:strchr obj:*libc-2.2.?.so fun:_dl_catch_error } { strrchr/_dl_map_object_from_fd/_dl_map_object MemCheck:Cond fun:strrchr fun:_dl_map_object_from_fd fun:_dl_map_object } # Needed with /lib/ld-2.12.1.so { index/expand_dynamic_string_token/_dl_map_object Memcheck:Cond fun:index fun:expand_dynamic_string_token fun:_dl_map_object } { strlen/_dl_signal_cerror/_dl_lookup_symbol_internal Memcheck:Cond fun:strlen fun:_dl_signal_cerror fun:_dl_lookup_symbol_internal fun:*dlsym } # # X library errors # { libXft(Cond) MemCheck:Cond obj:/usr/X11R6/lib/libXft.so.1.1 obj:/usr/X11R6/lib/libXft.so.1.1 } { write(buf)/libc/libICE Memcheck:Param write(buf) fun:__GI___libc_write fun:_IceTransWrite fun:_IceWrite fun:IceFlush } { write(buf)/libc/libICE(nosymbols) Memcheck:Param write(buf) fun:__write_nocancel obj:/usr/lib*/libICE.so.* obj:/usr/lib*/libICE.so.* } { write(buf)/libc/libICE(variant) Memcheck:Param write(buf) obj:/lib/libpthread-*.so obj:/usr/lib/libICE.so.* fun:_IceWrite fun:IceFlush } { write(buf)/libc/libX11 Memcheck:Param write(buf) fun:__GI___libc_write fun:_X11TransWrite fun:_XFlushInt fun:_XFlush } { write(buf)/libc/libX11 Memcheck:Param write(buf) fun:__GI___libc_write fun:_X11TransWrite fun:_XFlushInt fun:_XReply } { writev(vector[...]) Memcheck:Param writev(vector[...]) fun:*writev obj:libX11.so.* fun:_X11TransWritev fun:_XSend } # # SSL errors # { various1/libcrypto Memcheck:Value4 obj:*libcrypto.so.0.9.7 } { various2/libcrypto Memcheck:Cond obj:*libcrypto.so.0.9.7 } { ssl3_read_bytes1/libssl Memcheck:Cond fun:memcpy fun:ssl3_read_bytes } { ssl3_read_bytes2/libssl Memcheck:Cond fun:ssl3_read_bytes } { ssl3_get_message/libssl Memcheck:Cond fun:ssl3_get_message } # zlib-1.2.x uses uninitialised memory in some tricky way which # apparently is harmless (it must amount to a vectorised while-loop, # nothing else makes sense). Fools Memcheck though. See the mentioned # URL for details. # Valgrind has this in default.supp but only for deflate, not for uncompress/inflateInit2 { zlib-1.2.x trickyness (1): See http://www.zlib.net/zlib_faq.html#faq36 Memcheck:Cond obj:/*lib*/libz.so.1.2.* ... fun:inflateInit2* } # Leakcheck suppressions { dlopen_worker_malloc Memcheck:Leak fun:malloc ... fun:dl_open_worker } { dlopen_worker_calloc Memcheck:Leak fun:calloc ... fun:dl_open_worker } { fontconfig_init Memcheck:Leak ... fun:FcInit } # Helgrind suppressions # Most of them (apart from the first one) are probably real, # but they're deep inside Qt, and usually not what we want to see # when debugging a threading issue in a KDE application. { QMutex_qt4_unlock_false_race Helgrind:Race fun:_ZN6QMutex6unlockEv } { QMutex_qt4_lock_false_race Helgrind:Race fun:_ZN6QMutex4lockEv } { # Qt4 qmutex_p.h, see maximumSpinTime declared as "volatile qint64"... QMutex_lockInternal_real_race Helgrind:Race fun:_ZN6QMutex12lockInternalEv } { # (Qt5) QMutex::lock checks a bool that is set by the QMutex constructor. # To use a mutex from another thread, some synchronization must have happened # already, which propagated this non-atomic write. Helgrind doesn't catch that. QMutex_isRecursive_false_race Helgrind:Race fun:_ZL11isRecursiveP10QMutexData fun:_ZN6QMutex4lockEv } { # Qt5: helgrind doesn't understand the atomic-operation calls inside QMutex Qt5_lockInternal Helgrind:Race ... fun:_ZN11QBasicMutex12lockInternalEv } { # Qt5: helgrind doesn't understand the atomic-operation calls inside QMutex Qt5_unlockInternal Helgrind:Race ... fun:_ZN11QBasicMutex14unlockInternalEv } { # Qt5: helgrind doesn't understand the atomic-operation calls inside QMutex Qt5_QMutex_unlock Helgrind:Race ... fun:_ZN6QMutex6unlockEv } { # Qt5: helgrind doesn't understand the atomic-operation calls inside QMutex Qt5_QMutex_lock Helgrind:Race ... fun:_ZN6QMutex4lockEv } { # Qt5: helgrind doesn't understand the atomic-operation calls Qt5_release_refcount Helgrind:Race ... fun:_ZN9QtPrivate8RefCount3refEv } { # Qt5 in release mode Qt5_release_basicmutex_isrecursive Helgrind:Race fun:_ZN11QBasicMutex11isRecursiveEv } { # I don't really see a problem with fork+exit "exiting while still holding one mutex". # I think helgrind just doesn't notice that we forked before calling exit -> TODO: report hg bug Helgrind_Exit Helgrind:Misc fun:_Exit } { deallocate_stack Helgrind:Race fun:__deallocate_stack fun:start_thread fun:clone } { qt_thread_data_race Helgrind:Race fun:_ZL15set_thread_dataP11QThreadData fun:_ZN14QThreadPrivate5startEPv } { timerId_race_in_qt4 Helgrind:Race fun:_ZN31QAbstractEventDispatcherPrivate14releaseTimerIdEi } { timerId_second_race_in_qt4 Helgrind:Race fun:_ZN31QAbstractEventDispatcherPrivate15allocateTimerIdEv } { QPointer_race_in_qt4 Helgrind:Race fun:_ZNK5QHashIP7QObjectPS1_E7isEmptyEv fun:_ZN11QMetaObject11removeGuardEPP7QObject } # Helgrind suppressions for atomic operations. # This is because helgrind/drd cannot tell that they are atomic operations, on x86. The machine code is exactly the same. { QBasicAtomicPointer Helgrind:Race # catch load*, store*, and the qt4 operators ... fun:_*QBasicAtomicPointer* } { QBasicAtomicInt Helgrind:Race # catch Int and Integer, load, loadAcquire, store, storeRelease, testAndSet*, etc. ... fun:_*QBasicAtomicInt* } { Qt_5_9_atomic_ops Helgrind:Race ... fun:loadAcquire } # Additional helgrind suppressions: these are actual atomic operations, # but if they conflict with a suppressed store(), helgrind will warn anyway -- https://bugs.kde.org/show_bug.cgi?id=317381 { QBasicAtomicOps_fetchAndStore Helgrind:Race fun:_ZN15QBasicAtomicOps*fetchAndStore* } { QBasicAtomicOps_testAndSet Helgrind:Race fun:_ZN15QBasicAtomicOps*testAndSet* } { QBasicAtomicOps_ref Helgrind:Race fun:_ZN15QBasicAtomicOps*ref* } ## Same for DRD (tested with Qt5 only) { QBasicAtomic_load_drd drd:ConflictingAccess # catch load and loadAcquire fun:_ZNK19QBasicAtomic*load* } { QBasicAtomic_store_drd drd:ConflictingAccess # catch store and storeRelease fun:_ZN19QBasicAtomic*store* } { QBasicAtomicOps_fetchAndStore_drd drd:ConflictingAccess fun:_ZN15QBasicAtomicOps*fetchAndStore* } { QBasicAtomicOps_testAndSet_drd drd:ConflictingAccess fun:_ZN15QBasicAtomicOps*testAndSet* } # fixup QOrderedMutexLocker, see https://bugs.kde.org/show_bug.cgi?id=243232 { QOrderedMutexLocker_relock_tryLock Helgrind:LockOrder fun:QMutex_tryLock_int_WRK fun:_ZN19QOrderedMutexLocker6relockEP6QMutexS1_ } { QOrderedMutexLocker_relock_lock Helgrind:LockOrder fun:QMutex_lock_WRK fun:_ZN19QOrderedMutexLocker6relockEP6QMutexS1_ } { QOrderedMutexLocker_relock2_tryLock Helgrind:LockOrder fun:QMutex_tryLock_int_WRK fun:_ZN19QOrderedMutexLocker6relockEv } { QOrderedMutexLocker_relock2_lock Helgrind:LockOrder fun:QMutex_lock_WRK fun:_ZN19QOrderedMutexLocker6relockEv } # glib event loop integration, alternatively use QT_NO_GLIB=1 { glib_event_loop Helgrind:Race fun:g_private_get obj:/usr/lib/libglib-* fun:g_main_context_dispatch obj:/usr/lib/libglib-* fun:g_main_context_iteration } # C atomic operations { AO_store Helgrind:Race fun:AO_store } { AO_load Helgrind:Race fun:AO_load } { AO_compare_and_swap Helgrind:Race fun:AO_compare_and_swap } # C++ atomic operations { atomic_store Helgrind:Race fun:store fun:_ZN*St*atomic* } { atomic_load Helgrind:Race fun:load fun:_ZN*St*atomic* } ================================================ FILE: contrib/valgrind/valgrind-leaks.sh ================================================ #!/bin/sh # # Additional options: # # --suppressions=contrib/valgrind/seq66.supp # --leak-resolution=high valgrind --leak-check=full --track-origins=yes --num-callers=20 \ --log-file=valgrind.log --show-leak-kinds=all $* ================================================ FILE: contrib/vim-syntax/c.vim ================================================ "****************************************************************************** " c.vim "------------------------------------------------------------------------------ " " Language: C " Maintainer: Chris Ahlstrom " Last Change: 2006-08-04 to 2025-10-23 " Project: XPC Suite library project " License: None. Use it in any manner whatsover, and don't blame me. " Usage: " " This file is a Vim syntax add-on file for c.vim (and cpp.vim) as " installed when vim is installed. " " Do a ":set runtimepath" command in vim to see what it has in it. The " first entry is usually "~/.vim", so create that directory, then add an " "after" and "syntax" directory, so that you end up with this directory: " " ~/.vim/after/syntax " " Then copy the present file (c.vim) to " " ~/.vim/after/syntax/c.vim " " Verify that your code now highlights the following symbols. " " Not necessary if the above instructions are followed: " " :runtime ~/.vim/after/syntax/syncolor.vim " " Tip: " " If you want to quickly validate your Doxygen comments, run the following " command to highlight them using doxygen.vim in " /usr/share/vim/vimfiles/syntax: " " :set syntax=doxygen " " syn keyword XPCC " "------------------------------------------------------------------------------ syn keyword XPCC errno errprintfunc infoprintfunc warnprintfunc syn keyword XPCC strerrprintfunc strerrorprintfunc strerrnoprintfunc syn keyword XPCC atomic_int_t bool_to_string bool_to_code debugprint_func syn keyword XPCC EXTERN_C_DEC EXTERN_C_END find_data_t syn keyword XPCC find_function_t find_type_t ftw_function_t syn keyword XPCC is_invalid_handle is_invalid_thread syn keyword XPCC is_NULL is_nullptr is_nullptr_2 both_are_nullptr syn keyword XPCC is_nullfunc syn keyword XPCC is_posix_error is_posix_success is_socket_error is_thisptr syn keyword XPCC is_valid_handle is_valid_socket is_valid_thread not_thisptr syn keyword XPCC not_nullptr not_nullptr_2 not_nullptr_3 not_nullptr_4 syn keyword XPCC not_NULL not_nullfunc not_posix_error not_posix_success syn keyword XPCC not_null_result syn keyword XPCC not_socket_error not_valid_socket syn keyword XPCC perf_bits_enum_t perf_cpu_speed_t perf_function_t perf_hyper_t syn keyword XPCC perf_lock_enum_t perf_os_enum_t perf_processor_t perf_task_enum_t syn keyword XPCC perf_test_row_t syn keyword XPCC POSIX_ERROR POSIX_FAILURE POSIX_SUCCESS syn keyword XPCC xpc xpc_addrinfo_t xpc_always_inline xpc_hidden syn keyword XPCC xpc_app_arguments_t xpc_closesocket syn keyword XPCC xpc_cond_mutex xpc_cond_struct xpc_cond_t xpc_critex_struct syn keyword XPCC xpc_critex_t xpc_error_level_t xpc_filename_change_t syn keyword XPCC xpc_errprint_func syn keyword XPCC XPC_PACKAGE XPC_VERSION XPC_API_VERSION syn keyword XPCC xpc_filename_split_t xpc_filename_t xpc_filename_type_t syn keyword XPCC XPC_INLINE_CODE syn keyword XPCC XPC_INT_CAST xpc_invalid_handle XPC_INVALID_HANDLE syn keyword XPCC XPC_INVALID_SOCKET XPC_INVALID_THREAD XPC_LOCALHOST syn keyword XPCC XPC_LOCALHOST_NAME xpc_lseek_offset_t xpc_mutex_struct syn keyword XPCC xpc_mutex_t xpc_pointer_t xpc_recast syn keyword XPCC xpc_return XPC_REVISION XPC_REVISION_DECL syn keyword XPCC xpc_ringbuffer_t xpc_seedings_t xpc_semaphore_struct syn keyword XPCC xpc_semaphore_t xpc_server_socket_T XPC_SHOWINFO syn keyword XPCC xpc_string_t syn keyword XPCC xpc_sockaddr_list_t xpc_sockaddr_storage_t xpc_sock_cs_codes_t syn keyword XPCC XPC_SOCK_DEF_FRAGSIZE XPC_SOCK_DEF_NODELAY XPC_SOCK_DEF_RETRY syn keyword XPCC xpc_sock_endpoint_t xpc_sock_error_t XPC_SOCKET_ERRNO syn keyword XPCC XPC_SOCKET_ERROR xpc_socket_t xpc_sockbase_t xpc_sock_flag_t syn keyword XPCC xpc_sock_mode_t xpc_sock_parameters_t xpc_sock_pending_t syn keyword XPCC xpc_sock_port_t xpc_sock_state_t xpc_sock_status_t syn keyword XPCC XPC_SOCK_TIMEOUT_ERROR XPC_SOCK_TIMEOUT_INF XPC_SOCK_TIMEOUT_NONE syn keyword XPCC xpc_sock_timeout_t xpc_sock_tos_t xpc_statcast syn keyword XPCC xpc_test_arguments_t xpc_test_function_t xpc_test_row_t syn keyword XPCC xpc_timeout_t xpc_inline xpc_unused syn keyword XPCC XPC_VERSION XPC_VERSION_TAG XPC_LIB_VERSION syn keyword XPCC xpc_widetime_t syn keyword XPCC xpc_xdr_discrim_t xpc_xdr_enum_t xpc_xdrf_destroy_t syn keyword XPCC xpc_xdrf_func_t xpc_xdrf_getbyte_t xpc_xdrf_getint32_t syn keyword XPCC xpc_xdrf_getlong_t xpc_xdrf_getpost_t xpc_xdrf_inline_t syn keyword XPCC xpc_xdrf_putbyte_t xpc_xdrf_putint32_t xpc_xdrf_putlong_t syn keyword XPCC xpc_xdrf_setpost_t xpc_xdrf_sock_t xpc_xdr_mem_alloc syn keyword XPCC xpc_xdr_mem_free xpc_xdr_netobj_t xpc_xdr_ops_t syn keyword XPCC xpc_xdr_op_t xpc_xdr_t set_invalid_socket syn keyword XPCC xpc_strerrnoprint_func xpc_strerrprint_func syn keyword XPCC xpc_infoprint_func xpc_warnprint_func syn keyword XPCC safe_array_delete safe_ptr_delete safe_pointer_delete thisptr syn keyword XPCC set_nullptr set_posix_error set_posix_success syn keyword XPCC bussbyte colorbyte ctrlkey ctrlop eventkey edit jacktick syn keyword XPCC microsec midibool midibooleans midibyte midibyte_t midilong syn keyword XPCC midimacro midippqn midipulse midishort miditag midibpm syn keyword XPCC select "------------------------------------------------------------------------------ " More keywords from POSIX itself. Includes less common C data typedefs and " GNU keywords. "------------------------------------------------------------------------------ syn keyword cType byte word doubleword __int64 syn keyword cType caddr_t u_char u_short u_int u_long quad_t u_quad_t syn keyword cType mode_t off_t pid_t uid_t socklen_t wchar_t syn keyword cType pollfd timeval timespec "------------------------------------------------------------------------------ " Keywords from GNU "------------------------------------------------------------------------------ syn keyword cType __inline__ __attribute__ "------------------------------------------------------------------------------ " Keywords from POSIX. "------------------------------------------------------------------------------ syn keyword cType pthread_cond_t pthread_mutex_t sem_t addrinfo sockaddr_in syn keyword cType sockaddr_in6 sockaddr sockaddr_un sockaddr_nl syn keyword cType sockaddr_storeage SOCKET in_port_t in_addr_t stat "------------------------------------------------------------------------------ " Keywords from Windows. "------------------------------------------------------------------------------ syn keyword cType CRITICAL_SECTION syn keyword cType HANDLE SOCKET _finddata_t _stat "------------------------------------------------------------------------------ " Change the Todo coloring to something less obtrusive than black-on-yellow. "------------------------------------------------------------------------------ highlight todo term=standout cterm=NONE ctermfg=DarkGreen ctermbg=NONE gui=NONE guifg=DarkGreen guibg=Yellow "------------------------------------------------------------------------------ " Doxygen tags. "------------------------------------------------------------------------------ " " 'sideeffect', 'usage', and many others are ALIASES in doc/doxygen.cfg. " The rest are provided by Doxygen or by convention. They appear only in " comments, and so we can use the existing cTodo highlight group. " "------------------------------------------------------------------------------ syn keyword cTodo contained FIXME WARNING WTF XXX TBD FUTURE unknown HUH __func__ syn keyword cTodo contained abc abstract accessor algorithm application syn keyword cTodo contained assumptions author syn keyword cTodo contained background bit32 bit64 brief bug syn keyword cTodo contained callback callgraph case change code class syn keyword cTodo contained configuration convention syn keyword cTodo contained contents syn keyword cTodo contained crossplatform cutnpaste cygwin syn keyword cTodo contained date debug default deprecated designdoc syn keyword cTodo contained doxygen dummy endcode syn keyword cTodo contained endverbatim enum example exception syn keyword cTodo contained file followup freebsd friend friendof goals syn keyword cTodo contained gcc getter group gnu hardwired history idea inline syn keyword cTodo contained image install interface internal syn keyword cTodo contained library license linux syn keyword cTodo contained macosx macro mainpage namespace note obsolete syn keyword cTodo contained op overload page par param posix syn keyword cTodo contained private protected xpc xpcc XPCC public syn keyword cTodo contained question recursive return ref references syn keyword cTodo contained relates relatesalso security syn keyword cTodo contained section seealso setter sideeffect syn keyword cTodo contained solaris steps struct subpage subsection syn keyword cTodo contained subsubsection summary template syn keyword cTodo contained test tests threadsafe threadunsafe throw todo syn keyword cTodo contained tip tricky typedef undocumented usecase syn keyword cTodo contained unittests unix updates usage syn keyword cTodo contained validator var verbatim version syn keyword cTodo contained warning win32 win64 syn keyword cTodo contained utility xoperator syn keyword cTodo contained Revision Chris Ahlstrom "------------------------------------------------------------------------------ " Our boolean definitions "------------------------------------------------------------------------------ syn keyword cType cbool_t ubool_t "------------------------------------------------------------------------------ " Our slough of macros, including a pre C++-TR-1 definition for 'nullptr' used " in both C and C++, and some underscore macros related to GNU gettext() " support. "------------------------------------------------------------------------------ syn keyword cConstant nullptr NULLptr syn keyword cDefine _ N_ M_ "------------------------------------------------------------------------------ " Additional POSIX/BSD error codes not provided by vim "------------------------------------------------------------------------------ syn keyword cConstant EHOSTUNREACH ENETUNREACH EADDRNOTAVAIL ECONNREFUSED syn keyword cConstant ECONNRESET ENOTSOCK ENOTCONN ENOBUFS "------------------------------------------------------------------------------ " WinSock 2 error codes "------------------------------------------------------------------------------ syn keyword cConstant WSAENETDOWN WSAEINPROGRESS WSAEADDRNOTAVAIL syn keyword cConstant WSAECONNREFUSED WSAENETUNREACH "------------------------------------------------------------------------------ " C errno values (will expand later) "------------------------------------------------------------------------------ syn keyword ERR E2BIG EAGAIN EBADF EILSEQ EINVAL ENOENT ENOMEM ENOSPC "------------------------------------------------------------------------------ " XPCC socket error codes "------------------------------------------------------------------------------ syn keyword cDefine XPC_SOCK_ERR_SUCCESS XPC_SOCK_ERR_CREATEFAILED syn keyword cDefine XPC_SOCK_ERR_COPYFAILED XPC_SOCK_ERR_INPUT syn keyword cDefine XPC_SOCK_ERR_INPUTINTERRUPT XPC_SOCK_ERR_RESOURCEFAILURE syn keyword cDefine XPC_SOCK_ERR_OUTPUT XPC_SOCK_ERR_OUTPUTINTERRUPT syn keyword cDefine XPC_SOCK_ERR_NOTCONNECTED XPC_SOCK_ERR_CONNECTREFUSED syn keyword cDefine XPC_SOCK_ERR_CONNECTREJECTED syn keyword cDefine XPC_SOCK_ERR_CONNECTRESETBYPEER syn keyword cDefine XPC_SOCK_ERR_CONNECTTIMEOUT XPC_SOCK_ERR_CONNECTFAILED syn keyword cDefine XPC_SOCK_ERR_CONNECTINVALID XPC_SOCK_ERR_CONNECTBUSY syn keyword cDefine XPC_SOCK_ERR_CONNECTNOROUTE XPC_SOCK_ERR_BINDINGFAILED syn keyword cDefine XPC_SOCK_ERR_LISTENFAILED XPC_SOCK_ERR_BROADCASTDENIED syn keyword cDefine XPC_SOCK_ERR_ROUTINGDENIED XPC_SOCK_ERR_KEEPALIVEDENIED syn keyword cDefine XPC_SOCK_ERR_SERVICEDENIED XPC_SOCK_ERR_SERVICEUNAVAILABLE syn keyword cDefine XPC_SOCK_ERR_MULTICASTDISABLED XPC_SOCK_ERR_TIMEOUT syn keyword cDefine XPC_SOCK_ERR_NODELAY XPC_SOCK_ERR_BADDESCRIPTOR syn keyword cDefine XPC_SOCK_ERR_NOTASOCKET XPC_SOCK_ERR_INVALIDADDRESS syn keyword cDefine XPC_SOCK_ERR_BLOCKINGMODE XPC_SOCK_ERR_WOULDBLOCK syn keyword cDefine XPC_SOCK_ERR_OTHER XPC_SOCK_ERR_GENERIC syn keyword cDefine XPC_SOCK_ERR_OS XPC_SOCK_ERR_EXTENDED "------------------------------------------------------------------------------ " c.vim "------------------------------------------------------------------------------ " vim: ts=3 sw=3 et ft=vim "------------------------------------------------------------------------------ ================================================ FILE: contrib/vim-syntax/cpp.vim ================================================ "****************************************************************************** " cpp.vim "------------------------------------------------------------------------------ " " Language: C/C++ " Maintainer: Chris Ahlstrom " Last Change: 2006-09-04 to 2025-04-01 " Project: XPC Suite library project " Usage: " " This file is a Vim syntax add-on file used in addition to the installed " version of the cpp.vim file provided by vim. " " This file is similar to c.vim, but for C++ code. It adds " keywords and syntax highlighting useful to vim users. Please note that " all of the keywords in c.vim also apply to C++ code, so that they " do not need to be repeated here. " " Do a ":set runtimepath" command in vim to see what it has in it. The " first entry is usually "~/.vim", so create that directory, then add an " "after" and "syntax" directory, so that you end up with this directory: " " ~/.vim/after/syntax " " Then copy the present file (cpp.vim) to " " ~/.vim/after/syntax/cpp.vim " " Verify that your code now highlights the following symbols when edited. " "------------------------------------------------------------------------------ "------------------------------------------------------------------------------ " Our type definitions for new classes and types added by the XPCC++ library "------------------------------------------------------------------------------ syn keyword XPCC midibytes midistring midi_message phraselist seq64 seq66 syn keyword XPCC tokenization syn keyword XPCC boolean booleans bpm buffer byte bytes bytestring ppqn pulse syn keyword XPCC byte word doubleword syn keyword XPCC audio buss container ctrl meta seqspec status tag ulong syn keyword XPCC ulonglong ushort unavailable pos mod syn keyword XPCC alsa api api_list audio bus bus_in bus_out busarray syn keyword XPCC cfg cfg66 cli disabled enabled event eventlist syn keyword XPCC functor iothread lib66 syn keyword XPCC masterbus midi message po port ports ringbuffer syn keyword XPCC rtl rtaudio rtmidi rtmidi_engine rtmidi_in rtmidi_out rtl66 syn keyword XPCC seq seq66 session toggle toggler track util xpc xpc66 syn keyword XPCC action clock clocking e_clock ignore jack max none syn keyword XPCC input output player transport synch syn keyword XPCC automutex recmutex "------------------------------------------------------------------------------ " Our type definition for inside comments "------------------------------------------------------------------------------ syn keyword cTodo contained cpp hpp CPP HPP krufty "------------------------------------------------------------------------------ " Our Doxygen aliases to highlight inside of comments "------------------------------------------------------------------------------ syn keyword cTodo contained constructor copyctor ctor syn keyword cTodo contained defaultctor destructor dtor operator paop paoperator syn keyword cTodo contained pure singleton virtual "------------------------------------------------------------------------------ " Our type definitions that are basically standard C++ "------------------------------------------------------------------------------ syn keyword cType aggregation alias containment dependency inherits nested syn keyword cType array atomic auto_ptr bad_alloc begin c_str syn keyword cType cbegin cend clear const_iterator syn keyword cType const_reverse_iterator cbegin cend rbegin rend syn keyword cType deque difference_type iterator_category pointer queue syn keyword cType empty end erase exception find first forward_list fstream future syn keyword cType ifstream insert istream istringstream iterator syn keyword cType length list make_pair map multimap mutex unordered_map syn keyword cType ofstream ostream ostringstream pair promise reverse_iterator syn keyword cType reference const_reference syn keyword cType second set shared_ptr size size_type stack std string syn keyword cType stringstream locale syn keyword cType thread unique_ptr value_type vector wstring "------------------------------------------------------------------------------ " Operators, language constants, or manipulators "------------------------------------------------------------------------------ syn keyword cppOperator cin cout cerr dec endl hex left nothrow new npos syn keyword cppOperator flip number off on no yes syn keyword cppOperator oct right setfill setw "------------------------------------------------------------------------------ " Less common C data typedefs "------------------------------------------------------------------------------ syn keyword cType my_data_t "------------------------------------------------------------------------------ " Our slough of macros "------------------------------------------------------------------------------ syn keyword cConstant xxxxxxx syn keyword cDefine CPTR syn keyword cDefine CSTR syn keyword cDefine OPTR syn keyword cDefine SPTR syn keyword cDefine STR syn keyword cDefine V syn keyword cDefine SCUZZGOZIO "------------------------------------------------------------------------------ " cpp.vim "------------------------------------------------------------------------------ " vim: ts=3 sw=3 et ft=vim "------------------------------------------------------------------------------ ================================================ FILE: contrib/vim-syntax/meson.vim ================================================ " Vim syntax file " Language: Meson " License: VIM License " Maintainer: Nirbheek Chauhan " Last Change: 2019 Oct 18 " Credits: Zvezdan Petkovic " Neil Schemenauer " Dmitry Vasiliev " " This version is copied and edited from python.vim " It's very basic, and doesn't do many things I'd like it to " For instance, it should show errors for syntax that is valid in " Python but not in Meson. " " Optional highlighting can be controlled using these variables. " " let meson_space_error_highlight = 1 " " For version 5.x: Clear all syntax items. " For version 6.x: Quit when a syntax file was already loaded. if version < 600 syntax clear elseif exists("b:current_syntax") finish endif " We need nocompatible mode in order to continue lines with backslashes. " Original setting will be restored. let s:cpo_save = &cpo set cpo&vim " http://mesonbuild.com/Syntax.html syn keyword mesonConditional elif else if endif syn keyword mesonRepeat foreach endforeach syn keyword mesonOperator and not or syn match mesonComment "#.*$" contains=mesonTodo,@Spell syn keyword mesonTodo FIXME NOTE NOTES TODO XXX contained " Strings can either be single quoted or triple counted across multiple lines, " but always with a ' syn region mesonString \ start="\z('\)" end="\z1" skip="\\\\\|\\\z1" \ contains=mesonEscape,@Spell syn region mesonString \ start="\z('''\)" end="\z1" keepend \ contains=mesonEscape,mesonSpaceError,@Spell syn match mesonEscape "\\[abfnrtv'\\]" contained syn match mesonEscape "\\\o\{1,3}" contained syn match mesonEscape "\\x\x\{2}" contained syn match mesonEscape "\%(\\u\x\{4}\|\\U\x\{8}\)" contained " Meson allows case-insensitive Unicode IDs: http://www.unicode.org/charts/ syn match mesonEscape "\\N{\a\+\%(\s\a\+\)*}" contained syn match mesonEscape "\\$" " Meson only supports integer numbers " http://mesonbuild.com/Syntax.html#numbers syn match mesonNumber "\<\d\+\>" " booleans syn keyword mesonConstant false true " Built-in functions syn keyword mesonBuiltin \ add_global_arguments \ add_global_link_arguments \ add_languages \ add_project_arguments \ add_project_link_arguments \ add_test_setup \ alias_target \ assert \ benchmark \ both_libraries \ build_machine \ build_target \ configuration_data \ configure_file \ custom_target \ declare_dependency \ dependency \ disabler \ environment \ error \ executable \ files \ find_library \ find_program \ generator \ get_option \ get_variable \ gettext \ host_machine \ import \ include_directories \ install_data \ install_headers \ install_man \ install_subdir \ is_disabler \ is_variable \ jar \ join_paths \ library \ meson \ message \ option \ project \ run_command \ run_target \ set_variable \ shared_library \ shared_module \ static_library \ subdir \ subdir_done \ subproject \ target_machine \ test \ vcs_tag \ warning if exists("meson_space_error_highlight") " trailing whitespace syn match mesonSpaceError display excludenl "\s\+$" " mixed tabs and spaces syn match mesonSpaceError display " \+\t" syn match mesonSpaceError display "\t\+ " endif if version >= 508 || !exists("did_meson_syn_inits") if version <= 508 let did_meson_syn_inits = 1 command -nargs=+ HiLink hi link else command -nargs=+ HiLink hi def link endif " The default highlight links. Can be overridden later. HiLink mesonStatement Statement HiLink mesonConditional Conditional HiLink mesonRepeat Repeat HiLink mesonOperator Operator HiLink mesonComment Comment HiLink mesonTodo Todo HiLink mesonString String HiLink mesonEscape Special HiLink mesonNumber Number HiLink mesonBuiltin Function HiLink mesonConstant Number if exists("meson_space_error_highlight") HiLink mesonSpaceError Error endif delcommand HiLink endif let b:current_syntax = "meson" let &cpo = s:cpo_save unlet s:cpo_save " vim:set sw=2 sts=2 ts=8 noet: ================================================ FILE: contrib/vim-syntax/syncolor.vim ================================================ "****************************************************************************** " syncolor.vim "------------------------------------------------------------------------------ " " Language: N/A " Maintainer: Chris Ahlstrom " Last Change: 08/04/2006-03/05/2024 " Project: XPCC library project " Usage: " " Vim syntax-color add-on file for c.vim and cpp.vim add-ons " " Do a ":set runtimepath" command in vim to see what it has in it. " The first entry is usually "~/.vim", so create that directory, then " add an "after" and "syntax" directory, so that you end up with this " directory: " " ~/.vim/after/syntax " " Then copy the present file (syncolor.vim) to " " ~/.vim/after/syntax/syncolor.vim " " Verify that your code now highlights comments in dark cyan, not bright " cyan. " " I think I prefer light green to light red for XPC C/C++ data types. " Let's try blue though " "------------------------------------------------------------------------------ if &background == "dark" highlight Comment cterm=NONE term=NONE ctermfg=DarkCyan highlight XPCC term=bold cterm=bold ctermfg=LightBlue ctermbg=NONE gui=NONE guifg=Orange guibg=NONE highlight tjspkeyword term=bold cterm=bold ctermfg=LightGreen ctermbg=NONE gui=NONE guifg=Orange guibg=NONE highlight todo term=standout cterm=NONE ctermfg=DarkGreen ctermbg=NONE gui=NONE guifg=DarkGreen guibg=Yellow else highlight Comment cterm=NONE term=NONE ctermfg=DarkBlue highlight XPCC term=bold cterm=bold ctermfg=DarkBlue ctermbg=NONE gui=NONE guifg=SlateBlue guibg=NONE highlight tjspkeyword term=bold cterm=bold ctermfg=DarkGreen ctermbg=NONE gui=NONE guifg=SlateBlue guibg=NONE highlight todo term=standout cterm=NONE ctermfg=DarkGreen ctermbg=NONE gui=NONE guifg=DarkGreen guibg=Yellow endif highlight ERR term=bold cterm=bold ctermfg=Red ctermbg=NONE gui=NONE guifg=Red guibg=NONE "------------------------------------------------------------------------------ " syncolor.vim "------------------------------------------------------------------------------ " vim: ts=3 sw=3 et ft=vim "------------------------------------------------------------------------------ ================================================ FILE: contrib/vim.rc ================================================ "****************************************************************************** " vim.rc (.vimrc) "------------------------------------------------------------------------------ " " Language: C " Maintainer: Chris Ahlstrom " Last Change: 2006-08-04 to 2025-03-22 " Project: Any " License: None. Use it in any manner whatsoever, and don't blame me. " " Setting up for neovim: " " mkdir -p ${XDG_CONFIG_HOME:=$HOME/.config} " ln -s ~/.vim $XDG_CONFIG_HOME/nvim " ln -s ~/.vimrc $XDG_CONFIG_HOME/nvim/init.vim " "------------------------------------------------------------------------------ :set nocompatible :set autoindent :set smartindent " Try turning this off for awhile: :set cindent :set formatoptions=tcroql :set nohlsearch :set expandtab :set noic :set foldmethod=manual :set nofoldenable :set ts=4 :set wm=3 :set sw=4 :set noerrorbells :set novisualbell :set vb t_vb= :set modeline :set equalprg=astyle :filetype detect :syntax on :set background=dark :set mps+=<:> :let c_gnu=1 :set makeef=err.t " 'makeef' file is used with the ":make" command :set autowrite :let loaded_matchparen=1 " Inhibit loading of pi_paren plugin. (:NoMatchParen) " :match Ignore /\r$/ " Don't show those nasty ^M's " :let c_minlines=200 " Comment out if you think it hurts performance :let c_space_errors=1 " Comment out if you think it hurts your eyes " Function-key mappings " " F1: turn off annoying accidental invoking of help mode " F2: quit all files " F3: Like F10, for use in roxterm, which won't disable F10 " F4: Recode rest of document to UTF-8 " F5: Turn off pasting mode " F6: Turn on pasting mode to avoid auto-indent disasters " F7: Write the file " F8: Same as F11 " F9: go to previous file in list (like :N) " F10: go to next file in list (like :n) " F11: toggle-key for light versus dark backgrounds " F12: Refresh syntax high-lighting (e.g. in long Doxygen documents) :imap :map :map :qa :map :n :map :.,$!recode utf8 :map :set nopaste :map :set paste :map :w :map :%s/[\x91\x92]/'/g :%s/[\x93\x94]/"/g :map :N :map :n :map :let &background = ( &background == "dark" ? "light" : "dark" ) :map :syntax sync fromstart " Useful to avoid using the Fn key on small keyboards. :map :map " Shift function-key mappings :map :set fileformat=dos :map :set ft=cpp :map :n :map :let &background = ( &background == "dark" ? "light" : "dark" ) " Settings only for HTML autocmd BufRead,BufNewFile *.html setlocal tabstop=3 shiftwidth=3 softtabstop=3 " Acts like plain F5!: :map :set fileformat=dos :let $PAGER='' "------------------------------------------------------------------------------ " vim.rc (.vimrc) "------------------------------------------------------------------------------ " vim: ts=3 sw=3 et ft=vim "------------------------------------------------------------------------------ ================================================ FILE: data/Makefile.am ================================================ #****************************************************************************** # Makefile.am (seq66/data) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library seq66/data # \author Chris Ahlstrom # \date 2018-01-02 # \updates 2023-08-28 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the project's # seq66/data directory. These are files we want to copy to # /usr/share/seq66 upon installation. # # ca 2022-01-24 Fixes for issue #45, missed a couple DESTDIR usages. # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ # # Note that we have to add the appropriate wildcards to ensure these files # get distributed. # #------------------------------------------------------------------------------ EXTRA_DIST = readme.* license.* linux/*.* midi/*.* midi/FM/*.* midi/PSS-790/*.* samples/*.* win/*.* share/* #***************************************************************************** # Macros #----------------------------------------------------------------------------- # # This section is recommended by: # # http://olympus.het.brown.edu/cgi-bin/info2www?(gettext)Makefile # #----------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ # # Common prefix for installation directories. This directory must exist when # you start the install. # # prefix = /usr/local # datarootdir = $(prefix)/share # datadir = $(datarootdir) # # Where to put the Info files. # # infodir = $(datarootdir)/info # #------------------------------------------------------------------------------ prefix = @prefix@ datadir = @datadir@ datarootdir = @datarootdir@ pkgdatadir = @pkgdatadir@ seq66datadir = @seq66datadir@ seq66docdir = @seq66docdir@ datafolder = $(DESTDIR)$(seq66datadir) docfolder = $(DESTDIR)$(seq66docdir) applicfolder = $(DESTDIR)$(prefix)/share/applications #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ #------------------------------------------------------------------------------ # install-data files #------------------------------------------------------------------------------ # # seq66dir = $(datarootdir)/seq66 # seq66_DATA = GM_PSS-790_Multi.ini # # Instead we use variables defined in configure.ac for now. Also, the # desktop and icon handling below does not work. # #------------------------------------------------------------------------------ # # desktopdir = $(datadir)/applications # desktop_in_files = share/applications/seq66.desktop.in # desktop_DATA = $(desktop_in_files:.desktop.in=.desktop) # # icondir = $(datadir)/icons/hicolor/128x128/apps # icon_DATA = $(top_srcdir)/data/share/icons/hicolor/128x128/apps/qseq66.png # icondir = $(datadir)/icons/hicolor/ # icon_DATA = $(top_srcdir)/data/share/icons/hicolor/ # #------------------------------------------------------------------------------ #****************************************************************************** # Installing documentation and other "data" files. #------------------------------------------------------------------------------ # # We need to add an install-data-hook to copy the generated # documentation directories to the destination directory. The normal # method doesn't work because /usr/bin/install will only install files, # and automake doesn't give it the switch needed to install directories. # # Also, since we don't always build the documentation, we copied the # commands from doc/Makefile.am to here. # #------------------------------------------------------------------------------ install-data-local: @echo "Copying Seq66 data to $(datafolder) ..." mkdir -p $(datafolder) mkdir -p $(datafolder)/icons mkdir -p $(datafolder)/linux mkdir -p $(datafolder)/midi mkdir -p $(datafolder)/pixmaps mkdir -p $(datafolder)/samples mkdir -p $(datafolder)/seq66cli mkdir -p $(datafolder)/win mkdir -p $(datafolder)/wrk mkdir -p $(applicfolder) cp -r -p $(top_builddir)/data/readme.* $(datafolder)/ cp -r -p $(top_builddir)/data/license.* $(datafolder)/ cp -r -p $(top_builddir)/data/linux/* $(datafolder)/linux/ cp -r -p $(top_builddir)/data/midi/* $(datafolder)/midi/ cp -r -p $(top_builddir)/data/pixmaps/* $(datafolder)/pixmaps/ cp -r -p $(top_builddir)/data/samples/* $(datafolder)/samples/ cp -r -p $(top_builddir)/data/seq66cli/* $(datafolder)/seq66cli/ cp -r -p $(top_builddir)/data/win/* $(datafolder)/win/ cp -r -p $(top_builddir)/data/wrk/* $(datafolder)/wrk/ cp -p $(top_srcdir)/data/share/applications/seq66.desktop $(applicfolder) cp -r -p $(top_srcdir)/data/share/icons/* $(datafolder)/icons/ @echo "Copying Seq66 documentation to $(docfolder)..." mkdir -p $(docfolder) mkdir -p $(docfolder)/tutorial mkdir -p $(docfolder)/info cp -r -p $(top_srcdir)/data/share/doc/*.pdf $(docfolder) cp -r -p $(top_srcdir)/data/share/doc/*.ods $(docfolder) cp -r -p $(top_srcdir)/data/share/doc/tutorial/* $(docfolder)/tutorial cp -r -p $(top_srcdir)/data/share/doc/info/* $(docfolder)/info #****************************************************************************** # uninstall-hook #------------------------------------------------------------------------------ # # Also need to uninstall icons, desktop file, include files. The "icons" # directory has the "hicolor" subdirectory. # #------------------------------------------------------------------------------ uninstall-hook: -rm -rf $(datafolder) -rm -rf $(docfolder) -rm -f $(datafolder)/applications/seq66.desktop -find $(datafolder)/icons/ -iname "*seq66*" -exec rm -f '{}' \; #****************************************************************************** # Makefile.am (seq66/data) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 noet ft=automake #------------------------------------------------------------------------------ ================================================ FILE: data/license.text ================================================ Seq66 Licensing 0.99.24 Chris Ahlstrom 2015-09-10 to 2026-05-01 Also see the new LICENSE.* files in the root of the source-code directory. Applications: The Seq66 application license is either the GNU GPLv2 or the GNU GPLv3. Generally, our projects use the latter license, while projects we have extended may specify the former license. Copyright (C) 2015-2026 by Chris Ahlstrom 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 3 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. The text of the GNU GPL version 3 license can also be found here: https://www.gnu.org/licenses/gpl.txt Libraries: The Seq66 library license is the GNU LGPLv3. For each library (libseq66, libsessions, seq_portmidi, seq_qt5, and seq_rtmidi): 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 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 General Lesser 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 address noted above. The text of the GNU LGPL version 3 license can also be found here: https://www.gnu.org/licenses/lgpl.txt Documentation: The Seq66 documentation license is the GNU FDLv1.3. This documentation is free documentation; you can redistribute it and/or modify it under the terms of the GNU Free Documentation License as published by the Free Software Foundation; either version 1.3 of the License, or (at your option) any later version. This documentation 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 Free Documentation License for more details. You should have received a copy of the GNU Free Documentation License along with this documentation; if not, write to the address noted above. The text of the GNU FDL version 1.3 license can also be found here: https://www.gnu.org/licenses/fdl.txt # vim: ts=3 sw=3 wm=3 et ft=sh fileformat=dos ================================================ FILE: data/linux/alsa_ports.rc ================================================ # Seq66 0.99.3 main ('rc') configuration file # # /home/ahlstrom/.config/seq66/qseq66.rc # Written 2023-04-18 13:49:08 [midi-input] 5 # number of input MIDI buses 0 1 "[0] 0:1 system:ALSA Announce" 1 1 "[1] 14:0 Midi Through Port-0" 2 1 "[2] 20:0 nanoKEY2 nanoKEY2 _ CTRL" 3 1 "[3] 40:0 Launchpad Mini MIDI 1" 4 1 "[4] 44:0 E-MU XMidi1X1 Tab Out" # This table is similar to the [midi-clock-map] section. [midi-input-map] 1 # map is active 0 1 "ALSA Announce" 1 1 "Midi Through Port-0" 2 1 "nanoKEY2 nanoKEY2 _ CTRL" 3 1 "Launchpad Mini MIDI 1" 4 1 "E-MU XMidi1X1 Tab Out" [midi-clock] 10 # number of MIDI clocks (output buses) 0 0 "[0] 14:0 Midi Through Port-0" 1 0 "[1] 20:0 nanoKEY2 nanoKEY2 _ CTRL" 2 0 "[2] 40:0 Launchpad Mini MIDI 1" 3 0 "[3] 44:0 E-MU XMidi1X1 Tab Out" 4 0 "[4] 128:0 yoshimi:input" 5 0 "[5] 129:0 FLUID Synth (2531):Synth input port (2531:0)" 6 0 "[6] 130:0 TiMidity port 0" 7 0 "[7] 130:1 TiMidity port 1" 8 0 "[8] 130:2 TiMidity port 2" 9 0 "[9] 130:3 TiMidity port 3" [midi-clock-map] 1 # map is active 0 0 "Midi Through Port-0" 1 0 "nanoKEY2 nanoKEY2 _ CTRL" 2 0 "Launchpad Mini MIDI 1" 3 0 "E-MU XMidi1X1 Tab Out" 4 0 "yoshimi:input" 5 0 "FLUID Synth" 6 0 "TiMidity port 0" 7 0 "TiMidity port 1" 8 0 "TiMidity port 2" 9 0 "TiMidity port 3" # End of /home/ahlstrom/.config/seq66/qseq66.rc # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/ca_ports.rc ================================================ # Seq66 0.91.5 (and above) 'rc' configuration file [comment] These maps were constructed by running various synths and then saving the input/clocks maps, and accumulating the synths and ports to build a comprehensive list of the possible ports on our development system. These mapping sections can then be retrofitted (via a text editor) into an existing 'rc' file. [midi-input-map] 1 # map is active 0 "announce" 1 "Midi Through Port-0" 2 "Q25 MIDI 1" 3 "E-MU XMidi1X1 Tab MIDI 1" 4 "Launchpad Mini MIDI 1" 5 "nanoKEY2 MIDI 1" [midi-clock-map] 1 # map is active 0 "Midi Through Port-0" 1 "Q25 MIDI 1" 2 "E-MU XMidi1X1 Tab MIDI 1" 3 "Launchpad Mini MIDI 1" 4 "nanoKEY2 MIDI 1" 5 "FLUID Synth" 6 "TiMidity port 0" 7 "TiMidity port 1" 8 "TiMidity port 2" 9 "TiMidity port 3" 10 "yoshimi:input" # End of /data/linux/ca_ports.rc # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/jack/README ================================================ Installed Scripts Directory for Seq66 Chris Ahlstrom 2021-10-12 to 2025-06-07 This directory contains scripts that can be used with JACK and PulseAudio. jackctl: The latest attempt to script a JACK startup. Recommended. startjack: An older attempt to script a JACK startup. startqjack: Another attempt to add scripting to QJackCtl. pulseaudio: These files can be added to the Scripting setup for QJackCtrl so that PulseAudio output will be redirected to JACK. Study them to see if you want them. Will also be documented at some point in the user manual. jack-post-start.sh jack-post-stop.sh jack-pre-start.sh jack-pre-stop.sh repulse: Restarts the user-land run of PulseAudio. # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: data/linux/jack/jack_portmaps.rc ================================================ # Seq66 0.98.1 main ('rc') configuration file # # jack_portmaps.rc # Written 2021-12-15 12:09:32 # This file is only an EXCERPT. # # This file shows what happens on one of our development systems when # running JACK and the a2jmidid (to export hardware). This doubles the number # of system/hardware I/O ports. Either version (e.g. port 1 and port 5 below) # will work, but we grab the JACK aliases for names that contain "system:" [midi-input] 8 # number of input MIDI busses 0 0 "[0] 0:0 system:midi_capture_1" # Midi-Through 1 0 "[1] 0:1 system:midi_capture_2" # Launchpad-Mini 2 0 "[2] 0:2 system:midi_capture_3" # USB-Midi 3 0 "[3] 0:3 system:midi_capture_4" # nanoKEY2 4 0 "[4] 1:4 a2j:Midi Through [14] (capture): Midi Through Port-0" 5 0 "[5] 1:5 a2j:Launchpad Mini [32] (capture): Launchpad Mini MIDI 1" 6 0 "[6] 1:6 a2j:USB Midi [36] (capture): USB Midi MIDI 1" 7 0 "[7] 1:7 a2j:nanoKEY2 [40] (capture): nanoKEY2 MIDI 1" [midi-input-map] 1 # map is active 0 "midi_capture_1" # Midi-Through 1 "midi_capture_2" # Launchpad-Mini 2 "midi_capture_3" # USB-Midi 3 "midi_capture_4" # nanoKEY2 4 "Midi Through Port-0" 5 "Launchpad Mini MIDI 1" 6 "USB Midi MIDI 1" 7 "nanoKEY2 MIDI 1" [midi-clock] 9 # number of MIDI clocks (output busses) 0 0 "[0] 0:0 seq66:system midi_playback_1" # Midi-Through 1 0 "[1] 0:1 seq66:system midi_playback_2" # Launchpad-Mini 2 0 "[2] 0:2 seq66:system midi_playback_3" # USB-Midi 3 0 "[3] 0:3 seq66:system midi_playback_4" # nanoKEY2 4 0 "[4] 1:4 seq66:a2j Midi Through [14] (playback): Midi Through Port-0" 5 0 "[5] 1:5 seq66:a2j Launchpad Mini [32] (playback): Launchpad Mini MIDI 1" 6 0 "[6] 1:6 seq66:a2j USB Midi [36] (playback): USB Midi MIDI 1" 7 0 "[7] 1:7 seq66:a2j nanoKEY2 [40] (playback): nanoKEY2 MIDI 1" 8 0 "[8] 2:8 seq66:fluidsynth-midi midi_00" [midi-clock-map] 1 # map is active 0 "system midi_playback_1" # Midi-Through 1 "system midi_playback_2" # Launchpad-Mini 2 "system midi_playback_3" # USB-Midi 3 "system midi_playback_4" # nanoKEY2 4 "Midi Through Port-0" 5 "Launchpad Mini MIDI 1" 6 "USB Midi MIDI 1" 7 "nanoKEY2 MIDI 1" 8 "fluidsynth-midi midi_00" # End of jack_portmaps.rc # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/jack/jackctl ================================================ #!/bin/bash # #****************************************************************************** # jackctl #------------------------------------------------------------------------------ ## # \file jackctl # \library Any project # \author Chris Ahlstrom # \date 2022-09-25 # \update 2026-01-10 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # The above is modified by the following to remove even the mild GPL # restrictions: # # Use this script in any manner whatsoever. You don't even need to give # me any credit. However, keep in mind the value of the GPL in keeping # software and its descendant modifications available to the community # for all time. # # See "jack_control --help" for a list of options. The options used here # are: # # -S Configure for short (16-bit) samples first. # -d The driver to use (here, ALSA). The second -d is then an # ALSA option to set the ALSA PCM device. # -R The JACK frame rate (sample rate). # -p Number of frames between JACK process() calls; a power of 2. # -n Number of periods; the minimum is 2; 3 recommended for USB. # -X Use the raw or seq MIDI system. # -D Duplex ports except for -P... # -P Provide only playback ports. # # ALSA parameters partial list: # # device: ALSA device name (type:isset:default:value). # capture: Optionally set port (str:notset:none:none). # playback: Optionally set port (str:notset:none:none). # rate: Set the sample rate (48000). # period: Frames per period (the "cycle", time between process # callback calls (1024). # nperiod: Number of periods (cycles) of latency (2). # midi-driver: ALSA MIDI driver. # # /proc/asound/cards (on our system): # # 0: CODEC USB audio box. # 1: nanoKEY2 Korg keyboard. # 2: HDMI Onboard HDMI. # 3: PCH Intel on-board sound. # 4: NVidia Onboard NVidia card. # 5: Midi A generic USB MIDI cable. # 6: Mini LaunchPad Mini. # # Configuration file: # # The configuration file affected by jack_control is: # # ~/.config/jack/conf.xml # # a2jmidid: # # We ran into an issue on one of our test systems, where Seq66 # would show the MIDI ports with "[0]" prepended to every port # name. Royally screws up port-mapping. We had to add the "-u" # option to force generation of non-unique port names, which # removes the ALSA client ID. # # Session managers: # # Support for raysession and agordejo is provided. We currently # prefer the way agordejo works. # # JCTL_SESSMGR="raysession" # JCTL_SESSOPT="--session-root /home/$USER/.local/share/ray" # #------------------------------------------------------------------------------ JCTL_A2JMIDID="no" JCTL_ALL="no" JCTL_DO_SIMPLE="no" JCTL_DEVICE="hw:CODEC" # see "cat /proc/asound/cards" JCTL_DRIVER="alsa" JCTL_LATENCY="2" JCTL_OPERATION="list" JCTL_PERIOD="128" JCTL_RATE="48000" JCTL_SESSION="no" JCTL_SESSMGR="agordejo" JCTL_SESSOPT="--session-root /home/$USER/.local/share/nsm" JCTL_SYNTH="no" JCTL_SYNTHESIZER="qsynth" JCTL_SYNTHOPT="--midi-driver jack" JCTL_SIMPLE="/usr/bin/jackd -S -d$JCTL_DRIVER -d$JCTL_DEVICE -r$JCTL_RATE -p$JCTL_PERIOD -n$JCTL_LATENCY -Xseq -D -P$JCTL_DEVICE" JCTL_DO_PULSEAUDIO_FIX="no" JCTL_PULSEAUDIO_FIX="pactl set-card-profile alsa_card.usb-Burr-Brown_from_TI_USB_Audio_CODEC-00 off" if [ "$1" == "--help" ] || [ "$1" == "help" ] ; then cat << E_O_F Usage v. 2026-01-10 jackctl [ --start ] [ options ] Start jack/apps with the usual parameters Options: --simple Run jackd via a command-line. Edit JCTL_SIMPLE in this -s script to suit. The command-line here is: $JCTL_SIMPLE Add --a2j if hard-ware devices are not shown. Make sure the desired devices are not locked by pulseaudio. Use the option --pafix --list List the drivers and the ALSA/JACK parameters. [Default] --start Start the JACK server. -st --stop Stop the JACK server. Does not apply to --session. --kill Stop the JACK server and exit jackdbus. -k --session Also start the $JCTL_SESSMGR session manager with options $JCTL_SESSOPT. The --start option is assumed. --agordejo Run the agordejo session manager. --start is assumed. -ag --ray Run the raysession session manager. --start is assumed. --set Set the default values, shown here. Stops, then restarts jackdbus. Edit this script to change the defaults: Driver: $JCTL_DRIVER Device: $JCTL_DEVICE Frame rate: $JCTL_RATE Nperiods: $JCTL_LATENCY Frame period: $JCTL_PERIOD frames A2jmidid: $JCTL_A2JMIDID --period F Change the period of the JACK server, and restart it. --nperiod P Change ALSA period (playback latency, 2 or 3). --rate R Change the "sample rate". --a2j Also start/stop a2jmidid. Options used are --export-hw and -u. The latter is needed to avoid "[0]" in port names on some systems. --synth Start the software synthesizer $JCTL_SYNTHESIZER with option(s) $JCTL_SYNTHOPT. Useful for testing without a session manager. --all Combines --a2j --synth. Use with --start, --stop, but not --session. --help Show this message. Note that some of these settings can be permanently modified by editing this script to one's setup. E_O_F exit 0 else while [ "$1" != "" ] ; do case "$1" in --simple | -s | s | simple) JCTL_DO_SIMPLE="yes" ;; --pafix | pafix) JCTL_DO_PULSEAUDIO_FIX="yes" ;; --list | list) JCTL_OPERATION="list" ;; --set | set) JCTL_OPERATION="setdefaults" ;; --start | start | st | --st) JCTL_OPERATION="start" ;; --stop | stop) JCTL_OPERATION="stop" ;; --kill | kill | k | -k) JCTL_OPERATION="kill" ;; --session | session) JCTL_OPERATION="start" JCTL_SESSION="yes" ;; --ray | ray) JCTL_OPERATION="start" JCTL_SESSION="yes" JCTL_SESSMGR="raysession" JCTL_SESSOPT="--session-root /home/$USER/.local/share/ray" ;; --agordejo | ag | -ag) JCTL_OPERATION="start" JCTL_SESSION="yes" JCTL_SESSMGR="agordejo" JCTL_SESSOPT="--session-root /home/$USER/.local/share/nsm" ;; --session | session) JCTL_SESSION="yes" ;; --synth | synth) JCTL_SYNTH="yes" ;; --rate | rate) shift JCTL_RATE="$1" JCTL_OPERATION="setrate" ;; --period | period) shift JCTL_PERIOD="$1" JCTL_OPERATION="setperiod" ;; --nperiod | nperiod) shift JCTL_LATENCY="$1" ;; --a2j | a2j) JCTL_A2JMIDID="yes" ;; --all | all) JCTL_ALL="yes" JCTL_A2JMIDID="yes" JCTL_SYNTH="yes" ;; *) echo "? Unsupported option; --help for more information" exit 1 ;; esac shift done fi if [ "$JCTL_DO_PULSEAUDIO_FIX" == "yes" ] ; then echo $JCTL_PULSEAUDIO_FIX $JCTL_PULSEAUDIO_FIX JCTL_OPERATION="none" fi if [ "$JCTL_DO_SIMPLE" == "yes" ] ; then echo $JCTL_SIMPLE $JCTL_SIMPLE & JCTL_OPERATION="none" ps ax | grep jackd fi if [ "$JCTL_OPERATION" == "list" ] ; then echo "Available sound sinks:" cat /proc/asound/cards | grep "^[ 0-9][0-9]" echo "Selecting ALSA (seq). Available parameters:" jack_control ds $JCTL_DRIVER jack_control dp | grep "ALSA\|rate:\|period:\|nperiods:" jack_lsp --aliases elif [ "$JCTL_OPERATION" == "stop" ] ; then if [ "$JCTL_SYNTH" == "yes" ] ; then echo "STOPPING $JCTL_SYNTHESIZER" killall $JCTL_SYNTHESIZER fi if [ "$JCTL_A2JMIDID" == "yes" ] ; then echo "STOPPING a2jmidid" killall a2jmidid sleep 1 fi echo "STOPPING JACK" jack_control stop elif [ "$JCTL_OPERATION" == "kill" ] ; then if [ "$JCTL_SYNTH" == "yes" ] ; then killall -9 $JCTL_SYNTHESIZER fi if [ "$JCTL_A2JMIDID" == "yes" ] ; then killall -9 a2jmidid sleep 1 fi jack_control stop jack_control exit killall -9 jackd elif [ "$JCTL_OPERATION" == "start" ] ; then echo "STARTING JACK, setting period to $JCTL_PERIOD" jack_control start sleep 2 jack_control dps period $JCTL_PERIOD if [ "$JCTL_SYNTH" == "yes" ] ; then echo "STARTING $JCTL_SYNTHESIZER $JCTL_SYNTHOPT" $JCTL_SYNTHESIZER $JCTL_SYNTHOPT & fi sleep 2 if [ "$JCTL_A2JMIDID" == "yes" ] ; then echo "STARTING a2jmidid --export-hw -u" a2jmidid --export-hw -u & fi if [ "$JCTL_SESSION" == "yes" ] ; then echo "STARTING $JCTL_SESSMGR $JCTL_SESSOPT" $JCTL_SESSMGR $JCTL_SESSOPT & # redirect output to /dev/null? fi jack_lsp --aliases elif [ "$JCTL_OPERATION" == "setperiod" ] ; then echo "Setting JACK frame count to $JCTL_PERIOD" jack_control stop jack_control dps period $JCTL_PERIOD jack_control start jack_control dp | grep "ALSA\|rate:\|period:\|nperiods:" elif [ "$JCTL_OPERATION" == "setrate" ] ; then echo "Setting JACK frame rate to $JCTL_RATE" jack_control stop jack_control dps rate $JCTL_RATE jack_control start jack_control dp | grep "ALSA\|rate:\|period:\|nperiods:" elif [ "$JCTL_OPERATION" == "setdefaults" ] ; then jack_control stop jack_control ds $JCTL_DRIVER jack_control dps device $JCTL_DEVICE jack_control dps rate $JCTL_RATE jack_control dps nperiods $JCTL_LATENCY jack_control dps period $JCTL_PERIOD jack_control start jack_control dp | grep "ALSA\|rate:\|period:\|nperiods:" fi #****************************************************************************** # jackctl #------------------------------------------------------------------------------ # vim: ts=3 sw=3 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: data/linux/jack/pulseaudio/jack-post-start.sh ================================================ #!/bin/bash # # From # # https://archived.forum.manjaro.org/t/how-to-replace-pulseaudio-with- # jack-jack-and-pulseaudio-together-as-friend/2086 # # For some reason, putting the load-module commands in /etc/pulse/default.pa # does not work. pactl load-module module-jack-sink pactl load-module module-jack-source pacmd set-default-sink jack_out pacmd set-default-source jack_in # vim: ts=3 ================================================ FILE: data/linux/jack/pulseaudio/jack-post-stop.sh ================================================ #!/bin/bash # # From # # https://archived.forum.manjaro.org/t/how-to-replace-pulseaudio-with- # jack-jack-and-pulseaudio-together-as-friend/2086 pacmd suspend false # vim: ts=3 ================================================ FILE: data/linux/jack/pulseaudio/jack-pre-start.sh ================================================ #!/bin/bash # # From # # https://archived.forum.manjaro.org/t/how-to-replace-pulseaudio-with- # jack-jack-and-pulseaudio-together-as-friend/2086 # # Put these scripts into the proper slots of Qjackctl's Scripting settings. # # Go to the Misc tab and check the the "Start JACK audio server on application # startup", "Enable system tray icon", "Start minimized to system tray", # "Enable D-Bus interface". # # Leave the other settings with their default states. # # Start JACK by pressing the Start button on the main interface of QJackCtl, # Click the Connect button to play with connections. To save your connection # and start it up after reboot, modify it in the Patchbay and make sure # Activate Patchbay persistence is checked in the QJackCtl Setup. pacmd suspend true # vim: ts=3 ================================================ FILE: data/linux/jack/pulseaudio/jack-pre-stop.sh ================================================ #!/bin/bash # # From # # https://archived.forum.manjaro.org/t/how-to-replace-pulseaudio-with- # jack-jack-and-pulseaudio-together-as-friend/2086 # PULSESINKID=$(pactl list | grep -B 1 "Name: module-jack-sink" | grep Module | sed 's/[^0-9]//g') PULSESOURCEID=$(pactl list | grep -B 1 "Name: module-jack-source" | grep Module | sed 's/[^0-9]//g') pactl unload-module $PULSESINKID # module-jack-sink pactl unload-module $PULSESOURCEID # module-jack-source sleep 5 # vim: ts=3 ================================================ FILE: data/linux/jack/pulseaudio/repulse ================================================ #!/bin/bash # # Restarts pulseaudio (Debian-based systems use the --user mode). echo "Restarting PulseAudio. This action takes too many seconds...." systemctl --user restart pulseaudio.service # vim: ts=3 ================================================ FILE: data/linux/jack/startjack ================================================ #!/bin/bash # #****************************************************************************** # startjack #------------------------------------------------------------------------------ ## # \file startjack # \library Home/Audio # \author Chris Ahlstrom # \date 2020-12-08 # \update 2025-02-02 # \version $Revision$ # # This script provides a way to make a fast JACK setup on Linux. See: # # https://wiki.archlinux.org/index.php/JACK_Audio_Connection_Kit # #------------------------ # REPLACED BY jackctl! #------------------------ # # Requires the python-dbus package to be installed. The HWPORT values # available are found in /proc/asound/cards: PCH, HDMI, NVidia, CODEC, etc. # The "dps" rate can be 44100, 48000, and higher. The "nperiods" value # should be 2 for motherboard, PCI, PCI-X audio, and 3 for USB audio. # # However, we have had weird input issues, including missing input and # extremely slow response with this setup, but not with qjackctrl running # with the macroed settings below. Still experimenting. # # With the current settings, latency = 21.3 msec. # # Also note to be sure to set qjackctl to use the "seq" MIDI driver; the # "raw" driver seems to cause a lot of issues with the a2j bridge. # #------------------------------------------------------------------------------ #****************************************************************************** # Provide a sane environment, just in case it is needed. #------------------------------------------------------------------------------ LANG=C export LANG export STARTJACK_DATE="2021-09-02" #****************************************************************************** # Default simple parameter values #------------------------------------------------------------------------------ BLUEDEV="hci0" BLUEMAC="20:05:13:08:06:9B" BLUEPORT="bluealsa" BLUEPROFILE="a2dp" BLUETOOTH="no" DO_BASIC="no" DO_FULL="yes" DO_KILL="no" DO_QJACK_CONTROL="no" DO_A2J_MIDID="yes" HWPORT="PCH" JDRIVER="alsa" MDRIVER=seq # raw can cause real issues with a2jmidid NPERIODS=4 # was 2, 3 recommended for USB devices PFRAMES=256 # was 64 SAMPLERATE=48000 SLEEPTIME15=15 SLEEPTIME2=2 SLEEPTIME=10 #****************************************************************************** # Default option + simple parameter values #------------------------------------------------------------------------------ #****************************************************************************** # ALSA backend options # # The long forms give issues. # #------------------------------------------------------------------------------ # BACKDEV="" # defaults to "--device hw:0" (modules.conf) # BACKEND="--driver=$JDRIVER" # pretty much always alsa # REALTIME="--realtime" # versus --no-realtime # MSYSTEM="--midi=$MDRIVER" # -X seq or -X raw, raw is bad # RATE="--rate=$SAMPLERATE" # sampling rate # PERIOD="--period=$PFRAMES" # frames between calls (latency = period / rate) # PERIODS="--nperiods=$NPERIODS" # latency = np * period / rate; np > 2 # PORTMAX="--port-max=64" # JACK default is 256 # DUPLEX="--duplex" # -D, both capture and playback ports # CAPTURE="--capture=hw:$HWPORT" # e.g. hw:PCH (or --duplex for both) # PLAYBACK="--playback=hw:$HWPORT" # e.g. hw:PCH BACKDEV="" # defaults to "--device hw:0" (modules.conf) BACKEND="-d $JDRIVER" # pretty much always alsa REALTIME="-Rv" # versus --no-realtime; v = "verbose" MSYSTEM="-X $MDRIVER" # -X seq or -X raw, raw is bad RATE="-r $SAMPLERATE" # sampling rate PERIOD="-p $PFRAMES" # frames between calls (latency = period / rate) PERIODS="-n $NPERIODS" # latency = np * period / rate; np > 2 PORTMAX="-port-max 64" # JACK default is 256 DUPLEX="-D" # -D, both capture and playback ports CAPTURE="-C hw:$HWPORT" # e.g. hw:PCH (or --duplex for both) PLAYBACK="-P hw:$HWPORT" # e.g. hw:PCH DAEMON="/usr/bin/jackd" OPTIONS="$BACKEND $RATE $PERIOD $PERIODS $MSYSTEM $DUPLEX $CAPTURE $PLAYBACK" if [ "$1" == "--help" ] ; then cat << E_O_F Usage: startjack [ options ] ($STARTJACK_DATE) Starts JACK, a2jmidid, and qjackctrl. Also provides for stopping (killing) them. The options are: --basic Just start jack, a2jmidid, and qjackctl. --qjack Use qjackctl to start/stop jack, and use a2jmidid. Assumes that qjackctl is set up properly. --full Use jack_control and a2j_control to start things. The default. --kill Stop JACK instead of starting it. If using the --qjack option, add the following calls of this script to start and stop JACK in your QJackCtl setup: startjack --qjack startjack --qjack --kill However, in this case it is preferable to use the "startqjack" script, as it is much simpler. Read that script for more information on setup. E_O_F exit 1 fi if [ $# -ge 1 ] ; then while [ "$1" != "" ] ; do case "$1" in --blue | blue) BLUETOOTH="yes" ;; --basic | basic) DO_BASIC="yes" DO_FULL="no" ;; --qjack | qjack) DO_QJACK_CONTROL="yes" DO_BASIC="yes" DO_FULL="no" ;; --full | full) DO_BASIC="no" DO_FULL="yes" ;; --kill | kill) DO_KILL="yes" ;; *) echo "? Unsupported option; --help for more information" exit $EXIT_ERROR_NO_SUCH_OPTION ;; esac shift done fi if [ "$DO_QJACK_CTL" == "yes" ] ; then if [ "$DO_KILL" == "yes" ] ; then if [ "$DO_A2J_MIDID" == "yes"] ; then killall a2jmidid fi else echo "Waiting $SLEEPTIME seconds for qjackctl to settle..." sleep $SLEEPTIME if [ "$DO_A2J_MIDID" == "yes"] ; then a2jmidid --export-hw & echo "Waiting $SLEEPTIME15 seconds for a2jmidid to settle..." sleep $SLEEPTIME15 fi echo "Ready to use QJackCtl." fi exit 0 fi if [ "$DO_KILL" == "yes" ] ; then if [ "$DO_BASIC" == "yes" ] ; then if [ "$DO_A2J_MIDID" == "yes"] ; then killall a2jmidid fi killall jackd else if [ "$DO_A2J_MIDID" == "yes"] ; then a2j_control --stop a2j_control --exit fi jack_control exit fi killall qjackctl elif [ "$DO_BASIC" == "yes" ] ; then echo "Starting jackd..." # /usr/bin/jackd -dalsa -r 48000 -p 256 -n 4 -X seq -D -C hw:PCH -P hw:PCH & echo "$DAEMON $OPTIONS" $DAEMON $OPTIONS & echo "Waiting $SLEEPTIME seconds for jackd to settle..." sleep $SLEEPTIME if [ "$DO_A2J_MIDID" == "yes"] ; then echo "Starting ALSA-to-JACK bridge: 'a2jmidid --export-hw'..." a2jmidid --export-hw & echo "Waiting $SLEEPTIME15 seconds for a2jmidid to settle..." sleep $SLEEPTIME15 fi echo "Starting QJackCtl/JACK: 'qjackctl --start'..." qjackctl & sleep $SLEEPTIME2 elif [ "$DO_BASIC" == "no" ] ; then jack_control start jack_control ds $JDRIVER if [ "$BLUETOOTH" == "yes" ] ; then echo "Audio using $BLUEPORT..." echo "jack_control dps device $BLUEPORT WILL NOT WORK" else echo "Audio using hw:$HWPORT..." jack_control dps device hw:$HWPORT fi jack_control dps rate $SAMPLERATE jack_control dps nperiods $NPERIODS jack_control dps period $PFRAMES echo "Waiting $SLEEPTIME seconds for jack_control to settle..." sleep $SLEEPTIME if [ "$DO_A2J_MIDID" == "yes"] ; then a2j_control --ehw a2j_control --start echo "Waiting $SLEEPTIME seconds for a2j_control to settle..." sleep $SLEEPTIME fi if [ "$BLUETOOTH" == "yes" ] ; then echo "Starting alsa_out..." alsa_out -d bluealsa:HCI=$BLUEDEV,DEV=$BLUEMAC,PROFILE=$BLUEPROFILE else echo "Starting qjackctl..." qjackctl & fi fi #------------------------------------------------------------------------------ # vim: ft=sh #------------------------------------------------------------------------------ ================================================ FILE: data/linux/jack/startqjack ================================================ #!/bin/bash # #****************************************************************************** # startqjack #------------------------------------------------------------------------------ ## # \file startqjack and stopqjack # \library Home/Audio # \author Chris Ahlstrom # \date 2020-12-08 # \update 2025-02-02 # \version $Revision$ # # This script provides a way to make a fast JACK setup on Linux. It is a much # simpler alternative to our startjack script. # #------------------------ # REPLACED BY jackctl! #------------------------ # # https://wiki.archlinux.org/index.php/JACK_Audio_Connection_Kit # # Installation Steps: # # 0. Make sure the following are installed: # # a. jack2 # b. Jack D-Bus # c. a2j_control and a2jmidid # # 1. Pick where to install this file and copy it there: # # a. ~/bin # b. /usr/bin # c. /usr/local/bin # # 2. In that directory, do: # # # ln -s startqjack stopqjack # # Or copy and rename if desired. # # 3. Run qjackctl and make the following settings in the Setup / Settings tab. # Consider removing ~/.config/rncbc.org/QjackCtl.conf first, or if # difficulties occur. # # a. Driver: alsa # b. Realtime: checked # c. Sample Rate: 48000 # d. Frames/Period: 256 # e. Periods/Buffer: 4 # f. MIDI Driver: seq # g. Use server synchronous mode: checked # # 4. Setup / Options: # # a. Execute script after Startup: startqjack # b. Execute script on Shutdown: stopqjack # # 5. Setup / Misc: # # a. Start JACK audio server on ...: checked (startup) # b. Enable ALSA Sequencer support: checked # c. Enable D-Bus interface: checked # d. Enable JACK D-Bus interface: unchecked # e. Stop JACK audio server on ...: checked (exit) # f. Single application instance: checked # #------------------------------------------------------------------------------ #****************************************************************************** # Provide a sane environment, just in case it is needed. #------------------------------------------------------------------------------ LANG=C export LANG STARTJACK_DATE="2021-09-01" SLEEPTIME=10 DO_KILL="no" if [ "$1" == "--help" ] ; then cat << E_O_F Usage: startqjack | stopqjack ($STARTJACK_DATE) Starts JACK, a2jmidid, and qjackctrl. provides for stopping (killing) them. Uses qjackctl to start/stop jack, and uses a2j_control and a2jmidid. Assumes that qjackctl is set up properly as noted in the comments for this script. stopqjack Stop JACK instead of starting it. (A soft-link to startqjack) Add the following calls of this script to after the start and at stop JACK in your QJackCtl setup: startqjack stopqjack E_O_F exit 1 fi SCRIPTNAME="$(basename $0)" echo $SCRIPTNAME if [ "$SCRIPTNAME" == "stopqjack" ] ; then DO_KILL="yes" fi if [ "$DO_KILL" == "yes" ] ; then killall a2jmidid killall qjackctl else echo "Starting a2j bridge..." a2j_control --ehw a2j_control --start echo "Waiting $SLEEPTIME seconds for a2j_control to settle..." sleep $SLEEPTIME fi #------------------------------------------------------------------------------ # vim: ft=sh #------------------------------------------------------------------------------ ================================================ FILE: data/linux/jack_ports.rc ================================================ # Seq66 0.99.3 main ('rc') configuration file # # /home/ahlstrom/.config/seq66/qseq66.rc # Written 2023-04-18 14:15:12 [midi-input] 4 # number of input MIDI buses 0 1 "[0] 0:0 system:midi_capture_1" # 'Midi Through' 1 1 "[1] 0:1 system:midi_capture_2" # 'nanoKEY2' 2 1 "[2] 0:2 system:midi_capture_3" # 'Launchpad Mini' 3 1 "[3] 0:3 system:midi_capture_4" # 'E MU XMidi1X1 Tab' [midi-input-map] 1 # map is active 0 1 "Midi Through" 1 1 "nanoKEY2" 2 1 "Launchpad Mini" 3 1 "E MU XMidi1X1 Tab" [midi-clock] 6 # number of MIDI clocks (output buses) 0 0 "[0] 0:0 system:midi_playback_1" # 'Midi Through' 1 0 "[1] 0:1 system:midi_playback_2" # 'nanoKEY2' 2 0 "[2] 0:2 system:midi_playback_3" # 'Launchpad Mini' 3 0 "[3] 0:3 system:midi_playback_4" # 'E MU XMidi1X1 Tab' 4 0 "[4] 1:4 yoshimi:midi in" 5 0 "[5] 2:5 qsynth:midi_00" [midi-clock-map] 1 # map is active 0 0 "Midi Through" 1 0 "nanoKEY2" 2 0 "Launchpad Mini" 3 0 "E MU XMidi1X1 Tab" 4 0 "yoshimi:midi in" 5 0 "qsynth:midi_00" # End of /home/ahlstrom/.config/seq66/qseq66.rc # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/macros-APC40-mk2.ctrl ================================================ # This file provides samples, explanations, and ideas for the Akai APC40 Mk2. # # Copy the [macro-control-out] section and paste it into your # qseq66.ctrl or other 'ctrl' file. [macro-control-out] footer = 0xF7 header = 0xf0 0x47 0x7F 0x29 0x60 0x00 0x04 0x00 0xVH 0xVM 0xVF $footer # command = $header $command-byte # get-current-layout = $header 0x00 $footer # live-mode = $header 0x0e 0x00 $footer # programmer-mode = $header 0x0e 0x01 $footer # drum-layout = $header 0x02 # reset = $header 0x00 # shutdown = $header 0x00 # startup = $x-y-layout # x-y-layout = $header 0x01 # Some ideas follow: [macro-control-out-ex] # Akai-Specific Header from PC to Controller: # # Format of Outbound Message Type 0 (Introduction Message): # # 0xF0 MIDI SysEx start # 0x47 Manufacturers ID # 0x7F System Exclusive Device ID # 0x29 Product model ID # 0x60 Message type identifier # 0x00 Number of data bytes (most significant) # 0x04 Number of data bytes (least significant) # 0x4M Application/configuration ID (M = 0, 1, or 2) # 0xVH PC Application software version major # 0xVM PC Application software version minor # 0xVF PC Application software bug-fix level # 0xF7 MIDI SysEx terminator # # Message type identifiers: # # 0x60 Introduction message # 0x61 Introduction message response # # Modes: # # 0 0x40 Generic mode # 1 0x41 Ableton Live mode # 2 0x42 Alternate Ableton Live mode # # APC40 Mk2 Response from Introduction Message: # # 0xF0 MIDI SysEx start # 0x47 Manufacturers ID # 0x7F System Exclusive Device ID # 0x29 Product model ID # 0x61 Message type identifier # 0x00 Number of data bytes (most significant) # 0x04 Number of data bytes (least significant) [0x09 ???] # 0xS1 to 0xS9 Current values of Knobs #1 to #9 # 0xF7 MIDI SysEx terminator # # Format of Outbound Message Type 1 (LEDs): # # Note On: 0x9T LED# state (T is used for the track strips) # Note Off: 0x8T LED# ignored (turns the LED off) # # The state is for color, OFF, ON, blinking, etc. See the manual for the # mapping of note numbers to LEDs and for color mappings. # # Velocity 0 = Off, 1 to 127 = On. # # Format of Outbound Message Type 2 (Controller Value Update): # # Controller: 0xBT CC# value (T is used for the track strips) # # See the manual for the mapping of CC numbers to control knobs, sliders, # rings, footswitch..., and values for LED states # # Inbound Messages (from APC240 to computer): # # Type Note 1 (Note-On and Note-Off): # # These messages work with two-state controls (buttons), with a Note-On # when depressed, and Note-Off when release. The channel value is used # for the track strips. The ID (note) number determines the control # surface object, and the control value is either non-zero (Note On) or # ignored (Note Off). See the manual for the ID-to-controller mappings. # # Note On: 0x9T ID# 0x7F # Note Off: 0x8T LED# ignored (turns the LED off) # # Type CC 1 (Absolute Controller Messages): # # Controller: 0xBT ID# value # # See the manual for the ID-to-fader/knob mappings. # # Type CC 2 (Relative Controller Messages): # # Reports changes in value. # # Device Inquiry Format: # # 0xF0 MIDI SysEx start # 0x7E Non-Realtime message # 0xNN MiDI channel 0x00-0x0F or 0x7F for Omni inquiry # 0x06 Inquiry message # 0x01 Inquiry request # 0xF7 MIDI SysEx terminator # # APC40 Mk2 Response: # # 0xF0 MIDI SysEx start # 0x7E Non-Realtime message # 0xNN Common MiDI channel setting 0x00-0x0F # 0x06 Inquiry message # 0x02 Inquiry response # 0x47 Manufacturer ID # 0x29 Product model ID # 0x00 Number of data bytes (most significant) # 0x19 Number of data bytes (least significant) # 0xV1 Software version major (most significant) # 0xV2 Software version major (least significant) # 0xV3 Software version minor (most significant) # 0xV4 Software version minor (least significant) # 0xID SysEx device ID # 0xS1 to 0xS4 Serial, reserved, all set to 0x00 # 0xDD 15 manufacturing data bytes # 0x00 The last manufacturing data bytes # 0xF7 MIDI SysEx terminator # # Note On/Off: # # 0x9n 0xnn 0xvv n = channel, nn = note number, vv = velocity or 0x7F # 0x8n 0xnn 0xvv n = channel, nn = note number, vv = velocity or 0x7F # # Messages: # # The APC40 Mk2 sends MIDI controller-change (CC) messages from its buttons # and knobs, and receives CC message to turn LEDs On/Off. # # 0xBn 0xnn 0xvv n = channel, nn = CC number, vv = velocity or 0x7F # # device-inquiry = 0xf0 0x7e 0x7f 0x06 0x01 0xf7 # device-inquiry-response-1 = 0xf0 0x7e 0x00 0x06 0x02 0x00 0x20 0x29 0x13 0x01 0x00 0x00 $app-version 0xf7 # device-inquiry-response-1 = 0xf0 0x7e 0x00 0x06 0x02 0x00 0x20 0x29 0x13 0x11 0x00 0x00 $boot-version 0xf7 # mode = $header 0x0e $mode:0x00-0x01 0xf7 # grid-mapping = $header $mode:0x1-0x2 # double-buffering = $header $bits:0x20-0x3d # all-leds = $header $brightness:0x7d-0x7f # duty-cycle = 0xb0 $mode:0x1e-0x1f $data # rapid-led-update = 0x92 $vel1 $vel2 [midi-device-inquiry] # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/macros-MMC.ctrl ================================================ # This file provides samples, explanations, and ideas for MMC control. # # Copy the [macro-control-out] section and paste it into your # qseq66.ctrl or other 'ctrl' file. # # header = 0xf0 0x47 0x7F 0x29 0x60 0x00 0x04 0x00 0xVH 0xVM 0xVF $footer # # Format: # # F0 F7 devid subid1 [subid2 [parameters]] F7 # # devid ranges from 0 to 7F, with 7F being "all devices". # # subid1 (and subid2 for MMC) values: # # 01 = Long Form MTC # 02 = MIDI Show Control # 03 = Notation Information # 04 = Device Control # 05 = Real Time MTC Cueing # 06 = MIDI Machine Control Command (MMC) # 01 Stop # 02 Play # 03 Deferred Play (play after no longer busy) # 04 Fast Forward # 05 Rewind # 06 Record Strobe (Punch in/out | Punch In) # 07 Record Exit (Punch out (music) | Punch out) # 08 Record Pause # 09 Pause (pause playback) # 0A Eject (disengage media container from MMC device) # 0B Chase # 0D MMC Reset (to default/startup state) # 40 Write (Record Ready, Arm Tracks) # Parameters: 4F # 44 Goto (Locate) # Parameters: =06 01 # # 47 Shuttle # Parameters: =03 (MIDI Std Speed codes) # 07 = MIDI Machine Control Response (MMC) # Response state, plus values detailing response state. # 08 = Single Note Retune [macro-control-out] dev-id = 0x00 # or whatever footer = 0xF7 command = F0 F7 $dev-id 0x06 response = F0 F7 $dev-id 0x07 # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/macros-launchpad-mini.ctrl ================================================ # This file provides samples and ideas for the Launchpad Mini. # # Copy the [macro-control-out] section and paste it into your # qseq66.ctrl or other 'ctrl' file. [macro-control-out] drum-layout = $header 0x02 header = 0xb0 0x00 reset = $header 0x00 shutdown = $header 0x00 startup = $x-y-layout x-y-layout = $header 0x01 # Some ideas follow: [macro-control-out-ex] grid-mapping = $header $mode:0x1-0x2 double-buffering = $header $bits:0x20-0x3d all-leds = $header $brightness:0x7d-0x7f duty-cycle = 0xb0 $mode:0x1e-0x1f $data rapid-led-update = 0x92 $vel1 $vel2 # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/macros-launchpad-pro-mk3.ctrl ================================================ # This file provides samples, explanations, and ideas for the Launchpad Pro # MK3, which is much more complex than the Launchpad Mini. # # Copy the [macro-control-out] section and paste it into your # qseq66.ctrl or other 'ctrl' file. [macro-control-out] footer = 0xf7 header = 0xf0 0x00 0x20 0x29 0x02 0x0e command = $header $command-byte reset = $shutdown shutdown = $live-mode startup = $programmer-mode get-current-layout = $header 0x00 $footer live-mode = $header 0x0e 0x00 $footer programmer-mode = $header 0x0e 0x01 $footer drum-layout = $header 0x02 reset = $header 0x00 shutdown = $header 0x00 startup = $x-y-layout x-y-layout = $header 0x01 # Some ideas follow: device-inquiry = 0xf0 0x7e 0x7f 0x06 0x01 $footer device-inquiry-response-1 = 0xf0 0x7e 0x00 0x06 0x02 0x00 0x20 0x29 0x13 0x01 0x00 0x00 $app-version $footer device-inquiry-response-1 = 0xf0 0x7e 0x00 0x06 0x02 0x00 0x20 0x29 0x13 0x11 0x00 0x00 $boot-version $footer mode = $header 0x0e $mode:0x00-0x01 0xf7 grid-mapping = $header $mode:0x1-0x2 double-buffering = $header $bits:0x20-0x3d all-leds = $header $brightness:0x7d-0x7f duty-cycle = 0xb0 $mode:0x1e-0x1f $data rapid-led-update = 0x92 $vel1 $vel2 [midi-device-inquiry] # Here is the Identity Request message: # # 0xF0 SysEx # 0x7E Non-Realtime # 0x7F The SysEx channel. Could be from 0x00 to 0x7F. # Here we set it to "disregard channel". # 0x06 Sub-ID -- General Information # 0x01 Sub-ID2 -- Identity Request # 0xF7 End of SysEx # # Here is the Identity Reply message: # # 0xF0 SysEx # 0x7E Non-Realtime # 0x7F The SysEx channel. Could be from 0x00 to 0x7F. # Here we set it to "disregard channel". # 0x06 Sub-ID -- General Information # 0x02 Sub-ID2 -- Identity Reply # 0xID Manufacturer's ID # 0xf1 The f1 and f2 bytes make up the family code. Each # 0xf2 manufacturer assigns different family codes to his products. # 0xp1 The p1 and p2 bytes make up the model number. Each # 0xp2 manufacturer assigns different model numbers to his products. # 0xv1 The v1, v2, v3 and v4 bytes make up the version number. # 0xv2 # 0xv3 # 0xv4 # 0xF7 End of SysEx # # Host => Launchpad Pro [MK3]: # # Hex Version: F0h 00h 20h 29h 02h 0Eh 00h 00h F7h # # 0x00 Session (only selectable in DAW mode) # 0x01 Fader # 0x02 Chord # 0x03 Custom Mode # 0x04 Note / Drum # 0x05 Scale Settings # 0x06 Sequencer Settings # 0x07 Sequencer Steps # 0x08 Sequencer Velocity # 0x09 Sequencer Pattern Settings # 0x0A Sequencer Probability # 0x0B Sequencer Mutation # 0x0C Sequencer Micro Step # 0x0D Sequencer Projects # 0x0E Sequencer Patterns # 0x0F Sequencer Tempo # 0x10 Sequencer Swing # 0x11 Programmer Mode # 0x12 Settings Menu # 0x13 Custom mode Settings # # Where the available pages are: # # 00h-07h for Custom Mode Views # 00h-03h for any Sequencer View # 00h-03h for Fader view # 00h-04h for Settings Menu # 00h for any other view # # Programmer Mode provides an alternative to Session Mode for creating # scripts. In Programmer Mode, every pad and button sends out a MIDI message # and may be lit with the same message. In Programmer Mode, access to all # Launchpad Pro [MK3] functionality is disabled. Therefore, pressing the Mode # buttons will not cause Launchpad Pro [MK3] to change modes when in # Programmer Mode. All buttons and pads accept either Note or Control Change # messages. The indicated type is which is sent by the device on the MIDI # interface when the corresponding button or pad is pressed. # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66-alt-gray.palette ================================================ # Seq66 0.99.22 (and above) palette configuration file # # /home/user/.config/seq66/qseq66-alt-gray.palette # Written on 2025-10-24 12:00:00 # # This file can be used to change the colors used by patterns # and in some parts of the user-interface. It must be active and # specified in the 'rc' file. [Seq66] config-type = "palette" version = 1 # The [comments] section can document this file. Lines starting with # '#', '[', or that have no characters end the comment. [comments] This provides a greyscale palette where alternate patterns are distinguishable. # Set 'dark-theme' to true if the matching style-sheet is dark. This # also set the 'dark-theme' setting in the 'usr' file. # Set 'dark-ui' to true if the palette background elements are dark. [hints] dark-theme = false dark-ui = false # [palette] affects the pattern colors selected (by number). First is # the color number, 0 to 31. Next is the name of the background color. # The first stanza [square brackets] are the background ARGB values. # The second provides the foreground color name and ARGB values. The # alpha values should be set to FF. [palette] 0 "Black" [ 0xFF000000 ] "White" [ 0xFFFFFFFF ] 1 "Grey 16" [ 0xFF808080 ] "White" [ 0xFFFFFFFF ] 2 "Grey 1" [ 0xFF080808 ] "White" [ 0xFFFFFFFF ] 3 "Grey 17" [ 0xFF888888 ] "Black" [ 0xFF000000 ] 4 "Grey 2" [ 0xFF101010 ] "White" [ 0xFFFFFFFF ] 5 "Grey 18" [ 0xFF909090 ] "Black" [ 0xFF000000 ] 6 "Grey 3" [ 0xFF181818 ] "White" [ 0xFFFFFFFF ] 7 "Grey 19" [ 0xFF989898 ] "Black" [ 0xFF000000 ] 8 "Grey 4" [ 0xFF202020 ] "White" [ 0xFFFFFFFF ] 9 "Grey 20" [ 0xFFA0A0A0 ] "Black" [ 0xFF000000 ] 10 "Grey 5" [ 0xFF282828 ] "White" [ 0xFFFFFFFF ] 11 "Grey 21" [ 0xFFA8A8A8 ] "Black" [ 0xFF000000 ] 12 "Grey 6" [ 0xFF303030 ] "White" [ 0xFFFFFFFF ] 13 "Grey 22" [ 0xFFB0B0B0 ] "Black" [ 0xFF000000 ] 14 "Grey 7" [ 0xFF383838 ] "White" [ 0xFFFFFFFF ] 15 "Grey 23" [ 0xFFB8B8B8 ] "Black" [ 0xFF000000 ] 16 "Grey 8" [ 0xFF404040 ] "White" [ 0xFFFFFFFF ] 17 "Grey 24" [ 0xFFC0C0C0 ] "Black" [ 0xFF000000 ] 18 "Grey 9" [ 0xFF484848 ] "White" [ 0xFFFFFFFF ] 19 "Grey 25" [ 0xFFC8C8C8 ] "Black" [ 0xFF000000 ] 20 "Grey 10" [ 0xFF505050 ] "White" [ 0xFFFFFFFF ] 21 "Grey 26" [ 0xFFD0D0D0 ] "Black" [ 0xFF000000 ] 22 "Grey 11" [ 0xFF585858 ] "White" [ 0xFFFFFFFF ] 23 "Grey 27" [ 0xFFD8D8D8 ] "Black" [ 0xFF000000 ] 24 "Grey 12" [ 0xFF606060 ] "White" [ 0xFFFFFFFF ] 25 "Grey 28" [ 0xFFE0E0E0 ] "Black" [ 0xFF000000 ] 26 "Grey 13" [ 0xFF686868 ] "White" [ 0xFFFFFFFF ] 27 "Grey 29" [ 0xFFE8E8E8 ] "Black" [ 0xFF000000 ] 28 "Grey 14" [ 0xFF707070 ] "White" [ 0xFFFFFFFF ] 29 "Grey 30" [ 0xFFF0F0F0 ] "Black" [ 0xFF000000 ] 30 "Grey 15" [ 0xFF787878 ] "White" [ 0xFFFFFFFF ] 31 "White" [ 0xFFFFFFFF ] "Black" [ 0xFF000000 ] # Similar to the [palette] section, but applies to UI elements and to # the --inverse color option. The first integer is the color number, # ranging from 0 to 11. The names are feature names, not color names. [ui-palette] 0 "Foreground" [ 0xFF000000 ] "Foreground" [ 0xFFFFFFFF ] 1 "Background" [ 0xFFFFFFFF ] "Background" [ 0xFF000000 ] 2 "Label" [ 0xFF000000 ] "Label" [ 0xFFFFFFFF ] 3 "Selection" [ 0xFF2F4F4F ] "Selection" [ 0xFF808080 ] 4 "Drum" [ 0xFFFFFF00 ] "Drum" [ 0xFFFF0080 ] 5 "Tempo" [ 0xFFFF00FF ] "Tempo" [ 0xFFFFFF00 ] 6 "Note Fill" [ 0xFFA0A0A0 ] "Note Fill" [ 0xFFA0A0A0 ] 7 "Note Border" [ 0xFF202020 ] "Note Border" [ 0xFFFFFFFF ] 8 "Black Keys" [ 0xFF000000 ] "Black Keys" [ 0xFFFFFFFF ] 9 "White Keys" [ 0xFFFFFFFF ] "White Keys" [ 0xFF000000 ] 10 "Progress Bar" [ 0xFF000000 ] "Progress Bar" [ 0xFFFFFFFF ] 11 "Back Pattern" [ 0xFF8B8B8B ] "Back Pattern" [ 0xFF8B8B8B ] 12 "Medium Line" [ 0xFF808080 ] "Medium Line" [ 0xFF779988 ] 13 "Heavy Line" [ 0xFF2F4F4F ] "Heavy Line" [ 0xFF2F4F4F ] 14 "Light Line" [ 0xFF778899 ] "Light Line" [ 0xFF808080 ] 15 "Beat" [ 0xFF778899 ] "Beat" [ 0xFFBD6BB7 ] 16 "Near" [ 0xFFFFFF00 ] "Near" [ 0xFFFF00FF ] 17 "Time Brush" [ 0xFF808080 ] "Time Brush" [ 0xFF808080 ] 18 "Data Brush" [ 0xFF808080 ] "Data Brush" [ 0xFF808080 ] 19 "Event Brush" [ 0xFF808080 ] "Event Brush" [ 0xFF808080 ] 20 "Keys Brush" [ 0xFF808080 ] "Keys Brush" [ 0xFF808080 ] 21 "Names Brush" [ 0xFF808080 ] "Names Brush" [ 0xFF808080 ] 22 "Octave Line" [ 0xFF808080 ] "Octave Line" [ 0xFF808080 ] 23 "Text" [ 0xFF808080 ] "Text" [ 0xFF808080 ] 24 "Time Text" [ 0xFF000000 ] "Time Text" [ 0xFFFFFFFF ] 25 "Data Text" [ 0xFF000000 ] "Data Text" [ 0xFFFFFFFF ] 26 "Note Event" [ 0xFFFFFFFF ] "Note Event" [ 0xFF000000 ] 27 "Keys Text" [ 0xFF000000 ] "Keys Text" [ 0xFFFFFFFF ] 28 "Names Text" [ 0xFF000000 ] "Names Text" [ 0xFFFFFFFF ] 29 "Slots Text" [ 0xFF000000 ] "Slots Text" [ 0xFFFFFFFF ] 30 "Scale Brush" [ 0xFF000000 ] "Scale Brush" [ 0xFFFFFFFF ] 31 "Extra" [ 0xFF000000 ] "Extra" [ 0xFFFFFFFF ] # This section defines brush styles to use. The names are based on the # names in the Qt::BrushStyle enumeration. The names are: # # nobrush, solid, dense1, dense2, dense3, dense4, dense5, dense6, # dense7, horizontal, vertical, cross, bdiag, fdiag, diagcross, # lineargradient, radialgradient, and conicalgradient. # # For 'empty', best to just use 'solid' (try others and see why). # For 'note', use only 'solid' or the default, 'lineargradient'. These # also apply to the progress box and triggers. [brushes] empty = solid note = lineargradient scale = dense3 backseq = dense2 chord = bdiag # This section defines pen styles to use for vertical time lines. # The names are based on the names in the Qt::PenStyle enumeration. # They are: # nopen, solid, dash, dot, dashdot, dashdotdot, customdash (which # is currently not supported). # # 'measure' and 'beat' are obvious. 'fourth' is a line for the 1/4s of # a beat, and 'step' is the smallest unit (normally 6 pixels). [pens] measure = solid beat = solid fourth = dashdot step = dot # End of /home/user/.config/seq66/qseq66-alt-gray.palette # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66-azerty-fr.keymap ================================================ # Seq66 0.94.0 (and above) keyboard remapping configuration file # # /home/ahlstrom/.config/seq66/qseq66-azerty-fr.keymap # Written on 2023-08-06 14:52:54 # # This file holds keystroke remappings for the AZERTY FR keyboard. # If specified, it modifies the internal key-map with the changes # shown below. # # Note that the internal keymap is shown in doc/control-keys.ods and in # data/linux/qseq66-qwerty-us.keys. # # Also note that this file is only read... it is never written over. [Seq66] config-type = "keys" version = 0 [comments] This French AZERTY configuration was provided by Sylvain Margueritat (user name 'tripayou') as part of issue #47 "Change key map". Some of the issues are: 1. BPM+ and BPM- defaults '"' and ';' are replaced in AZERTY by '*' and 'ù', but neither work. 2. Auto-shift for group-learn for top line doesn't work. With '!' replaced by '&', the error is "Key '!' (code = 33) is not a configured mute-group key." The "azerty" option disables auto-shift. 3. Assign a mute group to key 'a'; loop control mute/unmute by pressing this key does not work. Here are the extended ASCII keys that appear in this file using special names (see the table below). Other keys need some modification to their mappings as well, mainly changes in keystroke modifiers. Key #0xa3 '£' Key #0xa7 '§' Key #0xb0 '°' Key #0xb2 '²' Key #0xc0 'à' Key #0xc7 'ç' Key #0xc8 'è' Key #0xc9 'é' Key #0xd9 'ù' Key #0x39c 'µ' [key-mappings] # Internal Qt Qt Key # Ordinal Evkey VirtKey Name Modifier 0x23 0x21 0x21 "!" "None" # Exclam 0x22 0x22 0x22 """ "None" # QuoteDbl 0x23 0x23 0x23 "#" "Alt-Gr" # NumberSign 0x26 0x26 0x26 "&" "None" # Ampersand 0x27 0x27 0x27 "'" "None" # Apostrophe 0x28 0x28 0x28 "(" "None" # ParenLeft 0x29 0x29 0x29 ")" "None" # ParenRight 0x2a 0x2a 0x2a "*" "None" # Asterisk 0x2e 0x2e 0x2e "." "Shift" # Period 0x2f 0x2f 0x2f "/" "Shift" # Slash 0x3a 0x3a 0x3a ":" "None" # Colon 0x3c 0x3c 0x3c "<" "None" # Less-than 0x40 0x40 0x40 "@" "Alt-Gr" # AtSign 0x5b 0x5b 0x5b "[" "Alt-Gr" # BracketLeft 0x5c 0x5c 0x5c "\" "Alt-Gr" # Backslash 0x5d 0x5d 0x5d "]" "Alt-Gr" # BracketRight 0x5e 0x5e 0x5e "^" "Alt-Gr" # AsciiCircumflex 0x5f 0x5f 0x5f "_" "None" # Underscore 0x60 0x60 0x60 "`" "Alt-Gr" # QuoteLeft Backtick 0x7b 0x7b 0x7b "{" "Alt-Gr" # BraceLeft 0x7c 0x7c 0x7c "|" "Alt-Gr" # Bar 0x7d 0x7d 0x7d "}" "Alt-Gr" # BraceRight 0x7e 0x7e 0x7e "~" "Alt-Gr" # Tilde (dead key) 0xe0 0xa3 0xa3 "L_pound" "None" # £ 0xe1 0xa4 0xa4 "Currency" "Alt-Gr" # ¤ 0xe2 0xa7 0xa7 "Silcrow" "Shift" # § 0xe3 0xb0 0xb0 "Degrees" "Shift" # ° 0xe4 0x01000022 0xffec "Super_2" "Meta" # ² 0xe5 0xc0 0xe0 "a_grave" "None" # à 0xe6 0xc7 0xe7 "c_cedilla" "None" # ç 0xe7 0xc8 0xe8 "e_grave" "None" # è 0xe8 0xc9 0xe9 "e_acute" "None" # é 0xe9 0xd9 0xf9 "u_grave" "None" # ù 0xea 0x039c 0xb5 "Mu" "Shift" # µ 0xeb 0x20ac 0xb6 "Euro" "Alt-Gr" # € 0xec 0x01001252 0xfe52 "Circflex" "None" # ^ Not the ASCII caret 0xed 0x01001257 0xfe57 "Umlaut" "Shift" # ¨ Diaeresis 0xee 0x01000022 0xffec "Super_2r" "None" # ² Release 0xff 0xffffffff 0xff "?" "None" # terminator # End of /home/ahlstrom/.config/seq66/qseq66-azerty-fr.keymap # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66-azerty.ctrl ================================================ # Seq66 0.94.1 (and above) MIDI control configuration file # # /home/user/.config/seq66/qseq66-azerty.ctrl # Written 2021-06-13 08:48:55 # # This file holds MIDI I/O control setups for Seq66. It follows the format # of the 'rc' configuration file, but is stored separately for flexibility # It is always stored in the main configuration directory. To use this # file, specify it in the [midi-control-file] section in the 'rc' file. # Use the base-name (e.g. nanomap.ctrl). # Version 1 adds the [mute-control-out] and [automation-control-out] # sections. Versions 2 and 3 simplify the data items. [Seq66] config-type = "ctrl" version = 5 # The [comments] section holds the user documentation for this file. # The first completely empty, comment, or tag line ends the comment. [comments] This French AZERTY configuration was provided by Sylvain Margueritat (user name 'tripayou') as part of issue #47 "Change key map". Some of the issues are: 1. BPM+ and BPM- defaults '"' and ';' are replaced in AZERTY by '*' and 'ù', but neither work. Fixed by a (currently internal) remapping of some keystrokes for AZERTY keyboards. 2. Auto-shift for group-learn for top line doesn't work. With '!' replaced by '&', the error is "Key '!' (code = 33) is not a configured mute-group key." The "azerty" option disables auto-shift, since digits require using the Shift key. 3. The AZERTY keyboard can have the following "dead keys", which modify the next keystroke: - Circumflex accent (^, on the key to the right of "P") - Diaeresis (umlaut, on the key to the right of "P") - Grave accent (`) - Acute accent (') - Tilde (~, the superscript "2") The first two keys are not usable as control keys on some setups. See the following site for information about the related system configuration files: https://medium.com/@damko/a-simple-humble-but-comprehensive-guide-to-xkb-for-linux-6f1ad5e13450 Especially check out the file "/usr/share/X11/xkb/symbols/fr". Here are some of the keys that appear in this file using made-up names (e.g. c_cedilla) to be used in the MIDI control settings. L_pound '£' Silcrow '§' Super_2 '²' a_grave 'à' c_cedilla 'ç' e_grave 'è' e_acute 'é' u_grave 'ù' Circflex '^' [midi-control-settings] # The control-buss value ranges from 0 to the maximum system input buss. If set, # then that buss will be allowed to send MIDI control. 255 (0xFF) means any buss # can send MIDI control. The buss(es) must be enabled in the 'rc' file. With # ALSA, there is an extra 'announce' buss, which will alter the numbering # compared to JACK. # # The 'midi-enabled' flag applies to the MIDI controls; keystrokes are always # enabled. Supported keyboard layouts are 'qwerty' (the default), 'qwertz', and # 'azerty'. AZERTY turns off the auto-shift feature for group-learn. control-buss = 0xFF midi-enabled = false button-offset = 0 button-rows = 4 button-columns = 8 keyboard-layout = azerty # The control stanza incorporates key control as well as MIDI, but # keys support only 'toggle', and key-release is an 'invert'. The # leftmost number on each line here is the pattern number (e.g. # 0 to 31); the group number, same range; or an automation control # number. This internal control number is followed by three groups of # bracketed numbers, each providing three different types of control: # # Normal: [toggle] [on] [off] # Increment/Decr: [increment] [increment] [decrement] # Playback: [pause] [start] [stop] # Playlist/Song: [by-value] [next] [previous] # # In each group, there are six numbers: # # [on/off invert status d0 d1min d1max] # # 'on/off' enables/disables (1/0) the control for the pattern; # 'invert' (1/0) causes the opposite, but not all support this, and # all keystroke-releases set invert to true; 'status' is the MIDI # event to match (channel is NOT ignored), and if set to 0x00, the # control is disabled; 'd0' is the first data value, e.g. if status # is 0x90 (Note On), d0 represents the note number; d1min to d1max # is the range of data values detected, e.g. for a Note On, 1 to 127 # indicate that any non-zero velocity will invoke the control. # Hex values can be used; precede with '0x'. # # ------------------------- Loop, group, or automation-slot number # | ---------------------- Name of the key (see the key map) # | | # | | ---------------- Inverse # | | | -------------- MIDI status/event byte (e.g. Note On) # | | | | ------------ d0: Data 1 (e.g. Note number) # | | | | | ---------- d1max: Data 2 min (e.g. Note velocity) # | | | | | | -------- d1min: Data 2 max # | | | | | | | # v v v v v v v # 0 "F1" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Toggle On Off [loop-control] 0 "&" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 0 1 "a" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 1 2 "q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 2 3 "w" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 3 4 "e_acute" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 4 5 "z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 5 6 "s" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 6 7 "x" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 7 8 """ [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 8 9 "e" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 9 10 "d" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 10 11 "c" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 11 12 "'" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 12 13 "r" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 13 14 "f" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 14 15 "v" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 15 16 "(" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 16 17 "t" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 17 18 "g" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 18 19 "b" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 19 20 "-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 20 21 "y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 21 22 "h" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 22 23 "n" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 23 24 "e_grave" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 24 25 "u" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 25 26 "j" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 26 27 "," [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 27 28 "_" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 28 29 "i" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 29 30 "k" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 30 31 ";" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 31 [mute-group-control] 0 "1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 0 1 "A" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 1 2 "Q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 2 3 "W" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 3 4 "2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 4 5 "Z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 5 6 "S" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 6 7 "X" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 7 8 "3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 8 9 "E" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 9 10 "D" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 10 11 "C" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 11 12 "4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 12 13 "R" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 13 14 "F" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 14 15 "V" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 15 16 "5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 16 17 "T" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 17 18 "G" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 18 19 "B" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 19 20 "6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 20 21 "Y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 21 22 "H" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 22 23 "N" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 23 24 "7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 24 25 "U" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 25 26 "J" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 26 27 "?" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 27 28 "8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 28 29 "I" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 29 30 "K" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 30 31 "." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 31 [automation-control] 0 "*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Up 1 "u_grave" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Dn 2 "$" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Up 3 "Circflex" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Dn 4 "KP_Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Replace 5 "Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Snapshot 6 "o" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Queue 7 "Super_2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Mute 8 "l" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Learn 9 "Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playing Set 10 ":" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playback 11 "P" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Record 12 "BkSpace" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Solo 13 "KP_/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Thru 14 "PageUp" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Up 15 "PageDn" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Dn 16 "KP_." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Set 17 "KP_*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record 18 "KP_-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quan Record 19 "KP_+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reset Seq 20 ">" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # One-shot 21 "F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # FF 22 "F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Rewind 23 "F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Top 24 "F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play List 25 "F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play Song 26 "F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Tap BPM 27 "Space" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Start 28 "Esc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Stop 29 "KP_Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 29 30 "Silcrow" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Mute 31 "F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Pos 32 "<" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Keep Queue 33 "/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Slot Shift 34 "a_grave" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mutes Clear 35 "Quit" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quit 36 "=" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop Edit 37 ")" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Event Edit 38 "F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Mode 39 "F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle JACK 40 "F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Menu Mode 41 "L_pound" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Follow JACK 42 "+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Panic 43 "0xf9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Visibility 44 "0xfa" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 44 45 "0xfb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 45 46 "0xfc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 47 "0xfd" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 48 "0xfe" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 48 [midi-control-out-settings] set-size = 32 output-buss = 255 midi-enabled = false button-offset = 0 button-rows = 4 button-columns = 8 [midi-control-out] # --------------------- Pattern number (as applicable) # | ---------------- MIDI status+channel (e.g. Note On) # | | ------------ data 1 (e.g. note number) # | | | ---------- data 2 (e.g. velocity) # | | | | # v v v v # 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0] # Arm Mute Queue Delete # # This is a change (2021-02-10) from version 1 of this file. # A test of the status/event byte determines the enabled status, # and channel is incorporated into the status. Much cleaner! # The order of the lines that follow must must be preserved. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [mute-control-out] # The format of the mute and automation output events is simpler: # # ---------------------- mute-group number # | ------------------ MIDI status+channel (e.g. Note On) # | | -------------- data 1 (e.g. note number) # | | | ------------ data 2 (e.g. velocity) # | | | | # v v v v # 1 [0x00 0 0 ] [0x00 0 0] [0x00 0 0] # On Off Empty (dark) # # The mute-controls have an additional stanza for non-populated # ("deleted") mute-groups. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [automation-control-out] # This format is similar to the [mute-control-out] format, but # the first number is an active-flag, not an index number. # The stanzas are on/off/inactive, except for 'snap', which is # store/restore/inactive. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Panic 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Stop 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Pause 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Toggle_mutes 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_record 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Slot_shift 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Free 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Queue 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Oneshot 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Replace 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Snap 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Learn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Tap_BPM 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Quit 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Visibility 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_2 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_3 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_4 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_5 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_6 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_7 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_8 # End of /home/user/.config/seq66/qseq66-azerty.ctrl # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66-default.palette ================================================ # Seq66 0.99.22 (and above) palette configuration file # # /home/ahlstrom/.config/seq66/qseq66.palette # Written on 2025-10-24 12:13:09 # # This file can be used to change the colors used by patterns # and in some parts of the user-interface. It must be active and # specified in the 'rc' file. [Seq66] config-type = "palette" version = 1 # The [comments] section can document this file. Lines starting with # '#', '[', or that have no characters end the comment. [comments] This palette contains the default (i.e. internal to the code) palette colors when no .palette file is active. # Set 'dark-theme' to true if the matching style-sheet is dark. This # also set the 'dark-theme' setting in the 'usr' file. # Set 'dark-ui' to true if the palette background elements are dark. [hints] dark-theme = false dark-ui = false # [palette] affects the pattern colors selected (by number). First is # the color number, 0 to 31. Next is the name of the background color. # The first stanza [square brackets] are the background ARGB values. # The second provides the foreground color name and ARGB values. The # alpha values should be set to FF. [palette] 0 "Black" [ 0xFF000000 ] "White" [ 0xFFFFFFFF ] 1 "Red" [ 0xFFFF0000 ] "White" [ 0xFFFFFFFF ] 2 "Green" [ 0xFF008000 ] "White" [ 0xFFFFFFFF ] 3 "Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 4 "Blue" [ 0xFF0000FF ] "White" [ 0xFFFFFFFF ] 5 "Magenta" [ 0xFFFF00FF ] "White" [ 0xFFFFFFFF ] 6 "Cyan" [ 0xFF00FFFF ] "Black" [ 0xFF000000 ] 7 "White" [ 0xFFFFFFFF ] "Black" [ 0xFF000000 ] 8 "Dark Black" [ 0xFF2F4F4F ] "White" [ 0xFFFFFFFF ] 9 "Dark Red" [ 0xFF8B0000 ] "White" [ 0xFFFFFFFF ] 10 "Dark Green" [ 0xFF006400 ] "White" [ 0xFFFFFFFF ] 11 "Dark Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 12 "Dark Blue" [ 0xFF00008B ] "White" [ 0xFFFFFFFF ] 13 "Dark Magenta" [ 0xFF8B008B ] "White" [ 0xFFFFFFFF ] 14 "Dark Cyan" [ 0xFF008B8B ] "White" [ 0xFFFFFFFF ] 15 "Dark White" [ 0xFF808080 ] "White" [ 0xFFFFFFFF ] 16 "Orange" [ 0xFFFFA500 ] "White" [ 0xFFFFFFFF ] 17 "Pink" [ 0xFFFFC0CB ] "Black" [ 0xFF000000 ] 18 "Pale Green" [ 0xFF98FB98 ] "Black" [ 0xFF000000 ] 19 "Khaki" [ 0xFFF0E68C ] "Black" [ 0xFF000000 ] 20 "Light Blue" [ 0xFFADD8E6 ] "Black" [ 0xFF000000 ] 21 "Light Magenta" [ 0xFFEE82EE ] "Black" [ 0xFF000000 ] 22 "Turquoise" [ 0xFF40E0D0 ] "Black" [ 0xFF000000 ] 23 "Grey" [ 0xFF808080 ] "Black" [ 0xFF000000 ] 24 "Dk Orange" [ 0xFFFF8C00 ] "Black" [ 0xFF000000 ] 25 "Dark Pink" [ 0xFFFF1493 ] "Black" [ 0xFF000000 ] 26 "Sea Green" [ 0xFF2E8B57 ] "Black" [ 0xFF000000 ] 27 "Dark Khaki" [ 0xFFBDB76B ] "Black" [ 0xFF000000 ] 28 "Dark Slate Blue" [ 0xFF483D8B ] "Black" [ 0xFF000000 ] 29 "Dark Violet" [ 0xFF9400D3 ] "Black" [ 0xFF000000 ] 30 "Light Grey" [ 0xFFC0C0C0 ] "Black" [ 0xFF000000 ] 31 "Dark Grey" [ 0xFF484848 ] "Black" [ 0xFF000000 ] # Similar to the [palette] section, but applies to the custom-drawn # piano rolls and the --inverse option. The values: color number (0 # to 31); main color feature name; main color value; inverse color # feature name; and the --inverse color value. [ui-palette] 0 "Foreground" [ 0xFF000000 ] "Foreground" [ 0xFFFFFFFF ] 1 "Background" [ 0xFFFFFFFF ] "Background" [ 0xFF000000 ] 2 "Label" [ 0xDEADBEEF ] "Label" [ 0xFFFFFFFF ] 3 "Selection" [ 0xFFFFA500 ] "Selection" [ 0xFFFF00A5 ] 4 "Drum" [ 0xFFFF0000 ] "Drum" [ 0xFF000080 ] 5 "Tempo" [ 0xFFFFFF00 ] "Tempo" [ 0xFFFFFF00 ] 6 "Note Fill" [ 0xFFFFFFFF ] "Note Fill" [ 0xFF000000 ] 7 "Note Border" [ 0xFF000000 ] "Note Border" [ 0xFFFFFFFF ] 8 "Black Keys" [ 0xFF000000 ] "Black Keys" [ 0xFFFFFFFF ] 9 "White Keys" [ 0xFFFFFFFF ] "White Keys" [ 0xFF000000 ] 10 "Progress Bar" [ 0xFFFF0000 ] "Progress Bar" [ 0xFFFF0000 ] 11 "Back Pattern" [ 0xFF008B8B ] "Back Pattern" [ 0xFF008B8B ] 12 "Medium Line" [ 0xFF808080 ] "Medium Line" [ 0xFF808080 ] 13 "Heavy Line" [ 0xFF484848 ] "Heavy Line" [ 0xFFFFFFFF ] 14 "Light Line" [ 0xFFC0C0C0 ] "Light Line" [ 0xFFC0C0C0 ] 15 "Beat" [ 0xFF000000 ] "Beat" [ 0xFFFFFFFF ] 16 "Near" [ 0xFFFFFF00 ] "Near" [ 0xFFFF00FF ] 17 "Time Brush" [ 0xFF808080 ] "Time Brush" [ 0xFF808080 ] 18 "Data Brush" [ 0xFF808080 ] "Data Brush" [ 0xFF808080 ] 19 "Event Brush" [ 0xFF808080 ] "Event Brush" [ 0xFF808080 ] 20 "Keys Brush" [ 0xFF808080 ] "Keys Brush" [ 0xFF808080 ] 21 "Names Brush" [ 0xFF808080 ] "Names Brush" [ 0xFF808080 ] 22 "Octave Line" [ 0xFF808080 ] "Octave Line" [ 0xFFFFFFFF ] 23 "Text" [ 0xFF000000 ] "Text" [ 0xFFFFFFFF ] 24 "Time Text" [ 0xFF000000 ] "Time Text" [ 0xFFFFFFFF ] 25 "Data Text" [ 0xFF000000 ] "Data Text" [ 0xFFFFFFFF ] 26 "Note Event" [ 0xFFFFFFFF ] "Note Event" [ 0xFF000000 ] 27 "Keys Text" [ 0xFF000000 ] "Keys Text" [ 0xFFFFFFFF ] 28 "Names Text" [ 0xFF000000 ] "Names Text" [ 0xFFFFFFFF ] 29 "Slots Text" [ 0xFF000000 ] "Slots Text" [ 0xFFFFFFFF ] 30 "Scale Brush" [ 0xFFC0C0C0 ] "Scale Brush" [ 0xFFC0C0C0 ] 31 "Extra" [ 0xFF000000 ] "Extra" [ 0xFFFFFFFF ] # This section defines brush styles to use. The names are based on the # names in the Qt::BrushStyle enumeration. The names are: # # nobrush, solid, dense1, dense2, dense3, dense4, dense5, dense6, # dense7, horizontal, vertical, cross, bdiag, fdiag, diagcross, # lineargradient, radialgradient, and conicalgradient. # # For 'empty', best to just use 'solid' (try others and see why). # For 'note', use only 'solid' or the default, 'lineargradient'. These # also apply to the progress box and triggers. [brushes] empty = solid note = lineargradient scale = dense3 backseq = dense2 chord = bdiag # This section defines pen styles to use for vertical time lines. # The names are based on the names in the Qt::PenStyle enumeration. # They are: # nopen, solid, dash, dot, dashdot, dashdotdot, customdash (which # is currently not supported). # # 'measure' and 'beat' are obvious. 'fourth' is a line for the 1/4s of # a beat, and 'step' is the smallest unit (normally 6 pixels). [pens] measure = solid beat = solid fourth = dashdot step = dot # End of /home/ahlstrom/.config/seq66/qseq66.palette # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66-gray.palette ================================================ # Seq66 0.99.22 (and above) palette configuration file # # /home/user/.config/seq66/qseq66-gray.palette # Written on 2025-10-24 12:00:00 # # This file can be used to change the colors used by patterns # and in some parts of the user-interface. It must be active and # specified in the 'rc' file. [Seq66] config-type = "palette" version = 1 # The [comments] section can document this file. Lines starting with # '#', '[', or that have no characters end the comment. [comments] This is a complete grey palette. # Set 'dark-theme' to true if the matching style-sheet is dark. This # also set the 'dark-theme' setting in the 'usr' file. # Set 'dark-ui' to true if the palette background elements are dark. [hints] dark-theme = false dark-ui = false # [palette] affects the pattern colors selected (by number). First is # the color number, 0 to 31. Next is the name of the background color. # The first stanza [square brackets] are the background ARGB values. # The second provides the foreground color name and ARGB values. The # alpha values should be set to FF. [palette] 0 "Black" [ 0xFF000000 ] "White" [ 0xFFFFFFFF ] 1 "Grey 1" [ 0xFF080808 ] "White" [ 0xFFFFFFFF ] 2 "Grey 2" [ 0xFF101010 ] "White" [ 0xFFFFFFFF ] 3 "Grey 3" [ 0xFF181818 ] "White" [ 0xFFFFFFFF ] 4 "Grey 4" [ 0xFF202020 ] "White" [ 0xFFFFFFFF ] 5 "Grey 5" [ 0xFF282828 ] "White" [ 0xFFFFFFFF ] 6 "Grey 6" [ 0xFF303030 ] "White" [ 0xFFFFFFFF ] 7 "Grey 7" [ 0xFF383838 ] "White" [ 0xFFFFFFFF ] 8 "Grey 8" [ 0xFF404040 ] "White" [ 0xFFFFFFFF ] 9 "Grey 9" [ 0xFF484848 ] "White" [ 0xFFFFFFFF ] 10 "Grey 10" [ 0xFF505050 ] "White" [ 0xFFFFFFFF ] 11 "Grey 11" [ 0xFF585858 ] "White" [ 0xFFFFFFFF ] 12 "Grey 12" [ 0xFF606060 ] "White" [ 0xFFFFFFFF ] 13 "Grey 13" [ 0xFF686868 ] "White" [ 0xFFFFFFFF ] 14 "Grey 14" [ 0xFF707070 ] "White" [ 0xFFFFFFFF ] 15 "Grey 15" [ 0xFF787878 ] "White" [ 0xFFFFFFFF ] 16 "Grey 16" [ 0xFF808080 ] "White" [ 0xFFFFFFFF ] 17 "Grey 17" [ 0xFF888888 ] "Black" [ 0xFF000000 ] 18 "Grey 18" [ 0xFF909090 ] "Black" [ 0xFF000000 ] 19 "Grey 19" [ 0xFF989898 ] "Black" [ 0xFF000000 ] 20 "Grey 20" [ 0xFFA0A0A0 ] "Black" [ 0xFF000000 ] 21 "Grey 21" [ 0xFFA8A8A8 ] "Black" [ 0xFF000000 ] 22 "Grey 22" [ 0xFFB0B0B0 ] "Black" [ 0xFF000000 ] 23 "Grey 23" [ 0xFFB8B8B8 ] "Black" [ 0xFF000000 ] 24 "Grey 24" [ 0xFFC0C0C0 ] "Black" [ 0xFF000000 ] 25 "Grey 25" [ 0xFFC8C8C8 ] "Black" [ 0xFF000000 ] 26 "Grey 26" [ 0xFFD0D0D0 ] "Black" [ 0xFF000000 ] 27 "Grey 27" [ 0xFFD8D8D8 ] "Black" [ 0xFF000000 ] 28 "Grey 28" [ 0xFFE0E0E0 ] "Black" [ 0xFF000000 ] 29 "Grey 29" [ 0xFFE8E8E8 ] "Black" [ 0xFF000000 ] 30 "Grey 30" [ 0xFFF0F0F0 ] "Black" [ 0xFF000000 ] 31 "White" [ 0xFFFFFFFF ] "Black" [ 0xFF000000 ] # Similar to the [palette] section, but applies to the custom-drawn # piano rolls and the --inverse option. The values: color number (0 # to 31); main color feature name; main color value; inverse color # feature name; and the --inverse color value. [ui-palette] 0 "Foreground" [ 0xFF000000 ] "Foreground" [ 0xFFFFFFFF ] 1 "Background" [ 0xFFFFFFFF ] "Background" [ 0xFF000000 ] 2 "Label" [ 0xFF000000 ] "Label" [ 0xFFFFFFFF ] 3 "Selection" [ 0xFF2F4F4F ] "Selection" [ 0xFF808080 ] 4 "Drum" [ 0xFFFFFF00 ] "Drum" [ 0xFFFF0080 ] 5 "Tempo" [ 0xFFFF00FF ] "Tempo" [ 0xFFFFFF00 ] 6 "Note Fill" [ 0xFFA0A0A0 ] "Note Fill" [ 0xFFA0A0A0 ] 7 "Note Border" [ 0xFF202020 ] "Note Border" [ 0xFFFFFFFF ] 8 "Black Keys" [ 0xFF000000 ] "Black Keys" [ 0xFFFFFFFF ] 9 "White Keys" [ 0xFFFFFFFF ] "White Keys" [ 0xFF000000 ] 10 "Progress Bar" [ 0xFF000000 ] "Progress Bar" [ 0xFFFFFFFF ] 11 "Back Pattern" [ 0xFF8B8B8B ] "Back Pattern" [ 0xFF8B8B8B ] 12 "Medium Line" [ 0xFF808080 ] "Medium Line" [ 0xFF779988 ] 13 "Heavy Line" [ 0xFF2F4F4F ] "Heavy Line" [ 0xFF2F4F4F ] 14 "Light Line" [ 0xFF778899 ] "Light Line" [ 0xFF808080 ] 15 "Beat" [ 0xFF778899 ] "Beat" [ 0xFFBD6BB7 ] 16 "Near" [ 0xFFFFFF00 ] "Near" [ 0xFFFF00FF ] 17 "Time Brush" [ 0xFF808080 ] "Time Brush" [ 0xFF808080 ] 18 "Data Brush" [ 0xFF808080 ] "Data Brush" [ 0xFF808080 ] 19 "Event Brush" [ 0xFF808080 ] "Event Brush" [ 0xFF808080 ] 20 "Keys Brush" [ 0xFF808080 ] "Keys Brush" [ 0xFF808080 ] 21 "Names Brush" [ 0xFF808080 ] "Names Brush" [ 0xFF808080 ] 22 "Octave Line" [ 0xFF808080 ] "Octave Line" [ 0xFF808080 ] 23 "Text" [ 0xFF808080 ] "Text" [ 0xFF808080 ] 24 "Time Text" [ 0xFF000000 ] "Time Text" [ 0xFFFFFFFF ] 25 "Data Text" [ 0xFF000000 ] "Data Text" [ 0xFFFFFFFF ] 26 "Note Event" [ 0xFFFFFFFF ] "Note Event" [ 0xFF000000 ] 27 "Keys Text" [ 0xFF000000 ] "Keys Text" [ 0xFFFFFFFF ] 28 "Names Text" [ 0xFF000000 ] "Names Text" [ 0xFFFFFFFF ] 29 "Slots Text" [ 0xFF000000 ] "Slots Text" [ 0xFFFFFFFF ] 30 "Scale Brush" [ 0xFF000000 ] "Scale Brush" [ 0xFFFFFFFF ] 31 "Extra" [ 0xFF000000 ] "Extra" [ 0xFFFFFFFF ] # This section defines brush styles to use. The names are based on the # names in the Qt::BrushStyle enumeration. The names are: # # nobrush, solid, dense1, dense2, dense3, dense4, dense5, dense6, # dense7, horizontal, vertical, cross, bdiag, fdiag, diagcross, # lineargradient, radialgradient, and conicalgradient. # # For 'empty', best to just use 'solid' (try others and see why). # For 'note', use only 'solid' or the default, 'lineargradient'. These # also apply to the progress box and triggers. [brushes] empty = solid note = lineargradient scale = dense3 backseq = dense2 chord = bdiag # This section defines pen styles to use for vertical time lines. # The names are based on the names in the Qt::PenStyle enumeration. # They are: # nopen, solid, dash, dot, dashdot, dashdotdot, customdash (which # is currently not supported). # # 'measure' and 'beat' are obvious. 'fourth' is a line for the 1/4s of # a beat, and 'step' is the smallest unit (normally 6 pixels). [pens] measure = solid beat = solid fourth = dashdot step = dot # End of /home/user/.config/seq66/qseq66-gray.palette # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66-lp-mini-8x8.ctrl ================================================ # Seq66 0.90.5 (and above) MIDI control configuration file # # /home/user/.config/seq66/qseq66-lp-mini-8x8.ctrl # Written on 2020-08-07 09:58:39 # # This file holds the MIDI control configuration for Seq66. It follows # the format of the 'rc' configuration file, but is stored separately for # flexibility. It is always stored in the main configuration directory. # To use this file, replace the [midi-control] section in the 'rc' file, # and its contents with a [midi-control-file] tag, and simply add the # basename (e.g. nanomap.ctrl) on a separate line. [Seq66] config-type = "ctrl" version = 5 # [comments] holds the user's documentation for this control file. # Lines starting with '#' and '[' are ignored. Blank lines are ignored; # add an empty line by adding a space character to the line. [comments] This file is the 8 x 8 version of the Launchpad Mini control file for Seq66. It is NOT READY. This file was created by copying the default qseq66.ctrl file and changing the MIDI control section to the values found in the sequencer64-laumchpad.rc file. This section provides for controlling the following actions: o Patterns 0 to 31. Patterns can be toggled by sending Note Ons on on channel 1. Observe that the Toggle entries are marked active. This works with the Launchpad Mini. o Patterns 0 to 31 alternative. Patterns can be turned on by sending Note Ons on on channel 1, and turned off by sending Note Offs. Make the Toggle entries inactive, and the On and Off entries active to achieve this setup. o Muting 32 to 64 (mute groups 0 to 31). o Automation control 64 to 73. This covers only part of the actions that can be controlled via MIDI in Seq66; we still need to upgrade the rest once we get familiar with our new LaunchPad Mini. The default setup for the LaunchPad Mini is shown in the "Launchpad programmer's reference" file (launchpad-programmers-reference.pdf). Since, by default, the Mini uses Note Ons of velocity 0 instead of Note Offs, we set the data range from 1 to 127 instead of 0 to 127. For MIDI output to the Launchpad Mini, we make the following color mappings: Arm Mute Queue Deleted/Empty Green Red Yellow Off 60 15 62 12 A sample line. Just need to fill in the key (note value) needed for this row and set the pattern number at the left: 2 [1 0 0x90 key 60] [1 0 0x90 key 15] [1 0 0x90 key 62] [1 0 0x90 key 12] The Play, Pause, and Stop controls are all mapped to the top left button (#1) on the Mini, with states of off, green (play), red (stop), and yellow (pause). [midi-control-settings] # The control-buss value should range from 0 to the maximum buss available on # your system. If set, then only that buss will be allowed to provide MIDI # control. A value of 255 or 0xff means any buss can. The 'midi-enabled' flag # applies to the MIDI controls; keystrokes are always enabled. control-buss = 6 midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 keyboard-layout = qwerty # This style of control stanza incorporates key control as well. # The leftmost number on each line here is the pattern number (e.g. # 0 to 31); the group number, same range, for up to 32 groups; or it # it is an automation control number, again a similar range. # This internal MIDI control number is followed by three groups of # bracketed numbers, each providing three different type of control: # # Normal: [toggle] [on] [off] # Increment/Decr: [increment] [increment] [decrement] # Playback: [pause] [start] [stop] # Playlist/Song: [by-value] [next] [previous] # # In each group, there are six numbers: # # [on/off invert status d0 d1min d1max] # # 'on/off' enables/disables (1/0) the control for the pattern; # 'invert' (1/0) causes the opposite, but not all support this, and # all keystroke-releases set invert to true; 'status' is the MIDI # event to match (channel is NOT ignored), and if set to 0x00, the # control is disabled; 'd0' is the first data value, e.g. if status # is 0x90 (Note On), d0 represents the note number; d1min to d1max # is the range of data values detected, e.g. for a Note On, 1 to 127 # indicate that any non-zero velocity will invoke the control. # Hex values can be used; precede with '0x'. # # ------------------------- Loop, group, or automation-slot number # | ---------------------- Name of the key (see the key map) # | | # | | ---------------- Inverse # | | | -------------- MIDI status/event byte (e.g. Note On) # | | | | ------------ d0: Data 1 (e.g. Note number) # | | | | | ---------- d1max: Data 2 min (e.g. Note velocity) # | | | | | | -------- d1min: Data 2 max # | | | | | | | # v v v v v v v # 0 "F1" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Toggle On Off [loop-control] 0 "1" [ 0 0x90 0 1 127 ] [ 0 0x90 0 1 127 ] [ 0 0x80 0 1 127 ] 1 "q" [ 0 0x90 16 1 127 ] [ 0 0x90 16 1 127 ] [ 0 0x80 16 1 127 ] 2 "a" [ 0 0x90 32 1 127 ] [ 0 0x90 32 1 127 ] [ 0 0x80 32 1 127 ] 3 "z" [ 0 0x90 48 1 127 ] [ 0 0x90 48 1 127 ] [ 0 0x80 48 1 127 ] 8 "2" [ 0 0x90 1 1 127 ] [ 0 0x90 1 1 127 ] [ 0 0x80 1 1 127 ] 9 "w" [ 0 0x90 17 1 127 ] [ 0 0x90 17 1 127 ] [ 0 0x80 17 1 127 ] 10 "s" [ 0 0x90 33 1 127 ] [ 0 0x90 33 1 127 ] [ 0 0x80 33 1 127 ] 11 "x" [ 0 0x90 49 1 127 ] [ 0 0x90 49 1 127 ] [ 0 0x80 49 1 127 ] 16 "3" [ 0 0x90 2 1 127 ] [ 0 0x90 2 1 127 ] [ 0 0x80 2 1 127 ] 17 "e" [ 0 0x90 18 1 127 ] [ 0 0x90 18 1 127 ] [ 0 0x80 18 1 127 ] 18 "d" [ 0 0x90 34 1 127 ] [ 0 0x90 34 1 127 ] [ 0 0x80 34 1 127 ] 19 "c" [ 0 0x90 50 1 127 ] [ 0 0x90 50 1 127 ] [ 0 0x80 50 1 127 ] 24 "4" [ 0 0x90 3 1 127 ] [ 0 0x90 3 1 127 ] [ 0 0x80 3 1 127 ] 25 "r" [ 0 0x90 19 1 127 ] [ 0 0x90 19 1 127 ] [ 0 0x80 19 1 127 ] 26 "f" [ 0 0x90 35 1 127 ] [ 0 0x90 35 1 127 ] [ 0 0x80 35 1 127 ] 27 "v" [ 0 0x90 51 1 127 ] [ 0 0x90 51 1 127 ] [ 0 0x80 51 1 127 ] 32 "5" [ 0 0x90 4 1 127 ] [ 0 0x90 4 1 127 ] [ 0 0x80 4 1 127 ] 33 "t" [ 0 0x90 20 1 127 ] [ 0 0x90 20 1 127 ] [ 0 0x80 20 1 127 ] 34 "g" [ 0 0x90 36 1 127 ] [ 0 0x90 36 1 127 ] [ 0 0x80 36 1 127 ] 35 "b" [ 0 0x90 52 1 127 ] [ 0 0x90 52 1 127 ] [ 0 0x80 52 1 127 ] 40 "6" [ 0 0x90 5 1 127 ] [ 0 0x90 5 1 127 ] [ 0 0x80 5 1 127 ] 41 "y" [ 0 0x90 21 1 127 ] [ 0 0x90 21 1 127 ] [ 0 0x80 21 1 127 ] 42 "h" [ 0 0x90 37 1 127 ] [ 0 0x90 37 1 127 ] [ 0 0x80 37 1 127 ] 43 "n" [ 0 0x90 53 1 127 ] [ 0 0x90 53 1 127 ] [ 0 0x80 53 1 127 ] 48 "7" [ 0 0x90 6 1 127 ] [ 0 0x90 6 1 127 ] [ 0 0x80 6 1 127 ] 49 "u" [ 0 0x90 22 1 127 ] [ 0 0x90 22 1 127 ] [ 0 0x80 22 1 127 ] 50 "j" [ 0 0x90 38 1 127 ] [ 0 0x90 38 1 127 ] [ 0 0x80 38 1 127 ] 51 "m" [ 0 0x90 54 1 127 ] [ 0 0x90 54 1 127 ] [ 0 0x80 54 1 127 ] 56 "8" [ 0 0x90 7 1 127 ] [ 0 0x90 7 1 127 ] [ 0 0x80 7 1 127 ] 57 "i" [ 0 0x90 23 1 127 ] [ 0 0x90 23 1 127 ] [ 0 0x80 23 1 127 ] 58 "k" [ 0 0x90 39 1 127 ] [ 0 0x90 39 1 127 ] [ 0 0x80 39 1 127 ] 59 "," [ 0 0x90 55 1 127 ] [ 0 0x90 55 1 127 ] [ 0 0x80 55 1 127 ] # --------------- 4 "~" [ 0 0x90 64 1 127 ] [ 0 0x90 64 1 127 ] [ 0 0x80 64 1 127 ] 5 "~" [ 0 0x90 80 1 127 ] [ 0 0x90 80 1 127 ] [ 0 0x80 80 1 127 ] 6 "~" [ 0 0x90 96 1 127 ] [ 0 0x90 96 1 127 ] [ 0 0x80 96 1 127 ] 7 "~" [ 0 0x90 112 1 127 ] [ 0 0x90 112 1 127 ] [ 0 0x80 112 1 127 ] 12 "~" [ 0 0x90 65 1 127 ] [ 0 0x90 65 1 127 ] [ 0 0x80 65 1 127 ] 13 "~" [ 0 0x90 81 1 127 ] [ 0 0x90 81 1 127 ] [ 0 0x80 81 1 127 ] 14 "~" [ 0 0x90 97 1 127 ] [ 0 0x90 97 1 127 ] [ 0 0x80 97 1 127 ] 15 "~" [ 0 0x90 113 1 127 ] [ 0 0x90 113 1 127 ] [ 0 0x80 113 1 127 ] 20 "~" [ 0 0x90 66 1 127 ] [ 0 0x90 66 1 127 ] [ 0 0x80 66 1 127 ] 21 "~" [ 0 0x90 82 1 127 ] [ 0 0x90 82 1 127 ] [ 0 0x80 82 1 127 ] 22 "~" [ 0 0x90 98 1 127 ] [ 0 0x90 98 1 127 ] [ 0 0x80 98 1 127 ] 23 "~" [ 0 0x90 114 1 127 ] [ 0 0x90 114 1 127 ] [ 0 0x80 114 1 127 ] 28 "~" [ 0 0x90 67 1 127 ] [ 0 0x90 67 1 127 ] [ 0 0x80 67 1 127 ] 29 "~" [ 0 0x90 83 1 127 ] [ 0 0x90 83 1 127 ] [ 0 0x80 83 1 127 ] 30 "~" [ 0 0x90 99 1 127 ] [ 0 0x90 99 1 127 ] [ 0 0x80 99 1 127 ] 31 "~" [ 0 0x90 115 1 127 ] [ 0 0x90 115 1 127 ] [ 0 0x80 115 1 127 ] 36 "~" [ 0 0x90 68 1 127 ] [ 0 0x90 68 1 127 ] [ 0 0x80 68 1 127 ] 37 "~" [ 0 0x90 84 1 127 ] [ 0 0x90 84 1 127 ] [ 0 0x80 84 1 127 ] 38 "~" [ 0 0x90 100 1 127 ] [ 0 0x90 100 1 127 ] [ 0 0x80 100 1 127 ] 39 "~" [ 0 0x90 116 1 127 ] [ 0 0x90 116 1 127 ] [ 0 0x80 116 1 127 ] 44 "~" [ 0 0x90 69 1 127 ] [ 0 0x90 69 1 127 ] [ 0 0x80 69 1 127 ] 45 "~" [ 0 0x90 85 1 127 ] [ 0 0x90 85 1 127 ] [ 0 0x80 85 1 127 ] 46 "~" [ 0 0x90 101 1 127 ] [ 0 0x90 101 1 127 ] [ 0 0x80 101 1 127 ] 47 "~" [ 0 0x90 117 1 127 ] [ 0 0x90 117 1 127 ] [ 0 0x80 117 1 127 ] 52 "~" [ 0 0x90 70 1 127 ] [ 0 0x90 70 1 127 ] [ 0 0x80 70 1 127 ] 53 "~" [ 0 0x90 86 1 127 ] [ 0 0x90 86 1 127 ] [ 0 0x80 86 1 127 ] 54 "~" [ 0 0x90 102 1 127 ] [ 0 0x90 102 1 127 ] [ 0 0x80 102 1 127 ] 55 "~" [ 0 0x90 118 1 127 ] [ 0 0x90 118 1 127 ] [ 0 0x80 118 1 127 ] 60 "~" [ 0 0x90 71 1 127 ] [ 0 0x90 71 1 127 ] [ 0 0x80 71 1 127 ] 61 "~" [ 0 0x90 87 1 127 ] [ 0 0x90 87 1 127 ] [ 0 0x80 87 1 127 ] 62 "~" [ 0 0x90 103 1 127 ] [ 0 0x90 103 1 127 ] [ 0 0x80 103 1 127 ] 63 "~" [ 0 0x90 119 1 127 ] [ 0 0x90 119 1 127 ] [ 0 0x80 119 1 127 ] [mute-group-control 0 "!" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 0 1 "Q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 1 2 "A" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 2 3 "Z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 3 4 "@" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 4 5 "W" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 5 6 "S" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 6 7 "X" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 7 8 "#" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 8 9 "E" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 9 10 "D" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 10 11 "C" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 11 12 "$" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 12 13 "R" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 13 14 "F" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 14 15 "V" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 15 16 "%" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 16 17 "T" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 17 18 "G" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 18 19 "B" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 19 20 "^" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 20 21 "Y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 21 22 "H" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 22 23 "N" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 23 24 "&" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 24 25 "U" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 25 26 "J" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 26 27 "M" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 27 28 "*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 28 29 "I" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 29 30 "K" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 30 31 "<" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 31 [automation-control] 0 "'" [ 0 0x00 0 0 0 ] [ 0 0xb0 104 127 127 ] [ 0 0xb0 104 127 127 ] # BPM Up 1 ";" [ 0 0x00 0 0 0 ] [ 0 0xb0 105 127 127 ] [ 0 0xb0 105 127 127 ] # BPM Dn 2 "]" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Set Up 3 "[" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Set Dn 4 "KP_Home" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Replace 5 "Ins" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Snapshot 6 "o" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Queue 7 "`" [ 0 0x00 0 0 0 ] [ 0 0x90 8 1 127 ] [ 0 0x80 8 1 127 ] # Group Mute 8 "l" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Group Learn 9 "Home" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Playing Set 10 "." [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Playback 11 "P" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Record 12 "BkSpace" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Solo 13 "KP_/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Thru 14 "PageUp" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Up 15 "PageDn" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Dn 16 "KP_." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Set 17 "KP_*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record 18 "KP_-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quan Record 19 "KP_+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reset Seq 20 "|" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # One-shot 21 "F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # FF 22 "F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Rewind 23 "F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Top 24 "F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play List 25 "F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play Song 26 "F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Tap BPM 27 "Space" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Start 28 "Esc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Stop 29 "KP_Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Snapshot_2 30 "F8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Mute 31 "F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Pos 32 "\" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Keep Queue 33 "/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Slot Shift 34 "0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mutes Clear 35 "Null_f1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 35 36 "=" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop Edit 37 "-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Event Edit 38 "F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Mode 39 "F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle JACK 40 "F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Menu Mode 41 "F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Follow JACK 42 "~" [ 0 0x00 0 0 0 ] [ 0 0xb0 104 1 127 ] [ 0 0x00 0 0 0 ] # Panic 43 "0xf9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Visibility 44 "0xfa" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 44 45 "0xfb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 45 46 "0xfc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 47 "0xfd" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 48 "0xfe" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 48 [midi-control-out-settings] set-size = 32 output-buss = 5 midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 [midi-control-out] # --------------------- Pattern number (as applicable) # | ---------------- MIDI status+channel (e.g. Note On) # | | ------------ data 1 (e.g. note number) # | | | ---------- data 2 (e.g. velocity) # | | | | # v v v v # 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0] # Arm Mute Queue Delete # # This is a change (2021-02-10) from version 1 of this file. # A test of the status/event byte determines the enabled status, # and channel is incorporated into the status. Much cleaner! # The order of the lines that follow must must be preserved. 0 [ 0x90 0 60 ] [ 0x90 0 15 ] [ 0x90 0 62 ] [ 0x90 0 12 ] 1 [ 0x90 16 60 ] [ 0x90 16 15 ] [ 0x90 16 62 ] [ 0x90 16 12 ] 2 [ 0x90 32 60 ] [ 0x90 32 15 ] [ 0x90 32 62 ] [ 0x90 32 12 ] 3 [ 0x90 48 60 ] [ 0x90 48 15 ] [ 0x90 48 62 ] [ 0x90 48 12 ] 4 [ 0x90 64 60 ] [ 0x90 64 15 ] [ 0x90 64 62 ] [ 0x90 64 12 ] 5 [ 0x90 80 60 ] [ 0x90 80 15 ] [ 0x90 80 62 ] [ 0x90 80 12 ] 6 [ 0x90 96 60 ] [ 0x90 96 15 ] [ 0x90 96 62 ] [ 0x90 96 12 ] 7 [ 0x90 112 60 ] [ 0x90 112 15 ] [ 0x90 112 62 ] [ 0x90 112 12 ] 8 [ 0x90 1 60 ] [ 0x90 1 15 ] [ 0x90 1 62 ] [ 0x90 1 12 ] 9 [ 0x90 17 60 ] [ 0x90 17 15 ] [ 0x90 17 62 ] [ 0x90 17 12 ] 10 [ 0x90 33 60 ] [ 0x90 33 15 ] [ 0x90 33 62 ] [ 0x90 33 12 ] 11 [ 0x90 49 60 ] [ 0x90 49 15 ] [ 0x90 49 62 ] [ 0x90 49 12 ] 12 [ 0x90 65 60 ] [ 0x90 65 15 ] [ 0x90 65 62 ] [ 0x90 65 12 ] 13 [ 0x90 81 60 ] [ 0x90 81 15 ] [ 0x90 81 62 ] [ 0x90 81 12 ] 14 [ 0x90 97 60 ] [ 0x90 97 15 ] [ 0x90 97 62 ] [ 0x90 97 12 ] 15 [ 0x90 113 60 ] [ 0x90 113 15 ] [ 0x90 113 62 ] [ 0x90 113 12 ] 16 [ 0x90 2 60 ] [ 0x90 2 15 ] [ 0x90 2 62 ] [ 0x90 2 12 ] 17 [ 0x90 18 60 ] [ 0x90 18 15 ] [ 0x90 18 62 ] [ 0x90 18 12 ] 18 [ 0x90 34 60 ] [ 0x90 34 15 ] [ 0x90 34 62 ] [ 0x90 34 12 ] 19 [ 0x90 50 60 ] [ 0x90 50 15 ] [ 0x90 50 62 ] [ 0x90 50 12 ] 20 [ 0x90 66 60 ] [ 0x90 66 15 ] [ 0x90 66 62 ] [ 0x90 66 12 ] 21 [ 0x90 82 60 ] [ 0x90 82 15 ] [ 0x90 82 62 ] [ 0x90 82 12 ] 22 [ 0x90 98 60 ] [ 0x90 98 15 ] [ 0x90 98 62 ] [ 0x90 98 12 ] 23 [ 0x90 114 60 ] [ 0x90 114 15 ] [ 0x90 114 62 ] [ 0x90 114 12 ] 24 [ 0x90 3 60 ] [ 0x90 3 15 ] [ 0x90 3 62 ] [ 0x90 3 12 ] 25 [ 0x90 19 60 ] [ 0x90 19 15 ] [ 0x90 19 62 ] [ 0x90 19 12 ] 26 [ 0x90 35 60 ] [ 0x90 35 15 ] [ 0x90 35 62 ] [ 0x90 35 12 ] 27 [ 0x90 51 60 ] [ 0x90 51 15 ] [ 0x90 51 62 ] [ 0x90 51 12 ] 28 [ 0x90 67 60 ] [ 0x90 67 15 ] [ 0x90 67 62 ] [ 0x90 67 12 ] 29 [ 0x90 83 60 ] [ 0x90 83 15 ] [ 0x90 83 62 ] [ 0x90 83 12 ] 30 [ 0x90 99 60 ] [ 0x90 99 15 ] [ 0x90 99 62 ] [ 0x90 99 12 ] 31 [ 0x90 115 60 ] [ 0x90 115 15 ] [ 0x90 115 62 ] [ 0x90 115 12 ] 32 [ 0x90 4 60 ] [ 0x90 4 15 ] [ 0x90 4 62 ] [ 0x90 4 12 ] 33 [ 0x90 20 60 ] [ 0x90 20 15 ] [ 0x90 20 62 ] [ 0x90 20 12 ] 34 [ 0x90 36 60 ] [ 0x90 36 15 ] [ 0x90 36 62 ] [ 0x90 36 12 ] 35 [ 0x90 52 60 ] [ 0x90 52 15 ] [ 0x90 52 62 ] [ 0x90 52 12 ] 36 [ 0x90 68 60 ] [ 0x90 68 15 ] [ 0x90 68 62 ] [ 0x90 68 12 ] 37 [ 0x90 84 60 ] [ 0x90 84 15 ] [ 0x90 84 62 ] [ 0x90 84 12 ] 38 [ 0x90 100 60 ] [ 0x90 100 15 ] [ 0x90 100 62 ] [ 0x90 100 12 ] 39 [ 0x90 116 60 ] [ 0x90 116 15 ] [ 0x90 116 62 ] [ 0x90 116 12 ] 40 [ 0x90 5 60 ] [ 0x90 5 15 ] [ 0x90 5 62 ] [ 0x90 5 12 ] 41 [ 0x90 21 60 ] [ 0x90 21 15 ] [ 0x90 21 62 ] [ 0x90 21 12 ] 42 [ 0x90 37 60 ] [ 0x90 37 15 ] [ 0x90 37 62 ] [ 0x90 37 12 ] 43 [ 0x90 53 60 ] [ 0x90 53 15 ] [ 0x90 53 62 ] [ 0x90 53 12 ] 44 [ 0x90 69 60 ] [ 0x90 69 15 ] [ 0x90 69 62 ] [ 0x90 69 12 ] 45 [ 0x90 85 60 ] [ 0x90 85 15 ] [ 0x90 85 62 ] [ 0x90 85 12 ] 46 [ 0x90 101 60 ] [ 0x90 101 15 ] [ 0x90 101 62 ] [ 0x90 101 12 ] 47 [ 0x90 117 60 ] [ 0x90 117 15 ] [ 0x90 117 62 ] [ 0x90 117 12 ] 48 [ 0x90 6 60 ] [ 0x90 6 15 ] [ 0x90 6 62 ] [ 0x90 6 12 ] 49 [ 0x90 22 60 ] [ 0x90 22 15 ] [ 0x90 22 62 ] [ 0x90 22 12 ] 50 [ 0x90 38 60 ] [ 0x90 38 15 ] [ 0x90 38 62 ] [ 0x90 38 12 ] 51 [ 0x90 54 60 ] [ 0x90 54 15 ] [ 0x90 54 62 ] [ 0x90 54 12 ] 52 [ 0x90 70 60 ] [ 0x90 70 15 ] [ 0x90 70 62 ] [ 0x90 70 12 ] 53 [ 0x90 86 60 ] [ 0x90 86 15 ] [ 0x90 86 62 ] [ 0x90 86 12 ] 54 [ 0x90 102 60 ] [ 0x90 102 15 ] [ 0x90 102 62 ] [ 0x90 102 12 ] 55 [ 0x90 118 60 ] [ 0x90 118 15 ] [ 0x90 118 62 ] [ 0x90 118 12 ] 56 [ 0x90 7 60 ] [ 0x90 7 15 ] [ 0x90 7 62 ] [ 0x90 7 12 ] 57 [ 0x90 23 60 ] [ 0x90 23 15 ] [ 0x90 23 62 ] [ 0x90 23 12 ] 58 [ 0x90 39 60 ] [ 0x90 39 15 ] [ 0x90 39 62 ] [ 0x90 39 12 ] 59 [ 0x90 55 60 ] [ 0x90 55 15 ] [ 0x90 55 62 ] [ 0x90 55 12 ] 60 [ 0x90 71 60 ] [ 0x90 71 15 ] [ 0x90 71 62 ] [ 0x90 71 12 ] 61 [ 0x90 87 60 ] [ 0x90 87 15 ] [ 0x90 87 62 ] [ 0x90 87 12 ] 62 [ 0x90 103 60 ] [ 0x90 103 15 ] [ 0x90 103 62 ] [ 0x90 103 12 ] 63 [ 0x90 119 60 ] [ 0x90 119 15 ] [ 0x90 119 62 ] [ 0x90 119 12 ] [mute-control-out] # The format of the mute and automation output events is simpler: # # ---------------------- mute-group number # | ------------------ MIDI status+channel (e.g. Note On) # | | -------------- data 1 (e.g. note number) # | | | ------------ data 2 (e.g. velocity) # | | | | # v v v v # 1 [0x00 0 0 ] [0x00 0 0] [0x00 0 0] # On Off Empty (dark) # # The mute-controls have an additional stanza for non-populated # ("deleted") mute-groups. 0 [ 0x90 64 60 ] [ 0x90 64 15 ] [ 0x90 64 12 ] 1 [ 0x90 80 60 ] [ 0x90 80 15 ] [ 0x90 80 12 ] 2 [ 0x90 96 60 ] [ 0x90 96 15 ] [ 0x90 96 12 ] 3 [ 0x90 112 60 ] [ 0x90 112 15 ] [ 0x90 112 12 ] 4 [ 0x90 65 60 ] [ 0x90 65 15 ] [ 0x90 65 12 ] 5 [ 0x90 81 60 ] [ 0x90 81 15 ] [ 0x90 81 12 ] 6 [ 0x90 97 60 ] [ 0x90 97 15 ] [ 0x90 97 12 ] 7 [ 0x90 113 60 ] [ 0x90 113 15 ] [ 0x90 113 12 ] 8 [ 0x90 66 60 ] [ 0x90 66 15 ] [ 0x90 66 12 ] 9 [ 0x90 82 60 ] [ 0x90 82 15 ] [ 0x90 82 12 ] 10 [ 0x90 98 60 ] [ 0x90 98 15 ] [ 0x90 98 12 ] 11 [ 0x90 114 60 ] [ 0x90 114 15 ] [ 0x90 114 12 ] 12 [ 0x90 67 60 ] [ 0x90 67 15 ] [ 0x90 67 12 ] 13 [ 0x90 83 60 ] [ 0x90 83 15 ] [ 0x90 83 12 ] 14 [ 0x90 99 60 ] [ 0x90 99 15 ] [ 0x90 99 12 ] 15 [ 0x90 115 60 ] [ 0x90 115 15 ] [ 0x90 115 12 ] 16 [ 0x90 68 60 ] [ 0x90 68 15 ] [ 0x90 68 12 ] 17 [ 0x90 84 60 ] [ 0x90 84 15 ] [ 0x90 84 12 ] 18 [ 0x90 100 60 ] [ 0x90 100 15 ] [ 0x90 100 12 ] 19 [ 0x90 116 60 ] [ 0x90 116 15 ] [ 0x90 116 12 ] 20 [ 0x90 69 60 ] [ 0x90 69 15 ] [ 0x90 69 12 ] 21 [ 0x90 85 60 ] [ 0x90 85 15 ] [ 0x90 85 12 ] 22 [ 0x90 101 60 ] [ 0x90 101 15 ] [ 0x90 101 12 ] 23 [ 0x90 117 60 ] [ 0x90 117 15 ] [ 0x90 117 12 ] 24 [ 0x90 70 60 ] [ 0x90 70 15 ] [ 0x90 70 12 ] 25 [ 0x90 86 60 ] [ 0x90 86 15 ] [ 0x90 86 12 ] 26 [ 0x90 102 60 ] [ 0x90 102 15 ] [ 0x90 102 12 ] 27 [ 0x90 118 60 ] [ 0x90 118 15 ] [ 0x90 118 12 ] 28 [ 0x90 71 60 ] [ 0x90 71 15 ] [ 0x90 71 12 ] 29 [ 0x90 87 60 ] [ 0x90 87 15 ] [ 0x90 87 12 ] 30 [ 0x90 103 60 ] [ 0x90 103 15 ] [ 0x90 103 12 ] 31 [ 0x90 119 60 ] [ 0x90 119 15 ] [ 0x90 119 12 ] [automation-control-out] # This format is similar to the [mute-control-out] format, but # the first number is an active-flag, not an index number. # The stanzas are on/off/inactive, except for 'snap', which is # store/restore/inactive. 1 [ 0xb0 104 60 ] [ 0xb0 104 15 ] [ 0xb0 104 12 ] # Panic 1 [ 0xb0 105 60 ] [ 0xb0 105 15 ] [ 0xb0 105 12 ] # Stop 1 [ 0xb0 106 60 ] [ 0xb0 106 15 ] [ 0xb0 106 12 ] # Pause 1 [ 0xb0 107 60 ] [ 0xb0 107 15 ] [ 0xb0 107 12 ] # Play 1 [ 0xb0 108 60 ] [ 0xb0 108 15 ] [ 0xb0 108 12 ] # Toggle_mutes 1 [ 0xb0 109 60 ] [ 0xb0 109 15 ] [ 0xb0 109 12 ] # Song_record 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Slot_shift 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Free 1 [ 0x90 8 60 ] [ 0x90 8 15 ] [ 0x90 8 12 ] # Queue 1 [ 0x90 24 60 ] [ 0x90 24 15 ] [ 0x90 24 12 ] # Oneshot 1 [ 0x90 40 60 ] [ 0x90 40 15 ] [ 0x90 40 12 ] # Replace 1 [ 0x90 56 60 ] [ 0x90 56 15 ] [ 0x90 56 12 ] # Snap 1 [ 0x90 72 60 ] [ 0x90 72 15 ] [ 0x90 72 12 ] # Song 1 [ 0x90 88 60 ] [ 0x90 88 15 ] [ 0x90 88 12 ] # Learn 1 [ 0x90 104 60 ] [ 0x90 104 15 ] [ 0x90 104 12 ] # BPM_Up 1 [ 0x90 120 60 ] [ 0x90 120 15 ] [ 0x90 120 12 ] # BPM_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Dn 1 [ 0xb0 110 60 ] [ 0xb0 110 15 ] [ 0xb0 110 12 ] # Set_Up 1 [ 0xb0 111 60 ] [ 0xb0 111 15 ] [ 0xb0 111 12 ] # Set_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Tap_BPM 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Quit 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Visibility 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_2 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_3 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_4 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_5 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_6 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_7 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_8 # End of /home/user/.config/seq66/qseq66-lp-mini-8x8.ctrl # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66-lp-mini-alt.ctrl ================================================ # Seq66 0.99.21 MIDI control configuration file # # /home/ahlstrom/.config/seq66/qseq66-lp-mini-alt.ctrl # Written 2025-07-10 07:49:53 # # Sets up MIDI I/O control. The format is like the 'rc' file. To use it, set it # active in the 'rc' [midi-control-file] section. It adds loop, mute, & # automation buttons, MIDI display, new settings, and macros. [Seq66] config-type = "ctrl" version = 6 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] This file is similar to qseq66-lp-mini.ctrl, except that is uses the buttons for mute-groups 24 to 31 for handling playlists and sets, allowing 8 fewer mute-groups in return for enhanced automation control. As a quick note, the following color numbers are used: 12 = Off 15 = Red 46 = Amber 60 = Green 62 = Yellow 63 = Amber See doc/launchpad-mini.ods for the mappings implemented by this 'ctrl' file. [midi-control-settings] # Input settings to control Seq66. 'control-buss' ranges from 0 to the highest # system input buss. If set, that buss can send MIDI control. 255 (0xFF) means # any ENABLED MIDI input can send control. ALSA has an extra 'announce' buss, # so add 1 to the port number with ALSA. With port-mapping enabled, the port # nick-name can be provided. # # 'midi-enabled' applies to the MIDI controls; keystroke controls are always # enabled. Supported keyboard layouts are 'qwerty' (default), 'qwertz', and # 'azerty'. AZERTY turns off auto-shift for group-learn. drop-empty-controls = false control-buss = "Launchpad Mini MIDI 1" midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 keyboard-layout = qwerty # A control stanza sets key and MIDI control. Keys support 'toggle', and # key-release is 'invert'. The leftmost number on each line is the loop number # (0 to 31), mutes number (same range), or an automation number. 3 groups of # of bracketed numbers follow, each providing a type of control: # # Normal: [toggle] [on] [off] # Increment/Decr: [increment] [increment] [decrement] # Playback: [pause] [start] [stop] # Playlist/Song: [by-value] [next] [previous] # # In each group, there are 5 numbers: # # [invert status d0 d1min d1max] # # A valid status (> 0x00) enables the control; 'invert' (1/0) inverts the, # the action, but not all support this. 'status' is the MIDI event to match # (channel is NOT ignored); 'd0' is the status value (eg. if 0x90, Note On, # d0 is the note number; d1min to d1max is the range of d1 values detectable. # Hex values can be used; precede with '0x'. # # ------------------------ Loop/group/automation-slot number # | -------------------- Name of key (see the key map) # | | -------------- Inverse # | | | ---------- MIDI status/event byte (eg. Note On) # | | | | ------- d0: Data 1 (eg. Note number) # | | | | | ----- d1max: Data 2 min (eg. Note velocity) # | | | | | | -- d1min: Data 2 max # | | | | | | | # v v v v v v v # 0 "F1" [0 0x90 0 1 127] [0 0x00 0 0 0] [0 0x00 0 0 0] # Toggle On Off # # MIDI controls often send a Note On upon a press and a Note Off on release. # To use a control as a toggle, define only the Toggle stanza. For the control # to act only while held, define the On and Off stanzas with appropriate # statuses for press-and-release. # # Warning: the 'BS' key is actually the Ctrl-H key, and NOT the Backspace key. # The Backspace key is called 'BkSpace' in the Seq66 key-map. [loop-control] 0 "1" [ 0 0x90 0 1 127 ] [ 0 0x00 0 1 127 ] [ 0 0x00 0 1 127 ] # Loop 0 1 "q" [ 0 0x90 16 1 127 ] [ 0 0x00 16 1 127 ] [ 0 0x00 16 1 127 ] # Loop 1 2 "a" [ 0 0x90 32 1 127 ] [ 0 0x00 32 1 127 ] [ 0 0x00 32 1 127 ] # Loop 2 3 "z" [ 0 0x90 48 1 127 ] [ 0 0x00 48 1 127 ] [ 0 0x00 48 1 127 ] # Loop 3 4 "2" [ 0 0x90 1 1 127 ] [ 0 0x00 1 1 127 ] [ 0 0x00 1 1 127 ] # Loop 4 5 "w" [ 0 0x90 17 1 127 ] [ 0 0x00 17 1 127 ] [ 0 0x00 17 1 127 ] # Loop 5 6 "s" [ 0 0x90 33 1 127 ] [ 0 0x00 33 1 127 ] [ 0 0x00 33 1 127 ] # Loop 6 7 "x" [ 0 0x90 49 1 127 ] [ 0 0x00 49 1 127 ] [ 0 0x00 49 1 127 ] # Loop 7 8 "3" [ 0 0x90 2 1 127 ] [ 0 0x00 2 1 127 ] [ 0 0x00 2 1 127 ] # Loop 8 9 "e" [ 0 0x90 18 1 127 ] [ 0 0x00 18 1 127 ] [ 0 0x00 18 1 127 ] # Loop 9 10 "d" [ 0 0x90 34 1 127 ] [ 0 0x00 34 1 127 ] [ 0 0x00 34 1 127 ] # Loop 10 11 "c" [ 0 0x90 50 1 127 ] [ 0 0x00 50 1 127 ] [ 0 0x00 50 1 127 ] # Loop 11 12 "4" [ 0 0x90 3 1 127 ] [ 0 0x00 3 1 127 ] [ 0 0x00 3 1 127 ] # Loop 12 13 "r" [ 0 0x90 19 1 127 ] [ 0 0x00 19 1 127 ] [ 0 0x00 19 1 127 ] # Loop 13 14 "f" [ 0 0x90 35 1 127 ] [ 0 0x00 35 1 127 ] [ 0 0x00 35 1 127 ] # Loop 14 15 "v" [ 0 0x90 51 1 127 ] [ 0 0x00 51 1 127 ] [ 0 0x00 51 1 127 ] # Loop 15 16 "5" [ 0 0x90 4 1 127 ] [ 0 0x00 4 1 127 ] [ 0 0x00 4 1 127 ] # Loop 16 17 "t" [ 0 0x90 20 1 127 ] [ 0 0x00 20 1 127 ] [ 0 0x00 20 1 127 ] # Loop 17 18 "g" [ 0 0x90 36 1 127 ] [ 0 0x00 36 1 127 ] [ 0 0x00 36 1 127 ] # Loop 18 19 "b" [ 0 0x90 52 1 127 ] [ 0 0x00 52 1 127 ] [ 0 0x00 52 1 127 ] # Loop 19 20 "6" [ 0 0x90 5 1 127 ] [ 0 0x00 5 1 127 ] [ 0 0x00 5 1 127 ] # Loop 20 21 "y" [ 0 0x90 21 1 127 ] [ 0 0x00 21 1 127 ] [ 0 0x00 21 1 127 ] # Loop 21 22 "h" [ 0 0x90 37 1 127 ] [ 0 0x00 37 1 127 ] [ 0 0x00 37 1 127 ] # Loop 22 23 "n" [ 0 0x90 53 1 127 ] [ 0 0x00 53 1 127 ] [ 0 0x00 53 1 127 ] # Loop 23 24 "7" [ 0 0x90 6 1 127 ] [ 0 0x00 6 1 127 ] [ 0 0x00 6 1 127 ] # Loop 24 25 "u" [ 0 0x90 22 1 127 ] [ 0 0x00 22 1 127 ] [ 0 0x00 22 1 127 ] # Loop 25 26 "j" [ 0 0x90 38 1 127 ] [ 0 0x00 38 1 127 ] [ 0 0x00 38 1 127 ] # Loop 26 27 "m" [ 0 0x90 54 1 127 ] [ 0 0x00 54 1 127 ] [ 0 0x00 54 1 127 ] # Loop 27 28 "8" [ 0 0x90 7 1 127 ] [ 0 0x00 7 1 127 ] [ 0 0x00 7 1 127 ] # Loop 28 29 "i" [ 0 0x90 23 1 127 ] [ 0 0x00 23 1 127 ] [ 0 0x00 23 1 127 ] # Loop 29 30 "k" [ 0 0x90 39 1 127 ] [ 0 0x00 39 1 127 ] [ 0 0x00 39 1 127 ] # Loop 30 31 "," [ 0 0x90 55 1 127 ] [ 0 0x00 55 1 127 ] [ 0 0x00 55 1 127 ] # Loop 31 [mute-group-control] 0 "!" [ 0 0x90 64 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 0 1 "Q" [ 0 0x90 80 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 1 2 "A" [ 0 0x90 96 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 2 3 "Z" [ 0 0x90 112 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 3 4 "@" [ 0 0x90 65 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 4 5 "W" [ 0 0x90 81 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 5 6 "S" [ 0 0x90 97 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 6 7 "X" [ 0 0x90 113 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 7 8 "#" [ 0 0x90 66 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 8 9 "E" [ 0 0x90 82 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 9 10 "D" [ 0 0x90 98 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 10 11 "C" [ 0 0x90 114 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 11 12 "$" [ 0 0x90 67 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 12 13 "R" [ 0 0x90 83 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 13 14 "F" [ 0 0x90 99 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 14 15 "V" [ 0 0x90 115 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 15 16 "%" [ 0 0x90 68 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 16 17 "T" [ 0 0x90 84 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 17 18 "G" [ 0 0x90 100 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 18 19 "B" [ 0 0x90 116 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 19 20 "^" [ 0 0x90 69 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 20 21 "Y" [ 0 0x90 85 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 21 22 "H" [ 0 0x90 101 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 22 23 "N" [ 0 0x90 117 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 23 24 "&" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 24 25 "U" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 25 26 "J" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 26 27 "M" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 27 28 "*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 28 29 "I" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 29 30 "K" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 30 31 "<" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 31 [automation-control] 0 "'" [ 0 0x00 0 0 0 ] [ 0 0x90 103 1 127 ] [ 0 0x00 0 0 0 ] # BPM Up 1 ";" [ 0 0x00 0 0 0 ] [ 0 0x90 119 1 127 ] [ 0 0x00 0 0 0 ] # BPM Dn 2 "]" [ 0 0x00 0 0 0 ] [ 0 0x90 71 1 127 ] [ 0 0x00 0 0 0 ] # Set Up 3 "[" [ 0 0x00 0 0 0 ] [ 0 0x90 87 1 127 ] [ 0 0x00 0 0 0 ] # Set Dn 4 "KP_Home" [ 0 0x00 0 0 0 ] [ 0 0x90 40 1 127 ] [ 0 0x00 0 0 0 ] # Replace 5 "Ins" [ 0 0x00 0 0 0 ] [ 0 0x90 56 1 127 ] [ 0 0x00 0 0 0 ] # Snapshot 6 "o" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Queue 7 "`" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Mute 8 "l" [ 0 0x00 0 0 0 ] [ 0 0x90 88 1 127 ] [ 0 0x00 0 0 0 ] # Group Learn 9 "Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playing Set 10 "." [ 0 0xb0 106 127 127 ] [ 0 0xb0 107 1 127 ] [ 0 0xb0 105 1 127 ] # Playback 11 "P" [ 0 0xb0 109 127 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Record 12 "BkSpace" [ 0 0x00 0 0 0 ] [ 0 0x90 40 1 127 ] [ 0 0x00 0 0 0 ] # Solo 13 "KP_/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Thru 14 "PageUp" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Up 15 "PageDn" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Dn 16 "KP_." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Set 17 "KP_*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record Style 18 "KP_-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quan Record 19 "KP_+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reset Sets 20 "|" [ 0 0x00 0 0 0 ] [ 0 0x90 24 1 127 ] [ 0 0x00 0 0 0 ] # One-shot 21 "F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # FF 22 "F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Rewind 23 "F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Top 24 "F2" [ 0 0x00 0 0 0 ] [ 0 0x90 70 1 127 ] [ 0 0x90 86 1 127 ] # Play List 25 "F3" [ 0 0x00 0 0 0 ] [ 0 0x90 102 1 127 ] [ 0 0x90 118 1 127 ] # Play Song 26 "F9" [ 0 0x90 104 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Tap BPM 27 "Space" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Start 28 "Esc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Stop 29 "KP_Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop L/R 30 "F8" [ 0 0xb0 108 127 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Mutes 31 "F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Pos 32 "\" [ 0 0x90 8 127 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Keep Queue 33 "/" [ 0 0x00 0 0 0 ] [ 0 0xb0 110 1 127 ] [ 0 0x00 0 0 0 ] # Slot Shift 34 "0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mutes Clear 35 "Quit" [ 0 0x00 0 0 0 ] [ 0 0x90 120 1 127 ] [ 0 0x00 0 0 0 ] # Quit 36 "=" [ 0 0x00 0 0 0 ] [ 0 0xb0 111 1 127 ] [ 0 0x00 0 0 0 ] # Loop Edit 37 "-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Event Edit 38 "F10" [ 0 0x90 72 127 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Mode 39 "F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle JACK 40 "F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Menu Mode 41 "F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Follow JACK 42 "~" [ 0 0x00 0 0 0 ] [ 0 0xb0 104 1 127 ] [ 0 0x00 0 0 0 ] # Panic 43 ">" [ 0 0xb0 111 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Visibility 44 "0xfa" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Save Session 45 "0xfb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record Toggle 46 "0xfc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Mutes 47 "0xfd" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 48 "0xfe" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 48 49 "Sh_F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Overdub 50 "Sh_F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Overwrite 51 "Sh_F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Expand 52 "Sh_F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Oneshot 53 "Sh_F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Loop 54 "Sh_F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Record 55 "Sh_F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Copy 56 "Sh_F8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Paste 57 "Sh_F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Clear 58 "Sh_F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Delete 59 "Sh_F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Thru 60 "Sh_F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Solo 61 "0xe0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Cut 62 "0xe1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Double 63 "0xe2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q None 64 "0xe3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q Full 65 "0xe4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q Tighten 66 "0xe5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Randomize 67 "0xe6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Jitter 68 "0xe7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Note-map 69 "0xe8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BBT/HMS 70 "0xe9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # LR Loop 71 "0xea" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Undo 72 "0xeb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Redo 73 "0xec" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Transpose Song 74 "0xed" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Copy Set 75 "0xee" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Paste Set 76 "0xef" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Tracks 77 "0x8c" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Normal 78 "0x8d" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Auto 79 "0x8e" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Additive 80 "0x8f" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # All Sets [midi-control-out-settings] set-size = 32 output-buss = "Launchpad Mini MIDI 1" midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 [midi-control-out] # This section determines how pattern statuses are to be displayed. # ---------------- Pattern or device-button number) # | ----------- MIDI status+channel (eg. Note On) # | | ------- data 1 (eg. note number) # | | | ----- data 2 (eg. velocity) # | | | | # v v v v # 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0] # Armed Muted (Un)queued Empty/Deleted # # A test of the status byte determines the enabled status, and channel is # included in the status. 0 [ 0x90 0 60 ] [ 0x90 0 15 ] [ 0x90 0 62 ] [ 0x90 0 12 ] 1 [ 0x90 16 60 ] [ 0x90 16 15 ] [ 0x90 16 62 ] [ 0x90 16 12 ] 2 [ 0x90 32 60 ] [ 0x90 32 15 ] [ 0x90 32 62 ] [ 0x90 32 12 ] 3 [ 0x90 48 60 ] [ 0x90 48 15 ] [ 0x90 48 62 ] [ 0x90 48 12 ] 4 [ 0x90 1 60 ] [ 0x90 1 15 ] [ 0x90 1 62 ] [ 0x90 1 12 ] 5 [ 0x90 17 60 ] [ 0x90 17 15 ] [ 0x90 17 62 ] [ 0x90 17 12 ] 6 [ 0x90 33 60 ] [ 0x90 33 15 ] [ 0x90 33 62 ] [ 0x90 33 12 ] 7 [ 0x90 49 60 ] [ 0x90 49 15 ] [ 0x90 49 62 ] [ 0x90 49 12 ] 8 [ 0x90 2 60 ] [ 0x90 2 15 ] [ 0x90 2 62 ] [ 0x90 2 12 ] 9 [ 0x90 18 60 ] [ 0x90 18 15 ] [ 0x90 18 62 ] [ 0x90 18 12 ] 10 [ 0x90 34 60 ] [ 0x90 34 15 ] [ 0x90 34 62 ] [ 0x90 34 12 ] 11 [ 0x90 50 60 ] [ 0x90 50 15 ] [ 0x90 50 62 ] [ 0x90 50 12 ] 12 [ 0x90 3 60 ] [ 0x90 3 15 ] [ 0x90 3 62 ] [ 0x90 3 12 ] 13 [ 0x90 19 60 ] [ 0x90 19 15 ] [ 0x90 19 62 ] [ 0x90 19 12 ] 14 [ 0x90 35 60 ] [ 0x90 35 15 ] [ 0x90 35 62 ] [ 0x90 35 12 ] 15 [ 0x90 51 60 ] [ 0x90 51 15 ] [ 0x90 51 62 ] [ 0x90 51 12 ] 16 [ 0x90 4 60 ] [ 0x90 4 15 ] [ 0x90 4 62 ] [ 0x90 4 12 ] 17 [ 0x90 20 60 ] [ 0x90 20 15 ] [ 0x90 20 62 ] [ 0x90 20 12 ] 18 [ 0x90 36 60 ] [ 0x90 36 15 ] [ 0x90 36 62 ] [ 0x90 36 12 ] 19 [ 0x90 52 60 ] [ 0x90 52 15 ] [ 0x90 52 62 ] [ 0x90 52 12 ] 20 [ 0x90 5 60 ] [ 0x90 5 15 ] [ 0x90 5 62 ] [ 0x90 5 12 ] 21 [ 0x90 21 60 ] [ 0x90 21 15 ] [ 0x90 21 62 ] [ 0x90 21 12 ] 22 [ 0x90 37 60 ] [ 0x90 37 15 ] [ 0x90 37 62 ] [ 0x90 37 12 ] 23 [ 0x90 53 60 ] [ 0x90 53 15 ] [ 0x90 53 62 ] [ 0x90 53 12 ] 24 [ 0x90 6 60 ] [ 0x90 6 15 ] [ 0x90 6 62 ] [ 0x90 6 12 ] 25 [ 0x90 22 60 ] [ 0x90 22 15 ] [ 0x90 22 62 ] [ 0x90 22 12 ] 26 [ 0x90 38 60 ] [ 0x90 38 15 ] [ 0x90 38 62 ] [ 0x90 38 12 ] 27 [ 0x90 54 60 ] [ 0x90 54 15 ] [ 0x90 54 62 ] [ 0x90 54 12 ] 28 [ 0x90 7 60 ] [ 0x90 7 15 ] [ 0x90 7 62 ] [ 0x90 7 12 ] 29 [ 0x90 23 60 ] [ 0x90 23 15 ] [ 0x90 23 62 ] [ 0x90 23 12 ] 30 [ 0x90 39 60 ] [ 0x90 39 15 ] [ 0x90 39 62 ] [ 0x90 39 12 ] 31 [ 0x90 55 60 ] [ 0x90 55 15 ] [ 0x90 55 62 ] [ 0x90 55 12 ] [mute-control-out] # The format of the mute and automation output events is similar: # # ----------------- mute-group number # | ------------- MIDI status+channel (eg. Note On) # | | --------- data 1 (eg. note number) # | | | ------- data 2 (eg. velocity) # | | | | # v v v v # 1 [0x00 0 0 ] [0x00 0 0] [0x00 0 0] # On Off Empty (dark) # # The mute-controls have an additional stanza for non-populated ("deleted") # mute-groups. 0 [ 0x90 64 60 ] [ 0x90 64 15 ] [ 0x90 64 12 ] 1 [ 0x90 80 60 ] [ 0x90 80 15 ] [ 0x90 80 12 ] 2 [ 0x90 96 60 ] [ 0x90 96 15 ] [ 0x90 96 12 ] 3 [ 0x90 112 60 ] [ 0x90 112 15 ] [ 0x90 112 12 ] 4 [ 0x90 65 60 ] [ 0x90 65 15 ] [ 0x90 65 12 ] 5 [ 0x90 81 60 ] [ 0x90 81 15 ] [ 0x90 81 12 ] 6 [ 0x90 97 60 ] [ 0x90 97 15 ] [ 0x90 97 12 ] 7 [ 0x90 113 60 ] [ 0x90 113 15 ] [ 0x90 113 12 ] 8 [ 0x90 66 60 ] [ 0x90 66 15 ] [ 0x90 66 12 ] 9 [ 0x90 82 60 ] [ 0x90 82 15 ] [ 0x90 82 12 ] 10 [ 0x90 98 60 ] [ 0x90 98 15 ] [ 0x90 98 12 ] 11 [ 0x90 114 60 ] [ 0x90 114 15 ] [ 0x90 114 12 ] 12 [ 0x90 67 60 ] [ 0x90 67 15 ] [ 0x90 67 12 ] 13 [ 0x90 83 60 ] [ 0x90 83 15 ] [ 0x90 83 12 ] 14 [ 0x90 99 60 ] [ 0x90 99 15 ] [ 0x90 99 12 ] 15 [ 0x90 115 60 ] [ 0x90 115 15 ] [ 0x90 115 12 ] 16 [ 0x90 68 60 ] [ 0x90 68 15 ] [ 0x90 68 12 ] 17 [ 0x90 84 60 ] [ 0x90 84 15 ] [ 0x90 84 12 ] 18 [ 0x90 100 60 ] [ 0x90 100 15 ] [ 0x90 100 12 ] 19 [ 0x90 116 60 ] [ 0x90 116 15 ] [ 0x90 116 12 ] 20 [ 0x90 69 60 ] [ 0x90 69 15 ] [ 0x90 69 12 ] 21 [ 0x90 85 60 ] [ 0x90 85 15 ] [ 0x90 85 12 ] 22 [ 0x90 101 60 ] [ 0x90 101 15 ] [ 0x90 101 12 ] 23 [ 0x90 117 60 ] [ 0x90 117 15 ] [ 0x90 117 12 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [automation-control-out] # This format is similar to [mute-control-out], but the first number is an # active-flag, not an index number. The stanzas are are on / off / inactive, # except for 'snap', which is store / restore / inactive. 1 [ 0xb0 104 15 ] [ 0xb0 104 15 ] [ 0xb0 104 12 ] # Panic 1 [ 0xb0 105 15 ] [ 0xb0 105 15 ] [ 0xb0 105 12 ] # Stop 1 [ 0xb0 106 62 ] [ 0xb0 106 15 ] [ 0xb0 106 12 ] # Pause 1 [ 0xb0 107 60 ] [ 0xb0 107 15 ] [ 0xb0 107 12 ] # Playback 1 [ 0xb0 108 60 ] [ 0xb0 108 15 ] [ 0xb0 108 12 ] # Toggle Mutes 1 [ 0xb0 109 60 ] [ 0xb0 109 15 ] [ 0xb0 109 12 ] # Song Record 1 [ 0xb0 110 60 ] [ 0xb0 110 15 ] [ 0xb0 110 12 ] # Slot Shift 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Free 1 [ 0x90 8 60 ] [ 0x90 8 15 ] [ 0x90 8 12 ] # Queue 1 [ 0x90 24 60 ] [ 0x90 24 15 ] [ 0x90 24 12 ] # One-shot 1 [ 0x90 40 60 ] [ 0x90 40 15 ] [ 0x90 40 12 ] # Replace 1 [ 0x90 56 60 ] [ 0x90 56 15 ] [ 0x90 56 12 ] # Snapshot 1 [ 0x90 72 60 ] [ 0x90 72 15 ] [ 0x90 72 12 ] # Song Mode 1 [ 0x90 88 63 ] [ 0x90 88 15 ] [ 0x90 88 12 ] # Group Learn 1 [ 0x90 103 60 ] [ 0x90 103 60 ] [ 0x90 103 12 ] # BPM Up 1 [ 0x90 119 62 ] [ 0x90 119 62 ] [ 0x90 119 12 ] # BPM Dn 1 [ 0x90 70 60 ] [ 0x90 70 60 ] [ 0x90 70 12 ] # Play List Up 1 [ 0x90 86 62 ] [ 0x90 86 62 ] [ 0x90 86 12 ] # Play List Dn 1 [ 0x90 102 60 ] [ 0x90 102 60 ] [ 0x90 102 12 ] # Play Song Up 1 [ 0x90 118 62 ] [ 0x90 118 62 ] [ 0x90 118 12 ] # Play Song Dn 1 [ 0x90 71 60 ] [ 0x90 71 60 ] [ 0x90 71 12 ] # Set Up 1 [ 0x90 87 62 ] [ 0x90 87 62 ] [ 0x90 87 12 ] # Set Dn 1 [ 0x90 104 60 ] [ 0x90 104 60 ] [ 0x90 104 12 ] # Tap BPM 1 [ 0x90 120 15 ] [ 0x90 120 15 ] [ 0x90 120 12 ] # Quit 1 [ 0xb0 111 46 ] [ 0xb0 111 46 ] [ 0xb0 111 12 ] # Visibility 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_2 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_3 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_4 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_5 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_6 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_7 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_8 [macro-control-out] # This format is 'macroname = [ hex bytes | macro-references]'. Macro references # are macro-names preceded by a '$'. Some values should always be defined, even # if empty: footer, header, reset, startup, and shutdown. all-leds-high = 0xB0 0x00 0x7F all-leds-low = 0xB0 0x00 0x7D all-leds-med = 0xB0 0x00 0x7E drum-layout = 0xB0 0x00 0x02 duty-cycle-default = 0xB0 0x1E 0x02 footer = 0xF7 header = 0xF0 0x00 0x00 middlec = 0x00 0x80 0x3C 0x40 0x60 0x90 0x3C 0x00 reset = 0xB0 0x00 0x00 shutdown = $header 0x00 $footer startup = $header 0x00 $footer xy-layout = 0xB0 0x00 0x01 # End of /home/ahlstrom/.config/seq66/qseq66-lp-mini-alt.ctrl # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66-lp-mini-swapped.ctrl ================================================ # Seq66 0.94.1 (and above) MIDI control configuration file # # /home/user/.config/seq66/qseq66-lp-mini.ctrl # Written 2021-10-27 08:42:31 # # This file holds MIDI I/O control setups for Seq66. It follows the format # of the 'rc' configuration file, but is stored separately for flexibility # It is always stored in the main configuration directory. To use this # file, specify it in the [midi-control-file] section in the 'rc' file. # Use the base-name (e.g. nanomap.ctrl). # Version 1 adds the [mute-control-out] and [automation-control-out] # sections. Versions 2 and 3 simplify the data items. [Seq66] config-type = "ctrl" version = 5 # The [comments] section holds the user's documentation for this file. # The first completely empty, comment, or tag line ends the comment. [comments] This file was created by copying the default qseq66.ctrl file and changing the MIDI control section to the values found in the sequencer64-laumchpad.rc file. This section provides for controlling the following actions: o Patterns 0 to 31. Patterns can be toggled by sending Note Ons on on channel 1. Observe that the Toggle entries are marked active. This works with the Launchpad Mini. o Patterns 0 to 31 alternative. Patterns can be turned on by sending Note Ons on on channel 1, and turned off by sending Note Offs. Make the Toggle entries inactive, and the On and Off entries active to achieve this setup. o Muting 32 to 64 (mute groups 0 to 31). o Automation control 64 to 73. This covers only part of the actions that can be controlled via MIDI in Seq66; we still need to upgrade the rest once we get familiar with our new LaunchPad Mini. See doc/launchpad-mini.ods for the mappings implemented by this 'ctrl' file. The default setup for the LaunchPad Mini is shown in the "Launchpad programmer's reference" file (launchpad-programmers-reference.pdf). Since, by default, the Mini uses Note Ons of velocity 0 instead of Note Offs, we set the data range from 1 to 127 instead of 0 to 127. For MIDI output to the Launchpad Mini, we make the following color mappings: Arm Mute Queue Deleted/Empty Green Red Yellow Off 60 15 62 12 A sample line. Just need to fill in the key (note value) needed for this row and set the pattern number at the left: 2 [1 0 0x90 key 60] [1 0 0x90 key 15] [1 0 0x90 key 62] [1 0 0x90 key 12] Note that this file is NOT FINISHED. We still need to rework it for swapped pattern numbers in the MIDI parts of the [loop-control] and [mute-group-control] sections. [midi-control-settings] # The control-buss value ranges from 0 to the maximum system input buss. If set, # then that buss will be allowed to send MIDI control. 255 (0xFF) means any buss # can send MIDI control. The buss(es) must be enabled in the 'rc' file. With # ALSA, there is an extra 'announce' buss, which will alter the numbering # compared to JACK. # # The 'midi-enabled' flag applies to the MIDI controls; keystrokes are always # enabled. Supported keyboard layouts are 'qwerty' (the default), 'qwertz', and # 'azerty'. AZERTY turns off the auto-shift feature for group-learn. control-buss = 6 midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 keyboard-layout = qwerty # The control stanza incorporates key control as well as MIDI, but # keys support only 'toggle', and key-release is an 'invert'. The # leftmost number on each line here is the pattern number (e.g. # 0 to 31); the group number, same range; or an automation control # number. This internal control number is followed by three groups of # bracketed numbers, each providing three different types of control: # # Normal: [toggle] [on] [off] # Increment/Decr: [increment] [increment] [decrement] # Playback: [pause] [start] [stop] # Playlist/Song: [by-value] [next] [previous] # # In each group, there are six numbers: # # [on/off invert status d0 d1min d1max] # # 'on/off' enables/disables (1/0) the control for the pattern; # 'invert' (1/0) causes the opposite, but not all support this, and # all keystroke-releases set invert to true; 'status' is the MIDI # event to match (channel is NOT ignored), and if set to 0x00, the # control is disabled; 'd0' is the first data value, e.g. if status # is 0x90 (Note On), d0 represents the note number; d1min to d1max # is the range of data values detected, e.g. for a Note On, 1 to 127 # indicate that any non-zero velocity will invoke the control. # Hex values can be used; precede with '0x'. # # ------------------------- Loop, group, or automation-slot number # | ---------------------- Name of the key (see the key map) # | | # | | ---------------- Inverse # | | | -------------- MIDI status/event byte (e.g. Note On) # | | | | ------------ d0: Data 1 (e.g. Note number) # | | | | | ---------- d1max: Data 2 min (e.g. Note velocity) # | | | | | | -------- d1min: Data 2 max # | | | | | | | # v v v v v v v # 0 "F1" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Toggle On Off [loop-control] 0 "1" [ 0 0x90 0 1 127 ] [ 0 0x90 0 1 127 ] [ 0 0x80 0 1 127 ] # Loop 0 1 "2" [ 0 0x90 16 1 127 ] [ 0 0x90 16 1 127 ] [ 0 0x80 16 1 127 ] # Loop 1 2 "3" [ 0 0x90 32 1 127 ] [ 0 0x90 32 1 127 ] [ 0 0x80 32 1 127 ] # Loop 2 3 "4" [ 0 0x90 48 1 127 ] [ 0 0x90 48 1 127 ] [ 0 0x80 48 1 127 ] # Loop 3 4 "5" [ 0 0x90 1 1 127 ] [ 0 0x90 1 1 127 ] [ 0 0x80 1 1 127 ] # Loop 4 5 "6" [ 0 0x90 17 1 127 ] [ 0 0x90 17 1 127 ] [ 0 0x80 17 1 127 ] # Loop 5 6 "7" [ 0 0x90 33 1 127 ] [ 0 0x90 33 1 127 ] [ 0 0x80 33 1 127 ] # Loop 6 7 "8" [ 0 0x90 49 1 127 ] [ 0 0x90 49 1 127 ] [ 0 0x80 49 1 127 ] # Loop 7 8 "q" [ 0 0x90 2 1 127 ] [ 0 0x90 2 1 127 ] [ 0 0x80 2 1 127 ] # Loop 8 9 "w" [ 0 0x90 18 1 127 ] [ 0 0x90 18 1 127 ] [ 0 0x80 18 1 127 ] # Loop 9 10 "e" [ 0 0x90 34 1 127 ] [ 0 0x90 34 1 127 ] [ 0 0x80 34 1 127 ] # Loop 10 11 "r" [ 0 0x90 50 1 127 ] [ 0 0x90 50 1 127 ] [ 0 0x80 50 1 127 ] # Loop 11 12 "t" [ 0 0x90 3 1 127 ] [ 0 0x90 3 1 127 ] [ 0 0x80 3 1 127 ] # Loop 12 13 "y" [ 0 0x90 19 1 127 ] [ 0 0x90 19 1 127 ] [ 0 0x80 19 1 127 ] # Loop 13 14 "u" [ 0 0x90 35 1 127 ] [ 0 0x90 35 1 127 ] [ 0 0x80 35 1 127 ] # Loop 14 15 "i" [ 0 0x90 51 1 127 ] [ 0 0x90 51 1 127 ] [ 0 0x80 51 1 127 ] # Loop 15 16 "a" [ 0 0x90 4 1 127 ] [ 0 0x90 4 1 127 ] [ 0 0x80 4 1 127 ] # Loop 16 17 "s" [ 0 0x90 20 1 127 ] [ 0 0x90 20 1 127 ] [ 0 0x80 20 1 127 ] # Loop 17 18 "d" [ 0 0x90 36 1 127 ] [ 0 0x90 36 1 127 ] [ 0 0x80 36 1 127 ] # Loop 18 19 "f" [ 0 0x90 52 1 127 ] [ 0 0x90 52 1 127 ] [ 0 0x80 52 1 127 ] # Loop 19 20 "g" [ 0 0x90 5 1 127 ] [ 0 0x90 5 1 127 ] [ 0 0x80 5 1 127 ] # Loop 20 21 "h" [ 0 0x90 21 1 127 ] [ 0 0x90 21 1 127 ] [ 0 0x80 21 1 127 ] # Loop 21 22 "j" [ 0 0x90 37 1 127 ] [ 0 0x90 37 1 127 ] [ 0 0x80 37 1 127 ] # Loop 22 23 "k" [ 0 0x90 53 1 127 ] [ 0 0x90 53 1 127 ] [ 0 0x80 53 1 127 ] # Loop 23 24 "z" [ 0 0x90 6 1 127 ] [ 0 0x90 6 1 127 ] [ 0 0x80 6 1 127 ] # Loop 24 25 "x" [ 0 0x90 22 1 127 ] [ 0 0x90 22 1 127 ] [ 0 0x80 22 1 127 ] # Loop 25 26 "c" [ 0 0x90 38 1 127 ] [ 0 0x90 38 1 127 ] [ 0 0x80 38 1 127 ] # Loop 26 27 "v" [ 0 0x90 54 1 127 ] [ 0 0x90 54 1 127 ] [ 0 0x80 54 1 127 ] # Loop 27 28 "b" [ 0 0x90 7 1 127 ] [ 0 0x90 7 1 127 ] [ 0 0x80 7 1 127 ] # Loop 28 29 "n" [ 0 0x90 23 1 127 ] [ 0 0x90 23 1 127 ] [ 0 0x80 23 1 127 ] # Loop 29 30 "m" [ 0 0x90 39 1 127 ] [ 0 0x90 39 1 127 ] [ 0 0x80 39 1 127 ] # Loop 30 31 "," [ 0 0x90 55 1 127 ] [ 0 0x90 55 1 127 ] [ 0 0x80 55 1 127 ] # Loop 31 [mute-group-control] 0 "!" [ 0 0x90 64 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 0 1 "@" [ 0 0x90 80 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 1 2 "#" [ 0 0x90 96 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 2 3 "$" [ 0 0x90 112 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 3 4 "%" [ 0 0x90 65 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 4 5 "^" [ 0 0x90 81 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 5 6 "&" [ 0 0x90 97 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 6 7 "*" [ 0 0x90 113 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 7 8 "Q" [ 0 0x90 66 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 8 9 "W" [ 0 0x90 82 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 9 10 "E" [ 0 0x90 98 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 10 11 "R" [ 0 0x90 114 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 11 12 "T" [ 0 0x90 67 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 12 13 "Y" [ 0 0x90 83 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 13 14 "U" [ 0 0x90 99 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 14 15 "I" [ 0 0x90 115 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 15 16 "A" [ 0 0x90 68 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 16 17 "S" [ 0 0x90 84 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 17 18 "D" [ 0 0x90 100 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 18 19 "F" [ 0 0x90 116 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 19 20 "G" [ 0 0x90 69 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 20 21 "H" [ 0 0x90 85 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 21 22 "J" [ 0 0x90 101 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 22 23 "K" [ 0 0x90 117 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 23 24 "Z" [ 0 0x90 70 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 24 25 "X" [ 0 0x90 86 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 25 26 "C" [ 0 0x90 102 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 26 27 "V" [ 0 0x90 118 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 27 28 "B" [ 0 0x90 71 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 28 29 "N" [ 0 0x90 87 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 29 30 "M" [ 0 0x90 103 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 30 31 ">" [ 0 0x90 119 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 31 [automation-control] 0 "'" [ 0 0x00 0 0 0 ] [ 0 0x90 104 1 127 ] [ 0 0x00 0 0 0 ] # BPM Up 1 ";" [ 0 0x00 0 0 0 ] [ 0 0x90 120 1 127 ] [ 0 0x00 0 0 0 ] # BPM Dn 2 "]" [ 0 0x00 0 0 0 ] [ 0 0xb0 111 1 127 ] [ 0 0x00 0 0 0 ] # Set Up 3 "[" [ 0 0x00 0 0 0 ] [ 0 0xb0 110 1 127 ] [ 0 0x00 0 0 0 ] # Set Dn 4 "KP_Home" [ 0 0x00 0 0 0 ] [ 0 0x90 40 1 127 ] [ 0 0x00 0 0 0 ] # Replace 5 "Ins" [ 0 0x00 0 0 0 ] [ 0 0x90 56 1 127 ] [ 0 0x00 0 0 0 ] # Snapshot 6 "o" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Queue 7 "`" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Mute 8 "l" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Learn 9 "Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playing Set 10 "." [ 0 0xb0 106 1 127 ] [ 0 0xb0 107 1 127 ] [ 0 0xb0 105 1 127 ] # Playback 11 "P" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Record 12 "BkSpace" [ 0 0x00 0 0 0 ] [ 0 0x90 40 1 127 ] [ 0 0x00 0 0 0 ] # Solo 13 "KP_/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Thru 14 "PageUp" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Up 15 "PageDn" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Dn 16 "KP_." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Set 17 "KP_*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record 18 "KP_-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quan Record 19 "KP_+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reset Seq 20 "|" [ 0 0x00 0 0 0 ] [ 0 0x90 24 1 127 ] [ 0 0x00 0 0 0 ] # One-shot 21 "F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # FF 22 "F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Rewind 23 "F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Top 24 "F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play List 25 "F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play Song 26 "F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Tap BPM 27 "Space" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Start 28 "Esc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Stop 29 "KP_Ins" [ 0 0x00 0 0 0 ] [ 0 0x90 72 1 127 ] [ 0 0x00 0 0 0 ] # Reserved 29 30 "F8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Mute 31 "F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Pos 32 "\" [ 0 0x90 8 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Keep Queue 33 "/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Slot Shift 34 "0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mutes Clear 35 "Quit" [ 0 0x00 0 0 0 ] [ 0 0x90 88 1 127 ] [ 0 0x00 0 0 0 ] # Quit 36 "=" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop Edit 37 "-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Event Edit 38 "F10" [ 0 0x90 72 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Mode 39 "F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle JACK 40 "F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Menu Mode 41 "F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Follow JACK 42 "~" [ 0 0x00 0 0 0 ] [ 0 0xb0 104 1 127 ] [ 0 0x00 0 0 0 ] # Panic 43 "0xf9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Visibility 44 "0xfa" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 44 45 "0xfb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 45 46 "0xfc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 47 "0xfd" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 48 "0xfe" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 48 [midi-control-out-settings] set-size = 32 output-buss = 5 midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 [midi-control-out] # --------------------- Pattern number (as applicable) # | ---------------- MIDI status+channel (e.g. Note On) # | | ------------ data 1 (e.g. note number) # | | | ---------- data 2 (e.g. velocity) # | | | | # v v v v # 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0] # Arm Mute Queue Delete # # This is a change (2021-02-10) from version 1 of this file. # A test of the status/event byte determines the enabled status, # and channel is incorporated into the status. Much cleaner! # The order of the lines that follow must must be preserved. 0 [ 0x90 0 60 ] [ 0x90 0 15 ] [ 0x90 0 62 ] [ 0x90 0 12 ] 1 [ 0x90 16 60 ] [ 0x90 16 15 ] [ 0x90 16 62 ] [ 0x90 16 12 ] 2 [ 0x90 32 60 ] [ 0x90 32 15 ] [ 0x90 32 62 ] [ 0x90 32 12 ] 3 [ 0x90 48 60 ] [ 0x90 48 15 ] [ 0x90 48 62 ] [ 0x90 48 12 ] 4 [ 0x90 1 60 ] [ 0x90 1 15 ] [ 0x90 1 62 ] [ 0x90 1 12 ] 5 [ 0x90 17 60 ] [ 0x90 17 15 ] [ 0x90 17 62 ] [ 0x90 17 12 ] 6 [ 0x90 33 60 ] [ 0x90 33 15 ] [ 0x90 33 62 ] [ 0x90 33 12 ] 7 [ 0x90 49 60 ] [ 0x90 49 15 ] [ 0x90 49 62 ] [ 0x90 49 12 ] 8 [ 0x90 2 60 ] [ 0x90 2 15 ] [ 0x90 2 62 ] [ 0x90 2 12 ] 9 [ 0x90 18 60 ] [ 0x90 18 15 ] [ 0x90 18 62 ] [ 0x90 18 12 ] 10 [ 0x90 34 60 ] [ 0x90 34 15 ] [ 0x90 34 62 ] [ 0x90 34 12 ] 11 [ 0x90 50 60 ] [ 0x90 50 15 ] [ 0x90 50 62 ] [ 0x90 50 12 ] 12 [ 0x90 3 60 ] [ 0x90 3 15 ] [ 0x90 3 62 ] [ 0x90 3 12 ] 13 [ 0x90 19 60 ] [ 0x90 19 15 ] [ 0x90 19 62 ] [ 0x90 19 12 ] 14 [ 0x90 35 60 ] [ 0x90 35 15 ] [ 0x90 35 62 ] [ 0x90 35 12 ] 15 [ 0x90 51 60 ] [ 0x90 51 15 ] [ 0x90 51 62 ] [ 0x90 51 12 ] 16 [ 0x90 4 60 ] [ 0x90 4 15 ] [ 0x90 4 62 ] [ 0x90 4 12 ] 17 [ 0x90 20 60 ] [ 0x90 20 15 ] [ 0x90 20 62 ] [ 0x90 20 12 ] 18 [ 0x90 36 60 ] [ 0x90 36 15 ] [ 0x90 36 62 ] [ 0x90 36 12 ] 19 [ 0x90 52 60 ] [ 0x90 52 15 ] [ 0x90 52 62 ] [ 0x90 52 12 ] 20 [ 0x90 5 60 ] [ 0x90 5 15 ] [ 0x90 5 62 ] [ 0x90 5 12 ] 21 [ 0x90 21 60 ] [ 0x90 21 15 ] [ 0x90 21 62 ] [ 0x90 21 12 ] 22 [ 0x90 37 60 ] [ 0x90 37 15 ] [ 0x90 37 62 ] [ 0x90 37 12 ] 23 [ 0x90 53 60 ] [ 0x90 53 15 ] [ 0x90 53 62 ] [ 0x90 53 12 ] 24 [ 0x90 6 60 ] [ 0x90 6 15 ] [ 0x90 6 62 ] [ 0x90 6 12 ] 25 [ 0x90 22 60 ] [ 0x90 22 15 ] [ 0x90 22 62 ] [ 0x90 22 12 ] 26 [ 0x90 38 60 ] [ 0x90 38 15 ] [ 0x90 38 62 ] [ 0x90 38 12 ] 27 [ 0x90 54 60 ] [ 0x90 54 15 ] [ 0x90 54 62 ] [ 0x90 54 12 ] 28 [ 0x90 7 60 ] [ 0x90 7 15 ] [ 0x90 7 62 ] [ 0x90 7 12 ] 29 [ 0x90 23 60 ] [ 0x90 23 15 ] [ 0x90 23 62 ] [ 0x90 23 12 ] 30 [ 0x90 39 60 ] [ 0x90 39 15 ] [ 0x90 39 62 ] [ 0x90 39 12 ] 31 [ 0x90 55 60 ] [ 0x90 55 15 ] [ 0x90 55 62 ] [ 0x90 55 12 ] [mute-control-out] # The format of the mute and automation output events is simpler: # # ---------------------- mute-group number # | ------------------ MIDI status+channel (e.g. Note On) # | | -------------- data 1 (e.g. note number) # | | | ------------ data 2 (e.g. velocity) # | | | | # v v v v # 1 [0x00 0 0 ] [0x00 0 0] [0x00 0 0] # On Off Empty (dark) # # The mute-controls have an additional stanza for non-populated # ("deleted") mute-groups. 0 [ 0x90 64 60 ] [ 0x90 64 15 ] [ 0x90 64 12 ] 1 [ 0x90 80 60 ] [ 0x90 80 15 ] [ 0x90 80 12 ] 2 [ 0x90 96 60 ] [ 0x90 96 15 ] [ 0x90 96 12 ] 3 [ 0x90 112 60 ] [ 0x90 112 15 ] [ 0x90 112 12 ] 4 [ 0x90 65 60 ] [ 0x90 65 15 ] [ 0x90 65 12 ] 5 [ 0x90 81 60 ] [ 0x90 81 15 ] [ 0x90 81 12 ] 6 [ 0x90 97 60 ] [ 0x90 97 15 ] [ 0x90 97 12 ] 7 [ 0x90 113 60 ] [ 0x90 113 15 ] [ 0x90 113 12 ] 8 [ 0x90 66 60 ] [ 0x90 66 15 ] [ 0x90 66 12 ] 9 [ 0x90 82 60 ] [ 0x90 82 15 ] [ 0x90 82 12 ] 10 [ 0x90 98 60 ] [ 0x90 98 15 ] [ 0x90 98 12 ] 11 [ 0x90 114 60 ] [ 0x90 114 15 ] [ 0x90 114 12 ] 12 [ 0x90 67 60 ] [ 0x90 67 15 ] [ 0x90 67 12 ] 13 [ 0x90 83 60 ] [ 0x90 83 15 ] [ 0x90 83 12 ] 14 [ 0x90 99 60 ] [ 0x90 99 15 ] [ 0x90 99 12 ] 15 [ 0x90 115 60 ] [ 0x90 115 15 ] [ 0x90 115 12 ] 16 [ 0x90 68 60 ] [ 0x90 68 15 ] [ 0x90 68 12 ] 17 [ 0x90 84 60 ] [ 0x90 84 15 ] [ 0x90 84 12 ] 18 [ 0x90 100 60 ] [ 0x90 100 15 ] [ 0x90 100 12 ] 19 [ 0x90 116 60 ] [ 0x90 116 15 ] [ 0x90 116 12 ] 20 [ 0x90 69 60 ] [ 0x90 69 15 ] [ 0x90 69 12 ] 21 [ 0x90 85 60 ] [ 0x90 85 15 ] [ 0x90 85 12 ] 22 [ 0x90 101 60 ] [ 0x90 101 15 ] [ 0x90 101 12 ] 23 [ 0x90 117 60 ] [ 0x90 117 15 ] [ 0x90 117 12 ] 24 [ 0x90 70 60 ] [ 0x90 70 15 ] [ 0x90 70 12 ] 25 [ 0x90 86 60 ] [ 0x90 86 15 ] [ 0x90 86 12 ] 26 [ 0x90 102 60 ] [ 0x90 102 15 ] [ 0x90 102 12 ] 27 [ 0x90 118 60 ] [ 0x90 118 15 ] [ 0x90 118 12 ] 28 [ 0x90 71 60 ] [ 0x90 71 15 ] [ 0x90 71 12 ] 29 [ 0x90 87 60 ] [ 0x90 87 15 ] [ 0x90 87 12 ] 30 [ 0x90 103 60 ] [ 0x90 103 15 ] [ 0x90 103 12 ] 31 [ 0x90 119 60 ] [ 0x90 119 15 ] [ 0x90 119 12 ] [automation-control-out] # This format is similar to the [mute-control-out] format, but # the first number is an active-flag, not an index number. # The stanzas are on/off/inactive, except for 'snap', which is # store/restore/inactive. 1 [ 0xb0 104 60 ] [ 0xb0 104 15 ] [ 0xb0 104 12 ] # Panic 1 [ 0xb0 105 60 ] [ 0xb0 105 15 ] [ 0xb0 105 12 ] # Stop 1 [ 0xb0 106 60 ] [ 0xb0 106 15 ] [ 0xb0 106 12 ] # Pause 1 [ 0xb0 107 60 ] [ 0xb0 107 15 ] [ 0xb0 107 12 ] # Play 1 [ 0xb0 108 60 ] [ 0xb0 108 15 ] [ 0xb0 108 12 ] # Toggle_mutes 1 [ 0xb0 109 60 ] [ 0xb0 109 15 ] [ 0xb0 109 12 ] # Song_record 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Slot_shift 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Free 1 [ 0x90 8 60 ] [ 0x90 8 15 ] [ 0x90 8 12 ] # Queue 1 [ 0x90 24 60 ] [ 0x90 24 15 ] [ 0x90 24 12 ] # Oneshot 1 [ 0x90 40 60 ] [ 0x90 40 15 ] [ 0x90 40 12 ] # Replace 1 [ 0x90 56 60 ] [ 0x90 56 15 ] [ 0x90 56 12 ] # Snap 1 [ 0x90 72 60 ] [ 0x90 72 15 ] [ 0x90 72 12 ] # Song 0 [ 0x0 0 0 ] [ 0x0 0 0 ] [ 0x0 0 0 ] # Learn 1 [ 0x90 104 60 ] [ 0x90 104 15 ] [ 0x90 104 12 ] # BPM_Up 1 [ 0x90 120 60 ] [ 0x90 120 15 ] [ 0x90 120 12 ] # BPM_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Dn 1 [ 0xb0 110 60 ] [ 0xb0 110 15 ] [ 0xb0 110 12 ] # Set_Up 1 [ 0xb0 111 60 ] [ 0xb0 111 15 ] [ 0xb0 111 12 ] # Set_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Tap_BPM 1 [ 0x90 88 60 ] [ 0x90 88 15 ] [ 0x90 88 12 ] # Quit 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Visibility 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_2 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_3 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_4 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_5 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_6 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_7 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_8 # End of /home/user/.config/seq66/qseq66-lp-mini.ctrl # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66-lp-mini.ctrl ================================================ # Seq66 0.94.1 (and above) MIDI control configuration file # # /home/user/.config/seq66/qseq66-lp-mini.ctrl # Written 2021-06-11 08:42:31 # # This file holds MIDI I/O control setups for Seq66. It follows the format # of the 'rc' configuration file, but is stored separately for flexibility # It is always stored in the main configuration directory. To use this # file, specify it in the [midi-control-file] section in the 'rc' file. # Use the base-name (e.g. nanomap.ctrl). # Version 1 adds the [mute-control-out] and [automation-control-out] # sections. Versions 2 and 3 simplify the data items. [Seq66] config-type = "ctrl" version = 5 # The [comments] section holds the user's documentation for this file. # The first completely empty, comment, or tag line ends the comment. [comments] This file was created by copying the default qseq66.ctrl file and changing the MIDI control section to the values found in the sequencer64-laumchpad.rc file. This section provides for controlling the following actions: o Patterns 0 to 31. Patterns can be toggled by sending Note Ons on on channel 1. Observe that the Toggle entries are marked active. This works with the Launchpad Mini. o Patterns 0 to 31 alternative. Patterns can be turned on by sending Note Ons on on channel 1, and turned off by sending Note Offs. Make the Toggle entries inactive, and the On and Off entries active to achieve this setup. o Muting 32 to 64 (mute groups 0 to 31). o Automation control 64 to 73. This covers only part of the actions that can be controlled via MIDI in Seq66; we still need to upgrade the rest once we get familiar with our new LaunchPad Mini. See doc/launchpad-mini.ods for the mappings implemented by this 'ctrl' file. The default setup for the LaunchPad Mini is shown in the "Launchpad programmer's reference" file (launchpad-programmers-reference.pdf). Since, by default, the Mini uses Note Ons of velocity 0 instead of Note Offs, we set the data range from 1 to 127 instead of 0 to 127. For MIDI output to the Launchpad Mini, we make the following color mappings: Arm Mute Queue Deleted/Empty Green Red Yellow Off 60 15 62 12 A sample line. Just need to fill in the key (note value) needed for this row and set the pattern number at the left: 2 [1 0 0x90 key 60] [1 0 0x90 key 15] [1 0 0x90 key 62] [1 0 0x90 key 12] [midi-control-settings] # The control-buss value ranges from 0 to the maximum system input buss. If set, # then that buss will be allowed to send MIDI control. 255 (0xFF) means any buss # can send MIDI control. The buss(es) must be enabled in the 'rc' file. With # ALSA, there is an extra 'announce' buss, which will alter the numbering # compared to JACK. # # The 'midi-enabled' flag applies to the MIDI controls; keystrokes are always # enabled. Supported keyboard layouts are 'qwerty' (the default), 'qwertz', and # 'azerty'. AZERTY turns off the auto-shift feature for group-learn. control-buss = 6 midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 keyboard-layout = qwerty # The control stanza incorporates key control as well as MIDI, but # keys support only 'toggle', and key-release is an 'invert'. The # leftmost number on each line here is the pattern number (e.g. # 0 to 31); the group number, same range; or an automation control # number. This internal control number is followed by three groups of # bracketed numbers, each providing three different types of control: # # Normal: [toggle] [on] [off] # Increment/Decr: [increment] [increment] [decrement] # Playback: [pause] [start] [stop] # Playlist/Song: [by-value] [next] [previous] # # In each group, there are six numbers: # # [on/off invert status d0 d1min d1max] # # 'on/off' enables/disables (1/0) the control for the pattern; # 'invert' (1/0) causes the opposite, but not all support this, and # all keystroke-releases set invert to true; 'status' is the MIDI # event to match (channel is NOT ignored), and if set to 0x00, the # control is disabled; 'd0' is the first data value, e.g. if status # is 0x90 (Note On), d0 represents the note number; d1min to d1max # is the range of data values detected, e.g. for a Note On, 1 to 127 # indicate that any non-zero velocity will invoke the control. # Hex values can be used; precede with '0x'. # # ------------------------- Loop, group, or automation-slot number # | ---------------------- Name of the key (see the key map) # | | # | | ---------------- Inverse # | | | -------------- MIDI status/event byte (e.g. Note On) # | | | | ------------ d0: Data 1 (e.g. Note number) # | | | | | ---------- d1max: Data 2 min (e.g. Note velocity) # | | | | | | -------- d1min: Data 2 max # | | | | | | | # v v v v v v v # 0 "F1" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Toggle On Off [loop-control] 0 "1" [ 0 0x90 0 1 127 ] [ 0 0x90 0 1 127 ] [ 0 0x80 0 1 127 ] # Loop 0 1 "q" [ 0 0x90 16 1 127 ] [ 0 0x90 16 1 127 ] [ 0 0x80 16 1 127 ] # Loop 1 2 "a" [ 0 0x90 32 1 127 ] [ 0 0x90 32 1 127 ] [ 0 0x80 32 1 127 ] # Loop 2 3 "z" [ 0 0x90 48 1 127 ] [ 0 0x90 48 1 127 ] [ 0 0x80 48 1 127 ] # Loop 3 4 "2" [ 0 0x90 1 1 127 ] [ 0 0x90 1 1 127 ] [ 0 0x80 1 1 127 ] # Loop 4 5 "w" [ 0 0x90 17 1 127 ] [ 0 0x90 17 1 127 ] [ 0 0x80 17 1 127 ] # Loop 5 6 "s" [ 0 0x90 33 1 127 ] [ 0 0x90 33 1 127 ] [ 0 0x80 33 1 127 ] # Loop 6 7 "x" [ 0 0x90 49 1 127 ] [ 0 0x90 49 1 127 ] [ 0 0x80 49 1 127 ] # Loop 7 8 "3" [ 0 0x90 2 1 127 ] [ 0 0x90 2 1 127 ] [ 0 0x80 2 1 127 ] # Loop 8 9 "e" [ 0 0x90 18 1 127 ] [ 0 0x90 18 1 127 ] [ 0 0x80 18 1 127 ] # Loop 9 10 "d" [ 0 0x90 34 1 127 ] [ 0 0x90 34 1 127 ] [ 0 0x80 34 1 127 ] # Loop 10 11 "c" [ 0 0x90 50 1 127 ] [ 0 0x90 50 1 127 ] [ 0 0x80 50 1 127 ] # Loop 11 12 "4" [ 0 0x90 3 1 127 ] [ 0 0x90 3 1 127 ] [ 0 0x80 3 1 127 ] # Loop 12 13 "r" [ 0 0x90 19 1 127 ] [ 0 0x90 19 1 127 ] [ 0 0x80 19 1 127 ] # Loop 13 14 "f" [ 0 0x90 35 1 127 ] [ 0 0x90 35 1 127 ] [ 0 0x80 35 1 127 ] # Loop 14 15 "v" [ 0 0x90 51 1 127 ] [ 0 0x90 51 1 127 ] [ 0 0x80 51 1 127 ] # Loop 15 16 "5" [ 0 0x90 4 1 127 ] [ 0 0x90 4 1 127 ] [ 0 0x80 4 1 127 ] # Loop 16 17 "t" [ 0 0x90 20 1 127 ] [ 0 0x90 20 1 127 ] [ 0 0x80 20 1 127 ] # Loop 17 18 "g" [ 0 0x90 36 1 127 ] [ 0 0x90 36 1 127 ] [ 0 0x80 36 1 127 ] # Loop 18 19 "b" [ 0 0x90 52 1 127 ] [ 0 0x90 52 1 127 ] [ 0 0x80 52 1 127 ] # Loop 19 20 "6" [ 0 0x90 5 1 127 ] [ 0 0x90 5 1 127 ] [ 0 0x80 5 1 127 ] # Loop 20 21 "y" [ 0 0x90 21 1 127 ] [ 0 0x90 21 1 127 ] [ 0 0x80 21 1 127 ] # Loop 21 22 "h" [ 0 0x90 37 1 127 ] [ 0 0x90 37 1 127 ] [ 0 0x80 37 1 127 ] # Loop 22 23 "n" [ 0 0x90 53 1 127 ] [ 0 0x90 53 1 127 ] [ 0 0x80 53 1 127 ] # Loop 23 24 "7" [ 0 0x90 6 1 127 ] [ 0 0x90 6 1 127 ] [ 0 0x80 6 1 127 ] # Loop 24 25 "u" [ 0 0x90 22 1 127 ] [ 0 0x90 22 1 127 ] [ 0 0x80 22 1 127 ] # Loop 25 26 "j" [ 0 0x90 38 1 127 ] [ 0 0x90 38 1 127 ] [ 0 0x80 38 1 127 ] # Loop 26 27 "m" [ 0 0x90 54 1 127 ] [ 0 0x90 54 1 127 ] [ 0 0x80 54 1 127 ] # Loop 27 28 "8" [ 0 0x90 7 1 127 ] [ 0 0x90 7 1 127 ] [ 0 0x80 7 1 127 ] # Loop 28 29 "i" [ 0 0x90 23 1 127 ] [ 0 0x90 23 1 127 ] [ 0 0x80 23 1 127 ] # Loop 29 30 "k" [ 0 0x90 39 1 127 ] [ 0 0x90 39 1 127 ] [ 0 0x80 39 1 127 ] # Loop 30 31 "," [ 0 0x90 55 1 127 ] [ 0 0x90 55 1 127 ] [ 0 0x80 55 1 127 ] # Loop 31 [mute-group-control] 0 "!" [ 0 0x90 64 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 0 1 "Q" [ 0 0x90 80 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 1 2 "A" [ 0 0x90 96 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 2 3 "Z" [ 0 0x90 112 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 3 4 "@" [ 0 0x90 65 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 4 5 "W" [ 0 0x90 81 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 5 6 "S" [ 0 0x90 97 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 6 7 "X" [ 0 0x90 113 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 7 8 "#" [ 0 0x90 66 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 8 9 "E" [ 0 0x90 82 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 9 10 "D" [ 0 0x90 98 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 10 11 "C" [ 0 0x90 114 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 11 12 "$" [ 0 0x90 67 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 12 13 "R" [ 0 0x90 83 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 13 14 "F" [ 0 0x90 99 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 14 15 "V" [ 0 0x90 115 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 15 16 "%" [ 0 0x90 68 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 16 17 "T" [ 0 0x90 84 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 17 18 "G" [ 0 0x90 100 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 18 19 "B" [ 0 0x90 116 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 19 20 "^" [ 0 0x90 69 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 20 21 "Y" [ 0 0x90 85 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 21 22 "H" [ 0 0x90 101 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 22 23 "N" [ 0 0x90 117 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 23 24 "&" [ 0 0x90 70 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 24 25 "U" [ 0 0x90 86 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 25 26 "J" [ 0 0x90 102 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 26 27 "M" [ 0 0x90 118 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 27 28 "*" [ 0 0x90 71 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 28 29 "I" [ 0 0x90 87 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 29 30 "K" [ 0 0x90 103 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 30 31 "<" [ 0 0x90 119 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 31 [automation-control] 0 "'" [ 0 0x00 0 0 0 ] [ 0 0x90 104 1 127 ] [ 0 0x00 0 0 0 ] # BPM Up 1 ";" [ 0 0x00 0 0 0 ] [ 0 0x90 120 1 127 ] [ 0 0x00 0 0 0 ] # BPM Dn 2 "]" [ 0 0x00 0 0 0 ] [ 0 0xb0 111 1 127 ] [ 0 0x00 0 0 0 ] # Set Up 3 "[" [ 0 0x00 0 0 0 ] [ 0 0xb0 110 1 127 ] [ 0 0x00 0 0 0 ] # Set Dn 4 "KP_Home" [ 0 0x00 0 0 0 ] [ 0 0x90 40 1 127 ] [ 0 0x00 0 0 0 ] # Replace 5 "Ins" [ 0 0x00 0 0 0 ] [ 0 0x90 56 1 127 ] [ 0 0x00 0 0 0 ] # Snapshot 6 "o" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Queue 7 "`" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Mute 8 "l" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Learn 9 "Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playing Set 10 "." [ 0 0xb0 106 1 127 ] [ 0 0xb0 107 1 127 ] [ 0 0xb0 105 1 127 ] # Playback 11 "P" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Record 12 "BkSpace" [ 0 0x00 0 0 0 ] [ 0 0x90 40 1 127 ] [ 0 0x00 0 0 0 ] # Solo 13 "KP_/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Thru 14 "PageUp" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Up 15 "PageDn" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Dn 16 "KP_." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Set 17 "KP_*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record 18 "KP_-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quan Record 19 "KP_+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reset Seq 20 "|" [ 0 0x00 0 0 0 ] [ 0 0x90 24 1 127 ] [ 0 0x00 0 0 0 ] # One-shot 21 "F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # FF 22 "F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Rewind 23 "F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Top 24 "F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play List 25 "F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play Song 26 "F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Tap BPM 27 "Space" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Start 28 "Esc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Stop 29 "KP_Ins" [ 0 0x00 0 0 0 ] [ 0 0x90 72 1 127 ] [ 0 0x00 0 0 0 ] # Reserved 29 30 "F8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Mute 31 "F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Pos 32 "\" [ 0 0x90 8 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Keep Queue 33 "/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Slot Shift 34 "0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mutes Clear 35 "Quit" [ 0 0x00 0 0 0 ] [ 0 0x90 88 1 127 ] [ 0 0x00 0 0 0 ] # Quit 36 "=" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop Edit 37 "-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Event Edit 38 "F10" [ 0 0x90 72 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Mode 39 "F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle JACK 40 "F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Menu Mode 41 "F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Follow JACK 42 "~" [ 0 0x00 0 0 0 ] [ 0 0xb0 104 1 127 ] [ 0 0x00 0 0 0 ] # Panic 43 "0xf9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Visibility 44 "0xfa" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 44 45 "0xfb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 45 46 "0xfc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 47 "0xfd" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 48 "0xfe" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 48 [midi-control-out-settings] set-size = 32 output-buss = 5 midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 [midi-control-out] # --------------------- Pattern number (as applicable) # | ---------------- MIDI status+channel (e.g. Note On) # | | ------------ data 1 (e.g. note number) # | | | ---------- data 2 (e.g. velocity) # | | | | # v v v v # 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0] # Arm Mute Queue Delete # # This is a change (2021-02-10) from version 1 of this file. # A test of the status/event byte determines the enabled status, # and channel is incorporated into the status. Much cleaner! # The order of the lines that follow must must be preserved. 0 [ 0x90 0 60 ] [ 0x90 0 15 ] [ 0x90 0 62 ] [ 0x90 0 12 ] 1 [ 0x90 16 60 ] [ 0x90 16 15 ] [ 0x90 16 62 ] [ 0x90 16 12 ] 2 [ 0x90 32 60 ] [ 0x90 32 15 ] [ 0x90 32 62 ] [ 0x90 32 12 ] 3 [ 0x90 48 60 ] [ 0x90 48 15 ] [ 0x90 48 62 ] [ 0x90 48 12 ] 4 [ 0x90 1 60 ] [ 0x90 1 15 ] [ 0x90 1 62 ] [ 0x90 1 12 ] 5 [ 0x90 17 60 ] [ 0x90 17 15 ] [ 0x90 17 62 ] [ 0x90 17 12 ] 6 [ 0x90 33 60 ] [ 0x90 33 15 ] [ 0x90 33 62 ] [ 0x90 33 12 ] 7 [ 0x90 49 60 ] [ 0x90 49 15 ] [ 0x90 49 62 ] [ 0x90 49 12 ] 8 [ 0x90 2 60 ] [ 0x90 2 15 ] [ 0x90 2 62 ] [ 0x90 2 12 ] 9 [ 0x90 18 60 ] [ 0x90 18 15 ] [ 0x90 18 62 ] [ 0x90 18 12 ] 10 [ 0x90 34 60 ] [ 0x90 34 15 ] [ 0x90 34 62 ] [ 0x90 34 12 ] 11 [ 0x90 50 60 ] [ 0x90 50 15 ] [ 0x90 50 62 ] [ 0x90 50 12 ] 12 [ 0x90 3 60 ] [ 0x90 3 15 ] [ 0x90 3 62 ] [ 0x90 3 12 ] 13 [ 0x90 19 60 ] [ 0x90 19 15 ] [ 0x90 19 62 ] [ 0x90 19 12 ] 14 [ 0x90 35 60 ] [ 0x90 35 15 ] [ 0x90 35 62 ] [ 0x90 35 12 ] 15 [ 0x90 51 60 ] [ 0x90 51 15 ] [ 0x90 51 62 ] [ 0x90 51 12 ] 16 [ 0x90 4 60 ] [ 0x90 4 15 ] [ 0x90 4 62 ] [ 0x90 4 12 ] 17 [ 0x90 20 60 ] [ 0x90 20 15 ] [ 0x90 20 62 ] [ 0x90 20 12 ] 18 [ 0x90 36 60 ] [ 0x90 36 15 ] [ 0x90 36 62 ] [ 0x90 36 12 ] 19 [ 0x90 52 60 ] [ 0x90 52 15 ] [ 0x90 52 62 ] [ 0x90 52 12 ] 20 [ 0x90 5 60 ] [ 0x90 5 15 ] [ 0x90 5 62 ] [ 0x90 5 12 ] 21 [ 0x90 21 60 ] [ 0x90 21 15 ] [ 0x90 21 62 ] [ 0x90 21 12 ] 22 [ 0x90 37 60 ] [ 0x90 37 15 ] [ 0x90 37 62 ] [ 0x90 37 12 ] 23 [ 0x90 53 60 ] [ 0x90 53 15 ] [ 0x90 53 62 ] [ 0x90 53 12 ] 24 [ 0x90 6 60 ] [ 0x90 6 15 ] [ 0x90 6 62 ] [ 0x90 6 12 ] 25 [ 0x90 22 60 ] [ 0x90 22 15 ] [ 0x90 22 62 ] [ 0x90 22 12 ] 26 [ 0x90 38 60 ] [ 0x90 38 15 ] [ 0x90 38 62 ] [ 0x90 38 12 ] 27 [ 0x90 54 60 ] [ 0x90 54 15 ] [ 0x90 54 62 ] [ 0x90 54 12 ] 28 [ 0x90 7 60 ] [ 0x90 7 15 ] [ 0x90 7 62 ] [ 0x90 7 12 ] 29 [ 0x90 23 60 ] [ 0x90 23 15 ] [ 0x90 23 62 ] [ 0x90 23 12 ] 30 [ 0x90 39 60 ] [ 0x90 39 15 ] [ 0x90 39 62 ] [ 0x90 39 12 ] 31 [ 0x90 55 60 ] [ 0x90 55 15 ] [ 0x90 55 62 ] [ 0x90 55 12 ] [mute-control-out] # The format of the mute and automation output events is simpler: # # ---------------------- mute-group number # | ------------------ MIDI status+channel (e.g. Note On) # | | -------------- data 1 (e.g. note number) # | | | ------------ data 2 (e.g. velocity) # | | | | # v v v v # 1 [0x00 0 0 ] [0x00 0 0] [0x00 0 0] # On Off Empty (dark) # # The mute-controls have an additional stanza for non-populated # ("deleted") mute-groups. 0 [ 0x90 64 60 ] [ 0x90 64 15 ] [ 0x90 64 12 ] 1 [ 0x90 80 60 ] [ 0x90 80 15 ] [ 0x90 80 12 ] 2 [ 0x90 96 60 ] [ 0x90 96 15 ] [ 0x90 96 12 ] 3 [ 0x90 112 60 ] [ 0x90 112 15 ] [ 0x90 112 12 ] 4 [ 0x90 65 60 ] [ 0x90 65 15 ] [ 0x90 65 12 ] 5 [ 0x90 81 60 ] [ 0x90 81 15 ] [ 0x90 81 12 ] 6 [ 0x90 97 60 ] [ 0x90 97 15 ] [ 0x90 97 12 ] 7 [ 0x90 113 60 ] [ 0x90 113 15 ] [ 0x90 113 12 ] 8 [ 0x90 66 60 ] [ 0x90 66 15 ] [ 0x90 66 12 ] 9 [ 0x90 82 60 ] [ 0x90 82 15 ] [ 0x90 82 12 ] 10 [ 0x90 98 60 ] [ 0x90 98 15 ] [ 0x90 98 12 ] 11 [ 0x90 114 60 ] [ 0x90 114 15 ] [ 0x90 114 12 ] 12 [ 0x90 67 60 ] [ 0x90 67 15 ] [ 0x90 67 12 ] 13 [ 0x90 83 60 ] [ 0x90 83 15 ] [ 0x90 83 12 ] 14 [ 0x90 99 60 ] [ 0x90 99 15 ] [ 0x90 99 12 ] 15 [ 0x90 115 60 ] [ 0x90 115 15 ] [ 0x90 115 12 ] 16 [ 0x90 68 60 ] [ 0x90 68 15 ] [ 0x90 68 12 ] 17 [ 0x90 84 60 ] [ 0x90 84 15 ] [ 0x90 84 12 ] 18 [ 0x90 100 60 ] [ 0x90 100 15 ] [ 0x90 100 12 ] 19 [ 0x90 116 60 ] [ 0x90 116 15 ] [ 0x90 116 12 ] 20 [ 0x90 69 60 ] [ 0x90 69 15 ] [ 0x90 69 12 ] 21 [ 0x90 85 60 ] [ 0x90 85 15 ] [ 0x90 85 12 ] 22 [ 0x90 101 60 ] [ 0x90 101 15 ] [ 0x90 101 12 ] 23 [ 0x90 117 60 ] [ 0x90 117 15 ] [ 0x90 117 12 ] 24 [ 0x90 70 60 ] [ 0x90 70 15 ] [ 0x90 70 12 ] 25 [ 0x90 86 60 ] [ 0x90 86 15 ] [ 0x90 86 12 ] 26 [ 0x90 102 60 ] [ 0x90 102 15 ] [ 0x90 102 12 ] 27 [ 0x90 118 60 ] [ 0x90 118 15 ] [ 0x90 118 12 ] 28 [ 0x90 71 60 ] [ 0x90 71 15 ] [ 0x90 71 12 ] 29 [ 0x90 87 60 ] [ 0x90 87 15 ] [ 0x90 87 12 ] 30 [ 0x90 103 60 ] [ 0x90 103 15 ] [ 0x90 103 12 ] 31 [ 0x90 119 60 ] [ 0x90 119 15 ] [ 0x90 119 12 ] [automation-control-out] # This format is similar to the [mute-control-out] format, but # the first number is an active-flag, not an index number. # The stanzas are on/off/inactive, except for 'snap', which is # store/restore/inactive. 1 [ 0xb0 104 60 ] [ 0xb0 104 15 ] [ 0xb0 104 12 ] # Panic 1 [ 0xb0 105 60 ] [ 0xb0 105 15 ] [ 0xb0 105 12 ] # Stop 1 [ 0xb0 106 60 ] [ 0xb0 106 15 ] [ 0xb0 106 12 ] # Pause 1 [ 0xb0 107 60 ] [ 0xb0 107 15 ] [ 0xb0 107 12 ] # Play 1 [ 0xb0 108 60 ] [ 0xb0 108 15 ] [ 0xb0 108 12 ] # Toggle_mutes 1 [ 0xb0 109 60 ] [ 0xb0 109 15 ] [ 0xb0 109 12 ] # Song_record 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Slot_shift 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Free 1 [ 0x90 8 60 ] [ 0x90 8 15 ] [ 0x90 8 12 ] # Queue 1 [ 0x90 24 60 ] [ 0x90 24 15 ] [ 0x90 24 12 ] # Oneshot 1 [ 0x90 40 60 ] [ 0x90 40 15 ] [ 0x90 40 12 ] # Replace 1 [ 0x90 56 60 ] [ 0x90 56 15 ] [ 0x90 56 12 ] # Snap 1 [ 0x90 72 60 ] [ 0x90 72 15 ] [ 0x90 72 12 ] # Song 0 [ 0x0 0 0 ] [ 0x0 0 0 ] [ 0x0 0 0 ] # Learn 1 [ 0x90 104 60 ] [ 0x90 104 15 ] [ 0x90 104 12 ] # BPM_Up 1 [ 0x90 120 60 ] [ 0x90 120 15 ] [ 0x90 120 12 ] # BPM_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Dn 1 [ 0xb0 110 60 ] [ 0xb0 110 15 ] [ 0xb0 110 12 ] # Set_Up 1 [ 0xb0 111 60 ] [ 0xb0 111 15 ] [ 0xb0 111 12 ] # Set_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Tap_BPM 1 [ 0x90 88 60 ] [ 0x90 88 15 ] [ 0x90 88 12 ] # Quit 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Visibility 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_2 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_3 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_4 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_5 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_6 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_7 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_8 # End of /home/user/.config/seq66/qseq66-lp-mini.ctrl # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66-qwerty-us.keymap ================================================ # Seq66 0.94.0 (and above) keyboard remapping configuration file # # /home/ahlstrom/.config/seq66/qseq66-qwerty-us.keymap # Written on 2023-08-06 14:52:54 # # This file holds keystroke remappings for the AZERTY FR keyboard. # If specified it modifies the internal key-map with the changes # shown below. # # Note that the internal keymap is shown in doc/control-keys.ods and in # data/linux/qseq66-qwerty-us.keys. # # Also note that this file is only read... it is never written over. [Seq66] config-type = "keys" version = 0 [comments] This file matches the internal key-mappings for Seq66. No need to load it, really. Also see the doc/control-keys.ods spreadsheet. [key-mappings] # Internal Qt Qt Key # Ordinal Evkey VirtKey Name Modifier 0x00 0x40 0x60 "NUL" "Ctrl" # ^@: Null 0x01 0x41 0x61 "SOH" "Ctrl" # ^A: Start of Heading 0x02 0x42 0x62 "STX" "Ctrl" # ^B: Start of Text 0x03 0x43 0x63 "ETX" "Ctrl" # ^C: End of Text 0x04 0x44 0x64 "EOT" "Ctrl" # ^D: End of Transmision 0x05 0x45 0x65 "ENQ" "Ctrl" # ^E: Enquiry 0x06 0x46 0x66 "ACK" "Ctrl" # ^F: Acknowledge 0x07 0x47 0x67 "BEL" "Ctrl" # ^G: Bell/beep 0x08 0x48 0x68 "BS" "Ctrl" # ^H: Backspace 0x09 0x49 0x69 "HT" "Ctrl" # ^I: Horizontal Tab 0x0a 0x4a 0x6a "LF" "Ctrl" # ^J: Line Feed 0x0b 0x4b 0x6b "VT" "Ctrl" # ^K: Vertical Tab 0x0c 0x4c 0x6c "FF" "Ctrl" # ^L: Form Feed 0x0d 0x4d 0x6d "CR" "Ctrl" # ^M: Carriage Return 0x0e 0x4e 0x60 "SO" "Ctrl" # ^N: Shift Out 0x0f 0x4f 0x6e "SI" "Ctrl" # ^O: Shift In 0x10 0x50 0x7f "DLE" "Ctrl" # ^P: Data Link Escape 0x11 0x51 0x71 "DC1" "Ctrl" # ^Q: Device Control 1 0x12 0x52 0x72 "DC2" "Ctrl" # ^R: Device Control 2 0x13 0x53 0x73 "DC3" "Ctrl" # ^S: Device Control 3 0x14 0x54 0x74 "DC4" "Ctrl" # ^T: Device Control 4 0x15 0x55 0x75 "NAK" "Ctrl" # ^U: Negative ACK 0x16 0x56 0x76 "SYN" "Ctrl" # ^V: Synchronous Idle 0x17 0x57 0x77 "ETB" "Ctrl" # ^W: End of Trans Block 0x18 0x58 0x78 "CAN" "Ctrl" # ^X: Cancel 0x19 0x59 0x79 "EM" "Ctrl" # ^Y: End of Medium 0x1a 0x5a 0x7a "SUB" "Ctrl" # ^Z: Substitute 0x1b 0x5b 0x7b "ESC" "Ctrl" # ^[: Escape 0x1c 0x5c 0x7c "FS" "Ctrl" # ^\: File Separator 0x1d 0x5d 0x7d "GS" "Ctrl" # ^]: Group Separator 0x1e 0x5e 0x7e "RS" "Ctrl-Shift" # ^^: Record Separator 0x1f 0x5f 0x7f "US" "Ctrl-Shift" # ^_???: Unit Separator 0x20 0x20 0x20 "Space" "None" # Space (" " not good) 0x21 0x21 0x21 "!" "Shift" # Exclam 0x22 0x22 0x22 "\"" "Shift" # QuoteDbl 0x23 0x23 0x23 "#" "Shift" # NumberSign 0x24 0x24 0x24 "$" "Shift" # Dollar 0x25 0x25 0x25 "%" "Shift" # Percent 0x26 0x26 0x26 "&" "Shift" # Ampersand 0x27 0x27 0x27 "'" "Shift" # Apostrophe 0x28 0x28 0x28 "(" "Shift" # ParenLeft 0x29 0x29 0x29 ")" "Shift" # ParenRight 0x2a 0x2a 0x2a "*" "Shift" # Asterisk 0x2b 0x2b 0x2b "+" "Shift" # Plus 0x2c 0x2c 0x2c "" "None" # Comma 0x2d 0x2d 0x2d "-" "None" # Minus 0x2e 0x2e 0x2e "." "None" # Period 0x2f 0x2f 0x2f "/" "None" # Slash 0x30 0x30 0x30 "0" "None" 0x31 0x31 0x31 "1" "None" 0x32 0x32 0x32 "2" "None" 0x33 0x33 0x33 "3" "None" 0x34 0x34 0x34 "4" "None" 0x35 0x35 0x35 "5" "None" 0x36 0x36 0x36 "6" "None" 0x37 0x37 0x37 "7" "None" 0x38 0x38 0x38 "8" "None" 0x39 0x39 0x39 "9" "None" 0x3a 0x3a 0x3a ":" "Shift" 0x3b 0x3b 0x3b ";" "None" 0x3c 0x3c 0x3c "<" "Shift" 0x3d 0x3d 0x3d "=" "None" 0x3e 0x3e 0x3e ">" "Shift" 0x3f 0x3f 0x3f "?" "Shift" 0x40 0x40 0x40 "@" "Shift" 0x41 0x41 0x41 "A" "Shift" 0x42 0x42 0x42 "B" "Shift" 0x43 0x43 0x43 "C" "Shift" 0x44 0x44 0x44 "D" "Shift" 0x45 0x45 0x45 "E" "Shift" 0x46 0x46 0x46 "F" "Shift" 0x47 0x47 0x47 "G" "Shift" 0x48 0x48 0x48 "H" "Shift" 0x49 0x49 0x49 "I" "Shift" 0x4a 0x4a 0x4a "J" "Shift" 0x4b 0x4b 0x4b "K" "Shift" 0x4c 0x4c 0x4c "L" "Shift" 0x4d 0x4d 0x4d "M" "Shift" 0x4e 0x4e 0x4e "N" "Shift" 0x4f 0x4f 0x4f "O" "Shift" 0x50 0x50 0x50 "P" "Shift" 0x51 0x51 0x51 "Q" "Shift" 0x52 0x52 0x52 "R" "Shift" 0x53 0x53 0x53 "S" "Shift" 0x54 0x54 0x54 "T" "Shift" 0x55 0x55 0x55 "U" "Shift" 0x56 0x56 0x56 "V" "Shift" 0x57 0x57 0x57 "W" "Shift" 0x58 0x58 0x58 "X" "Shift" 0x59 0x59 0x59 "Y" "Shift" 0x5a 0x5a 0x5a "Z" "Shift" 0x5b 0x5b 0x5b "[" "None" # BracketLeft 0x5c 0x5c 0x5c "\\" "None" # Backslash 0x5d 0x5d 0x5d "]" "None" # BracketRight 0x5e 0x5e 0x5e "^" "Shift" # AsciiCircumflex 0x5f 0x5f 0x5f "_" "Shift" # Underscore 0x60 0x60 0x60 "`" "None" # QuoteLeft Backtick 0x61 0x41 0x61 "a" "None" 0x62 0x42 0x62 "b" "None" 0x63 0x43 0x63 "c" "None" 0x64 0x44 0x64 "d" "None" 0x65 0x45 0x65 "e" "None" 0x66 0x46 0x66 "f" "None" 0x67 0x47 0x67 "g" "None" 0x68 0x48 0x68 "h" "None" 0x69 0x49 0x69 "i" "None" 0x6a 0x4a 0x6a "j" "None" 0x6b 0x4b 0x6b "k" "None" 0x6c 0x4c 0x6c "l" "None" 0x6d 0x4d 0x6d "m" "None" 0x6e 0x4e 0x6e "n" "None" 0x6f 0x4f 0x6f "o" "None" 0x70 0x50 0x50 "p" "None" 0x71 0x51 0x71 "q" "None" 0x72 0x52 0x72 "r" "None" 0x73 0x53 0x73 "s" "None" 0x74 0x54 0x74 "t" "None" 0x75 0x55 0x75 "u" "None" 0x76 0x56 0x76 "v" "None" 0x77 0x57 0x77 "w" "None" 0x78 0x58 0x78 "x" "None" 0x79 0x59 0x79 "y" "None" 0x7a 0x5a 0x7a "z" "None" 0x7b 0x7b 0x7b "{" "Shift" # BraceLeft 0x7c 0x7c 0x7c "|" "Shift" # Bar 0x7d 0x7d 0x7d "" "Shift" # BraceRight 0x7e 0x7e 0x7e "~" "Shift" # AsciiTilde 0x7f 0x7f 0x7f "DEL" "None" 0x80 0x01000000 0xff1b "Esc" "None" 0x81 0x01000001 0xff09 "Tab" "None" # avoid moves focus 0x82 0x01000002 0xff09 "BkTab" "Shift" # avoid moves focus 0x83 0x01000003 0xff08 "BkSpace" "None" # differs from Ctrl-H ! 0x84 0x01000004 0xff0d "Return" "None" 0x85 0x01000005 0xff8d "Enter" "Keypad" # Keypad-Enter 0x86 0x01000006 0xff63 "Ins" "None" 0x87 0x01000007 0xffff "Del" "None" 0x88 0x88 0x88 "0x88" "None" # was "Pause" a duplicate 0x89 0x89 0x89 "0x88" "None" # was "Print" a duplicate 0x8a 0x0100000a 0x8a "SysReq" "None" 0x8b 0x0100000b 0x8b "Clear" "None" 0x8c 0x0100000c 0x8c "0x8c" "None" 0x8d 0x0100000d 0x8d "0x8d" "None" 0x8e 0x0100000e 0x8e "0x8e" "None" 0x8f 0x0100000f 0x8f "0x8f" "None" 0x90 0x01000010 0xff50 "Home" "None" 0x91 0x01000011 0xff57 "End" "None" 0x92 0x01000012 0xff51 "Left" "None" 0x93 0x01000013 0xff52 "Up" "None" 0x94 0x01000014 0xff53 "Right" "None" 0x95 0x01000015 0xff54 "Down" "None" 0x96 0x01000016 0xff55 "PageUp" "None" 0x97 0x01000017 0xff56 "PageDn" "None" 0x98 0x01000020 0xffe1 "Shift_L" "Shift" # Left-Shift 0x99 0x01000021 0xffe3 "Ctrl_L" "Ctrl" # Left-Ctrl 0x9a 0x01000022 0x9a "Meta" "Meta" 0x9b 0x01000023 0xffe9 "Alt_L" "Alt" # Left-Alt 0x9c 0x01000024 0xffe5 "CapsLk" "None" # Shift-Lock too??? 0x9d 0x01000025 0xff7f "NumLk" "None" 0x9e 0x01000026 0xff14 "ScrlLk" "None" # Good? 0x9f 0x01000027 0x9f "0x9f" "None" 0xa0 0x01000030 0xffbe "F1" "None" 0xa1 0x01000031 0xffbf "F2" "None" 0xa2 0x01000032 0xffc0 "F3" "None" 0xa3 0x01000033 0xffc1 "F4" "None" 0xa4 0x01000034 0xffc2 "F5" "None" 0xa5 0x01000035 0xffc3 "F6" "None" 0xa6 0x01000036 0xffc4 "F7" "None" 0xa7 0x01000037 0xffc5 "F8" "None" 0xa8 0x01000038 0xffc6 "F9" "None" 0xa9 0x01000039 0xffc7 "F10" "None" 0xaa 0x0100003a 0xffc8 "F11" "None" 0xab 0x0100003b 0xffc9 "F12" "None" 0xac 0x01000053 0xffeb "Super_L" "None" # Left-Windows 0xad 0x01000054 0xffec "Super_R" "None" # Right-Windows 0xae 0x01000055 0xff67 "Menu" "None" # Win-Menu key 0xaf 0x01000056 0xaf "Hyper_L" "None" 0xb0 0x01000057 0xb0 "Hyper_R" "None" 0xb1 0x01000058 0xb1 "Help" "None" 0xb2 0x01000059 0xb2 "Dir_L" "None" 0xb3 0x01000060 0xb3 "Dir_R" "None" # Direction_R 0xb4 0x01000030 0xffbe "Sh_F1" "Shift" 0xb5 0x01000031 0xffbf "Sh_F2" "Shift" 0xb6 0x01000032 0xffc0 "Sh_F3" "Shift" 0xb7 0x01000033 0xffc1 "Sh_F4" "Shift" 0xb8 0x01000034 0xffc2 "Sh_F5" "Shift" 0xb9 0x01000035 0xffc3 "Sh_F6" "Shift" 0xba 0x01000036 0xffc4 "Sh_F7" "Shift" 0xbb 0x01000037 0xffc5 "Sh_F8" "Shift" 0xbc 0x01000038 0xffc6 "Sh_F9" "Shift" 0xbd 0x01000039 0xffc7 "Sh_F10" "Shift" 0xbe 0x0100003a 0xffc8 "Sh_F11" "Shift" 0xbf 0x0100003b 0xffc9 "Sh_F12" "Shift" 0xc0 0x01000006 0xff9e "KP_Ins" "Keypad" 0xc1 0x01000007 0xff9f "KP_Del" "Keypad" 0xc2 0x01000008 0xffe1 "Pause" "Shift" 0xc3 0x01000009 0xff61 "Print" "Shift" 0xc4 0x01000010 0xff95 "KP_Home" "Keypad" 0xc5 0x01000011 0xff9c "KP_End" "Keypad" 0xc6 0x01000012 0xff96 "KP_Left" "Keypad" 0xc7 0x01000013 0xff97 "KP_Up" "Keypad" 0xc8 0x01000014 0xff98 "KP_Right" "Keypad" 0xc9 0x01000015 0xff99 "KP_Down" "Keypad" 0xca 0x01000016 0xff9a "KP_PageUp" "Keypad" 0xcb 0x01000017 0xff9b "KP_PageDn" "Keypad" 0xcc 0x01000099 0xff9d "KP_Begin" "None" # KP_Begin 0xcd 0x01000099 0xcd "0xcd" "None" 0xce 0x01000099 0xce "0xce" "None" 0xcf 0x01000099 0xcf "0xcf" "None" 0xd0 0x2a 0xffaa "KP_*" "Keypad" # Asterisk KP_Multiply 0xd1 0x2b 0xffab "KP_+" "Keypad" # Plus KP_Add 0xd2 0x2c 0xffac "KP_"" "Keypad" # Comma KP_Separator 0xd3 0x2d 0xffad "KP_-" "Keypad" # Minus KP_Subtract 0xd4 0x2e 0xffae "KP_." "Keypad-Shift # Period KP_Decimal 0xd5 0x2f 0xffaf "KP_/" "Keypad" # Slash KP_Divide 0xd6 0x01000099 0xd6 "0xd6" "None" # available 0xd7 0x01000020 0xffe2 "Shift_R" "Shift" # Right-Shift 0xd8 0x01000021 0xffe4 "Ctrl_R" "Ctrl" # Right-Ctrl 0xd9 0x2e 0xffae "KP_." "Keypad" # KP_Decimal release 0xda 0x01000023 0xffea "Alt_R" "Alt" # Right-Alt 0xdb 0x01000020 0xffe1 "Shift_Lr" "None" # L-Shift release 0xdc 0x01000020 0xffe2 "Shift_Rr" "None" # R-Shift release 0xdd 0x01000021 0xffe3 "Ctrl_Lr" "None" # L-Ctrl release 0xde 0x01000021 0xffe4 "Ctrl_Rr" "None" # R-Ctrl release 0xdf 0x01000099 0xdf "0xdf" "None" # available 0xe0 0x01000099 0xe0 "0xe0" "None" 0xe1 0x01000099 0xe1 "0xe1" "None" 0xe2 0x01000099 0xe2 "0xe2" "None" 0xe3 0x01000099 0xe3 "0xe3" "None" 0xe4 0x01000099 0xe4 "0xe4" "None" 0xe5 0x01000099 0xe5 "0xe5" "None" 0xe6 0x01000099 0xe6 "0xe6" "None" 0xe7 0x01000099 0xe7 "0xe7" "None" 0xe8 0x01000099 0xe8 "0xe8" "None" 0xe9 0x01000099 0xe9 "0xe9" "None" 0xea 0x01000099 0xea "0xea" "None" 0xeb 0x01000099 0xeb "0xeb" "None" 0xec 0x01000099 0xec "0xec" "None" 0xed 0x01000099 0xed "0xed" "None" 0xee 0x01000099 0xee "0xee" "None" 0xef 0x01000099 0xef "0xef" "None" 0xf0 0x01000099 0xf0 "0xf0" "None" 0xf1 0x01000099 0xf1 "0xf1" "None" 0xf2 0x01000099 0xf2 "0xf2" "None" 0xf3 0x01000099 0xf3 "0xf3" "None" 0xf4 0x01000099 0xf4 "0xf4" "None" 0xf5 0x01000099 0xf5 "0xf5" "None" 0xf6 0x01000099 0xf6 "0xf6" "None" 0xf7 0x01000099 0xf7 "0xf7" "None" 0xf8 0x01000099 0xf8 "0xf8" "None" 0xf9 0x01000099 0xf9 "0xf9" "None" 0xfa 0x01000099 0xfa "0xfa" "None" 0xfb 0x01000099 0xfb "0xfb" "None" 0xfc 0x01000099 0xfc "0xfc" "None" 0xfd 0x01000099 0xfd "0xfd" "None" 0xfe 0x01000099 0xfe "0xfe" "None" 0xff 0xffffffff 0xff "Null_ff" "None" # end-of-list # End of /home/ahlstrom/.config/seq66/qseq66-azerty-us.keymap # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66-swapped.ctrl ================================================ # Seq66 0.97.1 MIDI control configuration file # # /home/user/.config/seq66/qseq66.ctrl # Written 2021-10-13 10:08:12 # # This file sets up MIDI I/O control for Seq66. It's format is like the # 'rc' file, but stored separately for flexibility. It is stored in the # the HOME configuration directory. To use this, list it as active, in the # 'rc' file's [midi-control-file] section (e.g. "nanomap.ctrl"). # Version 1 adds [mute-control-out] and [automation-control-out]. Versions # 2 and 3 simplify the data items; 4 and 5 add more settings. [Seq66] config-type = "ctrl" version = 5 # [comments] holds user documentation for this file. The first empty, # hash-commented, or tag line ends the comment. [comments] This is the version with the 'usr' setting 'swap-coordinates' set. [midi-control-settings] # The control-buss value ranges from 0 to the maximum system input buss. If set, # that buss will send MIDI control. 255 (0xFF) means any buss can send control. # The buss(es) must be enabled in the 'rc' file. With ALSA, there is an extra # 'announce' buss, which alters the port numbering. # # The 'midi-enabled' flag applies to the MIDI controls; keystrokes are always # enabled. Supported keyboard layouts are 'qwerty' (the default), 'qwertz', and # 'azerty'. AZERTY turns off the auto-shift feature for group-learn. control-buss = 0xFF midi-enabled = false button-offset = 0 button-rows = 4 button-columns = 8 keyboard-layout = qwerty # A control stanza incorporates key control and MIDI, but keys # support only 'toggle'; key-release is an 'invert'. The leftmost # number on each line is the pattern number (e.g. 0 to 31); the group # number, same range; or an automation control number. This number is # is followed by three groups of bracketed numbers, each providing 3 # types of control: # # Normal: [toggle] [on] [off] # Increment/Decr: [increment] [increment] [decrement] # Playback: [pause] [start] [stop] # Playlist/Song: [by-value] [next] [previous] # # In each group, there are six numbers: # # [on/off invert status d0 d1min d1max] # # 'on/off' enables/disables (1/0) the control; 'invert' (1/0) causes # the opposite, but not all support this; all keystroke-releases set # invert to true; 'status' is the MIDI event to match (channel is NOT # ignored); if set to 0x00, the control is disabled; 'd0' is the # first data value (e.g. if status is 0x90 (Note On), d0 is the note # number; d1min to d1max is the range of data values detectable (e.g. # 1 to 127 indicates that any non-zero velocity invokes the control. # Hex values can be used; precede with '0x'. # # ---------------------- Loop, group, or automation-slot number # | ------------------ Name of the key (see the key map) # | | # | | ------------ Inverse # | | | ---------- MIDI status/event byte (e.g. Note On) # | | | | -------- d0: Data 1 (e.g. Note number) # | | | | | ------ d1max: Data 2 min (e.g. Note velocity) # | | | | | | ---- d1min: Data 2 max # | | | | | | | # v v v v v v v # 0 "F1" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Toggle On Off [loop-control] 0 "1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 0 1 "2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 1 2 "3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 2 3 "4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 3 4 "5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 4 5 "6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 5 6 "7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 6 7 "8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 7 8 "q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 8 9 "w" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 9 10 "e" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 10 11 "r" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 11 12 "t" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 12 13 "y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 13 14 "u" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 14 15 "i" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 15 16 "a" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 16 17 "s" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 17 18 "d" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 18 19 "f" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 19 20 "g" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 20 21 "h" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 21 22 "j" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 22 23 "k" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 23 24 "z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 24 25 "x" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 25 26 "c" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 26 27 "v" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 27 28 "b" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 28 29 "n" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 29 30 "m" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 30 31 "," [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 31 [mute-group-control] 0 "!" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 0 1 "@" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 1 2 "#" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 2 3 "$" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 3 4 "%" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 4 5 "^" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 5 6 "&" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 6 7 "*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 7 8 "Q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 8 9 "W" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 9 10 "E" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 10 11 "R" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 11 12 "T" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 12 13 "Y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 13 14 "U" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 14 15 "I" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 15 16 "A" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 16 17 "S" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 17 18 "D" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 18 19 "F" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 19 20 "G" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 20 21 "H" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 21 22 "J" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 22 23 "K" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 23 24 "Z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 24 25 "X" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 25 26 "C" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 26 27 "V" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 27 28 "B" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 28 29 "N" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 29 30 "M" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 30 31 ">" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 31 [automation-control] 0 "'" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Up 1 ";" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Dn 2 "]" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Up 3 "[" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Dn 4 "KP_Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Replace 5 "Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Snapshot 6 "o" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Queue 7 "`" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Mute 8 "l" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Learn 9 "Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playing Set 10 "." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playback 11 "P" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Record 12 "BkSpace" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Solo 13 "KP_/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Thru 14 "PageUp" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Up 15 "PageDn" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Dn 16 "KP_." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Set 17 "KP_*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record 18 "KP_-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quan Record 19 "KP_+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reset Seq 20 "|" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # One-shot 21 "F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # FF 22 "F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Rewind 23 "F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Top 24 "F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play List 25 "F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play Song 26 "F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Tap BPM 27 "Space" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Start 28 "Esc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Stop 29 "KP_Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 29 30 "F8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Mute 31 "F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Pos 32 "\" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Keep Queue 33 "/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Slot Shift 34 "0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mutes Clear 35 "Quit" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quit 36 "=" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop Edit 37 "-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Event Edit 38 "F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Mode 39 "F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle JACK 40 "F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Menu Mode 41 "F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Follow JACK 42 "~" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Panic 43 "0xf9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Visibility 44 "0xfa" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Save Session 45 "0xfb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 45 46 "0xfc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 47 "0xfd" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 48 "0xfe" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 48 [midi-control-out-settings] set-size = 32 output-buss = 255 midi-enabled = false button-offset = 0 button-rows = 4 button-columns = 8 [midi-control-out] # ---------------- Pattern number (as applicable) # | ----------- MIDI status+channel (e.g. Note On) # | | ------- data 1 (e.g. note number) # | | | ----- data 2 (e.g. velocity) # | | | | # v v v v # 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0] # Arm Mute Queue Delete # # In a change from version 1 of this file, a test of the # status/event byte determines the enabled status, and channel # is incorporated into the status. Much cleaner! The order of # the lines must must be preserved. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [mute-control-out] # The format of the mute and automation output events is simpler: # # ----------------- mute-group number # | ------------- MIDI status+channel (e.g. Note On) # | | --------- data 1 (e.g. note number) # | | | ------- data 2 (e.g. velocity) # | | | | # v v v v # 1 [0x00 0 0 ] [0x00 0 0] [0x00 0 0] # On Off Empty (dark) # # The mute-controls have an additional stanza for non-populated # ("deleted") mute-groups. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [automation-control-out] # This format is similar to [mute-control-out], but the first # number is an active-flag, not an index number. The stanzas # are on / off / inactive, except for 'snap', which is store / # restore / inactive. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Panic 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Stop 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Pause 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Toggle_mutes 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_record 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Slot_shift 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Free 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Queue 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Oneshot 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Replace 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Snap 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Learn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Tap_BPM 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Quit 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Visibility 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_2 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_3 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_4 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_5 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_6 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_7 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_8 # End of /home/user/.config/seq66/qseq66.ctrl # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66.ctrl ================================================ # Seq66 0.99.3 MIDI control configuration file # # /home/user/.config/seq66/qseq66.ctrl # Written 2023-03-18 10:11:39 # # Sets up MIDI I/O control. The format is like the 'rc' file. To use it, set it # active in the 'rc' [midi-control-file] section. It adds loop, mute, & # automation buttons, MIDI display, new settings, and macros. [Seq66] config-type = "ctrl" version = 6 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] Add your comment block here [midi-control-settings] # Input settings to control Seq66. 'control-buss' ranges from 0 to the highest # system input buss. If set, that buss can send MIDI control. 255 (0xFF) means # any ENABLED MIDI input can send control. ALSA has an extra 'announce' buss, # so add 1 to the port number with ALSA. With port-mapping enabled, the port # nick-name can be provided. # # 'midi-enabled' applies to the MIDI controls; keystroke controls are always # enabled. Supported keyboard layouts are 'qwerty' (default), 'qwertz', and # 'azerty'. AZERTY turns off auto-shift for group-learn. drop-empty-controls = false control-buss = 0xff midi-enabled = false button-offset = 0 button-rows = 4 button-columns = 8 keyboard-layout = qwerty # A control stanza sets key and MIDI control. Keys support 'toggle', and # key-release is 'invert'. The leftmost number on each line is the loop number # (0 to 31), mutes number (same range), or an automation number. 3 groups of # of bracketed numbers follow, each providing a type of control: # # Normal: [toggle] [on] [off] # Increment/Decr: [increment] [increment] [decrement] # Playback: [pause] [start] [stop] # Playlist/Song: [by-value] [next] [previous] # # In each group, there are 5 numbers: # # [invert status d0 d1min d1max] # # A valid status (> 0x00) enables the control; 'invert' (1/0) inverts the, # the action, but not all support this. 'status' is the MIDI event to match # (channel is NOT ignored); 'd0' is the status value (eg. if 0x90, Note On, # d0 is the note number; d1min to d1max is the range of d1 values detectable. # Hex values can be used; precede with '0x'. # # ------------------------ Loop/group/automation-slot number # | -------------------- Name of key (see the key map) # | | -------------- Inverse # | | | ---------- MIDI status/event byte (eg. Note On) # | | | | ------- d0: Data 1 (eg. Note number) # | | | | | ----- d1max: Data 2 min (eg. Note velocity) # | | | | | | -- d1min: Data 2 max # | | | | | | | # v v v v v v v # 0 "F1" [0 0x90 0 1 127] [0 0x00 0 0 0] [0 0x00 0 0 0] # Toggle On Off # # MIDI controls often send a Note On upon a press and a Note Off on release. # To use a control as a toggle, define only the Toggle stanza. For the control # to act only while held, define the On and Off stanzas with appropriate # statuses for press-and-release. # # Warning: the 'BS' key is actually the Ctrl-H key, and NOT the Backspace key. # The Backspace key is called 'BkSpace' in the Seq66 key-map. [loop-control] 0 "1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 0 1 "q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 1 2 "a" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 2 3 "z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 3 4 "2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 4 5 "w" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 5 6 "s" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 6 7 "x" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 7 8 "3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 8 9 "e" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 9 10 "d" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 10 11 "c" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 11 12 "4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 12 13 "r" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 13 14 "f" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 14 15 "v" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 15 16 "5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 16 17 "t" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 17 18 "g" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 18 19 "b" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 19 20 "6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 20 21 "y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 21 22 "h" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 22 23 "n" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 23 24 "7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 24 25 "u" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 25 26 "j" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 26 27 "m" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 27 28 "8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 28 29 "i" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 29 30 "k" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 30 31 "," [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 31 [mute-group-control] 0 "!" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 0 1 "Q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 1 2 "A" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 2 3 "Z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 3 4 "@" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 4 5 "W" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 5 6 "S" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 6 7 "X" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 7 8 "#" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 8 9 "E" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 9 10 "D" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 10 11 "C" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 11 12 "$" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 12 13 "R" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 13 14 "F" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 14 15 "V" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 15 16 "%" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 16 17 "T" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 17 18 "G" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 18 19 "B" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 19 20 "^" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 20 21 "Y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 21 22 "H" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 22 23 "N" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 23 24 "&" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 24 25 "U" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 25 26 "J" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 26 27 "M" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 27 28 "*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 28 29 "I" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 29 30 "K" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 30 31 "<" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 31 [automation-control] 0 "'" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Up 1 ";" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Dn 2 "]" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Up 3 "[" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Dn 4 "KP_Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Replace 5 "Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Snapshot 6 "o" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Queue 7 "`" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Mute 8 "l" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Learn 9 "Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playing Set 10 "." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playback 11 "P" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Record 12 "BkSpace" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Solo 13 "LF" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Thru 14 "PageUp" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Up 15 "PageDn" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Dn 16 "KP_." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Set 17 "KP_*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record Style 18 "KP_-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quan Record 19 "KP_+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reset Sets 20 "|" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # One-shot 21 "F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # FF 22 "F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Rewind 23 "F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Top 24 "F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play List 25 "F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play Song 26 "F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Tap BPM 27 "Space" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Start 28 "Esc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Stop 29 "KP_Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop L/R 30 "F8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Mutes 31 "F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Pos 32 "\" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Keep Queue 33 "/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Slot Shift 34 "0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mutes Clear 35 "Quit" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quit 36 "=" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop Edit 37 "-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Event Edit 38 "F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Mode 39 "F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle JACK 40 "F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Menu Mode 41 "F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Follow JACK 42 "~" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Panic 43 "0xf9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Visibility 44 "0xfa" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Save Session 45 "+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record Toggle 46 "_" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Mutes 47 "0xfd" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 48 "0xfe" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 48 49 "Sh_F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Overdub 50 "Sh_F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Overwrite 51 "Sh_F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Expand 52 "Sh_F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Oneshot 53 "Sh_F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Loop 54 "Sh_F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Record 55 "Sh_F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Copy 56 "Sh_F8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Paste 57 "Sh_F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Clear 58 "Sh_F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Delete 59 "Sh_F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Thru 60 "Sh_F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Solo 61 "0xe0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Cut 62 "0xe1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Double 63 "0xe2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q None 64 "0xe3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q Full 65 "0xe4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q Tighten 66 "0xe5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Randomize 67 "0xe6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Jitter 68 "0xe7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Note-map 69 "0xe8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BBT/HMS 70 "0xe9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # LR Loop 71 "0xea" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Undo 72 "0xeb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Redo 73 "0xec" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Transpose Song 74 "0xed" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Copy Set 75 "0xee" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Paste Set 76 "0xef" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Tracks 77 "0x8c" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Normal 78 "0x8d" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Auto 79 "0x8e" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Additive 80 "0x8f" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # All Sets [midi-control-out-settings] set-size = 32 output-buss = 0xff midi-enabled = false button-offset = 0 button-rows = 4 button-columns = 8 [midi-control-out] # This section determines how pattern statuses are to be displayed. # ---------------- Pattern or device-button number) # | ----------- MIDI status+channel (eg. Note On) # | | ------- data 1 (eg. note number) # | | | ----- data 2 (eg. velocity) # | | | | # v v v v # 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0] # Armed Muted (Un)queued Empty/Deleted # # A test of the status byte determines the enabled status, and channel is # included in the status. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [mute-control-out] # The format of the mute and automation output events is similar: # # ----------------- mute-group number # | ------------- MIDI status+channel (eg. Note On) # | | --------- data 1 (eg. note number) # | | | ------- data 2 (eg. velocity) # | | | | # v v v v # 1 [0x00 0 0 ] [0x00 0 0] [0x00 0 0] # On Off Empty (dark) # # The mute-controls have an additional stanza for non-populated ("deleted") # mute-groups. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [automation-control-out] # This format is similar to [mute-control-out], but the first number is an # active-flag, not an index number. The stanzas are are on / off / inactive, # except for 'snap', which is store / restore / inactive. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Panic 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Stop 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Pause 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Toggle Mutes 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song Record 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Slot Shift 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Free 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Queue 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # One-shot 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Replace 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Snapshot 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song Mode 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Group Learn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play List Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play List Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play Song Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play Song Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Tap BPM 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Quit 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Visibility 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_2 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_3 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_4 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_5 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_6 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_7 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_8 [macro-control-out] # This format is 'macroname = [ hex bytes | macro-references]'. Macro references # are macro-names preceded by a '$'. Some values should always be defined, even # if empty: footer, header, reset, startup, and shutdown. footer = header = reset = shutdown = startup = # End of /home/user/.config/seq66/qseq66.ctrl # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66.drums ================================================ # Seq66 0.97.2 note-mapper ('drums') configuration file # # /home/user/.config/seq66/qseq66.drums # Written 2021-11-04 14:00:24 # # This file resembles the files generated by 'midicvtpp', modified # for Seq66: # # midicvtpp --csv-drum GM_DD-11_Drums.csv --output ddrums.ini # # This file can convert the percussion of non-GM devices to GM, as # closely as possible. Although it is for drums, it can be used # for other note-mappings. [Seq66] config-type = "drums" version = 0 # [comments] holds user documentation for this file. The first empty, # hash-commented, or tag line ends the comment. [comments] Add your comment block here # Drum/note-mapping configuration for Seq66, stored in the HOME # configuration directory. To use this file, add this file-name to # '[note-mapper]' section of the 'rc' file. There's no user-interface # for this file. The main values are: # # map-type: drum, patch, or multi; indicates the mapping to do. # gm-channel: Indicates the channel (1-16) applied to convert notes. # reverse: true or false; map in the opposite direction if true. [notemap-flags] map-type = "" gm-channel = 1 reverse = false # The drum section: # # [Drum 35]. Marks a GM drum-change section, one per instrument. # # gm-name GM name for the drum assigned to the input note. # gm-note Input note number, same as the section number. # dev-name The device's name for the drum. # dev-note GM MIDI note whose GM sound best matches the sound of # dev-name. The gm-note value is converted to the dev-note # value, unless reverse mapping is activated. The actual GM # drum sound might not match what the MIDI hardware puts out. # This is a sample. See 'data/samples/GM_DD-11.drums' for a full example. [Drum 36] dev-name = "Bass Drum Gated Reverb" gm-name = "Bass Drum 1" dev-note = 36 gm-note = 35 # End of /home/user/.config/seq66/qseq66.drums # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66.mutes ================================================ # Seq66 0.97.2 mute-groups configuration file # # /home/ahlstrom/.config/seq66/qseq66.mutes # Written 2021-11-04 14:00:24 # # Used in the [mute-group-file] section of the 'rc' file, making it # easier to manage multiple sets of mute groups. To use this file, # specify it in [mute-group-file] file and set 'active = true'. [Seq66] config-type = "mutes" version = 0 # [comments] holds user documentation for this file. The first empty, # hash-commented, or tag line ends the comment. [comments] This file goes with contrib/midi/mutes-test.midi # load-mute-groups: set to 'none', or 'mutes' to load from the 'mutes' # file, 'midi' to load from the song, or 'both' to try to # to read from the 'mutes' first, then the 'midi' file. # # save-mutes-to: 'both' writes the mutes to the 'mutes' and MIDI file; # 'midi' writes only to the MIDI file; and the 'mutes' only to the # 'mutes' file. # # strip-empty: If true, all-zero mute-groups are not written to the # MIDI file. # # mute-group-rows and mute-group-columns: Specifies the size of the # grid. Keep these values at 4 and 8; mute-group-count is only for # sanity-checking. # # groups-format: 'binary' means write mutes as 0 or 1; 'hex' means # write them as hexadecimal numbers (e.g. 0xff), useful for larger set # sizes. # # mute-group-selected: if 0 to 31, and mutes are available either from # this file or from the MIDI file, then this mute-group is applied at # startup; useful in restoring a session. Set to -1 to disable. # # toggle-active-only: when a mute-group is toggled off, all patterns, # even those outside the mute-group, are muted. If this flag is set # to true, only patterns in the mute-group are muted. Any patterns # unmuted directly by the user remain unmuted. [mute-group-flags] load-mute-groups = midi save-mutes-to = midi strip-empty = true mute-group-rows = 4 mute-group-columns = 8 mute-group-count = 32 mute-group-selected = -1 groups-format = binary toggle-active-only = false [mute-groups] # Mute-group values are saved in the 'mutes' file, even if all zeroes. # They can be stripped out of the MIDI file by 'strip-empty-mutes'. # A hex number indicates each number is a bit-mask, not a single bit. # An optional quoted group name can be placed at the end of the line. 0 [ 1 1 1 1 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 ] "Column 0" 1 [ 0 0 0 0 1 1 1 1 ] [ 0 0 0 0 0 0 0 0 ] [ 0 0 0 0 0 0 0 0 ] [ 0 0 0 0 0 0 0 0 ] "Column 1" 2 [ 0 0 0 0 0 0 0 0 ] [ 1 1 1 1 0 0 0 0 ] [ 0 0 0 0 0 0 0 0 ] [ 0 0 0 0 0 0 0 0 ] "Column 2" 3 [ 0 0 0 0 0 0 0 0 ] [ 0 0 0 0 1 1 1 1 ] [ 0 0 0 0 0 0 0 0 ] [ 0 0 0 0 0 0 0 0 ] "Column 3" 4 [ 0 0 0 0 0 0 0 0 ] [ 0 0 0 0 0 0 0 0 ] [ 1 1 1 1 0 0 0 0 ] [ 0 0 0 0 0 0 0 0 ] "Column 4" 5 [ 0 0 0 0 0 0 0 0 ] [ 0 0 0 0 0 0 0 0 ] [ 0 0 0 0 1 1 1 1 ] [ 0 0 0 0 0 0 0 0 ] "Column 5" 6 [ 0 0 0 0 0 0 0 0 ] [ 0 0 0 0 0 0 0 0 ] [ 0 0 0 0 0 0 0 0 ] [ 1 1 1 1 0 0 0 0 ] "Column 6" 7 [ 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 1 1 1 1 ] "Column 7" 8 [ 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 ] "Group 8" 9 [ 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 ] "Group 9" 10 [ 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 ] "Group 10" 11 [ 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 ] "Group 11" 12 [ 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 ] "Group 12" 13 [ 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 ] "Group 13" 14 [ 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 ] "Group 14" 15 [ 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 ] "Group 15" 16 [ 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 ] "Group 16" 17 [ 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 ] "Group 17" 18 [ 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 ] "Group 18" 19 [ 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 ] "Group 19" 20 [ 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 ] "Group 20" 21 [ 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 ] "Group 21" 22 [ 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 ] "Group 22" 23 [ 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 ] "Group 23" 24 [ 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 ] "Group 24" 25 [ 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 ] "Group 25" 26 [ 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 ] "Group 26" 27 [ 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 ] "Group 27" 28 [ 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 ] "Group 28" 29 [ 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 ] "Group 29" 30 [ 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 ] "Group 30" 31 [ 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 ] "Group 31" # End of /home/ahlstrom/.config/seq66/qseq66.mutes # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66.palette ================================================ # Seq66 0.99.22 (and above) palette configuration file # # /home/ahlstrom/.config/seq66/qseq66.palette # Written on 2025-10-24 12:36:17 # # This file can be used to change the colors used by patterns # and in some parts of the user-interface. It must be active and # specified in the 'rc' file. [Seq66] config-type = "palette" version = 1 # The [comments] section can document this file. Lines starting with # '#', '[', or that have no characters end the comment. [comments] This is the default palette, processed so the the colors in the last color-code column are the exact inverse of the first color-code column. We still need to verify these values. # Set 'dark-theme' to true if the matching style-sheet is dark. This # also set the 'dark-theme' setting in the 'usr' file. # Set 'dark-ui' to true if the palette background elements are dark. [hints] dark-theme = false dark-ui = false # [palette] affects the pattern colors selected (by number). First is # the color number, 0 to 31. Next is the name of the background color. # The first stanza [square brackets] are the background ARGB values. # The second provides the foreground color name and ARGB values. The # alpha values should be set to FF. [palette] 0 "Black" [ 0xFF000000 ] "White" [ 0xFFFFFFFF ] 1 "Red" [ 0xFFFF0000 ] "White" [ 0xFF00FFFF ] 2 "Green" [ 0xFF008000 ] "White" [ 0xFFFFFF7F ] 3 "Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF00FF00 ] 4 "Blue" [ 0xFF0000FF ] "White" [ 0xFFFF00FF ] 5 "Magenta" [ 0xFFFF00FF ] "White" [ 0xFF0000FF ] 6 "Cyan" [ 0xFF00FFFF ] "Black" [ 0xFFFF0000 ] 7 "White" [ 0xFFFFFFFF ] "Black" [ 0xFF000000 ] 8 "Dark Black" [ 0xFF2F4F4F ] "White" [ 0xFFD0B0B0 ] 9 "Dark Red" [ 0xFF8B0000 ] "White" [ 0xFF74FFFF ] 10 "Dark Green" [ 0xFF006400 ] "White" [ 0xFFFFFF9B ] 11 "Dark Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF00FF00 ] 12 "Dark Blue" [ 0xFF00008B ] "White" [ 0xFFFF74FF ] 13 "Dark Magenta" [ 0xFF8B008B ] "White" [ 0xFF7474FF ] 14 "Dark Cyan" [ 0xFF008B8B ] "White" [ 0xFFFF7474 ] 15 "Dark White" [ 0xFF808080 ] "White" [ 0xFF7F7F7F ] 16 "Orange" [ 0xFFFFA500 ] "White" [ 0xFF00FF5A ] 17 "Pink" [ 0xFFFFC0CB ] "Black" [ 0xFF00343F ] 18 "Pale Green" [ 0xFF98FB98 ] "Black" [ 0xFF676704 ] 19 "Khaki" [ 0xFFF0E68C ] "Black" [ 0xFF0F7319 ] 20 "Light Blue" [ 0xFFADD8E6 ] "Black" [ 0xFF521927 ] 21 "Light Magenta" [ 0xFFEE82EE ] "Black" [ 0xFF11117D ] 22 "Turquoise" [ 0xFF40E0D0 ] "Black" [ 0xFFBF2F1F ] 23 "Grey" [ 0xFF808080 ] "Black" [ 0xFF7F7F7F ] 24 "Dk Orange" [ 0xFFFF8C00 ] "Black" [ 0xFF00FF73 ] 25 "Dark Pink" [ 0xFFFF1493 ] "Black" [ 0xFF006CEB ] 26 "Sea Green" [ 0xFF2E8B57 ] "Black" [ 0xFFD1A874 ] 27 "Dark Khaki" [ 0xFFBDB76B ] "Black" [ 0xFF429448 ] 28 "Dark Slate Blue" [ 0xFF483D8B ] "Black" [ 0xFFB774C2 ] 29 "Dark Violet" [ 0xFF9400D3 ] "Black" [ 0xFF6B2CFF ] 30 "Light Grey" [ 0xFFC0C0C0 ] "Black" [ 0xFF3F3F3F ] 31 "Dark Grey" [ 0xFF484848 ] "Black" [ 0xFFB7B7B7 ] # Similar to the [palette] section, but applies to the custom-drawn # piano rolls and the --inverse option. The values: color number (0 # to 31); main color feature name; main color value; inverse color # feature name; and the --inverse color value. [ui-palette] 0 "Foreground" [ 0xFF000000 ] "Foreground" [ 0xFFFFFFFF ] 1 "Background" [ 0xFFFFFFFF ] "Background" [ 0xFF000000 ] 2 "Label" [ 0xDEADBEEF ] "Label" [ 0x00000000 ] 3 "Selection" [ 0xFFFFA500 ] "Selection" [ 0xFF00FF5A ] 4 "Drum" [ 0xFFFF0000 ] "Drum" [ 0xFF00FFFF ] 5 "Tempo" [ 0xFFFFFF00 ] "Tempo" [ 0xFF00FF00 ] 6 "Note Fill" [ 0xFFFFFFFF ] "Note Fill" [ 0xFF000000 ] 7 "Note Border" [ 0xFF000000 ] "Note Border" [ 0xFFFFFFFF ] 8 "Black Keys" [ 0xFF000000 ] "Black Keys" [ 0xFFFFFFFF ] 9 "White Keys" [ 0xFFFFFFFF ] "White Keys" [ 0xFF000000 ] 10 "Progress Bar" [ 0xFFFF0000 ] "Progress Bar" [ 0xFF00FFFF ] 11 "Back Pattern" [ 0xFF008B8B ] "Back Pattern" [ 0xFFFF7474 ] 12 "Medium Line" [ 0xFF808080 ] "Medium Line" [ 0xFF7F7F7F ] 13 "Heavy Line" [ 0xFF484848 ] "Heavy Line" [ 0xFFB7B7B7 ] 14 "Light Line" [ 0xFFC0C0C0 ] "Light Line" [ 0xFF3F3F3F ] 15 "Beat" [ 0xFF000000 ] "Beat" [ 0xFFFFFFFF ] 16 "Near" [ 0xFFFFFF00 ] "Near" [ 0xFF00FF00 ] 17 "Time Brush" [ 0xFF808080 ] "Time Brush" [ 0xFF7F7F7F ] 18 "Data Brush" [ 0xFF808080 ] "Data Brush" [ 0xFF7F7F7F ] 19 "Event Brush" [ 0xFF808080 ] "Event Brush" [ 0xFF7F7F7F ] 20 "Keys Brush" [ 0xFF808080 ] "Keys Brush" [ 0xFF7F7F7F ] 21 "Names Brush" [ 0xFF808080 ] "Names Brush" [ 0xFF7F7F7F ] 22 "Octave Line" [ 0xFF808080 ] "Octave Line" [ 0xFF7F7F7F ] 23 "Text" [ 0xFF000000 ] "Text" [ 0xFFFFFFFF ] 24 "Time Text" [ 0xFF000000 ] "Time Text" [ 0xFFFFFFFF ] 25 "Data Text" [ 0xFF000000 ] "Data Text" [ 0xFFFFFFFF ] 26 "Note Event" [ 0xFFFFFFFF ] "Note Event" [ 0xFF000000 ] 27 "Keys Text" [ 0xFF000000 ] "Keys Text" [ 0xFFFFFFFF ] 28 "Names Text" [ 0xFF000000 ] "Names Text" [ 0xFFFFFFFF ] 29 "Slots Text" [ 0xFF000000 ] "Slots Text" [ 0xFFFFFFFF ] 30 "Scale Brush" [ 0xFFC0C0C0 ] "Scale Brush" [ 0xFF3F3F3F ] 31 "Extra" [ 0xFF000000 ] "Extra" [ 0xFFFFFFFF ] # This section defines brush styles to use. The names are based on the # names in the Qt::BrushStyle enumeration. The names are: # # nobrush, solid, dense1, dense2, dense3, dense4, dense5, dense6, # dense7, horizontal, vertical, cross, bdiag, fdiag, diagcross, # lineargradient, radialgradient, and conicalgradient. # # For 'empty', best to just use 'solid' (try others and see why). # For 'note', use only 'solid' or the default, 'lineargradient'. These # also apply to the progress box and triggers. [brushes] empty = solid note = lineargradient scale = dense3 backseq = dense2 chord = bdiag # This section defines pen styles to use for vertical time lines. # The names are based on the names in the Qt::PenStyle enumeration. # They are: # nopen, solid, dash, dot, dashdot, dashdotdot, customdash (which # is currently not supported). # # 'measure' and 'beat' are obvious. 'fourth' is a line for the 1/4s of # a beat, and 'step' is the smallest unit (normally 6 pixels). [pens] measure = solid beat = solid fourth = dashdot step = dot # End of /home/ahlstrom/.config/seq66/qseq66.palette # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66.playlist ================================================ # Seq66 0.97.7 playlist configuration file # # /home/ahlstrom/.config/seq66/qseq66.playlist # Written 2023-08-06 14:00:24 # # This file holds multiple playlists, with one or more [playlist] # sections. Each has a user-specified number for sorting and MIDI # control, ranging from 0 to 127. Next comes a quoted name for this # list, followed by the quoted name of the song folder using the UNIX # separator ('/'). It should be accessible wherever Seq66 is run. # # Next is a list of tunes, each starting with a MIDI control number # and the quoted name of the MIDI file, sorted by the control number. # They can be simple 'base.midi' file-names; the playlist directory # is prepended to access the song. If the file-name has a path, that # will be used. [Seq66] config-type = "playlist" version = 1 # [comments] holds user documentation for this file. The first empty, # hash-commented, or tag line ends the comment. [comments] Defines the basic skeleton of a play-list file. [playlist-options] # 'unmute-next-song' causes the next selected song to have all patterns # armed for playback. (Should be called 'auto-arm'). Does not matter for # songs with triggers for Song mode. 'auto-play' causes songs to start play # automatically when loaded. 'auto-advance' implies the settings noted # above. It automatically loads the next song in the play-list when the # current song ends. 'deep-verify' causes each tune in the play-list to be # loaded to make sure each one can be loaded. Otherwise, only file existence # is checked. unmute-new-song = false auto-play = true auto-advance = true deep-verify = false # First provide the playlist settings, its default storage folder, # and then list each tune with its control number. The playlist # number is arbitrary but unique. 0 to 127 recommended for use with # the MIDI playlist control. Similar for the tune numbers. Each # tune can include a path; it overrides the base directory. [playlist] # This is a playlist SAMPLE. number = 0 name = "Legacy Midi Files" directory = "/usr/local/share/seq66-0.99/midi/FM/" 0 "brecluse.mid" 1 "carptsun.mid" 2 "cbflitfm.mid" 3 "dasmodel.mid" 4 "grntamb.mid" 5 "hapwandr.mid" 6 "judyblue.mid" 7 "k_seq11.mid" 8 "longhair.mid" 9 "marraksh.mid" 10 "oxyg4bfm.mid" 11 "pirates.mid" 12 "pss680.mid" 13 "qufrency.mid" 14 "stdemo3.mid" 15 "viceuk.mid" 16 "wallstsm.mid" [playlist] number = 1 name = "PSS-790 Midi Files" directory = "/usr/local/share/seq66-0.99/midi/PSS-790/" 0 "ancestor.mid" 10 "carptsun.mid" 20 "cbflite.mid" 30 "old_love.mid" [playlist] number = 3 name = "Live vs Song Files" directory = "/usr/local/share/seq66-0.99/midi/" 0 "carptsun.midi" 1 "Peter_Gunn-reconstructed.midi" # End of /home/ahlstrom/.config/seq66/qseq66.playlist # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66.rc ================================================ # Seq66 0.99.6 main ('rc') configuration file # # /home/user/.config/seq66/qseq66.rc # Written 2026-04-17 12:00:00 # # This file holds the main configuration for Seq66. It no longer follows the # format of the seq24rc configuration file. # # 'version' is set by Seq66; it is used to detect older configuration files, # which are upgraded to the new version when saved. # # 'quiet' suppresses start-up error messages. Useful when they are not # relevant. There's no --quiet command-line option yet. It's NOT the opposite # of 'verbose'. # # 'verbose' is temporary, same as --verbose; it's set to false at exit. # # 'sets-mode' affects set muting when moving to the next set. 'normal' leaves # the next set muted. 'auto-arm' unmutes it. 'additive' keeps the previous set # armed when moving to the next set. 'all-sets' arms all sets at once. # # 'port-naming': 'short', 'pair', or 'long'. If 'short', the device name is # shown. If it is generic, the client name is added for clarity. If 'pair', # the client:port number is prepended. If 'long', the full set of name items # is shown. If port-mapping is active (now the default), this does not apply. # # 'init-disabled-ports' does not yet work. It tries live toggle of port state. # # 'priority' greater than 0 is meant to increase the priority of the I/O # threads. It needs Seq66 to run as root, or be installed as setuid 0. [Seq66] config-type = "rc" version = 4 quiet = false verbose = false sets-mode = normal port-naming = short init-disabled-ports = false priority = 10 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] Add your comment block here. This is a sample file and not necessarily realistic for your system. Also, we add about 8 bogus ports to test the MIDI Clocks and MIDI Inputs tabs. # Provides a flag and file-name for MIDI-control I/O settings. '""' means # no 'ctrl' file. If none, default keystrokes are used, with no MIDI control. # Note that all configuration files are stored in the "home" configuration # directory; any paths in the file-names are stripped. [midi-control-file] active = true name = "qseq66-lp-mini-alt.ctrl" # Provides a flag and file-name for mute-groups settings. '""' means no # 'mutes' file. If none, there are no mute groups, unless the MIDI file # contains some. [mute-group-file] active = false name = "qseq66.mutes" # Provides a flag and file-name for 'user' settings. '""' means no 'usr' # file. If none, there are no special user settings. Using no 'usr' file # should be considered experimental. [usr-file] active = true name = "qseq66.usr" # Provides a flag and play-list file. If no list, use '""' and set active # = false. Use the extension '.playlist'. Even if not active, the play-list # file is read. 'base-directory' sets the directory holding all MIDI files # in all play-lists, useful when copying play-lists/tunes from one place to # another; it preserves sub-directories (e.g. in creating an NSM session). [playlist] active = false name = "qseq66.playlist" base-directory = "" # Provides a flag and file-name for note-maps. '""' means no 'drums' file. # This file is used when the user invokes the note-conversion operation in # the pattern editor of a transposable pattern. Make the pattern temporarily # transposable to allow this operation. [note-mapper] active = false name = "qseq66.drums" # Provides a flag and file-name to provide a list of patches for legacy # non-GM-compliant devices. [patches-file] active = false name = "PSS-790.patches" # Provides a flag and a file-name to allow modifying the palette using the file # specified. Use '""' to indicate no 'palette' file. If none or not active, # the internal palette is used. [palette-file] active = false name = "monogreen.palette" # If specified, a style-sheet (e.g. 'qseq66.qss') is applied at startup. # This file must be located in Seq66's "home" directory. Copy if needed. # Note that style-sheet specification has been removed from the 'usr' file. [style-sheet-file] active = false name = "monogreen.qss" # Defines features of MIDI meta-event handling. Tempo events are in the first # track (pattern 0), but one can use them elsewhere. It changes where tempo # events are recorded. The default is 0, the maximum is 1023. A pattern must # exist at this number. [midi-meta-events] tempo-track = 0 # Set to true to create virtual ALSA/JACK I/O ports and not auto-connect to # other clients. Allows up to 48 output or input ports (defaults to 8 and 4). # If true, it disables port-mapping. Keep it false to auto-connect Seq66 to # real ALSA/JACK MIDI ports and preserve port-mapping. Set 'auto-enable' to # enable all virtual ports automatically. [manual-ports] virtual-ports = false auto-enable = false output-port-count = 8 input-port-count = 4 # These MIDI ports are for input and control. JACK's view: these are # 'playback' devices. The first number is the bus, the second number is the # input status, disabled (0) or enabled (1). The item in quotes is the full # input bus name. The type of port depends on the 'virtual-ports' setting. [midi-input] 8 # number of MIDI input (or control) buses 0 1 "[0] 0:1 system:ALSA Announce" 1 0 "[1] 14:0 Midi Through Port-0" 2 0 "[2] 36:0 Launchpad Mini MIDI 1" 3 1 "[3] 40:0 nanoKEY2 nanoKEY2 _ CTRL" 4 0 "[4] 44:0 CH345 MIDI 1" 5 0 "[5] 48:0 E-MU XMidi1X1 Tab Out" 6 0 "[6] 52:0 USB Midi MIDI 1" 7 0 "[7] 56:0 Q25 MIDI 1" # This table is similar to the [midi-clock-map] section, but the values are # different. -2 = unavailable; 0 = not inputing; 1 = enabled for inputing. [midi-input-map] 1 # map is active 0 1 "ALSA Announce" 1 0 "Midi Through Port-0" 2 0 "Launchpad Mini MIDI 1" 3 1 "nanoKEY2 nanoKEY2 _ CTRL" 4 0 "CH345 MIDI 1" 5 0 "E-MU XMidi1X1 Tab Out" 6 0 "USB Midi MIDI 1" 7 0 "Q25 MIDI 1" 8 -2 "Excess 0" 9 -2 "Excess 1" 10 -2 "Excess 2" 11 -2 "Excess 3" 12 -2 "Excess 4" 13 -2 "Excess 5" 14 -2 "Excess 7" 15 -2 "Excess 7" # These MIDI ports are for output, playback, and display. JACK's view: these # are 'capture' devices. The first line shows the count of output ports. # Each line shows the bus number and clock status of that bus: # # -2 = The output port is not present on the system (unavailable). # -1 = The output port is disabled. # 0 = MIDI Clock is off. The output port is enabled. # 1 = MIDI Clock on; Song Position and MIDI Continue are sent. # 2 = MIDI Clock Modulo. # # With Clock Modulo, clocking doesn't begin until song position reaches the # start-modulo value [midi-clock-mod-ticks]. Ports that are unavailable # (because another portapplication, e.g. Windows MIDI Mapper, has exclusive # access to the device) are displayed ghosted. [midi-clock] 9 # number of MIDI clocks (output/display buses) 0 0 "[0] 14:0 Midi Through Port-0" 1 0 "[1] 36:0 Launchpad Mini MIDI 1" 2 0 "[2] 40:0 nanoKEY2 nanoKEY2 _ CTRL" 3 0 "[3] 44:0 CH345 MIDI 1" 4 0 "[4] 48:0 E-MU XMidi1X1 Tab Out" 5 0 "[5] 52:0 USB Midi MIDI 1" 6 0 "[6] 56:0 Q25 MIDI 1" 7 0 "[7] 128:0 FLUID Synth (21079):Synth input port (21079:0)" 8 0 "[8] 129:0 yoshimi:input" # Patterns use bus numbers, not names. This table provides virtual bus numbers # that match real devices and can be stored in each pattern. The bus number # is looked up in this table, the port nick-name is retrieved, and the true # bus number is obtained and used. Thus, if the ports change order in the MIDI # system, the pattern will use the proper port. The short nick-names work in # ALSA or JACK (a2jmidid bridge). [midi-clock-map] 1 # map is active 0 0 "Midi Through Port-0" 1 0 "Launchpad Mini MIDI 1" 2 0 "nanoKEY2 nanoKEY2 _ CTRL" 3 0 "CH345 MIDI 1" 4 0 "E-MU XMidi1X1 Tab Out" 5 0 "USB Midi MIDI 1" 6 0 "Q25 MIDI 1" 7 0 "FLUID Synth" 8 0 "yoshimi:input" 9 -2 "Excess 0" 10 -2 "Excess 1" 11 -2 "Excess 3" 12 -2 "Excess 4" 13 -2 "Excess 5" 14 -2 "Excess 6" 15 -2 "Excess 7" # 'ticks' provides the Song Position (16th notes) at which clocking begins if # the bus is set to MIDI Clock Mod setting. 'record-by-channel' allows the # master MIDI bus to record/filter incoming MIDI data by channel, adding each # new MIDI event to the pattern that is set to that channel. Option adopted # from the Seq32 project at GitHub. [midi-clock-mod-ticks] ticks = 64 record-by-buss = false record-by-channel = false # This section defines tweaks to the reading or writing of MIDI files. # Indicates how to handle MIDI files with incorrect running status. Default # is 'recover', which tries to recover the running status when a data byte # is encountered; 'skip' ignores the rest of the bytes in the track; # 'proceed' keeps going; 'abort' just exits the parsing, which is the old # and undesirable behavior. Try each option with the 'trilogy.mid' file. [midi-file-tweaks] running-status-action = recover # Set to true to have Seq66 ignore port names defined in the 'usr' file. Use # this option to to see the system ports as detected by ALSA/JACK. [reveal-ports] show-system-ports = false # This section sets up a metronome that can be activated from the main live # grid. It consists of a 'main' note on the first beat, then 'sub' notes on # the rest of the beats. The patch/program, note value, velocity, and # fraction length relative to the beat width (can be specified. The length # ranges from about 0.125 (one-eight) to 1.0 (the same length as the beat # width) to 2.0). [metronome] output-buss = 4 output-channel = 9 beats-per-bar = 4 beat-width = 4 main-patch = 15 main-note = 75 main-note-velocity = 120 main-note-length = 0 sub-patch = 33 sub-note = 76 sub-note-velocity = 84 sub-note-length = 0 count-in-active = false count-in-measures = 1 count-in-recording = false recording-buss = 3 recording-measures = 0 thru-buss = 0 thru-channel = 0 # Sets mouse usage for drawing/editing patterns. 'Fruity' mode is NOT in # Seq66. Other settings are available: 'snap-split' enables splitting # song-editor triggers at a snap position instead of in its middle. Split is # done by a middle-click or ctrl-left click. 'double-click-edit' allows double- # click on a slot to open it in a pattern editor. Set it to false if # you don't like how it works. [interaction-method] snap-split = false double-click-edit = true # transport-type enables synchronizing with JACK Transport. Values: # none: No JACK Transport in use. # slave: Use JACK Transport as Slave. # master: Attempt to serve as JACK Transport Master. # conditional: Serve as JACK master if no JACK master exists. # # song-start-mode playback is either Live, Song, or Auto: # live: Muting & unmuting of loops in the main window. # song: Playback uses Song (performance) editor data. # auto: If the loaded tune has song triggers, use Song mode. # # jack-midi sets/unsets JACK MIDI, separate from JACK transport. # jack-auto-connect sets connecting to JACK ports found. Default = true; use # false to have a session manager make the connections. # jack-use-offset attempts to calculate timestamp offsets to improve accuracy # at high-buffer sizes. Still a work in progress. # jack-buffer-size allows for changing the frame-count, a power of 2. [jack-transport] transport-type = slave song-start-mode = auto jack-midi = true jack-auto-connect = true jack-use-offset = true jack-buffer-size = 0 # 'auto-save-rc' sets automatic saving of the 'rc' and other files. If set, # many command-line settings are saved to configuration files. # # 'old-triggers' saves triggers in a format compatible with Seq24. Otherwise, # triggers are saved with an additional 'transpose' setting. The old-mutes # value, if true, saves mute-groups as long values (!) instead of bytes. [auto-option-save] auto-save-rc = false save-old-triggers = false save-old-mutes = false # Specifies the last-used/currently-active directory. [last-used-dir] "/home/user/data/midi" # The most recently-loaded MIDI files. 'full-paths' = true means to show the # full file-path in the menu. The most recent file (top of list) can be loaded # via 'load-most-recent' at startup. [recent-files] full-paths = false load-most-recent = true count = 2 "/home/user/data/midi/Chameleon-HHancock-Ov.midi" "/home/user/data/midi/carptsun-2.midi" # End of /home/user/.config/seq66/qseq66.rc # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66.rc.legacy ================================================ # Seq66 0.90.0 (and above) rc configuration file # # /home/user/.config/sequencer66/qseq66.rc # Written on 2019-03-26 09:00:11 # # This file holds the main configuration options for Seq66. # It loosely follows the format of the seq24 'rc' configuration # file, but adds some new options, and is no longer compatible. # # The next section is only a marker to help better control configuration # management. [Sequencer66] config-type = "rc" version = 0 # The [comments] section holds the user's documentation for this file. # Lines starting with '#' and '[' are ignored. Blank lines are ignored; # add a blank line by adding a space character to the line. [comments] (Comments added to this section are preserved. Lines starting with a '#' or '[', or that are blank, are ignored. Start lines that must be blank with a space.) [midi-control-flags] load-key-controls = true load-midi-control = true # This new style of control stanza incorporates key control as well. # The leftmost number on each line here is the pattern number (e.g. # 0 to 31); the group number, same range, for up to 32 groups; or it # it is an automation control number, again a similar range. # This internal MIDI control number is followed by three groups of # bracketed numbers, each providing three different type of control: # # Normal: [toggle] [on] [off] # Playback: [pause] [start] [stop] # Playlist: [by-value] [next] [previous] (if active) # # In each group, there are six numbers: # # [on/off invert status d0 d1min d1max] # # 'on/off' enables/disables (1/0) the MIDI control for the pattern. # 'invert' (1/0) causes the opposite if data is outside the range. # 'status' is by MIDI event to match (channel is NOT ignored). # 'd0' is the first data value. Example: if status is 144 (Note On), # then d0 represents Note 0. # # 'd1min'/'d1max' are the range of second values that should match. # Example: For a Note On for note 0, 0 and 127 indicate that any # Note On velocity will cause the MIDI control to take effect. # ------------------------- Loop, group, or automation-slot number # | ---------------------- Name of the key (see the key map) # | | ------------------ On/off (indicate if section is enabled) # | | | ----------------- Inverse # | | | | -------------- MIDI status (event) byte (e.g. Note On) # | | | | | ------------ Data 1 (e.g. Note number) # | | | | | | ---------- Data 2 min # | | | | | | | -------- Data 2 max # | | | | | | | | # v v v v v v v v # 0 "1" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Toggle On Off [loop-control] 0 "1" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 0 1 "q" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 1 2 "a" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 2 3 "z" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 3 4 "2" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 4 5 "w" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 5 6 "s" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 6 7 "x" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 7 8 "3" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 8 9 "e" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 9 10 "d" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 10 11 "c" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 11 12 "4" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 12 13 "r" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 13 14 "f" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 14 15 "v" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 15 16 "5" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 16 17 "t" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 17 18 "g" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 18 19 "b" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 19 20 "6" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 20 21 "y" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 21 22 "h" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 22 23 "n" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 23 24 "7" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 24 25 "u" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 25 26 "j" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 26 27 "m" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 27 28 "8" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 28 29 "i" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 29 30 "k" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 30 31 "," [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop 31 [mute-group-control] 0 "!" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 0 1 "Q" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 1 2 "A" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 2 3 "Z" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 3 4 "@" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 4 5 "W" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 5 6 "S" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 6 7 "X" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 7 8 "#" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 8 9 "E" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 9 10 "D" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 10 11 "C" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 11 12 "$" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 12 13 "R" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 13 14 "F" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 14 15 "V" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 15 16 "%" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 16 17 "T" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 17 18 "G" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 18 19 "B" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 19 20 "^" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 20 21 "Y" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 21 22 "H" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 22 23 "N" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 23 24 "&" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 24 25 "U" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 25 26 "J" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 26 27 "M" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 27 28 "*" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 28 29 "I" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 29 30 "K" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 30 31 "<" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Mute 31 [automation-control] 0 "'" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # BPM Up 1 ";" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # BPM Dn 2 "]" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Set Up 3 "[" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Set Dn 4 "KP_Home" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Replace 5 "Ins" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Snapshot 6 "o" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Queue 7 "`" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Group Mute 8 "l" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Group Learn 9 "Home" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Playing Set 10 "." [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Playback 11 "P" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Song Record 12 "BS" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Solo 13 "KP_/" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Thru 14 "PageUp" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # BPM Page Up 15 "PageDn" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # BPM Page Dn 16 "KP_." [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Set Set 17 "KP_*" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Record 18 "KP_-" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Quan Record 19 "KP_+" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Reset Seq 20 "|" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # One-shot 21 "F6" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # FF 22 "F5" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Rewind 23 "F1" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Top 24 "F2" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Play List 25 "F3" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Play Song 26 "F9" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Tap BPM 27 "Space" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Start 28 "Esc" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Stop 29 "KP_Ins" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Snapshot_2 30 "F8" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Toggle Mute 31 "F7" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Song Pos 32 "\" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Keep Queue 33 "/" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Slot Shift 34 "Null_f0" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Reserved 34 35 "Null_f1" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Reserved 35 36 "Null_fb" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Loop Edit 37 "Null_fc" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Event Edit 38 "Null_fd" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Song Mode 39 "Null_fe" [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] [ 0 0 0x00 0 0 0 ] # Toggle JACK [mute-group-flags] save-mutes-to-rc = preserve mute-group-rows = 4 mute-group-columns = 8 write-legacy-mutes = false [mute-groups] # All mute-group values are saved in this 'mutes' file, even if they # all are zero; but if all are zero, they will be stripped out from # the MIDI file by the new strip-empty-mutes functionality (a build # option). This is less confusing to the user, who expects that # section to be intact. [midi-clock] # The first line indicates the number of MIDI busses defined. # Each buss line contains the buss (re 0) and the clock status of # that buss. 0 = MIDI Clock is off; 1 = MIDI Clock on, and Song # Position and MIDI Continue will be sent, if needed; 2 = MIDI # Clock Modulo, where MIDI clocking will not begin until the song # position reaches the start modulo value [midi-clock-mod-ticks]. # A value of -1 indicates that the output port is totally # disabled. One can set this value manually for devices that are # present, but not available, perhaps because another application # has exclusive access to the device (e.g. on Windows). # number of MIDI clocks (output busses) [midi-clock-mod-ticks] # The Song Position (in 16th notes) at which clocking will begin # if the buss is set to MIDI Clock mod setting. 64 [midi-meta-events] # This section defines some features of MIDI meta-event handling. # Normally, tempo events are supposed to occur in the first track # (pattern 0). But one can move this track elsewhere to accomodate # one's existing body of tunes. If affects where tempo events are # recorded. The default value is 0, the maximum is 1023. # A pattern must exist at this number for it to work. 0 # tempo_track_number [midi-input] # number of input MIDI busses # The first number is the port number, and the second number # indicates whether it is disabled (0), or enabled (1). # If set to 1, this option allows the master MIDI bus to record # (filter) incoming MIDI data by channel, allocating each incoming # MIDI event to the sequence that is set to that channel. # This is an option adopted from the Seq32 project at GitHub. 0 # flag to record incoming data by channel [manual-alsa-ports] # Set to 1 to have seq66 create its own ALSA ports and not # connect to other clients. Use 1 to expose all 16 MIDI ports to # JACK (e.g. via a2jmidid). Use 0 to access the ALSA MIDI ports # already running on one's computer, or to use the autoconnect # feature (Seq66 connects to existing JACK ports on startup. 0 # flag for manual ALSA ports [reveal-alsa-ports] # Set to 1 to have seq66 ignore any system port names # declared in the 'user' configuration file. Use this option if # you want to be able to see the port names as detected by ALSA. 0 # flag for reveal ALSA ports # Set to 1 to allow Seq66 to stay in note-adding mode when # the right-click is released while holding the Mod4 (Super or # Windows) key. 0 # allow_mod4_mode # Set to 1 to allow Seq66 to split performerance editor # triggers at the closest snap position, instead of splitting the # trigger exactly in its middle. Remember that the split is # activated by a middle click. 0 # allow_snap_split # Set to 1 to allow a double-click on a slot to bring it up in # the pattern editor. This is the default. Set it to 0 if # it interferes with muting/unmuting a pattern. 1 # allow_click_edit [jack-transport] # jack_transport - Enable slave synchronization with JACK Transport. # Also contains the new flag to use JACK MIDI. 0 # with_jack_transport # jack_master - Seq66 attempts to serve as JACK Master. # Also must enable jack_transport (the user interface forces this, # and also disables jack_master_cond). 0 # with_jack_master # jack_master_cond - Seq66 is JACK master if no other JACK # master exists. Also must enable jack_transport (the user interface # forces this, and disables jack_master). 0 # with_jack_master_cond # song_start_mode (applies mainly if JACK is enabled). # 0 = Playback in live mode. Allows muting and unmuting of loops. # from the main (patterns) window. Disables both manual and # automatic muting and unmuting from the performerance window. # 1 = Playback uses the song (performerance) editor's data and mute # controls, regardless of which window was used to start the # playback. 0 # song_start_mode # jack_midi - Enable JACK MIDI, which is a separate option from # JACK Transport. 0 # with_jack_midi [auto-option-save] # Set the following value to 0 to disable the automatic saving of the # current configuration to the 'rc' and 'user' files. Set it to 1 to # follow seq24 behavior of saving the configuration at exit. # Note that, if auto-save is set, many of the command-line settings, # such as the JACK/ALSA settings, are then saved to the configuration, # which can confuse one at first. Also note that one currently needs # this option set to 1 to save the configuration, as there is not a # user-interface control for it at present. 1 # auto-save-options-on-exit support flag [last-used-dir] # Last-used and currently-active directory: ~/ [recent-files] # Holds a list of the last few recently-loaded MIDI files. 0 [playlist] # Provides a configured play-list and a flag to activate it. # playlist_active: 1 = active, 0 = do not use it 0 # Provides the name of a play-list. If there is none, use '""'. # Or set the flag above to 0. .playlist # End of /home/user/.config/sequencer66/qseq66.rc # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/qseq66.usr ================================================ # Seq66 0.99.9 user ('usr') configuration file # # /home/user/.config/seq66/qseq66.usr # Written 2024-12-22 06:56:34 # # 'usr' file. Edit it and place it in ~/.config/seq66. It allows naming each # MIDI bus/port, channel, and control code. [Seq66] config-type = "usr" version = 11 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] Things of note in this file: The user-options pdf-viewer on our test system is the old-but-workable GNU application evince. On our test Windows system, a normal-user install of evince put it in C:/Users/chris/AppData/Local/Apps/Evince-2.32.0.145/bin/evince.exe # [user-midi-bus-definitions] # # 1. Define instruments and their control-code names, as applicable. # 2. Define MIDI busses, names, and the instruments on each channel. # # Channels are counted from 0-15, not 1-16. Instruments not set here are set # to -1 and are GM (General MIDI). These labels are shown in MIDI Clocks, # Inputs, the pattern editor buss, channel, and event drop-downs. To disable # entries, set counts to 0. [user-midi-bus-definitions] 0 # number of user-defined MIDI busses # In these MIDI instrument definitions, active (supported by the instrument) # controller numbers are paired with the (optional) name of the controller. [user-instrument-definitions] 0 # instrument list count # [user-interface-settings] # # Configures some user-interface elements. Obsolete ones were removed in # version 5 of this file. Also see [user-ui-tweaks]. The grid holds Qt push- # buttons. For styling, use Qt themes/style-sheets. # # 'swap-coordinates' swaps numbering so pattern numbers vary fastest by column # instead of rows. This setting applies to the live grid, mute-group buttons, # and set-buttons. # # 'mainwnd-rows' and 'mainwnd-columns' (option '-o sets=RxC') specify # rows/columns in the main grid. R ranges from 4 to 8, C from 4 to 12. # Values other than 4x8 have not been tested thoroughly. # # 'mainwnd-spacing' is for grid buttons; from 0 to 16, default = 2. # # 'default-zoom' is the initial zoom for piano rolls. From 1 to 512, default # = 2. Larger PPQNs require larger zoom to look good. Seq66 adapts the zoom to # the PPQN if set to 0. The unit of zoom is ticks/pixel. # # 'global-seq-feature' applies the key, scale, and background pattern to all # patterns versus separately to each. If all, these values are stored in the # MIDI file in the global SeqSpec versus in each track. # # 'progress-bar-thick specifies a thicker progress bar. Default is 2 pixels, # 1 pixel if set to false. Also affects the slot box border and the boldness # of the slot font. # # 'follow-progress specifies the default for following progress in the piano # rolls. Each window has a button to toggle following progess # # 'inverse-colors' (option -K/--inverse) specifies use of an inverse color # palette. Palettes are for Seq66 drawing areas, not for Qt widgets. # Normal/inverse palettes can be reconfigured via a 'palette' file. # # 'time-fg-color' and 'time-bg-color' override the default colors for ticks # and time displays (lime on black). 'default' keeps the defaults. 'normal' # uses the theme color. # # 'dark-theme' specifies that a dark theme is active, so that some colors # (e.g. grid-slot text) are inverted. # # 'window-redraw-rate' specifies the base window redraw rate for all windows. # From 10 to 100; default = 40 ms (25 ms for Windows). # # Window-scale (option '-o scale=m.n[xp.q]') specifies scaling the main # window at startup. Defaults to 1.0 x 1.0. If between 0.5 and 3.0, it # changes the size of the main window proportionately. # # 'enable-learn-confirmation' can be set to false to disable the prompt that # the mute-group learn action succeeded. Can be annoying. [user-interface-settings] swap-coordinates = false mainwnd-rows = 4 mainwnd-columns = 8 mainwnd-spacing = 2 default-zoom = 2 global-seq-feature = false progress-bar-thick = false follow-progress = false progress-box-elliptical = false follow-progress = true gridlines-thick = true inverse-colors = false time-fg-color = "lime" time-bg-color = "black" dark-theme = false window-redraw-rate = 40 window-scale = 1 window-scale-y = 1 enable-learn-confirmation = false # Seq66 separates file PPQN from the Seq66 PPQN. 'default-ppqn' specifies the # Seq66 PPQN, from 32 to 19200, default = 192. 'use-file-ppqn' (recommended) # indicates to use file PPQN. [user-midi-ppqn] default-ppqn = 192 use-file-ppqn = true # This section specifies the default values to use to jitter the MIDI event # time-stamps and randomize event amplitudes (e.g. velocity for notes). The # range of jitter is 1/j times the current snap value. [user-randomization] jitter-divisor = 16 amplitude = 4 # [user-midi-settings] # # Specifies MIDI-specific variables. -1 means the value isn't used. # # Item Default Range # 'convert-to-smf-1': true true/false. # 'beats-per-bar': 4 1 to 32. # 'beats-per-minute': 120.0 2.0 to 600.0. # 'beat-width': 4 1 to 32. # 'buss-override': -1 (none) -1 to 48. # 'velocity-override': -1 (Free) -1 to 127. # 'bpm-precision': 0 0 to 2. # 'bpm-step-increment': 1.0 0.01 to 25.0. # 'bpm-page-increment': 1.0 0.01 to 25.0. # 'bpm-minimum': 0.0 127.0 # 'bpm-maximum': 0.0 127.0 # # 'convert-to-smf-1' controls if SMF 0 files are split into SMF 1 when read. # 'buss-override' sets the output port for all patterns, for testing, etc. # This value will be saved if you save the MIDI file!!! # 'velocity-override' controls adding notes in the pattern editor; see the # 'Vol' button. -1 ('Free'), preserves incoming velocity. # 'bpm-precision' (spinner and MIDI control) is 0, 1, or 2. # 'bpm-step-increment' affects the spinner and MIDI control. For 1 decimal, # 0.1 is good. For 2, 0.01 is good, 0.05 is faster. Set 'bpm-page-increment' # larger than the step-increment; used with the Page-Up/Page-Down keys in the # spinner. BPM minimum/maximum sets the range in tempo graphing; defaults to # 0.0 to 127.0. Decrease it for a magnified view of tempo. [user-midi-settings] convert-to-smf-1 = true beats-per-bar = 4 beats-per-minute = 120 beat-width = 4 buss-override = -1 velocity-override = -1 bpm-precision = 0 bpm-step-increment = 1 bpm-page-increment = 10 bpm-minimum = 2 bpm-maximum = 600 # [user-options] # # These settings specify some -o or --option switch values. 'daemonize' in # seq66cli indicates that it should run as a service. 'log' specifies a log- # file redirecting output from standard output/error. If no path in the name, # the log is stored in the configuration directory. For no log-file, use # "none" or "". On the command line: '-o log=filename.log'. [user-options] daemonize = false log = "/home/user/.config/seq66/seq66.log" pdf-viewer = "/usr/bin/zathura" browser = "/usr/bin/google-chrome" # [user-ui-tweaks] # # key-height specifies the initial height (before vertical zoom) of pattern # editor keys. Defaults to 10 pixels, ranges from 6 to 32. # # key-view specifies the default for showing labels for each key: # 'octave-letters' (default), 'even_letters', 'all-letters', # 'even-numbers', and 'all-numbers'. # # note-resume causes notes-in-progress to resume when the pattern toggles on. # # If specified, a style-sheet (e.g. 'qseq66.qss') is applied at startup. # Normally just a base-name, it can contain a file-path to provide a style # usable in many other applications. # # A fingerprint is a condensation of note events in a long track, to reduce # the time drawing the pattern in the buttons. Ranges from 32 (default) to # 128. 0 = don't use a fingerprint. # # progress-box-width and -height settings change the scaled size of the # progress box in the live-grid buttons. Width ranges from 0.50 to 1.0, and # the height from 0.10 to 1.0. If either is 'default', defaults (0.8 x 0.3) # are used. progress-box-shown controls if the boxes are shown at all. # # progress-box width and height settings change the scaled size of the # progress box in the live-loop grid buttons. Width ranges from 0.50 # to 1.0; the height from 0.10 to 0.50. If either is 0, then the box # isn't drawn. If either is 'default', defaults are used. # # progress-note-min and progress-note-max set the progress-box note # range so that notes aren't centered in the # box, but shown at their position by pitch. # # lock-main-window prevents the accidental change of size of the main # window. [user-ui-tweaks] key-height = 10 key-view = octave-letters note-resume = false fingerprint-size = 32 progress-box-width = 0.8 progress-box-height = 0.3 progress-box-shown = true progress-box-show-cc = true progress-note-min = 0 progress-note-max = 127 lock-main-window = false # [user-session] # # The session manager to use, if any. 'session' is 'none' (default), 'nsm' # (Non/New Session Manager), or 'jack'. 'url' can be set to the value set by # nsmd when run by command-line. Set 'url' if running nsmd stand-alone; use # the --osc-port number. Seq66 detects if started in NSM. The visibility flag # is used only by NSM to restore visibility. [user-session] session = none url = "" visibility = true # [pattern-editor] # # 'escape-pattern' allows the Esc key to close a pattern editor if not # playing or in paint mode. (Esc exits paint mode or stops playback.) # # 'apply-to-new-only' makes the next options work only when opening a new # pattern. A new pattern is 'Untitled' and has no events. # # These settings for play/record for a pattern editor save time in live # recording. Valid record-style values: 'merge' or 'overdub', 'overwrite', # 'expand', and 'one-shot'. 'wrap-around' allows recorded notes to wrap # to the pattern beginning. Currently 'notemap' and quantizing are # are mutually exclusive. [pattern-editor] escape-pattern = false apply-to-new-only = false armed = false thru = false record = false tighten = false qrecord = false notemap = false record-style = merge wrap-around = false # End of /home/user/.config/seq66/qseq66.usr # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/linux/yoshimi-b4uacuse-gm.state ================================================ Session /home/ahlstrom/.config/yoshimi/presets/ /home/ahlstrom/Home/ca/mls/git/yoshimi/presets /home/ahlstrom/Home/ca/mls/git/yoshimi/presets default default default default 12tET Equal Temperament 12 notes per octave Clean Guitar1 Electric Guitar Electric bass 1 Atte Jensen Natural Drum Kit from DS 2 (c) Dario Straulino and Chris Ahlstrom License: GPLv3 Version 2012-06-16 to 2014-04-05. CA removed the kicks and toms; not "real" enough. Added toms from original kit, lowered an octave, with an octave range of keys alloted. Added fill-in instruments to conform to some of GM drums range. Snare - Stick + Snares Snare-Head+Resonance HiHat closed 2 HiHat closed long 1 HiHat open 1 Crash Cymbal 3 Side Stick Tom Bass Drum 2 Acoustic Bass Drum Low Floor Tom High Floor Tom Low Tom Low-Mid Tom Hi-Mid Tom Synth Brazz 1 Synth Brazz 1 Synth Piano 3 fat Nasca Octavian Paul Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound Simple Sound ================================================ FILE: data/midi/Carpet_of_the_Sun.text ================================================ Deconstructing and Reconstructing "Carpet of the Sun" with Seq66 Chris Ahlstrom 2021-05-21 to 2021-05-21 "Carpet of the Sun" is a great tune from Renaissance that we tried to transcribe to MIDI by ear a long time ago. I've been told that the transcription is out-of-key, unfortunately. I still like it and want to convert it from scoring for the Yamaha PSS-790 to General MIDI. The original file, carptsun.mid, had the following setups: Track 0: Ch 1. Time Sig and Tempo. Track 1: Ch 3. Melody, notes only, no patching or control events. Needs an instrument with long sustain during the note. Track 2: Ch 8. "Guitar" chords, full-measure and half-measure long. Track 3: Ch 8. A feeble attempt to imitate guitar strumming. Annoying now. Needs a slower decay. Track 4: Ch 5. Chord accompaniment. Track 5: Ch 1. Piano accompaniment and flavor. Track 6: Ch 1. Improvisational Piano accompaniment and flavor. Track 7: Ch 6. Woodwind, oboe. Flavor for the melody. Track 8: Ch 3. Bass Cello or bass guitar. Track 9: Ch 1. Another bass line. Track 10: Ch 6. Strings. Track 11: Ch 1. Harpsichord. Track 12: Ch 2. Conga Low, note 91. Weird, next track's title is appended to this track's title in the Event Editor. BUG in Seq66 or in EditTrack? Track 13: Ch 2. Conga High, note 89. Same BUG. Track 14: Ch 2. Bass Drum, note 36. Same BUG. Track 15: Ch 2. Snare Drum 2, note 43. Same BUG. Track 16: Ch 2. Crash Cymbal 1, note 53. Same BUG. Track 17: Ch 2. Closed Hi Hat 1, note 47. Same BUG. Track 18: Ch 5 and 6. Ch 5 --> Program 10, Control 64 at 127, then 0. Ch 6 --> Program 7. Track 19: Chs 1, 2, & 3. Various program changes and some control 64s. Track 20: Ch. 7. "My" track, some extra flair, a few sour notes. Track 21: Ch. 2. A copy of the "Melody" track. Track 22: Ch. 2. Snare Drum 1, note 41. Translated drums: midicvtpp failed to translate them! And the Conga values are weird. Track 12: Ch 2. Conga Low, note 91 - 27 --> 64 Track 13: Ch 2. Conga High, note 89 - 26 --> 63 Track 14: Ch 2. Bass Drum, note 36 + 0 --> 36 Track 15: Ch 2. Snare Drum 2, note 43 - 3 --> 40 Track 16: Ch 2. Crash Cymbal 1, note 53 - 4 --> 49 Track 17: Ch 2. Closed Hi Hat 1, note 47 - 5 --> 42 Track 22: Ch. 2. Snare Drum 1, note 41 - 3 --> 38 # vim: sw=4 ts=4 wm=2 et ft=markdown ================================================ FILE: data/midi/EE-qsynth-presets.conf ================================================ # Add this to ~/.config/rncbc.org/Qsynth.conf [Preset] EuropeEndless\Chan1=0:0:7 EuropeEndless\Chan10=9:128:25 EuropeEndless\Chan11=10:0:10 EuropeEndless\Chan12=11:0:11 EuropeEndless\Chan13=12:0:12 EuropeEndless\Chan14=13:0:13 EuropeEndless\Chan15=14:0:14 EuropeEndless\Chan16=15:0:15 EuropeEndless\Chan2=1:0:50 EuropeEndless\Chan3=2:0:52 EuropeEndless\Chan4=3:0:26 EuropeEndless\Chan5=4:0:73 EuropeEndless\Chan6=5:0:54 EuropeEndless\Chan7=6:0:53 EuropeEndless\Chan8=7:0:7 EuropeEndless\Chan9=8:0:8 ================================================ FILE: data/midi/FM/README ================================================ Data/Midi/FM Directory for Seq66 0.99.17 Chris Ahlstrom 2024-12-27 to 2024-12-27 This directory contains additional MIDI files. Below we collect a few notes of interest about some of them. judyblue.mid This file has a lot of zero-length notes. When loaded into Seq66, these are corrected (and a list is shown in debugging in a console window). Also, all of the note-offs are represented by note-ons with velocity 0. Cannot remember whether we used EditTrack or Cakewalk (for DOS or for Windows) to key this one in. # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: data/midi/Kraftwerk-Europe_Endless.asc ================================================ MThd 1 19 192 MTrk 0 SeqNr 0 0 Meta SeqName "Europe Endless by Kraftwerk" 0 PrCh ch=2 p=50 0 TimeSig 4/4 24 8 0 PrCh ch=7 p=91 0 PrCh ch=6 p=87 0 PrCh ch=5 p=65 0 PrCh ch=4 p=39 0 PrCh ch=3 p=52 0 PrCh ch=1 p=6 0 Tempo 545455 0 KeySig 0 major 0 Par ch=1 c=7 v=120 0 Par ch=2 c=7 v=100 0 Par ch=3 c=7 v=100 0 Par ch=5 c=7 v=100 0 Par ch=6 c=7 v=110 0 Par ch=8 c=7 v=120 0 Par ch=4 c=7 v=100 0 SeqSpec 24 24 00 20 00 00 00 00 00 00 02 ff 00 00 03 00 00 0 SeqSpec 24 24 00 01 05 0 SeqSpec 24 24 00 06 04 04 0 SeqSpec 24 24 00 02 80 0 SeqSpec 24 24 00 14 01 768 Meta TrkEnd TrkEnd MTrk 0 SeqNr 1 0 Meta TrkName "Arp 1" 0 On ch=1 n=62 v=64 0 On ch=1 n=67 v=64 30 Off ch=1 n=62 v=64 30 Off ch=1 n=67 v=0 48 On ch=1 n=67 v=50 78 Off ch=1 n=67 v=0 96 On ch=1 n=62 v=52 96 On ch=1 n=71 v=54 126 Off ch=1 n=62 v=0 126 Off ch=1 n=71 v=0 144 On ch=1 n=67 v=33 174 Off ch=1 n=67 v=0 192 On ch=1 n=67 v=50 192 On ch=1 n=71 v=54 222 Off ch=1 n=67 v=0 222 Off ch=1 n=71 v=0 240 On ch=1 n=62 v=34 270 Off ch=1 n=62 v=0 288 On ch=1 n=67 v=35 288 On ch=1 n=67 v=56 318 Off ch=1 n=67 v=0 318 Off ch=1 n=67 v=0 336 On ch=1 n=71 v=35 366 Off ch=1 n=71 v=0 384 On ch=1 n=62 v=36 384 On ch=1 n=67 v=58 414 Off ch=1 n=62 v=0 414 Off ch=1 n=67 v=0 432 On ch=1 n=67 v=37 462 Off ch=1 n=67 v=0 480 On ch=1 n=62 v=60 480 On ch=1 n=71 v=37 510 Off ch=1 n=62 v=0 510 Off ch=1 n=71 v=0 528 On ch=1 n=67 v=38 558 Off ch=1 n=67 v=0 576 On ch=1 n=67 v=39 576 On ch=1 n=71 v=62 606 Off ch=1 n=67 v=0 606 Off ch=1 n=71 v=0 624 On ch=1 n=62 v=39 654 Off ch=1 n=62 v=0 672 On ch=1 n=67 v=64 672 On ch=1 n=67 v=30 702 Off ch=1 n=67 v=0 702 Off ch=1 n=67 v=0 720 On ch=1 n=71 v=40 750 Off ch=1 n=71 v=0 750 SeqSpec 24 24 00 20 00 00 03 00 00 00 05 ff 00 00 03 00 00 00 01 08 00 00 01 1f ff 00 00 03 00 00 00 01 20 00 00 01 4f ff 00 00 03 00 44 00 01 50 00 00 01 c7 ff 00 00 03 00 3f 00 01 c8 00 00 02 0f ff 00 00 03 00 44 00 02 10 00 00 02 3f ff 00 00 03 00 00 00 02 40 00 00 02 6f ff 00 00 03 00 44 00 02 70 00 00 03 05 ff 00 00 03 00 3f 750 SeqSpec 24 24 00 01 05 750 SeqSpec 24 24 00 06 04 04 750 SeqSpec 24 24 00 02 00 750 SeqSpec 24 24 00 14 01 750 SeqSpec 24 24 00 1b 02 768 Meta TrkEnd TrkEnd MTrk 0 SeqNr 2 0 Meta TrkName "Arp 2" 0 On ch=1 n=67 v=83 0 On ch=1 n=74 v=39 30 Off ch=1 n=67 v=0 30 Off ch=1 n=74 v=0 48 On ch=1 n=79 v=52 78 Off ch=1 n=79 v=0 96 On ch=1 n=74 v=85 96 On ch=1 n=83 v=39 126 Off ch=1 n=74 v=0 126 Off ch=1 n=83 v=0 144 On ch=1 n=67 v=54 174 Off ch=1 n=67 v=0 192 On ch=1 n=79 v=40 192 On ch=1 n=83 v=87 222 Off ch=1 n=79 v=0 222 Off ch=1 n=83 v=0 240 On ch=1 n=74 v=55 270 Off ch=1 n=74 v=0 288 On ch=1 n=67 v=41 288 On ch=1 n=79 v=89 318 Off ch=1 n=67 v=0 318 Off ch=1 n=79 v=0 336 On ch=1 n=83 v=56 366 Off ch=1 n=83 v=0 384 On ch=1 n=67 v=91 384 On ch=1 n=74 v=42 414 Off ch=1 n=67 v=0 414 Off ch=1 n=74 v=0 432 On ch=1 n=79 v=57 462 Off ch=1 n=79 v=0 480 On ch=1 n=74 v=93 480 On ch=1 n=83 v=43 510 Off ch=1 n=74 v=0 510 Off ch=1 n=83 v=0 528 On ch=1 n=67 v=59 558 Off ch=1 n=67 v=0 576 On ch=1 n=79 v=44 576 On ch=1 n=83 v=95 606 Off ch=1 n=79 v=0 606 Off ch=1 n=83 v=0 624 On ch=1 n=74 v=60 654 Off ch=1 n=74 v=0 672 On ch=1 n=67 v=45 672 On ch=1 n=79 v=97 702 Off ch=1 n=67 v=0 702 Off ch=1 n=79 v=0 720 On ch=1 n=83 v=61 750 Off ch=1 n=83 v=0 750 SeqSpec 24 24 00 20 00 00 06 00 00 00 0b ff 00 00 03 00 00 750 SeqSpec 24 24 00 01 05 750 SeqSpec 24 24 00 06 04 04 750 SeqSpec 24 24 00 02 00 750 SeqSpec 24 24 00 14 01 750 SeqSpec 24 24 00 1b 02 768 Meta TrkEnd TrkEnd MTrk 0 SeqNr 3 0 Meta TrkName "Arp 3" 0 On ch=1 n=67 v=100 0 On ch=1 n=74 v=47 30 Off ch=1 n=67 v=0 30 Off ch=1 n=74 v=0 48 On ch=1 n=79 v=63 78 Off ch=1 n=79 v=0 96 On ch=1 n=62 v=100 96 On ch=1 n=83 v=47 126 Off ch=1 n=62 v=0 126 Off ch=1 n=83 v=0 144 On ch=1 n=67 v=63 174 Off ch=1 n=67 v=0 192 On ch=1 n=71 v=100 192 On ch=1 n=79 v=47 222 Off ch=1 n=71 v=0 222 Off ch=1 n=79 v=0 240 On ch=1 n=62 v=63 270 Off ch=1 n=62 v=0 288 On ch=1 n=67 v=100 288 On ch=1 n=67 v=47 318 Off ch=1 n=67 v=0 318 Off ch=1 n=67 v=0 336 On ch=1 n=71 v=63 366 Off ch=1 n=71 v=0 384 On ch=1 n=62 v=47 384 On ch=1 n=67 v=100 414 Off ch=1 n=62 v=0 414 Off ch=1 n=67 v=0 432 On ch=1 n=67 v=63 462 Off ch=1 n=67 v=0 480 On ch=1 n=71 v=47 480 On ch=1 n=74 v=100 510 Off ch=1 n=71 v=0 510 Off ch=1 n=74 v=0 528 On ch=1 n=67 v=63 558 Off ch=1 n=67 v=0 576 On ch=1 n=67 v=47 576 On ch=1 n=83 v=100 606 Off ch=1 n=67 v=0 606 Off ch=1 n=83 v=0 624 On ch=1 n=74 v=63 654 Off ch=1 n=74 v=0 672 On ch=1 n=67 v=47 672 On ch=1 n=79 v=100 702 Off ch=1 n=67 v=0 702 Off ch=1 n=79 v=0 720 On ch=1 n=83 v=63 750 Off ch=1 n=83 v=0 750 SeqSpec 24 24 00 20 00 00 0c 00 00 00 23 ff 00 00 03 00 00 00 00 24 00 00 00 35 ff 00 00 03 00 4c 00 00 36 00 00 00 53 ff 00 00 03 00 00 00 00 54 00 00 00 5f ff 00 00 03 00 34 00 00 60 00 00 00 bf ff 00 00 03 00 00 00 00 c0 00 00 00 ef ff 00 00 03 00 44 00 00 f0 00 00 01 07 ff 00 00 03 00 00 750 SeqSpec 24 24 00 01 05 750 SeqSpec 24 24 00 06 04 04 750 SeqSpec 24 24 00 02 00 750 SeqSpec 24 24 00 14 01 750 SeqSpec 24 24 00 1b 02 768 Meta TrkEnd TrkEnd MTrk 0 SeqNr 4 0 Meta TrkName "Drums 1" 384 On ch=10 n=33 v=55 384 On ch=10 n=40 v=100 384 On ch=10 n=46 v=55 384 On ch=10 n=53 v=100 408 Off ch=10 n=33 v=0 408 Off ch=10 n=40 v=0 408 Off ch=10 n=53 v=0 576 On ch=10 n=40 v=100 576 Off ch=10 n=46 v=0 600 Off ch=10 n=40 v=0 624 On ch=10 n=40 v=100 648 Off ch=10 n=40 v=0 672 On ch=10 n=40 v=100 696 Off ch=10 n=40 v=0 696 SeqSpec 24 24 00 20 00 00 8d 00 00 00 8f ff 00 00 03 00 00 696 SeqSpec 24 24 00 01 05 696 SeqSpec 24 24 00 06 04 04 696 SeqSpec 24 24 00 02 09 696 SeqSpec 24 24 00 14 00 696 SeqSpec 24 24 00 1b 11 768 Meta TrkEnd TrkEnd MTrk 0 SeqNr 5 0 Meta TrkName "Drums 2" 0 On ch=10 n=33 v=115 24 Off ch=10 n=33 v=0 96 On ch=10 n=46 v=87 192 On ch=10 n=40 v=116 192 Off ch=10 n=46 v=0 216 Off ch=10 n=40 v=0 288 On ch=10 n=46 v=94 384 On ch=10 n=33 v=113 384 Off ch=10 n=46 v=0 408 Off ch=10 n=33 v=0 480 On ch=10 n=46 v=95 576 On ch=10 n=40 v=109 576 Off ch=10 n=46 v=0 600 Off ch=10 n=40 v=0 672 On ch=10 n=46 v=77 768 Off ch=10 n=46 v=0 768 SeqSpec 24 24 00 20 00 00 90 00 00 00 bf ff 00 00 03 00 00 00 00 c0 00 00 01 07 ff 00 00 03 00 00 00 01 08 00 00 01 4f ff 00 00 03 00 00 00 01 50 00 00 01 c7 ff 00 00 03 00 00 00 01 c8 00 00 02 0f ff 00 00 03 00 00 00 02 10 00 00 02 3f ff 00 00 03 00 00 00 02 40 00 00 02 6f ff 00 00 03 00 00 00 02 70 00 00 03 05 ff 00 00 03 00 00 768 SeqSpec 24 24 00 01 05 768 SeqSpec 24 24 00 06 04 04 768 SeqSpec 24 24 00 02 09 768 SeqSpec 24 24 00 14 00 768 SeqSpec 24 24 00 1b 11 768 Meta TrkEnd TrkEnd MTrk 0 SeqNr 6 0 Meta TrkName "Bass 1" 672 On ch=4 n=43 v=106 710 Off ch=4 n=43 v=0 720 On ch=4 n=43 v=106 760 Off ch=4 n=43 v=0 760 SeqSpec 24 24 00 20 00 00 75 00 00 00 77 ff 00 00 03 00 00 760 SeqSpec 24 24 00 01 05 760 SeqSpec 24 24 00 06 04 04 760 SeqSpec 24 24 00 02 03 760 SeqSpec 24 24 00 14 01 760 SeqSpec 24 24 00 1b 04 768 Meta TrkEnd TrkEnd MTrk 0 SeqNr 7 0 Meta TrkName "Bass 2" 0 On ch=4 n=31 v=106 48 Off ch=4 n=31 v=0 96 On ch=4 n=43 v=106 144 Off ch=4 n=43 v=0 192 On ch=4 n=31 v=106 240 Off ch=4 n=31 v=0 288 On ch=4 n=43 v=106 336 Off ch=4 n=43 v=0 384 On ch=4 n=31 v=106 432 Off ch=4 n=31 v=0 480 On ch=4 n=43 v=106 528 Off ch=4 n=43 v=0 576 On ch=4 n=31 v=106 624 Off ch=4 n=31 v=0 672 On ch=4 n=43 v=106 708 Off ch=4 n=43 v=0 720 On ch=4 n=43 v=106 758 Off ch=4 n=43 v=0 758 SeqSpec 24 24 00 20 00 00 78 00 00 00 bf ff 00 00 03 00 00 00 00 c0 00 00 00 ef ff 00 00 03 00 44 00 00 f0 00 00 01 1f ff 00 00 03 00 00 00 01 20 00 00 01 4f ff 00 00 03 00 44 00 01 50 00 00 01 c7 ff 00 00 03 00 3f 00 01 c8 00 00 02 0f ff 00 00 03 00 44 00 02 10 00 00 02 3f ff 00 00 03 00 00 00 02 40 00 00 02 6f ff 00 00 03 00 44 00 02 70 00 00 03 05 ff 00 00 03 00 3f 758 SeqSpec 24 24 00 01 05 758 SeqSpec 24 24 00 06 04 04 758 SeqSpec 24 24 00 02 03 758 SeqSpec 24 24 00 14 01 758 SeqSpec 24 24 00 1b 04 768 Meta TrkEnd TrkEnd MTrk 0 SeqNr 8 0 Meta TrkName "Voice 1 \"EE\"" 672 On ch=6 n=62 v=100 768 On ch=6 n=67 v=100 768 Off ch=6 n=62 v=0 944 Off ch=6 n=67 v=0 960 On ch=6 n=67 v=100 1131 Off ch=6 n=67 v=0 1537 On ch=6 n=64 v=100 1713 Off ch=6 n=64 v=0 1729 On ch=6 n=62 v=100 1900 Off ch=6 n=62 v=0 1900 SeqSpec 24 24 00 20 00 00 a5 00 00 00 ad ff 00 00 03 00 00 00 00 b1 00 00 00 b9 ff 00 00 06 00 00 00 01 05 00 00 01 0d ff 00 00 09 00 00 00 01 11 00 00 01 19 ff 00 00 03 00 00 00 02 25 00 00 02 2d ff 00 00 09 00 00 00 02 31 00 00 02 39 ff 00 00 03 00 00 1900 SeqSpec 24 24 00 01 05 1900 SeqSpec 24 24 00 06 04 04 1900 SeqSpec 24 24 00 02 05 1900 SeqSpec 24 24 00 14 01 1900 SeqSpec 24 24 00 1b 18 2304 Meta TrkEnd TrkEnd MTrk 0 SeqNr 9 0 Meta TrkName "Voice 2" 0 On ch=6 n=66 v=100 174 Off ch=6 n=66 v=0 192 On ch=6 n=66 v=100 267 Off ch=6 n=66 v=0 288 On ch=6 n=66 v=100 603 Off ch=6 n=66 v=0 672 On ch=6 n=66 v=100 768 On ch=6 n=68 v=100 768 Off ch=6 n=66 v=0 960 On ch=6 n=66 v=100 960 Off ch=6 n=68 v=0 1046 Off ch=6 n=66 v=0 1056 On ch=6 n=66 v=100 1296 Off ch=6 n=66 v=0 1296 SeqSpec 24 24 00 20 00 00 d8 00 00 00 dd ff 00 00 06 00 00 00 00 e4 00 00 00 e9 ff 00 00 06 00 00 00 01 38 00 00 01 3d ff 00 00 06 00 00 00 01 44 00 00 01 49 ff 00 00 06 00 00 00 01 e0 00 00 01 e5 ff 00 00 06 00 00 00 01 ec 00 00 01 f1 ff 00 00 06 00 00 00 01 f8 00 00 01 fd ff 00 00 06 00 00 00 02 04 00 00 02 09 ff 00 00 06 00 00 00 02 58 00 00 02 5d ff 00 00 06 00 00 00 02 64 00 00 02 69 ff 00 00 06 00 1296 Off ch=6 n=0 v=255 1423 Off ch=6 n=5 v=36 1459 Off ch=6 n=0 v=1 1464 Off ch=6 n=0 v=255 1591 Off ch=6 n=6 v=36 1627 Off ch=6 n=0 v=6 1631 Off ch=6 n=4 v=0 18014 Off ch=6 n=5 v=36 18050 Off ch=6 n=0 v=2 18055 Off ch=6 n=0 v=255 18182 Off ch=6 n=5 v=36 18218 Off ch=6 n=0 v=20 18219 Off ch=6 n=0 v=255 18346 Off ch=6 n=5 v=36 18382 Off ch=6 n=0 v=27 18406 Off ch=2 n=112 v=255 18453 Off ch=2 n=0 v=77 TrkEnd MTrk ================================================ FILE: data/midi/Kraftwerk-Europe_Endless.text ================================================ Deconstructing and Reconstructing "Europe Endless" with Seq66 Chris Ahlstrom 2021-04-01 to 2021-07-27 Kraftwerk's "Europe Endless" is a long, repetitive tune. So repetitive that the vinyl recording of it had a visible pattern in the grooves. And it seemed like a good test of using the pattern editing and pattern triggers of Seq66. And, man, did it uncover a lot of bugs in Seq66. Also uncovered the need for a trigger-transposition feature. First, we found an existing file. Sites for a MIDI file of Kraftwerk's "Europe Endless": https://bhs.minor9.com/midi/assortment/G-Q/Kraftwerk%20-%20Europe%20Endless.mid https://midisfree.com/download/kraftwerk-europe-endless-mid/ The file is fairly large: Kraftwerk - Europe Endless.mid (100384 bytes) A dump of this file using our "midicvt" application showed the following information: $ midicvt Kraftwerk....mid Kraftwerk....asc Copyright "Copyright \xa9 1999 by Paul Williamson email pw@dove.net.au" TimeSig 4/4 24 8 Some of the tracks were 258 measures long. Very bad display of the progress bar, they revealed a bug in playback... we forgot to convert milliseconds to microseconds! Any, onward. Time to deconstruct the file. The chords and melodies turned out to be base patterns that were transposed from time to time. Chords: 0 1 2 3 4 5 6 7 G = G B D B = B D# F# (G + 4, G - 8) F# = F# A# C# (G + 11, G - 1) We added the various patterns as noted below, and added provisional program changes and volume settings using the Seq66 "Events" tab. In this deconstruction/reconstruction, we do not worry about exactly duplicating sections that are very minor variations on a theme. Some additional Volume events could be added, but they might be synth/patch-dependent. The reproduction is probably about 97% faithful to the original (for example, we didn't add the fade-out at the end), and, at about 12000 bytes, is about 12% of the size of the original. We can also take this Seq66 tune as laid out in the song editor, unmute all the patterns, and export it as a reconstituted MIDI file, playable in any sequencer. In this form, it is back up to about 93000 bytes in size. The following command will convert the exported version to MP3: $ timidity K.midi -Ow -o - | ffmpeg -i - -acodec libmp3lame -ab 128k K.mp3 = = where "K" is "Kraftwek-Europe-Endless-exported" (see the contrib/midi directory of this project). At 128k, it ends up about 9 Mb in size, and so we don't include it with this project. Program and Controller Changes: Track 0: None Track 1: Ch1 Part 6 GM Chorused Piano Vol 127 Track 2: Ch2 Part 35 GM Bass Vol 1 to 100 Track 3: Ch3 Part 52 GM Synth Strings Vol 100 Track 4: Ch4 Part 39 GM Synth Bass Vol 127 Track 5: Ch5 Part 65 GM Sax Vol 100 Track 6: Ch6 Part 87 GM Synth Lead Vol 110 Track 7: Ch7 Part 91 GM Synth Pad Vol 70 Track 8: Ch10 Part 25 GM Unknown Vol 127 These have been set for the default Qsynth setup. However, that application would crackle a bit, and we found Timidity worked better, at least in ALSA. However, Timidity also has a lag in playback, noticeable when using MIDI Thru to play from a MIDI controller. Parts of the tune and their breakdown: Track 0: MIDI port events, Time Sig, Key Sig, and Tempo, Volume, Program Changes Track 1 (Background Arpeggiation): Ch 1 Arp 1 - 0: 1 M @ 2 M 2 (1) D4 to B4 1 oct Arp 2 - 0: 1 M @ 3/4 M 3-4 (2) D4 to B5 1.5 oct Arp 3 - 0: 1 M @ 5 M 5-12 (8) D4 to B5 2 oct Arp 3 + 12: 1 M @ 13/14 M 13-18 (6) D5 to B6 2 oct Arp 3 - 0: 1 M M 19-28 (10) D4 to B5 2 oct Arp 3 - 12: 1 M M 29-32 (4) D3 to B4 2 oct Arp 3 - 0: 1 M M 33-64 (32) D4 to B5 2 oct Arp 3 + 4: 1 M M 65-80 (D becomes F#) Arp 3 - 0: 1 M M 81-88 Arp 1 - 0: 1 M M 89-96 Arp 1 + 4: 1 M M 97-112 (G becomes B) Arp 1 + 0:, 1 M M 113-152 Arp 1 + 4: 1 M M 153-76 (G becomes B) Arp 1 + 0: 1 M M 177-192 Arp 1 + 4: 1 M M 193-208 (G becomes B) Arp 1 + 0: 1 M M 209-258 (END) Track 2 (Strings): Ch 2 Note 1: G2 at M 11 + 1/8, for 1M Note 2: G2 at M 33 + 1/2, for 8M Track 3 (Chorus): Ch 3 Chorus 1 - 0: 8 M @ 33 M 33-40 (8) D4 to C5 Chorus 1 - 0: M 41-48 Chorus 1 - 8: M 65-72 F#3 (minus 8) Chorus 1 - 8: M 97-104 F#3 Chorus 1 - 8: M 153-160 F#3 Chorus 1 - 8: M 193-200 F#3 Track 4 (Simple Bass): Ch 4 Bass 1 - 0: 1 M @ 40 M 40 only; Two-note intro Bass 2 - 0: 1 M @ 41 M 41-64 (24) Simple pattern, G1 and G2 Bass 2 + 4: M 65-80 (16) B1 and B2 (can we improve?) Bass 2 - 0: M 81-96 (16) G1 and G2 Bass 2 + 4: M 97-112 (16) B1 and B2 Bass 2 - 1: M 113-152 (16) F#1 and F#2 Bass 2 + 4: M 153-176 (16) B1 and B2 Bass 2 - 0: M 177-192 (16) G1 and G2 Bass 2 + 4: M 193-208 (16) B1 and B2 Bass 2 - 1: M 209-258 (16) F#1 and F#2 Track 5 (Melody): Ch 5 Melody 1 - 0: 4 M @ 49 M 49-52 - 53-56 Descending melody D4 Melody 1 - 0: M 53-56 Melody 1 - 0: M 81-84 Melody 1 - 0: M 85-88 Melody 2 - 0: 40 M @ 113 M 113-152 Melody A Melody 3 - 0: 4 M @ 177 M 177-180 Melody B Melody 3 - 0: M 181-184 Melody B Melody 2 - 0: M 209-248 Melody A Track 6 (Voice): Ch 6 Voice 1 - 0: 3 M @ 56 M 56-58 "Europe ... Endless" D4 Voice 1 - 0: M 60-62 Voice 2 - 0: 2 M @ 73 M 73-74 "...post-card views" F#4 Voice 2 - 0: M 77-78 Voice 1 - 0: M 88-90 Voice 1 - 0: M 92-94 Voice 2 - 0: M 105-106 Voice 2 - 0: M 109-110 Voice 2 - 0: M 161-162 Voice 2 - 0: M 165-166 Voice 2 - 0: M 169-170 Voice 2 - 0: M 173-174 Voice 1 - 0: M 184-186 Voice 1 - 0: M 188-190 Voice 2 - 0: M 201-202 Voice 2 - 0: M 205-206 Track 7 (Endless): Ch 7 Endless 1 - 0: 2 M @ 59 M 59-60 G4 "Endless" 4 times Endless 1 - 0: M 63-64 Endless 2 - 0: 2 M @ 74 M 74-75 "It's Europe endless" B3 Endless 2 - 0: M 78-79 Endless 1 - 0: M 91-92 Endless 1 - 0: M 95-96 Endless 2 - 0: M 106-107 Endless 2 - 0: M 110-111 Endless 3 - 0: 40 M @ 113 M 113-152 Chords at F#3 Endless 2 - 0: M 162-163 Endless 2 - 0: M 166-167 Endless 2 - 0: M 170-171 Endless 2 - 0: M 174-175 Endless 1 - 0: M 187-188 Endless 1 - 0: M 191-192 Endless 2 - 0: M 202-203 Endless 2 - 0: M 206-207 Endless 4 - 0: 50 M @ 209 M 209-258 Repetitive Chords at F#3 As an exercise, one could further break Endless 4 into short patterns and lay them out in the song editor. Track 8 (Drums): Ch 10 Drums 1 - 1 M @ 48 M 48 only; Intro part, plays once Drums 2 - 1 M @ 49 M 49-258 Plays for the rest of the song We could insert the introduction in one or two other places, perhaps. Drums 2 layed out in multiple triggers for convenience/flexibility. Also of interest: https://www.youtube.com/watch?v=xXnDMCRT7uU How to Play Kraftwerk Europe Endless # vim: sw=4 ts=4 wm=2 et ft=markdown ================================================ FILE: data/midi/Peter_Gunn.text ================================================ Deconstructing and Reconstructing "Peter Gun" with Seq66 Chris Ahlstrom 2021-04-14 to 2021-04-16 Since working with "Europe Endless" was so fruitful for uncovering bugs and suggesting new features, we thought we'd try again with a shorter tune. This is a basic, simply orchestrated version of the theme from "Peter Gunn". Track 0 (Tempo): Key Signature, MIDI Port (!), Time Signature, Tempo Track 1 (Trumpet): Ch. 5; Program 5; Vol 126 Trumpet 1: 8 M @ M 7 Trumpet 2: 21 M @ M 22 Trumpet 3: 8 M @ M 43 Trumpet 4: 6 M @ M 58 Trumpet 1 - 0: M 7-14 Trumpet 1 + 12: M 15-21 Trumpet 2 - 0: M 22-42 Trumpet 3 - 0: M 43-50 Trumpet 3 - 0: M 51-58 Trumpet 4 - 0: M 58-63 Track 2 (Brass): Ch. 3; Program 62; Vol 94 Better to just play the whole track as is. Brass: 57 M at 7-63, but use whole track Track 3 (Bass): Ch. 2; Program 38; Vol unspecified Bass 1: 1 M @ M 5 Bass 2: 4 M @ M 23 Bass 3: 3 M @ M 61 Bass 1 - 0: M 5-22 Bass 2 - 0: M 23-26 Bass 1 - 0: M 27-60 Bass 3 - 0: M 61-63 Track 4 (Percussion): Ch. 10; Program 0; Vol 126 Perc 1 (standard beat): 1 M @ M 1 Perc 2 (hammer bridge): 1 M @ M 6 Perc 3 (finale): 3 M @ M 61 Perc 1: M 1 - 5 Perc 2: M 6 Perc 1: M 7 - 13 Perc 2: M 14 Perc 1: M 15 - 25 Perc 2: M 26 Perc 1: M 27 - 33 Perc 2: M 34 Perc 1: M 35 - 41 Perc 2: M 42 Perc 1: M 43 - 49 Perc 2: M 50 Perc 1: M 51 - 60 Perc 3: M 61 Track 5 (Fretless Bass): Ch. 1; Program 35; Vol 126 Fretless 1: 1 M @ 3 M Fretless 2: 4 M @ M 23 Fretless 3: 3 M @ M 61 Fretless 1 - 0: M 3-22 Fretless 2 - 0: M 23-26 Fretless 1 - 0: M 27-60 Fretless 3 - 0: M 61-63 By the way, the best version for us is Henry Mancini's version. # vim: sw=4 ts=4 wm=2 et ft=markdown ================================================ FILE: data/midi/README ================================================ Data/Midi Directory for Seq66 0.99.11 Chris Ahlstrom 2021-04-14 to 2023-11-22 This directory contains sample MIDI files to be installed for instructional purposes and for detecting issues in loading files. 16-blank-patterns.midi This file is useful when using the record-by-channel option. Make a copy of it, load it, and use this option for recording. The record-by-channel option requires that the destination sequences for channels present in the input are already created. This file also include a 17th pattern that is "free"... it does not impose a channel number on the output. b4uacufm.mid b4uacuse-gm-patchless.midi The first file is our original transcription of the Eric Clapton & Robert Cray tune "Before You Accuse Me". The second file is converted to Seq66 format, though each pattern is very long and only one trigger per pattern is used in the song editor. It makes a good way to verify that the basics work. carptsun.midi "The Carpet of the Sun", a beautiful song from Renaissance. This file was converted from an initial effort, done for the nifty consumer keyboard, the Yamaha PSS-790, a couple decades ago, using our midicvtpp project (available on GitHub) and an INI file to convert the drum and patches from PSS-790 to General MIDI: $ midicvtpp --m2m data/samples/GM_PSS-790_Multi.ini \ carptsun.mid data/midi/carptsun.midi We will do some more chopping on this file, eventually, to use short loops where possible and lay them out using the song editor. Chameleon-HHancock-Ov.midi We found this file, and it is a pleasant rendition of a Herbie Hancock classic. colours.midi This file illustrates the usage of colors in patterns. EE-qsynth-presets.conf This sample can be added to the QSynth configuration. However, we have found that Timidity works better out of the box for previewing tunes. If_You_Could_Read_My_Mind.mid Another song found, used in our initial testing of the Windows build. Kraftwerk-Europe_Endless-reconstructed.midi Kraftwerk-Europe_Endless.asc Kraftwerk-Europe_Endless.text This MIDI file is a reconstruction of a MIDI file found on the Web. What we did with it is described in the "text" file. The "asc" is a partial dump (via midicvt) of some of the events in the original file. Peter_Gunn-reconstructed.midi Peter_Gunn.text This MIDI file is another, simpler and short reconstruction, as described in the text file. FM and PSS-790 directories These files support testing the installed play-list functionality. These tunes are old and currently do not support General MIDI. One of these days we will remap them or at least add more appropriate program/patch changes. # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: data/readme.text ================================================ readme.text for Seq66 0.99.24 Chris Ahlstrom 2015-09-10 to 2026-05-01 Release 0.99.24 fixes known issues and issues found by the authors. See the NEWS and RELNOTES files for details. Seq66 is a refactoring of a reboot (sequencer64) of seq24, extending it with new features and bug fixes, and incorporation of Modern C++ (C++11/C++14). It is a "live performance" sequencer, with the musician creating and controlling a number of pattern loops. It also provides some very useful editing, import, and export functions. An extensive manual for this application is found in the "data/share/doc" subdirectory of the installed project directory, and it also can be downloaded via Git: https://github.com/ahlstromcj/seq66.git It is also installed in "/usr/local/share/doc/seq66-0.90" (or a later version number) along with ODF spreadsheets describing the control keys and Launchpad Mini configuration. It covers everything that seq66 can do. Prebuilt packages, Windows installers, and source tarballs are provided as part of the GitHub release. This directory contains files to be installed in the ${prefix}/share/seq66-0.99 directory ("prefix" is /usr/share or /usr/local/share) on Linux, or in the installation ${install}/data directory (e.g. C:/Program Files/Seq66/data) on Windows. Seq66 will create the configuration directory and configuration file the first time, but we also supply some samples. The files include alternate configuration files that the user can copy to $HOME/.config/seq66 (Linux) C:/Users/YourName/AppData/Local/seq66 (Windows) and modify to satisfaction, plus some sample Seq66 MIDI files, and MIDI in/out control for the Novation Launchpad Mini. Please note that the files in the "data/linux" directory can also be used in Windows. They are simply named for the Linux executable, "qseq66", rather than the Windows executable "qpseq66.exe". Port-Mapping: See the PDF user manual for full details. Here, we note that port-mapping is enabled by default. If it is, and devices do not show up, then go to the Edit / Preferences / Clock tab and click the "Make maps" button. Exit and restart and the device should show up. Or just click the "Remap and restart" button if a warning message pops up. This process is usually necessary when adding new MIDI devices to the system setup. Windows support: This version uses a Qt 5 user-interface based on Kepler34, but using the standard Seq66 libraries. The user-interface works well, and Windows built-in MIDI devices are detected, inaccessible devices are ignored, and playback (e.g. to the built-in wavetable synthesizer) work. The Qt 5 GUI removes some of the overly-convoluted features of the Gtkmm 2.4 GUI, while adding a mutes-master and a set-master tab. Many configuration items are supported by the Edit / Preferences dialog, but some manual configuration of the "rc" and "usr" files may be necessary. Also supported are separate "ctrl" (MIDI control, input and output), "mutes" (toggles of patterns of sequences), "drums" (note mappings) and "playlist" files. See the READMEs for more information. See the file C:\Program Files\Seq66\data for readme.windows, which explains some things to watch for with Windows. There should also be an executable installer available with the GitHub release. See the INSTALL file for build-from-source instructions or using a conventional source tarball. This file is part of: https://github.com/ahlstromcj/seq66.git For Windows, additional build instructions are listed in the comments for the nsis/build_release_package.bat file. # vim: sw=4 ts=4 wm=4 et ft=sh fileformat=dos ================================================ FILE: data/readme.windows ================================================ readme.windows for Seq66 0.99.24 Chris Ahlstrom 2018-05-13 to 2026-05-01 This README file tells you how to run the native Windows implmentation of seq66, an executable named qpseq66.exe. It provides some notes on Seq66 and Windows. We mostly use Windows 10; some features might differ in Windows 7 or Windows 2012 Server. Note that Seq66 is a 64-bit application for Windows. This release fixes known issues and adds other work as noted in NEWS and RELNOTES. There is a portable 7z package for qpseq66; an NSIS-based installer is available (read "0.99.8" as the latest version number) as part of the latest GitHub release. An example: https://github.com/ahlstromcj/seq66/releases/download/0.99.8/seq66_setup_x64-0.99.8.exe For full documentation (PDF user manual), also see https://github.com/ahlstromcj/seq66/doc or the install directory. Or use the Help menu to access the tutorial and user manual. Some minor features are missing from the Windows version, such as virtual ports. Sample configuration files are provided. Why the name qpseq66.exe instead of seq66.exe? The "q" stands for the Qt user interface, and the "p" stands for the PortMidi-based engine used for Windows. Once installed, go to "C:/Program Files/Seq66" and create a desktop shortcut for qpseq66.exe, with the following command line (for a default Windows 10 setup having only the MIDI Mapper and the Microsoft wave-table synthesizer installed): C:\Program Files\Seq66\qpseq66.exe (Other files are available in the 'data' directory. For example, the user manual is a PDF file stored in: C:\Program Files\Seq66\data\doc\seq66-user-manual.pdf and sample Seq66 MIDI files are stored in C:\Program Files\Seq66\data\midi for demonstration of various Seq66 features.) Run the 'qpseq66.exe' shortcut and load a tune from the MIDI directory. If there application seems frozen, exit it (which saves the initial set of configuration files), and run it again. If an error appears, click out and run it again, and there should be a "MIDI Mapper" present on buss 0. Load a song, and select the "0" device from the buss dropdown ("None") at the top of the main page. The file should play. You can save the file to preserve the new buss number; songs can be set to play on multiple busses. Note that the configuration files for Seq66 get created at first exit. They reside in C:/Users/myname/AppData/Local/seq66/ Palette files, style sheets, and sample 'ctrl' files can be copied from C:\Program Files\Seq66\data\windows and C:\Program Files\Seq66\data\samples Try the incrypt-66 and perstfic-66 palette and qss (style-sheet) files! They can be set up by editing the 'rc' and 'usr' file, or in UI (followed by a complete restart of Seq66). Inaccessible Devices: When first starting qpseq66 on Windows 10, one might experience some issues. One issue is that the Microsoft MIDI Mapper, rumored to be removed in Windows 8 and beyond, is still detected by the PortMidi library used in qpseq66. Another issue is that the built-in Microsoft wave-table synthesizer is not accessible, because the MIDI Mapper grabs it. So Seq66 detects this situation and disables the wave-table synthesizer, instead using the MIDI Mapper (device 0) to access the synthesizer, and it shows a warning dialog. To make the disabling change permanent, exit Seq66 and restart it. Then, for the basic setup on Windows, only output bus 0 is available. And no input bus is available, so Seq66 ignores that, so that one can at least do MIDI playback. Furthermore... ...We installed the CoolSoft MIDIMapper and VirtualMIDISynth to try to get around these issues, and tried to turn off the system setup of "Allow applications to restrict access to this device." But we still had inaccessible devices, and the resulting errors would cause qpseq66 to abort. So we had to spend a lot of time supporting the disabling of inaccessible ports, and saving and restoring the "rc" setup properly. Here is the latest output on our Windows, generated using the option "-o log=virtualmidi.log": qpseq66 C:/Users/myname/AppData/Local/seq66/virtualmidi.log 2019-09-13 09:09:59 [MIDIMAPPER] 'mapper in : midiInGetDevCaps() error for device 'MIDIMAPPER': 'The specified device identifier is out of range' ' pm_winmm_general_inputs(): no input devices PortMidi MMSystem 0: Microsoft MIDI Mapper output opened PortMidi MMSystem 1: CoolSoft MIDIMapper output closed PortMidi MMSystem 2: Microsoft GS Wavetable Synth output opened PortMidi MMSystem 3: VirtualMIDISynth #1 output closed [Opened MIDI file, 'C:\Users\myname\Documents\Home\seq66\data\b4uacuse-gm-patchless.midi'] [Writing rc configuration C:\Users\myname\AppData\Local\seq66\qpseq66.rc] PortMidi call failed: [-1] 'Bad pointer' PortMidi call failed: [-1] 'Bad pointer' Begin closing open devices... Warning: devices were left open. They have been closed. We still have some minor issues at start up and at exit, but are now able to play a tune on the wavetable synthesizer using the "-b 0" option. Bus (port) 0 accesses the Windows MIDI Mapper. When you first run qpseq66 on Windows, it will create new configuration files: C:\Users\username\AppData\Local\seq66\qpseq66.rc C:\Users\username\AppData\Local\seq66\qpseq66.usr C:\Users\username\AppData\Local\seq66\qpseq66.ctrl C:\Users\username\AppData\Local\seq66\qpseq66.drums C:\Users\username\AppData\Local\seq66\qpseq66.mutes C:\Users\username\AppData\Local\seq66\qpseq66.palette C:\Users\username\AppData\Local\seq66\qpseq66.playlist Inaccessible devices are noted in the "[midi-clock]" or "[midi-clock-map]" sections of "C:\Users\username\AppData\Local\seq66\qpseq66.rc" by a "-1" (disabled) or "-2" (unavailable) value. Similar for the "[midi-input]" or "[midi-input-map]" sections. Configuration Files: On Linux, the normal directory location of the Seq66 configuration files is "/home/username/.config/seq66". Various confignames: qseq66.rc, .usr The RtMidi Native ALSA/JACK version. qpseq66.rc, .usr The PortMidi Qt 5 version. On Windows, the conventional location is different, and the location used is "C:\Users\username\AppData\Local\seq66". The file is: qpseq66.rc The PortMidi Qt 5 version for Windows. qpseq66.usr Ditto. To access AppData, highlight the username directory, then append "AppData" to the end of "C:\Users\username". It is a Windows thang. Building the Application: To build, see the instructions in the "nsis\build_release_package.bat" file. Basically, install Qt Creator to use the mingw compiler, and double-click on that batch file and let it do its thing. After a build, one can change to the shadow-build directory "seq66-release" and run Seq66qt5\release\qpseq66.exe If it does not come up after a few seconds (Windows is *slow*), then run: windeployqt Seq66qt5\release and try again. Immedidately quit the application, go to your "AppData" directory and verify the presence of: qpseq66.rc qpseq66.usr qpseq66.ctrl qpseq66.drums qpseq66.mutes qpseq66.playlist seq66.log Now plug in a MIDI device, and do the same exercise. Open qpseq66.rc and see what is in it: [midi-clock] 2 # number of MIDI clocks/busses # Output buss name: [0] 0:0 PortMidi:Microsoft MIDI Mapper 0 0 # buss number, clock status # Output buss name: [2] 1:1 PortMidi:Microsoft GS Wavetable Synth (virtual) 1 0 # buss number, clock status # Output buss name: [3] 1:1 PortMidi:nanoKEY2 2 0 # buss number, clock status [midi-input] 1 # number of input MIDI busses # The first number is the port number, and the second number # indicates whether it is disabled (0), or enabled (1). # [1] 0:1 PortMidi:nanoKEY2 0 0 We still have some minor issues at start up and at exit, but are now able to play a tune on the wavetable synthesizer using the "-b " option. When you first run qpseq66 on Windows, it will create two new configuration files: When you first run qpseq66 on Windows, it will create a new configuration file, with inaccessible devices noted in the "[midi-clock]" section of "C:\Users\username\AppData\Local\seq66\qpseq66.rc" by a "-1" value. Configuration Files For Windows: On Windows, the conventional location is "C:\Users\username\AppData\Local\seq66". The files are: qpseq66.rc The main usage parameters for Seq66. qpseq66.usr Supplemental options, not all supported in Windows yet. qpseq66.ctrl Provides the MIDI input controls and output controls that let one control Seq66 via MIDI and display some status items to LaunchPad-like devices. qpseq66.drums Provides the ability to remap drum notes. qpseq66.mutes Offloads the mute settings to a separate file. qpseq66.palette Provides a way to change the coloring of most of Seq66. Qt themes can also be applied. The log-file also goes into the AppData\Local\seq66 directory. To access AppData, highlight the username directory, then append "AppData" to the end of "C:\Users\username". It is a Windows thang. These settings are one way to alter the ports accessed. Another way is to make a command-line Shortcut like the following: qpseq66 --buss 1 --option log=filename.out It is also possible to alter the MIDI tune to use a particular buss by using the global buss-selection dropdown in the main window. Keystroke Support: Some keystrokes are hardwired, but a number of them can be configured by editing the "ctrl" file directly. This file is well-documented enough that it can be edited with care, so no Qt 5 keystroke editor will be provided. If you are afraid to edit, find a Linux user of Seq66 and get them to edit one for you. Other Information: http://donyaquick.com/midi-on-windows/#x1-120004.1 # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: data/samples/GM.patches ================================================ # Seq66 0.99.19 Patch file ('patches') configuration file # # /home/ahlstrom/.config/seq66/GM-2.patches # Written 2025-02-19 10:12:15 # # This file resembles the files generated by 'midicvtpp', modified for Seq66: # # midicvtpp --csv-drum GM_DD-11_Drums.csv --output ddrums.ini # # This file defines legacy device-specific non-GM patch mappings. They are # currently used for display when editing Program-Change events. [Seq66] config-type = "patches" version = 0 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] Provides the internal set of GM patches. # Patch-mapping configuration for Seq66, stored in the HOME configuration # directory. To use this file, add its name to the '[patch-file]' section of # the 'rc' file. There's no user-interface for this file. # # # The patches section: # # [Patch 5]. Provides the ordering number for the patch sections. # # gm-name GM name for the patch assigned to the patch number. # gm-patch Patch number, same as the section number. # dev-name The device's name for the patch. # dev-patch GM MIDI patch whose GM sound best matches the dev-name. # (Not yet used). [Patch 0] gm-name = "Acoustic Grand Piano" gm-patch = 0 dev-name = "Acoustic Grand Piano" [Patch 1] gm-name = "Bright Acoustic Piano" gm-patch = 1 dev-name = "Bright Acoustic Piano" [Patch 2] gm-name = "Electric Grand Piano" gm-patch = 2 dev-name = "Electric Grand Piano" [Patch 3] gm-name = "Honky-tonk Piano" gm-patch = 3 dev-name = "Honky-tonk Piano" [Patch 4] gm-name = "Electric Piano 1" gm-patch = 4 dev-name = "Electric Piano 1" [Patch 5] gm-name = "Electric Piano 2" gm-patch = 5 dev-name = "Electric Piano 2" [Patch 6] gm-name = "Harpsichord" gm-patch = 6 dev-name = "Harpsichord" [Patch 7] gm-name = "Clavinet" gm-patch = 7 dev-name = "Clavinet" [Patch 8] gm-name = "Celesta" gm-patch = 8 dev-name = "Celesta" [Patch 9] gm-name = "Glockenspiel" gm-patch = 9 dev-name = "Glockenspiel" [Patch 10] gm-name = "Music Box" gm-patch = 10 dev-name = "Music Box" [Patch 11] gm-name = "Vibraphone" gm-patch = 11 dev-name = "Vibraphone" [Patch 12] gm-name = "Marimba" gm-patch = 12 dev-name = "Marimba" [Patch 13] gm-name = "Xylophone" gm-patch = 13 dev-name = "Xylophone" [Patch 14] gm-name = "Tubular Bells" gm-patch = 14 dev-name = "Tubular Bells" [Patch 15] gm-name = "Dulcimer" gm-patch = 15 dev-name = "Dulcimer" [Patch 16] gm-name = "Drawbar Organ" gm-patch = 16 dev-name = "Drawbar Organ" [Patch 17] gm-name = "Percussive Organ" gm-patch = 17 dev-name = "Percussive Organ" [Patch 18] gm-name = "Rock Organ" gm-patch = 18 dev-name = "Rock Organ" [Patch 19] gm-name = "Church Organ" gm-patch = 19 dev-name = "Church Organ" [Patch 20] gm-name = "Reed Organ" gm-patch = 20 dev-name = "Reed Organ" [Patch 21] gm-name = "Accordion" gm-patch = 21 dev-name = "Accordion" [Patch 22] gm-name = "Harmonica" gm-patch = 22 dev-name = "Harmonica" [Patch 23] gm-name = "Tango Accordion" gm-patch = 23 dev-name = "Tango Accordion" [Patch 24] gm-name = "Acoustic Nylon Guitar" gm-patch = 24 dev-name = "Acoustic Nylon Guitar" [Patch 25] gm-name = "Acoustic Steel Guitar" gm-patch = 25 dev-name = "Acoustic Steel Guitar" [Patch 26] gm-name = "Electric Jazz Guitar" gm-patch = 26 dev-name = "Electric Jazz Guitar" [Patch 27] gm-name = "Electric Clean Guitar" gm-patch = 27 dev-name = "Electric Clean Guitar" [Patch 28] gm-name = "Electric Muted Guitar" gm-patch = 28 dev-name = "Electric Muted Guitar" [Patch 29] gm-name = "Overdriven Guitar" gm-patch = 29 dev-name = "Overdriven Guitar" [Patch 30] gm-name = "Distortion Guitar" gm-patch = 30 dev-name = "Distortion Guitar" [Patch 31] gm-name = "Guitar Harmonics" gm-patch = 31 dev-name = "Guitar Harmonics" [Patch 32] gm-name = "Acoustic Bass" gm-patch = 32 dev-name = "Acoustic Bass" [Patch 33] gm-name = "Fingered Electric Bass" gm-patch = 33 dev-name = "Fingered Electric Bass" [Patch 34] gm-name = "Plucked Electric Bass" gm-patch = 34 dev-name = "Plucked Electric Bass" [Patch 35] gm-name = "Fretless Bass" gm-patch = 35 dev-name = "Fretless Bass" [Patch 36] gm-name = "Slap Bass 1" gm-patch = 36 dev-name = "Slap Bass 1" [Patch 37] gm-name = "Slap Bass 2" gm-patch = 37 dev-name = "Slap Bass 2" [Patch 38] gm-name = "Synth Bass 1" gm-patch = 38 dev-name = "Synth Bass 1" [Patch 39] gm-name = "Synth Bass 2" gm-patch = 39 dev-name = "Synth Bass 2" [Patch 40] gm-name = "Violin" gm-patch = 40 dev-name = "Violin" [Patch 41] gm-name = "Viola" gm-patch = 41 dev-name = "Viola" [Patch 42] gm-name = "Cello" gm-patch = 42 dev-name = "Cello" [Patch 43] gm-name = "Contrabass" gm-patch = 43 dev-name = "Contrabass" [Patch 44] gm-name = "Tremolo Strings" gm-patch = 44 dev-name = "Tremolo Strings" [Patch 45] gm-name = "Pizzicato Strings" gm-patch = 45 dev-name = "Pizzicato Strings" [Patch 46] gm-name = "Orchestral Harp" gm-patch = 46 dev-name = "Orchestral Harp" [Patch 47] gm-name = "Timpani" gm-patch = 47 dev-name = "Timpani" [Patch 48] gm-name = "String Ensemble 1" gm-patch = 48 dev-name = "String Ensemble 1" [Patch 49] gm-name = "String Ensemble 2" gm-patch = 49 dev-name = "String Ensemble 2" [Patch 50] gm-name = "Synth Strings 1" gm-patch = 50 dev-name = "Synth Strings 1" [Patch 51] gm-name = "Synth Strings 2" gm-patch = 51 dev-name = "Synth Strings 2" [Patch 52] gm-name = "Choir Aah" gm-patch = 52 dev-name = "Choir Aah" [Patch 53] gm-name = "Voice Ooh" gm-patch = 53 dev-name = "Voice Ooh" [Patch 54] gm-name = "Synth Voice" gm-patch = 54 dev-name = "Synth Voice" [Patch 55] gm-name = "Orchestra Hit" gm-patch = 55 dev-name = "Orchestra Hit" [Patch 56] gm-name = "Trumpet" gm-patch = 56 dev-name = "Trumpet" [Patch 57] gm-name = "Trombone" gm-patch = 57 dev-name = "Trombone" [Patch 58] gm-name = "Tuba" gm-patch = 58 dev-name = "Tuba" [Patch 59] gm-name = "Muted Trumpet" gm-patch = 59 dev-name = "Muted Trumpet" [Patch 60] gm-name = "French Horn" gm-patch = 60 dev-name = "French Horn" [Patch 61] gm-name = "Brass Section" gm-patch = 61 dev-name = "Brass Section" [Patch 62] gm-name = "Synth Brass 1" gm-patch = 62 dev-name = "Synth Brass 1" [Patch 63] gm-name = "Synth Brass 2" gm-patch = 63 dev-name = "Synth Brass 2" [Patch 64] gm-name = "Soprano Sax" gm-patch = 64 dev-name = "Soprano Sax" [Patch 65] gm-name = "Alto Sax" gm-patch = 65 dev-name = "Alto Sax" [Patch 66] gm-name = "Tenor Sax" gm-patch = 66 dev-name = "Tenor Sax" [Patch 67] gm-name = "Baritone Sax" gm-patch = 67 dev-name = "Baritone Sax" [Patch 68] gm-name = "Oboe" gm-patch = 68 dev-name = "Oboe" [Patch 69] gm-name = "English Horn" gm-patch = 69 dev-name = "English Horn" [Patch 70] gm-name = "Bassoon" gm-patch = 70 dev-name = "Bassoon" [Patch 71] gm-name = "Clarinet" gm-patch = 71 dev-name = "Clarinet" [Patch 72] gm-name = "Piccolo" gm-patch = 72 dev-name = "Piccolo" [Patch 73] gm-name = "Flute" gm-patch = 73 dev-name = "Flute" [Patch 74] gm-name = "Recorder" gm-patch = 74 dev-name = "Recorder" [Patch 75] gm-name = "Pan Flute" gm-patch = 75 dev-name = "Pan Flute" [Patch 76] gm-name = "Bottle Blow" gm-patch = 76 dev-name = "Bottle Blow" [Patch 77] gm-name = "Shakuhachi" gm-patch = 77 dev-name = "Shakuhachi" [Patch 78] gm-name = "Whistle" gm-patch = 78 dev-name = "Whistle" [Patch 79] gm-name = "Ocarina" gm-patch = 79 dev-name = "Ocarina" [Patch 80] gm-name = "Lead 1 (Square)" gm-patch = 80 dev-name = "Lead 1 (Square)" [Patch 81] gm-name = "Lead 2 (Sawtooth)" gm-patch = 81 dev-name = "Lead 2 (Sawtooth)" [Patch 82] gm-name = "Lead 3 (Calliope)" gm-patch = 82 dev-name = "Lead 3 (Calliope)" [Patch 83] gm-name = "Lead 4 (Chiff)" gm-patch = 83 dev-name = "Lead 4 (Chiff)" [Patch 84] gm-name = "Lead 5 (Charang)" gm-patch = 84 dev-name = "Lead 5 (Charang)" [Patch 85] gm-name = "Lead 6 (Voice)" gm-patch = 85 dev-name = "Lead 6 (Voice)" [Patch 86] gm-name = "Lead 7 (Fifths)" gm-patch = 86 dev-name = "Lead 7 (Fifths)" [Patch 87] gm-name = "Lead 8 (bass)" gm-patch = 87 dev-name = "Lead 8 (bass)" [Patch 88] gm-name = "Pad 1 (New Age)" gm-patch = 88 dev-name = "Pad 1 (New Age)" [Patch 89] gm-name = "Pad 2 (Warm)" gm-patch = 89 dev-name = "Pad 2 (Warm)" [Patch 90] gm-name = "Pad 3 (Polysynth)" gm-patch = 90 dev-name = "Pad 3 (Polysynth)" [Patch 91] gm-name = "Pad 4 (Choir)" gm-patch = 91 dev-name = "Pad 4 (Choir)" [Patch 92] gm-name = "Pad 5 (Bowed)" gm-patch = 92 dev-name = "Pad 5 (Bowed)" [Patch 93] gm-name = "Pad 6 (Metallic)" gm-patch = 93 dev-name = "Pad 6 (Metallic)" [Patch 94] gm-name = "Pad 7 (Halo)" gm-patch = 94 dev-name = "Pad 7 (Halo)" [Patch 95] gm-name = "Pad 8 (Sweep)" gm-patch = 95 dev-name = "Pad 8 (Sweep)" [Patch 96] gm-name = "FX 1 (Rain)" gm-patch = 96 dev-name = "FX 1 (Rain)" [Patch 97] gm-name = "FX 2 (Soundtrack)" gm-patch = 97 dev-name = "FX 2 (Soundtrack)" [Patch 98] gm-name = "FX 3 (Crystal)" gm-patch = 98 dev-name = "FX 3 (Crystal)" [Patch 99] gm-name = "FX 4 (Atmosphere)" gm-patch = 99 dev-name = "FX 4 (Atmosphere)" [Patch 100] gm-name = "FX 5 (Brightness)" gm-patch = 100 dev-name = "FX 5 (Brightness)" [Patch 101] gm-name = "FX 6 (Goblins)" gm-patch = 101 dev-name = "FX 6 (Goblins)" [Patch 102] gm-name = "FX 7 (Echoes)" gm-patch = 102 dev-name = "FX 7 (Echoes)" [Patch 103] gm-name = "FX 8 (Sci-Fi)" gm-patch = 103 dev-name = "FX 8 (Sci-Fi)" [Patch 104] gm-name = "Sitar" gm-patch = 104 dev-name = "Sitar" [Patch 105] gm-name = "Banjo" gm-patch = 105 dev-name = "Banjo" [Patch 106] gm-name = "Shamisen" gm-patch = 106 dev-name = "Shamisen" [Patch 107] gm-name = "Koto" gm-patch = 107 dev-name = "Koto" [Patch 108] gm-name = "Kalimba" gm-patch = 108 dev-name = "Kalimba" [Patch 109] gm-name = "Bagpipe" gm-patch = 109 dev-name = "Bagpipe" [Patch 110] gm-name = "Fiddle" gm-patch = 110 dev-name = "Fiddle" [Patch 111] gm-name = "Shanai" gm-patch = 111 dev-name = "Shanai" [Patch 112] gm-name = "Tinkle Bell" gm-patch = 112 dev-name = "Tinkle Bell" [Patch 113] gm-name = "Agogo" gm-patch = 113 dev-name = "Agogo" [Patch 114] gm-name = "Steel Drums" gm-patch = 114 dev-name = "Steel Drums" [Patch 115] gm-name = "Woodblock" gm-patch = 115 dev-name = "Woodblock" [Patch 116] gm-name = "Taiko Drum" gm-patch = 116 dev-name = "Taiko Drum" [Patch 117] gm-name = "Melodic Tom" gm-patch = 117 dev-name = "Melodic Tom" [Patch 118] gm-name = "Synth Drum" gm-patch = 118 dev-name = "Synth Drum" [Patch 119] gm-name = "Reverse Cymbal" gm-patch = 119 dev-name = "Reverse Cymbal" [Patch 120] gm-name = "Guitar Fret Noise" gm-patch = 120 dev-name = "Guitar Fret Noise" [Patch 121] gm-name = "Breath Noise" gm-patch = 121 dev-name = "Breath Noise" [Patch 122] gm-name = "Seashore" gm-patch = 122 dev-name = "Seashore" [Patch 123] gm-name = "Bird Tweet" gm-patch = 123 dev-name = "Bird Tweet" [Patch 124] gm-name = "Telephone Ring" gm-patch = 124 dev-name = "Telephone Ring" [Patch 125] gm-name = "Helicopter" gm-patch = 125 dev-name = "Helicopter" [Patch 126] gm-name = "Applause" gm-patch = 126 dev-name = "Applause" [Patch 127] gm-name = "Gunshot" gm-patch = 127 dev-name = "Gunshot" # End of /home/ahlstrom/.config/seq66/GM-2.patches # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/samples/GM_DD-11.drums ================================================ # Seq66 0.91.0 (and above) note-mapper configuration file # # /home/user/.config/seq66/GM_DD-11.drums # Written 2023-10-28 # # This file is similar to files generated by our 'midicvt' program, # but heavily modified for simplicity for Seq66. # # midicvtpp --csv-drum GM_DD-11_Drums.csv --output ddrums.ini # # This file can be used to convert the percussion of non-GM devices # to GM, as best as permitted by GM percussion. Although it is a # 'drums' file, it can be used for other note-mappings as well. [Seq66] config-type = "drums" version = 0 # The [comments] section can document this file. Lines starting # with '#' and '[' are ignored. Blank lines are ignored. Show a # blank line by adding a space character to the line. [comments] This file is based on a similar file created using the "midicvt" application. It has been manually edited to fit Seq66 conventions. # This file holds a drum-note mapping configuration for Seq66. It is # always stored in the configuration directory. To use this file, # add this file-name to the '[note-mapper]' section of the 'rc' file. # There is currently no user interface for managing this file. # The main values are: # # map-type: drum, patch, or multi; indicates the mapping to do. # gm-channel: Indicates the channel (1-16) applied to converted notes. # reverse: true or false; map in the opposite direction if true. [notemap-flags] map-type = drums gm-channel = 10 reverse = false # The drum section: # # [Drum 35]. Marks a GM drum-change section, one per instrument. # # gm-name GM name for the drum assigned to the input note. # gm-note Input note number, same as the section number. # dev-name The device's name for the drum. # dev-note GM MIDI note whose GM sound best matches the sound # of dev-name. The gm-note value is converted to the # dev-note value (unless reverse mapping is activated). # Note that the actual GM drum sound might not match # what the MIDI hardware puts out. # # The gm-note value is converted to the dev-note value, unless reverse # mapping is activated. The actual GM drum sound might not match what the # MIDI hardware puts out. [Drum 35] dev-name = "Unsupported 35" gm-name = "Acoustic Bass Drum" dev-note = 35 gm-note = 35 [Drum 36] dev-name = "Bass Drum Gated Reverb" gm-name = "Bass Drum 1" dev-note = 36 gm-note = 35 [Drum 37] dev-name = "Unsupported 37" gm-name = "Side Stick" dev-note = 37 gm-note = 37 [Drum 38] dev-name = "Snare Drum Gated Reverb" gm-name = "Acoustic Snare" dev-note = 38 gm-note = 38 [Drum 39] dev-name = "Unsupported 39" gm-name = "Hand Clap" dev-note = 39 gm-note = 39 [Drum 40] dev-name = "Unsupported 40" gm-name = "Electric Snare" dev-note = 40 gm-note = 40 [Drum 41] dev-name = "Tom Low 1" gm-name = "Low Floor Tom" dev-note = 41 gm-note = 41 [Drum 42] dev-name = "Tom Mid 1" gm-name = "Low Mid Tom" dev-note = 42 gm-note = 47 [Drum 43] dev-name = "Tom High 1" gm-name = "High Floor Tom" dev-note = 43 gm-note = 43 [Drum 44] dev-name = "Bass Drum 1" gm-name = "Bass Drum 1" dev-note = 44 gm-note = 36 [Drum 45] dev-name = "Bass Drum 2" gm-name = "Acoustic Bass Drum" dev-note = 45 gm-note = 35 [Drum 46] dev-name = "Unsupported 46" gm-name = "Open Hi-Hat" dev-note = 46 gm-note = 46 [Drum 47] dev-name = "Unsupported 47" gm-name = "Low-Mid Tom" dev-note = 47 gm-note = 47 [Drum 48] dev-name = "Tom Low 2" gm-name = "Low Tom" dev-note = 48 gm-note = 45 [Drum 49] dev-name = "Snare Drum Hi" gm-name = "Electric Snare" dev-note = 49 gm-note = 40 [Drum 50] dev-name = "Tom Mid 2" gm-name = "High Mid Tom" dev-note = 50 gm-note = 48 [Drum 51] dev-name = "Snare Drum Rim Shot" gm-name = "Side Stick" dev-note = 51 gm-note = 37 [Drum 52] dev-name = "Snare Drum Low" gm-name = "Acoustic Snare" dev-note = 52 gm-note = 38 [Drum 53] dev-name = "Tom High 2" gm-name = "High Tom" dev-note = 53 gm-note = 50 [Drum 54] dev-name = "Hand Clap" gm-name = "Hand Clap" dev-note = 54 gm-note = 39 [Drum 55] dev-name = "Cowbell" gm-name = "Cowbell" dev-note = 55 gm-note = 56 [Drum 56] dev-name = "Shaker" gm-name = "Maracas" dev-note = 56 gm-note = 70 [Drum 57] dev-name = "High Hat Closed" gm-name = "Closed High Hat" dev-note = 57 gm-note = 42 [Drum 58] dev-name = "Unsupported 58" gm-name = "Vibraslap" dev-note = 58 gm-note = 58 [Drum 59] dev-name = "High Hat Open" gm-name = "Open High Hat" dev-note = 59 gm-note = 46 [Drum 60] dev-name = "Crash Cymbal" gm-name = "Crash Cymbal 1" dev-note = 60 gm-note = 49 [Drum 61] dev-name = "Splash Cymbal" gm-name = "Splash Cymbal" dev-note = 61 gm-note = 55 [Drum 62] dev-name = "Ride Cymbal Cup" gm-name = "Ride Cymbal 2" dev-note = 62 gm-note = 59 [Drum 63] dev-name = "Ride Cymbal" gm-name = "Ride Cymbal 1" dev-note = 63 gm-note = 51 [Drum 64] dev-name = "Conga Low" gm-name = "Low Conga" dev-note = 64 gm-note = 64 [Drum 65] dev-name = "Conga High" gm-name = "Open High Conga" dev-note = 65 gm-note = 63 [Drum 66] dev-name = "Conga Muted" gm-name = "Mute High Conga" dev-note = 66 gm-note = 62 [Drum 67] dev-name = "Bongo Low" gm-name = "Low Bongo" dev-note = 67 gm-note = 61 [Drum 68] dev-name = "Bongo High" gm-name = "High Bongo" dev-note = 68 gm-note = 60 [Drum 69] dev-name = "Timbale Low" gm-name = "Low Timbale" dev-note = 69 gm-note = 66 [Drum 70] dev-name = "Timbale High" gm-name = "High Timbale" dev-note = 70 gm-note = 65 [Drum 71] dev-name = "Unsupported 71" gm-name = "Short Whistle" dev-note = 71 gm-note = 71 [Drum 72] dev-name = "Claves Low" gm-name = "Low Wood Block" dev-note = 72 gm-note = 77 [Drum 73] dev-name = "Claves High" gm-name = "High Wood Block" dev-note = 73 gm-note = 76 [Drum 74] dev-name = "Agogo Low" gm-name = "Low Agogo" dev-note = 74 gm-note = 68 [Drum 75] dev-name = "Agogo High" gm-name = "High Agogo" dev-note = 75 gm-note = 67 [Drum 76] dev-name = "Cuica Low" gm-name = "Mute Cuica" dev-note = 76 gm-note = 78 [Drum 77] dev-name = "Cuica High" gm-name = "Open Cuica" dev-note = 77 gm-note = 79 [Drum 78] dev-name = "Unsupported 78" gm-name = "Mute Cuica" dev-note = 78 gm-note = 78 [Drum 79] dev-name = "Unsupported 79" gm-name = "Open Cuica" dev-note = 79 gm-note = 79 [Drum 80] dev-name = "Unsupported 80" gm-name = "Mute Triangle" dev-note = 80 gm-note = 80 [Drum 81] dev-name = "Unsupported 81" gm-name = "Open Triangle" dev-note = 81 gm-note = 81 # End of /home/user/.config/seq66/GM_DD-11.drums # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/samples/GM_PSS-790.drums ================================================ # Generated mostly by 'midicvtpp --csv-drum tests/csvfiles/GM_PSS-790_Drums.csv # --output gmdrums.ini' # # This file provides easy-to-use settings for remapping some MIDI events # so that a MIDI file for a specific device plays back in General MIDI (GM). # This file consists of one unnamed section and a number of named sections # as described below. Note that the 'name' values can be empty, if you don't # want to key them in. [Seq66] config-type = "drums" version = 0 [notemap-flags] # map-type Types of maps in this file: 'drum', 'patch', or 'multi'. # gm-channel Indicates the drum channel for GM. Usually 10. # reverse Reverse mapping. Usually better as a command-line option. map-type = drums gm-channel = 10 reverse = false # The drum section: # # [Drum 35]. Marks a GM drum-change section, one per instrument. # # gm-name GM name for the drum assigned to the input note. # gm-note Input note number, same as the section number. # note. # dev-note GM MIDI note value whose GM sound best matches the sound # described by dev-name. The gm-note value is converted to # the dev-note value (unless reverse mapping is activated). # Note that the actual GM drum might not match what the old # MIDI hardware put ut. # [Drum 35] dev-name = "Unsupported 35" gm-name = "Acoustic Bass Drum" gm-note = 35 dev-note = 35 [Drum 36] dev-name = "Bass Drum Reverb" gm-name = "BassDrum 1" dev-note = 36 gm-note = 36 [Drum 37] dev-name = "Triangle Mute" gm-name = "Side Stick" dev-note = 80 gm-note = 37 [Drum 38] dev-name = "Synth Snare" gm-name = "Acoustic Snare" dev-note = 40 gm-note = 38 [Drum 39] dev-name = "Triangle Open" gm-name = "Hand Clap" dev-note = 81 gm-note = 39 [Drum 40] dev-name = "Synth Tom Base" gm-name = "Electric Snare" dev-note = 41 gm-note = 40 [Drum 41] dev-name = "Synth Tom Lo" gm-name = "Low Floor Tom" dev-note = 45 gm-note = 41 [Drum 42] dev-name = "Synth Tom Mid" gm-name = "Closed Hi-Hat" dev-note = 47 gm-note = 42 [Drum 43] dev-name = "Synth Tom Hi" gm-name = "High Floor Tom" dev-note = 48 gm-note = 43 [Drum 44] dev-name = "Bass Drum Lo" gm-name = "Pedal Hi-Hat" dev-note = 35 gm-note = 44 [Drum 45] dev-name = "Bass Drum Hi" gm-name = "Low Tom" dev-note = 36 gm-note = 45 [Drum 46] dev-name = "Rim Shot 1" gm-name = "Open Hi-Hat" dev-note = 37 gm-note = 46 [Drum 47] dev-name = "Tom Bass" gm-name = "Low-Mid Tom" dev-note = 41 gm-note = 47 [Drum 48] dev-name = "Tom Lo" gm-name = "Hi-Mid Tom" dev-note = 45 gm-note = 48 [Drum 49] dev-name = "Snare Hi" gm-name = "Crash Cymbal 1" dev-note = 40 gm-note = 49 [Drum 50] dev-name = "Tom Mid" gm-name = "High Tom" dev-note = 47 gm-note = 50 [Drum 51] dev-name = "Rim Shot 2" gm-name = "Ride Cymbal 1" dev-note = 37 gm-note = 51 [Drum 52] dev-name = "Snare Lo" gm-name = "Chinese Cymbal" dev-note = 38 gm-note = 52 [Drum 53] dev-name = "Tom Hi" gm-name = "Ride Bell" dev-note = 48 gm-note = 53 [Drum 54] dev-name = "Hand Claps" gm-name = "Tambourine" dev-note = 77 gm-note = 54 [Drum 55] dev-name = "Cowbell" gm-name = "Splash Cymbal" dev-note = 56 gm-note = 55 [Drum 56] dev-name = "Cabasa" gm-name = "Cowbell" dev-note = 69 gm-note = 56 [Drum 57] dev-name = "Hi Hat Closed" gm-name = "Crash Cymbal 2" dev-note = 42 gm-note = 57 [Drum 58] dev-name = "Brash Hit" gm-name = "Vibraslap" dev-note = 57 gm-note = 58 [Drum 59] dev-name = "Hi Hat Open" gm-name = "Ride Cymbal 2" dev-note = 46 gm-note = 59 [Drum 60] dev-name = "Crash Cymbal" gm-name = "Hi Bongo" dev-note = 49 gm-note = 60 [Drum 61] dev-name = "Splash Cymbal" gm-name = "Low Bongo" dev-note = 55 gm-note = 61 [Drum 62] dev-name = "Ride Cymbal Cup" gm-name = "Mute Hi Conga" dev-note = 51 gm-note = 62 [Drum 63] dev-name = "Ride Cymbal Edge" gm-name = "Open Hi Conga" dev-note = 59 gm-note = 63 [Drum 64] dev-name = "Conga Lo" gm-name = "Low Conga" dev-note = 64 gm-note = 64 [Drum 65] dev-name = "Conga Hi" gm-name = "High Timbale" dev-note = 63 gm-note = 65 [Drum 66] dev-name = "Conga Hi Mute" gm-name = "Low Timbale" dev-note = 62 gm-note = 66 [Drum 67] dev-name = "Bongo Lo" gm-name = "High Agogo" dev-note = 61 gm-note = 67 [Drum 68] dev-name = "Bongo Hi" gm-name = "Low Agogo" dev-note = 60 gm-note = 68 [Drum 69] dev-name = "Timbale Lo" gm-name = "Cabasa" dev-note = 66 gm-note = 69 [Drum 70] dev-name = "Timbale Hi" gm-name = "Maracas" dev-note = 65 gm-note = 70 [Drum 71] dev-name = "Tambourine" gm-name = "Short Whistle" dev-note = 54 gm-note = 71 [Drum 72] dev-name = "Claves Lo" gm-name = "Long Whistle" dev-note = 75 gm-note = 72 [Drum 73] dev-name = "Claves Hi" gm-name = "Short Guiro" dev-note = 75 gm-note = 73 [Drum 74] dev-name = "Agogo Lo" gm-name = "Long Guiro" dev-note = 68 gm-note = 74 [Drum 75] dev-name = "Agogo Hi" gm-name = "Claves" dev-note = 67 gm-note = 75 [Drum 76] dev-name = "Cuica Lo" gm-name = "Hi Wood Block" dev-note = 78 gm-note = 76 [Drum 77] dev-name = "Cuica Hi" gm-name = "Low Wood Block" dev-note = 79 gm-note = 77 [Drum 78] dev-name = "Whistle" gm-name = "Mute Cuica" dev-note = 71 gm-note = 78 [Drum 79] dev-name = "Brash Squeeze" gm-name = "Open Cuica" dev-note = 55 gm-note = 79 [Drum 80] dev-name = "Hi Hat Foot" gm-name = "Mute Triangle" dev-note = 44 gm-note = 80 [Drum 81] dev-name = "Snare Gated Reverb" gm-name = "Open Triangle" dev-note = 40 gm-note = 81 [Drum 82] dev-name = "One" gm-name = "N/A" dev-note = 45 gm-note = 82 [Drum 83] dev-name = "Two" gm-name = "N/A" dev-note = 47 gm-note = 83 [Drum 84] dev-name = "Three" gm-name = "N/A" dev-note = 48 gm-note = 84 [Drum 85] dev-name = "Four" gm-name = "N/A" gm-note = 85 dev-note = 50 # vim: ts=3 sw=3 et ft=dosini ================================================ FILE: data/samples/GM_PSS-790_Multi.ini ================================================ # Generated mostly by 'midicvtpp --csv-drum tests/csvfiles/GM_PSS-790_Drums.csv # --output drums.ini' # # midicvtpp v 0.4.0 # # This file provides easy-to-use settings for remapping some MIDI events # so that a MIDI file for a specific device plays back in General MIDI (GM). # This file consists of one unnamed section and a number of named sections # as described below. Note that the 'name' values can be empty, if you don't # want to key them in. # # The unnamed section: # # file-style The format of the INI file. Values: 'sectioned' (only). # setup-name Provides a name for the setup, for information only. # map-type Types of maps in this file: 'drum', 'patch', or 'multi'. # gm-channel Indicates the drum channel for GM. Usually 10. # dev-channel Drum channel for the device. Some use channel 16. # extract-channel Channel to write to MIDI file. Other channels dropped. # reject-channel Channel for events to be dropped from the new MIDI file. # reverse Reverse mapping. Usually better as a command-line option. # testing Do the latest developer test. Command-line preferred. # # The drum section: # # [Drum 35]. Marks a GM drum-change section, one per instrument. # # gm-name GM name for the drum assigned to the input note. # gm-note Input note number, same as the section number. # dev-name The device's name for the drum represented by the input # note. # dev-note GM MIDI note value whose GM sound best matches the sound # described by dev-name. The gm-note value is converted to # the dev-note value (unless reverse mapping is activated). # cases, there is no exact GM drum equivalent, so we use # a GM drum that is close enough to the device's drum. # # The patch/program section (see below). file-style = sectioned setup-name = midicvtpp map-type = multi gm-channel = 10 dev-channel = 16 extract-channel = none reject-channel = none # reverse = false testing = false [ Drum 35 ] gm-name = "Acoustic Bass Drum" gm-note = 35 dev-name = "N/A" dev-note = 35 [ Drum 36 ] gm-name = "Bass Drum 1" gm-note = 36 dev-name = "Bass Drum Reverb" dev-note = 36 [ Drum 37 ] gm-name = "Side Stick" gm-note = 37 dev-name = "Triangle Mute" dev-note = 80 [ Drum 38 ] gm-name = "Acoustic Snare" gm-note = 38 dev-name = "Synth Snare" dev-note = 40 [ Drum 39 ] gm-name = "Hand Clap" gm-note = 39 dev-name = "Triangle Open" dev-note = 81 [ Drum 40 ] gm-name = "Electric Snare" gm-note = 40 dev-name = "Synth Tom Bass" dev-note = 41 [ Drum 41 ] gm-name = "Low Floor Tom" gm-note = 41 dev-name = "Synth Tom Low" dev-note = 45 [ Drum 42 ] gm-name = "Closed Hi-Hat" gm-note = 42 dev-name = "Synth Tom Mid" dev-note = 47 [ Drum 43 ] gm-name = "High Floor Tom" gm-note = 43 dev-name = "Synth Tom High" dev-note = 48 [ Drum 44 ] gm-name = "Pedal Hi-Hat" gm-note = 44 dev-name = "Bass Drum Low" dev-note = 35 [ Drum 45 ] gm-name = "Low Tom" gm-note = 45 dev-name = "Bass Drum High" dev-note = 36 [ Drum 46 ] gm-name = "Open Hi-Hat" gm-note = 46 dev-name = "Rim Shot 1" dev-note = 37 [ Drum 47 ] gm-name = "Low-Mid Tom" gm-note = 47 dev-name = "Tom Bass" dev-note = 41 [ Drum 48 ] gm-name = "Hi-Mid Tom" gm-note = 48 dev-name = "C2 Tom Low" dev-note = 45 [ Drum 49 ] gm-name = "Crash Cymbal 1" gm-note = 49 dev-name = "Snare High" dev-note = 40 [ Drum 50 ] gm-name = "High Tom" gm-note = 50 dev-name = "Tom Mid" dev-note = 47 [ Drum 51 ] gm-name = "Ride Cymbal 1" gm-note = 51 dev-name = "Rim Shot 2" dev-note = 37 [ Drum 52 ] gm-name = "Chinese Cymbal" gm-note = 52 dev-name = "Snare Low" dev-note = 38 [ Drum 53 ] gm-name = "Ride Bell" gm-note = 53 dev-name = "Tom High" dev-note = 48 [ Drum 54 ] gm-name = "Tambourine" gm-note = 54 dev-name = "Hand Claps" dev-note = 77 [ Drum 55 ] gm-name = "Splash Cymbal" gm-note = 55 dev-name = "Cowbell" dev-note = 56 [ Drum 56 ] gm-name = "Cowbell" gm-note = 56 dev-name = "Cabasa" dev-note = 69 [ Drum 57 ] gm-name = "Crash Cymbal 2" gm-note = 57 dev-name = "High Hat Closed" dev-note = 42 [ Drum 58 ] gm-name = "Vibraslap" gm-note = 58 dev-name = "Brash Hit" dev-note = 57 [ Drum 59 ] gm-name = "Ride Cymbal 2" gm-note = 59 dev-name = "High Hat Open" dev-note = 46 [ Drum 60 ] gm-name = "Hi Bongo" gm-note = 60 dev-name = "C3 Crash Cymbal" dev-note = 49 [ Drum 61 ] gm-name = "Low Bongo" gm-note = 61 dev-name = "Splash Cymbal" dev-note = 55 [ Drum 62 ] gm-name = "Mute Hi Conga" gm-note = 62 dev-name = "Ride Cymbal Cup" dev-note = 51 [ Drum 63 ] gm-name = "Open Hi Conga" gm-note = 63 dev-name = "Ride Cymbal Edge" dev-note = 59 [ Drum 64 ] gm-name = "Low Conga" gm-note = 64 dev-name = "Conga Low" dev-note = 64 [ Drum 65 ] gm-name = "High Timbale" gm-note = 65 dev-name = "Conga High" dev-note = 63 [ Drum 66 ] gm-name = "Low Timbale" gm-note = 66 dev-name = "Conga High Mute" dev-note = 62 [ Drum 67 ] gm-name = "High Agogo" gm-note = 67 dev-name = "Bongo Low" dev-note = 61 [ Drum 68 ] gm-name = "Low Agogo" gm-note = 68 dev-name = "Bongo High" dev-note = 60 [ Drum 69 ] gm-name = "Cabasa" gm-note = 69 dev-name = "Timbale Low" dev-note = 66 [ Drum 70 ] gm-name = "Maracas" gm-note = 70 dev-name = "Timbale High" dev-note = 65 [ Drum 71 ] gm-name = "Short Whistle" gm-note = 71 dev-name = "Tambourine" dev-note = 54 [ Drum 72 ] gm-name = "Long Whistle" gm-note = 72 dev-name = "C4 Claves Low" dev-note = 75 [ Drum 73 ] gm-name = "Short Guiro" gm-note = 73 dev-name = "Claves High" dev-note = 75 [ Drum 74 ] gm-name = "Long Guiro" gm-note = 74 dev-name = "Agogo Low" dev-note = 68 [ Drum 75 ] gm-name = "Claves" gm-note = 75 dev-name = "Agogo High" dev-note = 67 [ Drum 76 ] gm-name = "Hi Wood Block" gm-note = 76 dev-name = "Cuica Low" dev-note = 78 [ Drum 77 ] gm-name = "Low Wood Block" gm-note = 77 dev-name = "Cuica High" dev-note = 79 [ Drum 78 ] gm-name = "Mute Cuica" gm-note = 78 dev-name = "Whistle" dev-note = 71 [ Drum 79 ] gm-name = "Open Cuica" gm-note = 79 dev-name = "Brash Squeeze" dev-note = 55 [ Drum 80 ] gm-name = "Mute Triangle" gm-note = 80 dev-name = "High Hat Foot" dev-note = 44 [ Drum 81 ] gm-name = "Open Triangle" gm-note = 81 dev-name = "Snare Gated Reverb" dev-note = 40 [ Drum 82 ] gm-name = "N/A" gm-note = 82 dev-name = "Voice One" dev-note = 45 [ Drum 83 ] gm-name = "N/A" gm-note = 83 dev-name = "Voice Two" dev-note = 47 [ Drum 84 ] gm-name = "N/A" gm-note = 84 dev-name = "Voice Three" dev-note = 48 [ Drum 85 ] gm-name = "N/A" gm-note = 85 dev-name = "Voice Four" dev-note = 50 # Generated mostly by 'midicvtpp --csv-patch tests/csvfiles/GM_PSS-790_Patches.csv # --output patches.ini' # # The patch/program section: # # [Patch 35]. Marks a GM patch-change section, one per program. # # gm-name GM name of the input patch/program in the input MIDI file. # gm-patch Input patch number representing the named GM instrument. # dev-name Name of the device patch corresponding to th GM MIDI note. # dev-patch GM patch number which best matches the device's sound for # the input patch number. The gm-patch number is converted # to this patch number to reconstruct the device's sound as # well as possible. # cases, there is no exact GM patch equivalent, so we use # a GM patch that is close enough to the device's patch. [ Patch 0 ] gm-name = "Acoustic Grand Piano" gm-patch = 0 dev-name = "Synth Brass 1" dev-patch = 62 [ Patch 1 ] gm-name = "Bright Acoustic Piano" gm-patch = 1 dev-name = "Jazz Organ 1" dev-patch = 16 [ Patch 2 ] gm-name = "Electric Grand Piano" gm-patch = 2 dev-name = "Pipe Organ" dev-patch = 19 [ Patch 3 ] gm-name = "Honky-tonk Piano" gm-patch = 3 dev-name = "Piano" dev-patch = 1 [ Patch 4 ] gm-name = "Electric Piano 1" gm-patch = 4 dev-name = "Harpsichord 1" dev-patch = 6 [ Patch 5 ] gm-name = "Electric Piano 2" gm-patch = 5 dev-name = "Electric Piano 1" dev-patch = 4 [ Patch 6 ] gm-name = "Harpsichord" gm-patch = 6 dev-name = "Celesta" dev-patch = 8 [ Patch 7 ] gm-name = "Clavinet" gm-patch = 7 dev-name = "Vibraphone" dev-patch = 11 [ Patch 8 ] gm-name = "Celesta" gm-patch = 8 dev-name = "Marimba" dev-patch = 12 [ Patch 9 ] gm-name = "Glockenspiel" gm-patch = 9 dev-name = "Steel Drum" dev-patch = 114 [ Patch 10 ] gm-name = "Music Box" gm-patch = 10 dev-name = "Violin 1" dev-patch = 40 [ Patch 11 ] gm-name = "Vibraphone" gm-patch = 11 dev-name = "Cello" dev-patch = 42 [ Patch 12 ] gm-name = "Marimba" gm-patch = 12 dev-name = "Jazz Guitar" dev-patch = 26 [ Patch 13 ] gm-name = "Xylophone" gm-patch = 13 dev-name = "Distortion Guitar" dev-patch = 30 [ Patch 14 ] gm-name = "Tubular Bells" gm-patch = 14 dev-name = "Wood Bass 1" dev-patch = 32 [ Patch 15 ] gm-name = "Dulcimer" gm-patch = 15 dev-name = "Trumpet" dev-patch = 56 [ Patch 16 ] gm-name = "Drawbar Organ" gm-patch = 16 dev-name = "Trombone" dev-patch = 57 [ Patch 17 ] gm-name = "Percussive Organ" gm-patch = 17 dev-name = "Horn" dev-patch = 60 [ Patch 18 ] gm-name = "Rock Organ" gm-patch = 18 dev-name = "Alto Sax" dev-patch = 65 [ Patch 19 ] gm-name = "Church Organ" gm-patch = 19 dev-name = "Clarinet" dev-patch = 71 [ Patch 20 ] gm-name = "Reed Organ" gm-patch = 20 dev-name = "Flute" dev-patch = 73 [ Patch 21 ] gm-name = "Accordion" gm-patch = 21 dev-name = "Bassoon" dev-patch = 70 [ Patch 22 ] gm-name = "Harmonica" gm-patch = 22 dev-name = "Harmonica" dev-patch = 22 [ Patch 23 ] gm-name = "Tango Accordion" gm-patch = 23 dev-name = "N/A" dev-patch = 23 [ Patch 24 ] gm-name = "Acoustic Guitar (nylon)" gm-patch = 24 dev-name = "Music Box" dev-patch = 10 [ Patch 25 ] gm-name = "Acoustic Guitar (steel)" gm-patch = 25 dev-name = "Honkytonk Piano" dev-patch = 3 [ Patch 26 ] gm-name = "Electric Guitar (jazz)" gm-patch = 26 dev-name = "N/A" dev-patch = 26 [ Patch 27 ] gm-name = "Electric Guitar (clean)" gm-patch = 27 dev-name = "Jazz Organ 2" dev-patch = 18 [ Patch 28 ] gm-name = "Electric Guitar (muted)" gm-patch = 28 dev-name = "Tremolo Organ" dev-patch = 17 [ Patch 29 ] gm-name = "Overdriven Guitar" gm-patch = 29 dev-name = "Full Organ" dev-patch = 19 [ Patch 30 ] gm-name = "Distortion Guitar" gm-patch = 30 dev-name = "Clavi" dev-patch = 7 [ Patch 31 ] gm-name = "Guitar harmonics" gm-patch = 31 dev-name = "Accordion" dev-patch = 23 [ Patch 32 ] gm-name = "Acoustic Bass" gm-patch = 32 dev-name = "Glockenspiel" dev-patch = 9 [ Patch 33 ] gm-name = "Electric Bass (finger)" gm-patch = 33 dev-name = "Steel Guitar" dev-patch = 25 [ Patch 34 ] gm-name = "Electric Bass (pick)" gm-patch = 34 dev-name = "Banjo" dev-patch = 28 [ Patch 35 ] gm-name = "Fretless Bass" gm-patch = 35 dev-name = "Bowed Bass" dev-patch = 42 [ Patch 36 ] gm-name = "Slap Bass 1" gm-patch = 36 dev-name = "Folk Guitar" dev-patch = 25 [ Patch 37 ] gm-name = "Slap Bass 2" gm-patch = 37 dev-name = "Harp" dev-patch = 46 [ Patch 38 ] gm-name = "Synth Bass 1" gm-patch = 38 dev-name = "Elec Bass" dev-patch = 33 [ Patch 39 ] gm-name = "Synth Bass 2" gm-patch = 39 dev-name = "Slap Bass" dev-patch = 36 [ Patch 40 ] gm-name = "Violin" gm-patch = 40 dev-name = "Ukulele" dev-patch = 25 [ Patch 41 ] gm-name = "Viola" gm-patch = 41 dev-name = "Strings 1" dev-patch = 48 [ Patch 42 ] gm-name = "Cello" gm-patch = 42 dev-name = "N/A" dev-patch = 42 [ Patch 43 ] gm-name = "Contrabass" gm-patch = 43 dev-name = "N/A" dev-patch = 43 [ Patch 44 ] gm-name = "Tremolo Strings" gm-patch = 44 dev-name = "Mute Trumpet" dev-patch = 59 [ Patch 45 ] gm-name = "Pizzicato Strings" gm-patch = 45 dev-name = "Synth Reed 1" dev-patch = 71 [ Patch 46 ] gm-name = "Orchestral Harp" gm-patch = 46 dev-name = "N/A" dev-patch = 46 [ Patch 47 ] gm-name = "Timpani" gm-patch = 47 dev-name = "Synth Flute 2" dev-patch = 73 [ Patch 48 ] gm-name = "String Ensemble 1" gm-patch = 48 dev-name = "N/A" dev-patch = 48 [ Patch 49 ] gm-name = "String Ensemble 2" gm-patch = 49 dev-name = "Reed Organ" dev-patch = 20 [ Patch 50 ] gm-name = "Synth Strings 1" gm-patch = 50 dev-name = "Strings 2" dev-patch = 49 [ Patch 51 ] gm-name = "Synth Strings 2" gm-patch = 51 dev-name = "Synth Strings 1" dev-patch = 51 [ Patch 52 ] gm-name = "Choir Aahs" gm-patch = 52 dev-name = "N/A" dev-patch = 52 [ Patch 53 ] gm-name = "Voice Oohs" gm-patch = 53 dev-name = "Harpsichord 2" dev-patch = 6 [ Patch 54 ] gm-name = "Synth Voice" gm-patch = 54 dev-name = "Electric Piano 2" dev-patch = 5 [ Patch 55 ] gm-name = "Orchestra Hit" gm-patch = 55 dev-name = "N/A" dev-patch = 55 [ Patch 56 ] gm-name = "Trumpet" gm-patch = 56 dev-name = "N/A" dev-patch = 56 [ Patch 57 ] gm-name = "Trombone" gm-patch = 57 dev-name = "N/A" dev-patch = 57 [ Patch 58 ] gm-name = "Tuba" gm-patch = 58 dev-name = "Synth Bass 1" dev-patch = 38 [ Patch 59 ] gm-name = "Muted Trumpet" gm-patch = 59 dev-name = "Xylophone" dev-patch = 13 [ Patch 60 ] gm-name = "French Horn" gm-patch = 60 dev-name = "Synth Piano 1" dev-patch = 2 [ Patch 61 ] gm-name = "Brass Section" gm-patch = 61 dev-name = "N/A" dev-patch = 61 [ Patch 62 ] gm-name = "Synth Brass 1" gm-patch = 62 dev-name = "N/A" dev-patch = 62 [ Patch 63 ] gm-name = "Synth Brass 2" gm-patch = 63 dev-name = "N/A" dev-patch = 63 [ Patch 64 ] gm-name = "Soprano Sax" gm-patch = 64 dev-name = "Fantasy" dev-patch = 99 [ Patch 65 ] gm-name = "Alto Sax" gm-patch = 65 dev-name = "Pizzicato Violin" dev-patch = 45 [ Patch 66 ] gm-name = "Tenor Sax" gm-patch = 66 dev-name = "Timpani" dev-patch = 47 [ Patch 67 ] gm-name = "Baritone Sax" gm-patch = 67 dev-name = "Violin 2" dev-patch = 40 [ Patch 68 ] gm-name = "Oboe" gm-patch = 68 dev-name = "Electric Guitar" dev-patch = 68 [ Patch 69 ] gm-name = "English Horn" gm-patch = 69 dev-name = "Tremolo Guitar" dev-patch = 27 [ Patch 70 ] gm-name = "Bassoon" gm-patch = 70 dev-name = "Mute Guitar" dev-patch = 28 [ Patch 71 ] gm-name = "Clarinet" gm-patch = 71 dev-name = "N/A" dev-patch = 71 [ Patch 72 ] gm-name = "Piccolo" gm-patch = 72 dev-name = "12-string Guitar" dev-patch = 25 [ Patch 73 ] gm-name = "Flute" gm-patch = 73 dev-name = "Gut Guitar" dev-patch = 24 [ Patch 74 ] gm-name = "Recorder" gm-patch = 74 dev-name = "N/A" dev-patch = 74 [ Patch 75 ] gm-name = "Pan Flute" gm-patch = 75 dev-name = "N/A" dev-patch = 75 [ Patch 76 ] gm-name = "Blown Bottle" gm-patch = 76 dev-name = "N/A" dev-patch = 76 [ Patch 77 ] gm-name = "Shakuhachi" gm-patch = 77 dev-name = "N/A" dev-patch = 77 [ Patch 78 ] gm-name = "Whistle" gm-patch = 78 dev-name = "Pizzicato Strings" dev-patch = 45 [ Patch 79 ] gm-name = "Ocarina" gm-patch = 79 dev-name = "Pick Bass" dev-patch = 34 [ Patch 80 ] gm-name = "Lead 1 (square)" gm-patch = 80 dev-name = "Fretless Bass" dev-patch = 35 [ Patch 81 ] gm-name = "Lead 2 (sawtooth)" gm-patch = 81 dev-name = "Wood Bass 2" dev-patch = 41 [ Patch 82 ] gm-name = "Lead 3 (calliope)" gm-patch = 82 dev-name = "Synth Brass 2" dev-patch = 63 [ Patch 83 ] gm-name = "Lead 4 (chiff)" gm-patch = 83 dev-name = "N/A" dev-patch = 83 [ Patch 84 ] gm-name = "Lead 5 (charang)" gm-patch = 84 dev-name = "N/A" dev-patch = 84 [ Patch 85 ] gm-name = "Lead 6 (voice)" gm-patch = 85 dev-name = "Synth Reed 2" dev-patch = 71 [ Patch 86 ] gm-name = "Lead 7 (fifths)" gm-patch = 86 dev-name = "Synth Bass 2" dev-patch = 39 [ Patch 87 ] gm-name = "Lead 8 (bass + lead)" gm-patch = 87 dev-name = "Flugelhorn" dev-patch = 69 [ Patch 88 ] gm-name = "Pad 1 (new age)" gm-patch = 88 dev-name = "Recorder" dev-patch = 74 [ Patch 89 ] gm-name = "Pad 2 (warm)" gm-patch = 89 dev-name = "N/A" dev-patch = 89 [ Patch 90 ] gm-name = "Pad 3 (polysynth)" gm-patch = 90 dev-name = "Orchestra Hit" dev-patch = 55 [ Patch 91 ] gm-name = "Pad 4 (choir)" gm-patch = 91 dev-name = "Samba Whistle" dev-patch = 78 [ Patch 92 ] gm-name = "Pad 5 (bowed)" gm-patch = 92 dev-name = "Brass Ensemble" dev-patch = 61 [ Patch 93 ] gm-name = "Pad 6 (metallic)" gm-patch = 93 dev-name = "Woodwind Ensemble" dev-patch = 70 [ Patch 94 ] gm-name = "Pad 7 (halo)" gm-patch = 94 dev-name = "Synth Chorus" dev-patch = 91 [ Patch 95 ] gm-name = "Pad 8 (sweep)" gm-patch = 95 dev-name = "Synth Piano 4" dev-patch = 1 [ Patch 96 ] gm-name = "FX 1 (rain)" gm-patch = 96 dev-name = "Chorus" dev-patch = 91 [ Patch 97 ] gm-name = "FX 2 (soundtrack)" gm-patch = 97 dev-name = "N/A" dev-patch = 97 [ Patch 98 ] gm-name = "FX 3 (crystal)" gm-patch = 98 dev-name = "N/A" dev-patch = 98 [ Patch 99 ] gm-name = "FX 4 (atmosphere)" gm-patch = 99 dev-name = "SINE WAVE???" dev-patch = 74 [ Patch 100 ] gm-name = "FX 5 (brightness)" gm-patch = 100 dev-name = "Percussions" dev-patch = 0 [ Patch 101 ] gm-name = "FX 6 (goblins)" gm-patch = 101 dev-name = "Soprano Sax" dev-patch = 64 [ Patch 102 ] gm-name = "FX 7 (echoes)" gm-patch = 102 dev-name = "Tenor Sax" dev-patch = 66 [ Patch 103 ] gm-name = "FX 8 (sci-fi)" gm-patch = 103 dev-name = "Piccolo" dev-patch = 72 [ Patch 104 ] gm-name = "FX 1 (rain)" gm-patch = 104 dev-name = "Brass Hit" dev-patch = 61 [ Patch 105 ] gm-name = "FX 2 (soundtrack)" gm-patch = 105 dev-name = "N/A" dev-patch = 105 [ Patch 106 ] gm-name = "FX 3 (crystal)" gm-patch = 106 dev-name = "N/A" dev-patch = 106 [ Patch 107 ] gm-name = "FX 4 (atmosphere)" gm-patch = 107 dev-name = "Mute Bass Echo" dev-patch = 36 [ Patch 108 ] gm-name = "FX 5 (brightness)" gm-patch = 108 dev-name = "Dist Guitar Flange" dev-patch = 30 [ Patch 109 ] gm-name = "FX 6 (goblins)" gm-patch = 109 dev-name = "Synth Strings 2" dev-patch = 51 [ Patch 110 ] gm-name = "FX 7 (echoes)" gm-patch = 110 dev-name = "Synth Piano 3" dev-patch = 7 [ Patch 111 ] gm-name = "FX 8 (sci-fi)" gm-patch = 111 dev-name = "Synth Pan Voice" dev-patch = 85 [ Patch 112 ] gm-name = "Tinkle Bell" gm-patch = 112 dev-name = "Synth Flute 1" dev-patch = 75 [ Patch 113 ] gm-name = "Agogo" gm-patch = 113 dev-name = "Synth Reed 3" dev-patch = 71 [ Patch 114 ] gm-name = "Steel Drums" gm-patch = 114 dev-name = "Mute Bass" dev-patch = 37 [ Patch 115 ] gm-name = "Woodblock" gm-patch = 115 dev-name = "Synth Brass 3" dev-patch = 62 [ Patch 116 ] gm-name = "Taiko Drum" gm-patch = 116 dev-name = "Mute Guitar Echo" dev-patch = 24 [ Patch 117 ] gm-name = "Melodic Tom" gm-patch = 117 dev-name = "Synth Piano 2" dev-patch = 7 [ Patch 118 ] gm-name = "Synth Drum" gm-patch = 118 dev-name = "Trumpet Echo" dev-patch = 56 [ Patch 119 ] gm-name = "Reverse Cymbal" gm-patch = 119 dev-name = "Synth Strings 3" dev-patch = 50 [ Patch 120 ] gm-name = "Guitar Fret Noise" gm-patch = 120 dev-name = "Mute Trumpet Echo" dev-patch = 59 [ Patch 121 ] gm-name = "Breath Noise" gm-patch = 121 dev-name = "Elec Guitar Flange" dev-patch = 27 [ Patch 122 ] gm-name = "Seashore" gm-patch = 122 dev-name = "Jazz Guitar Echo" dev-patch = 26 [ Patch 123 ] gm-name = "Bird Tweet" gm-patch = 123 dev-name = "Elec Guitar Echo" dev-patch = 27 [ Patch 124 ] gm-name = "Telephone Ring" gm-patch = 124 dev-name = "Gut Guitar Echo" dev-patch = 24 [ Patch 125 ] gm-name = "Helicopter" gm-patch = 125 dev-name = "N/A" dev-patch = 125 [ Patch 126 ] gm-name = "Applause" gm-patch = 126 dev-name = "N/A" dev-patch = 126 [ Patch 127 ] gm-name = "Gunshot" gm-patch = 127 dev-name = "N/A" dev-patch = 127 # vim: ts=3 sw=3 et ft=dosini ================================================ FILE: data/samples/PSS-790.patches ================================================ # Seq66 0.97.2 note-mapper ('drums') configuration file # # /home/user/.config/seq66/qseq66.drums # Written 2021-11-04 14:00:24 # # This file provides a way to show the proper patches for a non-GM # device while editing a tune specifically for that device. # # Compare this to the GM_PSS-790.patches file, which will never be # supported. # # [Patch 35]. Provides the program/patch number for the non-GM # device. # # dev-name Name of the device patch corresponding to the # non-gm patch number. # # At some point we ought to implement MIDINAM. [Seq66] config-type = "patches" version = 0 [Patch 0] gm-name = "Acoustic Grand Piano" gm-patch = 0 dev-name = "Synth Brass 1" [Patch 1] gm-name = "Bright Acoustic Piano" gm-patch = 1 dev-name = "Jazz Organ 1" [Patch 2] gm-name = "Electric Grand Piano" gm-patch = 2 dev-name = "Pipe Organ" [Patch 3] gm-name = "Honky-tonk Piano" gm-patch = 3 dev-name = "Piano" [Patch 4] gm-name = "Electric Piano 1" gm-patch = 4 dev-name = "Harpsichord 1" [Patch 5] gm-name = "Electric Piano 2" gm-patch = 5 dev-name = "Electric Piano 1" [Patch 6] gm-name = "Harpsichord" gm-patch = 6 dev-name = "Celesta" [Patch 7] gm-name = "Clavinet" gm-patch = 7 dev-name = "Vibraphone" [Patch 8] gm-name = "Celesta" gm-patch = 8 dev-name = "Marimba" [Patch 9] gm-name = "Glockenspiel" gm-patch = 9 dev-name = "Steel Drum" [Patch 10] gm-name = "Music Box" gm-patch = 10 dev-name = "Violin 1" [Patch 11] gm-name = "Vibraphone" gm-patch = 11 dev-name = "Cello" [Patch 12] gm-name = "Marimba" gm-patch = 12 dev-name = "Jazz Guitar" [Patch 13] gm-name = "Xylophone" gm-patch = 13 dev-name = "Distortion Guitar" [Patch 14] gm-name = "Tubular Bells" gm-patch = 14 dev-name = "Wood Bass 1" [Patch 15] gm-name = "Dulcimer" gm-patch = 15 dev-name = "Trumpet" [Patch 16] gm-name = "Drawbar Organ" gm-patch = 16 dev-name = "Trombone" [Patch 17] gm-name = "Percussive Organ" gm-patch = 17 dev-name = "Horn" [Patch 18] gm-name = "Rock Organ" gm-patch = 18 dev-name = "Alto Sax" [Patch 19] gm-name = "Church Organ" gm-patch = 19 dev-name = "Clarinet" [Patch 20] gm-name = "Reed Organ" gm-patch = 20 dev-name = "Flute" [Patch 21] gm-name = "Accordion" gm-patch = 21 dev-name = "Bassoon" [Patch 22] gm-name = "Harmonica" gm-patch = 22 dev-name = "Harmonica" [Patch 23] gm-name = "Tango Accordion" gm-patch = 23 dev-name = "N/A" [Patch 24] gm-name = "Acoustic Guitar (nylon)" gm-patch = 24 dev-name = "Music Box" [Patch 25] gm-name = "Acoustic Guitar (steel)" gm-patch = 25 dev-name = "Honkytonk Piano" [Patch 26] gm-name = "Electric Guitar (jazz)" gm-patch = 26 dev-name = "N/A" [Patch 27] gm-name = "Electric Guitar (clean)" gm-patch = 27 dev-name = "Jazz Organ 2" [Patch 28] gm-name = "Electric Guitar (muted)" gm-patch = 28 dev-name = "Tremolo Organ" [Patch 29] gm-name = "Overdriven Guitar" gm-patch = 29 dev-name = "Full Organ" [Patch 30] gm-name = "Distortion Guitar" gm-patch = 30 dev-name = "Clavi" [Patch 31] gm-name = "Guitar harmonics" gm-patch = 31 dev-name = "Accordion" [Patch 32] gm-name = "Acoustic Bass" gm-patch = 32 dev-name = "Glockenspiel" [Patch 33] gm-name = "Electric Bass (finger)" gm-patch = 33 dev-name = "Steel Guitar" [Patch 34] gm-name = "Electric Bass (pick)" gm-patch = 34 dev-name = "Banjo" [Patch 35] gm-name = "Fretless Bass" gm-patch = 35 dev-name = "Bowed Bass" [Patch 36] gm-name = "Slap Bass 1" gm-patch = 36 dev-name = "Folk Guitar" [Patch 37] gm-name = "Slap Bass 2" gm-patch = 37 dev-name = "Harp" [Patch 38] gm-name = "Synth Bass 1" gm-patch = 38 dev-name = "Elec Bass" [Patch 39] gm-name = "Synth Bass 2" gm-patch = 39 dev-name = "Slap Bass" [Patch 40] gm-name = "Violin" gm-patch = 40 dev-name = "Ukulele" [Patch 41] gm-name = "Viola" gm-patch = 41 dev-name = "Strings 1" [Patch 42] gm-name = "Cello" gm-patch = 42 dev-name = "N/A" [Patch 43] gm-name = "Contrabass" gm-patch = 43 dev-name = "N/A" [Patch 44] gm-name = "Tremolo Strings" gm-patch = 44 dev-name = "Mute Trumpet" [Patch 45] gm-name = "Pizzicato Strings" gm-patch = 45 dev-name = "Synth Reed 1" [Patch 46] gm-name = "Orchestral Harp" gm-patch = 46 dev-name = "N/A" [Patch 47] gm-name = "Timpani" gm-patch = 47 dev-name = "Synth Flute 2" [Patch 48] gm-name = "String Ensemble 1" gm-patch = 48 dev-name = "N/A" [Patch 49] gm-name = "String Ensemble 2" gm-patch = 49 dev-name = "Reed Organ" [Patch 50] gm-name = "Synth Strings 1" gm-patch = 50 dev-name = "Strings 2" [Patch 51] gm-name = "Synth Strings 2" gm-patch = 51 dev-name = "Synth Strings 1" [Patch 52] gm-name = "Choir Aahs" gm-patch = 52 dev-name = "N/A" [Patch 53] gm-name = "Voice Oohs" gm-patch = 53 dev-name = "Harpsichord 2" [Patch 54] gm-name = "Synth Voice" gm-patch = 54 dev-name = "Electric Piano 2" [Patch 55] gm-name = "Orchestra Hit" gm-patch = 55 dev-name = "N/A" [Patch 56] gm-name = "Trumpet" gm-patch = 56 dev-name = "N/A" [Patch 57] gm-name = "Trombone" gm-patch = 57 dev-name = "N/A" [Patch 58] gm-name = "Tuba" gm-patch = 58 dev-name = "Synth Bass 1" [Patch 59] gm-name = "Muted Trumpet" gm-patch = 59 dev-name = "Xylophone" [Patch 60] gm-name = "French Horn" gm-patch = 60 dev-name = "Synth Piano 1" [Patch 61] gm-name = "Brass Section" gm-patch = 61 dev-name = "N/A" [Patch 62] gm-name = "Synth Brass 1" gm-patch = 62 dev-name = "N/A" [Patch 63] gm-name = "Synth Brass 2" gm-patch = 63 dev-name = "N/A" [Patch 64] gm-name = "Soprano Sax" gm-patch = 64 dev-name = "Fantasy" [Patch 65] gm-name = "Alto Sax" gm-patch = 65 dev-name = "Pizzicato Violin" [Patch 66] gm-name = "Tenor Sax" gm-patch = 66 dev-name = "Timpani" [Patch 67] gm-name = "Baritone Sax" gm-patch = 67 dev-name = "Violin 2" [Patch 68] gm-name = "Oboe" gm-patch = 68 dev-name = "Electric Guitar" [Patch 69] gm-name = "English Horn" gm-patch = 69 dev-name = "Tremolo Guitar" [Patch 70] gm-name = "Bassoon" gm-patch = 70 dev-name = "Mute Guitar" [Patch 71] gm-name = "Clarinet" gm-patch = 71 dev-name = "N/A" [Patch 72] gm-name = "Piccolo" gm-patch = 72 dev-name = "12-string Guitar" [Patch 73] gm-name = "Flute" gm-patch = 73 dev-name = "Gut Guitar" [Patch 74] gm-name = "Recorder" gm-patch = 74 dev-name = "N/A" [Patch 75] gm-name = "Pan Flute" gm-patch = 75 dev-name = "N/A" [Patch 76] gm-name = "Blown Bottle" gm-patch = 76 dev-name = "N/A" [Patch 77] gm-name = "Shakuhachi" gm-patch = 77 dev-name = "N/A" [Patch 78] gm-name = "Whistle" gm-patch = 78 dev-name = "Pizzicato Strings" [Patch 79] gm-name = "Ocarina" gm-patch = 79 dev-name = "Pick Bass" [Patch 80] gm-name = "Lead 1 (square)" gm-patch = 80 dev-name = "Fretless Bass" [Patch 81] gm-name = "Lead 2 (sawtooth)" gm-patch = 81 dev-name = "Wood Bass 2" [Patch 82] gm-name = "Lead 3 (calliope)" gm-patch = 82 dev-name = "Synth Brass 2" [Patch 83] gm-name = "Lead 4 (chiff)" gm-patch = 83 dev-name = "N/A" [Patch 84] gm-name = "Lead 5 (charang)" gm-patch = 84 dev-name = "N/A" [Patch 85] gm-name = "Lead 6 (voice)" gm-patch = 85 dev-name = "Synth Reed 2" [Patch 86] gm-name = "Lead 7 (fifths)" gm-patch = 86 dev-name = "Synth Bass 2" [Patch 87] gm-name = "Lead 8 (bass + lead)" gm-patch = 87 dev-name = "Flugelhorn" [Patch 88] gm-name = "Pad 1 (new age)" gm-patch = 88 dev-name = "Recorder" [Patch 89] gm-name = "Pad 2 (warm)" gm-patch = 89 dev-name = "N/A" [Patch 90] gm-name = "Pad 3 (polysynth)" gm-patch = 90 dev-name = "Orchestra Hit" [Patch 91] gm-name = "Pad 4 (choir)" gm-patch = 91 dev-name = "Samba Whistle" [Patch 92] gm-name = "Pad 5 (bowed)" gm-patch = 92 dev-name = "Brass Ensemble" [Patch 93] gm-name = "Pad 6 (metallic)" gm-patch = 93 dev-name = "Woodwind Ensemble" [Patch 94] gm-name = "Pad 7 (halo)" gm-patch = 94 dev-name = "Synth Chorus" [Patch 95] gm-name = "Pad 8 (sweep)" gm-patch = 95 dev-name = "Synth Piano 4" [Patch 96] gm-name = "FX 1 (rain)" gm-patch = 96 dev-name = "Chorus" [Patch 97] gm-name = "FX 2 (soundtrack)" gm-patch = 97 dev-name = "N/A" [Patch 98] gm-name = "FX 3 (crystal)" gm-patch = 98 dev-name = "N/A" [Patch 99] gm-name = "FX 4 (atmosphere)" gm-patch = 99 dev-name = "SINE WAVE???" [Patch 100] gm-name = "FX 5 (brightness)" gm-patch = 100 dev-name = "Percussions" [Patch 101] gm-name = "FX 6 (goblins)" gm-patch = 101 dev-name = "Soprano Sax" [Patch 102] gm-name = "FX 7 (echoes)" gm-patch = 102 dev-name = "Tenor Sax" [Patch 103] gm-name = "FX 8 (sci-fi)" gm-patch = 103 dev-name = "Piccolo" [Patch 104] gm-name = "FX 1 (rain)" gm-patch = 104 dev-name = "Brass Hit" [Patch 105] gm-name = "FX 2 (soundtrack)" gm-patch = 105 dev-name = "N/A" [Patch 106] gm-name = "FX 3 (crystal)" gm-patch = 106 dev-name = "N/A" [Patch 107] gm-name = "FX 4 (atmosphere)" gm-patch = 107 dev-name = "Mute Bass Echo" [Patch 108] gm-name = "FX 5 (brightness)" gm-patch = 108 dev-name = "Dist Guitar Flange" [Patch 109] gm-name = "FX 6 (goblins)" gm-patch = 109 dev-name = "Synth Strings 2" [Patch 110] gm-name = "FX 7 (echoes)" gm-patch = 110 dev-name = "Synth Piano 3" [Patch 111] gm-name = "FX 8 (sci-fi)" gm-patch = 111 dev-name = "Synth Pan Voice" [Patch 112] gm-name = "Tinkle Bell" gm-patch = 112 dev-name = "Synth Flute 1" [Patch 113] gm-name = "Agogo" gm-patch = 113 dev-name = "Synth Reed 3" [Patch 114] gm-name = "Steel Drums" gm-patch = 114 dev-name = "Mute Bass" [Patch 115] gm-name = "Woodblock" gm-patch = 115 dev-name = "Synth Brass 3" [Patch 116] gm-name = "Taiko Drum" gm-patch = 116 dev-name = "Mute Guitar Echo" [Patch 117] gm-name = "Melodic Tom" gm-patch = 117 dev-name = "Synth Piano 2" [Patch 118] gm-name = "Synth Drum" gm-patch = 118 dev-name = "Trumpet Echo" [Patch 119] gm-name = "Reverse Cymbal" gm-patch = 119 dev-name = "Synth Strings 3" [Patch 120] gm-name = "Guitar Fret Noise" gm-patch = 120 dev-name = "Mute Trumpet Echo" [Patch 121] gm-name = "Breath Noise" gm-patch = 121 dev-name = "Elec Guitar Flange" [Patch 122] gm-name = "Seashore" gm-patch = 122 dev-name = "Jazz Guitar Echo" [Patch 123] gm-name = "Bird Tweet" gm-patch = 123 dev-name = "Elec Guitar Echo" [Patch 124] gm-name = "Telephone Ring" gm-patch = 124 dev-name = "Gut Guitar Echo" [Patch 125] gm-name = "Helicopter" gm-patch = 125 dev-name = "N/A" [Patch 126] gm-name = "Applause" gm-patch = 126 dev-name = "N/A" [Patch 127] gm-name = "Gunshot" gm-patch = 127 dev-name = "N/A" # vim: ts=3 sw=3 et ft=dosini ================================================ FILE: data/samples/ca_midi.playlist ================================================ # Seq66 0.99.7 playlist configuration file # # /home/ahlstrom/.config/seq66/ca_midi.playlist # Written 2023-08-06 14:01:24 # # This file holds multiple playlists, each in a [playlist] section. Each has # a user-specified number for sorting and MIDI control, ranging from 0 to 127. # Next comes a quoted name for this list, followed by the quoted name # of the song directory using the UNIX separator ('/'). # # Next is a list of tunes, each starting with a MIDI control number and the # quoted name of the MIDI file, sorted by control number. They can be simple # 'base.midi' file-names; the playlist directory is prepended to access the # song file. If the file-name has a path, that will be used. [Seq66] config-type = "playlist" version = 1 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] This playlist points to files on my Debian laptop. I created these files long, long ago. Ah, such memories. Added them to the install to make live testing easier. The MIDI files are (on Linux) installed to /usr/share/seq66-0.99/midi. [playlist-options] # 'unmute-next-song' causes the next selected song to have all patterns # armed for playback. (Should be called 'auto-arm'). Does not matter for # songs with triggers for Song mode. 'auto-play' causes songs to start play # automatically when loaded. 'auto-advance' implies the settings noted # above. It automatically loads the next song in the play-list when the # current song ends. 'deep-verify' causes each tune in the play-list to be # loaded to make sure each one can be loaded. Otherwise, only file existence # is checked. unmute-next-song = true auto-play = true auto-advance = true deep-verify = false # Here are the playlist settings, default storage folder, and then a list of # each tune with its control number. The playlist number is arbitrary but # unique. 0 to 127 enforced for use with MIDI playlist controls. Similar # for the tune numbers. Each tune can include a path; it overrides the base # directory. [playlist] number = 0 name = "Legacy Midi Files" directory = "/usr/local/share/seq66-0.99/midi/FM/" 0 "brecluse.mid" 1 "carptsun.mid" 2 "cbflitfm.mid" 3 "dasmodel.mid" 4 "grntamb.mid" 5 "hapwandr.mid" 6 "judyblue.mid" 7 "k_seq11.mid" 8 "longhair.mid" 9 "marraksh.mid" 10 "oxyg4bfm.mid" 11 "pirates.mid" 12 "pss680.mid" 13 "qufrency.mid" 14 "stdemo3.mid" 15 "viceuk.mid" 16 "wallstsm.mid" [playlist] number = 1 name = "PSS-790 Midi Files" directory = "/usr/local/share/seq66-0.99/midi/PSS-790/" 0 "ancestor.mid" 10 "carptsun.mid" 20 "cbflite.mid" 30 "old_love.mid" [playlist] number = 3 name = "Live vs Song Files" directory = "/usr/local/share/seq66-0.99/midi/" 1 "Peter_Gunn-reconstructed.midi" 2 "Chameleon-HHancock-Ov.midi" 3 "Kraftwerk-Europe_Endless-reconstructed.midi" 4 "If_You_Could_Read_My_Mind.mid" 5 "b4uacuse-gm-patchless.midi" # End of /home/ahlstrom/.config/seq66/ca_midi.playlist # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/samples/dark-gradient.qss ================================================ /* * \file flat.qss * \library seq66 application * \author Chris Ahlstrom * \date 2021-05-10 * \updates 2021-05-10 * \license Public domain * * Provides a sample Qt style sheet for Seq66. See * https://doc.qt.io/archives/qt-4.8/stylesheet-examples.html and * https://doc.qt.io/qt-5/stylesheet-reference.html. */ QTabBar::tab { background: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 #404040, stop: 0.4 #202020, stop: 0.5 #404040, stop: 1.0 #303030); border: 2px solid #C4C4C3; border-bottom-color: #6495ED; /* same as the pane color */ border-top-left-radius: 4px; border-top-right-radius: 4px; min-width: 8ex; padding: 2px; } /* * Style the tab using the tab sub-control. Note that it reads QTabBar _not_ * QTabWidget */ QTabBar::tab:selected, QTabBar::tab:hover { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #808080, stop: 0.4 #404040, stop: 0.5 #404040, stop: 1.0 #808080); } QTabBar::tab:selected { border-color: #9B9B9B; border-bottom-color: #6495ED; /* same as pane color */ } QPushButton { color: black; font: 12px; background: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 #404040, stop: 0.4 #202020, stop: 0.5 #404040, stop: 1.0 #303030); border-style: ridge; border-width: 2px; border-radius: 4px; border-color: beige; min-width: 2em; padding: 2px; } QPushButton:checked { color: yellow; font: bold 12px; background-color: grey; border-style: ridge; border-width: 2px; border-radius: 4px; border-color: beige; min-width: 2em; padding: 2px; } /* * vim: sw=4 ts=4 wm=4 et ft=css */ ================================================ FILE: data/samples/flat-rounded.qss ================================================ /* * \file flat.qss * \library seq66 application * \author Chris Ahlstrom * \date 2021-05-10 * \updates 2025-05-16 * \license Public domain * * Provides a sample Qt style sheet for Seq66. See * https://doc.qt.io/archives/qt-4.8/stylesheet-examples.html and * https://doc.qt.io/qt-5/stylesheet-reference.html. * * min-width: 20px; */ QPushButton { color: black; font: 12px; background-color: gray; border-style: solid; border-width: 2px; border-radius: 12; border-color: black; padding: 1px; } QPushButton:checked { color: yellow; font: 12px; background-color: white; border-style: solid; border-width: 2px; border-radius: 12; border-color: black; padding: 1px; } /* * vim: sw=4 ts=4 wm=4 et ft=css */ ================================================ FILE: data/samples/green.palette ================================================ # Seq66 0.99.22 (and above) palette configuration file # # /home/user/.config/seq66/perstfic-66.palette # Written on 2025-10-24 10:30:00 # # This file can be used to change the colors used by patterns # and in some parts of the user-interface. It must be active and # specified in the 'rc' file. [Seq66] config-type = "palette" version = 1 # The [comments] section can document this file. Lines starting with # '#', '[', or that have no characters end the comment. [comments] This palette is meant to tweak the normal palette for the perstfic-66.qss style-sheet. The main thing is to make the drawn text white. We created an additional "Text" ui-palette entry to replace "Foreground" for text. # Set 'dark-theme' to true if the matching style-sheet is dark. This # also set the 'dark-theme' setting in the 'usr' file. # Set 'dark-ui' to true if the palette background elements are dark. [hints] dark-theme = false dark-ui = false # [palette] affects the pattern colors selected (by number). First is # the color number, 0 to 31. Next is the name of the background color. # The first stanza [square brackets] are the background ARGB values. # The second provides the foreground color name and ARGB values. The # alpha values should be set to FF. [palette] 0 "Black" [ 0xFF000000 ] "White" [ 0xFFFFFFFF ] 1 "Red" [ 0xFFFF0000 ] "White" [ 0xFFFFFFFF ] 2 "Green" [ 0xFF008000 ] "White" [ 0xFFFFFFFF ] 3 "Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 4 "Blue" [ 0xFF0000FF ] "White" [ 0xFFFFFFFF ] 5 "Magenta" [ 0xFFFF00FF ] "White" [ 0xFFFFFFFF ] 6 "Cyan" [ 0xFF00FFFF ] "Black" [ 0xFF000000 ] 7 "White" [ 0xFFFFFFFF ] "Black" [ 0xFF000000 ] 8 "Dark Black" [ 0xFF2F4F4F ] "White" [ 0xFFFFFFFF ] 9 "Dark Red" [ 0xFF8B0000 ] "White" [ 0xFFFFFFFF ] 10 "Dark Green" [ 0xFF006400 ] "White" [ 0xFFFFFFFF ] 11 "Dark Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 12 "Dark Blue" [ 0xFF00008B ] "White" [ 0xFFFFFFFF ] 13 "Dark Magenta" [ 0xFF8B008B ] "White" [ 0xFFFFFFFF ] 14 "Dark Cyan" [ 0xFF008B8B ] "White" [ 0xFFFFFFFF ] 15 "Dark White" [ 0xFF808080 ] "White" [ 0xFFFFFFFF ] 16 "Orange" [ 0xFFFFA500 ] "White" [ 0xFFFFFFFF ] 17 "Pink" [ 0xFFFFC0CB ] "Black" [ 0xFF000000 ] 18 "Pale Green" [ 0xFF98FB98 ] "Black" [ 0xFF000000 ] 19 "Khaki" [ 0xFFF0E68C ] "Black" [ 0xFF000000 ] 20 "Light Blue" [ 0xFFADD8E6 ] "Black" [ 0xFF000000 ] 21 "Light Magenta" [ 0xFFEE82EE ] "Black" [ 0xFF000000 ] 22 "Turquoise" [ 0xFF40E0D0 ] "Black" [ 0xFF000000 ] 23 "Grey" [ 0xFF808080 ] "Black" [ 0xFF000000 ] 24 "Dk Orange" [ 0xFFFF8C00 ] "Black" [ 0xFF000000 ] 25 "Dark Pink" [ 0xFFFF1493 ] "Black" [ 0xFF000000 ] 26 "Sea Green" [ 0xFF2E8B57 ] "Black" [ 0xFF000000 ] 27 "Dark Khaki" [ 0xFFBDB76B ] "Black" [ 0xFF000000 ] 28 "Dark Slate Blue" [ 0xFF483D8B ] "Black" [ 0xFF000000 ] 29 "Dark Violet" [ 0xFF9400D3 ] "Black" [ 0xFF000000 ] 30 "Light Grey" [ 0xFFC0C0C0 ] "Black" [ 0xFF000000 ] 31 "Dark Grey" [ 0xFF484848 ] "Black" [ 0xFF000000 ] # Similar to the [palette] section, but applies to the custom-drawn # piano rolls and the --inverse option. The first value is the color # number, from 0 to 23. The names are feature names, not color names. # The second column block is the inverse color. [ui-palette] 0 "Foreground" [ 0xFF000000 ] "Foreground" [ 0xFFFFFFFF ] 1 "Background" [ 0xFF08B276 ] "Background" [ 0xFF484848 ] 2 "Label" [ 0xDEADBEEF ] "Label" [ 0xFFFFFFFF ] 3 "Selection" [ 0xFFFFA500 ] "Selection" [ 0xFFFF00A5 ] 4 "Drum" [ 0xFFFF0000 ] "Drum" [ 0xFF000080 ] 5 "Tempo" [ 0xFFFFFF00 ] "Tempo" [ 0xFFFFFF00 ] 6 "Note Fill" [ 0xFF80FFFF ] "Note Fill" [ 0xFF000000 ] 7 "Note Border" [ 0xFF000000 ] "Note Border" [ 0xFFFFFFFF ] 8 "Black Keys" [ 0xFF000000 ] "Black Keys" [ 0xFFFFFFFF ] 9 "White Keys" [ 0xFFFFFFFF ] "White Keys" [ 0xFF000000 ] 10 "Progress Bar" [ 0xFFFF0000 ] "Progress Bar" [ 0xFFFF0000 ] 11 "Back Pattern" [ 0xFF008B8B ] "Back Pattern" [ 0xFF008B8B ] 12 "Medium Line" [ 0xFF202020 ] "Medium Line" [ 0xFF808080 ] 13 "Heavy Line" [ 0xFF101010 ] "Heavy Line" [ 0xFFFFFFFF ] 14 "Light Line" [ 0xFF404040 ] "Light Line" [ 0xFFC0C0C0 ] 15 "Beat" [ 0xFF000000 ] "Beat" [ 0xFFFFFFFF ] 16 "Near" [ 0xFFFFFF00 ] "Near" [ 0xFFFF00FF ] 17 "Time Brush" [ 0xFF0AC0A0 ] "Time Brush" [ 0xFF808080 ] 18 "Data Brush" [ 0xFF0AC0A0 ] "Data Brush" [ 0xFF808080 ] 19 "Event Brush" [ 0xFF0AC0A0 ] "Event Brush" [ 0xFF808080 ] 20 "Keys Brush" [ 0xFF0AC0A0 ] "Keys Brush" [ 0xFF0AC0A0 ] 21 "Names Brush" [ 0xFF0AC0A0 ] "Names Brush" [ 0xFF808080 ] 22 "Octave Line" [ 0xFF00FFFF ] "Octave Line" [ 0xFFFFFFFF ] 23 "Text" [ 0xFFFFFFFF ] "Text" [ 0xFF000000 ] 24 "Time Text" [ 0xFF000000 ] "Time Text" [ 0xFFFFFFFF ] 25 "Data Text" [ 0xFF000000 ] "Data Text" [ 0xFFFFFFFF ] 26 "Note Event" [ 0xFF80FFFF ] "Note Event" [ 0xFFFFFFFF ] 27 "Keys Text" [ 0xFF000000 ] "Keys Text" [ 0xFFFFFFFF ] 28 "Names Text" [ 0xFF000000 ] "Names Text" [ 0xFFFFFFFF ] 29 "Slots Text" [ 0xFF00FFFF ] "Slots Text" [ 0xFFFFFFFF ] 30 "Extra 1" [ 0xFF000000 ] "Extra 1" [ 0xFFFFFFFF ] 31 "Extra 2" [ 0xFF000000 ] "Extra 2" [ 0xFFFFFFFF ] # This section defines brush styles to use. The names are based on the # names in the Qt::BrushStyle enumeration. The names are: # # nobrush, solid, dense1, dense2, dense3, dense4, dense5, dense6, # dense7, horizontal, vertical, cross, bdiag, fdiag, diagcross, # lineargradient, radialgradient, and conicalgradient. # # For 'empty', best to just use 'solid' (try others and see why). # For 'note', use 'solid' or the default, 'lineargradient'. These # also apply to the progress box and triggers. [brushes] empty = solid note = lineargradient scale = dense3 backseq = dense2 chord = bdiag # This section defines pen styles to use for vertical time lines. # The names are based on the names in the Qt::PenStyle enumeration. # They are: # nopen, solid, dash, dot, dashdot, dashdotdot, customdash (which # is currently not supported). # # 'measure' and 'beat' are obvious. 'fourth' is a line for the 1/4s of # a beat, and 'step' is the smallest unit (normally 6 pixels). [pens] measure = solid beat = solid fourth = dashdot step = dot # End of /home/user/.config/seq66/qseq66.palette # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/samples/green.qss ================================================ /* Copyright (c) DevSec Studio. All rights reserved. \file incrypt-66.qss \library seq66 application \author Chris Ahlstrom \date 2023-10-18 \updates 2023-10-26 \license MIT MIT License 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. This file and license is based on Incrypt.qss; it has been updated to work better with Seq66. */ QWidget { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(3, 76, 50, 255),stop:0.293269 rgba(6, 125, 82, 255),stop:0.514423 rgba(8, 178, 117, 255),stop:0.745192 rgba(7, 164, 108, 255),stop:1 rgba(3, 77, 51, 255)); color: #ffffff; } /* Additions */ QTabBar::tab { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(3, 76, 50, 255),stop:0.293269 rgba(6, 125, 82, 255),stop:0.514423 rgba(8, 178, 117, 255),stop:0.745192 rgba(7, 164, 108, 255),stop:1 rgba(3, 77, 51, 255)); color: #FFFFFF; font-size: 11pt; font-weight: normal; width: 80px; border: 1px solid #444; border-bottom-style: none; border-top-style: none; padding-top: 3px; padding-bottom: 2px; margin-right: -1px; } QTabBar::tab:selected { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(0, 171, 66, 255),stop:1 rgba(0, 153, 255, 255)); color: #000000; margin-bottom: 0px; } QMenuBar { } /* Affects the top-level menu. */ QMenuBar::item { color: #FFFFFF; padding: 1px 3px; background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(3, 76, 50, 255),stop:0.293269 rgba(6, 125, 82, 255),stop:0.514423 rgba(8, 178, 117, 255),stop:0.745192 rgba(7, 164, 108, 255),stop:1 rgba(3, 77, 51, 255)); } QMenuBar::item:selected { background-color:#0080ff; color: #ffffff; } QMenu { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(3, 76, 50, 255),stop:0.293269 rgba(6, 125, 82, 255),stop:0.514423 rgba(8, 178, 117, 255),stop:0.745192 rgba(7, 164, 108, 255),stop:1 rgba(3, 77, 51, 255)); } /* Affects the entries under the top-level menus. */ QMenu::item { border-style: solid; border-top-color: transparent; border-right-color: transparent; border-left-color: transparent; border-bottom-color: transparent; border-bottom-width: 1px; border-style: solid; color: #ffffff; padding-left:17px; padding-top:4px; padding-bottom:4px; padding-right:7px; /*background-color: #008080;*/ } QMenu::item:selected { border-style: solid; border-top-color: transparent; border-right-color: transparent; border-left-color: #04b97f; border-bottom-color: transparent; border-left-width: 2px; color: #00ffff; padding-left:15px; padding-top:4px; padding-bottom:4px; padding-right:7px; background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(3, 76, 50, 255),stop:0.293269 rgba(6, 125, 82, 255),stop:0.514423 rgba(8, 178, 117, 255),stop:0.745192 rgba(7, 164, 108, 255),stop:1 rgba(3, 77, 51, 255)); } QToolTip { color: #ffffff; background-color: #000000; border: 0px; } /* QTextEdit { border-width: 1px; border-radius: 4px; border-color: rgb(58, 58, 58); border-style: inset; padding: 0 8px; color: #a9b7c6; background:#008080; selection-background-color:#007b50; selection-color: #FFFFFF; } QPlainTextEdit { background-color:#008080; border-width: 1px; border-color: rgb(58, 58, 58); border-style: solid; color: #a9b7c6; selection-background-color:#007b50; } */ /* End of Additions */ /*-----QLabel-----*/ QLabel { background-color: transparent; color: #fff; } /*-----QPushButton-----*/ QPushButton { background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(42, 113, 95, 255),stop:1 rgba(12, 97,53, 255)); color: #fff; border: 0px; border-radius: 5px; padding: 5px; } QPushButton::hover { background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(167, 214, 50, 255),stop:1 rgba(98, 169, 67, 255)); color: #fff; } QPushButton::pressed { background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(147, 194, 30, 255),stop:1 rgba(78, 149, 47, 255)); color: #fff; border: 0px; border-radius: 5px; padding: 5px; } /*-----QLineEdit----- */ QLineEdit { background-color: #0aa0c0; color: #000000; border-style: solid; border-color: #141414; border-radius: 5px; padding-left: 10px; } QLineEdit:read-only { background-color: #0880a0; color: #000000; border-style: solid; border-color: #141414; border-radius: 5px; padding-left: 10px; } /*-----QListView-----*/ QListView { background-color: transparent; font-size: 11pt; border: none; color: #fff; show-decoration-selected: 0; padding-left: px; } QListView::item:selected { color: #fff; font-weight: bold; background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(147, 194, 30, 255),stop:1 rgba(78, 149, 47, 255)); border: none; border-radius: 0px; } QListView::item:!selected{ color: #fff; background-color: transparent; border: none; border-radius: 0px; } QListView::item:!selected:hover{ color: #fff; background-color: #0c3561; border: none; border-radius: 0px; } /*-----QScrollBar-----*/ QScrollBar:vertical { border: none; width: 10px; } QScrollBar::handle:vertical { border: none; border-radius : 0px; background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(167, 214, 50, 255),stop:1 rgba(98, 169, 67, 255)); min-height: 50px; width : 16px; } QScrollBar::handle:vertical:pressed { background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(147, 194, 30, 255),stop:1 rgba(78, 149, 47, 255)); } QScrollBar::add-line:vertical { border: none; background: transparent; height: 0px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::add-line:vertical:hover { background-color: transparent; } QScrollBar::add-line:vertical:pressed { background-color: #3f3f3f; } QScrollBar::sub-line:vertical { border: none; background: transparent; height: 0px; } QScrollBar::sub-line:vertical:hover { background-color: transparent; } QScrollBar::sub-line:vertical:pressed { background-color: #3f3f3f; } QScrollBar::up-arrow:vertical { width: 0px; height: 0px; background: transparent; } QScrollBar::down-arrow:vertical { width: 0px; height: 0px; background: transparent; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background-color: #0c2669; width: 11px;; } /* vim: fileformat=unix ft=css */ ================================================ FILE: data/samples/grey-ghost.qss ================================================ /* * \file grey-ghost.qss * \library seq66 application * \author Chris Ahlstrom * \date 2021-03-22 * \updates 2025-05-16 * \license Public domain * * Provides a sample Qt style sheet for Seq66. See * https://doc.qt.io/archives/qt-4.8/stylesheet-examples.html and * https://doc.qt.io/qt-5/stylesheet-reference.html. */ QTabWidget::pane /* the tab widget frame */ { border-top: 2px solid #6495ED; } QTabWidget::tab-bar /* move to the right by 5px */ { left: 5px; } /* * Style the tab using the tab sub-control. Note that it reads QTabBar _not_ * QTabWidget */ QTabBar::tab { color: black; font: bold 14px; background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #E1E1E1, stop: 0.4 #DDDDDD, stop: 0.5 #D8D8D8, stop: 1.0 #D3D3D3); border: 2px solid #C4C4C3; border-bottom-color: #6495ED; /* same as the pane color */ border-top-left-radius: 4px; border-top-right-radius: 4px; min-width: 8ex; padding: 2px; } QTabBar::tab:selected, QTabBar::tab:hover { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #fafafa, stop: 0.4 #f4f4f4, stop: 0.5 #e7e7e7, stop: 1.0 #fafafa); } QTabBar::tab:selected { border-color: #9B9B9B; border-bottom-color: #6495ED; /* same as pane color */ } /* * Make tabs extra tall for use with a touch-screen. */ QTabBar::tab { height: 40px; width: 80px; } QLineEdit { background-color: cornflowerblue } QDoubleSpinBox { background-color: cornflowerblue } QSpinBox { background-color: cornflowerblue } QComboBox { background-color: cornflowerblue } QPushButton { color: black; font: bold 12px; background-color: darkgray; border-style: ridge; border-width: 2px; border-radius: 4px; border-color: beige; } QPushButton:checked { color: yellow; font: bold 12px; background-color: white; border-style: ridge; border-width: 2px; border-radius: 4px; border-color: beige; } /* * vim: sw=4 ts=4 wm=4 et ft=css */ ================================================ FILE: data/samples/incrypt-66.palette ================================================ # Seq66 0.99.22 (and above) palette configuration file # # /home/user/.config/seq66/incrypt-66.palette # Written on 2025-04-15 # # This file can be used to change the colors used by patterns # and in some parts of the user-interface. It must be active and # specified in the 'rc' file. [Seq66] config-type = "palette" version = 1 # The [comments] section can document this file. Lines starting with # '#', '[', or that have no characters end the comment. [comments] This palette reverses the ui-palette colors, effectively making --inverse the default. This is meant for dark themes or the use of the incrypt-66.qss style-sheet. # Set 'dark-theme' to true if the matching style-sheet is dark. This # also set the 'dark-theme' setting in the 'usr' file. # Set 'dark-ui' to true if the palette background elements are dark. [hints] dark-theme = true dark-ui = true # [palette] affects the pattern colors selected (by number). First is # the color number, 0 to 31. Next is the name of the background color. # The first stanza [square brackets] are the background ARGB values. # The second provides the foreground color name and ARGB values. The # alpha values should be set to FF. [palette] 0 "Black" [ 0xFF000000 ] "White" [ 0xFFFFFFFF ] 1 "Red" [ 0xFFFF0000 ] "White" [ 0xFFFFFFFF ] 2 "Green" [ 0xFF008000 ] "White" [ 0xFFFFFFFF ] 3 "Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 4 "Blue" [ 0xFF0000FF ] "White" [ 0xFFFFFFFF ] 5 "Magenta" [ 0xFFFF00FF ] "White" [ 0xFFFFFFFF ] 6 "Cyan" [ 0xFF00FFFF ] "Black" [ 0xFF000000 ] 7 "White" [ 0xFFFFFFFF ] "Black" [ 0xFF000000 ] 8 "Dark Black" [ 0xFF2F4F4F ] "White" [ 0xFFFFFFFF ] 9 "Dark Red" [ 0xFF8B0000 ] "White" [ 0xFFFFFFFF ] 10 "Dark Green" [ 0xFF006400 ] "White" [ 0xFFFFFFFF ] 11 "Dark Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 12 "Dark Blue" [ 0xFF00008B ] "White" [ 0xFFFFFFFF ] 13 "Dark Magenta" [ 0xFF8B008B ] "White" [ 0xFFFFFFFF ] 14 "Dark Cyan" [ 0xFF008B8B ] "White" [ 0xFFFFFFFF ] 15 "Dark White" [ 0xFF808080 ] "White" [ 0xFFFFFFFF ] 16 "Orange" [ 0xFFFFA500 ] "White" [ 0xFFFFFFFF ] 17 "Pink" [ 0xFFFFC0CB ] "Black" [ 0xFF000000 ] 18 "Pale Green" [ 0xFF98FB98 ] "Black" [ 0xFF000000 ] 19 "Khaki" [ 0xFFF0E68C ] "Black" [ 0xFF000000 ] 20 "Light Blue" [ 0xFFADD8E6 ] "Black" [ 0xFF000000 ] 21 "Light Magenta" [ 0xFFEE82EE ] "Black" [ 0xFF000000 ] 22 "Turquoise" [ 0xFF40E0D0 ] "Black" [ 0xFF000000 ] 23 "Grey" [ 0xFF808080 ] "Black" [ 0xFF000000 ] 24 "Dk Orange" [ 0xFFFF8C00 ] "Black" [ 0xFF000000 ] 25 "Dark Pink" [ 0xFFFF1493 ] "Black" [ 0xFF000000 ] 26 "Sea Green" [ 0xFF2E8B57 ] "Black" [ 0xFF000000 ] 27 "Dark Khaki" [ 0xFFBDB76B ] "Black" [ 0xFF000000 ] 28 "Dark Slate Blue" [ 0xFF483D8B ] "Black" [ 0xFF000000 ] 29 "Dark Violet" [ 0xFF9400D3 ] "Black" [ 0xFF000000 ] 30 "Light Grey" [ 0xFFC0C0C0 ] "Black" [ 0xFF000000 ] 31 "Dark Grey" [ 0xFF484848 ] "Black" [ 0xFF000000 ] # Similar to the [palette] section, but applies to the custom-drawn # piano rolls and the --inverse option. The first value is the color # number, from 0 to 31. The names are feature names, not color names. # The second column block is the inverse color. [ui-palette] 0 "Foreground" [ 0xFF000000 ] "Foreground" [ 0xFFFFFFFF ] 1 "Background" [ 0xFF3C2010 ] "Background" [ 0xFF484848 ] 2 "Label" [ 0xFFADBEEF ] "Label" [ 0xFFFFFFFF ] 3 "Selection" [ 0xFFFFA500 ] "Selection" [ 0xFFFF00A5 ] 4 "Drum" [ 0xFFFF0000 ] "Drum" [ 0xFF000080 ] 5 "Tempo" [ 0xFFFFFF00 ] "Tempo" [ 0xFFFFFF00 ] 6 "Note Fill" [ 0xFFD0B0A0 ] "Note Fill" [ 0xFF000000 ] 7 "Note Border" [ 0xFF000000 ] "Note Border" [ 0xFFFFFFFF ] 8 "Black Keys" [ 0xFF000000 ] "Black Keys" [ 0xFFFFFFFF ] 9 "White Keys" [ 0xFFFFFFFF ] "White Keys" [ 0xFF000000 ] 10 "Progress Bar" [ 0xFFFF0000 ] "Progress Bar" [ 0xFFFF0000 ] 11 "Back Pattern" [ 0xFF008B8B ] "Back Pattern" [ 0xFF008B8B ] 12 "Medium Line" [ 0xFF808080 ] "Medium Line" [ 0xFF808080 ] 13 "Beat Line" [ 0xFF484848 ] "Beat Line" [ 0xFFFFFFFF ] 14 "Step Line" [ 0xFF008000 ] "Step Line" [ 0xFFC0C0C0 ] 15 "Beat" [ 0xFF00FF00 ] "Beat" [ 0xFF000064 ] 16 "Near" [ 0xFFFFFF00 ] "Near" [ 0xFFFF00FF ] 17 "Time Brush" [ 0xFFB09080 ] "Time Brush" [ 0xFF808080 ] 18 "Data Brush" [ 0xFFA08070 ] "Data Brush" [ 0xFF808080 ] 19 "Event Brush" [ 0xFFA08070 ] "Event Brush" [ 0xFF808080 ] 20 "Keys Brush" [ 0xFFA08070 ] "Keys Brush" [ 0xFF808080 ] 21 "Names Brush" [ 0xFFA08070 ] "Names Brush" [ 0xFF808080 ] 22 "Octave Line" [ 0xFF808080 ] "Octave Line" [ 0xFF808080 ] 23 "Text" [ 0xFF808080 ] "Extra 3" [ 0xFF808080 ] 24 "Time Text" [ 0xFFFFFFFF ] "Time Text" [ 0xFFFFFFFF ] 25 "Data Text" [ 0xFFFFFFFF ] "Data Text" [ 0xFFFFFFFF ] 26 "Note Event" [ 0xFFD0B0A0 ] "Note Event" [ 0xFFFFFFFF ] 27 "Keys Text" [ 0xFFFFFFFF ] "Keys Text" [ 0xFFFFFFFF ] 28 "Names Text" [ 0xFFFFA000 ] "Names Text" [ 0xFFFFFFFF ] 29 "Slots Text" [ 0xFFFFA000 ] "Slots Text" [ 0xFFFFFFFF ] 30 "Scale Brush" [ 0xFF000000 ] "Scale Brush" [ 0xFFFFFFFF ] 31 "Extra" [ 0xFF000000 ] "Extra" [ 0xFFFFFFFF ] # This section defines brush styles to use. The names are based on the # names in the Qt::BrushStyle enumeration. The names are: # # nobrush, solid, dense1, dense2, dense3, dense4, dense5, dense6, # dense7, horizontal, vertical, cross, bdiag, fdiag, diagcross, # lineargradient, radialgradient, and conicalgradient. # # For 'empty', best to just use 'solid' (try others and see why). # For 'note', use 'solid' or the default, 'lineargradient'. These # also apply to the progress box and triggers. [brushes] empty = solid note = lineargradient scale = dense3 backseq = dense2 chord = bdiag # This section defines pen styles to use for vertical time lines. # The names are based on the names in the Qt::PenStyle enumeration. # They are: # nopen, solid, dash, dot, dashdot, dashdotdot, customdash (which # is currently not supported). # # 'measure' and 'beat' are obvious. 'fourth' is a line for the 1/4s of # a beat, and 'step' is the smallest unit (normally 6 pixels). [pens] measure = solid beat = solid fourth = dashdot step = dot # End of /home/user/.config/seq66/incrypt-66.palette # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/samples/incrypt-66.qss ================================================ /* Copyright (c) DevSec Studio. All rights reserved. \file incrypt-66.qss \library seq66 application \author Chris Ahlstrom \date 2023-10-18 \updates 2023-10-21 \license MIT MIT License 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. This file and license is based on Incrypt.qss; it has been updated to work better with Seq66. QComboBox::down-arrow { image: url(/usr/share/icons/crystalsvg/16x16/actions/1downarrow.png); } */ QWidget { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:.4 #343434); color: #ffa000; border-color: #000000; } /* Additions */ QMainWindow { background-color: #212121; } QDialog { background-color: #212121; } QTextEdit { border-width: 1px; border-radius: 4px; border-color: rgb(58, 58, 58); border-style: inset; padding: 0 8px; color: #a9b7c6; background:#1e1d23; selection-background-color:#007b50; selection-color: #FFFFFF; } QPlainTextEdit { selection-background-color:#007b50; background-color:#1e1d23; border-style: solid; border-top-color: transparent; border-right-color: transparent; border-left-color: transparent; border-bottom-color: transparent; border-width: 1px; color: #a9b7c6; } QLineEdit { border-width: 1px; border-radius: 4px; border-color: rgb(58, 58, 58); border-style: inset; padding: 0 8px; color: #a9b7c6; background:#1e1d23; selection-background-color:#007b50; selection-color: #FFFFFF; } QPushButton { border-style: solid; border-top-color: transparent; border-right-color: transparent; border-left-color: transparent; border-bottom-color: transparent; border-width: 1px; border-style: solid; color: #a9b7c6; padding: 2px; background-color: #1e1d23; } QPushButton::default { border-style: inset; border-top-color: transparent; border-right-color: transparent; border-left-color: transparent; border-bottom-color: #04b97f; border-width: 1px; color: #a9b7c6; padding: 2px; background-color: #1e1d23; } QPushButton:hover { border-style: solid; border-top-color: transparent; border-right-color: transparent; border-left-color: transparent; border-bottom-color: #37efba; border-bottom-width: 1px; border-style: solid; color: #FFFFFF; padding-bottom: 2px; background-color: #1e1d23; } QPushButton:pressed { border-style: solid; border-top-color: transparent; border-right-color: transparent; border-left-color: transparent; border-bottom-color: #37efba; border-bottom-width: 2px; border-style: solid; color: #37efba; padding-bottom: 1px; background-color: #1e1d23; } QPushButton:disabled { border-style: solid; border-top-color: transparent; border-right-color: transparent; border-left-color: transparent; border-bottom-color: #808086; border-bottom-width: 2px; border-style: solid; color: #808086; padding-bottom: 1px; background-color: #1e1d23; } QMenuBar { } /* Affects the top-level menu. */ QMenuBar::item { color: #a9b7c6; padding: 1px 4px; background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:.4 #343434); } QMenuBar::item:selected { background:#1e1d23; color: #FFFFFF; } QMenu { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:.4 #343434); } QMenu::item:selected { border-style: solid; border-top-color: transparent; border-right-color: transparent; border-left-color: #04b97f; border-bottom-color: transparent; border-left-width: 2px; color: #FFFFFF; padding-left:15px; padding-top:4px; padding-bottom:4px; padding-right:7px; background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:.4 #343434); } QMenu::item { border-style: solid; border-top-color: transparent; border-right-color: transparent; border-left-color: transparent; border-bottom-color: transparent; border-bottom-width: 1px; border-style: solid; color: #a9b7c6; padding-left:17px; padding-top:4px; padding-bottom:4px; padding-right:7px; background-color: #1e1d23; } QComboBox { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:.4 #343434); color: #ffa000; padding: 1px 23px 1px 3px; border: 1px solid #a08070; border-radius: 3px; min-width: 64px; max-width: 108px; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 20px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; } QComboBox::down-arrow { image: url(:/images/combobox-arrow.png); } QComboBox QAbstractView { background-color: #4f4f4f; color: #999999; selection-background-color: #999999; selection-color: #4f4f4f; } QToolTip { color: #ffffff; background-color: #000000; border: 0px; } /* End of Additions */ QLabel { background-color: transparent; color: #fff; font-size: 11pt; border-color: #000000; } /*-----QToolButton-----*/ QToolButton { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(254, 171, 66, 255),stop:1 rgba(255, 153, 0, 255)); color: #000000; font-size: 11pt; border-radius: 4px; padding: 10px; } QToolButton::pressed { background-color: #6b960f; } /*-----QTabWidget-----*/ QTabBar::tab { color: #ffa000; font-size: 11pt; font-weight: bold; width: 80px; border: 1px solid #444; border-bottom-style: none; border-top-style: none; background-color: #323232; padding-top: 3px; padding-bottom: 2px; margin-right: -1px; } QTabWidget::pane { border: 1px solid qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(254, 171, 66, 255),stop:1 rgba(255, 153, 0, 255)); top: 1px; } QTabBar::tab:last { margin-right: 0; } QTabBar::tab:first:!selected { margin-left: 0px; } QTabBar::tab:!selected { color: #ffa000; border-bottom-style: solid; margin-top: 3px; background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:.4 #343434); } QTabBar::tab:selected { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(254, 171, 66, 255),stop:1 rgba(255, 153, 0, 255)); color: #000000; margin-bottom: 0px; } QTabBar::tab:!selected:hover { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:0.4 #343434, stop:0.2 #343434, stop:0.1 #ffaa00); } /* Additions from DevSec's Cstartpage.qss */ /*-----QCheckBox-----*/ QCheckBox { /*background-color: transparent;*/ color: #ffa000; font-weight: normal; padding: 2px; } QCheckBox::indicator { /*color: #ffa000;*/ /*background-color: #000000;*/ border: 1px solid #41cd52; width: 10px; height: 10px; } QCheckBox::indicator:checked { /*image:url("./resources/check.png"); To replace*/ background-color: #c0c0c0; border: 1px solid #41cd52; } QCheckBox::indicator:unchecked:hover { border: 1px solid #41cd52; } QCheckBox::disabled { color: #656565; } QCheckBox::indicator:disabled { background-color: #656565; color: #656565; border: 1px solid #656565; } QSpinBox { color: #ffa000; background-color: #403030; border: 1px solid #000000; } QDoubleSpinBox { color: #ffa000; background-color: #403030; border: 1px solid #000000; } /*-----QListView-----*/ QListView { border : none; background-color: #343434; color: white; show-decoration-selected: 1; /* make selection span entire width of view */ outline: 0; border: 1px solid gray; } QListView::disabled { background-color: #656565; color: #1b1b1b; border: 1px solid #656565; } QListView::item { padding: 1px; } QListView::item:alternate { background-color: #4a4b4d; } QListView::item:selected { border: 1px solid #6a6ea9; border: none; color: rgb(0, 0, 0); } QListView::item:selected:!active { background-color: #41cd52; border: none; color: rgb(0, 0, 0); } QListView::item:selected:active { background-color: #41cd52; border: none; color: rgb(0, 0, 0); } QListView::item:hover { background-color: #262626; border: none; color: white; } /*-----QScrollBar-----*/ QScrollBar:horizontal { border: 1px solid #222222; background-color: #3d3d3d; height: 15px; margin: 0px 16px 0 16px; } QScrollBar::handle:horizontal { background-color: #41cd52; min-height: 20px; } QScrollBar::add-line:horizontal { border: 1px solid #1b1b19; background-color: #41cd52; width: 14px; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal { border: 1px solid #1b1b19; background-color: #41cd52; width: 14px; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar::right-arrow:horizontal { image: url(://arrow-right.png); width: 6px; height: 6px; } QScrollBar::left-arrow:horizontal { image: url(://arrow-left.png); width: 6px; height: 6px; } QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: none; } QScrollBar:vertical { background-color: #3d3d3d; width: 13px; margin: 16px 0 16px 0; border: 1px solid #222222; } QScrollBar::handle:vertical { background-color: #41cd52; min-height: 20px; } QScrollBar::add-line:vertical { border: 1px solid #1b1b19; background-color: #41cd52; height: 14px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::sub-line:vertical { border: 1px solid #41cd52; background-color: #41cd52; height: 14px; subcontrol-position: top; subcontrol-origin: margin; } /* Seems to yield empty boxes. QScrollBar::up-arrow:vertical { image: url(://arrow-up.png); width: 6px; height: 6px; } QScrollBar::down-arrow:vertical { image: url(://arrow-down.png); width: 6px; height: 6px; } */ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background-color: none; } /* vim: fileformat=unix ft=css */ ================================================ FILE: data/samples/monogreen.palette ================================================ # Seq66 0.99.22 (and above) palette configuration file # # /home/ahlstrom/.config/seq66/monogreen.palette # Written on 2025-10-24 07:10:46 # # This file can be used to change the colors used by patterns # and in some parts of the user-interface. It must be active and # specified in the 'rc' file. [Seq66] config-type = "palette" version = 1 # The [comments] section can document this file. Lines starting with # '#', '[', or that have no characters end the comment. [comments] This palette is meant to tweak the normal palette for the monogreen.qss style-sheet. It works well with green-on-black themes and console windows. # Set 'dark-theme' to true if the matching style-sheet is dark. This # also set the 'dark-theme' setting in the 'usr' file. # Set 'dark-ui' to true if the palette background elements are dark. [hints] dark-theme = true dark-ui = true # [palette] affects the pattern colors selected (by number). First is # the color number, 0 to 31. Next is the name of the background color. # The first stanza [square brackets] are the background ARGB values. # The second provides the foreground color name and ARGB values. The # alpha values should be set to FF. [palette] 0 "Black" [ 0xFF000000 ] "White" [ 0xFFFFFFFF ] 1 "Red" [ 0xFFFF0000 ] "White" [ 0xFFFFFFFF ] 2 "Green" [ 0xFF008000 ] "White" [ 0xFFFFFFFF ] 3 "Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 4 "Blue" [ 0xFF0000FF ] "White" [ 0xFFFFFFFF ] 5 "Magenta" [ 0xFFFF00FF ] "White" [ 0xFFFFFFFF ] 6 "Cyan" [ 0xFF00FFFF ] "Black" [ 0xFF000000 ] 7 "White" [ 0xFFFFFFFF ] "Black" [ 0xFF000000 ] 8 "Dark Black" [ 0xFF2F4F4F ] "White" [ 0xFFFFFFFF ] 9 "Dark Red" [ 0xFF8B0000 ] "White" [ 0xFFFFFFFF ] 10 "Dark Green" [ 0xFF006400 ] "White" [ 0xFFFFFFFF ] 11 "Dark Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 12 "Dark Blue" [ 0xFF00008B ] "White" [ 0xFFFFFFFF ] 13 "Dark Magenta" [ 0xFF8B008B ] "White" [ 0xFFFFFFFF ] 14 "Dark Cyan" [ 0xFF008B8B ] "White" [ 0xFFFFFFFF ] 15 "Dark White" [ 0xFF808080 ] "White" [ 0xFFFFFFFF ] 16 "Orange" [ 0xFFFFA500 ] "White" [ 0xFFFFFFFF ] 17 "Pink" [ 0xFFFFC0CB ] "Black" [ 0xFF000000 ] 18 "Pale Green" [ 0xFF98FB98 ] "Black" [ 0xFF000000 ] 19 "Khaki" [ 0xFFF0E68C ] "Black" [ 0xFF000000 ] 20 "Light Blue" [ 0xFFADD8E6 ] "Black" [ 0xFF000000 ] 21 "Light Magenta" [ 0xFFEE82EE ] "Black" [ 0xFF000000 ] 22 "Turquoise" [ 0xFF40E0D0 ] "Black" [ 0xFF000000 ] 23 "Grey" [ 0xFF808080 ] "Black" [ 0xFF000000 ] 24 "Dk Orange" [ 0xFFFF8C00 ] "Black" [ 0xFF000000 ] 25 "Dark Pink" [ 0xFFFF1493 ] "Black" [ 0xFF000000 ] 26 "Sea Green" [ 0xFF2E8B57 ] "Black" [ 0xFF000000 ] 27 "Dark Khaki" [ 0xFFBDB76B ] "Black" [ 0xFF000000 ] 28 "Dark Slate Blue" [ 0xFF483D8B ] "Black" [ 0xFF000000 ] 29 "Dark Violet" [ 0xFF9400D3 ] "Black" [ 0xFF000000 ] 30 "Light Grey" [ 0xFFC0C0C0 ] "Black" [ 0xFF000000 ] 31 "Dark Grey" [ 0xFF484848 ] "Black" [ 0xFF000000 ] # Similar to the [palette] section, but applies to the custom-drawn # piano rolls and the --inverse option. The values: color number (0 # to 31); main color feature name; main color value; inverse color # feature name; and the --inverse color value. [ui-palette] 0 "Foreground" [ 0xFF00FF00 ] "Foreground" [ 0xFFFFFFFF ] 1 "Background" [ 0xFF000000 ] "Background" [ 0xFF0000FF ] 2 "Label" [ 0xFFADBEEF ] "Label" [ 0xFFFFFFFF ] 3 "Selection" [ 0xFFCF8500 ] "Selection" [ 0xFFFFA500 ] 4 "Drum" [ 0xFFFF0000 ] "Drum" [ 0xFF008000 ] 5 "Tempo" [ 0xFFFFFF00 ] "Tempo" [ 0xFFFF00FF ] 6 "Note Fill" [ 0xFF000000 ] "Note Fill" [ 0xFF0000FF ] 7 "Note Border" [ 0xFF00FF00 ] "Note Border" [ 0xFFFFFFFF ] 8 "Black Keys" [ 0xFF00FF00 ] "Black Keys" [ 0xFFFFFFFF ] 9 "White Keys" [ 0xFFFFFFFF ] "White Keys" [ 0xFF0000FF ] 10 "Progress Bar" [ 0xFFFF0000 ] "Progress Bar" [ 0xFFFF0000 ] 11 "Back Pattern" [ 0xFF008B8B ] "Back Pattern" [ 0xFF008B8B ] 12 "Medium Line" [ 0xFF202020 ] "Medium Line" [ 0xFF808080 ] 13 "Heavy Line" [ 0xFF101010 ] "Heavy Line" [ 0xFFFFFFFF ] 14 "Light Line" [ 0xFF404040 ] "Light Line" [ 0xFFC0C0C0 ] 15 "Beat" [ 0xFF00FF00 ] "Beat" [ 0xFFFFFFFF ] 16 "Near" [ 0xFFFFFF00 ] "Near" [ 0xFFFFFF00 ] 17 "Time Brush" [ 0xFF000000 ] "Time Brush" [ 0xFF808080 ] 18 "Data Brush" [ 0xFF000000 ] "Data Brush" [ 0xFF808080 ] 19 "Event Brush" [ 0xFF000000 ] "Event Brush" [ 0xFF808080 ] 20 "Keys Brush" [ 0xFF000000 ] "Keys Brush" [ 0xFF0AA0C0 ] 21 "Names Brush" [ 0xFF000000 ] "Names Brush" [ 0xFF808080 ] 22 "Octave Line" [ 0xFF00FFFF ] "Octave Line" [ 0xFFFFFFFF ] 23 "Text" [ 0xFF00FF00 ] "Text" [ 0xFFFFFF00 ] 24 "Time Text" [ 0xFF00FF00 ] "Time Text" [ 0xFFFFFFFF ] 25 "Data Text" [ 0xFF00FF00 ] "Data Text" [ 0xFFFFFFFF ] 26 "Note Event" [ 0xFF80FFFF ] "Note Event" [ 0xFFFFFFFF ] 27 "Keys Text" [ 0xFF00FF00 ] "Keys Text" [ 0xFFFFFFFF ] 28 "Names Text" [ 0xFF00FF00 ] "Names Text" [ 0xFFFFFFFF ] 29 "Slots Text" [ 0xFF00FFFF ] "Slots Text" [ 0xFFFFFFFF ] 30 "Scale Brush" [ 0xFF808080 ] "Scale Brush" [ 0xFFC0C0C0 ] 31 "Extra" [ 0xFF00C000 ] "Extra" [ 0xFFFFFFFF ] # This section defines brush styles to use. The names are based on the # names in the Qt::BrushStyle enumeration. The names are: # # nobrush, solid, dense1, dense2, dense3, dense4, dense5, dense6, # dense7, horizontal, vertical, cross, bdiag, fdiag, diagcross, # lineargradient, radialgradient, and conicalgradient. # # For 'empty', best to just use 'solid' (try others and see why). # For 'note', use only 'solid' or the default, 'lineargradient'. These # also apply to the progress box and triggers. [brushes] empty = solid note = lineargradient scale = dense3 backseq = dense2 chord = bdiag # This section defines pen styles to use for vertical time lines. # The names are based on the names in the Qt::PenStyle enumeration. # They are: # nopen, solid, dash, dot, dashdot, dashdotdot, customdash (which # is currently not supported). # # 'measure' and 'beat' are obvious. 'fourth' is a line for the 1/4s of # a beat, and 'step' is the smallest unit (normally 6 pixels). [pens] measure = solid beat = solid fourth = dashdot step = dot # End of /home/ahlstrom/.config/seq66/monogreen.palette # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/samples/monogreen.qss ================================================ /* Copyright (c) DevSec Studio. All rights reserved. \file monogreen.qss \library seq66 application \author Chris Ahlstrom \date 2023-10-18 \updates 2024-12-30 \license MIT MIT License 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. This file and license is based on Incrypt.qss; it has been updated to work better with Seq66. */ QWidget { color: #00ff00; background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(0, 0, 0, 255),stop:0.293269 rgba(6, 40, 10, 255),stop:0.514423 rgba(8, 48, 16, 255),stop:0.745192 rgba(7, 54, 14, 255),stop:1 rgba(0, 0, 0, 255)); } QTabBar::tab { color: #00ff00; background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(0, 0, 0, 255),stop:0.293269 rgba(6, 40, 10, 255),stop:0.514423 rgba(8, 48, 16, 255),stop:0.745192 rgba(7, 54, 14, 255),stop:1 rgba(0, 0, 0, 255)); font-size: 11pt; font-weight: normal; width: 80px; border: 1px solid #444; border-bottom-style: none; border-top-style: none; padding-top: 3px; padding-bottom: 2px; margin-right: -1px; } QTabBar::tab:selected { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(0, 171, 66, 255),stop:1 rgba(0, 153, 255, 255)); color: #000000; margin-bottom: 0px; } QComboBox { color: #00ff00; background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(0, 0, 0, 255),stop:0.293269 rgba(6, 40, 10, 255),stop:0.514423 rgba(8, 48, 16, 255),stop:0.745192 rgba(7, 54, 14, 255),stop:1 rgba(0, 0, 0, 255)); selection-background-color: #346792; } /* selection-background-color: #000000; */ QComboBox QAbstractItemView { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(0, 0, 0, 255),stop:1 rgba(0, 48, 56, 255)); color: #00ff00; selection-color: #00ff80; selection-background-color: #346792; } QComboBox QAbstractItemView:hover { background-color: #19232D; color: #DFE1E2; } QComboBox QAbstractItemView:selected { background: #346792; color: #455364; } /* subcontrol-origin: padding; subcontrol-position: top right; */ QComboBox::down-arrow { image: url("/home/ahlstrom/.config/seq66/down.png"); background: #008000; color: #00ff00; border-top-right-radius: 3px; border-bottom-right-radius: 3px; } QComboBox::drop-down { background: #008000; color: #00ff00; border-top-right-radius: 3px; border-bottom-right-radius: 3px; } QComboBox:!editable { background: #008000; color: #00ff00; background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(0, 0, 0, 255),stop:0.293269 rgba(6, 40, 10, 255),stop:0.514423 rgba(8, 48, 16, 255),stop:0.745192 rgba(7, 54, 14, 255),stop:1 rgba(0, 0, 0, 255)); selection-color: #00ff00; selection-background-color: #000000; } QComboBox::indicator { border: none; border-radius: 0; background-color: transparent; selection-background-color: transparent; color: transparent; selection-color: transparent; /* Needed to remove indicator - fix #132 */ } QMenu { color: #00ff00; background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(0, 0, 0, 255),stop:0.293269 rgba(6, 40, 10, 255),stop:0.514423 rgba(8, 48, 16, 255),stop:0.745192 rgba(7, 54, 14, 255),stop:1 rgba(0, 0, 0, 255)); } QMenuBar { color: #00ff00; background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(0, 0, 0, 255),stop:0.293269 rgba(6, 40, 10, 255),stop:0.514423 rgba(8, 48, 16, 255),stop:0.745192 rgba(7, 54, 14, 255),stop:1 rgba(0, 0, 0, 255)); } /* Affects the top-level menu. */ QMenuBar::item { color: #00ff00; padding: 1px 3px; background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(0, 0, 0, 255),stop:0.293269 rgba(6, 40, 10, 255),stop:0.514423 rgba(8, 48, 16, 255),stop:0.745192 rgba(7, 54, 14, 255),stop:1 rgba(0, 0, 0, 255)); } QMenuBar::item:selected { background-color:#0080ff; color: #a0ff00; } /* Affects the entries under the top-level menus. */ QMenu::item { border-style: solid; border-top-color: transparent; border-right-color: transparent; border-left-color: transparent; border-bottom-color: transparent; border-bottom-width: 1px; border-style: solid; color: #00ff00; padding-left:17px; padding-top:4px; padding-bottom:4px; padding-right:7px; } QMenu::item:selected { border-style: solid; border-top-color: transparent; border-right-color: transparent; border-left-color: #04b97f; border-bottom-color: transparent; border-left-width: 2px; color: #00ffff; padding-left:15px; padding-top:4px; padding-bottom:4px; padding-right:7px; background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(3, 76, 50, 255),stop:0.293269 rgba(6, 125, 82, 255),stop:0.514423 rgba(8, 178, 117, 255),stop:0.745192 rgba(7, 164, 108, 255),stop:1 rgba(3, 77, 51, 255)); } QToolTip { color: #00ff00; background-color: #000000; border: 0px; } QLineEdit { background-color: #000000; color: #00ff00; border-style: solid; border-color: #141414; border-radius: 5px; padding-left: 10px; } QLineEdit:read-only { background-color: #404040; color: #00ff00; border-style: solid; border-color: #141414; border-radius: 5px; padding-left: 10px; } QTextEdit { background-color: #000000; color: #00ff00; border-style: solid; border-color: #141414; border-radius: 5px; padding-left: 10px; } QPlainTextEdit { background-color: #000000; color: #00ff00; border-style: solid; border-color: #141414; border-radius: 5px; padding-left: 10px; } QLabel { background-color: #000000; color: #00ff00; } QPushButton { color: #00ff00; background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(0, 0, 0, 255),stop:1 rgba(12, 48, 12, 255)); border: 0px; border-radius: 5px; padding: 5px; } QPushButton::default { color: #00ff00; background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(167, 100, 24, 128),stop:1 rgba(98, 169, 67, 255)); } QPushButton::disabled { color: #00a000; background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(167, 100, 100, 100),stop:1 rgba(98, 170, 170, 170)); } QPushButton::hover { color: #00ff00; background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(167, 214, 50, 255),stop:1 rgba(98, 169, 67, 255)); } QPushButton::pressed { color: #00ff00; background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(147, 194, 30, 255),stop:1 rgba(78, 149, 47, 255)); border: 0px; border-radius: 5px; padding: 5px; } QListView { color: #00ff00; background-color: #000000; font-size: 11pt; border: none; show-decoration-selected: 0; padding-left: px; } QListView::item:selected { color: #00ff00; font-weight: bold; background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(147, 194, 30, 255),stop:1 rgba(78, 149, 47, 255)); border: none; border-radius: 0px; } QListView::item:!selected { color: #00ff00; background-color: #000000; border: none; border-radius: 0px; } QListView::item:!selected:hover { color: #00ff00; background-color: #ffffff; border: none; border-radius: 0px; } QScrollBar:vertical { border: none; width: 10px; } QScrollBar::handle:vertical { border: none; border-radius : 0px; background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(167, 214, 50, 255),stop:1 rgba(98, 169, 67, 255)); min-height: 50px; width : 16px; } QScrollBar::handle:vertical:pressed { background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(147, 194, 30, 255),stop:1 rgba(78, 149, 47, 255)); } QScrollBar::add-line:vertical { border: none; background: transparent; height: 0px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::add-line:vertical:hover { background-color: transparent; } QScrollBar::add-line:vertical:pressed { background-color: #3f3f3f; } QScrollBar::sub-line:vertical { border: none; background: transparent; height: 0px; } QScrollBar::sub-line:vertical:hover { background-color: transparent; } QScrollBar::sub-line:vertical:pressed { background-color: #3f3f3f; } QScrollBar::up-arrow:vertical { width: 0px; height: 0px; background: transparent; } QScrollBar::down-arrow:vertical { width: 0px; height: 0px; background: transparent; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background-color: #0c2669; width: 11px;; } /* vim: fileformat=unix ft=css */ ================================================ FILE: data/samples/nanomap.ctrl ================================================ # Seq66 0.99.9 MIDI control configuration file # # /home/ahlstrom/.config/seq66/nanomap.ctrl # Written 2023-09-03 10:47:46 # # Sets up MIDI I/O control. The format is like the 'rc' file. To use it, set it # active in the 'rc' [midi-control-file] section. It adds loop, mute, & # automation buttons, MIDI display, new settings, and macros. # # This file holds MIDI I/O control setups for Seq66. It follows the format # of the 'rc' configuration file, but is stored separately for flexibility # It is always stored in the main configuration directory. To use this # file, specify it in the [midi-control-file] section in the 'rc' file. # Use the base-name (e.g. nanomap.ctrl). # Version 1 adds the [mute-control-out] and [automation-control-out] # sections. Versions 2 and 3 simplify the data items. [Seq66] config-type = "ctrl" version = 6 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] This file was created by copying the old nanomap.rc file and updating it to the newest .ctrl format. This file is useful mainly in following Section 17.1 of the Seq66 user manual. It has no midi-control-out section. [midi-control-settings] # Input settings to control Seq66. 'control-buss' ranges from 0 to the highest # system input buss. If set, that buss can send MIDI control. 255 (0xFF) means # any ENABLED MIDI input can send control. ALSA has an extra 'announce' buss, # so add 1 to the port number with ALSA. With port-mapping enabled, the port # nick-name can be provided. # # 'midi-enabled' applies to the MIDI controls; keystroke controls are always # enabled. Supported keyboard layouts are 'qwerty' (default), 'qwertz', and # 'azerty'. AZERTY turns off auto-shift for group-learn. # # control-buss = 4 drop-empty-controls = false control-buss = "nanoKEY2 nanoKEY2 _ CTRL" midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 keyboard-layout = qwerty # A control stanza sets key and MIDI control. Keys support 'toggle', and # key-release is 'invert'. The leftmost number on each line is the loop number # (0 to 31), mutes number (same range), or an automation number. 3 groups of # of bracketed numbers follow, each providing a type of control # number. This internal control number is followed by three groups of # bracketed numbers, each providing three different types of control: # # Normal: [toggle] [on] [off] # Increment/Decr: [increment] [increment] [decrement] # Playback: [pause] [start] [stop] # Playlist/Song: [by-value] [next] [previous] # # In each group, there are 5 numbers: # # [invert status d0 d1min d1max] # # A valid status (> 0x00) enables the control; 'invert' (1/0) inverts the, # the action, but not all support this. 'status' is the MIDI event to match # (channel is NOT ignored); 'd0' is the status value (eg. if 0x90, Note On, # d0 is the note number; d1min to d1max is the range of d1 values detectable. # Hex values can be used; precede with '0x'. # # ------------------------ Loop/group/automation-slot number # | -------------------- Name of key (see the key map) # | | -------------- Inverse # | | | ---------- MIDI status/event byte (eg. Note On) # | | | | ------- d0: Data 1 (eg. Note number) # | | | | | ----- d1max: Data 2 min (eg. Note velocity) # | | | | | | -- d1min: Data 2 max # | | | | | | | # v v v v v v v # 0 "F1" [0 0x90 0 1 127] [0 0x00 0 0 0] [0 0x00 0 0 0] # Toggle On Off # # MIDI controls often send a Note On upon a press and a Note Off on release. # To use a control as a toggle, define only the Toggle stanza. For the control # to act only while held, define the On and Off stanzas with appropriate # statuses for press-and-release. [loop-control] 0 "1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 0 1 "q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 1 2 "a" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 2 3 "z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 3 4 "2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 4 5 "w" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 5 6 "s" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 6 7 "x" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 7 8 "3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 8 9 "e" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 9 10 "d" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 10 11 "c" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 11 12 "4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 12 13 "r" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 13 14 "f" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 14 15 "v" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 15 16 "5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 16 17 "t" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 17 18 "g" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 18 19 "b" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 19 20 "6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 20 21 "y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 21 22 "h" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 22 23 "n" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 23 24 "7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 24 25 "u" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 25 26 "j" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 26 27 "m" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 27 28 "8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 28 29 "i" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 29 30 "k" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 30 31 "," [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 31 [mute-group-control] 0 "!" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 0 1 "Q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 1 2 "A" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 2 3 "Z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 3 4 "@" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 4 5 "W" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 5 6 "S" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 6 7 "X" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 7 8 "#" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 8 9 "E" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 9 10 "D" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 10 11 "C" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 11 12 "$" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 12 13 "R" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 13 14 "F" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 14 15 "V" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 15 16 "%" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 16 17 "T" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 17 18 "G" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 18 19 "B" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 19 20 "^" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 20 21 "Y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 21 22 "H" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 22 23 "N" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 23 24 "&" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 24 25 "U" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 25 26 "J" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 26 27 "M" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 27 28 "*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 28 29 "I" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 29 30 "K" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 30 31 "<" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 31 [automation-control] 0 "'" [ 0 0x00 0 0 0 ] [ 0 0x90 8 1 127 ] [ 0 0x00 0 0 0 ] # BPM Up 1 ";" [ 0 0x00 0 0 0 ] [ 0 0x90 6 1 127 ] [ 0 0x00 0 0 0 ] # BPM Dn 2 "]" [ 0 0x00 0 0 0 ] [ 0 0x90 15 1 127 ] [ 0 0x00 0 0 0 ] # Set Up 3 "[" [ 0 0x00 0 0 0 ] [ 0 0x90 13 1 127 ] [ 0 0x00 0 0 0 ] # Set Dn 4 "KP_Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Replace 5 "Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Snapshot 6 "o" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Queue 7 "`" [ 0 0x00 0 0 0 ] [ 0 0x90 8 1 127 ] [ 0 0x80 8 1 127 ] # Group Mute 8 "l" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Learn 9 "Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playing Set 10 "." [ 0 0x90 20 1 127 ] [ 0 0x90 22 1 127 ] [ 0 0x90 18 1 127 ] # Playback 11 "P" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Record 12 "BkSpace" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Solo 13 "KP_/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Thru 14 "PageUp" [ 0 0x00 0 0 0 ] [ 0 0x90 9 1 127 ] [ 0 0x00 0 0 0 ] # BPM Page Up 15 "PageDn" [ 0 0x00 0 0 0 ] [ 0 0x90 5 1 127 ] [ 0 0x00 0 0 0 ] # BPM Page Dn 16 "KP_." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Set 17 "KP_*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record Style 18 "KP_-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quan Record 19 "KP_+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reset Sets 20 "|" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # One-shot 21 "F6" [ 0 0x90 21 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # FF 22 "F5" [ 0 0x90 19 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Rewind 23 "F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Top 24 "F2" [ 0 0x00 0 0 0 ] [ 0 0x90 4 1 127 ] [ 0 0x90 0 1 127 ] # Play List 25 "F3" [ 0 0x00 0 0 0 ] [ 0 0x90 3 1 127 ] [ 0 0x90 1 1 127 ] # Play Song 26 "F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Tap BPM 27 "Space" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Start 28 "Esc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Stop 29 "KP_Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop L/R 30 "F8" [ 0 0x90 2 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Mutes 31 "F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Pos 32 "\" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Keep Queue 33 "/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Slot Shift 34 "0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mutes Clear 35 "Quit" [ 0 0x00 0 0 0 ] [ 0 0x90 24 1 127 ] [ 0 0x00 0 0 0 ] # Quit 36 "=" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop Edit 37 "-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Event Edit 38 "F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Mode 39 "F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle JACK 40 "F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Menu Mode 41 "F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Follow JACK 42 "~" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Panic 43 ">" [ 0 0x90 23 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Visibility 44 "0xfa" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Save Session 45 "0xfb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 45 46 "0xfc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 46 47 "0xfd" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 48 "0xfe" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 48 49 "Sh_F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Overdub 50 "Sh_F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Overwrite 51 "Sh_F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Expand 52 "Sh_F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Oneshot 53 "Sh_F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Loop 54 "Sh_F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Record 55 "Sh_F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Copy 56 "Sh_F8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Paste 57 "Sh_F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Clear 58 "Sh_F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Delete 59 "Sh_F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Thru 60 "Sh_F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Solo 61 "0xe0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Cut 62 "0xe1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Double 63 "0xe2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q None 64 "0xe3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q Full 65 "0xe4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q Tighten 66 "0xe5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Randomize 67 "0xe6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Jitter 68 "0xe7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Note-map 69 "0xe8" [ 0 0x90 12 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BBT/HMS 70 "0xe9" [ 0 0x90 14 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # LR Loop 71 "0xea" [ 0 0x90 16 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Undo 72 "0xeb" [ 0 0x90 17 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Redo 73 "0xec" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Transpose Song 74 "0xed" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Copy Set 75 "0xee" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Paste Set 76 "0xef" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Tracks 77 "0xf0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Normal 78 "0xf1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Auto 79 "0xf2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Additive 80 "0xf3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # All Sets [midi-control-out-settings] set-size = 32 output-buss = 0 midi-enabled = false button-offset = 0 button-rows = 4 button-columns = 8 [midi-control-out] # This section determines how pattern statuses are to be displayed. # ---------------- Pattern or device-button number) # | ----------- MIDI status+channel (eg. Note On) # | | ------- data 1 (eg. note number) # | | | ----- data 2 (eg. velocity) # | | | | # v v v v # 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0] # Armed Muted (Un)queued Empty/Deleted # # A test of the status byte determines the enabled status, and channel is # included in the status. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [mute-control-out] # The format of the mute and automation output events is similar: # # ----------------- mute-group number # | ------------- MIDI status+channel (eg. Note On) # | | --------- data 1 (eg. note number) # | | | ------- data 2 (eg. velocity) # | | | | # v v v v # 1 [0x00 0 0 ] [0x00 0 0] [0x00 0 0] # On Off Empty (dark) # # The mute-controls have an additional stanza for non-populated ("deleted") # mute-groups. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [automation-control-out] # This format is similar to [mute-control-out], but the first number is an # active-flag, not an index number. The stanzas are are on / off / inactive, # except for 'snap', which is store / restore / inactive. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Panic 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Stop 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Pause 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Playback 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Toggle Mutes 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song Record 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Slot Shift 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Free 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Queue 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # One-shot 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Replace 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Snapshot 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song Mode 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Group Learn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play List Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play List Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play Song Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play Song Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Tap BPM 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Quit 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Visibility 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_2 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_3 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_4 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_5 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_6 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_7 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_8 [macro-control-out] # This format is 'macroname = [ hex bytes | macro-references]'. Macro references # are macro-names preceded by a '$'. Some values should always be defined, even # if empty: footer, header, reset, startup, and shutdown. footer = 0xF7 # End-of-SysEx byte header = 0xF0 0x00 0x00 # device SysEx header, 0xF0 required reset = $header 0x00 $footer # fill in with device's reset command shutdown = $header 0x00 $footer # sent at exit, if not empty startup = $header 0x00 $footer # sent at start, if not empty # End of /home/ahlstrom/.config/seq66/nanomap.ctrl # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/samples/perstfic-66.palette ================================================ # Seq66 0.99.22 (and above) palette configuration file # # /home/user/.config/seq66/perstfic-66.palette # Written on 2025-10-24 10:30:00 # # This file can be used to change the colors used by patterns # and in some parts of the user-interface. It must be active and # specified in the 'rc' file. [Seq66] config-type = "palette" version = 1 # The [comments] section can document this file. Lines starting with # '#', '[', or that have no characters end the comment. [comments] This palette is meant to tweak the normal palette for the perstfic-66.qss style-sheet. The main thing is to make the drawn text white. We probably need an additional "Text" ui-palette entry to replace "Foreground" for text. # Set 'dark-theme' to true if the matching style-sheet is dark. This # also set the 'dark-theme' setting in the 'usr' file. # Set 'dark-ui' to true if the palette background elements are dark. [hints] dark-theme = false dark-ui = false # [palette] affects the pattern colors selected (by number). First is # the color number, 0 to 31. Next is the name of the background color. # The first stanza [square brackets] are the background ARGB values. # The second provides the foreground color name and ARGB values. The # alpha values should be set to FF. [palette] 0 "Black" [ 0xFF000000 ] "White" [ 0xFFFFFFFF ] 1 "Red" [ 0xFFFF0000 ] "White" [ 0xFFFFFFFF ] 2 "Green" [ 0xFF008000 ] "White" [ 0xFFFFFFFF ] 3 "Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 4 "Blue" [ 0xFF0000FF ] "White" [ 0xFFFFFFFF ] 5 "Magenta" [ 0xFFFF00FF ] "White" [ 0xFFFFFFFF ] 6 "Cyan" [ 0xFF00FFFF ] "Black" [ 0xFF000000 ] 7 "White" [ 0xFFFFFFFF ] "Black" [ 0xFF000000 ] 8 "Dark Black" [ 0xFF2F4F4F ] "White" [ 0xFFFFFFFF ] 9 "Dark Red" [ 0xFF8B0000 ] "White" [ 0xFFFFFFFF ] 10 "Dark Green" [ 0xFF006400 ] "White" [ 0xFFFFFFFF ] 11 "Dark Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 12 "Dark Blue" [ 0xFF00008B ] "White" [ 0xFFFFFFFF ] 13 "Dark Magenta" [ 0xFF8B008B ] "White" [ 0xFFFFFFFF ] 14 "Dark Cyan" [ 0xFF008B8B ] "White" [ 0xFFFFFFFF ] 15 "Dark White" [ 0xFF808080 ] "White" [ 0xFFFFFFFF ] 16 "Orange" [ 0xFFFFA500 ] "White" [ 0xFFFFFFFF ] 17 "Pink" [ 0xFFFFC0CB ] "Black" [ 0xFF000000 ] 18 "Pale Green" [ 0xFF98FB98 ] "Black" [ 0xFF000000 ] 19 "Khaki" [ 0xFFF0E68C ] "Black" [ 0xFF000000 ] 20 "Light Blue" [ 0xFFADD8E6 ] "Black" [ 0xFF000000 ] 21 "Light Magenta" [ 0xFFEE82EE ] "Black" [ 0xFF000000 ] 22 "Turquoise" [ 0xFF40E0D0 ] "Black" [ 0xFF000000 ] 23 "Grey" [ 0xFF808080 ] "Black" [ 0xFF000000 ] 24 "Dk Orange" [ 0xFFFF8C00 ] "Black" [ 0xFF000000 ] 25 "Dark Pink" [ 0xFFFF1493 ] "Black" [ 0xFF000000 ] 26 "Sea Green" [ 0xFF2E8B57 ] "Black" [ 0xFF000000 ] 27 "Dark Khaki" [ 0xFFBDB76B ] "Black" [ 0xFF000000 ] 28 "Dark Slate Blue" [ 0xFF483D8B ] "Black" [ 0xFF000000 ] 29 "Dark Violet" [ 0xFF9400D3 ] "Black" [ 0xFF000000 ] 30 "Light Grey" [ 0xFFC0C0C0 ] "Black" [ 0xFF000000 ] 31 "Dark Grey" [ 0xFF484848 ] "Black" [ 0xFF000000 ] # Similar to the [palette] section, but applies to the custom-drawn # piano rolls and the --inverse option. The first value is the color # number, from 0 to 23. The names are feature names, not color names. # The second column block is the inverse color. [ui-palette] 0 "Foreground" [ 0xFF000000 ] "Foreground" [ 0xFFFFFFFF ] 1 "Background" [ 0xFF0876B2 ] "Background" [ 0xFF484848 ] 2 "Label" [ 0xDEADBEEF ] "Label" [ 0xFFFFFFFF ] 3 "Selection" [ 0xFFFFA500 ] "Selection" [ 0xFFFF00A5 ] 4 "Drum" [ 0xFFFF0000 ] "Drum" [ 0xFF000080 ] 5 "Tempo" [ 0xFFFFFF00 ] "Tempo" [ 0xFFFFFF00 ] 6 "Note Fill" [ 0xFF80FFFF ] "Note Fill" [ 0xFF000000 ] 7 "Note Border" [ 0xFF000000 ] "Note Border" [ 0xFFFFFFFF ] 8 "Black Keys" [ 0xFF000000 ] "Black Keys" [ 0xFFFFFFFF ] 9 "White Keys" [ 0xFFFFFFFF ] "White Keys" [ 0xFF000000 ] 10 "Progress Bar" [ 0xFFFF0000 ] "Progress Bar" [ 0xFFFF0000 ] 11 "Back Pattern" [ 0xFF008B8B ] "Back Pattern" [ 0xFF008B8B ] 12 "Medium Line" [ 0xFF202020 ] "Medium Line" [ 0xFF808080 ] 13 "Heavy Line" [ 0xFF101010 ] "Heavy Line" [ 0xFFFFFFFF ] 14 "Light Line" [ 0xFF404040 ] "Light Line" [ 0xFFC0C0C0 ] 15 "Beat" [ 0xFF0CC0E0 ] "Beat" [ 0xFFFFFFFF ] 16 "Near" [ 0xFFFFFF00 ] "Near" [ 0xFFFF00FF ] 17 "Time Brush" [ 0xFF0AA0C0 ] "Time Brush" [ 0xFF808080 ] 18 "Data Brush" [ 0xFF0AA0C0 ] "Data Brush" [ 0xFF808080 ] 19 "Event Brush" [ 0xFF0AA0C0 ] "Event Brush" [ 0xFF808080 ] 20 "Keys Brush" [ 0xFF0AA0C0 ] "Keys Brush" [ 0xFF0AA0C0 ] 21 "Names Brush" [ 0xFF0AA0C0 ] "Names Brush" [ 0xFF808080 ] 22 "Octave Line" [ 0xFF00FFFF ] "Octave Line" [ 0xFFFFFFFF ] 23 "Text" [ 0xFFFFFFFF ] "Text" [ 0xFF000000 ] 24 "Time Text" [ 0xFF000000 ] "Time Text" [ 0xFFFFFFFF ] 25 "Data Text" [ 0xFF000000 ] "Data Text" [ 0xFFFFFFFF ] 26 "Note Event" [ 0xFF80FFFF ] "Note Event" [ 0xFFFFFFFF ] 27 "Keys Text" [ 0xFF000000 ] "Keys Text" [ 0xFFFFFFFF ] 28 "Names Text" [ 0xFF000000 ] "Names Text" [ 0xFFFFFFFF ] 29 "Slots Text" [ 0xFF00FFFF ] "Slots Text" [ 0xFFFFFFFF ] 30 "Scale Brush" [ 0xFF000000 ] "Scale Brush" [ 0xFFFFFFFF ] 31 "Extra" [ 0xFF000000 ] "Extra" [ 0xFFFFFFFF ] # This section defines brush styles to use. The names are based on the # names in the Qt::BrushStyle enumeration. The names are: # # nobrush, solid, dense1, dense2, dense3, dense4, dense5, dense6, # dense7, horizontal, vertical, cross, bdiag, fdiag, diagcross, # lineargradient, radialgradient, and conicalgradient. # # For 'empty', best to just use 'solid' (try others and see why). # For 'note', use 'solid' or the default, 'lineargradient'. These # also apply to the progress box and triggers. [brushes] empty = solid note = lineargradient scale = dense3 backseq = dense2 chord = bdiag # This section defines pen styles to use for vertical time lines. # The names are based on the names in the Qt::PenStyle enumeration. # They are: # nopen, solid, dash, dot, dashdot, dashdotdot, customdash (which # is currently not supported). # # 'measure' and 'beat' are obvious. 'fourth' is a line for the 1/4s of # a beat, and 'step' is the smallest unit (normally 6 pixels). [pens] measure = solid beat = solid fourth = dashdot step = dot # End of /home/user/.config/seq66/qseq66.palette # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/samples/perstfic-66.qss ================================================ /* Copyright (c) DevSec Studio. All rights reserved. \file perstfic-66.qss \library seq66 application \author Chris Ahlstrom \date 2023-10-18 \updates 2024-12-29 \license MIT MIT License 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. This file and license is based on Incrypt.qss; it has been updated to work better with Seq66. */ QWidget { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(3, 50, 76, 255),stop:0.293269 rgba(6, 82, 125, 255),stop:0.514423 rgba(8, 117, 178, 255),stop:0.745192 rgba(7, 108, 164, 255),stop:1 rgba(3, 51, 77, 255)); color: #ffffff; } /* Additions */ QTabBar::tab { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(3, 50, 76, 255),stop:0.293269 rgba(6, 82, 125, 255),stop:0.514423 rgba(8, 117, 178, 255),stop:0.745192 rgba(7, 108, 164, 255),stop:1 rgba(3, 51, 77, 255)); color: #FFFFFF; font-size: 11pt; font-weight: normal; width: 80px; border: 1px solid #444; border-bottom-style: none; border-top-style: none; padding-top: 3px; padding-bottom: 2px; margin-right: -1px; } QTabBar::tab:selected { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(0, 171, 66, 255),stop:1 rgba(0, 153, 255, 255)); color: #000000; margin-bottom: 0px; } QMenuBar { } /* font: 14px; */ QHeaderView::section { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(0, 171, 200, 255),stop:1 rgba(0, 153, 255, 255)); color: black; height: 28px; } /* * font: 10px; * border: 1px solid #4181C0; * color: #4181C0; * alternate-background-color: yellow; * selection-background-color: #4181C0; * selection-color: #FFF; */ QTableView { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(3, 50, 76, 255),stop:0.293269 rgba(6, 82, 125, 255),stop:0.514423 rgba(8, 117, 178, 255),stop:0.745192 rgba(7, 108, 164, 255),stop:1 rgba(3, 51, 77, 255)); color: white; gridline-color: black; border-color: rgb(242, 128, 133); } QTableView:disabled { background-color: #19232D; color: #788D9C; } QTableView:selected { background-color: #346792; color: #455364; } QTableView:focus { border: 1px solid #1A72BB; } QTableView::item:pressed { background-color: grey; } QTableView::item:selected:active { background-color: grey; } QTableView::item:selected:!active { color: #DFE1E2; background-color: grey; } QTableView::item:!selected:hover { outline: 0; color: #DFE1E2; background-color: grey; } QTableView::item:focus { border: 2px solid rgb(242, 128, 133); background-color: rgb(196, 196, 196); } /* Affects the top-level menu. */ QMenuBar::item { color: #FFFFFF; padding: 1px 3px; background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(3, 50, 76, 255),stop:0.293269 rgba(6, 82, 125, 255),stop:0.514423 rgba(8, 117, 178, 255),stop:0.745192 rgba(7, 108, 164, 255),stop:1 rgba(3, 51, 77, 255)); } QMenuBar::item:selected { background-color:#0080ff; color: #ffffff; } QMenu { background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(3, 50, 76, 255),stop:0.293269 rgba(6, 82, 125, 255),stop:0.514423 rgba(8, 117, 178, 255),stop:0.745192 rgba(7, 108, 164, 255),stop:1 rgba(3, 51, 77, 255)); } /* Affects the entries under the top-level menus. */ QMenu::item { border-style: solid; border-top-color: transparent; border-right-color: transparent; border-left-color: transparent; border-bottom-color: transparent; border-bottom-width: 1px; border-style: solid; color: #ffffff; padding-left:17px; padding-top:4px; padding-bottom:4px; padding-right:7px; /*background-color: #008080;*/ } QMenu::item:selected { border-style: solid; border-top-color: transparent; border-right-color: transparent; border-left-color: #04b97f; border-bottom-color: transparent; border-left-width: 2px; color: #00ffff; padding-left:15px; padding-top:4px; padding-bottom:4px; padding-right:7px; background-color: qlineargradient(spread:repeat, x1:1, y1:0, x2:1, y2:1, stop:0.00480769 rgba(3, 50, 76, 255),stop:0.293269 rgba(6, 82, 125, 255),stop:0.514423 rgba(8, 117, 178, 255),stop:0.745192 rgba(7, 108, 164, 255),stop:1 rgba(3, 51, 77, 255)); } QToolTip { color: #ffffff; background-color: #000000; border: 0px; } /* QTextEdit { border-width: 1px; border-radius: 4px; border-color: rgb(58, 58, 58); border-style: inset; padding: 0 8px; color: #a9b7c6; background:#008080; selection-background-color:#007b50; selection-color: #FFFFFF; } QPlainTextEdit { background-color:#008080; border-width: 1px; border-color: rgb(58, 58, 58); border-style: solid; color: #a9b7c6; selection-background-color:#007b50; } */ /* End of Additions */ /*-----QLabel-----*/ QLabel { background-color: transparent; color: #fff; } /*-----QPushButton-----*/ QPushButton { background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(42, 95, 113, 255),stop:1 rgba(12, 53, 97, 255)); color: #fff; border: 2px; border-radius: 15px; padding: 5px; } QPushButton::hover { background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(167, 214, 50, 255),stop:1 rgba(98, 169, 67, 255)); color: #fff; border: 2px; border-radius: 15px; padding: 5px; } QPushButton::pressed { background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(147, 194, 30, 255),stop:1 rgba(78, 149, 47, 255)); color: #fff; border: 2px; border-radius: 20px; padding: 5px; } /*-----QLineEdit----- */ QLineEdit { background-color: #0aa0c0; color: #000000; border-style: solid; border-color: #141414; border-radius: 5px; padding-left: 10px; } QLineEdit:read-only { background-color: #0880a0; color: #000000; border-style: solid; border-color: #141414; border-radius: 5px; padding-left: 10px; } /*-----QListView-----*/ QListView { background-color: transparent; font-size: 11pt; border: none; color: #fff; show-decoration-selected: 0; padding-left: px; } QListView::item:selected { color: #fff; font-weight: bold; background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(147, 194, 30, 255),stop:1 rgba(78, 149, 47, 255)); border: none; border-radius: 0px; } QListView::item:!selected{ color: #fff; background-color: transparent; border: none; border-radius: 0px; } QListView::item:!selected:hover{ color: #fff; background-color: #0c3561; border: none; border-radius: 0px; } /*-----QScrollBar-----*/ QScrollBar:vertical { border: none; width: 10px; } QScrollBar::handle:vertical { border: none; border-radius : 0px; background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(167, 214, 50, 255),stop:1 rgba(98, 169, 67, 255)); min-height: 50px; width : 16px; } QScrollBar::handle:vertical:pressed { background-color: qlineargradient(spread:pad, x1:1, y1:0, x2:1, y2:1, stop:0 rgba(147, 194, 30, 255),stop:1 rgba(78, 149, 47, 255)); } QScrollBar::add-line:vertical { border: none; background: transparent; height: 0px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::add-line:vertical:hover { background-color: transparent; } QScrollBar::add-line:vertical:pressed { background-color: #3f3f3f; } QScrollBar::sub-line:vertical { border: none; background: transparent; height: 0px; } QScrollBar::sub-line:vertical:hover { background-color: transparent; } QScrollBar::sub-line:vertical:pressed { background-color: #3f3f3f; } QScrollBar::up-arrow:vertical { width: 0px; height: 0px; background: transparent; } QScrollBar::down-arrow:vertical { width: 0px; height: 0px; background: transparent; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background-color: #0c2669; width: 11px;; } /* vim: fileformat=unix ft=css */ ================================================ FILE: data/samples/qseq66-sample.palette ================================================ # Seq66 0.99.22 (and above) palette configuration file # # /home/user/.config/seq66/qseq66-sample.palette # Written on 2025-10-24 # # This file can be used to change the colors used by patterns # and in some parts of the user-interface. [Seq66] config-type = "palette" version = 1 # The [comments] section can document this file. Lines starting # with '#' and '[' are ignored. Blank lines are ignored. Show a # blank line by adding a space character to the line. [comments] Comments added to this section are preserved. Lines starting with a '#' or '[', or that are blank, are ignored. Start lines that must look empty with a space. # Set 'dark-theme' to true if the matching style-sheet is dark. This # also set the 'dark-theme' setting in the 'usr' file. # Set 'dark-ui' to true if the palette background elements are dark. [hints] dark-theme = false dark-ui = false # The first integer is the color number, ranging from 0 to 31. The # first string is the name of the background color. The first # stanza (in square brackets) are the ARGB values for the background. # The second set provides the foreground color name and color. # The alpha values are not important here, but should be set to FF. [palette] 0 "Black" [ 0xFF000000 ] "White" [ 0xFFFFFFFF ] 1 "Red" [ 0xFFFF0000 ] "White" [ 0xFFFFFFFF ] 2 "Green" [ 0xFF008000 ] "White" [ 0xFFFFFFFF ] 3 "Yellow" [ 0xFFFFFF00 ] "Dark!" [ 0xFF202020 ] 4 "Blue" [ 0xFF0000FF ] "White" [ 0xFFFFFFFF ] 5 "Magenta" [ 0xFFFF00FF ] "White" [ 0xFFFFFFFF ] 6 "Cyan" [ 0xFF00FFFF ] "Black" [ 0xFF000000 ] 7 "White" [ 0xFFFFFFFF ] "Black" [ 0xFF000000 ] 8 "Dark Black" [ 0xFF2F4F4F ] "White" [ 0xFFFFFFFF ] 9 "Dark Red" [ 0xFF8B0000 ] "White" [ 0xFFFFFFFF ] 10 "Dark Green" [ 0xFF006400 ] "White" [ 0xFFFFFFFF ] 11 "Dark Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 12 "Dark Blue" [ 0xFF00008B ] "White" [ 0xFFFFFFFF ] 13 "Dark Magenta" [ 0xFF8B008B ] "White" [ 0xFFFFFFFF ] 14 "Dark Cyan" [ 0xFF008B8B ] "White" [ 0xFFFFFFFF ] 15 "Dark White" [ 0xFF808080 ] "White" [ 0xFFFFFFFF ] 16 "Orange" [ 0xFFFFA500 ] "White" [ 0xFFFFFFFF ] 17 "Pink" [ 0xFFFFC0CB ] "Black" [ 0xFF000000 ] 18 "Pale Green" [ 0xFF98FB98 ] "Black" [ 0xFF000000 ] 19 "Khaki" [ 0xFFF0E68C ] "Black" [ 0xFF000000 ] 20 "Light Blue" [ 0xFFADD8E6 ] "Black" [ 0xFF000000 ] 21 "Light Magenta" [ 0xFFEE82EE ] "Black" [ 0xFF000000 ] 22 "Turquoise" [ 0xFF40E0D0 ] "Black" [ 0xFF000000 ] 23 "Grey" [ 0xFF808080 ] "Black" [ 0xFF000000 ] 24 "Dk Orange" [ 0xFFFF8C00 ] "Black" [ 0xFF000000 ] 25 "Dark Pink" [ 0xFFFF1493 ] "Black" [ 0xFF000000 ] 26 "Sea Green" [ 0xFF2E8B57 ] "Black" [ 0xFF000000 ] 27 "Dark Khaki" [ 0xFFBDB76B ] "Black" [ 0xFF000000 ] 28 "Dark Slate Blue" [ 0xFF483D8B ] "Black" [ 0xFF000000 ] 29 "Dark Violet" [ 0xFF9400D3 ] "Black" [ 0xFF000000 ] 30 "Light Grey" [ 0xFFC0C0C0 ] "Black" [ 0xFF000000 ] 31 "Dark Grey" [ 0xFF484848 ] "Black" [ 0xFF000000 ] # Similar to the [palette] section, but applies to UI elements and to # the --inverse color option. The first integer is the color number, # ranging from 0 to 12. The names are feature names, not color names. # The second column block is the inverse color. [ui-palette] 0 "Foreground" [ 0xFF0000FF ] "Foreground" [ 0xFFFFFFFF ] 1 "Background" [ 0xFFFFFFFF ] "Background" [ 0xFF484848 ] 2 "Label" [ 0xFFFFFF00 ] "Label" [ 0xFFFFFFFF ] 3 "Selection" [ 0xFFFFA500 ] "Selection" [ 0xFFFF00FF ] 4 "Drum" [ 0xFFFF0000 ] "Drum" [ 0xFF000080 ] 5 "Tempo" [ 0xFFFF00FF ] "Tempo" [ 0xFFFFFF00 ] 6 "Note Fill" [ 0xFFFFFFFF ] "Note Fill" [ 0xFF000000 ] 7 "Note Border" [ 0xFF000000 ] "Note Border" [ 0xFFFFFFFF ] 8 "Black Keys" [ 0xFF000000 ] "Black Keys" [ 0xFFFFFFFF ] 9 "White Keys" [ 0xFFFFFFFF ] "White Keys" [ 0xFF000000 ] 10 "Progress Bar" [ 0xFFFF0000 ] "Progress Bar" [ 0xFF000080 ] 11 "Back Pattern" [ 0xFF008B8B ] "Back Pattern" [ 0xFF008B8B ] 12 "Medium Line" [ 0xFF808080 ] "Medium Line" [ 0xFF808080 ] 13 "Heavy Line" [ 0xFF484848 ] "Heavy Line" [ 0xFFFFFFFF ] 14 "Light Line" [ 0xFFC0C0C0 ] "Light Line" [ 0xFFC0C0C0 ] 15 "Beat" [ 0xFF000000 ] "Beat" [ 0xFFFFFFFF ] 16 "Near" [ 0xFFFFFF00 ] "Near" [ 0xFFFF00FF ] 17 "Time Brush" [ 0xFF808080 ] "Time Brush" [ 0xFF808080 ] 18 "Data Brush" [ 0xFF808080 ] "Data Brush" [ 0xFF808080 ] 19 "Event Brush" [ 0xFF808080 ] "Event Brush" [ 0xFF808080 ] 20 "Keys Brush" [ 0xFF808080 ] "Keys Brush" [ 0xFF808080 ] 21 "Names Brush" [ 0xFF808080 ] "Names Brush" [ 0xFF808080 ] 22 "Octave Line" [ 0xFF808080 ] "Octave Line" [ 0xFF808080 ] 23 "Text" [ 0xFF808080 ] "Text" [ 0xFF808080 ] 24 "Time Text" [ 0xFF000000 ] "Time Text" [ 0xFFFFFFFF ] 25 "Data Text" [ 0xFF000000 ] "Data Text" [ 0xFFFFFFFF ] 26 "Note Event" [ 0xFFFFFFFF ] "Note Event" [ 0xFF000000 ] 27 "Keys Text" [ 0xFF000000 ] "Keys Text" [ 0xFFFFFFFF ] 28 "Names Text" [ 0xFF000000 ] "Names Text" [ 0xFFFFFFFF ] 29 "Slots Text" [ 0xFF000000 ] "Slots Text" [ 0xFFFFFFFF ] 30 "Scale Brush" [ 0xFF000000 ] "Scale Brush" [ 0xFFFFFFFF ] 31 "Extra" [ 0xFF000000 ] "Extra" [ 0xFFFFFFFF ] # This section defines brush styles to use. The names are based on the # names in the Qt::BrushStyle enumeration. The supported names are: # # nobrush, solid, dense1, dense2, dense3, dense4, dense5, dense6, # dense7, horizontal, vertical, cross, bdiag, fdiag, diagcross, # lineargradient, radialgradient, and conicalgradient. # # For 'empty', best to just use 'solid' (try others and see why). # For 'note', use 'solid' or the default, 'lineargradient'. These # also apply to the progress box and triggers. [brushes] empty = solid note = lineargradient scale = dense3 backseq = dense2 chord = bdiag # This section defines pen styles to use for vertical time lines. # The names are based on the names in the Qt::PenStyle enumeration. # They are: # nopen, solid, dash, dot, dashdot, dashdotdot, customdash (which # is currently not supported). # # 'measure' and 'beat' are obvious. 'fourth' is a line for the 1/4s of # a beat, and 'step' is the smallest unit (normally 6 pixels). [pens] measure = solid beat = solid fourth = dashdot step = dot # End of /home/user/.config/seq66/qseq66-sample.palette # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/samples/qseq66.qss ================================================ /* * \file qseq66.qss * \library seq66 application * \author Chris Ahlstrom * \date 2021-03-22 * \updates 2025-01-15 * \license Public domain * * Provides a sample Qt style sheet for Seq66. See * https://doc.qt.io/archives/qt-4.8/stylesheet-examples.html and * https://doc.qt.io/qt-5/stylesheet-reference.html. * * Copy this file to the Seq66 configuration directory. * In the 'rc' file, section [style-sheet-file], specify: * * active = true * name = "qseq66.qss" * * However, note that this sample is quite garish, and the changes * in buttons breaks the grid alignments in the pattern editor. */ QTabWidget::pane /* the tab widget frame */ { border-top: 2px solid #6495ED; } QTabWidget::tab-bar /* move to the right by 5px */ { left: 5px; } /* * Style the tab using the tab sub-control. Note that it reads QTabBar _not_ * QTabWidget */ QTabBar::tab { height: 48px; width: 80px; background: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 #808080, stop: 0.4 #404040, stop: 0.5 #808080, stop: 1.0 #606060); border: 2px solid #C4C4C3; border-bottom-color: #6495ED; /* same as the pane color */ border-top-left-radius: 4px; border-top-right-radius: 4px; min-width: 8ex; padding: 2px; } QTabBar::tab:selected, QTabBar::tab:hover { background: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 #404040, stop: 0.4 #202020, stop: 0.5 #303030, stop: 1.0 #101010); } QTabBar::tab:selected { border-color: #9B9B9B; border-bottom-color: #6495ED; /* same as pane color */ } /* * Make tabs extra tall for use with a touch-screen. */ QLineEdit { color: white; background-color: cornflowerblue; } QLineEdit:disabled { color: lightgray; background-color: darkgrey; } QDoubleSpinBox { background-color: cornflowerblue; } QSpinBox { background-color: cornflowerblue; } QComboBox { background-color: cornflowerblue; } QPushButton { color: green; background-color: maroon; border-style: ridge; border-width: 2px; border-radius: 4px; border-color: black; font: bold 12px; min-width: 2em; padding: 2px; } QPushButton:checked { background-color: salmon; } /* * vim: sw=4 ts=4 wm=4 et ft=css */ ================================================ FILE: data/samples/sample.playlist ================================================ # Seq66 0.94.1 (and above) playlist file # # /home/user/data/samples/sample.playlist # Written 2021-06-13 08:18:13 # # This file holds multiple playlists for Seq66. It consists of 1 or # more [playlist] sections. Each has a user-specified number for # sorting and MIDI control, ranging from 0 to 127. Next comes a # quoted display name for this list, followed by the quoted name of # the song directory, always using the UNIX-style separator ('/'). # It should be accessible from wherever Seq66 is run. # # Then comes a list of tunes, each starting with a MIDI control number # and the quoted name of the MIDI file. They are sorted by the # control number, starting from 0. They can be simple 'base.midi' # file-names; the playlist directory is prepended before the song is # accessed. If the MIDI file-name already has a path, that will be # used. [Seq66] config-type = "playlist" version = 1 # The [comments] section holds user documentation for this file. # The first completely empty, comment, or tag line ends the comment. [comments] This sample play-list file contains three playlists. One for demo ditties, one for more realistic songs, one for Cakewalk files. These songs are found in the contrib/midi directory. You may need to change that directory name for both of the playlists if you have a different setup than the developers. What we've done (in Linux) is created a soft-link in the HOME directory, ~/seq66, that points to the actual seq66 project tree, and used it here. Copy this file to your ~/.config/seq66 directory and add it to the qseq66.rc file's "[playlist]" specification. [playlist-options] unmute-new-song = true deep-verify = false [playlist] # Playlist number, arbitrary but unique. 0 to 127 recommended # for use with the MIDI playlist control. number = 125 name = "Music for Serious Dogs" directory = "~/seq66/contrib/midi/" 70 "allofarow.mid" 71 "CountryStrum.midi" 72 "1Bar.midi" 73 "2Bars.midi" 74 "click_4_4.midi" 75 "NR_Route_66.midi" [playlist] number = 126 name = "Data Ditties" directory = "~/seq66/data/midi/" 70 "b4uacuse-gm-patchless.midi" 71 "Chameleon-HHancock-Ov.midi" 72 "If_You_Could_Read_My_Mind.mid" 73 "Kraftwerk-Europe_Endless-reconstructed.midi" 74 "Peter_Gunn-reconstructed.midi" [playlist] number = 127 name = "Cakewalk Files" directory = "~/seq66/data/wrk/" # Provides the MIDI song-control number (0 to 127), and also the # base file-name (tune.midi) of each song in this playlist. # The playlist directory is used, unless the file-name contains its # own path. 70 "oxygen4b.wrk" 71 "longhair.wrk" # End of /home/user/data/samples/sample.playlist # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/samples/sample.usr ================================================ # Seq66 0.94.1 (and above) user ('usr') configuration file # # /home/ahlstrom/.config/seq66/sample.usr # Written 2026-02-16 08:00:00 # # This is a Seq66 'usr' file. Edit it and place it in the # $HOME/.config/seq66 directory. Enable it in the 'rc' file. # It allows one to apply an alias (alternate name) to each MIDI bus, MIDI # channel, and MIDI control control code, per channel. It has additional # options not present in in Seq24, and supports DOS INI-style variable # setting. [Seq66] config-type = "usr" version = 5 # The [comments] section lets one document this file. Lines starting # with '#' and '[', or that are empty end the comment. [comments] This file is a sample showing how to define a set of MIDI instruments and the names of the MIDI control values (CC's) that are defined for that instrument. Use this sample only as a guide. This sample is adapted from: https://github.com/vext01/seq24/blob/master/seq24usr.example 1. Define your instruments and their control-code names, if they have them. 2. Define a MIDI bus, its name, and what instruments are on which channel. In the following MIDI buss definitions, channels are counted from 0 to 15, not 1 to 16. Instruments not set here are set to -1 (SEQ66_GM_INSTRUMENT_FLAG) and are GM (General MIDI). These replacement MIDI buss labels are shown in MIDI Clocks, MIDI Inputs, and in the Pattern Editor buss drop-down. To temporarily disable the entries, set the count values to 0. The following 9 instruments are defined in this sample file: 0 = Waldorf Micro Q (128 CCs defined) 1 = SuperNova (128 CCs defined) 2 = DrumStation (92 CCs defined) 3 = TX81Z (5 CCs defined) 4 = WaveStation (4 CCs defined) 5 = ESI-2000 (4 CCs defined) 6 = ES-1 (4 CCs defined) 7 = ER-1 (4 CCs defined) 8 = TB-303 (4 CCs defined) The first bus is "2x2 A (SuperNova/Q/TX81Z/DrumStation)" with the following instruments defined for each set of channels: Channels 0-7 = Instrument 1 Channels 8-10 = Instrument 3 Channels 11-14 = Instrument 0 Channel 15 = Instrument 2 The second bus is "2x2 B (WaveStation,ESI-2000,MV4,ES-1,ER-1)": Channels 0-3 = Instrument 4 Channels 4-13 = Instrument 5 Channels 14 = Instrument 6 Channel 15 = Instrument 7 The third bus is "PCR-30 (303)": Channels 0 = Instrument 8 The first column is the channel number for this buss. The second column is the instrument number, which specifies the desired "[user-instrument-M]" section, where M ranges from 0 to one less than the number of configured instruments. If the instrument number is -1, then the instrument uses the GM instrument settings. # [user-midi-bus-definitions] # # 1. Define your instruments and their control-code names, if they # have them. # 2. Define a MIDI bus, its name, and what instruments are on which # channel. # # In the following MIDI buss definitions, channels are counted from # 0 to 15, not 1 to 16. Instruments not set here are set to -1 and # are GM (General MIDI). These replacement MIDI buss labels are shown # in MIDI Clocks, MIDI Inputs, and in the Pattern Editor buss and # channel drop-downs. To disable the entries, set the counts to 0. [user-midi-bus-definitions] 3 # number of user-defined MIDI busses [user-midi-bus-0] # Device/bus name. For the three MIDI busses, it's name appears # in (1) the main window's output-buss dropdown; (2) the right-click # "Output bus" menu entry; (3) the output-bus drop-down in the # pattern editor, and in the MIDI Clock tab of the Preferences # dialog. These names override the actual port name of the device, # if available. If port-mapping is active the shorter port-mapping # names are shown. "2x2 A (SuperNova/Q/TX81Z/DrumStation)" 16 # number of instrument settings # Channel, instrument number, and instrument names. # This list of names appears in the MIDI channel drop-down in the # pattern editor. 0 1 "SuperNova" 1 1 "SuperNova" 2 1 "SuperNova" 3 1 "SuperNova" 4 1 "SuperNova" 5 1 "SuperNova" 6 1 "SuperNova" 7 1 "SuperNova" 8 3 "TX81Z" 9 3 "TX81Z" 10 3 "TX81Z" 11 0 "Waldorf Micro Q" 12 0 "Waldorf Micro Q" 13 0 "Waldorf Micro Q" 14 0 "Waldorf Micro Q" 15 2 "DrumStation" [user-midi-bus-1] # Device/bus name "2x2 B (WaveStation,ESI-2000,MV4,ES-1,ER-1)" 16 # number of instrument settings # Channel, instrument number, and instrument names 0 4 "WaveStation" 1 4 "WaveStation" 2 4 "WaveStation" 3 4 "WaveStation" 4 5 "ESI-2000" 5 5 "ESI-2000" 6 5 "ESI-2000" 7 5 "ESI-2000" 8 5 "ESI-2000" 9 5 "ESI-2000" 10 5 "ESI-2000" 11 5 "ESI-2000" 12 5 "ESI-2000" 13 5 "ESI-2000" 14 6 "ES-1" 15 7 "ER-1" [user-midi-bus-2] # Device/bus name "PCR-30 (303)" 1 # number of instrument settings # Channel, instrument number, and instrument names 0 8 "TB-303" # In the following MIDI instrument definitions, active controller # numbers (i.e. supported by the instrument) are paired with # the (optional) name of the controller supported. [user-instrument-definitions] 9 # instrument list count [user-instrument-0] # Name of instrument. For each instrument, the controller numbers # and the actual name (i.e. function) are specified. When a given # user-midi-bus is selected, its controller names appear in the # "Event" drop-down's Controllers sections. "Waldorf Micro Q" 128 # number of MIDI controller number & name pairs 0 "" 1 "WMQ Modulation Wheel" 2 "WMQ Breath Control" 3 "" 4 "WMQ Foot Control" 5 "WMQ Glide Rate" 6 "" 7 "WMQ Channel Volume" 8 "" 9 "" 10 "WMQ Pan" 11 "" 12 "WMQ Arp Range (0-9) (1-10 octaves)" 13 "WMQ Arp Length (0-15) (1-16 steps)" 14 "WMQ Arp Active (0-3) (Off,On,One Shot,Hold)" 15 "WMQ LFO 1 Shape (0-5) (Sine,Tri,Square,Saw,Rand,S&H)" 16 "WMQ LFO 1 Speed (0-127) (256 Bars-1/96)" 17 "WMQ LFO 1 Sync (0-1) (Off,On)" 18 "WMQ LFO 1 Delay" 19 "WMQ LFO 2 Shape (0-5) (Sine,Tri,Square,Saw,Rand,S&H)" 20 "WMQ LFO 2 Speed (0-127) (256 Bars-1/96)" 21 "WMQ LFO 2 Sync (0-1) (Off,On)" 22 "WMQ LFO 2 Delay" 23 "WMQ LFO 3 Shape (0-5) (Sine,Tri,Square,Saw,Rand,S&H)" 24 "WMQ LFO 3 Speed (0-127) (256 Bars-1/96)" 25 "WMQ LFO 3 Sync (0-1) (Off,On)" 26 "WMQ LFO 3 Delay" 27 "WMQ Osc 1 Octave (16,28,40-112) (128'-1/2')" 28 "WMQ Osc 1 Semitone (52-76) (-12-+12)" 29 "WMQ Osc 1 Detune (0-127) (-64-63)" 30 "WMQ Osc 1 FM" 31 "WMQ Osc 1 Shape (0-5) (Pulse,Saw,Tri,Sine,Alt 1,Alt 2)" 32 "WMQ Bank Select LSB (0-3) (Bank A-D)" 33 "WMQ Osc 1 PW" 34 "WMQ Osc 1 PWM (0-127) (-64-63)" 35 "WMQ Osc 2 Octave (16,28,40-112) (128'-1/2')" 36 "WMQ Osc 2 Semitone (52-76) (-12-+12)" 37 "WMQ Osc 2 Detune (0-127) (-64-63)" 38 "WMQ Osc 2 FM" 39 "WMQ Osc 2 Shape (0-5) (Pulse,Saw,Tri,Sine,Alt 1,Alt 2)" 40 "WMQ Osc 2 PW" 41 "WMQ Osc 2 PWM (0-127) (-64-63)" 42 "WMQ Osc 3 Octave (16,28,40-112) (128'-1/2')" 43 "WMQ Osc 3 Semitone (52-76) (-12-+12)" 44 "WMQ Osc 3 Detune (0-127) (-64-63)" 45 "WMQ Osc 3 FM" 46 "WMQ Osc 3 Shape (0-5) (Pulse,Saw,Tri,Sine,Alt 1,Alt 2)" 47 "WMQ Osc 3 PW" 48 "WMQ Osc 3 PWM (0-127) (-64-63)" 49 "WMQ Sync (0-1) (Off,On)" 50 "WMQ Pitchmod (0-127) (-64-63)" 51 "WMQ Glide Mode (0-9)" 52 "WMQ Osc 1 Level" 53 "WMQ Osc 1 Balance (0-127) (F1-mid-F2)" 54 "WMQ Ringmod Level" 55 "WMQ Ringmod Balance (0-127) (F1-mid-F2)" 56 "WMQ Osc 2 Level" 57 "WMQ Osc 2 Balance (0-127) (F1-mid-F2)" 58 "WMQ Osc 3 Level" 59 "WMQ Osc 3 Balance (0-127) (F1-mid-F2)" 60 "WMQ Noise/External Level" 61 "WMQ Noise/External Balance (0-127) (F1-mid-F2)" 62 "" 63 "" 64 "WMQ Sustain Pedal" 65 "WMQ Glide Active (0-1) (Off,On)" 66 "WMQ Sostenuto (0-1) (Off,On)" 67 "WMQ Routing (0-1) (Serial,Parallel)" 68 "WMQ Filter 1 Type (0-10)" 69 "WMQ Filter 1 Cutoff" 70 "WMQ Filter 1 Resonance" 71 "WMQ Filter 1 Drive" 72 "WMQ Filter 1 Keytrack (0-127) (-200-197)" 73 "WMQ Filter 1 Env Amount (0-127) (-64-63)" 74 "WMQ Filter 1 Env Velocity (0-127) (-64-63)" 75 "WMQ Filter 1 Cutoff Mod (0-127) (-64-63)" 76 "WMQ Filter 1 FM (0-127) (Off,1-127)" 77 "WMQ Filter 1 Pan (0-127) (L-mid-R)" 78 "WMQ Filter 1 Pan Mod (0-127) (-64-63)" 79 "WMQ Filter 2 Type (0-10)" 80 "WMQ Filter 2 Cutoff" 81 "WMQ Filter 2 Resonance" 82 "WMQ Filter 2 Drive" 83 "WMQ Filter 2 Keytrack (0-127) (-200-197)" 84 "WMQ Filter 2 Env Amount (0-127) (-64-63)" 85 "WMQ Filter 2 Env Velocity (0-127) (-64-63)" 86 "WMQ Filter 2 Cutoff Mod (0-127) (-64-63)" 87 "WMQ Filter 2 FM (0-127) (Off,1-127)" 88 "WMQ Filter 2 Pan (0-127) (L-mid-R)" 89 "WMQ Filter 2 Pan Mod (0-127) (-64-63)" 90 "WMQ Amp Volume" 91 "WMQ Amp Velocity (0-127) (-64-63)" 92 "WMQ Amp Mod (0-127) (-64-63)" 93 "WMQ FX 1 Mix" 94 "WMQ FX 2 Mix" 95 "WMQ Filter Env Attack" 96 "WMQ Filter Env Decay" 97 "WMQ Filter Env Sustain" 98 "WMQ Filter Env Decay 2" 99 "WMQ Filter Env Sustain 2" 100 "WMQ Filter Env Release" 101 "WMQ Amp Env Attack" 102 "WMQ Amp Env Decay" 103 "WMQ Amp Env Sustain" 104 "WMQ Amp Env Decay 2" 105 "WMQ Amp Env Sustain 2" 106 "WMQ Amp Env Release" 107 "WMQ Env 3 Attack" 108 "WMQ Env 3 Decay" 109 "WMQ Env 3 Sustain" 110 "WMQ Env 3 Decay 2" 111 "WMQ Env 3 Sustain 2" 112 "WMQ Env 3 Release" 113 "WMQ Env 4 Attack" 114 "WMQ Env 4 Decay" 115 "WMQ Env 4 Sustain" 116 "WMQ Env 4 Decay 2" 117 "WMQ Env 4 Sustain 2" 118 "WMQ Env 4 Release" 119 "" 120 "WMQ All Sound Off (0)" 121 "WMQ Reset All Controllers (0)" 122 "WMQ Local Control (0-127) (Off,On)" 123 "WMQ All Notes Off (0)" 124 "" 125 "" 126 "" 127 "" [user-instrument-1] # Name of instrument "SuperNova" 128 # number of MIDI controller number & name pairs 0 "SN Bank Select MSB" 1 "SN Modulation Wheel" 2 "SN Breath Controller" 3 "SN Arp Pattern Select" 4 "SN Ring Modulator 2 * 3 Mix Level" 5 "SN Portamento Time" 6 "SN Data Entry" 7 "SN Part / Program Volume" 8 "SN Effects Confg Morph Amount" 9 "SN Arp Speed (Internal Clock Rate) [*]" 10 "SN Pan" 11 "SN Osc 1 Fine Tune" 12 "SN Osc 3 Fine Tune" 13 "SN Osc 1 Soften" 14 "SN Osc 2 Soften" 15 "SN Osc 3 Soften" 16 "SN LFO 1 Speed" 17 "SN LFO 1 Delay" 18 "SN LFO 2 Speed" 19 "SN LFO 2 Delay" 20 "SN Osc 1 Pitch Env 2" 21 "SN Osc 1 Pitch LFO 1" 22 "SN Osc 1 Pulse Width" 23 "SN Osc 2 Fine Tune" 24 "SN Noise Soften" 25 "SN Osc 2 Pitch Env 2" 26 "SN Osc 2 Pitch LFO 1" 27 "SN Osc 2 Pulse Width" 28 "SN Osc 1 Mix Level" 29 "SN Osc 2 Mix Level" 30 "SN Noise Mix Level" 31 "SN Ring Modulator 1 * 3 Mix Level" 32 "SN Bank Select LSB" 33 "SN Osc 3 Mix Level" 34 "SN Filter Tracking" 35 "SN Filter Freq LFO 2" 36 "SN Osc 1 Mix Env 2" 37 "SN Osc 2 Mix Env 2" 38 "SN Osc 3 Mix Env 2" 39 "SN Noise Mix Env 2" 40 "SN Osc 3 Pitch Env 2" 41 "SN Osc 3 Pitch LFO 1" 42 "SN Osc 3 Pulse Width" 43 "SN Osc 1 Width Env 2" 44 "SN Osc 2 Width Env 2" 45 "SN Osc 3 Width Env 2" 46 "SN Osc 1 Width LFO 1" 47 "SN Osc 2 Width LFO 1" 48 "SN Osc 3 Width LFO 1" 49 "SN Osc 1 Sync" 50 "SN Osc 2 Sync" 51 "SN Osc 3 Sync" 52 "SN Osc 1 Sync Env 2" 53 "SN Osc 2 Sync Env 2" 54 "SN Osc 3 Sync Env 2" 55 "SN Osc 1 Sync LFO 1" 56 "SN Osc 2 Sync LFO 1" 57 "SN Osc 3 Sync LFO 1" 58 "SN Distortion Mod Wheel Depth" 59 "SN Filter Freq Env 3" 60 "SN Filter Freq LFO 1" 61 "SN Osc 1 Soften Env 2" 62 "SN Osc 2 Soften Env 2" 63 "SN Osc 3 Soften Env 2" 64 "SN Sustain / Arp Latch" 65 "SN Arp Latch" 66 "SN Osc 1 Pitch Env 3" 67 "SN Osc 2 Pitch Env 3" 68 "SN Osc 3 Pitch Env 3" 69 "SN Osc 1 Width LFO 2" 70 "SN Osc 2 Width LFO 2" 71 "SN Osc 3 Width LFO 2" 72 "SN Filter Res Env 2" 73 "SN Filter Res LFO 1" 74 "SN Env 3 Delay" 75 "SN Env 3 Attack" 76 "SN Env 3 Decay" 77 "SN Env 3 Sustain" 78 "SN Env 3 Release" 79 "SN Env 3 Velocity" 80 "SN LFO 1 Speed Env 3" 81 "SN LFO 2 Speed Env 3" 82 "SN LFO 1 Offset" 83 "SN LFO 2 Offset" 84 "SN Reverb Mod Wheel Depth" 85 "SN Filter Res Env 3" 86 "SN Filter Res LFO 2" 87 "SN Chorus Speed" 88 "SN Chorus Mod Depth" 89 "SN Chorus Feedback" 90 "SN Distortion Level" 91 "SN Reverb Send Level" 92 "SN Delay Send Level" 93 "SN Chorus Send Level" 94 "SN Chorus Mod Wheel Depth" 95 "SN Reverb Decay" 96 "SN Reverb HF Damp" 97 "SN Master Volume Level [*]" 98 "SN NRPN Select LSB" 99 "SN NRPN Select MSB" 100 "SN Reverb Type / Early Ref Level [**]" 101 "SN Delay Time" 102 "SN Delay Feedback" 103 "SN Delay HF Damp" 104 "SN Filter Overdrive" 105 "SN Filter Cutoff Freq" 106 "SN Filter Resonance" 107 "SN Filter Freq Env 2" 108 "SN Env 1 Attack" 109 "SN Env 1 Decay" 110 "SN Env 1 Sustain" 111 "SN Env 1 Release" 112 "SN Env 1 Velocity" 113 "SN Env 2 Delay" 114 "SN Env 2 Attack" 115 "SN Env 2 Decay" 116 "SN Env 2 Sustain" 117 "SN Env 2 Release" 118 "SN Env 2 Velocity" 119 "SN Delay Mod Wheel Depth" 120 "SN All Sound Off" 121 "SN Reset Controllers" 122 "SN Local Control [*]" 123 "SN All Notes Off" 124 "SN All Notes Off" 125 "SN All Notes Off" 126 "SN All Notes Off" 127 "SN All Notes Off" [user-instrument-2] # Name of instrument "DrumStation" 92 # number of MIDI controller number & name pairs 20 "808 Bass Drum Front cut" 21 "808 Bass Drum Pan" 22 "808 Bass Drum Distortion" 23 "808 Bass Drum Tune" 24 "808 Bass Drum Tone" 25 "808 Bass Drum Decay" 26 "808 Snare Drum Front cut" 27 "808 Snare Drum Pan" 28 "808 Snare Drum Distortion" 29 "808 Snare Drum Tune" 30 "808 Snare Drum Tone" 31 "808 Snare Drum Snappy" 32 "808 Low Tom Front cut" 33 "808 Low Tom Pan" 34 "808 Low Tom Distortion" 35 "808 Low Tom Tune" 36 "808 Low Tom Decay" 37 "808 Mid Tom Front cut" 38 "808 Mid Tom Pan" 43 "808 Mid Tom Pan" 44 "808 Mid Tom Distortion" 45 "808 Mid Tom Tune" 46 "808 Mid Tom Decay" 47 "808 Rim Shot Pan" 48 "808 Rim Shot Tune" 49 "808 Hand Clap Pan" 50 "808 Hand Clap Tune" 51 "808 Cowbell Pan" 52 "808 Cowbell Distortion" 53 "808 Cowbell Tune" 54 "808 Closed HiHat Pan" 55 "808 Closed HiHat Tune" 56 "808 Closed HiHat Decay" 57 "808 Open HiHat Pan" 58 "808 Open HiHat Tune" 59 "808 Open HiHat Decay" 60 "808 Crash Cymbal Pan" 65 "808 Crash Cymbal Tune" 66 "808 Mid Conga Pan" 67 "808 Mid Conga Distortion" 68 "808 Mid Conga Tune" 69 "808 High Conga Pan" 70 "808 High Conga Distortion" 71 "808 High Conga Tune" 72 "808 Maracas Pan" 73 "808 Maracas Tune" 74 "808 Claves Pan" 75 "808 Claves Tune" 76 "909 Bass Drum Tune" 77 "909 Bass Drum Attack" 78 "909 Bass Drum Decay" 79 "909 Snare Drum Tune" 80 "909 Snare Drum Tone" 81 "909 Snare Drum Snappy" 82 "909 Low Tom Front cut" 83 "909 Low Tom Pan" 84 "909 Low Tom Distortion" 85 "909 Low Tom Tune" 86 "909 Low Tom Decay" 87 "909 Mid Tom Front cut" 88 "909 Mid Tom Pan" 89 "909 Mid Tom Distortion" 90 "909 Mid Tom Tune" 91 "909 Mid Tom Decay" 92 "909 High Tom Front cut" 93 "909 High Tom Pan" 94 "909 High Tom Distortion" 95 "909 High Tom Tune" 96 "909 High Tom Decay" 97 "909 Rim Shot Pan" 98 "909 Rim Shot Tune" 99 "909 Rim Shot Hand Clap Pan" 100 "909 Rim Shot Tune" 101 "909 Closed HiHat Distortion" 102 "909 Closed HiHat Tune" 103 "909 Closed HiHat Decay" 104 "909 Open HiHat Tune" 105 "909 Bass Drum Front cut" 106 "909 Bass Drum Pan" 107 "909 Bass Drum Distortion" 108 "909 Snare Drum Front cut" 109 "909 Snare Drum Pan" 110 "909 Snare Drum Distortion" 111 "909 Closed HiHat Pan" 112 "909 Open HiHat Pan" 113 "909 Open HiHat Decay" 114 "909 Crash Cymbal Pan" 115 "909 Crash Cymbal Tune" 116 "909 Crash Cymbal Decay" 117 "909 Ride Cymbal Pan" 118 "909 Ride Cymbal Tune" 119 "909 Ride Cymbal Decay" [user-instrument-3] # Name of instrument "TX81Z" 4 # number of MIDI controller number & name pairs 0 "TX81Z CC 0" 1 "TX81Z CC 1" 2 "TX81Z CC 2" 3 "TX81Z CC 3" [user-instrument-4] # Name of instrument "WaveStation" 4 # number of MIDI controller number & name pairs 0 "WaveStation CC 0" 1 "WaveStation CC 1" 2 "WaveStation CC 2" 3 "WaveStation CC 3" [user-instrument-5] # Name of instrument "ESI-2000" 4 # number of MIDI controller number & name pairs 0 "ESI-2000 CC 0" 1 "ESI-2000 CC 1" 2 "ESI-2000 CC 2" 3 "ESI-2000 CC 3" [user-instrument-6] # Name of instrument "ES-1" 4 # number of MIDI controller number & name pairs 0 "ES-1 CC 0" 1 "ES-1 CC 1" 2 "ES-1 CC 2" 3 "ES-1 CC 3" [user-instrument-7] # Name of instrument "ER-1" 4 # number of MIDI controller number & name pairs 0 "ER-1 CC 0" 1 "ER-1 CC 1" 2 "ER-1 CC 2" 3 "ER-1 CC 3" [user-instrument-8] # Name of instrument "TB-303" 4 # number of MIDI controller number & name pairs 0 "TB-303 CC 0" 1 "TB-303 CC 1" 2 "TB-303 CC 2" 3 "TB-303 CC 3" # [user-interface-settings] # # Specifies the configuration of some user-interface elements. Many # items were rendered obsolete and removed in version 5 of this file. # The main grid-style is now Qt buttons. To use a flat style, use # Qt themes/style-sheets. # # 'mainwnd-rows' and 'mainwnd-columns' (option '-o sets=RxC') specify # rows/columns in the main grid. R ranges from 4 to 8, C from 8 to 12. # Values other than 4x8 have not been tested, use at your own risk. # # 'mainwnd-spacing' specifies spacing in the main window. It ranges # from 2 (default) to 16. # # 'default-zoom' specifies initial zoom for the piano rolls. Ranges # from 1 to 512; defaults to 2. Larger PPQNs require larger zoom to # look good in the editors. Seq66 adapts the zoom to the PPQN # if 'default-zoom' zoom is set to 0. The unit of zoon is ticks/pixel. # # 'global-seq-feature' specifies if the key, scale, and background # pattern are to be applied globally to all patterns, or separately # to each. These three values are stored in the MIDI file, either in # the global SeqSpec section, or in each track. # # false: Each pattern has its own key/scale/background. # true: Apply these settings globally to all patterns. # # 'progress-bar-thick specifies a thicker progress bar. Default is 1 # pixel; thick is 2 pixels. Set it to true to enable the feature # # 'inverse-colors' (option -K/--inverse) specifies use of an inverse # color palette. Palettes are for Seq66 drawing areas, not for the # Qt theme. Normal/inverse palettes are changed via a 'palette' file. # # 'window-redraw-rate' specifies the base window redraw rate for all # windows. The default is 40 ms (25 ms for Windows). # # Window-scale (option '-o scale=m.n[xp.q]') specifies scaling the # main window at startup. Defaults to 1.0 x 1.0. If between 0.8 and # 3.0, it changes the size of the main window proportionately. If the # y-value is 0, the first value applies to both dimensions. [user-interface-settings] swap-coordinates = false mainwnd-rows = 4 mainwnd-columns = 8 mainwnd-spacing = 2 default-zoom = 2 global-seq-feature = true progress-bar-thick = true progress-bar-thickness = 2 progress-box-elliptical = false follow-progress = true gridlines-thick = true inverse-colors = false time-fg-color = "default" time-bg-color = "default" dark-theme = true window-redraw-rate = 40 window-scale = 1 window-scale-y = 1 enable-learn-confirmation = true # [user-midi-ppqn] # # Seq66 separates the file PPQN from the Seq66 PPQN the user wants # to use. # # 'default-ppqn' specifies the PPQN to use by default. The classic # default is 192, but can range from 32 to 19200. # # 'use-file-ppqn' indicates to use the file PPQN. This is the best # setting, to avoid changing the file's PPQN. [user-midi-ppqn] default-ppqn = 192 use-file-ppqn = true # [user-midi-settings] # # These settings specify MIDI-specific values better off as variables, # rather than constants. Values of -1 mean the value won't be used. # # 'beats-per-bar': default = 4 range = 1 to 32. # 'beats-per-minute': default = 120.0 range = 2.0 to 600.0. # 'beat-width': default = 4 range = 1 to 32. # 'buss-override': default = -1 range = 0 to 48. # 'velocity-override': default = -1 range = 0 to 127. # 'bpm-precision': default = 0 range = 0 to 2. # 'bpm-step-increment': default = 1.0 range = 0.01 to 25.0. # 'bpm-page-increment': default = 1.0 range = 0.01 to 25.0. # 'bpm-minimum': default = 0.0 range = 127.0 # 'bpm-maximum': default = 0.0 range = 127.0 # # A buss-override from 0 to 48 overrides the busses for all patterns, # for testing or convenience. Do not save the MIDI file afterwards # unless you want to overwrite all the buss values! # # The velocity override when adding notes in the pattern editor is set # via the 'Vol' button. -1 ('Free'), preserves incoming velocity. # # Precision of the BPM spinner and MIDI control of BPM is 0, 1, or 2. # The step increment affects the beats/minute spinner and MIDI control # of BPM. For 1 decimal point, 0.1 is good. For 2 decimal points, # 0.01 is good, but one might want something faster, like 0.05. # Set the page increment to a larger value than the step increment; # it is used when the Page-Up/Page-Down keys are pressed when the BPM # spinner has keyboard focus. # The BPM-minimum and maximum set the range BPM in tempo graphing. # By default, the tempo graph ranges from 0.0 to 127.0. This range # decreased to give a magnified view of tempo. [user-midi-settings] beats-per-bar = 4 beats-per-minute = 120 beat-width = 4 buss-override = -1 velocity-override = -1 bpm-precision = 0 bpm-step-increment = 1 bpm-page-increment = 10 bpm-minimum = 0 bpm-maximum = 127 # [user-options] # # These settings specify values set via the -o or --option switch, # which helps expand the number of options supported. # The 'daemonize' option is used in seq66cli to indicate that the # application should be gracefully run as a service. # The 'log' value specifies a log-file that replaces output to # standard output/error. For no log-file, use "". This option # also works from the command line: '-o log=filename.log'. The name # here is used for the no-name '-o log' option. [user-options] daemonize = false log = "" pdf-viewer = "" browser = "" # [user-ui-tweaks] # # key-height specifies the initial height (before vertical zoom) of pattern # editor keys. Defaults to 10 pixels, ranges from 6 to 32. # # key-view specifies the default for showing labels for each key: # 'octave-letters' (default), 'even_letters', 'all-letters', # 'even-numbers', and 'all-numbers'. # # note-resume causes notes-in-progress to resume when the pattern toggles on. # # Note that style-sheet specification has been moved to the 'rc' file with # all the rest of the files. # # A fingerprint is a condensation of note events in a long track, to reduce # the time drawing the pattern in the buttons. Ranges from 32 (default) to # 128. 0 = don't use a fingerprint. # # progress-box-width and -height settings change the scaled size of the # progress box in the live-grid buttons. Width ranges from 0.50 to 1.0, and # the height from 0.10 to 1.0. If either is 'default', defaults (0.8 x 0.3) # are used. progress-box-shown controls if the boxes are shown at all. # progress-box-show-cc enables displaying CC and pitchbend events (as dots). # # progress-note-min and progress-note-max set the progress-box note range so # that notes aren't centered in the box, but shown at their position by pitch. # # lock-main-window prevents the accidental change of size of the main # window. [user-ui-tweaks] key-height = 10 key-view = octave-letters note-resume = false fingerprint-size = 0 progress-box-width = 0 progress-box-height = 0 progress-box-shown = true progress-box-show-cc = true progress-note-min = 0 progress-note-max = 127 lock-main-window = false # [user-session] # # This section specifies the session manager to use, if any. The # 'session' variable can be set to 'none' (the default), 'nsm' # (Non or New Session Manager), or 'lash' (LASH, not yet supported). # 'url' can be set to the value of the NSM_URL environment variable # set by nsmd when run outside of the Non Session Manager user- # interface. Set the URL only if running nsmd standalone with a # matching --osc-port number. [user-session] session = none url = "" visibility = true # [pattern-editor] # # 'escape-pattern' allows the Esc key to close a pattern editor if not # playing or in paint mode. (Esc exits paint mode or stops playback.) # # 'apply-to-new-only' makes the next options work only when opening a new # pattern. A new pattern is 'Untitled' and has no events. # # These settings for play/record for a pattern editor save time in live # recording. Valid record-style values: 'merge' or 'overdub', 'overwrite', # 'expand', and 'one-shot'. 'wrap-around' allows recorded notes to wrap # to the pattern beginning. Currently 'notemap' and quantizing are # are mutually exclusive. [pattern-editor] escape-pattern = true apply-to-new-only = false armed = false thru = false record = false tighten = false qrecord = false notemap = false record-style = merge wrap-around = false # End of /home/ahlstrom/.config/seq66/sample.usr # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/samples/sessions.rc ================================================ # Seq66 0.99.10 session 'rc' configuration file # # /home/user/.config/seq66/sessions.rc # Written 2023-11-04 00:00:00 # # This file holds the redirection of the main configuration for Seq66. # Here are the options supported: # # - 'home'. If not empty, replaces "~/.config/seq66". It is the same as # the --home command-line option. # - 'client-name'. Changes the label for the application. It is the same # as the --client-name option. # - 'config'. Changes the base name of the configuration. It is the same # as the --config option. # - 'log'. Sets up a log file. Similar to --option log=x.y. # This option overrides the "log" option in the 'usr' file. [Seq66] config-type = "session" version = 0 [comments] This file is meant to change the configuration directory or files for a Seq66 session. It is a read-only file (in Seq66) meant for test situations or for selection one of many setups. The user is responsible for the management of this file, using a text editor. [normal] home = "~/.config/seq66/" client-name = "seq66" config = "qseq66" log = "seq66.log" [test] home = "~/.config/seq66/test/" client-name = "test66" config = "test66" log = "test66.log" [test-export] home = "~/.config/seq66/test2/" client-name = "test2" config = "test66" log = "" [4x4] home = "~/Home/ca/mls/git/seq66/contrib/tests/4x4/" client-name = "seq664x4" config = "qseq66" log = "4x4.log" # End of /home/user/.config/seq66/session.rc # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/samples/textfix.qss ================================================ /* * \file textfix.qss * \library seq66 application * \author Chris Ahlstrom * \date 2021-10-20 * \updates 2021-12-07 * \license Public domain * * Provides a sample Qt style sheet for Seq66. Makes the text of disabled * elements easier to see in some light themes. */ QLabel { color : cornflowerblue; } QLabel:disabled { color: #F0A080; } QLineEdit { color : cornflowerblue; } QLineEdit:disabled { color: #F0A080; } QTextEdit { color : cornflowerblue; } QTextEdit:disabled { color: #F0A080; } QCheckBox { color : cornflowerblue; } QCheckBox:disabled { color: #F0A080; } QRadioButton { color : cornflowerblue; } QRadioButton:disabled { color: #F0A080; } QDoubleSpinBox { color : cornflowerblue; } QDoubleSpinBox:disabled { color: #F0A080; } QSpinBox { color : cornflowerblue; } QSpinBox:disabled { color: #F0A080; } QComboBox { color : cornflowerblue; } QComboBox:disabled { color: #F0A080; } /* * vim: sw=4 ts=4 wm=4 et ft=css */ ================================================ FILE: data/seq66cli/seq66cli.ctrl ================================================ # Seq66 0.99.11 MIDI control configuration file # # /home/user/.config/seq66/seq66cli.ctrl # Written 2023-10-28 13:51:23 # # Sets up MIDI I/O control. The format is like the 'rc' file. To use it, set it # active in the 'rc' [midi-control-file] section. It adds loop, mute, & # automation buttons, MIDI display, new settings, and macros. [Seq66] config-type = "ctrl" version = 6 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] This file is a copy of nanomap.ctrl; it can be used for any MIDI controller keyboard set so that the lowest note is 0. This file was created by copying the old nanomap.rc file and updating it to the newest .ctrl format. This file is useful mainly in following Section 17.1 of the Seq66 user manual. It has no midi-control-out section. [midi-control-settings] # Input settings to control Seq66. 'control-buss' ranges from 0 to the highest # system input buss. If set, that buss can send MIDI control. 255 (0xFF) means # any ENABLED MIDI input can send control. ALSA has an extra 'announce' buss, # so add 1 to the port number with ALSA. With port-mapping enabled, the port # nick-name can be provided. # # 'midi-enabled' applies to the MIDI controls; keystroke controls are always # enabled. Supported keyboard layouts are 'qwerty' (default), 'qwertz', and # 'azerty'. AZERTY turns off auto-shift for group-learn. drop-empty-controls = false control-buss = "nanoKEY2 nanoKEY2 _ CTRL" midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 keyboard-layout = qwerty # A control stanza sets key and MIDI control. Keys support 'toggle', and # key-release is 'invert'. The leftmost number on each line is the loop number # (0 to 31), mutes number (same range), or an automation number. 3 groups of # of bracketed numbers follow, each providing a type of control: # # Normal: [toggle] [on] [off] # Increment/Decr: [increment] [increment] [decrement] # Playback: [pause] [start] [stop] # Playlist/Song: [by-value] [next] [previous] # # In each group, there are 5 numbers: # # [invert status d0 d1min d1max] # # A valid status (> 0x00) enables the control; 'invert' (1/0) inverts the, # the action, but not all support this. 'status' is the MIDI event to match # (channel is NOT ignored); 'd0' is the status value (eg. if 0x90, Note On, # d0 is the note number; d1min to d1max is the range of d1 values detectable. # Hex values can be used; precede with '0x'. # # ------------------------ Loop/group/automation-slot number # | -------------------- Name of key (see the key map) # | | -------------- Inverse # | | | ---------- MIDI status/event byte (eg. Note On) # | | | | ------- d0: Data 1 (eg. Note number) # | | | | | ----- d1max: Data 2 min (eg. Note velocity) # | | | | | | -- d1min: Data 2 max # | | | | | | | # v v v v v v v # 0 "F1" [0 0x90 0 1 127] [0 0x00 0 0 0] [0 0x00 0 0 0] # Toggle On Off # # MIDI controls often send a Note On upon a press and a Note Off on release. # To use a control as a toggle, define only the Toggle stanza. For the control # to act only while held, define the On and Off stanzas with appropriate # statuses for press-and-release. # # Warning: the 'BS' key is actually the Ctrl-H key, and NOT the Backspace key. # The Backspace key is called 'BkSpace' in the Seq66 key-map. [loop-control] 0 "1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 0 1 "q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 1 2 "a" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 2 3 "z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 3 4 "2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 4 5 "w" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 5 6 "s" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 6 7 "x" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 7 8 "3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 8 9 "e" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 9 10 "d" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 10 11 "c" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 11 12 "4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 12 13 "r" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 13 14 "f" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 14 15 "v" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 15 16 "5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 16 17 "t" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 17 18 "g" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 18 19 "b" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 19 20 "6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 20 21 "y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 21 22 "h" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 22 23 "n" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 23 24 "7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 24 25 "u" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 25 26 "j" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 26 27 "m" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 27 28 "8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 28 29 "i" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 29 30 "k" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 30 31 "," [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 31 [mute-group-control] 0 "!" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 0 1 "Q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 1 2 "A" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 2 3 "Z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 3 4 "@" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 4 5 "W" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 5 6 "S" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 6 7 "X" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 7 8 "#" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 8 9 "E" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 9 10 "D" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 10 11 "C" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 11 12 "$" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 12 13 "R" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 13 14 "F" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 14 15 "V" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 15 16 "%" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 16 17 "T" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 17 18 "G" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 18 19 "B" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 19 20 "^" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 20 21 "Y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 21 22 "H" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 22 23 "N" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 23 24 "&" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 24 25 "U" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 25 26 "J" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 26 27 "M" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 27 28 "*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 28 29 "I" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 29 30 "K" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 30 31 "<" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 31 [automation-control] 0 "'" [ 0 0x00 0 0 0 ] [ 0 0x90 8 1 127 ] [ 0 0x00 0 0 0 ] # BPM Up 1 ";" [ 0 0x00 0 0 0 ] [ 0 0x90 6 1 127 ] [ 0 0x00 0 0 0 ] # BPM Dn 2 "]" [ 0 0x00 0 0 0 ] [ 0 0x90 15 1 127 ] [ 0 0x00 0 0 0 ] # Set Up 3 "[" [ 0 0x00 0 0 0 ] [ 0 0x90 13 1 127 ] [ 0 0x00 0 0 0 ] # Set Dn 4 "KP_Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Replace 5 "Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Snapshot 6 "o" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Queue 7 "`" [ 0 0x00 0 0 0 ] [ 0 0x90 8 1 127 ] [ 0 0x80 8 1 127 ] # Group Mute 8 "l" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Learn 9 "Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playing Set 10 "." [ 0 0x90 20 1 127 ] [ 0 0x90 22 1 127 ] [ 0 0x90 18 1 127 ] # Playback 11 "P" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Record 12 "BkSpace" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Solo 13 "KP_/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Thru 14 "PageUp" [ 0 0x00 0 0 0 ] [ 0 0x90 9 1 127 ] [ 0 0x00 0 0 0 ] # BPM Page Up 15 "PageDn" [ 0 0x00 0 0 0 ] [ 0 0x90 5 1 127 ] [ 0 0x00 0 0 0 ] # BPM Page Dn 16 "KP_." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Set 17 "KP_*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record Style 18 "KP_-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quan Record 19 "KP_+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reset Sets 20 "|" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # One-shot 21 "F6" [ 0 0x90 21 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # FF 22 "F5" [ 0 0x90 19 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Rewind 23 "F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Top 24 "F2" [ 0 0x00 0 0 0 ] [ 0 0x90 4 1 127 ] [ 0 0x90 0 1 127 ] # Play List 25 "F3" [ 0 0x00 0 0 0 ] [ 0 0x90 3 1 127 ] [ 0 0x90 1 1 127 ] # Play Song 26 "F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Tap BPM 27 "Space" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Start 28 "Esc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Stop 29 "KP_Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop L/R 30 "F8" [ 0 0x90 2 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Mutes 31 "F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Pos 32 "\" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Keep Queue 33 "/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Slot Shift 34 "0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mutes Clear 35 "Quit" [ 0 0x00 0 0 0 ] [ 0 0x90 24 1 127 ] [ 0 0x00 0 0 0 ] # Quit 36 "=" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop Edit 37 "-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Event Edit 38 "F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Mode 39 "F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle JACK 40 "F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Menu Mode 41 "F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Follow JACK 42 "~" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Panic 43 ">" [ 0 0x90 23 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Visibility 44 "0xfa" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Save Session 45 "0xfb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 45 46 "0xfc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 46 47 "0xfd" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 48 "0xfe" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 48 49 "Sh_F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Overdub 50 "Sh_F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Overwrite 51 "Sh_F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Expand 52 "Sh_F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Oneshot 53 "Sh_F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Loop 54 "Sh_F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Record 55 "Sh_F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Copy 56 "Sh_F8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Paste 57 "Sh_F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Clear 58 "Sh_F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Delete 59 "Sh_F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Thru 60 "Sh_F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Solo 61 "0xe0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Cut 62 "0xe1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Grid Double 63 "0xe2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q None 64 "0xe3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q Full 65 "0xe4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Q Tighten 66 "0xe5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Randomize 67 "0xe6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Jitter 68 "0xe7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Note-map 69 "0xe8" [ 0 0x90 12 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BBT/HMS 70 "0xe9" [ 0 0x90 14 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # LR Loop 71 "0xea" [ 0 0x90 16 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Undo 72 "0xeb" [ 0 0x90 17 1 127 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Redo 73 "0xec" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Transpose Song 74 "0xed" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Copy Set 75 "0xee" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Paste Set 76 "0xef" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Tracks 77 "0xf0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Normal 78 "0xf1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Auto 79 "0xf2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Sets Additive 80 "0xf3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # All Sets [midi-control-out-settings] set-size = 32 output-buss = "0xFF" midi-enabled = false button-offset = 0 button-rows = 4 button-columns = 8 [midi-control-out] # This section determines how pattern statuses are to be displayed. # ---------------- Pattern or device-button number) # | ----------- MIDI status+channel (eg. Note On) # | | ------- data 1 (eg. note number) # | | | ----- data 2 (eg. velocity) # | | | | # v v v v # 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0] # Armed Muted (Un)queued Empty/Deleted # # A test of the status byte determines the enabled status, and channel is # included in the status. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [mute-control-out] # The format of the mute and automation output events is similar: # # ----------------- mute-group number # | ------------- MIDI status+channel (eg. Note On) # | | --------- data 1 (eg. note number) # | | | ------- data 2 (eg. velocity) # | | | | # v v v v # 1 [0x00 0 0 ] [0x00 0 0] [0x00 0 0] # On Off Empty (dark) # # The mute-controls have an additional stanza for non-populated ("deleted") # mute-groups. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [automation-control-out] # This format is similar to [mute-control-out], but the first number is an # active-flag, not an index number. The stanzas are are on / off / inactive, # except for 'snap', which is store / restore / inactive. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Panic 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Stop 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Pause 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Playback 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Toggle Mutes 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song Record 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Slot Shift 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Free 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Queue 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # One-shot 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Replace 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Snapshot 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song Mode 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Group Learn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play List Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play List Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play Song Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play Song Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Tap BPM 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Quit 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Visibility 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_2 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_3 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_4 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_5 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_6 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_7 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Alt_8 [macro-control-out] # This format is 'macroname = [ hex bytes | macro-references]'. Macro references # are macro-names preceded by a '$'. Some values should always be defined, even # if empty: footer, header, reset, startup, and shutdown. footer = 0xF7 # End-of-SysEx byte header = 0xF0 0x00 0x00 # device SysEx header, 0xF0 required reset = $header 0x00 $footer # fill in with device's reset command shutdown = $header 0x00 $footer # sent at exit, if not empty startup = $header 0x00 $footer # sent at start, if not empty # End of /home/user/.config/seq66/seq66cli.ctrl # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/seq66cli/seq66cli.drums ================================================ # Seq66 0.99.11 note-mapper ('drums') configuration file # # /home/user/.config/seq66/seq66cli.drums # Written 2023-10-28 13:51:23 # # This file resembles the files generated by 'midicvtpp', modified for Seq66: # # midicvtpp --csv-drum GM_DD-11_Drums.csv --output ddrums.ini # # This file can convert the percussion of non-GM devices to GM, as closely as # possible. Although it is for drums, it can be used for other note-mappings. [Seq66] config-type = "drums" version = 0 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] This file is based on a similar file created using the "midicvt" application. It has been manually edited to fit Seq66 conventions. It is essentially a copy of the sample file GM_DD-11.drums. # Drum/note-mapping configuration for Seq66, stored in the HOME configuration # directory. To use this file, add this file-name to '[note-mapper]' section of # the 'rc' file. There's no user-interface for this file. The main values are: # # map-type: drum, patch, or multi; indicates the mapping to do. # gm-channel: Indicates the channel (1-16) applied to convert notes. # reverse: true or false; map in the opposite direction if true. [notemap-flags] map-type = "" gm-channel = 1 reverse = false # The drum section: # # [Drum 35]. Marks a GM drum-change section, one per instrument. # # gm-name GM name for the drum assigned to the input note. # gm-note Input note number, same as the section number. # dev-name The device's name for the drum. # dev-note GM MIDI note whose GM sound best matches the sound of dev-name. # # The gm-note value is converted to the dev-note value, unless reverse # mapping is activated. The actual GM drum sound might not match what the # MIDI hardware puts out. [Drum 35] dev-name = "Unsupported 35" gm-name = "Acoustic Bass Drum" dev-note = 35 gm-note = 35 [Drum 36] dev-name = "Bass Drum Gated Reverb" gm-name = "Bass Drum 1" dev-note = 36 gm-note = 35 [Drum 37] dev-name = "Unsupported 37" gm-name = "Side Stick" dev-note = 37 gm-note = 37 [Drum 38] dev-name = "Snare Drum Gated Reverb" gm-name = "Acoustic Snare" dev-note = 38 gm-note = 38 [Drum 39] dev-name = "Unsupported 39" gm-name = "Hand Clap" dev-note = 39 gm-note = 39 [Drum 40] dev-name = "Unsupported 40" gm-name = "Electric Snare" dev-note = 40 gm-note = 40 [Drum 41] dev-name = "Tom Low 1" gm-name = "Low Floor Tom" dev-note = 41 gm-note = 41 [Drum 42] dev-name = "Tom Mid 1" gm-name = "Low Mid Tom" dev-note = 42 gm-note = 47 [Drum 43] dev-name = "Tom High 1" gm-name = "High Floor Tom" dev-note = 43 gm-note = 43 [Drum 44] dev-name = "Bass Drum 1" gm-name = "Bass Drum 1" dev-note = 44 gm-note = 36 [Drum 45] dev-name = "Bass Drum 2" gm-name = "Acoustic Bass Drum" dev-note = 45 gm-note = 35 [Drum 46] dev-name = "Unsupported 46" gm-name = "Open Hi-Hat" dev-note = 46 gm-note = 46 [Drum 47] dev-name = "Unsupported 47" gm-name = "Low-Mid Tom" dev-note = 47 gm-note = 47 [Drum 48] dev-name = "Tom Low 2" gm-name = "Low Tom" dev-note = 48 gm-note = 45 [Drum 49] dev-name = "Snare Drum Hi" gm-name = "Electric Snare" dev-note = 49 gm-note = 40 [Drum 50] dev-name = "Tom Mid 2" gm-name = "High Mid Tom" dev-note = 50 gm-note = 48 [Drum 51] dev-name = "Snare Drum Rim Shot" gm-name = "Side Stick" dev-note = 51 gm-note = 37 [Drum 52] dev-name = "Snare Drum Low" gm-name = "Acoustic Snare" dev-note = 52 gm-note = 38 [Drum 53] dev-name = "Tom High 2" gm-name = "High Tom" dev-note = 53 gm-note = 50 [Drum 54] dev-name = "Hand Clap" gm-name = "Hand Clap" dev-note = 54 gm-note = 39 [Drum 55] dev-name = "Cowbell" gm-name = "Cowbell" dev-note = 55 gm-note = 56 [Drum 56] dev-name = "Shaker" gm-name = "Maracas" dev-note = 56 gm-note = 70 [Drum 57] dev-name = "High Hat Closed" gm-name = "Closed High Hat" dev-note = 57 gm-note = 42 [Drum 58] dev-name = "Unsupported 58" gm-name = "Vibraslap" dev-note = 58 gm-note = 58 [Drum 59] dev-name = "High Hat Open" gm-name = "Open High Hat" dev-note = 59 gm-note = 46 [Drum 60] dev-name = "Crash Cymbal" gm-name = "Crash Cymbal 1" dev-note = 60 gm-note = 49 [Drum 61] dev-name = "Splash Cymbal" gm-name = "Splash Cymbal" dev-note = 61 gm-note = 55 [Drum 62] dev-name = "Ride Cymbal Cup" gm-name = "Ride Cymbal 2" dev-note = 62 gm-note = 59 [Drum 63] dev-name = "Ride Cymbal" gm-name = "Ride Cymbal 1" dev-note = 63 gm-note = 51 [Drum 64] dev-name = "Conga Low" gm-name = "Low Conga" dev-note = 64 gm-note = 64 [Drum 65] dev-name = "Conga High" gm-name = "Open High Conga" dev-note = 65 gm-note = 63 [Drum 66] dev-name = "Conga Muted" gm-name = "Mute High Conga" dev-note = 66 gm-note = 62 [Drum 67] dev-name = "Bongo Low" gm-name = "Low Bongo" dev-note = 67 gm-note = 61 [Drum 68] dev-name = "Bongo High" gm-name = "High Bongo" dev-note = 68 gm-note = 60 [Drum 69] dev-name = "Timbale Low" gm-name = "Low Timbale" dev-note = 69 gm-note = 66 [Drum 70] dev-name = "Timbale High" gm-name = "High Timbale" dev-note = 70 gm-note = 65 [Drum 71] dev-name = "Unsupported 71" gm-name = "Short Whistle" dev-note = 71 gm-note = 71 [Drum 72] dev-name = "Claves Low" gm-name = "Low Wood Block" dev-note = 72 gm-note = 77 [Drum 73] dev-name = "Claves High" gm-name = "High Wood Block" dev-note = 73 gm-note = 76 [Drum 74] dev-name = "Agogo Low" gm-name = "Low Agogo" dev-note = 74 gm-note = 68 [Drum 75] dev-name = "Agogo High" gm-name = "High Agogo" dev-note = 75 gm-note = 67 [Drum 76] dev-name = "Cuica Low" gm-name = "Mute Cuica" dev-note = 76 gm-note = 78 [Drum 77] dev-name = "Cuica High" gm-name = "Open Cuica" dev-note = 77 gm-note = 79 [Drum 78] dev-name = "Unsupported 78" gm-name = "Mute Cuica" dev-note = 78 gm-note = 78 [Drum 79] dev-name = "Unsupported 79" gm-name = "Open Cuica" dev-note = 79 gm-note = 79 [Drum 80] dev-name = "Unsupported 80" gm-name = "Mute Triangle" dev-note = 80 gm-note = 80 [Drum 81] dev-name = "Unsupported 81" gm-name = "Open Triangle" dev-note = 81 gm-note = 81 # End of /home/user/.config/seq66/seq66cli.drums # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/seq66cli/seq66cli.mutes ================================================ # Seq66 0.99.20 mute-groups configuration file # # /home/user/.config/seq66/seq66cli.mutes # Written 2025-05-26 08:48:41 # # Used in the [mute-group-file] section of the 'rc' file, making it easier to # multiple mute groups. To use this file, specify it in [mute-group-file] file # and set 'active = true'. [Seq66] config-type = "mutes" version = 0 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] Add your comment block here # load-mute-groups: Set to 'none' or 'mutes' to load from the 'mutes' file, # 'midi' to load from the song, or 'both' to try to read from 'mutes' first, # then the 'midi' file. # # save-mutes-to: 'both' writes mutes to the 'mutes' and MIDI file; 'midi' # writes only to the MIDI file; and the mutes only to the 'mutes' file. # # strip-empty: If true, all-zero mute-groups are not written to the MIDI file. # # groups-format: 'binary' means write mutes as 0/1; 'hex' means write them as # hexadecimal numbers (e.g. 0xff), useful for larger set sizes. # # mute-group-selected: If 0 to 31, and mutes are available from this file # or from the MIDI file, then this mute-group is applied at startup; useful in # restoring a session. Set to -1 to disable. # # toggle-active-only: When a group is toggled off, all patterns, even those # outside the mute-group, are muted. With this flag, only patterns in the # mute-group are muted. Patterns unmuted directly by the user remain unmuted. # # mute-group-selected: if 0 to 31, and mutes are available either from # this file or from the MIDI file, then the mute-group is applied at # startup; useful in restoring a session. Set to -1 to disable. # # toggle-active-only: when a mute-group is toggled off, all patterns, # even those outside the mute-group, are muted. If this flag is set # to true, only patterns in the mute-group are muted. Any patterns # unmuted directly by the user remain unmuted. [mute-group-flags] load-mute-groups = midi save-mutes-to = midi strip-empty = false mute-group-selected = -1 groups-format = binary toggle-active-only = false [mute-groups] # We save mute-group values in the 'mutes' file, even if all zeroes. They can # be stripped out of the MIDI file by 'strip-empty-mutes'. Hex values indicate # a bit-mask, not a single bit. A quoted group name can be placed at the end # of the line. 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 ] "Group 0" 1 [ 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 ] "Group 1" 2 [ 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 ] "Group 2" 3 [ 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 ] "Group 3" 4 [ 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 ] "Group 4" 5 [ 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 ] "Group 5" 6 [ 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 ] "Group 6" 7 [ 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 ] "Group 7" 8 [ 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 ] "Group 8" 9 [ 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 ] "Group 9" 10 [ 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 ] "Group 10" 11 [ 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 ] "Group 11" 12 [ 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 ] "Group 12" 13 [ 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 ] "Group 13" 14 [ 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 ] "Group 14" 15 [ 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 ] "Group 15" 16 [ 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 ] "Group 16" 17 [ 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 ] "Group 17" 18 [ 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 ] "Group 18" 19 [ 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 ] "Group 19" 20 [ 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 ] "Group 20" 21 [ 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 ] "Group 21" 22 [ 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 ] "Group 22" 23 [ 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 ] "Group 23" 24 [ 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 ] "Group 24" 25 [ 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 ] "Group 25" 26 [ 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 ] "Group 26" 27 [ 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 ] "Group 27" 28 [ 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 ] "Group 28" 29 [ 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 ] "Group 29" 30 [ 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 ] "Group 30" 31 [ 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 ] "Group 31" # End of /home/user/.config/seq66/seq66cli.mutes # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/seq66cli/seq66cli.playlist ================================================ # Seq66 0.99.20 playlist configuration file # # /home/user/.config/seq66/seq66cli.playlist # Written 2025-05-26 08:48:41 # # This file holds multiple playlists, each in a [playlist] section. Each has # a user-specified number for sorting and MIDI control, ranging from 0 to 127. # Next comes a quoted name for this list, followed by the quoted name # of the song directory using the UNIX separator ('/'). # # Next is a list of tunes, each starting with a MIDI control number and the # quoted name of the MIDI file, sorted by control number. They can be simple # 'base.midi' file-names; the playlist directory is prepended to access the # song file. If the file-name has a path, that will be used. [Seq66] config-type = "playlist" version = 1 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] I created these files long, long ago. Ah, such memories. Added them to the install to make live testing easier. The MIDI files are (on Linux) installed to /usr/share/seq66-0.99/midi. [playlist-options] # 'unmute-next-song' causes the next selected song to have all patterns # armed for playback. (Should be called 'auto-arm'). Does not matter for # songs with triggers for Song mode. 'auto-play' causes songs to start play # automatically when loaded. 'auto-advance' implies the settings noted # above. It automatically loads the next song in the play-list when the # current song ends. 'deep-verify' causes each tune in the play-list to be # loaded to make sure each one can be loaded. Otherwise, only file existence # is checked. unmute-next-song = true auto-play = true auto-advance = true deep-verify = false # Here are the playlist settings, default storage folder, and then a list of # each tune with its control number. The playlist number is arbitrary but # unique. 0 to 127 enforced for use with MIDI playlist controls. Similar # for the tune numbers. Each tune can include a path; it overrides the base # directory. [playlist] number = 0 name = "Legacy Midi Files" directory = "/usr/local/share/seq66-0.99/midi/FM/" 0 "brecluse.mid" 1 "carptsun.mid" 2 "cbflitfm.mid" 3 "dasmodel.mid" 4 "grntamb.mid" 5 "hapwandr.mid" 6 "judyblue.mid" 7 "k_seq11.mid" 8 "longhair.mid" 9 "marraksh.mid" 10 "oxyg4bfm.mid" 11 "pirates.mid" 12 "pss680.mid" 13 "qufrency.mid" 14 "stdemo3.mid" 15 "viceuk.mid" 16 "wallstsm.mid" [playlist] number = 1 name = "PSS-790 Midi Files" directory = "/usr/local/share/seq66-0.99/midi/PSS-790/" 0 "ancestor.mid" 10 "carptsun.mid" 20 "cbflite.mid" 30 "old_love.mid" [playlist] number = 3 name = "Live vs Song Files" directory = "/usr/local/share/seq66-0.99/midi/" 1 "Peter_Gunn-reconstructed.midi" 2 "Chameleon-HHancock-Ov.midi" 3 "Kraftwerk-Europe_Endless-reconstructed.midi" 4 "If_You_Could_Read_My_Mind.mid" 5 "b4uacuse-gm-patchless.midi" # End of /home/user/.config/seq66/seq66cli.playlist # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/seq66cli/seq66cli.rc ================================================ # Seq66 0.99.11 main ('rc') configuration file # # /home/user/.config/seq66/seq66cli.rc # Written 2025-05-26 08:48:41 # # This file holds the main configuration for Seq66. It diverges greatly from # the of the seq24rc configuration file. # # 'version' is set by Seq66; it is used to detect older configuration files, # which are upgraded to the new version when saved. # # 'quiet' suppresses start-up error messages. Useful when they are not # relevant. There's no --quiet command-line option yet. It's NOT the opposite # of 'verbose'. # # 'verbose' is temporary, same as --verbose; it's set to false at exit. # # 'sets-mode' affects set muting when moving to the next set. 'normal' leaves # the next set muted. 'auto-arm' unmutes it. 'additive' keeps the previous set # armed when moving to the next set. 'all-sets' arms all sets at once. # # 'port-naming': 'short', 'pair', or 'long'. If 'short', the device name is # shown; but if generic, the client name is added for clarity. If 'pair', # the client:port number is prepended. If 'long', the full set of name items # is shown. If port-mapping is active (now the default), this does not apply. # # 'init-disabled-ports' does not yet work. It tries live toggle of port state. # # 'priority' greater than 0 is meant to increase the priority of the I/O # threads. It needs Seq66 to run as root, or be installed as setuid 0. [Seq66] config-type = "rc" version = 4 quiet = false verbose = false sets-mode = normal port-naming = short init-disabled-ports = false priority = 0 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] This is a seq66cli 'rc' file to test basic control and playlist handling. The 'ctrl' file is a renamed copy of data/samples/nanomap.ctrl. The 'playlist' file is a renamed copy of data/samples/ca_midi.playlist. The 'drums' file is a renamed copy of data/samples/GM_DD-11.drums. # Provides a flag and file-name for MIDI-control I/O settings. '""' means # no 'ctrl' file. If none, default keystrokes are used, with no MIDI control. # Note that all configuration files are stored in the "home" configuration # directory; any paths in the file-names are stripped. [midi-control-file] active = true name = "seq66cli.ctrl" # Provides a flag and file-name for mute-groups settings. '""' means no # 'mutes' file. If none, there are no mute groups, unless the MIDI file # contains some. [mute-group-file] active = false name = "seq66cli.mutes" # Provides a flag and file-name for 'user' settings. '""' means no 'usr' # file. If none, there are no special user settings. Using no 'usr' file # should be considered experimental. [usr-file] active = true name = "seq66cli.usr" # Provides a flag and play-list file. If no list, use '""' and set active # = false. Use the extension '.playlist'. Even if not active, the play-list # file is read. 'base-directory' sets the directory holding all MIDI files # in all play-lists, useful when copying play-lists/tunes from one place to # another; it preserves sub-directories (e.g. in creating an NSM session). [playlist] active = true name = "seq66cli.playlist" base-directory = "" # Provides a flag and file-name for note-mapping. '""' means no 'drums' file. # This file is used when the user invokes the note-conversion operation in # the pattern editor of a transposable pattern. Make the pattern temporarily # transposable to allow this operation. [note-mapper] active = true name = "seq66cli.drums" # Provides a flag and file-name to provide a list of patches for legacy # non-GM-compliant devices. [patches-file] active = false name = "seq66cli.patches" # Provides a flag and file-name to allow modifying the palette using the file # specified. Use '""' to indicate no 'palette' file. If none or not active, # the internal palette is used. [palette-file] active = false name = "seq66cli.palette" # If specified, a style-sheet (e.g. 'qseq66.qss') is applied at startup. # This file must be located in Seq66's "home" directory. Copy if needed. # Note that style-sheet specification has been removed from the 'usr' file. [style-sheet-file] active = false name = "seq66cli.qss" # Defines features of MIDI meta-event handling. Tempo events are in the first # track (pattern 0), but one can use them elsewhere. It changes where tempo # events are recorded. The default is 0, the maximum is 1023. A pattern must # exist at this number. [midi-meta-events] tempo-track = 0 # Set to true to create virtual ALSA/JACK I/O ports and not auto-connect to # other clients. Allows up to 48 output or input ports (defaults to 8 and 4). # If true, it disables port-mapping. Keep it false to auto-connect Seq66 to # real ALSA/JACK MIDI ports and preserve port-mapping. Set 'auto-enable' to # enable all virtual ports automatically. [manual-ports] virtual-ports = false auto-enable = false output-port-count = 8 input-port-count = 4 # These MIDI ports are for input and control. JACK's view: these are # 'playback' devices. The first number is the bus, the second number is the # input status, disabled (0) or enabled (1). The item in quotes is the full # input bus name. The type of port depends on the 'virtual-ports' setting. [midi-input] 3 # number of MIDI input (or control) buses 0 1 "[0] 0:1 system:ALSA Announce" 1 0 "[1] 14:0 Midi Through Port-0" 2 0 "[2] 20:0 nanoKEY2 nanoKEY2 _ CTRL" # This table is similar to the [midi-clock-map] section, but the values are # different. -2 = unavailable; 0 = not inputing; 1 = enabled for inputing. [midi-input-map] 1 # map is active 0 1 "ALSA Announce" 1 0 "Midi Through Port-0" 2 0 "nanoKEY2 nanoKEY2 _ CTRL" # These MIDI ports are for output, playback, and display. JACK's view: these # are 'capture' devices. The first line shows the count of output ports. # Each line shows the bus number and clock status of that bus: # # -2 = Output port is not present on the system (unavailable). # -1 = Output port is disabled. # 0 = MIDI Clock is off. Output port is enabled. # 1 = MIDI Clock on; Song Position and MIDI Continue are sent. # 2 = MIDI Clock Modulo. # # With Clock Modulo, clocking doesn't begin until song position reaches the # start-modulo value [midi-clock-mod-ticks]. Ports that are unavailable # (because another port, e.g. Windows MIDI Mapper, has exclusive access to # the device) are displayed ghosted. The type of port depends on the # 'virtual-ports' setting. [midi-clock] 3 # number of MIDI clocks (output/display buses) 0 0 "[0] 14:0 Midi Through Port-0" 1 0 "[1] 20:0 nanoKEY2 nanoKEY2 _ CTRL" 2 0 "[2] 128:0 FLUID Synth (9854):Synth input port (9854:0)" # Patterns use bus numbers, not names. This table provides virtual bus numbers # that match real devices and can be stored in each pattern. The bus number # is looked up in this table, the port nick-name is retrieved, and the true # bus number is obtained and used. Thus, if the ports change order in the MIDI # system, the pattern will use the proper port. The short nick-names work in # ALSA or JACK (a2jmidid bridge). [midi-clock-map] 1 # map is active 0 0 "Midi Through Port-0" 1 0 "nanoKEY2 nanoKEY2 _ CTRL" 2 0 "FLUID Synth" # 'ticks' provides the Song Position (16th notes) at which clocking begins if # the bus is set to MIDI Clock Mod setting. 'record-by-channel' allows the # master MIDI bus to record/filter incoming MIDI data by channel, adding each # new MIDI event to the pattern that is set to that channel. Option adopted # from the Seq32 project at GitHub. [midi-clock-mod-ticks] ticks = 64 record-by-channel = false # This section defines tweaks to the reading or writing of MIDI files. # Indicates how to handle MIDI files with incorrect running status. Default # is 'recover', which tries to recover the running status when a data byte # is encountered; 'skip' ignores the rest of the bytes in the track; # 'proceed' keeps going; 'abort' just exits the parsing, which is the old # and undesirable behavior. Try each option with the 'trilogy.mid' file. [midi-file-tweaks] running-status-action = recover # Set to true to have Seq66 ignore port names defined in the 'usr' file. Use # this option to to see the system ports as detected by ALSA/JACK. [reveal-ports] show-system-ports = false # This section sets up a metronome that can be activated from the main live # grid. It consists of a 'main' note on the first beat, then 'sub' notes on # the rest of the beats. The patch/program, note value, velocity, and # fraction length relative to the beat width (can be specified. The length # ranges from about 0.125 (one-eight) to 1.0 (the same length as the beat # width) to 2.0). [metronome] output-buss = 0 output-channel = 9 beats-per-bar = 4 beat-width = 4 main-patch = 0 main-note = 75 main-note-velocity = 96 main-note-length = 0 sub-patch = 0 sub-note = 76 sub-note-velocity = 84 sub-note-length = 0 count-in-active = false count-in-measures = 1 count-in-recording = false recording-buss = 0 recording-measures = 0 thru-buss = 0 thru-channel = 0 # Sets mouse usage for drawing/editing patterns. 'Fruity' mode is NOT in # Seq66. Other settings are available: 'snap-split' enables splitting # song-editor triggers at a snap position instead of in its middle. Split is # done by a middle-click or ctrl-left click. 'double-click-edit' allows double- # click on a slot to open it in a pattern editor. Set it to false if # you don't like how it works. [interaction-method] snap-split = false double-click-edit = true # transport-type enables synchronizing with JACK Transport. Values: # none: No JACK Transport in use. # slave: Use JACK Transport as Slave. # master: Attempt to serve as JACK Transport Master. # conditional: Serve as JACK master if no JACK master exists. # # song-start-mode playback is either Live, Song, or Auto: # live: Muting & unmuting of loops in the main window. # song: Playback uses Song (performance) editor data. # auto: If the loaded tune has song triggers, use Song mode. # # jack-midi sets/unsets JACK MIDI, separate from JACK transport. # jack-auto-connect sets connecting to JACK ports found. Default = true; use # false to have a session manager make the connections. # jack-use-offset attempts to calculate timestamp offsets to improve accuracy # at high-buffer sizes. Still a work in progress. # jack-buffer-size allows for changing the frame-count, a power of 2. [jack-transport] transport-type = none song-start-mode = auto jack-midi = false jack-auto-connect = true jack-use-offset = true jack-buffer-size = 0 # 'auto-save-rc' sets automatic saving of the 'rc' and other files. If set # (true if changes were made in Preferences), settings are saved. # # 'old-triggers' saves triggers in a format compatible with Seq24. Otherwise, # triggers are saved with an additional 'transpose' setting. The old-mutes # value, if true, saves mute-groups as long values (!) instead of bytes. [auto-option-save] auto-save-rc = true save-old-triggers = false save-old-mutes = false # Specifies the last directory used for save/open of MIDI files. [last-used-dir] "" # The most recently-loaded MIDI files. 'full-paths' means to show the full # file-path in the menu. The most recent file (top of list) can be loaded # via 'load-most-recent' at startup. [recent-files] full-paths = false load-most-recent = true count = 0 # End of /home/user/.config/seq66/seq66cli.rc # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/seq66cli/seq66cli.usr ================================================ # Seq66 0.99.20 user ('usr') configuration file # # /home/usr/.config/seq66/seq66cli.usr # Written 2025-05-26 08:48:41 # # 'usr' file. Edit it and place it in ~/.config/seq66. It allows naming each # MIDI bus/port, channel, and control code. [Seq66] config-type = "usr" version = 15 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] Add your comment block here # [user-midi-bus-definitions] # # 1. Define instruments and their control-code names, as applicable. # 2. Define MIDI busses, names, and the instruments on each channel. # # Channels are counted from 0-15, not 1-16. Instruments not set here are set # to -1 and are GM (General MIDI). These labels are shown in MIDI Clocks, # Inputs, the pattern editor buss, channel, and event drop-downs. To disable # entries, set counts to 0. [user-midi-bus-definitions] 0 # number of user-defined MIDI busses # In these MIDI instrument definitions, active (supported by the instrument) # controller numbers are paired with the (optional) name of the controller. [user-instrument-definitions] 0 # instrument list count # [user-interface-settings] # # Configures some user-interface items. Also see [user-ui-tweaks]. # For window styling, use Qt style-sheets as specified in the 'rc' file. # # 'swap-coordinates' swaps numbering so pattern numbers vary fastest by column # instead of rows. This setting applies to the live grid, mute-group buttons, # and set-buttons. # # 'mainwnd-rows' and 'mainwnd-columns' (option '-o sets=RxC') specify # rows/columns in the main grid. R ranges from 4 to 8, C from 4 to 12. # Values other than 4x8 have not been tested thoroughly. # # 'mainwnd-spacing' is for grid buttons; from 0 to 16, default = 2. # # 'default-zoom' is the initial zoom for piano rolls. From 1 to 512, default # = 2. Larger PPQNs require larger zoom to look good. Seq66 adapts the zoom to # the PPQN if set to 0. The unit of zoom is ticks/pixel. # # 'global-seq-feature' applies the key, scale, and background pattern to all # patterns versus separately to each. If all, these values are stored in the # MIDI file in the global SeqSpec versus in each track. # # 'progress-bar-thick specifies a thicker progress bar. Default is 2 pixels, # 1 pixel if set to false. Also affects the slot box border and the boldness # of the slot font. # # 'follow-progress specifies the default for following progress in the piano # rolls. Each window has a button to toggle following progess # # 'gridlines-thick', if false (the default) specifies the normal style of # grid-lines. If true, then lines are thicker. Useful to control the lines # re the palette or theme. # # 'inverse-colors' (option -K/--inverse) specifies use of an inverse color # palette. Palettes are for Seq66 drawing areas, not for Qt widgets. # Normal/inverse palettes can be reconfigured via a 'palette' file. # # 'time-fg-color' and 'time-bg-color' override the default colors for ticks # and time displays (lime on black). 'default' keeps the defaults. 'normal' # uses the theme color. # # 'dark-theme' specifies that a dark theme is active, so that some colors # (e.g. grid-slot text) are inverted. # # 'window-redraw-rate' specifies the base window redraw rate for all windows. # From 10 to 100; default = 40 ms (25 ms for Windows). # # Window-scale (option '-o scale=m.n[xp.q]') specifies scaling the main # window at startup. Defaults to 1.0 x 1.0. If between 0.5 and 3.0, it # changes the size of the main window proportionately. # # 'enable-learn-confirmation' can be set to false to disable the prompt that # the mute-group learn action succeeded. Can be annoying. [user-interface-settings] swap-coordinates = false mainwnd-rows = 4 mainwnd-columns = 8 mainwnd-spacing = 2 default-zoom = 2 global-seq-feature = true progress-bar-thick = true progress-bar-thickness = 2 progress-box-elliptical = false follow-progress = true gridlines-thick = false inverse-colors = false time-fg-color = "default" time-bg-color = "default" dark-theme = false window-redraw-rate = 40 window-scale = 1 window-scale-y = 1 enable-learn-confirmation = true # Seq66 separates file PPQN from the Seq66 PPQN. 'default-ppqn' specifies the # Seq66 PPQN, from 32 to 19200, default = 192. 'use-file-ppqn' (recommended) # indicates to use file PPQN. [user-midi-ppqn] default-ppqn = 192 use-file-ppqn = true # This section specifies the default values to use to jitter the MIDI event # time-stamps and randomize event amplitudes (e.g. velocity for notes). The # range of jitter is 1/j times the current snap value. [user-randomization] jitter-divisor = 8 amplitude = 8 # [user-midi-settings] # # Specifies MIDI-specific variables. -1 means the value isn't used. # # Item Default Range # 'convert-to-smf-1': true true/false. # 'beats-per-bar': 4 1 to 32. # 'beats-per-minute': 120.0 2.0 to 600.0. # 'beat-width': 4 1 to 32. # 'buss-override': -1 (none) -1 to 48. # 'velocity-override': -1 (Free) -1 to 127. # 'bpm-precision': 0 0 to 2. # 'bpm-step-increment': 1.0 0.01 to 25.0. # 'bpm-page-increment': 1.0 0.01 to 25.0. # 'bpm-minimum': 0.0 127.0 # 'bpm-maximum': 0.0 127.0 # # 'convert-to-smf-1' controls if SMF 0 files are split into SMF 1 when read. # 'buss-override' sets the output port for all patterns, for testing, etc. # This value will be saved if you save the MIDI file!!! # 'velocity-override' controls adding notes in the pattern editor; see the # 'Vol' button. -1 ('Free'), preserves incoming velocity. # 'bpm-precision' (spinner and MIDI control) is 0, 1, or 2. # 'bpm-step-increment' affects the spinner and MIDI control. For 1 decimal, # 0.1 is good. For 2, 0.01 is good, 0.05 is faster. Set 'bpm-page-increment' # larger than the step-increment; used with the Page-Up/Page-Down keys in the # spinner. BPM minimum/maximum sets the range in tempo graphing; defaults to # 0.0 to 127.0. Decrease it for a magnified view of tempo. [user-midi-settings] convert-to-smf-1 = true beats-per-bar = 4 beats-per-minute = 120 beat-width = 4 buss-override = -1 velocity-override = -1 bpm-precision = 0 bpm-step-increment = 1 bpm-page-increment = 10 bpm-minimum = 2 bpm-maximum = 600 # [user-options] # # These settings specify some -o or --option switch values. 'daemonize' in # seq66cli indicates that it should run as a service. 'log' specifies a log- # file redirecting output from standard output/error. If no path in the name, # the log is stored in the configuration directory. For no log-file, use # "none" or "". On the command line: '-o log=filename.log'. [user-options] daemonize = false log = "/home/usr/.config/seq66/seq66cli.log" pdf-viewer = "" browser = "" # [user-ui-tweaks] # # key-height specifies the initial height (before vertical zoom) of pattern # editor keys. Defaults to 10 pixels, ranges from 6 to 32. # # key-view specifies the default for showing labels for each key: # 'octave-letters' (default), 'even_letters', 'all-letters', # 'even-numbers', and 'all-numbers'. # # note-resume causes notes-in-progress to resume when the pattern toggles on. # # Note that style-sheet specification has been moved to the 'rc' file with # all the rest of the files. # # A fingerprint is a condensation of note events in a long track, to reduce # the time drawing the pattern in the buttons. Ranges from 32 (default) to # 128. 0 = don't use a fingerprint. # # progress-box-width and -height settings change the scaled size of the # progress box in the live-grid buttons. Width ranges from 0.50 to 1.0, and # the height from 0.10 to 1.0. If either is 'default', defaults (0.8 x 0.3) # are used. progress-box-shown controls if the boxes are shown at all. # progress-box-show-cc enables displaying CC and pitchbend events (as dots). # # progress-note-min and progress-note-max set the progress-box note range so # that notes aren't centered in the box, but shown at their position by pitch. # # lock-main-window prevents the accidental change of size of the main # window. [user-ui-tweaks] key-height = 10 key-view = octave-letters note-resume = false fingerprint-size = 32 progress-box-width = 0.8 progress-box-height = 0.3 progress-box-shown = true progress-box-show-cc = true progress-note-min = 0 progress-note-max = 127 lock-main-window = false # [user-session] # # The session manager to use, if any. 'session' is 'none' (default), 'nsm' # (Non/New Session Manager), or 'jack'. 'url' can be set to the value set by # nsmd when run by command-line. Set 'url' if running nsmd stand-alone; use # the --osc-port number. Seq66 detects if started in NSM. The visibility flag # is used only by NSM to restore visibility. [user-session] session = none url = "" visibility = true # [pattern-editor] # # 'escape-pattern' allows the Esc key to close a pattern editor if not # playing or in paint mode. (Esc exits paint mode or stops playback.) # # 'apply-to-new-only' makes the next options work only when opening a new # pattern. A new pattern is 'Untitled' and has no events. # # These settings for play/record for a pattern editor save time in live # recording. Valid record-style values: 'merge' or 'overdub', 'overwrite', # 'expand', and 'one-shot'. 'wrap-around' allows recorded notes to wrap # to the pattern beginning. Currently 'notemap' and quantizing are # are mutually exclusive. [pattern-editor] escape-pattern = false apply-to-new-only = false armed = false thru = false record = false tighten = false qrecord = false notemap = false record-style = merge wrap-around = false # End of /home/usr/.config/seq66/seq66cli.usr # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/share/applications/seq66.desktop ================================================ [Desktop Entry] Name=Seq66 Version=1.0 GenericName=Live Grid Sequencer Comment=MIDI Loop Sequencer derived from seq24 Comment[fr]=Séquenceur de boucle MIDI dérivé de seq24 Exec=qseq66 Type=Application Terminal=false Icon=qseq66 Categories=Qt;AudioVideo;Audio;Midi;Music;Sequencer;X-Alsa;X-Jack;X-MIDI; X-NSM-Capable=true X-NSM-Exec=qseq66 ================================================ FILE: data/share/doc/info/automation_keys.html ================================================ Main Automation Keystrokes Some of the more important automation keystrokes are described here. The arrow-keys are hardwired in all panes; here they are used when the live grid has focus. For full details, see the user manual or the .../share/doc/seq66-0.99/control_keys.ods spreadsheet. The latter is more likely to be up-to-date.
Start/stop playback with rewind Space
Stop playback and rewind Esc
Pause/continue playback, no rewind . (period)
Panic (stop and turn off all events) ~ (circumflex)
Move to next and previous play-set ] and [
Tap BPM F9
Next and previous play-list Arrow-Down and Arrow-Up
Next and previous play-list song Arrow-Right and Arrow-Left
Open pattern editor window = followed by the desired slot hot-key
================================================ FILE: data/share/doc/info/common_keys.html ================================================ Common Keystrokes Each piano roll panel has keystrokes separate from the automation keystrokes, and thus not configurable. The arrow keys and some other characters provide functions that are common to the pattern edit and song "piano rolls". Time, event, and data panes indirectly support these events.
Standard panel movement keys (no events selected) Arrow-left, Arrow-right, Arrow-up, Arrow-down
vi-like panel movement keys (no events selected) h, j, k, l
Large panel movement keys Page-Up, Page-Down, Home, Ctrl-Home, End, Ctrl-End
Horizontal zoom keys Zoom out 'z', zoom in 'Z', reset '0'
Vertical zoom keys Zoom out 'v', zoom in 'V', reset '0'
Enter painting (notes or triggers) mode i or p
Exit painting mode x (or Esc if not playing)
================================================ FILE: data/share/doc/info/mute_group_keys.html ================================================ Mute group Keys These keys map to the on-screen 4x8 grid. They are used for toggling multiple patterns (a "mute group") at once. It is generally good to lay them out to match the pattern hot keys. When group-learn is initiated, the next keystroke is automatically shifted, so that "1" becomes "!", "a" becomes "A", etc. This makes it easy to use the learn keys. Another difference from the hot-keys is that there are always 32 mute-groups available, no matter the set size or layout. Note that these keys are configurable in the 'ctrl' file. But one won't generally want to change them except to adapt to a non-US keyboard.
#0
- ! -
#4
- @ -
#8
- # -
#12
- $ -
#16
- % -
#20
- ^ -
#24
- & -
#28
- * -
#1
- Q -
#5
- W -
#9
- E -
#13
- R -
#17
- T -
#21
- Y -
#25
- U -
#29
- I -
#2
- A -
#6
- S -
#10
- D -
#14
- F -
#18
- G -
#22
- H -
#26
- J -
#30
- K -
#3 -
- Z -
#9 -
- X -
#11 -
- C -
#15 -
- V -
#19 -
- B -
#23 -
- N -
#27 -
- M -
#31 -
- < -
================================================ FILE: data/share/doc/info/pattern_hotkeys.html ================================================ Slot/Pattern Hot Keys These keys map to the on-screen 4x8 grid. They can be used for toggling a pattern in the grid, as well as opening a pattern editor or event editor, turning on recording, and many other actions. Note that there are options to reverse the grid to 8x4 and to change the size of the grid, including making it up to 8x12. (In that case, the slot-shift key, "/" is needed.) In the following cells, the top number is the pattern number (starting at 0) and the bottom item is the hot key. Note that these keys are configurable in the 'ctrl' file. But one won't generally want to change them except to adapt to a non-US keyboard.
#0
- 1 -
#4
- 2 -
#8
- 3 -
#12
- 4 -
#16
- 5 -
#20
- 6 -
#24
- 7 -
#28
- 8 -
#1
- q -
#5
- w -
#9
- e -
#13
- r -
#17
- t -
#21
- y -
#25
- u -
#29
- i -
#2
- a -
#6
- s -
#10
- d -
#14
- f -
#18
- g -
#22
- h -
#26
- j -
#30
- k -
#3 -
- z -
#9 -
- x -
#11 -
- c -
#15 -
- v -
#19 -
- b -
#23 -
- n -
#27 -
- m -
#31 -
- , -
================================================ FILE: data/share/doc/info/seqroll_keys.html ================================================ Pattern Piano-roll Keystrokes Some of the Tools operations in the pattern editor have keystroke support in the piano roll ("seqroll"). Other keys (e.g. the arrow and hijk movement keys without selected events) are Common keystrokes.
Move selected notes by snap or note value Arrow-left, Arrow-right, Arrow-up, Arrow-down
Move selected notes by snap or note value Ctrl-Arrow-left, Ctrl-Arrow-right, Ctrl-Arrow-up, Ctrl-Arrow-down
Delete selected notes Delete or Backspace
Select all notes Ctrl-A
Repitch (change) selected notes c
Copy selected notes Ctrl-C
Drop (clear) clipboard Ctrl-D
Select events by channel Ctrl-E
Edge-fix notes f
Jitter (timing) selected notes J
Analyze notes for scale Ctrl-K
Set note-map recording mode N
Select notes by channel Ctrl-N
Enter note-painting/insert mode i or p
Toggle Quantize recording Q
Quantize selected notes timing q
Toggle recording mode R
Randomize select notes (amplitude) r
Tighten selected notes timing t
Verify and link Note Offs to Note Ons =
Remove unlinked note events. u
Paste Ctrl-V
Exit note-painting/insert mode x or Esc (if not playing)
Cut Ctrl-X
Undo Ctrl-Z
Redo Ctrl-Shift-Z
================================================ FILE: data/share/doc/info/songroll_keys.html ================================================ Song Piano-roll Keystrokes Some of the operations have keystroke support. Movement keys when no song triggers are selected are like the "Common" keystrokes, as are the playback start/stop/continue keys and the "insert/paint" keys.
Delete selected trigger Delete or Backspace
Copy/Cut/Paste Ctrl-C, Ctrl-X, Ctrl-V, Ctrl-Z, Shift-Ctrl-Z
Move selected trigger by snap Arrow-left, -right
================================================ FILE: data/share/doc/tutorial/configuration.html ================================================ Seq66 Basic Tutorial: Configuration

Seq66 Basic Tutorial

Configuration

 

The figure below shows Edit / Preferences ; it provides some configuration items; it can restart the application to load the new settings.

 
Seq66 Configuration. Configuration is stored in a number of files, shown here with wildcards:
  • Linux: /home/user/.config/seq66/qseq66.*
  • Windows: C:/Users/user/AppData/Local/seq66/qpseq66.*
Some settings can be made in Edit / Preferences; other options less often modified, or very complex, require editing a file. The file extensions:
  • rc. The main configuration file. Sets MIDI I/O ports, port mapping, names/status of the other configuration files, "recent files", etc.
  • usr. Configures instrument and buss names, user-interface settings, default song settings, and a few more.
  • ctrl. Keystrokes/MIDI events to control Seq66. By default, only keystrokes are set up. Edit it to add MIDI control, status-display events, and MIDI macros. A Novation LaunchPad Mini setup provides an example.
  • mutes. Mute-group settings; can be edited in the Live grid and in the Mutes tab. See the user manual.
  • playlist. This file contains play-lists, editable in the Playlist tab or in the play-list file. See the user manual.
  • drums. Contains note mappings, useful for converting from old MIDI drum sets to GM drum sets. See the user manual.
  • palette. Control of pattern colors, user-interface coloring in Seq66-drawn areas (e.g. piano rolls). It can set brush patterns for drawing notes, scales, and background patterns. Must be edited with a text editor.

  • qss. Qt style sheets, which can be used to change the colors of Qt-drawn elements, overriding the theme settings

Seq66 preferences dialog

Preferences Dialog

The Seq66 configuration has a large number of options. In addition to the user's manual, the configuration files contain comments that explain the options, and might be worth reading.

================================================ FILE: data/share/doc/tutorial/css/dark-slide.css ================================================ /* * File: emac-slide.css background-color: #004040; background-color: #004040; * Library: seq66/data/share/tutorial * Author: Chris Ahlstrom * Date: 2022-05-26 * Updates: 2022-05-27 * Version: $Revision$ * License: $XPC_SUITE_GPL_LICENSES$ */ body,h1,h2,h3,h4,h5,h6,p,td,th,ul,dl,div { font-family-xxx: Geneva, Arial, Helvetica, sans-serif; font-family: Roman, Times, serif; } :root { --bg-color: #404040; --nav-color: #606060; --dir-color: #404040; --intro-color: #404040; --ul-color: #404040; --main-color: #404040; --note-color: Gainsboro; --text-color: White; --label-color: Salmon; --treelink-color: White; --hdr-color: CornflowerBlue; --hdr-text-color: Black; } body { /* * background-image: url('../img/stock/OS5.png'); * background-repeat: no-repeat; * background-attachment: fixed; */ background-color: var(--bg-color); color: var(--text-color); margin-top: 0px; margin-bottom: 0px; margin-right: 20px; margin-left: 20px; } body,td { font-size: 90%; } /* * Dang. Affects all tables. TABLE { width: 816; height: 512; border: 1; } * */ table { border: 1; } h1 { text-align: center; font-size: 160%; } h2 { font-size: 120%; } h3 { font-size: 100%; } caption { font-weight: bold; font-size: 100%; } td.caption { font-weight: bold; font-size: 90%; text-align: center; padding: 10px; } div.nav { width: 100%; background-color: var(--nav-color); border-width: 1px; border-style: solid; border-color: #84B0C7; color: var(--text-color); text-align: center; margin: 2px; padding: 2px; line-height: 140%; } table.navtab { background-color: var(--nav-color); border-width: 2px; border-style: solid; border-color: green; text-align: center; margin: 4px; margin-right: 15px; padding: 4px; } a.code:link { text-decoration: none; font-weight: normal; color: #0000FF; } a.code:visited { text-decoration: none; font-weight: normal; color: #0000FF; } a { color: #A0FFA0; } a:active { color: #FFA0A0; } a:focus { color: #A0A0FF; } a:visited { color: #FFA0FF; } a:hover { text-decoration: none; background-color: #f2f2ff; } .previous { text-decoration: none; font-weight: bold; background-color: cyan; color: Black; } .home { text-decoration: none; font-weight: bold; background-color: var(--bg-color); color: Black; } .next { text-decoration: none; font-weight: bold; background-color: #00FF00; color: Black; } dl.el { margin-left: -1cm; } .fragment { font-family: monospace, fixed; font-size: 95%; } pre.fragment { border-width: 1px; border-style: solid; border-color: #C0C0C0; background-color: var(--bg-color); margin-top: 4px; margin-bottom: 4px; margin-left: 2px; margin-right: 8px; padding-left: 6px; padding-right: 6px; padding-top: 4px; padding-bottom: 4px; } div.ah { background-color: Black; font-weight: bold; color: #ffffff; margin-bottom: 3px; margin-top: 3px; } span.keyword { color: #008000; } span.keywordtype { color: #604020; } span.keywordflow { color: #e08000; } span.comment { color: #800000; } span.preprocessor { color: #806020; } span.stringliteral { color: #002080; } span.charliteral { color: #008080; } .search { color: #003399; font-weight: bold; } form.search { margin-bottom: 0px; margin-top: 0px; } input.search { font-size: 75%; color: #000080; font-weight: normal; background-color: #e8eef2; } td.tiny { font-size: 75%; } hr { height: 1px; border: none; border-width: 1px; border-style: solid; border-color: Black; } /* * For the tree view. */ body.tree { background-color: var(--bg-color); color: var(--text-color); } a.treelink { color: var(--treelink-color); text-decoration: none; font-weight: bold; } a.treelink:active { background-color: #002020; color: Red; } a.treelink:focus { background-color: #002020; color: Green; } a.treelink:visited { background-color: #002020; color: var(--text-color); } .directory { font-size: 9pt; font-weight: bold; background-color: var(--dir-color); border-width: 2px; border-style: solid; border-color: green; margin: 4px; margin-right: 15px; padding: 4px; } .directory H3 { margin: 0px; margin-top: 1em; font-size: 11pt; } .directory > H3 { margin-top: 0; } .directory P { margin: 0px; white-space: nowrap; } .directory DIV { display: none; margin: 0px; } .directory IMG { vertical-align: -30%; } /* End styling for the tree view */ /* For tutorial slides */ td.slideheader { height: 40px; background-color: var(--hdr-color); border-width: 2px; border-style: solid; border-color: #808080; color: var(--hdr-text-color); } p.slideheader { margin-top: 0px; margin-bottom: 0px; font-weight: bold; font-size: 150%; color: var(--hdr-text-color); } p.slidetopic { margin-top: 0px; margin-bottom: 0px; font-style: oblique; font-weight: bold; font-size: 115%; } td.slideintro { background-color: var(--intro-color); border-width: 2px; border-style: solid; border-color: #C0C0C0; } p.slideintro { background-color: var(--intro-color); color: var(--text-color); font-weight: normal; font-size: 110%; margin-top: 0px; margin-bottom: 0px; } ul { background-color: var(--ul-color); color: var(--text-color); } .slidemain { background-color: var(--main-color); color: var(--text-color); font-weight: normal; font-size: 110%; margin-bottom: 6px; } td.slidenote { background-color: var(--note-color); border-width: 1px; border-style: solid; border-color: #808080; color: Black; /* text-color */ } a.slidenote { color: Black; /* text-color */ } .slidenote { margin-top: 0px; margin-bottom: 0px; font-style: italic; font-size: 100%; color: Black; /* text-color */ } .slidelabel { font-size: 125%; font-weight: bold; font-style: oblique; color: var(--label-color); } ================================================ FILE: data/share/doc/tutorial/css/emac-slide.css ================================================ /* * File: emac-slide.css background-color: #004040; background-color: #004040; * Library: seq66/data/share/tutorial * Author: Chris Ahlstrom * Date: 2022-05-26 * Updates: 2022-05-27 * Version: $Revision$ * License: $XPC_SUITE_GPL_LICENSES$ */ body,h1,h2,h3,h4,h5,h6,p,td,th,ul,dl,div { font-family-xxx: Geneva, Arial, Helvetica, sans-serif; font-family: Roman, Times, serif; } :root { --bg-color: #004040; --nav-color: #006060; --dir-color: #004040; --intro-color: #004040; --ul-color: #004040; --main-color: #004040; --note-color: Gainsboro; --text-color: Wheat; --label-color: Salmon; --treelink-color: White; --hdr-color: CornflowerBlue; --hdr-text-color: Black; } body { /* * background-image: url('../img/stock/OS5.png'); * background-repeat: no-repeat; * background-attachment: fixed; */ background-color: var(--bg-color); color: var(--text-color); margin-top: 0px; margin-bottom: 0px; margin-right: 20px; margin-left: 20px; } body,td { font-size: 90%; } /* * Dang. Affects all tables. TABLE { width: 816; height: 512; border: 1; } * */ table { border: 1; } h1 { text-align: center; font-size: 160%; } h2 { font-size: 120%; } h3 { font-size: 100%; } caption { font-weight: bold; font-size: 100%; } td.caption { font-weight: bold; font-size: 90%; text-align: center; padding: 10px; } div.nav { width: 100%; background-color: var(--nav-color); border-width: 1px; border-style: solid; border-color: #84B0C7; color: var(--text-color); text-align: center; margin: 2px; padding: 2px; line-height: 140%; } table.navtab { background-color: var(--nav-color); border-width: 2px; border-style: solid; border-color: green; text-align: center; margin: 4px; margin-right: 15px; padding: 4px; } a.code:link { text-decoration: none; font-weight: normal; color: #0000FF; } a.code:visited { text-decoration: none; font-weight: normal; color: #0000FF; } a { color: #A0FFA0; } a:active { color: #FFA0A0; } a:focus { color: #A0A0FF; } a:visited { color: #FFA0FF; } a:hover { text-decoration: none; background-color: #f2f2ff; } .previous { text-decoration: none; font-weight: bold; background-color: cyan; color: Black; } .home { text-decoration: none; font-weight: bold; background-color: var(--bg-color); color: Black; } .next { text-decoration: none; font-weight: bold; background-color: #00FF00; color: Black; } dl.el { margin-left: -1cm; } .fragment { font-family: monospace, fixed; font-size: 95%; } pre.fragment { border-width: 1px; border-style: solid; border-color: #C0C0C0; background-color: var(--bg-color); margin-top: 4px; margin-bottom: 4px; margin-left: 2px; margin-right: 8px; padding-left: 6px; padding-right: 6px; padding-top: 4px; padding-bottom: 4px; } div.ah { background-color: Black; font-weight: bold; color: #ffffff; margin-bottom: 3px; margin-top: 3px; } span.keyword { color: #008000; } span.keywordtype { color: #604020; } span.keywordflow { color: #e08000; } span.comment { color: #800000; } span.preprocessor { color: #806020; } span.stringliteral { color: #002080; } span.charliteral { color: #008080; } .search { color: #003399; font-weight: bold; } form.search { margin-bottom: 0px; margin-top: 0px; } input.search { font-size: 75%; color: #000080; font-weight: normal; background-color: #e8eef2; } td.tiny { font-size: 75%; } hr { height: 1px; border: none; border-width: 1px; border-style: solid; border-color: Black; } /* * For the tree view. */ body.tree { background-color: var(--bg-color); color: var(--text-color); } a.treelink { color: var(--treelink-color); text-decoration: none; font-weight: bold; } a.treelink:active { background-color: #002020; color: Red; } a.treelink:focus { background-color: #002020; color: Green; } a.treelink:visited { background-color: #002020; color: var(--text-color); } .directory { font-size: 9pt; font-weight: bold; background-color: var(--dir-color); border-width: 2px; border-style: solid; border-color: green; margin: 4px; margin-right: 15px; padding: 4px; } .directory H3 { margin: 0px; margin-top: 1em; font-size: 11pt; } .directory > H3 { margin-top: 0; } .directory P { margin: 0px; white-space: nowrap; } .directory DIV { display: none; margin: 0px; } .directory IMG { vertical-align: -30%; } /* End styling for the tree view */ /* For tutorial slides */ td.slideheader { height: 40px; background-color: var(--hdr-color); border-width: 2px; border-style: solid; border-color: #808080; color: var(--hdr-text-color); } p.slideheader { margin-top: 0px; margin-bottom: 0px; font-weight: bold; font-size: 150%; color: var(--hdr-text-color); } p.slidetopic { margin-top: 0px; margin-bottom: 0px; font-style: oblique; font-weight: bold; font-size: 115%; } td.slideintro { background-color: var(--intro-color); border-width: 2px; border-style: solid; border-color: #C0C0C0; } p.slideintro { background-color: var(--intro-color); color: var(--text-color); font-weight: normal; font-size: 110%; margin-top: 0px; margin-bottom: 0px; } ul { background-color: var(--ul-color); color: var(--text-color); } .slidemain { background-color: var(--main-color); color: var(--text-color); font-weight: normal; font-size: 110%; margin-bottom: 6px; } td.slidenote { background-color: var(--note-color); border-width: 1px; border-style: solid; border-color: #808080; color: Black; /* text-color */ } a.slidenote { color: Black; /* text-color */ } .slidenote { margin-top: 0px; margin-bottom: 0px; font-style: italic; font-size: 100%; color: Black; /* text-color */ } .slidelabel { font-size: 125%; font-weight: bold; font-style: oblique; color: var(--label-color); } ================================================ FILE: data/share/doc/tutorial/css/light-slide.css ================================================ /* * File: light-slide.css * Library: seq66/data/share/tutorial * Author: Chris Ahlstrom * Date: 2022-05-17 * Updates: 2022-05-27 * Version: $Revision$ * License: $XPC_SUITE_GPL_LICENSES$ */ body,h1,h2,h3,h4,h5,h6,p,td,th,ul,dl,div { font-family-xxx: Geneva, Arial, Helvetica, sans-serif; font-family: Roman, Times, serif; } :root { --bg-color: #FFFFFF; --nav-color: #E8EEF2; --dir-color: #E8EEF2; --intro-color: #E8EEF2; --ul-color: #F0F0F0; --main-color: #F0F0F0; --note-color: #EBEEF2; --text-color: Black; --label-color: Red; --treelink-color: Blue; --hdr-color: #C0C0C0; --hdr-text-color: Black; } body { /* * background-image: url('../img/stock/OS5.png'); * background-repeat: no-repeat; * background-attachment: fixed; */ background-color: var(--bg-color); color: var(--text-color); margin-top: 0px; margin-bottom: 0px; margin-right: 20px; margin-left: 20px; } body,td { font-size: 90%; } /* * Dang. Affects all tables. TABLE { width: 816; height: 512; border: 1; } * */ table { border: 1; } h1 { text-align: center; font-size: 160%; } h2 { font-size: 120%; } h3 { font-size: 100%; } caption { font-weight: bold; font-size: 100%; } td.caption { font-weight: bold; font-size: 90%; text-align: center; padding: 10px; } div.nav { width: 100%; background-color: var(--nav-color); border-width: 1px; border-style: solid; border-color: #84B0C7; color: var(--text-color); text-align: center; margin: 2px; padding: 2px; line-height: 140%; } table.navtab { background-color: var(--nav-color); border-width: 2px; border-style: solid; border-color: green; text-align: center; margin: 4px; margin-right: 15px; padding: 4px; } a.code:link { text-decoration: none; font-weight: normal; color: #0000FF; } a.code:visited { text-decoration: none; font-weight: normal; color: #0000FF; } .previous { text-decoration: none; font-weight: bold; background-color: cyan; color: Black; } .home { text-decoration: none; font-weight: bold; background-color: var(--bg-color); color: Black; } .next { text-decoration: none; font-weight: bold; background-color: #00FF00; color: White; } dl.el { margin-left: -1cm; } .fragment { font-family: monospace, fixed; font-size: 95%; } pre.fragment { border-width: 1px; border-style: solid; border-color: #C0C0C0; background-color: var(--bg-color); margin-top: 4px; margin-bottom: 4px; margin-left: 2px; margin-right: 8px; padding-left: 6px; padding-right: 6px; padding-top: 4px; padding-bottom: 4px; } div.ah { background-color: Black; font-weight: bold; color: #ffffff; margin-bottom: 3px; margin-top: 3px; } span.keyword { color: #008000; } span.keywordtype { color: #604020; } span.keywordflow { color: #e08000; } span.comment { color: #800000; } span.preprocessor { color: #806020; } span.stringliteral { color: #002080; } span.charliteral { color: #008080; } .search { color: #003399; font-weight: bold; } form.search { margin-bottom: 0px; margin-top: 0px; } input.search { font-size: 75%; color: #000080; font-weight: normal; background-color: #e8eef2; } td.tiny { font-size: 75%; } hr { height: 1px; border: none; border-width: 1px; border-style: solid; border-color: Black; } /* * For the tree view. */ body.tree { background-color: var(--bg-color); color: var(--text-color); } a.treelink { color: var(--treelink-color); text-decoration: none; font-weight: bold; } .directory { font-size: 9pt; font-weight: bold; background-color: var(--dir-color); border-width: 2px; border-style: solid; border-color: green; margin: 4px; margin-right: 15px; padding: 4px; } .directory H3 { margin: 0px; margin-top: 1em; font-size: 11pt; } .directory > H3 { margin-top: 0; } .directory P { margin: 0px; white-space: nowrap; } .directory DIV { display: none; margin: 0px; } .directory IMG { vertical-align: -30%; } /* End styling for the tree view */ /* For tutorial slides */ td.slideheader { height: 40px; background-color: var(--hdr-color); border-width: 2px; border-style: solid; border-color: #808080; color: var(--hdr-text-color); } p.slideheader { margin-top: 0px; margin-bottom: 0px; font-weight: bold; font-size: 150%; color: var(--hdr-text-color); } p.slidetopic { margin-top: 0px; margin-bottom: 0px; font-style: oblique; font-weight: bold; font-size: 115%; } td.slideintro { background-color: var(--intro-color); border-width: 2px; border-style: solid; border-color: #C0C0C0; } p.slideintro { background-color: var(--intro-color); color: var(--text-color); font-weight: normal; font-size: 110%; margin-top: 0px; margin-bottom: 0px; } ul { background-color: var(--ul-color); color: var(--text-color); } .slidemain { background-color: var(--main-color); color: var(--text-color); font-weight: normal; font-size: 110%; margin-bottom: 6px; } td.slidenote { background-color: var(--note-color); border-width: 1px; border-style: solid; border-color: #C0C0C0; color: var(--text-color); } .slidenote { margin-top: 0px; margin-bottom: 0px; font-style: italic; font-size: 90%; color: var(--text-color); } .slidelabel { font-size: 125%; font-weight: bold; font-style: oblique; color: var(--label-color); } ================================================ FILE: data/share/doc/tutorial/css/slide.css ================================================ /* * File: light-slide.css * Library: seq66/data/share/tutorial * Author: Chris Ahlstrom * Date: 2022-05-17 * Updates: 2022-05-27 * Version: $Revision$ * License: $XPC_SUITE_GPL_LICENSES$ */ body,h1,h2,h3,h4,h5,h6,p,td,th,ul,dl,div { font-family-xxx: Geneva, Arial, Helvetica, sans-serif; font-family: Roman, Times, serif; } :root { --bg-color: #FFFFFF; --nav-color: #E8EEF2; --dir-color: #E8EEF2; --intro-color: #E8EEF2; --ul-color: #F0F0F0; --main-color: #F0F0F0; --note-color: #EBEEF2; --text-color: Black; --label-color: Red; --treelink-color: Blue; --hdr-color: #C0C0C0; --hdr-text-color: Black; } body { /* * background-image: url('../img/stock/OS5.png'); * background-repeat: no-repeat; * background-attachment: fixed; */ background-color: var(--bg-color); color: var(--text-color); margin-top: 0px; margin-bottom: 0px; margin-right: 20px; margin-left: 20px; } body,td { font-size: 90%; } /* * Dang. Affects all tables. TABLE { width: 816; height: 512; border: 1; } * */ table { border: 1; } h1 { text-align: center; font-size: 160%; } h2 { font-size: 120%; } h3 { font-size: 100%; } caption { font-weight: bold; font-size: 100%; } td.caption { font-weight: bold; font-size: 90%; text-align: center; padding: 10px; } div.nav { width: 100%; background-color: var(--nav-color); border-width: 1px; border-style: solid; border-color: #84B0C7; color: var(--text-color); text-align: center; margin: 2px; padding: 2px; line-height: 140%; } table.navtab { background-color: var(--nav-color); border-width: 2px; border-style: solid; border-color: green; text-align: center; margin: 4px; margin-right: 15px; padding: 4px; } a.code:link { text-decoration: none; font-weight: normal; color: #0000FF; } a.code:visited { text-decoration: none; font-weight: normal; color: #0000FF; } .previous { text-decoration: none; font-weight: bold; background-color: cyan; color: Black; } .home { text-decoration: none; font-weight: bold; background-color: var(--bg-color); color: Black; } .next { text-decoration: none; font-weight: bold; background-color: #00FF00; color: White; } dl.el { margin-left: -1cm; } .fragment { font-family: monospace, fixed; font-size: 95%; } pre.fragment { border-width: 1px; border-style: solid; border-color: #C0C0C0; background-color: var(--bg-color); margin-top: 4px; margin-bottom: 4px; margin-left: 2px; margin-right: 8px; padding-left: 6px; padding-right: 6px; padding-top: 4px; padding-bottom: 4px; } div.ah { background-color: Black; font-weight: bold; color: #ffffff; margin-bottom: 3px; margin-top: 3px; } span.keyword { color: #008000; } span.keywordtype { color: #604020; } span.keywordflow { color: #e08000; } span.comment { color: #800000; } span.preprocessor { color: #806020; } span.stringliteral { color: #002080; } span.charliteral { color: #008080; } .search { color: #003399; font-weight: bold; } form.search { margin-bottom: 0px; margin-top: 0px; } input.search { font-size: 75%; color: #000080; font-weight: normal; background-color: #e8eef2; } td.tiny { font-size: 75%; } hr { height: 1px; border: none; border-width: 1px; border-style: solid; border-color: Black; } /* * For the tree view. */ body.tree { background-color: var(--bg-color); color: var(--text-color); } a.treelink { color: var(--treelink-color); text-decoration: none; font-weight: bold; } .directory { font-size: 9pt; font-weight: bold; background-color: var(--dir-color); border-width: 2px; border-style: solid; border-color: green; margin: 4px; margin-right: 15px; padding: 4px; } .directory H3 { margin: 0px; margin-top: 1em; font-size: 11pt; } .directory > H3 { margin-top: 0; } .directory P { margin: 0px; white-space: nowrap; } .directory DIV { display: none; margin: 0px; } .directory IMG { vertical-align: -30%; } /* End styling for the tree view */ /* For tutorial slides */ td.slideheader { height: 40px; background-color: var(--hdr-color); border-width: 2px; border-style: solid; border-color: #808080; color: var(--hdr-text-color); } p.slideheader { margin-top: 0px; margin-bottom: 0px; font-weight: bold; font-size: 150%; color: var(--hdr-text-color); } p.slidetopic { margin-top: 0px; margin-bottom: 0px; font-style: oblique; font-weight: bold; font-size: 115%; } td.slideintro { background-color: var(--intro-color); border-width: 2px; border-style: solid; border-color: #C0C0C0; } p.slideintro { background-color: var(--intro-color); color: var(--text-color); font-weight: normal; font-size: 110%; margin-top: 0px; margin-bottom: 0px; } ul { background-color: var(--ul-color); color: var(--text-color); } .slidemain { background-color: var(--main-color); color: var(--text-color); font-weight: normal; font-size: 110%; margin-bottom: 6px; } td.slidenote { background-color: var(--note-color); border-width: 1px; border-style: solid; border-color: #C0C0C0; color: var(--text-color); } .slidenote { margin-top: 0px; margin-bottom: 0px; font-style: italic; font-size: 90%; color: var(--text-color); } .slidelabel { font-size: 125%; font-weight: bold; font-style: oblique; color: var(--label-color); } ================================================ FILE: data/share/doc/tutorial/faq.html ================================================ Seq66 Basic Tutorial

Seq66 Basic Tutorial

Frequently Asked Questions

 

This section tries to anticipate the most common problems that can occur with Seq66 under the various setups and operating systems.

 
  • When Seq66 runs, it gives port errors or does not show all ports. When Seq66 starts for the first time (i.e. no configuration files yet exist), it scans the system for existing ports. On Windows, it disabled ports that are already grabbed by the Windows MIDI Mapper. If later devices are removed or added to the system, the user has the option of rebuilding the port maps by going to Edit / Preferences / MIDI Clock and clicking the Make Maps button. Then restart Seq66 and make sure of the desired set of ports. (One can also edit the 'rc' file manually while Seq66 is not running.)
  • Clicking on the Edit / Preferences / MIDI Clock tab does not work. It actually does work, but in some themes one has to click in the exact right spot. Why? Don't know. Another option is to use Alt-C to move to that tab.
  • Undo/redo does not work consistently. While Seq66 makes great improvements on the management of modifications, there are currently still issues with some modifications. Make a bug report and we'll see if the particular issue can be fixed.
 
 

As time goes on, this FAQ will be updated, especially as requested by user input at GitHub: https://github.com/ahlstromcj/seq66

 

Updated 2023-06-01

================================================ FILE: data/share/doc/tutorial/home.html ================================================ Seq66 Basic Tutorial

Seq66 Basic Tutorial

Home Page

 

This tutorial describes the basics of Seq66; it includes enough to get started with creating a song and understanding the configuration. It is a quick introduction. For the complete manual, see the Help / User Manual command. If this fails, send an e-mail reporting this problem and your setup (operating system, default browser and PDF viewer settings) to ahlstromcj@gmail.com . Or find the complete manual, seq66-user-manual.pdf, at:

  • Seq66 User Manual (at ahlstromcj.github.io)
  • /usr/share/doc/seq66-0.99 or /usr/local/share/doc/seq66-0.99 (Linux)
  • C:/Program Files/Seq66/doc (Windows)
  • seq66/data/share/doc (Seq66 source-code package)

This tutorial is simply for familiarizing people with this live-grid MIDI sequencer application.

 

This tutorial starts with a quick survey of the most important elements, which cover creating tracks (patterns) and modifying them. More advanced elements (sets, mute-groups, playlists) are best covered in the user's manual, not this tutorial.

Once the basics have been summarized, the tutorial begins, covering startup, configuration, creating a tune, and laying it out as a song performance.

Use the side-bar at the left for fast navigation.

Note that this tutorial and the user's manual are available in Seq66 via the Help menu.

 
 

As time goes on, this tutorial will be tweaked, especially as requested by user input at GitHub: https://github.com/ahlstromcj/seq66

 

Updated 2023-07-19

================================================ FILE: data/share/doc/tutorial/images/README ================================================ psxc/doc/tutorial/slides/img/dox/README -- Chris Ahlstrom 12/31/2006 This directory contains stock images obtained from Doxygen, plus our own images. ================================================ FILE: data/share/doc/tutorial/index.html ================================================ Seq66 Basic Tutorial <body bgcolor="FFFFFF" text="000000" link="blue" vlink="purple" marginwidth=0 marginheight=0 leftmargin=0 topmargin=0> </body> ================================================ FILE: data/share/doc/tutorial/introduction.html ================================================ Seq66 Basic Tutorial

Seq66 Basic Tutorial

Introduction

 

2024-10-26 This section summarizes the main components of the Seq66 graphical user interface.

 

  • Main Window / Live Grid. Seq66 starts with a main window that has a standard menu, a row of controls involving the basic timing of MIDI playback, a set of tabs, including a Live tab that presents a grid of patterns, with other controls, including playback controls, at the bottom. In Live mode, all patterns play continuously, over and over. The Live grid can also be shown in an external window for flexibility. A MIDI file can be dragged onto the live grid to open it.
  • Song Editor. The Song Editor provides a way to lay out a complete performance, in much the same manner as other MIDI sequencers. The Song Editor can also be shown in an external window for flexibility.
  • Pattern Editor. The Pattern Editor is a way to record MIDI events and paint MIDI events, and view them. The Patter Editor can also be shown in multiple external windows for flexibility.
  • Event Editor. The Event Editor is a way to view individual event and correct them, if necessary. This tab is explained in more detail in the Seq66 User Manual.
  • Playlists. The Playlists is a way to move back and forth through a collection of MIDI songs. See the Seq66 User Manual.
  • Sets. Sets are collections of patterns that can be loaded into the Live Grid in order to change the way the arrangement is playing. See the Seq66 User Manual.
  • Mutes. Mutes ("mute groups") provide a mute and unmute a number of patterns at the same time. See the Seq66 User Manual.
  • Session. The Session tab shows information about the current run of Seq66. See the Seq66 User Manual.

In addition to the interfacs described above, Seq66 handles SMF 0 and 1 MIDI formats, provides for control of Seq66 by a MIDI controller such as the Novation Launchpad (as well as status display by such a device), a large and flexible set of keystroke controls, port mapping, support for JACK (on Linux), Windows, and more.

 
 

As noted, much more is explained in the Seq66 User Manual. Once you grok Seq66 the heavily-indexed user manual can fill in the details. Or, use good old trial-and-error. If you find the diagrams too small, just fire up Seq66 and bring up the screen item at issue.

================================================ FILE: data/share/doc/tutorial/left-tree.html ================================================ Tree View

Seq66
================================================ FILE: data/share/doc/tutorial/main_window.html ================================================ Seq66 Basic Tutorial: Main Window

Seq66 Basic Tutorial

Main Window

 

The figure below is an annotated version of the main window. It is a bit small, so run qseq66 (qpseq66.exe in Windows) to see the window elements.

 
  • Menu. Provides some expected functions, including opening, saving, import/export, recent files, help....
  • Main Top bar. Provides status and time information, and allows for changing the MIDI bus and time signature of the whole song.
  • Tabs. Tabs give compact access to various views of Seq66.
  • Patterns Panel (Live Grid). Shows a single set of patterns ("tracks", "loops", "sequences"). Patterns can be muted/unmuted by clicking on them. Patterns can also be opened for editing via a right-click menu. Keystrokes ("hot-keys") can also activate or edit a pattern.
  • Grid Mode. Selects what a click or hot-key does to a pattern. "Loop" toggles the track. "Record" mode toggles recording. There are other modes, too.
  • Main Bottom. The first row at the bottom allows for naming and changing the active set. The second bottom row has standard play/record buttons, plus a Song/Live toggle button and a beats-per-minute indicator.

Seq66 main window with Live grid

The "Live" Grid

This window is packed with functionality. Be sure to try clicking or right clicking on the grid and the various buttons. Hover the mouse cursor over an interface item; often a tool-tip will appear.

================================================ FILE: data/share/doc/tutorial/main_window_patterns.html ================================================ Seq66 Basic Tutorial: Main Window

Seq66 Basic Tutorial

Pattern Slots

 
The figure below shows the layout of patterns slots, and the right-click menu for a pattern slot.
 

The Slot. Pattern slots are the square buttons in the Live Grid. Note the following contents of a slot, if it is not an empty/blank slot:

  • Track Title. This holds a short title for the slot's track.
  • Measures. The number of measures of a pattern can be any value. Usually the patterns are short: 1, 2, 4, 8 ... measures. Whatever the song needs.
  • Status. Most often, a track is either "Muted" (silent) or "Armed". Other statuses are "Queued" and "One-shot", which are explained in the user's manual.
  • Progress Box. The rectangle in the center of the slot represents the "color" of the track (if any), the events in the track, and the progress bar (if armed).
  • Pattern Number. Each pattern is numbered from 0 to 31. Pattern numbers vary fastest by row; unusual, but can be changed. See the user's manual.
  • Port-Channel Numbers. Each pattern is assigned MIDI port and output channel numbers. Ports numbers start at 0; port names are shown in Edit / Preferences. Channels go from 1 to 16; "F" means there's no output channel.
  • Time Signature. The time signature of the pattern merely determines how the track looks in the pattern editor.
  • Right-click Menu. This menu provides for creating/editing a pattern (also doable by a double-click or a hot-key). Patterns can be copied, moved, merged, have a color shown, etc.

Seq66 pattern slots in the Live grid

Slots and Right-Click Menu

Patterns go by many names: tracks, loops, sequences (!), and slots.

================================================ FILE: data/share/doc/tutorial/mutes_manager.html ================================================ Seq66 Basic Tutorial: Mutes Manager

Seq66 Basic Tutorial

Mutes Manager

 

The figure at right shows the Mutes tab. It is a bit small, so run qseq66 (Windows: qpseq66.exe) to see the window elements. This tab is quite complex, so we will not describe its operation in detail here. See the user's manual.

 
  • Mute group storage. Mute groups can be of the song, and can be stored in the .midi file. They are stored in a sequencer-specific (SeqSpec) section of the song, and so are not available to other sequencers. Mute groups can also be reused by storing them in a .mutes configuration file.
  • Number of mute groups. Currently, up to 32 mute groups can be defined. Mute groups are numbered from 0 to 31. This is about the maximum useful number, since most of the other keys on the computer keyboard have functions allocated to them.
  • Mute group features. Each mute group has a number, a hot-key, and a name. Currently the names are hardwired and simple: "Group 0", etc. In addition to a hot-key, a MIDI control can be assigned to mute groups in the .ctrl file.
  • Group grid. In the Mutes tab, the top grid shows the 32 places for the mute groups.
  • Group pattern grid. In the Mutes tab, the bottom grid shows the patterns that are enabled/disabled in the currently-selected mute group.

  • Tricky. The sets tab is a bit tricky to use, and probably needs some more testing. Report bugs!

Seq66 mutes tab

Mutes Tab

Have fun with your mute groups!

================================================ FILE: data/share/doc/tutorial/pagenotready.html ================================================ Seq66 Basic Tutorial

Seq66 Basic Tutorial

This page is not ready

 

This section is not ready.

 
   
 

For the last time, this section is not ready!!!

Not quite ready for prime time

================================================ FILE: data/share/doc/tutorial/pattern_editor.html ================================================ Seq66 Basic Tutorial: Pattern Editor

Seq66 Basic Tutorial

Pattern Editor

 

The figure below shows the Pattern Editor window. It is a bit small, so run qseq66 (qpseq66.exe in Windows) to see the window elements. It is also available in the Editor tab of the main window via the right-click command Edit pattern in tab.

 
  • Main top bars. These bars allow setting the pattern title, time signature, length, chord-generation, output buss and channel, snap, note length, time zoom, and much more.
  • Time line. Provides for vertical zoom and the time markers "L", "R" (for sub-looping), and "END". The L/R markers can be moved; a starting position can be set.
  • Keys pane. Piano keys at left show the current note, the scale; a click to previews notes. Right-click cycles through the note-name style.
  • Piano roll. Shows the notes in a track and a progress bar while playing. Notes can be recorded from a MIDI controller or be painted via the mouse. Copy/paste and note modification are supported.
  • Left buttons. These support painting note, transposition setting, note-mapping, drum mode, chord-generation, and changing the events shown in the data pane.
  • Event pane. This pane shows only the exact timing of selected events (e.g. Note On vs. Note Off). Movement and deletion are supported for non-note events.
  • Data pane. The data pane shows the selected event plus their magnitude. By dragging the mouse, the height of each event can be modified.

  • Bottom bar. The bottom bar allows selecting events to show/edit, provides low-frequency oscillator (LFO) adjustments, a loop-repetition count, buttons for arming, MIDI Thru, recording, style of recording, and note velocity.

Seq66 pattern editor

Pattern Editor

This editor is jam-packed with functionality. Be sure to try clicking or right clicking on the grid and the various buttons. Hover the mouse cursor over an interface item; often a tool-tip will appear.

================================================ FILE: data/share/doc/tutorial/pattern_tools.html ================================================ Seq66 Basic Tutorial: Pattern Tools

Seq66 Basic Tutorial

Pattern Tools

 

The Tools button provides for note selection, quantization, pitch and harmonic transposition, an LFO generator, and a "pattern fixer". The figure below shows the LFO and Pattern-fix dialogs.

 

LFO Generator. The LFO generator allows one to apply modulation to the events shown in the data pane. The following adjustments can be made:

  • DC Offset. Provides the basic level of the adjustment.
  • Depth. Provides the modulation depth.
  • Period. Provides the number of periods in the modulation, where period applies.
  • Phase. Provides the modulation phase, if applicable.
  • Use Measures (vs Lenght). Determines the duration to use.
  • Wave Type. Determines the wave to use, such as sine and triangle waves.

Pattern Fix. This dialog allows for multiple ways to alter/fix a pattern, useful for people with lousy timing, etc. The following adjustments can be made:

  • Length Change. One can change the number of measures, the time-signature (tricky), and the scale factor. Note that here, changing measures rescales the pattern, unlike changing the measure in the pattern-editor.
  • Quantization. Provides tightening/full quantization, and also time jitter.
  • Other Fixes. Provides left-alignment, pattern reversal, and the option to preserve note length. period applies.

Seq66 LFO generator for patterns


Seq66 pattern fix window


See the user's manual for full descriptions of the tools in the Pattern Editor Tools menu.

================================================ FILE: data/share/doc/tutorial/playlist_manager.html ================================================ Seq66 Basic Tutorial: Playlist Manager

Seq66 Basic Tutorial

Playlist Manager

 

The figure at right shows the Playlists tab. It is a bit small, so run qseq66 (Windows: qpseq66.exe) to see the window elements. This tab is quite complex, so we will not describe its operation in detail here. See the user's manual.

 
  • Playlist file. A playlist file has the extension .playlist and is specified and activated in the .rc file. Each playlist file can hold multiple playlists, with each playlist specifying multiple songs.
  • Options. The playlist file includes options to unmute the next song when moving to it, and to do a deep verification of the playlist (by attempting to load all playlists and all songs. These options are not yet editable in Preferences.
  • Playlist features. Each playlist has a number, name, and an optional base directory that specifies where all the songs are stored for that playlist. The playlist number both orders the playlists and serves as a data value for the direct selection of a particular playlist via a MIDI controller. Each playlist contains a list of songs for that playlist.
  • Playlist songs. Each song has a number and a name. As with the playlist, the number orders the songs and serves as a value to use for selection via a MIDI controller.
  • Playlist manager. While it is very easy to edit the .playlist file in a text editor, the Playlists tab can also be used. It is fairly complex, so see the user's manual.

  • Tricky. The playlist tab is a bit tricky to use, and probably needs some more testing. Report bugs!

Seq66 playlist tab

Playlist Tab

Have fun with your playlists!

================================================ FILE: data/share/doc/tutorial/sets_manager.html ================================================ Seq66 Basic Tutorial: Sets Manager

Seq66 Basic Tutorial

Sets Manager

 

The figure at right shows the Sets tab. It is a bit small, so run qseq66 (Windows: qpseq66.exe) to see the window elements. This tab is quite complex, so we will not describe its operation in detail here. See the user's manual.

 
  • Set storage. Sets are part of the song, and are thus stored in the .midi file. They are stored in a sequencer-specific (SeqSpec) section of the song, and so are not available to other sequencers.
  • Number of sets. Currently, up to 32 sets can be stored in a song. Sets are numbered from 0 to 31. Note that Seq66 always starts showing set 0.
  • Set features. Each set has a number and a name. The names can be entered in the bottom of the main windows.
  • Sets grid. In the Sets tab, the sets can be selected via a grid of buttons, or by selecting a row in the sets table. Sets can be moved around and deleted.

  • Tricky. The sets tab is a bit tricky to use, and probably needs some more testing. Report bugs!

Seq66 playlist tab

Sets Tab

Have fun with your sets!

================================================ FILE: data/share/doc/tutorial/song_editor.html ================================================ Seq66 Basic Tutorial: Song Editor

Seq66 Basic Tutorial

Song Editor

 

The figure at right shows the external Song Editor window (as opposed to the Song tab). It is a bit small, so run qseq66 (Windows: qpseq66.exe) to see the window elements.

 
  • Top bar. These controls support undo/redo, follow-progress, zoom, snap, entering "triggers", transposition, and editing the layout. Also shows the time duration of the layout.
  • Time line. Provides for vertical zoom and the markers "L" and "R" for looping. The L/R markers can be moved; a starting position can be set.
  • Names panel. Shows much the same information as does a pattern slot. Shows the mute status. A track can be clicked here to toggle its mute status. The pattern color selected is shown here, helpful in grokking the layouts.
  • Piano roll. The piano roll shows the layout of the patterns in a song. It starts out empty, then one can paint layouts of each track. The layouts can snap to a given resolution, and can be split, moved, or deleted. They show the pattern color. The File / Export / Export song action can be used to save the song in a format that other sequencers can read.
  • Triggers. Triggers control playback: when and how long a pattern plays, and how the pattern is transposed. Triggers allow patterns to be reused in very powerful ways. See Kraftwerk-Europe_Endless-reconstructed.midi in the midi installation directory. Triggers compress the original MIDI file by a factor of ten.

  • Colors. Note that the colors of each layout match the color of the pattern, if a color was assigned.

Seq66 external song editor window

Song/Performance Editor

This window is packed with functionality. Be sure to try clicking or right clicking on the grid and the various buttons. Hover the mouse cursor over an interface item; often a tool-tip will appear.

================================================ FILE: data/share/doc/tutorial/tutorial_first_startup.html ================================================ Seq66 Basic Tutorial: First Startup

Seq66 Basic Tutorial

First Startup

 

The first startup of Seq66 makes default settings, and creates the configuration files when Seq66 exits. Now is a good time to make sure the desktop theme, the Qt theme, qss style-sheet, and the palette all come together for a nice look. See the user's manual for details.

 
  • Ports. This diagram shows the MIDI port setups on Linux using the ALSA engine (the default). It will look different if JACK is used, or if running in Windows. The top section is "Clocks" (MIDI-out ports), with enabled ports set to "Off". The bottom section is "Input" (MIDI-in ports); check-marked ports are enabled for recording.
  • JACK. Running qseq66 --jack or setting JACK in Preferences / JACK) requires using a2jmidid or a recent version of JACK to expose USB MIDI ports to JACK.
  • Windows. Running qpseq66.exe might show an error; the Microsoft MIDI Mapper has grabbed the port(s). See the manual or C:/Program Files/Seq66/data/ readme.windows.
  • First Exit. At exit, Seq66 saves the current settings in the files shown here.
  • Smoke Test. Load a demo file like Peter Gunn - reconstructed.midi in /usr/local/share/seq66-0.99/midi/ (Linux) or C:/Program Files/Seq66/data/midi. (Windows). Go to the "None" drop-down box at the top and select the desired output (hardware/software synthesizer). Click the "Play" button and verify that it smokes (plays)!

If not, try trouble-shooting; if that fails, ask us! Save the song with this output for later demos.

Seq66 preferences showing clocks/ports

Output Ports ("Clocks") and Input Ports

There are many many use cases for Seq66. No way to cover them all, even in the user's manual.

================================================ FILE: data/share/doc/tutorial/tutorial_live_play.html ================================================ Seq66 Basic Tutorial: Live Play

Seq66 Basic Tutorial

Live Play

 

Live play consists of creating a number of looping patterns that can be muted or unmuted while playing a live solo. This live performance can, to some extent, be recorded. The discussion here is light; as usual, see the user's manual and play around with Seq66.

 
  • Hot-keys. While playing, patterns can be muted and unmuted via hot-keys, as configured in the 'ctrl' configuration file. The default layout on the keyboard matches the 4 x 8 live grid. A MIDI controller can also be set up; see the installed file qseq66-lp-mini-alt.ctrl for a good example. It is documented in the launchpad-mini.ods spreadsheet.
  • Queueing. Queueing means to enable a pattern, but not have it actually turn on (or off) until the loop makes its next repetition. To activate it, click the Q button at the bottom. Then press the hot-key of the desired pattern. The word "Queued" appears, and changes to "Armed" or "Muted" at the next repetition.
  • Mute groups. Mute groups allow for the arming/muting of a number of patterns at once. Up to 32 mute-groups can be defined. They can be stored inside the song or in a 'mutes' file. To assign a mute-group, first arm all of the desired patterns. Click the "L" button to the right of the Q (or the "L" keystroke). Then click a pattern hot-key. The mute group is saved. During play-back, click the shifted version of that hot-key to restore the pattern. Mute groups can also be edited in the Mutes tab.
  • Sets. A set is all of the patterns on a single "page" of the Live grid. A number of sets can be created, each holding all the patterns for that set. A set, when recalled (by number), then will play automatically. Sets are stored in the song. Sets can also be edited in the Sets tab.
  • Playlists. Playlists can be set up so that it is easy to move from song-to-song using the arrow keys or a MIDI control. Playlists can also be managed in the Playlists tab.

 

Seq66

 

Seq66 provides many ways to control live play. See the user's manual. Play around a lot. Report bugs!

================================================ FILE: data/share/doc/tutorial/tutorial_main.html ================================================ Seq66 Basic Tutorial: The Tutorial

Seq66 Basic Tutorial

The Tutorial

 

This section starts the actual tutorial, where we step through the first start of Seq66, creating a song, editing the patterns, laying out a song performance, etc.

 
  • First Startup. Seq66 runs, a default setup loads, MIDI ports are detected. When Seq66 exits, a number of configuration files are created as discussed here. They are very readable and include explanations of most of the settings.
  • New Song. Before making the first pattern, decide on the time signature and precision, beats-per-minute, MIDI subsystem (Linux), preferences, etc.
  • New Patterns/Tracks. Create a new pattern, decide its length,color, output bus, and output channel. Record a track, or paint notes in the track. Organize them into sets.
  • Live Play. Mute and unmute tracks on the fly, set up mute-groups.
  • Song Performance. Lay out the tracks to create a song that play without the musician's input.
  • Other Features. The event editor; play-lists; set-master; mutes-master; session information (see the picture at right).

Seq66 session tab

Normal Session Info

Seq66 is a live-looping MIDI sequencer with a hardware-sampler-like grid interface, sets and playlists for song management, a scale and chord-aware piano-roll interface, song editor for creative composition, and control via MIDI automation for live performance.

================================================ FILE: data/share/doc/tutorial/tutorial_new_patterns.html ================================================ Seq66 Basic Tutorial: New Patterns

Seq66 Basic Tutorial

New Patterns

 

The next step is to create patterns/tracks. This can be done by a right-click on a blank pattern slot, or by double-clicking on it. The pattern can be edited in a tab or an external windows.

 
  • Create a pattern/track. The easiest way to create a pattern is by double-clicking on a blank slot. This brings up a pattern editor.
  • Make settings. If desired, then one can set the pattern's title, time-signature, length in measures, chord-generation, output buss and channel, snap, painted note length, zoom, musical key, scale, background sequence, recording style (e.g. merge versus overwrite), note-velocity handling, transposability, and much more. Hover over the buttons to see what they do. Some settings can also be made via a right-click menu over the pattern slot.
  • Paint notes or record live? Painting notes involves going into painting mode via the "hand" button, the "p" key, or holding the right mouse button, then the left, then dragging. Notes are drawn with the snap and length values set in the editor. MIDI inputs enabled in Preferences can be used to record by the record buttons (shown below). The recording modes are merge, overwrite, expand, and one-shot. See the user's manual.
  • Tools menu. The Tools menu is accessed by clicking on the button with a hammer icon. It provides for note selection (which can also be done with the mouse), quantization, harmonic (scale-aware) pitch transposition, the the LFO and pattern-fix tools shown here.
  • Output bus/channel. The output bus determines where the pattern is played. Each pattern can go to a different MIDI device/softsynth. The output channel determines the playback channel; no matter what channel the event was recorded on, it is sent on this channel. To avoid that, set the channel to Free.

 

Seq66

 

Seq66

================================================ FILE: data/share/doc/tutorial/tutorial_new_song.html ================================================ Seq66 Basic Tutorial: New Song

Seq66 Basic Tutorial

New Song

 

The creation of a new song in Seq66 (or any sequencer) requires making a number of decisions about the song.

 
  • Time precision. Seq66 has a default pulses-per-quarter-note (PPQN) value of 192. This can be changed per-song, or it can be changed globally in the 'usr' file / Preferences. It can range from 32 to 19200, though be aware of the potential impact of high PPQN on playback. Generally, when reading existing tunes, one wants the PPQN of that tune. Seq66 will scale time to match.
  • Live loops versus tracks. A simple yet satisfying song can be made by creating a set of short patterns and letting them play, turning them on and off in a live performance. Or one can make long patterns (tracks) in the same manner as other sequencers. And short patterns (bass, drums) can be combined with long solos.
  • Time signature. The time signature of a track does not affect the MIDI timing of the track, although it can change the length (in pulses) of the track. The global time signature (in the main window) controls the display of the timing beats in the time bar.
  • Beats per minute (BPM). This value is read from a MIDI file or specified when creating a new song (in the main window). It can also be set via a "tap BPM" button; click the button in time with some music.
  • MIDI subsystem. Currently, there are two Linux MIDI subsystems, ALSA and JACK. Either can be used by using the --alsa and --jack options to start qseq66. There is a new subsystem called Pipewire, but currently Seq66 doesn't adapt to that. In Windows and Mac OSX, our own version of PortMidi is used. No one has asked about the Mac, so that is not tested.
  • Live play versus "song performance". This question is simple: does one want to control the song during a live performance, or lay it out and export it so that others can read it and play it?
  • Other features. Other features include MIDI automation control (e.g. LaunchPad, MIDI status display, set management, play-lists, mute-group management, LFO generation, gross pattern fixes, coloring, style-sheets, fingerprint display, and much more as described in the user's manual.

 

Seq66

 

There are many many use cases for Seq66. No way to cover them all, even in the user's manual.

================================================ FILE: data/share/doc/tutorial/tutorial_other_features.html ================================================ Seq66 Basic Tutorial: Other Features

Seq66 Basic Tutorial

Other Features

 

We have seen where we can edit patterns, songs, the configuration files, sets, playlists, and mute groups. This section summarizes some other features, which can be studied more deeply in the user's manual. We just want to hit a few more highlights here.

 
  • Events Editor. Sometimes, there is an issue with a track, in which case one would like to see some details about the events in the track. Or one might want to add an event in a very specific place. The events editor does that. Its a fairly complex editor, though meant more for tweaking than event creations.
  • Sessions. Seq66 supports some session managers under Linux. Supported are the JACK Session Manager and the New/Non Session Manager. See the user's manual.
  • Control. A large number of the actions in Seq66 can be controlled by keystrokes or MIDI event. These are configurable only in the .ctrl file. a user-interface for these controls would be huge!
  • Status Display. The status of a number of items can be displayed in a MIDI controller. These are configurable only in the .ctrl file.
  • MIDI Macros. These macros can be used for various purposes, such as putting a MIDI controller into the proper mode for automation. These are configurable only in the .ctrl file.
  • Port Mapping. Port/buss numbers are stored with each pattern/track. Port names are not stored, because they can vary widely between systems and even between runs of Seq66. However, a section can be added to the .rc file to allow a superset of ports to be specified and mapped to the actual port number available at run-time.
  • Palettes and theming. A .palette file can be saved and edited (only via text-editor currently) in order to change the colors drawn in the piano rows and other drawn panels. The inverse-colors option can be set to enable a kind of "night mode". The current Qt theme can be tweaked via a .qss file for even more customization.
  • Auto-connect ports. At startup, Seq66 will automatically connect to configured ports.
  • Virtual ports. On Linux, Seq66 supports the creation of virtual software ports which can be manually connected using aconnect or an application like QjackCtl.

 

Seq66

 

There are many many use cases for Seq66. No way to cover them all, even in the user's manual.

================================================ FILE: data/share/doc/tutorial/tutorial_song_performance.html ================================================ Seq66 Basic Tutorial: Song Performance

Seq66 Basic Tutorial

Song Performance

 

Here, we discuss how to build a stand-alone song by laying out existing patterns. Pattern loops can be reused and transposed, as shown in the demo song Kraftwerk-Europe_Endless-reconstructed.midi. Or long tracks can be used, and played without a layout or with a simple layout, as in the demo song b4uacuse-gm-patchless.midi.

 
  • Pattern creation. The first step is to create a number of patterns to represent snippets of the song: drum loops (intro, main, bridge, etc.), bass loops, chord loops.
  • Pattern arrangement. One has to decide at what measures each loop comes into play. It can help to create a text file that shows where each pattern will be played.
  • Lay out the triggers. Each pattern's playback is represented by one or more triggers. See the annotated figure at the right. A trigger is created by entering edit mode (by the "hand" button), selecting a pattern and the starting location of the trigger, and dragging to the left to create as many repetitions of the loop as desired. Note that the drawing snaps to the selected snap value.
  • Adjust the triggers. Once created, triggers can be moved, resized by grabbing and dragging a square handle at the left or right of the trigger, split in half by Ctrl-left-click, and deleted.
  • Song mode. In the main window, click the Live button to turn on Song mode. Now playback will be controlled by the triggers.

Seq66

Song Editor Names and Triggers

A Seq66 song layout is a great way to set up a tune to play automatically, and to export it in a format other sequencers can read.

================================================ FILE: data/share/metainfo/seq66.appdata.xml ================================================ seq66.desktop CC0-1.0 GPL-2.0+ Seq66

A Live-Play Software Sequencer Grid for Linux

Seq66 is a MIDI software sequencer for Linux. ....

Based on the 0.9.2 version of seq24 (Copyright 200x-2010 Rob Buse), development of Seq66 has continued for quite a while now in its own direction, with many new features, bug-fixes, and ongoing optimizations.

seq66 https://a.fsdn.com/con/app/proj/seq66/screenshots/Main.png The main window showing Seq66 running https://github.com/ahlstromcj/seq66/ ahlstromcj@gmail.com ================================================ FILE: data/testing/mapping-snippet.rc ================================================ # This port-map snippet tests the performer::any_unavailable_ports() # function. [midi-input] 3 # number of MIDI input (or control) buses 0 1 "[0] 0:1 system:ALSA Announce" 1 1 "[1] 14:0 Midi Through Port-0" 2 0 "[2] 129:0 VMPK Output:out" [midi-input-map] 1 # map is active 0 1 "ALSA Announce" 1 1 "Midi Through Port-0" 2 -2 "Yamaha PSS-680" 3 -2 "Yamaha PSS-790" 4 0 "VMPK Output:out" [midi-clock] 3 # number of MIDI clocks (output/display buses) 0 0 "[0] 14:0 Midi Through Port-0" 1 0 "[1] 128:0 VMPK Input:in" 2 0 "[2] 130:0 FLUID Synth (3063):Synth input port (3063:0)" [midi-clock-map] 1 # map is active 0 0 "Midi Through Port-0" 1 -2 "Yamaha PSS-680" 2 -2 "Yamaha PSS-790" 3 0 "VMPK Input:in" 4 0 "FLUID Synth" # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/testing/simple-test.notemap ================================================ # Seq66 0.99.17 (and above) note-mapper ('drums'/'notemap') configuration file # # ~/.config/seq66/simple-test.notemap # Written 2025-01-09 12:00:00 # # This file is similar to files generated by the 'midicvt' program, # but heavily modified for simplicity for Seq66. # # This file is meant only for simple testing of note-mapping. It # has only a few notes, and each note is mapped to one-octave lower. [Seq66] config-type = "drums" version = 0 # The [comments] section can document this file. Lines starting # with '#', '[', that are blank end the comment. [comments] Provides a test of lowering notes by one octave for easy testing. Thus, it starts at MIDI note 12. It's not really a drum file, so has an extension of ".notemap". # This file holds a drum/note mapping configuration for Seq66. It is # stored in the configuration directory. To use this file, add this # file-name to the '[note-mapper]' section of the 'rc' file. There is # no user interface for managing this file. The main values are: # # map-type: drum, patch, or multi; indicates the mapping to do. # gm-channel: Indicates the channel (1-16) applied to convert notes. # reverse: true or false; map in the opposite direction if true. [notemap-flags] map-type = drum gm-channel = 1 reverse = false [Drum 12] dev-name = "Dev note 12" gm-name = "GM note 0" dev-note = 12 gm-note = 0 [Drum 13] dev-name = "Dev note 13" gm-name = "GM note 1" dev-note = 13 gm-note = 1 [Drum 14] dev-name = "Dev note 14" gm-name = "GM note 2" dev-note = 14 gm-note = 2 [Drum 15] dev-name = "Dev note 15" gm-name = "GM note 3" dev-note = 15 gm-note = 3 [Drum 16] dev-name = "Dev note 16" gm-name = "GM note 4" dev-note = 16 gm-note = 4 [Drum 17] dev-name = "Dev note 17" gm-name = "GM note 5" dev-note = 17 gm-note = 5 [Drum 18] dev-name = "Dev note 18" gm-name = "GM note 6" dev-note = 18 gm-note = 6 [Drum 19] dev-name = "Dev note 19" gm-name = "GM note 7" dev-note = 19 gm-note = 7 [Drum 20] dev-name = "Dev note 20" gm-name = "GM note 8" dev-note = 20 gm-note = 8 [Drum 21] dev-name = "Dev note 21" gm-name = "GM note 9" dev-note = 21 gm-note = 9 [Drum 22] dev-name = "Dev note 22" gm-name = "GM note 10" dev-note = 22 gm-note = 10 [Drum 23] dev-name = "Dev note 23" gm-name = "GM note 11" dev-note = 23 gm-note = 11 [Drum 24] dev-name = "Dev note 24" gm-name = "GM note 12" dev-note = 24 gm-note = 12 [Drum 25] dev-name = "Dev note 25" gm-name = "GM note 13" dev-note = 25 gm-note = 13 [Drum 26] dev-name = "Dev note 26" gm-name = "GM note 14" dev-note = 26 gm-note = 14 [Drum 27] dev-name = "Dev note 27" gm-name = "GM note 15" dev-note = 27 gm-note = 15 [Drum 28] dev-name = "Dev note 28" gm-name = "GM note 16" dev-note = 28 gm-note = 16 [Drum 29] dev-name = "Dev note 29" gm-name = "GM note 17" dev-note = 29 gm-note = 17 [Drum 30] dev-name = "Dev note 30" gm-name = "GM note 18" dev-note = 30 gm-note = 18 [Drum 31] dev-name = "Dev note 31" gm-name = "GM note 19" dev-note = 31 gm-note = 19 [Drum 32] dev-name = "Dev note 32" gm-name = "GM note 20" dev-note = 32 gm-note = 20 [Drum 33] dev-name = "Dev note 33" gm-name = "GM note 21" dev-note = 33 gm-note = 21 [Drum 34] dev-name = "Dev note 34" gm-name = "GM note 22" dev-note = 34 gm-note = 22 [Drum 35] dev-name = "Dev note 35" gm-name = "GM note 23" dev-note = 35 gm-note = 23 [Drum 36] dev-name = "Dev note 36" gm-name = "GM note 24" dev-note = 36 gm-note = 24 [Drum 37] dev-name = "Dev note 37" gm-name = "GM note 25" dev-note = 37 gm-note = 25 [Drum 38] dev-name = "Dev note 38" gm-name = "GM note 26" dev-note = 38 gm-note = 26 [Drum 39] dev-name = "Dev note 39" gm-name = "GM note 27" dev-note = 39 gm-note = 27 [Drum 40] dev-name = "Dev note 40" gm-name = "GM note 28" dev-note = 40 gm-note = 28 [Drum 41] dev-name = "Dev note 41" gm-name = "GM note 29" dev-note = 41 gm-note = 29 [Drum 42] dev-name = "Dev note 42" gm-name = "GM note 30" dev-note = 42 gm-note = 30 [Drum 43] dev-name = "Dev note 43" gm-name = "GM note 31" dev-note = 43 gm-note = 31 [Drum 44] dev-name = "Dev note 44" gm-name = "GM note 32" dev-note = 44 gm-note = 32 [Drum 45] dev-name = "Dev note 45" gm-name = "GM note 33" dev-note = 45 gm-note = 33 [Drum 46] dev-name = "Dev note 46" gm-name = "GM note 34" dev-note = 46 gm-note = 34 [Drum 47] dev-name = "Dev note 47" gm-name = "GM note 35" dev-note = 47 gm-note = 35 [Drum 48] dev-name = "Dev note 48" gm-name = "GM note 36" dev-note = 48 gm-note = 36 [Drum 49] dev-name = "Dev note 49" gm-name = "GM note 37" dev-note = 49 gm-note = 37 [Drum 50] dev-name = "Dev note 50" gm-name = "GM note 38" dev-note = 50 gm-note = 38 [Drum 51] dev-name = "Dev note 51" gm-name = "GM note 39" dev-note = 51 gm-note = 39 [Drum 52] dev-name = "Dev note 52" gm-name = "GM note 40" dev-note = 52 gm-note = 40 [Drum 53] dev-name = "Dev note 53" gm-name = "GM note 41" dev-note = 53 gm-note = 41 [Drum 54] dev-name = "Dev note 54" gm-name = "GM note 42" dev-note = 54 gm-note = 42 [Drum 55] dev-name = "Dev note 55" gm-name = "GM note 43" dev-note = 55 gm-note = 43 [Drum 56] dev-name = "Dev note 56" gm-name = "GM note 44" dev-note = 56 gm-note = 44 [Drum 57] dev-name = "Dev note 57" gm-name = "GM note 45" dev-note = 57 gm-note = 45 [Drum 58] dev-name = "Dev note 58" gm-name = "GM note 46" dev-note = 58 gm-note = 46 [Drum 59] dev-name = "Dev note 59" gm-name = "GM note 47" dev-note = 59 gm-note = 47 [Drum 60] dev-name = "Dev note 60" gm-name = "GM note 48" dev-note = 60 gm-note = 48 [Drum 61] dev-name = "Dev note 61" gm-name = "GM note 49" dev-note = 61 gm-note = 49 [Drum 62] dev-name = "Dev note 62" gm-name = "GM note 50" dev-note = 62 gm-note = 50 [Drum 63] dev-name = "Dev note 63" gm-name = "GM note 51" dev-note = 63 gm-note = 51 [Drum 64] dev-name = "Dev note 64" gm-name = "GM note 52" dev-note = 64 gm-note = 52 [Drum 65] dev-name = "Dev note 65" gm-name = "GM note 53" dev-note = 65 gm-note = 53 [Drum 66] dev-name = "Dev note 66" gm-name = "GM note 54" dev-note = 66 gm-note = 54 [Drum 67] dev-name = "Dev note 67" gm-name = "GM note 55" dev-note = 67 gm-note = 55 [Drum 68] dev-name = "Dev note 68" gm-name = "GM note 56" dev-note = 68 gm-note = 56 [Drum 69] dev-name = "Dev note 69" gm-name = "GM note 57" dev-note = 69 gm-note = 57 [Drum 70] dev-name = "Dev note 70" gm-name = "GM note 58" dev-note = 70 gm-note = 58 [Drum 71] dev-name = "Dev note 71" gm-name = "GM note 59" dev-note = 71 gm-note = 59 [Drum 72] dev-name = "Dev note 72" gm-name = "GM note 60" dev-note = 72 gm-note = 60 [Drum 73] dev-name = "Dev note 73" gm-name = "GM note 61" dev-note = 73 gm-note = 61 [Drum 74] dev-name = "Dev note 74" gm-name = "GM note 62" dev-note = 74 gm-note = 62 [Drum 75] dev-name = "Dev note 75" gm-name = "GM note 63" dev-note = 75 gm-note = 63 [Drum 76] dev-name = "Dev note 76" gm-name = "GM note 64" dev-note = 76 gm-note = 64 [Drum 77] dev-name = "Dev note 77" gm-name = "GM note 65" dev-note = 77 gm-note = 65 [Drum 78] dev-name = "Dev note 78" gm-name = "GM note 66" dev-note = 78 gm-note = 66 [Drum 79] dev-name = "Dev note 79" gm-name = "GM note 67" dev-note = 79 gm-note = 67 [Drum 80] dev-name = "Dev note 80" gm-name = "GM note 68" dev-note = 80 gm-note = 68 [Drum 81] dev-name = "Dev note 81" gm-name = "GM note 69" dev-note = 81 gm-note = 69 [Drum 82] dev-name = "Dev note 82" gm-name = "GM note 70" dev-note = 82 gm-note = 70 [Drum 83] dev-name = "Dev note 83" gm-name = "GM note 71" dev-note = 83 gm-note = 71 [Drum 84] dev-name = "Dev note 84" gm-name = "GM note 72" dev-note = 84 gm-note = 72 [Drum 85] dev-name = "Dev note 85" gm-name = "GM note 73" dev-note = 85 gm-note = 73 [Drum 86] dev-name = "Dev note 86" gm-name = "GM note 74" dev-note = 86 gm-note = 74 [Drum 87] dev-name = "Dev note 87" gm-name = "GM note 75" dev-note = 87 gm-note = 75 [Drum 88] dev-name = "Dev note 88" gm-name = "GM note 76" dev-note = 88 gm-note = 76 [Drum 89] dev-name = "Dev note 89" gm-name = "GM note 77" dev-note = 89 gm-note = 77 [Drum 90] dev-name = "Dev note 90" gm-name = "GM note 78" dev-note = 90 gm-note = 78 [Drum 91] dev-name = "Dev note 91" gm-name = "GM note 79" dev-note = 91 gm-note = 79 [Drum 92] dev-name = "Dev note 92" gm-name = "GM note 80" dev-note = 92 gm-note = 80 [Drum 93] dev-name = "Dev note 93" gm-name = "GM note 81" dev-note = 93 gm-note = 81 [Drum 94] dev-name = "Dev note 94" gm-name = "GM note 82" dev-note = 94 gm-note = 82 [Drum 95] dev-name = "Dev note 95" gm-name = "GM note 83" dev-note = 95 gm-note = 83 [Drum 96] dev-name = "Dev note 96" gm-name = "GM note 84" dev-note = 96 gm-note = 84 [Drum 97] dev-name = "Dev note 97" gm-name = "GM note 85" dev-note = 97 gm-note = 85 [Drum 98] dev-name = "Dev note 98" gm-name = "GM note 86" dev-note = 98 gm-note = 86 [Drum 99] dev-name = "Dev note 99" gm-name = "GM note 87" dev-note = 99 gm-note = 87 [Drum 100] dev-name = "Dev note 100" gm-name = "GM note 88" dev-note = 100 gm-note = 88 [Drum 101] dev-name = "Dev note 101" gm-name = "GM note 89" dev-note = 101 gm-note = 89 [Drum 102] dev-name = "Dev note 102" gm-name = "GM note 90" dev-note = 102 gm-note = 90 [Drum 103] dev-name = "Dev note 103" gm-name = "GM note 91" dev-note = 103 gm-note = 91 [Drum 104] dev-name = "Dev note 104" gm-name = "GM note 92" dev-note = 104 gm-note = 92 [Drum 105] dev-name = "Dev note 105" gm-name = "GM note 93" dev-note = 105 gm-note = 93 [Drum 106] dev-name = "Dev note 106" gm-name = "GM note 94" dev-note = 106 gm-note = 94 [Drum 107] dev-name = "Dev note 107" gm-name = "GM note 95" dev-note = 107 gm-note = 95 [Drum 108] dev-name = "Dev note 108" gm-name = "GM note 96" dev-note = 108 gm-note = 96 [Drum 109] dev-name = "Dev note 109" gm-name = "GM note 97" dev-note = 109 gm-note = 97 [Drum 110] dev-name = "Dev note 110" gm-name = "GM note 98" dev-note = 110 gm-note = 98 [Drum 111] dev-name = "Dev note 111" gm-name = "GM note 99" dev-note = 111 gm-note = 99 [Drum 112] dev-name = "Dev note 112" gm-name = "GM note 100" dev-note = 112 gm-note = 100 [Drum 113] dev-name = "Dev note 113" gm-name = "GM note 101" dev-note = 113 gm-note = 101 [Drum 114] dev-name = "Dev note 114" gm-name = "GM note 102" dev-note = 114 gm-note = 102 [Drum 115] dev-name = "Dev note 115" gm-name = "GM note 103" dev-note = 115 gm-note = 103 [Drum 116] dev-name = "Dev note 116" gm-name = "GM note 104" dev-note = 116 gm-note = 104 [Drum 117] dev-name = "Dev note 117" gm-name = "GM note 105" dev-note = 117 gm-note = 105 [Drum 118] dev-name = "Dev note 118" gm-name = "GM note 106" dev-note = 118 gm-note = 106 [Drum 119] dev-name = "Dev note 119" gm-name = "GM note 107" dev-note = 119 gm-note = 107 [Drum 120] dev-name = "Dev note 120" gm-name = "GM note 108" dev-note = 120 gm-note = 108 [Drum 121] dev-name = "Dev note 121" gm-name = "GM note 109" dev-note = 121 gm-note = 109 [Drum 122] dev-name = "Dev note 122" gm-name = "GM note 110" dev-note = 122 gm-note = 110 [Drum 123] dev-name = "Dev note 123" gm-name = "GM note 111" dev-note = 123 gm-note = 111 [Drum 124] dev-name = "Dev note 124" gm-name = "GM note 112" dev-note = 124 gm-note = 112 [Drum 125] dev-name = "Dev note 125" gm-name = "GM note 113" dev-note = 125 gm-note = 113 [Drum 126] dev-name = "Dev note 126" gm-name = "GM note 114" dev-note = 126 gm-note = 114 [Drum 127] dev-name = "Dev note 127" gm-name = "GM note 115" dev-note = 127 gm-note = 115 # End of ~/.config/seq66/simple-test.notemap # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/testing/sixteen-ports-snippet.rc ================================================ # This port-map snippet tests the scroll-bar in the Clocks and Inputs # tabs. We only have 8 things to plug in on our system :-(. [midi-input] 8 # number of MIDI input (or control) buses 0 1 "[0] 0:1 system:ALSA Announce" 1 0 "[1] 14:0 Midi Through Port-0" 2 0 "[2] 36:0 Launchpad Mini MIDI 1" 3 1 "[3] 40:0 nanoKEY2 nanoKEY2 _ CTRL" 4 0 "[4] 44:0 CH345 MIDI 1" 5 0 "[5] 48:0 E-MU XMidi1X1 Tab Out" 6 0 "[6] 52:0 USB Midi MIDI 1" 7 0 "[7] 56:0 Q25 MIDI 1" [midi-input-map] 1 # map is active 0 1 "ALSA Announce" 1 0 "Midi Through Port-0" 2 0 "Launchpad Mini MIDI 1" 3 1 "nanoKEY2 nanoKEY2 _ CTRL" 4 0 "CH345 MIDI 1" 5 0 "E-MU XMidi1X1 Tab Out" 6 0 "USB Midi MIDI 1" 7 0 "Q25 MIDI 1" 8 0 "Excess 0" 9 0 "Excess 1" 10 0 "Excess 2" 11 0 "Excess 3" 12 0 "Excess 4" 13 0 "Excess 5" 14 0 "Excess 7" 15 0 "Excess 7" [midi-clock] 9 # number of MIDI clocks (output/display buses) 0 0 "[0] 14:0 Midi Through Port-0" 1 0 "[1] 36:0 Launchpad Mini MIDI 1" 2 0 "[2] 40:0 nanoKEY2 nanoKEY2 _ CTRL" 3 0 "[3] 44:0 CH345 MIDI 1" 4 0 "[4] 48:0 E-MU XMidi1X1 Tab Out" 5 0 "[5] 52:0 USB Midi MIDI 1" 6 0 "[6] 56:0 Q25 MIDI 1" 7 0 "[7] 128:0 FLUID Synth (21079):Synth input port (21079:0)" 8 0 "[8] 129:0 yoshimi:input" # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/win/dark-theme.qss ================================================ /* * \file dark-theme.qss * \library seq66 application * \author Chris Ahlstrom * \date 2021-12-27 * \updates 2021-12-27 * \license Public domain * * Provides a sample Qt style sheet for Seq66. Derived from the Dark * theme provided with SMPlayer, with all of the icon/png elements removed. * One might want to update the palette to draw text in a better color for * the grid buttons. */ QToolTip { border: 1px solid #76797C; background-color: #5A7566; color: white; padding: 0px; /*remove padding, for fix combobox tooltip.*/ opacity: 200; } QWidget { color: #eff0f1; background-color: #31363b; selection-background-color: #3daee9; selection-color: #eff0f1; background-clip: border; border-image: none; border: 0px transparent black; outline: 0; } QWidget:item:hover { background-color: #18465d; color: #eff0f1; } QWidget:item:selected { background-color: #18465d; } QCheckBox { spacing: 5px; outline: none; color: #eff0f1; margin-bottom: 2px; } QCheckBox:disabled { color: #76797C; } QCheckBox::indicator, QGroupBox::indicator { width: 18px; height: 18px; } QGroupBox::indicator { margin-left: 2px; } QCheckBox::indicator:unchecked:hover, QCheckBox::indicator:unchecked:focus, QCheckBox::indicator:unchecked:pressed, QGroupBox::indicator:unchecked:hover, QGroupBox::indicator:unchecked:focus, QGroupBox::indicator:unchecked:pressed { border: none; } QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:focus, QCheckBox::indicator:checked:pressed, QGroupBox::indicator:checked:hover, QGroupBox::indicator:checked:focus, QGroupBox::indicator:checked:pressed { border: none; } QRadioButton { spacing: 5px; outline: none; color: #eff0f1; margin-bottom: 2px; } QRadioButton:disabled { color: #76797C; } QRadioButton::indicator { width: 21px; height: 21px; } QRadioButton::indicator:unchecked:hover, QRadioButton::indicator:unchecked:focus, QRadioButton::indicator:unchecked:pressed { border: none; outline: none; } QRadioButton::indicator:checked { border: none; outline: none; } QRadioButton::indicator:checked:hover, QRadioButton::indicator:checked:focus, QRadioButton::indicator:checked:pressed { border: none; outline: none; } QRadioButton::indicator:checked:disabled { outline: none; } QMenuBar { background-color: #31363b; color: #eff0f1; } QMenuBar::item { background: transparent; } QMenuBar::item:selected { background: transparent; border: 1px solid #76797C; } QMenuBar::item:pressed { border: 1px solid #76797C; background-color: #3daee9; color: #eff0f1; margin-bottom: -1px; padding-bottom: 1px; } QMenu { border: 1px solid #76797C; color: #eff0f1; margin: 2px; } QMenu::icon { margin: 5px; } QMenu::item { padding: 5px 30px 5px 30px; border: 1px solid transparent; /* reserve space for selection border */ } QMenu::item:selected { color: #eff0f1; } QMenu::separator { height: 2px; background: lightblue; margin-left: 10px; margin-right: 5px; } QMenu::indicator { width: 18px; height: 18px; } QMenu::right-arrow { margin: 5px; } QWidget:disabled { /*color: #454545;*/ color: #777777; background-color: #31363b; } QAbstractItemView { alternate-background-color: #31363b; color: #eff0f1; border: 1px solid #3A3939; border-radius: 2px; } QWidget:focus, QMenuBar:focus { border: 1px solid #3daee9; } QTabWidget:focus, QCheckBox:focus, QRadioButton:focus, QSlider:focus { border: none; } QLineEdit { background-color: #232629; padding: 5px; border-style: solid; border: 1px solid #76797C; border-radius: 2px; color: #eff0f1; } QAbstractItemView QLineEdit { padding: 0; } QGroupBox { border: 1px solid #76797C; border-radius: 2px; margin-top: 20px; } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top center; padding-left: 10px; padding-right: 10px; padding-top: 10px; } QAbstractScrollArea { border-radius: 2px; border: 1px solid #76797C; background-color: transparent; } QScrollBar:horizontal { height: 15px; margin: 3px 15px 3px 15px; border: 1px transparent #2A2929; border-radius: 4px; background-color: #2A2929; } QScrollBar::handle:horizontal { background-color: #605F5F; min-width: 5px; border-radius: 4px; } QScrollBar::add-line:horizontal { margin: 0px 3px 0px 3px; width: 10px; height: 10px; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal { margin: 0px 3px 0px 3px; height: 10px; width: 10px; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar::add-line:horizontal:hover, QScrollBar::add-line:horizontal:on { height: 10px; width: 10px; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on { height: 10px; width: 10px; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal { background: none; } QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: none; } QScrollBar:vertical { background-color: #2A2929; width: 15px; margin: 15px 3px 15px 3px; border: 1px transparent #2A2929; border-radius: 4px; } QScrollBar::handle:vertical { background-color: #605F5F; min-height: 5px; border-radius: 4px; } QScrollBar::sub-line:vertical { margin: 3px 0px 3px 0px; height: 10px; width: 10px; subcontrol-position: top; subcontrol-origin: margin; } QScrollBar::add-line:vertical { margin: 3px 0px 3px 0px; height: 10px; width: 10px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::sub-line:vertical:hover, QScrollBar::sub-line:vertical:on { height: 10px; width: 10px; subcontrol-position: top; subcontrol-origin: margin; } QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on { height: 10px; width: 10px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { background: none; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: none; } QTextEdit { background-color: #232629; color: #eff0f1; border: 1px solid #76797C; } QPlainTextEdit { background-color: #232629; color: #eff0f1; border-radius: 2px; border: 1px solid #76797C; } QHeaderView::section { background-color: #76797C; color: #eff0f1; padding: 5px; border: 1px solid #76797C; } QSizeGrip { width: 12px; height: 12px; } QMainWindow::separator { background-color: #31363b; color: white; padding-left: 4px; spacing: 2px; border: 1px dashed #76797C; } QMainWindow::separator:hover { background-color: #787876; color: white; padding-left: 4px; border: 1px solid #76797C; spacing: 2px; } QMenu::separator { height: 1px; background-color: #76797C; color: white; padding-left: 4px; margin-left: 10px; margin-right: 5px; } QFrame { border-radius: 2px; border: 1px solid #76797C; } QFrame[frameShape="0"] { border-radius: 2px; border: 1px transparent #76797C; } QStackedWidget { border: 1px transparent black; } QToolBar { border: 1px transparent #393838; background: 1px solid #31363b; font-weight: bold; } QToolButton#qt_toolbar_ext_button { background: #58595a } QPushButton { color: #eff0f1; background-color: #31363b; border-width: 1px; border-color: #76797C; border-style: solid; padding: 5px; border-radius: 2px; outline: none; } QPushButton:disabled { background-color: #31363b; border-width: 1px; border-color: #454545; border-style: solid; padding-top: 5px; padding-bottom: 5px; padding-left: 10px; padding-right: 10px; border-radius: 2px; color: #454545; } QPushButton:focus { background-color: #3daee9; color: white; } QPushButton:pressed { background-color: #3daee9; padding-top: -15px; padding-bottom: -17px; } QComboBox { selection-background-color: #3daee9; border-style: solid; border: 1px solid #76797C; border-radius: 2px; padding: 5px; min-width: 75px; } QPushButton:checked { background-color: #76797C; border-color: #6A6969; } QComboBox:hover, QPushButton:hover, QAbstractSpinBox:hover, QLineEdit:hover, QTextEdit:hover, QPlainTextEdit:hover, QAbstractView:hover, QTreeView:hover { border: 1px solid #3daee9; color: #eff0f1; } QComboBox:on { padding-top: 3px; padding-left: 4px; selection-background-color: #4a4a4a; } QComboBox QAbstractItemView { background-color: #232629; border-radius: 2px; border: 1px solid #76797C; selection-background-color: #18465d; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 15px; border-left-width: 0px; border-left-color: darkgray; border-left-style: solid; border-top-right-radius: 3px; border-bottom-right-radius: 3px; } QAbstractSpinBox { padding: 5px; border: 1px solid #76797C; background-color: #232629; color: #eff0f1; border-radius: 2px; min-width: 75px; } QAbstractSpinBox:up-button { background-color: transparent; subcontrol-origin: border; subcontrol-position: center right; } QAbstractSpinBox:down-button { background-color: transparent; subcontrol-origin: border; subcontrol-position: center left; } QAbstractSpinBox::up-arrow, QAbstractSpinBox::up-arrow:disabled, QAbstractSpinBox::up-arrow:off { width: 10px; height: 10px; } QAbstractSpinBox::down-arrow, QAbstractSpinBox::down-arrow:disabled, QAbstractSpinBox::down-arrow:off { width: 10px; height: 10px; } QLabel { border: 0px solid black; } QTabWidget { border: 0px transparent black; } QTabWidget::pane { border: 1px solid #76797C; padding: 5px; margin: 0px; } QTabWidget::tab-bar { /* left: 5px; move to the right by 5px */ } QTabBar { qproperty-drawBase: 0; border-radius: 3px; } QTabBar:focus { border: 0px transparent black; } QTabBar::close-button { background: transparent; } QTabBar::close-button:hover { background: transparent; } QTabBar::close-button:pressed { background: transparent; } /* TOP TABS */ QTabBar::tab:top { color: #eff0f1; border: 1px solid #76797C; border-bottom: 1px transparent black; background-color: #31363b; padding: 5px; min-width: 50px; border-top-left-radius: 2px; border-top-right-radius: 2px; } QTabBar::tab:top:selected { color: #eff0f1; background-color: #54575B; border: 1px solid #76797C; border-bottom: 2px solid #3daee9; border-top-left-radius: 2px; border-top-right-radius: 2px; } QTabBar::tab:top:!selected:hover { background-color: #3daee9; } /* BOTTOM TABS */ QTabBar::tab:bottom { color: #eff0f1; border: 1px solid #76797C; border-top: 1px transparent black; background-color: #31363b; padding: 5px; border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; min-width: 50px; } QTabBar::tab:bottom:selected { color: #eff0f1; background-color: #54575B; border: 1px solid #76797C; border-top: 2px solid #3daee9; border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; } QTabBar::tab:bottom:!selected:hover { background-color: #3daee9; } /* LEFT TABS */ QTabBar::tab:left { color: #eff0f1; border: 1px solid #76797C; border-left: 1px transparent black; background-color: #31363b; padding: 5px; border-top-right-radius: 2px; border-bottom-right-radius: 2px; min-height: 50px; } QTabBar::tab:left:selected { color: #eff0f1; background-color: #54575B; border: 1px solid #76797C; border-left: 2px solid #3daee9; border-top-right-radius: 2px; border-bottom-right-radius: 2px; } QTabBar::tab:left:!selected:hover { background-color: #3daee9; } /* RIGHT TABS */ QTabBar::tab:right { color: #eff0f1; border: 1px solid #76797C; border-right: 1px transparent black; background-color: #31363b; padding: 5px; border-top-left-radius: 2px; border-bottom-left-radius: 2px; min-height: 50px; } QTabBar::tab:right:selected { color: #eff0f1; background-color: #54575B; border: 1px solid #76797C; border-right: 2px solid #3daee9; border-top-left-radius: 2px; border-bottom-left-radius: 2px; } QTabBar::tab:right:!selected:hover { background-color: #3daee9; } QDockWidget { background: #31363b; border: 1px solid #403F3F; } QDockWidget::close-button, QDockWidget::float-button { border: 1px solid transparent; border-radius: 2px; background: transparent; } QDockWidget::close-button:hover, QDockWidget::float-button:hover { background: rgba(255, 255, 255, 10); } QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { padding: 1px -1px -1px 1px; background: rgba(255, 255, 255, 10); } QTreeView, QListView { border: 1px solid #76797C; background-color: #232629; } QListView::item:!selected:hover, QTreeView::item:!selected:hover { background: #18465d; outline: 0; color: #eff0f1 } QListView::item:selected:hover, QTreeView::item:selected:hover { background: #287399; color: #eff0f1; } QSlider::groove:horizontal { border: 1px solid #565a5e; height: 4px; background: #565a5e; margin: 0px; border-radius: 2px; } QSlider::handle:horizontal { background: #232629; border: 1px solid #565a5e; width: 16px; height: 16px; margin: -8px 0; border-radius: 9px; } QSlider::groove:vertical { border: 1px solid #565a5e; width: 4px; background: #565a5e; margin: 0px; border-radius: 3px; } QSlider::handle:vertical { background: #232629; border: 1px solid #565a5e; width: 16px; height: 16px; margin: 0 -8px; border-radius: 9px; } QToolButton { background-color: transparent; border: 1px transparent #76797C; border-radius: 2px; margin: 3px; padding: 5px; } QToolButton[popupMode="1"] { /* only for MenuButtonPopup */ padding-right: 20px; /* make way for the popup button */ border: 1px #76797C; border-radius: 5px; } QToolButton[popupMode="2"] { /* only for InstantPopup */ padding-right: 10px; /* make way for the popup button */ border: 1px #76797C; } QToolButton:hover, QToolButton::menu-button:hover { background-color: transparent; border: 1px solid #3daee9; padding: 5px; } QToolButton:checked, QToolButton:pressed, QToolButton::menu-button:pressed { background-color: #3daee9; border: 1px solid #3daee9; padding: 5px; } /* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */ QToolButton::menu-indicator { top: -7px; left: -2px; /* shift it a bit */ } /* the subcontrols below are used only in the MenuButtonPopup mode */ QToolButton::menu-button { border: 1px transparent #76797C; border-top-right-radius: 6px; border-bottom-right-radius: 6px; /* 16px width + 4px for border = 20px allocated above */ width: 16px; outline: none; } QToolButton::menu-arrow:open { border: 1px solid #76797C; } QPushButton::menu-indicator { subcontrol-origin: padding; subcontrol-position: bottom right; left: 8px; } QTableView { border: 1px solid #76797C; gridline-color: #31363b; background-color: #232629; } QTableView, QHeaderView { border-radius: 0px; } QTableView::item:pressed, QListView::item:pressed, QTreeView::item:pressed { background: #18465d; color: #eff0f1; } QTableView::item:selected:active, QTreeView::item:selected:active, QListView::item:selected:active { background: #287399; color: #eff0f1; } QHeaderView { background-color: #31363b; border: 1px transparent; border-radius: 0px; margin: 0px; padding: 0px; } QHeaderView::section { background-color: #31363b; color: #eff0f1; padding: 5px; border: 1px solid #76797C; border-radius: 0px; text-align: center; } QHeaderView::section::vertical::first, QHeaderView::section::vertical::only-one { border-top: 1px solid #76797C; } QHeaderView::section::vertical { border-top: transparent; } QHeaderView::section::horizontal::first, QHeaderView::section::horizontal::only-one { border-left: 1px solid #76797C; } QHeaderView::section::horizontal { border-left: transparent; } QHeaderView::section:checked { color: white; background-color: #334e5e; } QTableCornerButton::section { background-color: #31363b; border: 1px transparent #76797C; border-radius: 0px; } QToolBox { padding: 5px; border: 1px transparent black; } QToolBox::tab { color: #eff0f1; background-color: #31363b; border: 1px solid #76797C; border-bottom: 1px transparent #31363b; border-top-left-radius: 5px; border-top-right-radius: 5px; } QToolBox::tab:selected { /* italicize selected tabs */ font: italic; background-color: #31363b; border-color: #3daee9; } QStatusBar::item { border: 0px transparent dark; } QFrame[height="3"], QFrame[width="3"] { background-color: #76797C; } QSplitter::handle { border: 1px dashed #76797C; } QSplitter::handle:hover { background-color: #787876; border: 1px solid #76797C; } QSplitter::handle:horizontal { width: 1px; } QSplitter::handle:vertical { height: 1px; } QProgressBar { border: 1px solid #76797C; border-radius: 5px; text-align: center; } QProgressBar::chunk { background-color: #05B8CC; } QDateEdit { selection-background-color: #3daee9; border-style: solid; border: 1px solid #3375A3; border-radius: 2px; padding: 1px; min-width: 75px; } QDateEdit:on { padding-top: 3px; padding-left: 4px; selection-background-color: #4a4a4a; } QDateEdit QAbstractItemView { background-color: #232629; border-radius: 2px; border: 1px solid #3375A3; selection-background-color: #3daee9; } QDateEdit::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 15px; border-left-width: 0px; border-left-color: darkgray; border-left-style: solid; border-top-right-radius: 3px; border-bottom-right-radius: 3px; } QMenu { menu-scrollable: 1; } /* Icon size for the QListWidget in the preferences dialog */ QAbstractItemView#sections { qproperty-iconSize: 22px; } PreferencesDialog { qproperty-iconMode: false; } /* Change colors in the control bars */ EditableToolbar { border: none; background-color: #31363b; } EditableToolbar::separator { width: 0px; } AutohideWidget { border: 0px; } AutohideWidget > QToolBar { border: 1px solid black; } EditableToolbar > QToolButton { border: 1px solid transparent; margin-left: 5px; margin-right: 5px; margin-top: 2px; margin-bottom: 2px; color: black; min-height: 24px; } EditableToolbar > QToolButton:hover { border: 1px solid gray; background: #21262b; border-radius: 4px; } EditableToolbar > QToolButton::menu-button:hover { border: none; background-color: transparent; padding: 0; } EditableToolbar > QToolButton:pressed { border: 1px solid gray; background: #21262b; } EditableToolbar > QToolButton:checked { border: 1px solid gray; background: #11161b; border-radius: 4px; } EditableToolbar > QToolButton:disabled { color: gray; background-color: transparent; } EditableToolbar > QToolButton[popupMode="1"] { padding-right: 20px; } EditableToolbar > QToolButton[popupMode="2"] { padding-right: 10px; } EditableToolbar > QToolButton::menu-button { border: 0px solid gray; border-top-right-radius: 6px; border-bottom-right-radius: 6px; width: 16px; margin-right: 3px; } EditableToolbar > QToolButton::menu-arrow:open { top: 1px; left: 1px; } /* Status bar */ QStatusBar, QStatusBar QLabel { padding-left: 2px; padding-right: 2px; font: italic bold 16px; background: black; color: white; } QStatusBar::item { border: none; } /* Time labels in control bars */ #time_label { color: white; /* color: black; */ border: 0px solid transparent; font: italic bold 18px; padding-left: 10px; padding-right: 10px; background: transparent; } /* Seek and volume slider in tool bars */ MySlider { /* border: 1px solid black; */ min-height: 24px; background: transparent; } MySlider:disabled { background: transparent; } /* Horizontal */ MySlider::groove:horizontal { border: 1px solid #637EB8; background: white; height: 12px; border-radius: 3px; } MySlider::sub-page:horizontal { background: #3daee9; } MySlider::add-page:horizontal { background: #565a5e; } MySlider::handle:horizontal{ background: #232629; border: 1px solid #565a5e; width: 10px; border-radius: 2px; margin: -4px 0; } /* Vertical */ MySlider::groove:vertical{ border: 1px solid #637EB8; background: white; width: 12px; border-radius: 3px; } MySlider::add-page:vertical { background: #565a5e; } MySlider::sub-page:vertical { background: #3daee9; } MySlider::handle:vertical{ background: #232629; border: 1px solid #565a5e; height: 10px; border-radius: 2px; margin: 0 -4px; } /* Horizontal and vertical */ MySlider::handle:horizontal:hover, MySlider::handle:vertical:hover { border: 1px solid #154A98; border-radius: 3px; } QWidget#panel:focus { border: none; } QComboBox > LineEditWithIcon, QComboBox > LineEditWithIcon:focus, QComboBox > LineEditWithIcon:hover { padding: 0; border: 0; } QTabBar::tab:disabled { color: #777777; } /* * vim: sw=4 ts=4 wm=4 et ft=css */ ================================================ FILE: data/win/qpseq66.ctrl ================================================ # Seq66 0.94.1 (and above) MIDI control configuration file # # /home/ahlstrom/.config/seq66/qseq66.ctrl # Written 2021-06-11 13:46:09 # # This file holds MIDI I/O control setups for Seq66. It follows the format # of the 'rc' configuration file, but is stored separately for flexibility # It is always stored in the main configuration directory. To use this # file, specify it in the [midi-control-file] section in the 'rc' file. # Use the base-name (e.g. nanomap.ctrl). # Version 1 adds the [mute-control-out] and [automation-control-out] # sections. Versions 2 and 3 simplify the data items. [Seq66] config-type = "ctrl" version = 4 # The [comments] section holds the user documentation for this file. # The first completely empty, comment, or tag line ends the comment. [comments] Add your comment block here [midi-control-settings] # The control-buss value ranges from 0 to the maximum system input buss. # If set, then that buss will be allowed to send MIDI control. 255 (0xFF) # means any buss can send MIDI control. The buss(es) must be enabled in # the 'rc' file. With ALSA, there is an extra 'announce' buss, which will # alter the numbering compared to JACK. # # The 'midi-enabled' flag applies to the MIDI controls; keystrokes are # always enabled. Supported keyboard layouts are 'qwerty' (the default), # 'qwertz', and 'azerty'. AZERTY turns off the auto-shift feature for # group-learn. control-buss = 0xFF midi-enabled = false button-offset = 0 button-rows = 4 button-columns = 8 keyboard-layout = qwerty # The control stanza incorporates key control as well as MIDI, but # keys support only 'toggle', and key-release is an 'invert'. The # leftmost number on each line here is the pattern number (e.g. # 0 to 31); the group number, same range; or an automation control # number. This internal control number is followed by three groups of # bracketed numbers, each providing three different types of control: # # Normal: [toggle] [on] [off] # Increment/Decr: [increment] [increment] [decrement] # Playback: [pause] [start] [stop] # Playlist/Song: [by-value] [next] [previous] # # In each group, there are six numbers: # # [on/off invert status d0 d1min d1max] # # 'on/off' enables/disables (1/0) the control for the pattern; # 'invert' (1/0) causes the opposite, but not all support this, and # all keystroke-releases set invert to true; 'status' is the MIDI # event to match (channel is NOT ignored), and if set to 0x00, the # control is disabled; 'd0' is the first data value, e.g. if status # is 0x90 (Note On), d0 represents the note number; d1min to d1max # is the range of data values detected, e.g. for a Note On, 1 to 127 # indicate that any non-zero velocity will invoke the control. # Hex values can be used; precede with '0x'. # # ------------------------- Loop, group, or automation-slot number # | ---------------------- Name of the key (see the key map) # | | # | | ---------------- Inverse # | | | -------------- MIDI status/event byte (e.g. Note On) # | | | | ------------ d0: Data 1 (e.g. Note number) # | | | | | ---------- d1max: Data 2 min (e.g. Note velocity) # | | | | | | -------- d1min: Data 2 max # | | | | | | | # v v v v v v v # 0 "F1" [0 0 0 0 0 0] [0 0 0 0 0 0] [0 0 0 0 0 0] # Toggle On Off [loop-control] 0 "1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 0 1 "q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 1 2 "a" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 2 3 "z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 3 4 "2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 4 5 "w" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 5 6 "s" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 6 7 "x" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 7 8 "3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 8 9 "e" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 9 10 "d" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 10 11 "c" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 11 12 "4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 12 13 "r" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 13 14 "f" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 14 15 "v" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 15 16 "5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 16 17 "t" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 17 18 "g" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 18 19 "b" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 19 20 "6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 20 21 "y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 21 22 "h" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 22 23 "n" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 23 24 "7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 24 25 "u" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 25 26 "j" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 26 27 "m" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 27 28 "8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 28 29 "i" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 29 30 "k" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 30 31 "," [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 31 [mute-group-control] 0 "!" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 0 1 "Q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 1 2 "A" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 2 3 "Z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 3 4 "@" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 4 5 "W" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 5 6 "S" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 6 7 "X" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 7 8 "#" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 8 9 "E" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 9 10 "D" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 10 11 "C" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 11 12 "$" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 12 13 "R" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 13 14 "F" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 14 15 "V" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 15 16 "%" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 16 17 "T" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 17 18 "G" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 18 19 "B" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 19 20 "^" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 20 21 "Y" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 21 22 "H" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 22 23 "N" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 23 24 "&" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 24 25 "U" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 25 26 "J" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 26 27 "M" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 27 28 "*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 28 29 "I" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 29 30 "K" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 30 31 "<" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 31 [automation-control] 0 "'" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Up 1 ";" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Dn 2 "]" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Up 3 "[" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Dn 4 "KP_Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Replace 5 "Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Snapshot 6 "o" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Queue 7 "`" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Mute 8 "l" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Group Learn 9 "Home" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playing Set 10 "." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Playback 11 "P" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Record 12 "BkSpace" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Solo 13 "KP_/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Thru 14 "PageUp" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Up 15 "PageDn" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Page Dn 16 "KP_." [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Set Set 17 "KP_*" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Record 18 "KP_-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quan Record 19 "KP_+" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reset Seq 20 "|" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # One-shot 21 "F6" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # FF 22 "F5" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Rewind 23 "F1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Top 24 "F2" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play List 25 "F3" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Play Song 26 "F9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Tap BPM 27 "Space" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Start 28 "Esc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Stop 29 "KP_Ins" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 29 30 "F8" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle Mute 31 "F7" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Pos 32 "\" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Keep Queue 33 "/" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Slot Shift 34 "0" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mutes Clear 35 "Quit" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Quit 36 "=" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop Edit 37 "-" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Event Edit 38 "F10" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Song Mode 39 "F11" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Toggle JACK 40 "F12" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Menu Mode 41 "F4" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Follow JACK 42 "~" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Panic 43 "0xf9" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 43 44 "0xfa" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 44 45 "0xfb" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 45 46 "0xfc" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 47 "0xfd" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 47 48 "0xfe" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 48 [midi-control-out-settings] set-size = 32 output-buss = 255 midi-enabled = false button-offset = 0 button-rows = 4 button-columns = 8 [midi-control-out] # --------------------- Pattern number (as applicable) # | ---------------- MIDI status+channel (e.g. Note On) # | | ------------ data 1 (e.g. note number) # | | | ---------- data 2 (e.g. velocity) # | | | | # v v v v # 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0] # Arm Mute Queue Delete # # This is a change (2021-02-10) from version 1 of this file. # A test of the status/event byte determines the enabled status, # and channel is incorporated into the status. Much cleaner! # The order of the lines that follow must must be preserved. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [mute-control-out] # The format of the mute and automation output events is simpler: # # ---------------------- mute-group number # | ------------------ MIDI status+channel (e.g. Note On) # | | -------------- data 1 (e.g. note number) # | | | ------------ data 2 (e.g. velocity) # | | | | # v v v v # 1 [0x00 0 0 ] [0x00 0 0] [0x00 0 0] # On Off Empty (dark) # # The mute-controls have an additional stanza for non-populated # ("deleted") mute-groups. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 2 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 3 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 4 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 5 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 6 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 7 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 8 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 9 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 10 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 11 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 12 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 13 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 14 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 15 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 16 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 17 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 18 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 19 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 20 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 21 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 22 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 23 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 24 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 25 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 26 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 27 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 28 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 29 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 30 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [automation-control-out] # This format is similar to the [mute-control-out] format, but # the first number is an active-flag, not an index number. # The stanzas are on/off/inactive, except for 'snap', which is # store/restore/inactive. 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Panic 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Stop 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Pause 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Play 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Toggle_mutes 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_record 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Slot_shift 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Free 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Queue 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Oneshot 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Replace 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Snap 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Learn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # BPM_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # List_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Song_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set_Up 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Set_Dn 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Tap_BPM 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Quit # End of /home/ahlstrom/.config/seq66/qseq66.ctrl # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/win/qpseq66.drums ================================================ # Seq66 0.94.1 (and above) note-mapper ('drums') configuration file # # C:\Users\chris\AppData\Local\seq66\qpseq66.drums # Written 2021-06-19 07:58:39 # # This file is similar to files generated by the 'midicvt' program, # but heavily modified for simplicity for Seq66. # # midicvtpp --csv-drum GM_DD-11_Drums.csv --output ddrums.ini # # This file can be used to convert the percussion of non-GM devices # to GM, as best as permitted by GM percussion. Although it is a # 'drums' file, it can be used for other note-mappings as well. [Seq66] config-type = "drums" version = 0 # The [comments] section can document this file. Lines starting # with '#', '[', that are blank end the comment. [comments] Comments added to this section are preserved. Lines starting with a '#' or '[', or that are blank, are ignored. Start lines that must look empty with a space. # This file holds a drum/note mapping configuration for Seq66. It is # stored in the configuration directory. To use this file, add this # file-name to the '[note-mapper]' section of the 'rc' file. There is # no user interface for managing this file. The main values are: # # map-type: drum, patch, or multi; indicates the mapping to do. # gm-channel: Indicates the channel (1-16) applied to convert notes. # reverse: true or false; map in the opposite direction if true. [notemap-flags] map-type = gm-channel = 1 reverse = false # The drum section: # # [Drum 35]. Marks a GM drum-change section, one per instrument. # # gm-name GM name for the drum assigned to the input note. # gm-note Input note number, same as the section number. # dev-name The device's name for the drum. # dev-note GM MIDI note whose GM sound best matches the sound # of dev-name. The gm-note value is converted to the # dev-note value (unless reverse mapping is activated). # Note that the actual GM drum sound might not match # what the MIDI hardware puts out. [Drum 36] dev-name = "Bass Drum Gated Reverb" gm-name = "Bass Drum 1" dev-note = 36 gm-note = 35 # End of C:\Users\chris\AppData\Local\seq66\qpseq66.drums # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/win/qpseq66.mutes ================================================ # Seq66 0.94.1 (and above) mute-groups configuration file # # C:\Users\chris\AppData\Local\seq66\qpseq66.mutes # Written 2021-06-19 07:58:39 # # This file replaces the [mute-group] section of the 'rc' file, # making it easier to manage multiple sets of mute groups. [Seq66] config-type = "mutes" version = 0 # The [comments] section documents this file. Lines starting with # '#', '[', or that have no characters end the comment. [comments] Comments added to this section are preserved. Lines starting with a '#' or '[', or that are blank, are ignored. Start lines that must look empty with a space. # The 'mutes' file holds the global mute-groups configuration. It is # is stored separately for flexibility, always in the configuration # directory. To use this file, specify it in the [mute-group-file] # tag in the 'rc' file and set 'active' to true. # # load-mute-groups: set to 'none', or 'mutes' to load only from the # 'mutes' file, 'midi' to load from the song, or 'both' to try to # to read from the 'mutes' first, then the 'midi' if empty. # # save-mutes-to: 'both' writes the mutes values to both the mutes and # the MIDI file; 'midi' writes only to the MIDI file; and the 'mutes' # only to the mutes file. # # mute-group-rows and mute-group-columns: Specifies the size of the # grid. For now, keep these values at 4 and 8. # # groups-format: 'binary' means write mutes as 0 or 1; 'hex' means to # write them as hexadecimal numbers (e.g. 0xff), which is useful for # larger set sizes. # # mute-group-selected: if 0 to 31, and mutes are available either from # this file or from the MIDI file, then the mute-group is applied at # startup; useful in restoring a session. Set to -1 to disable. # # toggle-active-only: when a mute-group is toggled off, all patterns, # even those outside the mute-group, are muted. If this flag is set # to true, only patterns in the mute-group are muted. Any patterns # unmuted directly by the user remain unmuted. [mute-group-flags] load-mute-groups = none save-mutes-to = mutes strip-empty = true mute-group-rows = 4 mute-group-columns = 8 mute-group-count = 32 mute-group-selected = -1 groups-format = binary toggle-active-only = false [mute-groups] # All mute-group values are saved in this 'mutes' file, even if they # all are zero; but if all are zero, they can be stripped out of the # MIDI file by the strip-empty-mutes functionality. If a hex number # is found, each number represents a bit mask, rather than a single # bit. An optional quoted group name can be placed at the end of the # line. 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 ] 1 [ 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 ] 2 [ 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 ] 3 [ 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 ] 4 [ 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 ] 5 [ 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 ] 6 [ 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 ] 7 [ 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 ] 8 [ 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 ] 9 [ 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 ] 10 [ 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 ] 11 [ 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 ] 12 [ 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 ] 13 [ 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 ] 14 [ 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 ] 15 [ 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 ] 16 [ 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 ] 17 [ 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 ] 18 [ 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 ] 19 [ 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 ] 20 [ 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 ] 21 [ 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 ] 22 [ 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 ] 23 [ 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 ] 24 [ 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 ] 25 [ 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 ] 26 [ 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 ] 27 [ 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 ] 28 [ 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 ] 29 [ 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 ] 30 [ 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 ] 31 [ 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 ] # End of C:\Users\chris\AppData\Local\seq66\qpseq66.mutes # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/win/qpseq66.palette ================================================ # Seq66 0.99.11 (and above) palette configuration file # # /home/user/.config/seq66/qpseq66.palette # Written on 2023-10-27 # # This file can be used to change the colors used by patterns # and in some parts of the user-interface. It must be active and # specified in the 'rc' file. [Seq66] config-type = "palette" version = 1 # The [comments] section can document this file. Lines starting with # '#', '[', or that have no characters end the comment. [comments] Comments added to this section are preserved. Lines starting with a '#' or '[', or that are blank, are ignored. Start lines that must look empty with a space. # Set 'dark-theme' to true if the matching style-sheet is dark. This # also set the 'dark-theme' setting in the 'usr' file. # Set 'dark-ui' to true if the palette background elements are dark. [hints] dark-theme = false dark-ui = false # The first integer is the color number, ranging from 0 to 31. The # first string is the name of the background color. The first # stanza (in square brackets) are the ARGB values for the background. # The second set provides the foreground color name and color. # The alpha values are not important here, but should be set to FF. [palette] 0 "Black" [ 0xFF000000 ] "White" [ 0xFFFFFFFF ] 1 "Red" [ 0xFFFF0000 ] "White" [ 0xFFFFFFFF ] 2 "Green" [ 0xFF008000 ] "White" [ 0xFFFFFFFF ] 3 "Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 4 "Blue" [ 0xFF0000FF ] "White" [ 0xFFFFFFFF ] 5 "Magenta" [ 0xFFFF00FF ] "White" [ 0xFFFFFFFF ] 6 "Cyan" [ 0xFF00FFFF ] "Black" [ 0xFF000000 ] 7 "White" [ 0xFFFFFFFF ] "Black" [ 0xFF000000 ] 8 "Dark Black" [ 0xFF2F4F4F ] "White" [ 0xFFFFFFFF ] 9 "Dark Red" [ 0xFF8B0000 ] "White" [ 0xFFFFFFFF ] 10 "Dark Green" [ 0xFF006400 ] "White" [ 0xFFFFFFFF ] 11 "Dark Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 12 "Dark Blue" [ 0xFF00008B ] "White" [ 0xFFFFFFFF ] 13 "Dark Magenta" [ 0xFF8B008B ] "White" [ 0xFFFFFFFF ] 14 "Dark Cyan" [ 0xFF008B8B ] "White" [ 0xFFFFFFFF ] 15 "Dark White" [ 0xFF808080 ] "White" [ 0xFFFFFFFF ] 16 "Orange" [ 0xFFFFA500 ] "White" [ 0xFFFFFFFF ] 17 "Pink" [ 0xFFFFC0CB ] "Black" [ 0xFF000000 ] 18 "Pale Green" [ 0xFF98FB98 ] "Black" [ 0xFF000000 ] 19 "Khaki" [ 0xFFF0E68C ] "Black" [ 0xFF000000 ] 20 "Light Blue" [ 0xFFADD8E6 ] "Black" [ 0xFF000000 ] 21 "Light Magenta" [ 0xFFEE82EE ] "Black" [ 0xFF000000 ] 22 "Turquoise" [ 0xFF40E0D0 ] "Black" [ 0xFF000000 ] 23 "Grey" [ 0xFF808080 ] "Black" [ 0xFF000000 ] 24 "Dk Orange" [ 0xFFFF8C00 ] "Black" [ 0xFF000000 ] 25 "Dark Pink" [ 0xFFFF1493 ] "Black" [ 0xFF000000 ] 26 "Sea Green" [ 0xFF2E8B57 ] "Black" [ 0xFF000000 ] 27 "Dark Khaki" [ 0xFFBDB76B ] "Black" [ 0xFF000000 ] 28 "Dark Slate Blue" [ 0xFF483D8B ] "Black" [ 0xFF000000 ] 29 "Dark Violet" [ 0xFF9400D3 ] "Black" [ 0xFF000000 ] 30 "Light Grey" [ 0xFFC0C0C0 ] "Black" [ 0xFF000000 ] 31 "Dark Grey" [ 0xFF606060 ] "Black" [ 0xFF000000 ] # Similar to the [palette] section, but applies to UI elements and to # the --inverse color option. The first integer is the color number, # ranging from 0 to 12. The names are feature names, not color names. [ui-palette] 0 "Foreground" [ 0xFF000000 ] "Foreground" [ 0xFFFFFFFF ] 1 "Background" [ 0xFFFFFFFF ] "Background" [ 0xFF606060 ] 2 "Label" [ 0xFF000000 ] "Label" [ 0xFFFFFFFF ] 3 "Selection" [ 0xFFFFA500 ] "Selection" [ 0xFFFF00FF ] 4 "Drum" [ 0xFFFF0000 ] "Drum" [ 0xFF000080 ] 5 "Tempo" [ 0xFFFF00FF ] "Tempo" [ 0xFFFFFF00 ] 6 "Note Fill" [ 0xFFFFFFFF ] "Note Fill" [ 0xFF000000 ] 7 "Note Border" [ 0xFF000000 ] "Note Border" [ 0xFFFFFFFF ] 8 "Black Keys" [ 0xFF000000 ] "Black Keys" [ 0xFFFFFFFF ] 9 "White Keys" [ 0xFFFFFFFF ] "White Keys" [ 0xFF000000 ] 10 "Progress Bar" [ 0xFFFF0000 ] "Progress Bar" [ 0xFF000080 ] 11 "Back Pattern" [ 0xFF008B8B ] "Back Pattern" [ 0xFF008B8B ] 12 "Medium Line" [ 0xFF808080 ] "Medium Line" [ 0xFF808080 ] 13 "Heavy Line" [ 0xFF484848 ] "Heavy Line" [ 0xFFFFFFFF ] 14 "Light Line" [ 0xFFC0C0C0 ] "Light Line" [ 0xFFC0C0C0 ] 15 "Beat" [ 0xFF000000 ] "Beat" [ 0xFFFFFFFF ] 16 "Near" [ 0xFFFFFF00 ] "Near" [ 0xFFFF00FF ] 17 "Time Brush" [ 0xFF808080 ] "Time Brush" [ 0xFF808080 ] 18 "Data Brush" [ 0xFF808080 ] "Data Brush" [ 0xFF808080 ] 19 "Event Brush" [ 0xFF808080 ] "Event Brush" [ 0xFF808080 ] 20 "Keys Brush" [ 0xFF808080 ] "Keys Brush" [ 0xFF808080 ] 21 "Names Brush" [ 0xFF808080 ] "Names Brush" [ 0xFF808080 ] 22 "Octave Line" [ 0xFF808080 ] "Octave Line" [ 0xFF808080 ] 23 "Text" [ 0xFF808080 ] "Text" [ 0xFF808080 ] 24 "Time Text" [ 0xFF000000 ] "Time Text" [ 0xFFFFFFFF ] 25 "Data Text" [ 0xFF000000 ] "Data Text" [ 0xFFFFFFFF ] 26 "Note Event" [ 0xFFFFFFFF ] "Note Event" [ 0xFF000000 ] 27 "Keys Text" [ 0xFF000000 ] "Keys Text" [ 0xFFFFFFFF ] 28 "Names Text" [ 0xFF000000 ] "Names Text" [ 0xFFFFFFFF ] 29 "Slots Text" [ 0xFF000000 ] "Slots Text" [ 0xFFFFFFFF ] 30 "Extra 1" [ 0xFF000000 ] "Extra 1" [ 0xFFFFFFFF ] 31 "Extra 2" [ 0xFF000000 ] "Extra 2" [ 0xFFFFFFFF ] # This section defines brush styles to use. The names are based on the # names in the Qt::BrushStyle enumeration. The supported names are: # # nobrush, solid, dense1, dense2, dense3, dense4, dense5, dense6, # dense7, horizontal, vertical, cross, bdiag, fdiag, diagcross, # lineargradient, radialgradient, and conicalgradient. # # For 'empty', best to just use 'solid' (try others and see why). # For 'note', use 'solid' or the default, 'lineargradient'. These # also apply to the progress box and triggers. [brushes] empty = solid note = lineargradient scale = dense3 backseq = dense2 chord = bdiag # This section defines pen styles to use for vertical time lines. # The names are based on the names in the Qt::PenStyle enumeration. # They are: # nopen, solid, dash, dot, dashdot, dashdotdot, customdash (which # is currently not supported). # # 'measure' and 'beat' are obvious. 'fourth' is a line for the 1/4s of # a beat, and 'step' is the smallest unit (normally 6 pixels). [pens] measure = solid beat = solid fourth = dashdot step = dot # End of /home/user/.config/seq66/qpseq66.palette # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/win/qpseq66.playlist ================================================ # Seq66 0.94.1 (and above) playlist configuration file # # C:\Users\chris\AppData\Local\seq66\qpseq66.playlist # Written 2021-06-19 07:58:39 # # This file holds multiple playlists for Seq66. It consists of 1 or # more [playlist] sections. Each has a user-specified number for # sorting and MIDI control, ranging from 0 to 127. Next comes a # quoted display name for this list, followed by the quoted name of # the song directory, always using the UNIX-style separator ('/'). # It should be accessible from wherever Seq66 is run. # # Then comes a list of tunes, each starting with a MIDI control number # and the quoted name of the MIDI file. They are sorted by the # control number, starting from 0. They can be simple 'base.midi' # file-names; the playlist directory is prepended before the song is # accessed. If the MIDI file-name already has a path, that will be # used. [Seq66] config-type = "playlist" version = 1 # The [comments] section holds user documentation for this file. # The first completely empty, comment, or tag line ends the comment. [comments] (Put your comment line(s) here as per the explanation above.) [playlist-options] unmute-new-song = false deep-verify = false # First provide the playlist settings, then its default storage folder # and then list each tune with its control number. The playlist # number is arbitrary but unique. 0 to 127 recommended for use with # the MIDI playlist control. Similar for the tune numbers. Each # tune can include a path; it overrides the base directory. [playlist] number = 0 name = "My Midi Files" directory = "C:/Users/chris/AppData/Local/My Songs/" 0 "b4uacufm.midi" 1 "brecluse.midi" # End of C:\Users\chris\AppData\Local\seq66\qpseq66.playlist # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/win/qpseq66.rc ================================================ # Seq66 0.94.1 (and above) main ('rc') configuration file # # C:\Users\chris\AppData\Local\seq66\qpseq66.rc # Written 2021-06-19 07:58:39 # # This file holds the main configuration for Seq66. It no longer # follows the format of the seq24rc configuration file very much. # # 'version' is set by Seq66; it is used to detect older configuration # files, which are upgraded to the new version when saved. # # 'sets-mode' affects if sets are muted when moving to the next set or # play-screen ('normal'). 'autoarm' unmutes the next set. 'additive' # keeps the previous set armed when moving to the next set. 'allsets' # arms all sets at once. # # 'port-naming' values are 'short' or 'long'. The short style just # shows the port number and short port name; the long style shows # all the numbers and long port name. [Seq66] config-type = "rc" version = 2 verbose = false sets-mode = normal port-naming = short # The [comments] section holds user documentation for this file. # The first completely empty, comment, or tag line ends the comment. [comments] Comments added to this section are preserved. Lines starting with a '#' or '[', or that are blank, are ignored. Start lines that must look empty with a space. # Provides a flag and a file-name for MIDI-control I/O settings. Use # '""' to indicate no 'ctrl' file. If none, the default internal # keystrokes are used, with no MIDI control I/O. [midi-control-file] active = false name = "qpseq66.ctrl" # Provides a flag and a file-name for mute-groups settings. Use # '""' to indicate no 'mutes' file. If none, there are no mute # groups unless the MIDI file contains some. [mute-group-file] active = false name = "qpseq66.mutes" # Provides a flag and a file-name for 'user' settings. Use '""' # to indicate no 'usr' file. If none, there are no special user # settings. Using no 'usr' file should be considered experimental. [usr-file] active = true name = "qpseq66.usr" # Provides a play-list file and a flag to activate it. If no list, # use '""' and set 'active' to false. Use the extension '.playlist'. # Even if not active, the play-list file is read, which adds to the # startup time. The 'base-directory' is optional. If non-empty, it # sets the directory holding all MIDI files in all play-lists, helpful # when play-lists and tunes from one directory to another, preserving # the sub-directories. [playlist] active = false name = "qpseq66.playlist" base-directory = "" # Provides a flag and file-name for note-mapping settings. Use '""' # to indicate no 'drums' file. Use the extension '.drums'. This file is # used when the user invokes the note-conversion operation in the # pattern editor on a transposable pattern. For percussion, make the # pattern temporarily transposable to allow this operation. [note-mapper] active = false name = "qpseq66.drums" # Provides a flag and a file-name to allow modifying the palette # using the file given below. Use '""' to indicate no 'palette' # file. If none or not active, the internal palette is used. [palette-file] active = false name = "qpseq66.palette" # Defines features of MIDI meta-event handling. Tempo events occur in # the first track (pattern 0), but one can move tempo elsewhere. It # It changes where tempo events are recorded. The default is 0, the # maximum is 1023. A pattern must exist at this number. [midi-meta-events] tempo-track = 0 # Set to true to have Seq66 create virtual ALSA/JACK I/O ports and not # auto-connect to other clients. It allows up to 48 output ports and # 48 input ports (defaults to 8 and 4). Set to false to auto-connect # Seq66 to the system's existing ALSA/JACK MIDI ports. [manual-ports] virtual-ports = false output-port-count = 8 input-port-count = 4 # These system ports are available for input. From JACK's view, these # are 'playback' devices. The first number is a buss number, and the # second number is the input status, disabled (0) or # enabled (1). The item in quotes is the input-buss name. [midi-input] 0 # number of input MIDI busses # These system ports are available for output, for playback/control. # From JACK's view, these are 'capture' devices. The first line shows # the count of MIDI 'capture' ports. Each line shows the buss number # and clock status of that buss: # # -1 = The output port is disabled. # 0 = MIDI Clock is off. The output port is enabled. # 1 = MIDI Clock on; Song Position and MIDI Continue are sent. # 2 = MIDI Clock Modulo. # # With Clock Modulo, MIDI clocking doesn't begin until song position # reaches the start modulo value [midi-clock-mod-ticks]. One can # disable a port manually for devices that are present, but not # available (because another application, e.g. Windows MIDI Mapper, # has exclusive access to the device. [midi-clock] 2 # number of MIDI clocks (output busses) 0 0 "[0] 0:0 PortMidi:Microsoft MIDI Mapper" 1 -1 "[1] 1:1 PortMidi:Microsoft GS Wavetable Synth" # 'ticks' provides the Song Position (in 16th notes) at which # clocking begins if the buss is set to MIDI Clock Mod setting. # 'record-by-channel' allows the master MIDI bus to record/filter # incoming MIDI data by channel, adding each new MIDI event to the # pattern that is set to that channel. This option adopted from the # Seq32 project at GitHub. [midi-clock-mod-ticks] ticks = 64 record-by-channel = true # Set to true to have Seq66 ignore port names defined in the 'usr' # file. Use this option to to see the system ports as detected # by ALSA/JACK. [reveal-ports] show-system-ports = false # Sets the mouse usage for drawing and editing a pattern. The # 'fruity' mode is currently NOT supported in Seq66. Also obsolete # is the Mod4 feature. Other interaction settings are available. # 'snap-split' enables splitting performance editor triggers at the # closest snap position, instead of exactly in its middle. The split # is activated by a middle click or Ctrl-left click. # 'click-edit' allows a double-click on a slot to bring it up in # the pattern editor. This is the default. Set it to false if # it interferes with muting/unmuting a pattern. [interaction-method] snap-split = true double-click-edit = true # 'transport-type' enables synchronization with JACK Transport. Values # can be: # # 'none': No JACK Transport in use. # 'slave': Use JACK Transport as Slave. # 'master': Attempt to serve as JACK Transport Master. # 'conditional': Serve as JACK master if no JACK master exists. # # 'song-start-mode' is one of the following values: # # live: Playback in Live mode. Allows muting and unmuting of loops # from the main (patterns) window. Same as false. # song: Playback uses the Song (performance) editor's data and mute # controls. Same as true. # # 'jack-midi' sets/unsets JACK MIDI, separate from JACK transport. [jack-transport] transport-type = none song-start-mode = song jack-midi = false # 'auto-save-rc' enables/disables the automatic saving of the running # configuration to 'rc' and other files. True is the old Seq24 # behavior. If auto-save is set, many of the command-line settings, # (e.g. JACK/ALSA), are saved to some of the configuration files. # There is no user-interface control for this setting at present. # # The old-triggers value indicates to save triggers in a format # compatible with Seq24. Otherwise, triggers are saved with an # additional 'transpose' setting. Similarly, the old-mutes value, # if true, saves mute-groups as long values (!) instead of bytes. [auto-option-save] auto-save-rc = true save-old-triggers = false save-old-mutes = false # Specifies the last-used and currently-active directory. [last-used-dir] "C:/Users/chris/AppData/Local/seq66/" # Holds a list of the last few recently-loaded MIDI files. The first # number is the number of items in the list. The second value # indicates if to load the most recent file (the top of the list) # at startup. [recent-files] count = 2 load-most-recent = true "C:/Users/chris/AppData/Local/seq66/Peter_Gunn_port_0.midi" "C:/Program Files/Seq66/data/midi/Peter_Gunn-reconstructed.midi" # End of C:\Users\chris\AppData\Local\seq66\qpseq66.rc # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/win/qpseq66.usr ================================================ # Seq66 0.94.1 (and above) user ('usr') configuration file # # C:\Users\chris\AppData\Local\seq66\qpseq66.usr # Written 2021-06-19 07:58:39 # # This is a Seq66 'usr' file. Edit it and place it in the # $HOME/.config/seq66 directory. It allows one to apply an alias # (alternate name) to each MIDI bus, MIDI channel, and MIDI control # control code, per channel. It has additional options not present in # in Seq24, and supports DOS INI-style variable setting. [Seq66] config-type = "usr" version = 5 # The [comments] section lets one document this file. Lines starting # with '#' and '[', or that are empty end the comment. [comments] Comments added to this section are preserved. Lines starting with a '#' or '[', or that are blank, are ignored. Start lines that must look empty with a space. # [user-midi-bus-definitions] # # 1. Define your instruments and their control-code names, if they # have them. # 2. Define a MIDI bus, its name, and what instruments are on which # channel. # # In the following MIDI buss definitions, channels are counted from # 0 to 15, not 1 to 16. Instruments not set here are set to -1 and # are GM (General MIDI). These replacement MIDI buss labels are shown # in MIDI Clocks, MIDI Inputs, and in the Pattern Editor buss and # channel drop-downs. To disable the entries, set the counts to 0. [user-midi-bus-definitions] 0 # number of user-defined MIDI busses # In the following MIDI instrument definitions, active controller # numbers (i.e. supported by the instrument) are paired with # the (optional) name of the controller supported. [user-instrument-definitions] 0 # instrument list count # [user-interface-settings] # # Specifies the configuration of some user-interface elements. Many # items were rendered obsolete and removed in version 5 of this file. # The main grid-style is now Qt buttons. To use a flat style, use # Qt themes/style-sheets. # # 'mainwnd-rows' and 'mainwnd-columns' (option '-o sets=RxC') specify # rows/columns in the main grid. R ranges from 4 to 8, C from 8 to 12. # Values other than 4x8 have not been tested, use at your own risk. # # 'mainwnd-spacing' specifies spacing in the main window. It ranges # from 2 (default) to 16. # # 'default-zoom' specifies initial zoom for the piano rolls. Ranges # from 1 to 512; defaults to 2. Larger PPQNs require larger zoom to # look good in the editors. Seq66 adapts the zoom to the PPQN # if 'default-zoom' zoom is set to 0. The unit of zoon is ticks/pixel. # # 'global-seq-feature' specifies if the key, scale, and background # pattern are to be applied globally to all patterns, or separately # to each. These three values are stored in the MIDI file, either in # the global SeqSpec section, or in each track. # # false: Each pattern has its own key/scale/background. # true: Apply these settings globally to all patterns. # # 'progress-bar-thick specifies a thicker progress bar. Default is 1 # pixel; thick is 2 pixels. Set it to true to enable the feature # # 'inverse-colors' (option -K/--inverse) specifies use of an inverse # color palette. Palettes are for Seq66 drawing areas, not for the # Qt theme. Normal/inverse palettes are changed via a 'palette' file. # # 'window-redraw-rate' specifies the base window redraw rate for all # windows. The default is 40 ms (25 ms for Windows). # # Window-scale (option '-o scale=m.n[xp.q]') specifies scaling the # main window at startup. Defaults to 1.0 x 1.0. If between 0.8 and # 3.0, it changes the size of the main window proportionately. If the # y-value is 0, the first value applies to both dimensions. [user-interface-settings] mainwnd-rows = 4 mainwnd-columns = 8 mainwnd-spacing = 2 default-zoom = 2 global-seq-feature = true progress-bar-thick = true inverse-colors = false window-redraw-rate = 20 window-scale = 1 window-scale-y = 1 # [user-midi-ppqn] # # Seq66 separates the file PPQN from the Seq66 PPQN the user wants # to use. # # 'default-ppqn' specifies the PPQN to use by default. The classic # default is 192, but can range from 32 to 19200. # # 'use-file-ppqn' indicates to use the file PPQN. This is the best # setting, to avoid changing the file's PPQN. [user-midi-ppqn] default-ppqn = 192 use-file-ppqn = false # [user-midi-settings] # # These settings specify MIDI-specific values better off as variables, # rather than constants. Values of -1 mean the value won't be used. # # 'beats-per-bar': default = 4 range = 1 to 32. # 'beats-per-minute': default = 120.0 range = 2.0 to 600.0. # 'beat-width': default = 4 range = 1 to 32. # 'buss-override': default = -1 range = 0 to 48. # 'velocity-override': default = -1 range = 0 to 127. # 'bpm-precision': default = 0 range = 0 to 2. # 'bpm-step-increment': default = 1.0 range = 0.01 to 25.0. # 'bpm-page-increment': default = 1.0 range = 0.01 to 25.0. # 'bpm-minimum': default = 0.0 range = 127.0 # 'bpm-maximum': default = 0.0 range = 127.0 # # A buss-override from 0 to 48 overrides the busses for all patterns, # for testing or convenience. Do not save the MIDI file afterwards # unless you want to overwrite all the buss values! # # The velocity override when adding notes in the pattern editor is set # via the 'Vol' button. -1 ('Free'), preserves incoming velocity. # # Precision of the BPM spinner and MIDI control of BPM is 0, 1, or 2. # The step increment affects the beats/minute spinner and MIDI control # of BPM. For 1 decimal point, 0.1 is good. For 2 decimal points, # 0.01 is good, but one might want something faster, like 0.05. # Set the page increment to a larger value than the step increment; # it is used when the Page-Up/Page-Down keys are pressed when the BPM # spinner has keyboard focus. # The BPM-minimum and maximum set the range BPM in tempo graphing. # By default, the tempo graph ranges from 0.0 to 127.0. This range # decreased to give a magnified view of tempo. [user-midi-settings] beats-per-bar = 4 beats-per-minute = 120 beat-width = 4 buss-override = -1 velocity-override = -1 bpm-precision = 0 bpm-step-increment = 1 bpm-page-increment = 10 bpm-minimum = 0 bpm-maximum = 127 # [user-options] # # These settings specify values set via the -o or --option switch, # which helps expand the number of options supported. # The 'daemonize' option is used in seq66cli to indicate that the # application should be gracefully run as a service. # The 'log' value specifies a log-file that replaces output to # standard output/error. For no log-file, use "". This option # also works from the command line: '-o log=filename.log'. The name # here is used for the no-name '-o log' option. [user-options] daemonize = false log = "" # [user-ui-tweaks] # # The key-height value specifies the initial height (before vertical # zoom) of the keys in the pattern editor. Defaults to 10 pixels, # ranges from 6 to 32. # # The note-resume option, if active, causes any notes in progress # to be resumed when the pattern is toggled back on. # # If specified, a style-sheet (e.g. 'qseq66.qss') is applied at # startup. Normally just a base-name, it can contain a file-path # to provide a style usable in many other applications. # # A fingerprint is a condensation of the note events in a long track, # to reduce the amount of drawing in the grid buttons. Ranges from 32 # (the default) to 128. Set to 0 to not use a fingerprint. # # The progress-box width and height settings change the size of the # progress box in the live-loop grid buttons. Width ranges from 0.50 # to 1.0; the height from 0.10 to 0.50. If either is 0, then the box # isn't drawn. If either is 'default', defaults are used. [user-ui-tweaks] key-height = 10 note-resume = false style-sheet = "" fingerprint-size = 32 progress-box-width = 0 progress-box-height = 0 # [user-session] # # This section specifies the session manager to use, if any. The # 'session' variable can be set to 'none' (the default), 'nsm' # (Non or New Session Manager), or 'lash' (LASH, not yet supported). # 'url' can be set to the value of the NSM_URL environment variable # set by nsmd when run outside of the Non Session Manager user- # interface. Set the URL only if running nsmd standalone with a # matching --osc-port number. [user-session] session = none url = "" # [pattern-editor] # # This section contains the setup values for recording when a new # pattern is opened. For flexibility, a new pattern means only that # the loop has the default name, 'Untitled'. These values save time # during a live recording session. Note that the valid values for # record-style are 'merge', 'overwrite', and 'expand'. [pattern-editor] armed = false thru = false record = false qrecord = false record-style = merge # End of C:\Users\chris\AppData\Local\seq66\qpseq66.usr # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: data/win/win_midi.playlist ================================================ # Seq66 0.99.7 playlist configuration file # # C:/Program Files/Seq66/data/win/win_midi.playlist # Written 2023-07-17 09:50:24 # # This file holds multiple playlists, each in a [playlist] section. Each has # a user-specified number for sorting and MIDI control, ranging from 0 to 127. # Next comes a quoted name for this list, followed by the quoted name # of the song directory using the UNIX separator ('/'). # # Next is a list of tunes, each starting with a MIDI control number and the # quoted name of the MIDI file, sorted by control number. They can be simple # 'base.midi' file-names; the playlist directory is prepended to access the # song file. If the file-name has a path, that will be used. [Seq66] config-type = "playlist" version = 1 # [comments] holds user documentation for this file. The first empty, hash- # commented, or tag line ends the comment. [comments] This playlist points to files installed in Windows. I created these files long, long ago. Ah, such memories. Added them to the install to make live testing easier. [playlist-options] unmute-new-song = true deep-verify = false # Here are the playlist settings, default storage folder, and then a list of # each tune with its control number. The playlist number is arbitrary but # unique. 0 to 127 enforced for use with MIDI playlist controls. Similar # for the tune numbers. Each tune can include a path; it overrides the base # directory. [playlist] number = 0 name = "Legacy Midi Files" directory = "C:/Program Files/Seq66/data/midi/FM/" 0 "brecluse.mid" 1 "carptsun.mid" 2 "cbflitfm.mid" 3 "dasmodel.mid" 4 "grntamb.mid" 5 "hapwandr.mid" 6 "judyblue.mid" 7 "k_seq11.mid" 8 "longhair.mid" 9 "marraksh.mid" 10 "oxyg4bfm.mid" 11 "pirates.mid" 12 "pss680.mid" 13 "qufrency.mid" 14 "stdemo3.mid" 15 "viceuk.mid" 16 "wallstsm.mid" [playlist] number = 1 name = "PSS-790 Midi Files" directory = "C:/Program Files/Seq66/data/midi/PSS-790/" 0 "ancestor.mid" 10 "carptsun.mid" 20 "cbflite.mid" 30 "old_love.mid" [playlist] number = 3 name = "Live vs Song Files" directory = "C:/Program Files/Seq66/data/midi/" 1 "Peter_Gunn-reconstructed.midi" 2 "Chameleon-HHancock-Ov.midi" 3 "Kraftwerk-Europe_Endless-reconstructed.midi" 4 "If_You_Could_Read_My_Mind.mid" 5 "b4uacuse-gm-patchless.midi" # End of /home/ahlstrom/.config/seq66/win_midi.playlist # # vim: sw=4 ts=4 wm=4 et ft=dosini ================================================ FILE: desktop/seq66.xpm ================================================ /* XPM */ static char * seq66_xpm[] = { "64 64 560 2", " c None", ". c #5B5C5C", "+ c #000000", "@ c #2B2B2B", "# c #000404", "$ c #1B1D1D", "% c #484748", "& c #343634", "* c #343334", "= c #3B3B3D", "- c #0D0E0E", "; c #040B0B", "> c #000606", ", c #1A1919", "' c #3A3A3A", ") c #575A5A", "! c #141414", "~ c #090A0A", "{ c #061313", "] c #411C1C", "^ c #130F0F", "/ c #030B0B", "( c #0C0B0B", "_ c #1C1C1C", ": c #3D3C3C", "< c #161616", "[ c #000304", "} c #152121", "| c #000505", "1 c #222222", "2 c #4E4E4E", "3 c #050405", "4 c #1C2929", "5 c #9E3030", "6 c #602A2A", "7 c #041717", "8 c #000101", "9 c #080808", "0 c #1E1E1E", "a c #555252", "b c #6E6C6D", "c c #615E60", "d c #010101", "e c #090D0D", "f c #454646", "g c #9D2A2A", "h c #FCF4F4", "i c #FFFFFF", "j c #6B1515", "k c #091C1C", "l c #080707", "m c #111313", "n c #434141", "o c #515151", "p c #101010", "q c #030404", "r c #122527", "s c #641F1F", "t c #874747", "u c #000A0A", "v c #494949", "w c #1D2E2E", "x c #7A3B3B", "y c #252828", "z c #000303", "A c #020303", "B c #010505", "C c #1F2B2B", "D c #6D4545", "E c #EAD0D0", "F c #914848", "G c #1E3131", "H c #060B0B", "I c #0E1416", "J c #4A4444", "K c #9F5858", "L c #8D2B2B", "M c #020E0E", "N c #414141", "O c #090E10", "P c #FCF5F5", "Q c #9F2C2C", "R c #774D4D", "S c #71696A", "T c #645E5C", "U c #675A5C", "V c #6C6161", "W c #795E5E", "X c #921313", "Y c #DFBEBE", "Z c #820606", "` c #797575", " . c #776E6E", ".. c #7B7778", "+. c #7E2728", "@. c #9A1F1F", "#. c #893738", "$. c #070B0B", "%. c #161414", "&. c #6B6B6B", "*. c #302C2E", "=. c #502A2A", "-. c #FFF4F4", ";. c #8B3838", ">. c #7F7F7F", ",. c #363536", "'. c #592F2F", "). c #F8E0E0", "!. c #C29090", "~. c #C28C8C", "{. c #C79595", "]. c #B98181", "^. c #EBD8D8", "/. c #C29191", "(. c #C79292", "_. c #AC5252", ":. c #BD8181", "<. c #BA8080", "[. c #A33E3E", "}. c #071516", "|. c #504D4E", "1. c #151414", "2. c #6C191A", "3. c #B97878", "4. c #070202", "5. c #A03939", "6. c #FCCECE", "7. c #AE8E8E", "8. c #E5CDCD", "9. c #281E1F", "0. c #000201", "a. c #3B3B3B", "b. c #100F0F", "c. c #010D0D", "d. c #903434", "e. c #B77272", "f. c #616060", "g. c #A13737", "h. c #FBE0E0", "i. c #AC8787", "j. c #E7F4F4", "k. c #EBCDCD", "l. c #3D3434", "m. c #404040", "n. c #121212", "o. c #B05B5B", "p. c #B97777", "q. c #DCEBEB", "r. c #A03636", "s. c #FAEBEB", "t. c #AB8686", "u. c #F1EAEA", "v. c #D3AEAE", "w. c #081212", "x. c #1B1B1B", "y. c #5E5C5C", "z. c #182A2A", "A. c #DADBDB", "B. c #CB9696", "C. c #B54949", "D. c #CB9292", "E. c #B34A4D", "F. c #E7EBEB", "G. c #B87575", "H. c #B54646", "I. c #DFBFBF", "J. c #F8E8E8", "K. c #AC8686", "L. c #EEE7E7", "M. c #5D2222", "N. c #6D4F4F", "O. c #B87274", "P. c #A13638", "Q. c #AD8688", "R. c #A63232", "S. c #000707", "T. c #3C3B3C", "U. c #191919", "V. c #AC3234", "W. c #E7D0D0", "X. c #FFE5E5", "Y. c #FDDADA", "Z. c #FCE0E0", "`. c #2A1B1C", " + c #101112", ".+ c #211D1D", "++ c #FAD8D8", "@+ c #A13636", "#+ c #848787", "$+ c #F8C8C8", "%+ c #FDF3F3", "&+ c #F8CECE", "*+ c #C2A9A9", "=+ c #944A4A", "-+ c #393939", ";+ c #752E2E", ">+ c #FEF5F5", ",+ c #9F3636", "'+ c #E8D3D3", ")+ c #FCE8E8", "!+ c #1C1212", "~+ c #080A0A", "{+ c #121111", "]+ c #250A0A", "^+ c #EBDDDD", "/+ c #8D8383", "(+ c #FFFAFA", "_+ c #8C3638", ":+ c #000202", "<+ c #4A4848", "[+ c #5D5D5D", "}+ c #801A1A", "|+ c #B87273", "1+ c #A4383A", "2+ c #FDEBEB", "3+ c #AF898A", "4+ c #453232", "5+ c #0A0D0D", "6+ c #F1E2E2", "7+ c #C2B7B7", "8+ c #E4E0E0", "9+ c #C2B2B2", "0+ c #C2B7B8", "a+ c #F7DADA", "b+ c #F3E5E5", "c+ c #E1DBDB", "d+ c #EEE8E8", "e+ c #D7BCBC", "f+ c #E0DBDB", "g+ c #E2D3D3", "h+ c #FDFCFC", "i+ c #070000", "j+ c #1C1B1C", "k+ c #242826", "l+ c #110304", "m+ c #672D2D", "n+ c #7E3B3B", "o+ c #803A3A", "p+ c #7E3A3B", "q+ c #7E3A3C", "r+ c #7E3A3A", "s+ c #803A3C", "t+ c #7D3A3A", "u+ c #7F3B3A", "v+ c #813332", "w+ c #7F2A2A", "x+ c #7E2A2A", "y+ c #7F2B2B", "z+ c #802B2B", "A+ c #812A2B", "B+ c #7F3434", "C+ c #7C3B3B", "D+ c #823535", "E+ c #7F292A", "F+ c #7F3535", "G+ c #7F3B3B", "H+ c #7F3A3B", "I+ c #823030", "J+ c #7E2B2B", "K+ c #813D3E", "L+ c #827676", "M+ c #828483", "N+ c #7E8787", "O+ c #7E3636", "P+ c #803C3C", "Q+ c #7E3C3C", "R+ c #833D3D", "S+ c #4E2121", "T+ c #020000", "U+ c #3F3C3F", "V+ c #060B07", "W+ c #12190C", "X+ c #131A0C", "Y+ c #141A0C", "Z+ c #14190C", "`+ c #171C0F", " @ c #161A0D", ".@ c #151A0C", "+@ c #13180A", "@@ c #141306", "#@ c #141205", "$@ c #131305", "%@ c #13190C", "&@ c #15190C", "*@ c #10160B", "=@ c #12110C", "-@ c #181848", ";@ c #121223", ">@ c #282626", ",@ c #282845", "'@ c #DCDCFA", ")@ c #16162C", "!@ c #090907", "~@ c #4B4A39", "{@ c #BFBFE5", "]@ c #D4D2C9", "^@ c #46453A", "/@ c #DBDBF4", "(@ c #626173", "_@ c #D9D8D7", ":@ c #17162C", "<@ c #11100C", "[@ c #212147", "}@ c #CECED2", "|@ c #15152B", "1@ c #171817", "2@ c #14130F", "3@ c #2F2F6B", "4@ c #EDEDE8", "5@ c #CBCBEB", "6@ c #121329", "7@ c #413F40", "8@ c #1E1C1C", "9@ c #050500", "0@ c #2C2C9F", "a@ c #CECDFA", "b@ c #DEDEEC", "c@ c #0E0E23", "d@ c #0F0D0D", "e@ c #1B1B19", "f@ c #0B0B15", "g@ c #F5F5FA", "h@ c #C8C8E2", "i@ c #383665", "j@ c #030301", "k@ c #4B4B4B", "l@ c #2B2B70", "m@ c #424148", "n@ c #B4B4D3", "o@ c #040407", "p@ c #1E1D1E", "q@ c #141420", "r@ c #FCFCFD", "s@ c #E3E2DE", "t@ c #DCDCFC", "u@ c #272739", "v@ c #373637", "w@ c #30306A", "x@ c #E5E5F1", "y@ c #BEBEEA", "z@ c #BCBCD9", "A@ c #090802", "B@ c #212121", "C@ c #090909", "D@ c #27272B", "E@ c #D2D1F0", "F@ c #2C2B53", "G@ c #363433", "H@ c #0D0D0D", "I@ c #D8D8E4", "J@ c #DCDCF8", "K@ c #C4C3E2", "L@ c #E8E8F7", "M@ c #EBEBF8", "N@ c #262613", "O@ c #171618", "P@ c #23221A", "Q@ c #E5E5DA", "R@ c #464548", "S@ c #84838B", "T@ c #DCDBEC", "U@ c #FAFAFD", "V@ c #9595B5", "W@ c #1D1D1E", "X@ c #ECECF1", "Y@ c #E8E8F8", "Z@ c #202034", "`@ c #242220", " # c #151515", ".# c #141404", "+# c #2E2E95", "@# c #6E6E6E", "## c #54535E", "$# c #EFEED9", "%# c #1F1E21", "&# c #2F2D2C", "*# c #252626", "=# c #E9E9F5", "-# c #F5F5FC", ";# c #868583", "># c #C5C4E4", ",# c #C6C7E9", "'# c #BDBDE9", ")# c #E3E3E2", "!# c #2D2D53", "~# c #050503", "{# c #1A1A1A", "]# c #2D2C18", "^# c #CFCFE6", "/# c #E8E8FA", "(# c #BEBEEE", "_# c #1F2066", ":# c #0F0F3E", "<# c #414199", "[# c #E2E2EC", "}# c #DBDBE9", "|# c #28287F", "1# c #686868", "2# c #080841", "3# c #E7E7FD", "4# c #7373AA", "5# c #10100B", "6# c #434343", "7# c #2E2E29", "8# c #201E58", "9# c #E4E4EE", "0# c #161510", "a# c #2B2B2F", "b# c #31312B", "c# c #14144F", "d# c #A2A2C4", "e# c #EBEBFC", "f# c #414096", "g# c #0F0F0A", "h# c #444342", "i# c #20201C", "j# c #12123D", "k# c #1E1E78", "l# c #010200", "m# c #7C7A7A", "n# c #131211", "o# c #24231E", "p# c #EEEEDD", "q# c #7D7D7B", "r# c #DDDCE5", "s# c #BFBFFB", "t# c #272860", "u# c #2F2E2E", "v# c #CECEE6", "w# c #606060", "x# c #DFDFEF", "y# c #222234", "z# c #282827", "A# c #040303", "B# c #323222", "C# c #F0EFE0", "D# c #E6E6E9", "E# c #FAFAFE", "F# c #3C3C9E", "G# c #020200", "H# c #4C4949", "I# c #CECEEA", "J# c #D9D9DE", "K# c #393948", "L# c #0E0E0C", "M# c #020203", "N# c #242414", "O# c #E8E8F3", "P# c #C5C5E5", "Q# c #292981", "R# c #2E2E2E", "S# c #171717", "T# c #20203D", "U# c #CDCDF0", "V# c #BEBDE5", "W# c #E3E2ED", "X# c #C8C8C0", "Y# c #C9C8E8", "Z# c #F5F5FB", "`# c #404090", " $ c #111102", ".$ c #222123", "+$ c #24233C", "@$ c #E1E1F9", "#$ c #F8F8FC", "$$ c #DCDBF6", "%$ c #383678", "&$ c #0E0E05", "*$ c #100F10", "=$ c #16141B", "-$ c #5A5A91", ";$ c #BFBFF2", ">$ c #DCDCF7", ",$ c #BDBDE0", "'$ c #1D1D30", ")$ c #030302", "!$ c #161515", "~$ c #323033", "{$ c #E6E6ED", "]$ c #C0C0DE", "^$ c #484847", "/$ c #050507", "($ c #2B2A2B", "_$ c #040404", ":$ c #1B1910", "<$ c #454449", "[$ c #242476", "}$ c #C7C7E8", "|$ c #9898C0", "1$ c #121171", "2$ c #2A2A66", "3$ c #484844", "4$ c #121007", "5$ c #737373", "6$ c #383638", "7$ c #0A0A0A", "8$ c #050504", "9$ c #14130E", "0$ c #070600", "a$ c #100E00", "b$ c #3C3A28", "c$ c #282746", "d$ c #272771", "e$ c #E8E8F1", "f$ c #21216D", "g$ c #3E3E4B", "h$ c #393929", "i$ c #0D0C00", "j$ c #0B0B04", "k$ c #0A0A02", "l$ c #0E0F0F", "m$ c #1F1F1F", "n$ c #646162", "o$ c #3C393A", "p$ c #292828", "q$ c #090804", "r$ c #0F0E07", "s$ c #070700", "t$ c #3E3D30", "u$ c #141454", "v$ c #35359A", "w$ c #E8E8FC", "x$ c #39398B", "y$ c #13142D", "z$ c #39392A", "A$ c #101007", "B$ c #17160F", "C$ c #060603", "D$ c #0E0E0E", "E$ c #313131", "F$ c #111111", "G$ c #363636", "H$ c #1D1C17", "I$ c #060600", "J$ c #1E1F2C", "K$ c #343462", "L$ c #222268", "M$ c #1A1A2C", "N$ c #030401", "O$ c #1F1E1B", "P$ c #242424", "Q$ c #2E2D2D", "R$ c #3E3E3E", "S$ c #1A1A19", "T$ c #040400", "U$ c #040414", "V$ c #0E0E2D", "W$ c #C0C0DA", "X$ c #24243D", "Y$ c #090918", "Z$ c #0C0C0A", "`$ c #282828", " % c #000003", ".% c #15141B", "+% c #A8A7C1", "@% c #232339", "#% c #030308", "$% c #6E6C6C", "%% c #383838", "&% c #6F6C6F", " ", " ", " . + @ # $ % & * ", " = - ; > , ' ) ! ~ { ] ^ / ( _ : < [ } | 1 ", " 2 3 4 5 6 7 8 9 0 a b c _ d e + f g h i j k + l m n o _ p q + r s i t u 0 ", " v + w i i i i x y + z A + + + + + B + C D E i i i i i i F G H 8 + + + + + z + I J K i i i i L M < ", " N + O i i i i i i i P Q R S T U V W X i i i i i i i i i i i i i Y Z ` ...+.@.i i i i i i i i i #.$.%.&. ", " *.+ =.i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i -.;.m + >. ", " ,.+ '.).i i i i i i i i i i Y !.~.{.~.~.].i i i ^./.~.{.~.~.(.i i i _.:.~.~.{.~.<.i i i i i i i i i i i [.}.+ |. ", " 1.8 2.i i i i i i i i i i i i 3.4.4.4.4.4.4.i i i 5.4.4.4.4.4.4.6.i i 7.4.4.4.4.4.4.i i i i i i i i i i i i 8.9.0.a. ", " b.c.d.i i i i i i i i i i i i i e.4.f.4.4.4.4.i i i g.4.4.4.4.4.4.h.i i i.4.4.4.j.4.4.i i i i i i i i i i i i i k.l.+ m. ", " n.+ o.i i i i i i i i i i i i i i p.4.q.i i i i i i i r.4.4.s.i i i i i i t.4.4.i i u.4.i i i i i i i i i i i i i i v.w.x. ", " y.p z.i i i i i i i i i i i i i i p.4.A.B.C.D.E.i i i r.4.F.G.H.D.I.J.i i K.4.4.i i L.4.i i i i i i i i i i i i i i M.# ", " 1 + N.i i i i i i i i i i i i i O.4.4.4.4.4.4.i i i P.4.4.4.4.4.4.6.i i Q.4.4.i i L.4.i i i i i i i i i i i i i R.S.T. ", " U.+ V.i i i i i i i i i i i i W.X.Y.Y.Y.4.4.i i i r.4.4.4.4.4.4.Z.i i K.4.4.i i L.4.i i i i i i i i i i i i i `. + ", " + .+++i i i i i i i i i i i i i i i i 4.4.i i i @+4.#+i $+%+&+i i i K.4.4.i i *+4.i i i i i i i i i i i i =++ -+ ", " ' + ;+i i i i i i i i i i i i >+i i i 4.4.i i i ,+4.4.i i i i i i i t.4.4.i i '+4.i i i i i i i i i i i )+!+~+ ", " {+]+i i i i i i i i i i i ^+4.4.4.4.4.4.i i i P.4.4.4.4.4./+(+i i t.4.4.4.4.4.4.i i i i i i i i i i i _+:+<+ ", " [+z }+i i i i i i i i i i |+4.4.4.4.4.4.i i i 1+4.4.4.4.4.4.2+i i 3+4.4.4.4.4.4.i i i i i i i i i i i 4++ ", " 5+] i i i i i i i i i i 6+7+8+8+8+9+0+a+i i b+c+8+8+8+8+d+i i i e+8+8+f+g+4.4.i i i i i i i i i i h+i+j+ ", " k+l+m+n+o+p+q+r+r+s+t+u+v+w+x+y+y+z+A+B+n+C+D+E+w+w+z+y+y+F+G+H+I+z+J+w+K+L+M+N+O+P+P+P+o+Q+r+r+R+S+T+U+ ", " A V+W+X+X+Y+X+Y+Y+X+Z+Z+X+`+ @Y+X+X+Y+Y+Y+X+Y+Y+.@Y+Y+Y+Y+X+X+X+Y+X+Y++@@@#@$@Y+%@&@Z+X+Z+Y+Y+*@0.+ ", " =@-@i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i ;@>@ ", " + ,@i i i i i i i i i i i '@i i i i i i i i i i i i i i i i i i i i '@i i i i i i i i i i i i i )@!@ ", " + ~@i i i i i i i i i i i {@]@i i i i i i i i i i i i i i i i i i i {@]@i i i i i i i i i i i i )@+ ", " + ^@i i i i i i i i i /@(@4.4._@i i i i i i i i i i i i i i i i /@(@4.4._@i i i i i i i i i i i :@+ ", " <@[@i i i i i i i i i i 4.4.4.4.}@i i i i i i i i i i i i i i i i 4.4.4.4.}@i i i i i i i i i i |@1@ ", " 2@3@i i i i i i i i i 4.4.4.4.4@5@i i i i i i i i i i i i i i i 4.4.4.4.4@5@i i i i i i i i i i 6@7@ ", " 8@9@0@i i i i i i a@i 4.4.4.4.4.b@i i i i i i i i i i i i i a@i 4.4.4.4.4.b@i i i i i i i i i i i c@d@ ", " e@f@i i i i i i i g@4.4.4.4.4.h@i i i i i i i i i i i i i i g@4.4.4.4.4.h@i i i i i i i i i i i i i@j@ ", " k@+ l@i i i i i i i m@4.4.4.4.4.i i i i i i i i i i i i i i i m@4.4.4.4.4.i i i i i i i i i i i i i n@o@-+ ", " p@q@i i i i i i i r@4.4.4.4.s@i i i i i i i i i i i i i i i r@4.4.4.4.s@i i i i i i i i i i i i i i t@u@+ ", " v@+ w@i i i i i i i x@4.4.4.4.y@i i i i i i i i i i i i i i i x@4.4.4.4.y@i i i i i i i i i i i i i i i z@A@B@ ", " C@D@i i i i i i i i 4.4.4.4.E@i i i i i i i i i i i i i i i i 4.4.4.4.E@i i i i i i i i i i i i i i i i i F@+ G@ ", " H@+ I@i i i i i i J@K@4.4.4.4.i L@i i M@i i i i i i i i i i J@K@4.4.4.4.i L@i i M@i i i i i i i i i i i i i i N@O@ ", " d P@i i i i i i i i Q@4.4.4.4.R@S@4.4.4.4.T@U@i i i i i i i i Q@4.4.4.4.R@S@4.4.4.4.T@U@i i i i i i i i i i i V@+ W@ ", " 0 + X@i i i i i i i i 4.4.4.4.4.4.4.4.4.4.4.4.4.4.i i i i i i i 4.4.4.4.4.4.4.4.4.4.4.4.4.4.i i i i i i i i i i Y@Z@`@ ", " #.#i i i i i i i i i 4.4.4.4.4.4.4.4.4.4.4.4.4.4.i i i i i i i 4.4.4.4.4.4.4.4.4.4.4.4.4.4.i i i i i i i i i i i +#+ @# ", " + ##i i i i i i i i $#4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.i i i i i $#4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.i i i i i i i i i i i %#&# ", " *#+ =#i i i i i i i -#4.4.4.4.4.4.;#>#,#'#i 4.4.4.4.4.)#i i i -#4.4.4.4.4.4.;#>#,#'#i 4.4.4.4.4.)#i i i i i i i i i i !#~# ", " {#]#i i i i i i i i ^#4.4.4.4.4.4.i /#i i i i i 4.4.4.4.(#i i ^#4.4.4.4.4.4.i /#i i i i i 4.4.4.4.(#i i i i i i i i i _#+ ", " + :#i i i i i i i i <#4.4.4.4.4.i i i i i i i i i 4.4.4.[#i i <#4.4.4.4.4.i i i i i i i i i 4.4.4.[#i }#i i i i i i i |#9@1#", " + 2#i i i i i i i i i 4.4.4.4.i i i i i i i i i i 4.4.4.4.i 3#i 4.4.4.4.i i i i i i i i i i 4.4.4.4.i i i i i i i i i 4#5#6#", " 7#8#i i i i i i i i i 4.4.4.4.i i i i i i i i i i 4.4.4.4.i i i 4.4.4.4.i i i i i i i i i i 4.4.4.4.i i i i i i i i i 9#0#a#", " b#c#i i i i i i i i d#4.4.4.4.U@i i i i i i i i e#4.4.4.4.i i d#4.4.4.4.U@i i i i i i i i e#4.4.4.4.i i i i i i i i i f#g#h#", " i#j#i i i i i i i i U@4.4.4.4.i i i i i i i i i i 4.4.4.4.i i U@4.4.4.4.i i i i i i i i i i 4.4.4.4.i i i i i i i i i k#l#m#", " n#o#i i i i i i i i i p#4.4.4.q#i i i i i i i i r#4.4.4.4.i i i p#4.4.4.q#i i i i i i i i r#4.4.4.4.i i i i i i i i s#t#+ ", " u#+ v#i i i i i i i i i 4.4.4.4.w#i i i i x#4.4.4.4.4.4.i i i i i 4.4.4.4.w#i i i i x#4.4.4.4.4.4.i i i i i i i i i i y#z# ", " A#B#i i i i i i i i i C#4.4.4.4.4.4.4.4.4.4.4.4.4.4.D#E#i i i i C#4.4.4.4.4.4.4.4.4.4.4.4.4.4.D#E#i i i i i i i i F#G#H# ", " 0 + I#i i i i i i i i i 4.4.4.4.4.4.4.4.4.4.4.4.4.J#/@i i i i i i 4.4.4.4.4.4.4.4.4.4.4.4.4.J#/@i i i i i i i i i K#L# ", " M#N#i i i i i i i i i i O#4.4.4.4.4.4.4.4.4.4.P#/@i i i i i i i i O#4.4.4.4.4.4.4.4.4.4.P#/@i i i i i i i i i Q#+ R# ", " S#+ T#U#i i i i i i i i i V#W#4.4.4.4.4.X#Y#Z#i i i i i i i i i i i V#W#4.4.4.4.4.X#Y#Z#i i i i i i i i i i `# $.$ ", " v + +$i i i i i i i i i i i @$i i #$i i $$i i i i i i i i i i i i i i @$i i #$i i $$i i i i i i i i i i %$&$*$ ", " a.+ =$-$i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i ;$;$;$;$>$i i i i i ,$'$)$U. ", " !$+ ~${$i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i ]$^$/$+ >@ ", " ($_$+ :$<$[$i }$i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i i |$1$2$3$4$+ + {#5$ ", " 6$7$8$9$0$+ + a$b$c$d$i i i i i i i i i i i i i i i i i i i i e$f$g$h$i$+ j$k$+ + l$m$ ", " n$o$p$+ _ n.q$r$s$t$u$v$i w$i i i i i i i i i i i x$y$z$A$B$C$D$E$F$<+ ", " G$+ H$I$J$K$i w$i i i i i i i L$M$N$O$P$Q$ ", " R$S$T$U$V$W$i i i i X$Y$+ Z$ ", " `$+ %.%+%@%#%+ {#$% ", " %%+ + + d &% ", " ", " "}; ================================================ FILE: distros/README ================================================ Seq66 Forks and Distro Packaging Chris Ahlstrom 2023-07-19 to 2023-07-19 This directory contains information about projects related to Seq66. GitHub Forks: henning Distro Packaging: Debian. Created by us for this project. Fedora/Audinux. NixOS. # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: distros/arch/README ================================================ Seq66 Arch Linux Package Build Chris Ahlstrom 2020-07-20 to 2021-05-01 The directory "arch/package" holds a copy of the package build files for Arch Linux. TO BE DETERMINED: These files may be updated from the original build files via: git subtree pull --prefix arch/package https://aur.archlinux.org/seq66-git.git master --squash This README also provides some handy links to the Arch build of Seq66. Package URL: https://aur.archlinux.org/packages/seq66-git/ Arch Package Guidelines: https://wiki.archlinux.org/index.php/VCS_package_guidelines GitHub issues: TBD Discussions: https://www.linuxmusicians.com/viewtopic.php?f=4&t=15133&p=67807#p67807 Working with git subtree: https://www.atlassian.com/git/articles/alternatives-to-git-submodule-git-subtree Dependencies: libasound.so (alsa-lib-x205ta, alsa-lib-git, alsa-lib-minimal-git, lib32-alsa-lib-minimal-git, lib32-alsa-lib-git, alsa-lib-a52pcm, alsa-lib, lib32-alsa-lib) libjack.so (pipewire-jack-dropin, jack2-git, pipewire-jack-git, jack, jack2, lib32-jack, lib32-jack2) liblo.so (liblo-ipv6, liblo) qt5-base (qt5-base-git, qt5-base-fractional-fix, qt5-base-headless) alsa-lib (alsa-lib-x205ta, alsa-lib-git, alsa-lib-minimal-git, alsa-lib-a52pcm) (make) git (git-git) (make) jack (jack-git, jack-stub, jack-dbus, jack2-git, pipewire-jack-git, jack2) (make) liblo (liblo-git, liblo-ipv6) (make) # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: distros/arch/package/PKGBUILD ================================================ # Maintainer: Christopher Arndt _pkgname=seq66 pkgname="${_pkgname}-git" pkgver=0.99.0.r0.g3102ace6 pkgrel=1 pkgdesc="A live-looping sequencer with a Qt graphical interface (git version)" arch=('i686' 'x86_64') url="https://github.com/ahlstromcj/seq66" license=('GPL2') depends=('qt5-base') makedepends=('git' 'alsa-lib' 'jack' 'liblo') groups=('pro-audio') provides=("${_pkgname}" "${_pkgname}=${pkgver//.r*/}") conflicts=("${_pkgname}") source=("${_pkgname}::git+https://github.com/ahlstromcj/${_pkgname}.git") md5sums=('SKIP') pkgver() { cd "${srcdir}/${_pkgname}" local ver=$(tail -n 1 VERSION) ( set -o pipefail git describe --long --tags 2>/dev/null | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || echo "$ver.r$(git rev-list --count HEAD).$(git rev-parse --short HEAD)" ) } build() { cd "${srcdir}/${_pkgname}" ./bootstrap ./configure --prefix=/usr --enable-rtmidi make -j $[$(nproc) - 1] } package() { depends+=('libasound.so' 'libjack.so' 'liblo.so') cd "${srcdir}/${_pkgname}" make DESTDIR="${pkgdir}" install } ================================================ FILE: distros/arch/package/PKGBUILD-alt ================================================ # Adapted from a script of Christopher Arndt # Also see his version: Daniel Appelt _pkgname=seq66 pkgname="${_pkgname}-git" pkgver=0.90.6.r230.2a73c11 pkgrel=1 pkgdesc="A live-looping sequencer with an Qt graphical interface (git version)" arch=('i686''x86_64') url="https://github.com/ahlstromcj/seq66" license=('GPL2') depends=('alsa-lib''jack' 'qt5-base') makedepends=('git') groups=('pro-audio') provides=("${_pkgname}""${_pkgname}=${pkgver//.r*/}") conflicts=("${_pkgname}") source=("${_pkgname}::git+https://github.com/ahlstromcj/${_pkgname}.git") md5sums=('SKIP') sha256sums=('SKIP') pkgver() { cd "${srcdir}/${_pkgname}" echo "$(git describe).r$(git rev-list --count HEAD).$(git rev-parse --short HEAD)" } build() { cd "${srcdir}/${_pkgname}" ./bootstrap --full-clean ./bootstrap ./configure --prefix=/usr --enable-rtmidi make } package() { cd "${srcdir}/${_pkgname}" make DESTDIR="${pkgdir}/" install } # vim: ft=sh ================================================ FILE: distros/fedora/README ================================================ Seq66 Fedora/Auinux Linux Package Build Chris Ahlstrom 2023-07-19 to 2023-07-19 A copy of the spec file from github/audinux/fedora-spec. # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: distros/fedora/seq66.spec ================================================ Name: seq66 Version: 0.99.6 Release: 1%{?dist} Summary: MIDI sequencer License: GPL URL: https://github.com/ahlstromcj/seq66 Vendor: Audinux Distribution: Audinux Source0: https://github.com/ahlstromcj/seq66/archive/refs/tags/%{version}.tar.gz#/%{name}-%{version}.tar.gz BuildRequires: gcc gcc-c++ make BuildRequires: autoconf BuildRequires: automake BuildRequires: libtool BuildRequires: qt5-linguist BuildRequires: qtchooser BuildRequires: qt5-qttools BuildRequires: git BuildRequires: alsa-lib-devel BuildRequires: qt5-qtbase-devel BuildRequires: qt5-qtdeclarative-devel BuildRequires: liblo-devel BuildRequires: libglvnd-devel BuildRequires: rtmidi-devel BuildRequires: portmidi-devel BuildRequires: jack-audio-connection-kit-devel BuildRequires: desktop-file-utils %description Seq66 is a MIDI sequencer and live-looper with a hardware-sampler-like grid-pattern interface, sets and playlists for song management, a scale and chord-aware piano-roll interface, song editor for creative composition, and control via MIDI automation for live performance. Mute-groups enable/disable multiple patterns with one keystroke or MIDI control. Supports NSM (Non Session Manager) on Linux; can also run headless. It does not support audio samples, just MIDI. Seq66 is a major refactoring of Sequencer64/Kepler34, rebooting Seq24 with modern C++ and new features. Linux users can build this application from the source code. See the INSTALL file; it has notes on many types on installation. Windows users can get an installer package on GitHub or build it with Qt Creator. Provides a comprehensive PDF user-manual. %prep %autosetup -n %{name}-%{version} mkdir -p .local/bin ln -s /usr/bin/qmake-qt5 .local/bin/qmake ln -s /usr/bin/moc-qt5 .local/bin/moc ln -s /usr/bin/uic-qt5 .local/bin/uic ln -s /usr/bin/rcc-qt5 .local/bin/rcc ln -s /usr/bin/lupdate-qt5 .local/bin/lupdate ln -s /usr/bin/lrelease-qt5 .local/bin/lrelease %build %set_build_flags %if 0%{?fedora} >= 38 export CXXFLAGS="-std=c++11 -include cstdint $CXXFLAGS" %endif export PATH=.local/bin:$PATH ./bootstrap %configure --enable-cli %make_build %install %make_install install -m 755 -d %{buildroot}/%{_datadir}/icons/%{name}/ install -m 644 -p desktop/seq66.xpm %{buildroot}/%{_datadir}/icons/%{name}/ install -m 755 -d %{buildroot}/%{_datadir}/applications/ install -m 644 -p data/share/applications/seq66.desktop %{buildroot}%{_datadir}/applications/%{name}.desktop desktop-file-install \ --add-category="Audio;AudioVideo" \ --delete-original \ --dir=%{buildroot}%{_datadir}/applications \ %{buildroot}/%{_datadir}/applications/%{name}.desktop %check desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop %files %doc ChangeLog INSTALL NEWS README.md RELNOTES ROADMAP.md TODO %{_bindir}/* %{_datadir}/* %{_includedir}/* %{_libdir}/* %changelog * Sun Jul 02 2023 Yann Collette - 0.99.6-1 - update 0.99.6-1 * Sat May 20 2023 Yann Collette - 0.99.5-1 - update 0.99.5-1 * Sun Apr 30 2023 Yann Collette - 0.99.4-1 - update 0.99.4-1 * Thu Apr 20 2023 Yann Collette - 0.99.3-1 - update 0.99.3-1 * Mon Mar 20 2023 Yann Collette - 0.99.2-1 - update 0.99.2-1 * Mon Jul 11 2022 Yann Collette - 0.98.9.1-1 - initial version ================================================ FILE: distros/nixos/README ================================================ Seq66 NixOS Linux Package Build Chris Ahlstrom 2023-07-19 to 2023-07-19 A copy of the spec file from 0.99.6 https://github.com/audinux/fedora-spec/seq66/seq66.spec Others: 0.90.5 github/league/nixpkgs/pkgs/applications/audio/seq66/default.nix # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: distros/nixos/default.nix ================================================ { lib, stdenv, fetchFromGitHub, autoreconfHook, pkg-config, qttools, which , alsaLib, libjack2, liblo, qtbase }: stdenv.mkDerivation rec { pname = "seq66"; version = "0.90.5"; src = fetchFromGitHub { owner = "ahlstromcj"; repo = pname; rev = version; sha256 = "1jvra1wzlycfpvffnqidk264zw6fyl4fsghkw5256ldk22aalmq9"; }; nativeBuildInputs = [ autoreconfHook pkg-config qttools which ]; buildInputs = [ alsaLib libjack2 liblo qtbase ]; postPatch = '' for d in libseq66/include libseq66/src libsessions/include libsessions/src seq_qt5/src seq_rtmidi/include seq_rtmidi/src Seqtool/src; do substituteInPlace "$d/Makefile.am" --replace '$(git_info)' '${version}' done ''; enableParallelBuilding = true; dontWrapQtApps = true; meta = with lib; { homepage = "https://github.com/ahlstromcj/seq66"; description = "Loop based midi sequencer with Qt GUI derived from seq24 and sequencer64"; license = licenses.gpl2Plus; maintainers = with maintainers; [ orivej ]; platforms = platforms.linux; }; } ================================================ FILE: doc/Makefile.am ================================================ #***************************************************************************** # Makefile.am (seq66) #----------------------------------------------------------------------------- ## # \file Makefile.am # \library seq66 # \author Chris Ahlstrom # \date 2020-10-02 # \updates 2022-01-24 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This Makefile manages the "doc" directory. It supports a number of # sub-projects # # ca 2022-01-24 Fixes found while working on issue #45. # #----------------------------------------------------------------------------- #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = make_dox *.cfg dox/*.dox *.pdf *.ods #***************************************************************************** # SUBDIRS #----------------------------------------------------------------------------- # # We now make the Doxygen documentation only manually: dox removed. # Actually we also make the LaTeX/PDF documentation manually as well (to # avoid checking in the binary/pdf file all the time. # #----------------------------------------------------------------------------- SUBDIRS = latex #***************************************************************************** # DIST_SUBDIRS #----------------------------------------------------------------------------- DIST_SUBDIRS = $(SUBDIRS) #****************************************************************************** # Install directories #------------------------------------------------------------------------------ prefix = @prefix@ datadir = @datadir@ docdir = @docdir@ top_srcdir = @top_srcdir@ datarootdir = @datarootdir@ seq66docdir = @seq66docdir@ #****************************************************************************** # Installing documentation #------------------------------------------------------------------------------ # # We need to add an install-data-hook to copy the generated # documentation directories to the destination directory. The normal # method doesn't work because /usr/bin/install will only install files, # and automake doesn't give it the switch needed to install directories. # # Also, since we don't always build the documentation, we moved these # commands to data/Makefile.am. # #------------------------------------------------------------------------------ # # install-data-local: # @echo "Copying Seq66 documentation to $(seq66docdir)..." # mkdir -p $(seq66docdir) # cp -r -p $(top_srcdir)/doc/*.pdf $(seq66docdir) # cp -r -p $(top_srcdir)/doc/*.ods $(seq66docdir) # #------------------------------------------------------------------------------ #******************************************************************************* # uninstall-hook #------------------------------------------------------------------------------- # # uninstall-hook: # rm -rf $(seq66docdir) # #------------------------------------------------------------------------------- #**************************************************************************** # Makefile.am (seq66 top-level) #---------------------------------------------------------------------------- # vim: ts=3 sw=3 ft=automake #---------------------------------------------------------------------------- ================================================ FILE: doc/README ================================================ Documentation Directory for Seq66 0.xx.0 Chris Ahlstrom 2021-04-17 to 2023-07-19 This directory used to contain documents to be installed in the ${prefix}/share/doc/seq66-0.xx directory (e.g. /usr/share/doc or /usr/local/share/doc) on Linux, or in the installation ${install}/data directory (e.g. C:/Program Files/Seq66/data) on Windows. These documents have been moved to the "data/share/doc" directory for uniformity. The documentation consists of a large PDF Seq66 User Manual and an Open Document Format spreadsheet describing the configuration for the Novation LaunchPad Mini supplied with Seq6. An HTML mini-tutorial is also available. # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: doc/dia/libseq66-headers.dia ================================================ #Letter# #libseq66-headers.dia# #libseq66-headers.dia# #util/basic_macros.h# #seq66_platform_macros.h# #seq66-config.h# #seq66-features# #seq66-features.h# #util/basic_macros# #midi/midibytes# #util/strfunctions# #ctrl/automation# #ctrl/opcontrol# #MB# #MB# #ctrl/keymap# #ctrl/keycontrol# #ctrl/keycontainer# #ctrl# #util# #midi# #ctrl/keystroke# #BM# #ctrl/midimacro# #ctrl/midicontrolbase# #cfg# #ctrl/midicontrol# #BM# #ctrl/midicontrolin# #ctrl/midicontrolout# #midi/mastermidibus (rm, pm)# #ctrl/midimacros# #ctrl/midioperation# #ctrl/opcontainer# #midi/businfo# #midi/midibus (rm, pm)# #midi/midibus_common# #midi/controllers# #midi/calculations# #EV# #midi/editable_event# #midi/editable_events# #midi/eventlist# #midi/event# #EV# #midi/jack_assistant# #C# #Str# #Calc# #util/named_bools# #Rc# #MB# #midi/mastermidibase# #midi/midibase# #util/automutex# #util/recmutex# #util/condition# #util/filefunctions# #F# #MBC# #midi/midi_splitter# #libseq66-headers.dia# #libseq66-headers.dia# #cfg/configfile# #cfg/comments# #cfg/basesettings# #cfg/scales# #cfg/usrsettings# #cfg/cmdlineopts# #Cmd# #Str# #BM# #cfg/rcsettings# #Rc# #Usr# #cfg/usermidibus# #MB# #cfg/userinstrument# #MMbase# #MMB# #util/palette# #pthread.h# #util/rect# #play/clockslist# #play/inputslist# #play/portslist# #MBC# #MB# #MMbase# #MMbus# #MMbus# #BM# #Str# #Amutex# #Calc# #play/clockslist# #play/inputslist# #play/portslist# #MBC# #MB# #MB# #Amutex# #midi/midibus (rm, pm)# #Seq# #midi/midifile# #Amutex# #midi/midi_vector_base# #midi/midi_vector# #play/seq# #midi/songsummary# #Move to play directory# #midi/wrkfile# #sessions/smanager# #sessions/clinsmanager# #nsm/nsmclient# #play/performer# #nsm/nsmbase# #BM# #nsm/nsmmessagesex# #Seq# #nsm/nsmserver# #P# #P# #play/mutegroup# #play/mutegroups# #play/notemapper# #play/playlist# #play/screenset# #play/seq# #play/sequence# #play/setmapper# #play/setmaster# #play/triggers# #BSet# #BSet# #cfg/recent# #Rc# #KC# #KC# #MCO# #MCI# #MCI# #MCO# #NB# #NB# #os/daemonize# #os/timing# #Rc# #Cond# #Jass# #Jass# #Cond# #MMbus# #Pal# #Amutex# #Calc# #Usr# #cfg/settings# #SPM# #SPM# #play# #nsm# #sessions# #C# #SF# #SF# #cfg/usrfile# #cfg/notemapfile# #cfg/playlistfile# #cfg/sessionfile# #cfg# #midi# #util# #cfg/midicontrolfile# #cfg/mutegroupsfile# #cfg/rcfile# #BM# #KC# #Cfg# #Cfg# #Op# #MCI# #MCO# #Op# #Rc# #Usr# #MG# #MG# #NM# #NM# #PL# #PL# #for comments!# #EV# #Str# #MG# #Str# #BM# #seq66# #os# #libseq66-headers.dia# #seq_rtmidi/midi_api# #libseq66-headers.dia# #seq_rtmidi/mastermidibus_rm# #seq_rtmidi/midi_alsa# #seq_rtmidi/midi_alsa_info# #seq_rtmidi/midibus_rm# #seq_rtmidi/midi_info# #seq_rtmidi/midi_jack# #seq_rtmidi/midi_jack_info# #seq_rtmidi/midi_jack_data# #seq_rtmidi/midi_probe# #seq_rtmidi/rterror# #seq_rtmidi/rtmidi# #seq_rtmidi/rtmidi_info# #seq_rtmidi/rtmidi_types# #seq_rtmidi_features.h# #seq66-config.h# #midi/midibus (rm, pm)# #midi/mastermidibase# #midi/midibase# #lmidi/midibytes# #seq66-features# #llutil/basic_macros# #lmidi/event# ================================================ FILE: doc/dia/rtbusses.dia ================================================ #Letter# #mastermidibus# #rtmidi# ## #m_midi_master# #rtmidi_info# ## ## #api_port_start# ## #void# # # #mbus# #mastermidibus &# ## ## #bus# #int# ## ## #port# #int# ## ## #api_init# ## ## ## #mastermidibase# ## ## #m_inbus_array# #busarray# ## ## #m_outbus_array# #busarray# ## ## #m_bus_announce# #midibus *# ## ## #m_master_clocks# #vector<clock_e># ## ## #m_master_inputs# #vector<bool># ## ## #m_vector_sequence# #vector<sequence *># ## ## #m_seq# #sequence *# ## #Why???# #get_sequence# ## #sequence *# ## #start# ## #void# ## #stop# ## #void# ## #port_start# ## #void# ## #client# #int# ## ## #port# #int# ## ## #port_exit# ## #void# ## #client# #int# ## ## #port# #int# ## ## #continue_from# ## #void# ## #tick# #midipulse# ## ## #api_start# ## #void# ## #api_continue_from# ## #void# ## #tick# #midipulse# ## ## #api_stop# ## #void# ## #api_port_start# ## #void# ## #client# #int# ## ## #port# #int# ## ## ## ## #rtmidi_info# #midi master# ## #m_info_api# #midi_info# ## ## #api_port_start# ## #void# ## #mbus# #mastermidibus &# ## ## #bus# #int# ## ## #port# #int# ## ## #add_bus# ## ## ## #bus# #midibus# ## ## #midi_info# ## ## #m_input# #midi_port_info# ## ## #m_output# #midi_port_info# ## ## #m_bus_container# #vector<midibus *># ## ## #m_midi_handle# #void *# ## ## #api_port_start# ## #void# ## #mbus# #mastermidibus &# ## ## #bus# #int# ## ## #port# #int# ## ## #get_all_port_info# ## ## ## #add_bus# ## ## ## #m# #midibus *# ## ## #midi_port_info# ## ## #midibase# ## ## #midi_api# ## ## #m_master_info# #midi_info# ## ## #m_parent_bus# #midibus &# ## ## #m_input_data# #rtmidi_in_data# ## ## #api_start# ## #void# ## #api_stop# ## #void# ## #api_continue_from# ## #void# ## #tick# ## ## ## #beats# ## ## ## #parent_bus()# ## ## ## ## #rtmidi_in_data# ## ## ## ## ## ## ## #midibus# ## ## #m_rt_midi# #rtmidi# ## ## #m_master_info# #rtmidi_info# ## ## #api_start# ## #void# ## #api_stop# ## #void# ## #api_continue_from# ## #void# ## #tick# ## ## ## #beats# ## ## ## ## ## #rtmidi# ## ## #m_midi_info# #rtmidi_info# ## ## #m_midi_api# #midi_api# ## #Inherits and aggregates a midi_api!# ## ## #midi_jack# ## ## #api_start# ## #void# ## #api_stop# ## #void# ## #api_continue_from# ## #void# ## #port_handle# ## #jack_port_t# ## #midi_jack_info# ## ## #m_jack_ports# #vector<midi_jack *># ## ## #m_jack_client# #jack_client_t *# ## ## #api_port_start# ## #void# ## #jack_ports# ## #portlist# ## ## ## #midi_alsa# ## ## #api_start# ## #void# ## #api_stop# ## #void# ## #api_continue_from# ## #void# ## ## ## #midi_alsa_info# ## ## #api_port_start# ## #void# ## ## ## #performer# ## ## #m_master_bus# #mastermidibus *# ## ## #m_master_clocks# #vector<clock_e># ## ## #m_master_inputs# #vector<bool># ## ## #launch# ## #bool# ## #Calls rtmidi_info::api_port_start(), which has a mastermidibus reference parameter. Call it a "weak" aggregation.# #port_start() calls api_port_start()# #rtmidi_info::openmidi_api() creates a new midi_xxxx_info. Could pass the mastermidibus to this function.# #port_info# ## ## ## ## ## ## #1..*# #master_info()# ## ## ## ## ## ## ## ## ## #via new() (pointers)# ## ## #midi_jack_data# ## ## #m_jack_client# ## ## ## #m_jack_port# ## ## ## #m_jack_rtmidiin# ## ## ## ## ## ## ## ## ## #jack_data()# ## ## ## ## ## ## ## ## ## #rtmidi_info calls the selected APIs get_all_port_info() function# ## ## ## ## ## ## ## ## ## #1..*# ## ## ## ## ## #busarray# ## ## #I/O# ## ## ## #2# #Inheritance and aggregation# ## ## ## ## ## #rtmidi_in# ## ## #rtmidi_out# ## ## ## ## ## ## ## ## ## ## ## ## #businfo# ## ## ## ## ## ## #1..*# ## ## ## ## ## #I/O# ## ## ## #2# #midi_queue# ## ## #add# ## #bool# ## #midi_message# ## ## ## #rtmidi_api# #enum class# ## #unspecified# ## ## ## #alsa# ## ## ## #jack# ## ## ## #sm_selected_api# ## ## ## ## #Basic steps: 1. Get all port info. 2. For each port, create a midibus. 3. Add it to the appropriate busarray. 4. Add it to the rtmidi_info "master". a. Get the correct API info (JACK). b. Add the midibus pointer to the info's bus container. 5. Set PPQN via mastermidibus. a. Access rtmidi_info. b. Get the specific "info" object. c. Set PPQN. In JACK, this just sets a member value. 6. Activate the master bus's in and out busarrays.# #The "info" classes gather data on the ports that exist in the system, and provide helper functions and API- specific MIDI-related calls.# #The "api" classes provide port-specific API functions. The JACK version also provides callback functions (some which might be better in the "info" class).# ## ## #This "data" class provides the client, port handle, ringbuffer, and rtmidi_in_data (see top).# #midi_message# ## ## #m_bytes# #vector<midibyte># ## ## ## ## #queue()# ## ## ## ## #rtbusses.dia# #These items are defined in the rtmidi_types module. The "queue" is a home-grown ring buffer of MIDI messages.# ## ## ## ## ## #The rtmidi version of midibus also has a reference to an rtmidi_info object to handle initialization, polling, output, etc. That item is a "master" to support MIDI functionality.# #To do?# #rtbusses.dia# #midi_master()# #portlist# ## #vector<midi_jack *># ## ## ## ## #1..*# #vector<midi_jack *># #How to keep in sync?# #jack_port_register_callback()# ## ## ## ## ## ## #?# #midi_in_jack# ## ## #midi_out_jack# ## ## ## ## ## ## #jack_process_io() iterates through the portlist to process MIDI I/O through the other JACK process callbacks.# #These items are created via the midibus api_init_[in/out]() function via the rtmidi_[in/out] openmidi_api() function, when the busarray is initialized(), in mastermidibus::activate(). These are stored in the portlist vector.# #These items use the global/parent rtmidi_info openmidi_api() function to create new midi_[in/out]_jack objects to handle I/O.# #vector<midibus *># #This "master" object accesses the actual JACK/ALSA API functions via the get_api_info() function. Is there any way to streamline this process?# #When registering seq66 port, we need to get the (new) internal port numbers from midi_port_info into: 1. The portlist of midi_jack objects. 2. The busarray, where each midibus has indirect access to midi_jacks via the rtmidi_info master.# #Consider adding a mastermidibus reference member to rtmidi_info (as shown above) to tell it to update its list of ports/midibusses.# #Creates midibusses to match the device/software MIDI ports that are present. It can also create virtual (manual) busses.# #The internal list of system MIDI ports. Doesn't have registered internal port IDs. Currently obtained only at startup.# #The rtmidi pointer is used to implement/access the midibus's calls to the api_xxx() functions. Consider that pointer a port proxy. The "master" (rtmidi_info) is passed to the rtmidi_[in/out] constructors in the api_init_xxx() functions called when the port/bus is created.# #jack_process_rtmidi_input()# ## #jack_process_rtmidi_input()# #jack_process_rtmidi_input()# ## ## ## ## ## #add()# #jack_shutdown_callback()# #jack_ports()# #jack_process_io()# #::jack_get_ports() [names]# #A midi_jack pointer is added in the midi_jack constructor.# #lookup_by_name() # #Add an internal ID code?# #vector<port_info># ================================================ FILE: doc/dia/rtjack_init.dia ================================================ #Letter# ## ## ## #Jack Midi Subsysem# ## ## ## #rtmidi_info# ## ## ## #midi_jack_info# ## ## ## #midi_port_info# ## ## ## #mastermidibus# ## ## ## ## ## ## #midi_jack# ## ## ## #performer# #rtmidi_info()# #launch()# #ctor# #openmidi_api()# #midi_info::get_all_port_info()# #jack_get_ports(in)# #add()# ## ## ## #port_info# ## #jack_get_ports(out)# #add()# ## ## ## #port_info# ## ## ## ## #midicontrolout# #init(), api_init()# #make_normal_bus()# ## ## ## #midibus# ## #Uses rtmidi_info, index, io & port type parameters.# ## ## ## ## ## #busarray# #add(midibus *)# ## ## ## #businfo# ## #push_back()# #add_bus()# #add_bus(midibus *)# #push_back()# #The "midi master"# #Input Ports# #Output Ports# #I/O Ports# #activate()# ## ## ## #rtmidi# #initialize()# #inititalize()# #api_init_in/out() via initialize()# ## #openmidi_api()# ## #Holds a reference to the "parent" midibus# #add(midi_jack &)# #api_connect()# #rtjack_init.dia# #rtjack_init.dia# #api_connect()# #jack_activate()# #api_connect()# #api_connect()# ## #I/O busses# #Uses "midi master"# ## #api_init_in/out()# #api_init_in/out()# #jack_port_register()# #api_connect()# #jack_connect()# #loops through all ports# #copy_io_busses()# ## #get_port_statuses()# #This call copies the clocks and inputs into the performer's copies.# # # ## ## #launch() also deals with true-buss values, announcing, and launching the I/O threads,and sends any macros.# #This call loops through the I/O busses, obtaining their status and names, and adding them to the master clocks and inputs lists.# #Contains portlist, a vector of midi_jack pointers. Also contains a vector of port_infos, holding basic info about a port.# #Partly redundant# ## ## ## #midi_port_info# ## ## ## #midi_info# ## ## #input_ports()# #output_ports()# #input# #output# #The port_infos are used in getters such as get_port_count().# ## ## ## #JACK# ## ## ## #Rt MIDI# ## ## ## #Common MiDI# ## ## ## #API MIDI# #LEGEND# #vector<businfo># #vector<midibus *># #portlist = vector<midi_jack *># ================================================ FILE: doc/dox/Makefile.am ================================================ #******************************************************************************* # Makefile.am #------------------------------------------------------------------------------- ## # \file Makefile.am # \library doc/dox # \author Chris Ahlstrom # \date 2015-09-10 # \update 2022-01-24 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Makefile for the doc/dox subdirectory of this package. We had to do a # little two-stepping to make sure that the documentation is built # everytime we touch one of the doxygen files, and NOT when we touch one of # the source-code files. Tired of checking in the PDF file all the time! # # Also, we leave it to the user to decide if the programmer's documentation # should be built. The PDFs are not checked into git. # # The dox is no longer present in the SUBDIRS of doc/Makefile.am. # #------------------------------------------------------------------------------- #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in dox-warnings.log CLEANFILES = dox-stamp \ seq_rtmidi/seq66_lib_seq_rtmidi.pdf \ libsessions/seq66_lib_libsessions.pdf \ seq_portmidi/seq66_lib_seq_portmidi.pdf \ libseq66/seq66_lib_libseq66.pdf #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = make_dox make-helper optimize *.cfg *.dox #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ srcdir = @srcdir@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ prefix = @prefix@ datadir = @datadir@ docdir = @docdir@ datarootdir = @datarootdir@ seq66docdir = @seq66docdir@ #------------------------------------------------------------------------------ # The $(srcdir) component points us to the correct place whether the build is # in-source or out-of-source. Can't use pushd/popd because they are bash # builtins and are not available, except inside a script. #------------------------------------------------------------------------------ all-local: $(srcdir)/make_dox $(srcdir) libseq66 $(srcdir)/make_dox $(srcdir) libsessions $(srcdir)/make_dox $(srcdir) seq_portmidi $(srcdir)/make_dox $(srcdir) seq_rtmidi clean-local: $(srcdir)/make_dox $(srcdir) libseq66 clean $(srcdir)/make_dox $(srcdir) libsessions clean $(srcdir)/make_dox $(srcdir) seq_portmidi clean $(srcdir)/make_dox $(srcdir) seq_rtmidi clean #****************************************************************************** # Installing documentation #------------------------------------------------------------------------------ # # We need to add an install-data-hook to copy the generated # documentation directories to the destination directory. The normal # method doesn't work because /usr/bin/install will only install files, # and automake doesn't give it the switch needed to install directories. # # Don't install the html files: # # cp -r -p $(top_builddir)/doc/dox/html $(xpcdoxygendir) # #------------------------------------------------------------------------------ install-data-local: @echo "Copying Seq66 documentation to $(DESTDIR)$(seq66docdir)..." mkdir -p $(DESTDIR)$(seq66docdir) cp -r -p $(top_builddir)/doc/dox/*.pdf $(DESTDIR)$(seq66docdir) #******************************************************************************* # uninstall-hook #------------------------------------------------------------------------------- # # Quick and dirty method used. # #------------------------------------------------------------------------------- uninstall-hook: rm -rf $(DESTDIR)$(seq66docdir) #****************************************************************************** # Makefile.am (doc/dox) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 noet ft=automake #------------------------------------------------------------------------------ ================================================ FILE: doc/dox/doxy-common.cfg ================================================ #***************************************************************************** # seq66/doc/dox/doxy-common.cfg #----------------------------------------------------------------------------- ## # \file doxy-common.cfg # \library seq66 # \author Chris Ahlstrom) # \date 2015-09-10 # \update 2023-12-11 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This "Doxyfile" provides the configuration needed to build the XPC # library suite documentation. It describes the settings to be used by # the documentation system Doxygen (www.doxygen.org) for a project. # # \references # - http://www.stack.nl/~dimitri/doxygen/config.html # #----------------------------------------------------------------------------- #--------------------------------------------------------------------------- # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. #--------------------------------------------------------------------------- PROJECT_NUMBER = 0.99.12 #--------------------------------------------------------------------------- # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. If a relative # path is entered, it will be relative to the location where doxygen was # started. If left blank the current directory will be used. #--------------------------------------------------------------------------- OUTPUT_DIRECTORY = "." #--------------------------------------------------------------------------- # CREATE_SUBDIRS set to "YES" will cause subdirectories to be created to # hold the various parts of the documentation "tree". # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory # would otherwise causes performance problems for the file system. #--------------------------------------------------------------------------- CREATE_SUBDIRS = YES #--------------------------------------------------------------------------- # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Brazilian, Chinese, Croatian, Czech, Danish, Dutch, Finnish, French, # German, Greek, Hungarian, Italian, Japanese, Korean, Norwegian, Polish, # Portuguese, Romanian, Russian, Slovak, Slovene, Spanish and Swedish. #--------------------------------------------------------------------------- OUTPUT_LANGUAGE = English #--------------------------------------------------------------------------- # If the REPEAT_BRIEF tag is set to YES (the default) doxygen will prepend # the brief description of a member or function before the detailed # description. # # Note: # # If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # #--------------------------------------------------------------------------- BRIEF_MEMBER_DESC = YES #--------------------------------------------------------------------------- # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed # description. Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are # set to NO, the brief descriptions will be completely suppressed. #--------------------------------------------------------------------------- REPEAT_BRIEF = NO #--------------------------------------------------------------------------- # ABBREVIATE_BRIEF. This tag implements a quasi-intelligent brief # description abbreviator that is used to form the text in various listings. # Each string in this list, if found as the leading text of the brief # description, will be stripped from the text and the result after # processing the whole list, is used as the annotated text. Otherwise, the # brief description is used as-is. If left blank, the following values are # used ("\$name" is automatically replaced with the name of the entity): # "The $name class" "The $name widget" "The $name file" "is" "provides" # "specifies" "contains" "represents" "a" "an" "the". #--------------------------------------------------------------------------- ABBREVIATE_BRIEF = \ "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the #--------------------------------------------------------------------------- # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. #--------------------------------------------------------------------------- ALWAYS_DETAILED_SEC = NO #--------------------------------------------------------------------------- # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if # those members were ordinary class members. Constructors, destructors and # assignment operators of the base classes will not be shown. #--------------------------------------------------------------------------- INLINE_INHERITED_MEMB = NO #--------------------------------------------------------------------------- # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the # full path before files name in the file list and in the header files. If # set to NO the shortest path that makes the file name unique will be used. # # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag can # be used to strip a user defined part of the path. Stripping is only done # if one of the specified strings matches the left-hand part of the path. It # is allowed to use relative paths in the argument list. # # FULL_PATH_NAMES = YES # #--------------------------------------------------------------------------- FULL_PATH_NAMES = NO #--------------------------------------------------------------------------- # STRIP_FROM_PATH. If the FULL_PATH_NAMES tag is set to YES then the # STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the # left-hand part of the path. The tag can be used to show relative paths in # the file list. If left blank the directory from which doxygen is run is # used as the path to strip. # # STRIP_FROM_PATH = /home/ahlstrom/mls/oss/libs/xpc/ # #--------------------------------------------------------------------------- STRIP_FROM_PATH = #--------------------------------------------------------------------------- # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells the # reader which header file to include in order to use a class. If left # blank only the name of the header file containing the class definition # is used. Otherwise one should specify the include paths that are # normally passed to the compiler using the -I flag. #--------------------------------------------------------------------------- STRIP_FROM_INC_PATH = #--------------------------------------------------------------------------- # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. #--------------------------------------------------------------------------- SHORT_NAMES = NO #--------------------------------------------------------------------------- # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the # first line (until the first dot) of a JavaDoc-style comment as the brief # description. If set to NO, the JavaDoc comments will behave just like the # Qt-style comments (thus requiring an explict @brief command for a brief # description. #--------------------------------------------------------------------------- JAVADOC_AUTOBRIEF = YES #--------------------------------------------------------------------------- # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) # as a brief description. This used to be the default behaviour. The new # default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. # Note that setting this tag to YES also means that Rational Rose comments # are not recognized any more. #--------------------------------------------------------------------------- MULTILINE_CPP_IS_BRIEF = NO #--------------------------------------------------------------------------- # The DETAILS_AT_TOP option shows the detailed description at the top of the # page, instead of providing a "More..." link. If the DETAILS_AT_TOP # tag is set to YES then Doxygen will output the detailed description near # the top, like JavaDoc. If set to NO, the detailed description appears # after the member documentation. # # Obsolete: DETAILS_AT_TOP = YES #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # reimplements. #--------------------------------------------------------------------------- INHERIT_DOCS = NO #--------------------------------------------------------------------------- # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member # will be part of the file/class/namespace that contains it. #--------------------------------------------------------------------------- SEPARATE_MEMBER_PAGES = NO #--------------------------------------------------------------------------- # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. #--------------------------------------------------------------------------- TAB_SIZE = 3 #--------------------------------------------------------------------------- # This tag can be used to specify a number of aliases that acts as commands # in the documentation. An alias has the form "name=value". For example # adding "sideeffect=\par Side Effects:\n" will allow you to put the command # \sideeffect (or @sideeffect) in the documentation, which will result in a # user defined paragraph with heading "Side Effects:". You can put \n's in # the value part of an alias to insert newlines. #--------------------------------------------------------------------------- ALIASES = \ "abc=\nAbstract Base Class\n" \ "abstract=\nAbstract Base Class\n" \ "accessor=\nAccessor\n" \ "algorithm=\nAlgorithm\n" \ "assumptions=\nAssumption(s)\n" \ "author=\nAuthor(s) " \ "background=\nBackground\n" \ "bit32=\n32-bit\n" \ "bit64=\n64-bit\n" \ "broken=Broken" \ "callback=Callback function" \ "change=Change Note" \ "configuration=\nConfiguration Element\n" \ "constructor=\nConstructor\n" \ "convention=\nConvention\n" \ "contents=\nTable of Contents\n" \ "copyctor=Copy constructor" \ "crossplatform=\nCross-platform\n" \ "ctor=Constructor" \ "cutnpaste=\nCut-and-Paste Code\n" \ "cygwin=\nCygwin\n" \ "date=\nDate " \ "debug=\nDebugging\n" \ "default=\nDefault\n\n" \ "defaultctor=Default constructor" \ "designdoc=\nDesign Section\n\n" \ "destructor=\nDestructor\n" \ "doxygen=\nDoxygen\n" \ "dtor=Destructor" \ "dummy=\nDummy Item\n" \ "example=\nExample\n" \ "friendof=\nFriend of\n" \ "freebsd=\nFreeBSD\n" \ "gcc=\nGCC\n" \ "goals=\nGoals\n" \ "getter='Getter' function for member \e " \ "gnu=\nGNU\n" \ "hardwired=\nHard-wired\n" \ "history=\nHistory\n" \ "inline=inlined" \ "install=\nInstallation\n" \ "intro=\nIntroduction\n" \ "library=\nLibrary " \ "license=License " \ "linux=\nLinux\n" \ "macosx=\nMac OSX\n" \ "macro=\nMacro\n" \ "obsolete=Obsolete" \ "op=Operator" \ "options=\nOptions\n" \ "xoperator=\nOperator\n" \ "paop=Principal assignment operator" \ "paoperator=\nPrincipal Assignment Operator\n" \ "posix=\nPOSIX\n" \ "private=Internal (static or private)" \ "protected=Protected" \ "public=Public" \ "pure=Pure virtual function" \ "question=\nQuestion\n" \ "recursive=Recursive function" \ "reference=\nReference\n" \ "references=\nReferences\n" \ "requirements=\nRequirements\n" \ "setter='Setter' function for member \e " \ "sideeffect=\nSide-effect(s)\n\n" \ "singleton=Singleton" \ "solaris=\nSolaris\n" \ "steps=\nFunction Steps\n" \ "summary=\nSummary" \ "tests=\nTest Case For\n" \ "threadsafe=Threadsafe" \ "threadunsafe=Not threadsafe" \ "tip=\nTip\n" \ "tricky=\nTricky Code\n" \ "undocumented=\nUndocumented\n" \ "unittests=\nUnit Test(s)\n" \ "unix=\nOther UNIX\n" \ "usecase=\nUse Cases\n" \ "update=\nLast Edit " \ "updates=\nLast Edits " \ "usage=\nUsage\n\n" \ "virtual=Virtual function" \ "win32=\nWin32\n" \ "win64=\nWin64\n" #--------------------------------------------------------------------------- # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for # C. For instance some of the names that are used will be different. The # list of all members will be omitted, etc. #--------------------------------------------------------------------------- OPTIMIZE_OUTPUT_FOR_C = NO #--------------------------------------------------------------------------- # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # or Python sources only. Doxygen will then generate output that is more # tailored for that language. For instance, namespaces will be presented as # packages, qualified scopes will look different, etc. #--------------------------------------------------------------------------- OPTIMIZE_OUTPUT_JAVA = NO #--------------------------------------------------------------------------- # BUILTIN_STL_SUPPORT. If you use STL classes (i.e. std::string, # std::vector, etc.) but do not want to include (a tag file for) the STL # sources as input, then you should set this tag to YES in order to let # doxygen match functions declarations and definitions whose arguments # contain STL classes (e.g. func(std::string); v.s. func(std::string) {}). # This also make the inheritance and collaboration diagrams that involve STL # classes more complete and accurate. # # Not supported in doxygen 1.4.4, but leave it in anyway for now. # Nahhhhhhhh. # # BUILTIN_STL_SUPPORT = NO # #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- # If member grouping is used in the documentation and the # DISTRIBUTE_GROUP_DOC tag is set to YES, then doxygen will reuse the # documentation of the first member in the group (if any) for the other # members of the group. By default all members of a group must be documented # explicitly. #--------------------------------------------------------------------------- DISTRIBUTE_GROUP_DOC = NO #--------------------------------------------------------------------------- # Set the SUBGROUPING tag to YES (the default) to allow class member groups # of the same type (for instance a group of public functions) to be put as # a subgroup of that type (e.g. under the Public Functions section). Set it # to NO to prevent subgrouping. Alternatively, this can be done per class # using the \nosubgrouping command. #--------------------------------------------------------------------------- SUBGROUPING = YES #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # # We set most of these EXTRACTs to YES. # # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # # Doxygen will also disable the WARN_IF_UNDOCUMENTED option. # # Private class members and static file members will be hidden unless the # EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES. # # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. If # set to NO only classes defined in header files are included. # # EXTRACT_LOCAL_METHODS. This flag is only useful for Objective-C code. # When set to YES local methods, which are defined in the implementation # section but not in the interface are included in the documentation. If set # to NO (the default) only methods in the interface are included. # #--------------------------------------------------------------------------- EXTRACT_ALL = YES EXTRACT_PRIVATE = NO # YES EXTRACT_STATIC = YES EXTRACT_LOCAL_CLASSES = NO EXTRACT_LOCAL_METHODS = NO #--------------------------------------------------------------------------- # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. If set # to NO (the default) these members will be included in the various # overviews, but no documentation section is generated. This option has no # effect if EXTRACT_ALL is enabled. #--------------------------------------------------------------------------- HIDE_UNDOC_MEMBERS = YES #--------------------------------------------------------------------------- # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If # set to NO (the default) these class will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. #--------------------------------------------------------------------------- HIDE_UNDOC_CLASSES = YES #--------------------------------------------------------------------------- # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. If set to NO (the default) these # declarations will be included in the documentation. #--------------------------------------------------------------------------- HIDE_FRIEND_COMPOUNDS = YES # NO #--------------------------------------------------------------------------- # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. If set to NO # (the default) these blocks will be appended to the function's detailed # documentation block. #--------------------------------------------------------------------------- HIDE_IN_BODY_DOCS = YES # NO #--------------------------------------------------------------------------- # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO (the default) then # the documentation will be excluded. Set it to YES to include the internal # documentation. #--------------------------------------------------------------------------- INTERNAL_DOCS = NO #--------------------------------------------------------------------------- # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower case letters. If set to YES upper case letters are # also allowed. This is useful if you have classes or files whose names only # differ in case and if your file system supports case sensitive file names. # Windows users are adviced to set this option to NO. #--------------------------------------------------------------------------- CASE_SENSE_NAMES = YES #--------------------------------------------------------------------------- # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen will # show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. #--------------------------------------------------------------------------- HIDE_SCOPE_NAMES = NO #--------------------------------------------------------------------------- # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put list of the files that are included by a file in the # documentation of that file. #--------------------------------------------------------------------------- SHOW_INCLUDE_FILES = NO # YES #--------------------------------------------------------------------------- # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] is # inserted in the documentation for inline members. #--------------------------------------------------------------------------- INLINE_INFO = NO # YES #--------------------------------------------------------------------------- # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen will # sort the (detailed) documentation of file and class members alphabetically # by member name. If set to NO the members will appear in declaration order. #--------------------------------------------------------------------------- SORT_MEMBER_DOCS = NO # YES SORT_BRIEF_DOCS = NO SORT_BY_SCOPE_NAME = NO #--------------------------------------------------------------------------- # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the # todo list. This list is created by putting \todo commands in the # documentation. #--------------------------------------------------------------------------- GENERATE_TODOLIST = NO # YES #--------------------------------------------------------------------------- # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the # test list. This list is created by putting \test commands in the # documentation. #--------------------------------------------------------------------------- GENERATE_TESTLIST = NO # YES #--------------------------------------------------------------------------- # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the # bug list. This list is created by putting \bug commands in the # documentation. #--------------------------------------------------------------------------- GENERATE_BUGLIST = NO # YES GENERATE_DEPRECATEDLIST= NO # YES #--------------------------------------------------------------------------- # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if sectionname ... \endif. #--------------------------------------------------------------------------- ENABLED_SECTIONS = #--------------------------------------------------------------------------- # The MAX_INITIALIZER_LINES tag determines the maximum number of lines the # initial value of a variable or define consist of for it to appear in the # documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in # the documentation can be controlled using \showinitializer or # \hideinitializer command in the documentation regardless of this setting. #--------------------------------------------------------------------------- MAX_INITIALIZER_LINES = 0 # 30 #--------------------------------------------------------------------------- # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES # the list will mention the files that were used to generate the # documentation. #--------------------------------------------------------------------------- SHOW_USED_FILES = NO # YES #--------------------------------------------------------------------------- # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the Folder # Tree View (if specified). The default is YES. #--------------------------------------------------------------------------- SHOW_FILES = NO # YES #--------------------------------------------------------------------------- # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. This will remove the Namespaces entry from the Quick # Index and from the Folder Tree View (if specified). The default is YES. # No breaks Doxygen prior to version 1.8.16. #--------------------------------------------------------------------------- SHOW_NAMESPACES = YES #--------------------------------------------------------------------------- # The FILE_VERSION_FILTER tag can be used to specify a program or script # that doxygen should invoke to get the current version for each file # (typically from the version control system). Doxygen will invoke the # program by executing (via popen()) the command command input-file, where # command is the value of the FILE_VERSION_FILTER tag, and input-file is the # name of an input file provided by doxygen. Whatever the program writes to # standard output is used as the file version. # # Example of using a shell script as a filter for Unix: # # FILE_VERSION_FILTER = "/bin/sh versionfilter.sh" # # Example shell script for CVS: # # #!/bin/sh # cvs status $1 | sed -n 's/^[ \]*Working revision: # [ \t]*\([0-9][0-9\.]*\).*/\1/p' # # Example shell script for Subversion: # # #!/bin/sh # svn stat -v $1 | sed -n 's/^[ A-Z?\*|!]\{1,15\}/r/;s/ \{1,15\}/\/r/;s/ .*//p' # # Example filter for ClearCase: # # FILE_VERSION_INFO = "cleartool desc -fmt \%Vn" # #--------------------------------------------------------------------------- FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. # #--------------------------------------------------------------------------- QUIET = NO #--------------------------------------------------------------------------- # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank NO is # used. Use YES every so often to check for real problems. Or leave it at # YES and set the warning logfile below, like we did. (Actually, we now use # the shell's > and 2> redirection operators if make docs is used.) #--------------------------------------------------------------------------- WARNINGS = YES #--------------------------------------------------------------------------- # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. # # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that don't # exist or using markup commands wrongly. # # This WARN_NO_PARAMDOC option can be abled to get warnings for functions # that are documented, but have no documentation for their parameters or # return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. #--------------------------------------------------------------------------- WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = YES #--------------------------------------------------------------------------- # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. #--------------------------------------------------------------------------- WARN_FORMAT = "$file:$line: $text" #--------------------------------------------------------------------------- # The WARN_LOGFILE tag can be used to specify a file to which warning and # error messages should be written. If left blank the output is written to # stderr. You'll want a log file to cut down a little on the verbiage, as # warnings for innocuous stuff are all too common. #--------------------------------------------------------------------------- WARN_LOGFILE = "dox-warnings.log" #--------------------------------------------------------------------------- # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank the # following patterns are tested: # # *.c *.cc *.cxx *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh # *.hxx *.hpp *.h++ *.idl # #--------------------------------------------------------------------------- FILE_PATTERNS = *.dox *.c *.cpp *.h *.hpp EXTENSION_MAPPING = .h=C++ #--------------------------------------------------------------------------- # The RECURSIVE tag can be used to turn specify whether or not # subdirectories should be searched for input files as well. Possible values # are YES and NO. If left blank NO is used. #--------------------------------------------------------------------------- RECURSIVE = NO # YES #--------------------------------------------------------------------------- # The EXCLUDE tag can be used to specify files and/or directories that # should excluded from the INPUT source files. This way you can easily # exclude a subdirectory from a directory tree whose root is specified with # the INPUT tag. #--------------------------------------------------------------------------- EXCLUDE = #--------------------------------------------------------------------------- # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are # excluded from the input. #--------------------------------------------------------------------------- EXCLUDE_SYMLINKS = YES # NO #--------------------------------------------------------------------------- # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # We don't want to document the intl code (it is GNU code), the inspiration # code (it is not ours), or the safety code (it is not useful). #--------------------------------------------------------------------------- EXCLUDE_PATTERNS = */intl/* \ */inspiration/* \ */doc/Notes/* \ */m4/* \ */man/* \ */safety/* \ */tests/* \ */tools/* \ */tmp/* \ */contrib/* #--------------------------------------------------------------------------- # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see the # \include command). #--------------------------------------------------------------------------- EXAMPLE_PATH = #--------------------------------------------------------------------------- # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left blank # all files are included. #--------------------------------------------------------------------------- EXAMPLE_PATTERNS = * #--------------------------------------------------------------------------- # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. Possible values # are YES and NO. If left blank NO is used. #--------------------------------------------------------------------------- EXAMPLE_RECURSIVE = NO #--------------------------------------------------------------------------- # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain image that are included in the documentation (see the \image # command). #--------------------------------------------------------------------------- IMAGE_PATH = #--------------------------------------------------------------------------- # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter # program by executing (via popen()) the command , # where is the value of the INPUT_FILTER tag, and is # the name of an input file. Doxygen will then use the output that the # filter program writes to standard output. # # INPUT_FILTER = "sed -e \"/long on[A-Za-z0-9_]*\(.*\)/d\"" # INPUT_FILTER = ./filter.pl # #--------------------------------------------------------------------------- INPUT_FILTER = FILTER_PATTERNS = #--------------------------------------------------------------------------- # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set # using INPUT_FILTER) will be used to filter the input files when producing # source files to browse. #--------------------------------------------------------------------------- FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these # sources. This adds a lot to the output, but makes it easy for the casual # reader to peruse the code. And comments do not appear, and so the code is # not cluttered. # #--------------------------------------------------------------------------- SOURCE_BROWSER = NO # YES #--------------------------------------------------------------------------- # Setting the INLINE_SOURCES tag to YES will include the body of functions # and classes directly in the documentation. #--------------------------------------------------------------------------- INLINE_SOURCES = NO #--------------------------------------------------------------------------- # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. #--------------------------------------------------------------------------- STRIP_CODE_COMMENTS = YES #--------------------------------------------------------------------------- # If the REFERENCED_BY_RELATION tag is set to YES (the default) then for # each documented function all documented functions referencing it will be # listed. # # If the REFERENCES_RELATION tag is set to YES (the default) then for each # documented function all documented entities called/used by that function # will be listed. # # REFERENCES_LINK_SOURCE seems to be a new one. # #--------------------------------------------------------------------------- REFERENCED_BY_RELATION = NO REFERENCES_RELATION = NO REFERENCES_LINK_SOURCE = NO # YES #--------------------------------------------------------------------------- # If this tag is set to YES, then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). To # use it do the following: # # 1. Install the latest version of global (i.e. 4.8.6 or better) # 2. Enable SOURCE_BROWSER and USE_HTAGS in the config file # 3. Make sure the INPUT points to the root of the source tree # 4. Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links # to source code will now point to the output of htags. # #--------------------------------------------------------------------------- USE_HTAGS = NO #--------------------------------------------------------------------------- # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen will # generate a verbatim copy of the header file for each class for which an # include is specified. Set to NO to disable this. #--------------------------------------------------------------------------- VERBATIM_HEADERS = NO #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then the # COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which this list will be split (can be a number in the range [1..20]) # # In case all classes in a project start with a common prefix, all classes # will be put under the same header in the alphabetical index. The # IGNORE_PREFIX tag can be used to specify one or more prefixes that should # be ignored while generating the index headers. # #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = YES COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # # If the GENERATE_HTML tag is set to YES (the default) Doxygen will generate # HTML output. # # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be put in # front of it. If left blank `html' will be used as the default path. # # The HTML_HEADER tag can be used to specify a personal HTML header for each # generated HTML page. If it is left blank doxygen will generate a standard # header. # # The HTML_FOOTER tag can be used to specify a personal HTML footer for each # generated HTML page. If it is left blank doxygen will generate a standard # footer. # #--------------------------------------------------------------------------- GENERATE_HTML = NO # YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html #--------------------------------------------------------------------------- # Right now, this style sheet is disabled. It removes the boxes around the # function signatures, making the documentation more difficult to read. We # will have to improve the page.css file before we use it. # # HTML_STYLESHEET = css/page.css # HTML_HEADER = css/header.html # HTML_FOOTER = css/footer.html # HTML_STYLESHEET = css/styles.css # HTML_STYLESHEET = css/menu.css #--------------------------------------------------------------------------- HTML_HEADER = HTML_FOOTER = HTML_STYLESHEET = #--------------------------------------------------------------------------- # If the GENERATE_HTMLHELP tag is set to YES, additional index files will be # generated that can be used as input for tools like the Microsoft HTML help # workshop to generate a compressed HTML help file (.chm) of the generated # HTML documentation. # # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag controls # if a separate .chi index file is generated (YES) or that it should be # included in the master .chm file (NO). # # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag controls # whether a binary table of contents is generated (YES) or a normal table of # contents (NO) in the .chm file. #--------------------------------------------------------------------------- GENERATE_HTMLHELP = NO CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO BINARY_TOC = NO #--------------------------------------------------------------------------- # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the Html help documentation and to the tree view. #--------------------------------------------------------------------------- TOC_EXPAND = NO #--------------------------------------------------------------------------- # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. #--------------------------------------------------------------------------- DISABLE_INDEX = YES # NO #--------------------------------------------------------------------------- # This tag can be used to set the number of enum values (range [1..20]) that # doxygen will group on one line in the generated HTML documentation. #--------------------------------------------------------------------------- ENUM_VALUES_PER_LINE = 1 #--------------------------------------------------------------------------- # If the GENERATE_TREEVIEW tag is set to YES, a side panel will be generated # containing a tree-like index structure (just like the one that is # generated for HTML Help). For this to work a browser that supports # JavaScript and frames is required (for instance Mozilla, Netscape 4.0+, or # Internet explorer 4.0+). Note that for large projects the tree generation # can take a very long time. In such cases it is better to disable this # feature. Windows users are probably better off using the HTML help # feature. # # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. # #--------------------------------------------------------------------------- GENERATE_TREEVIEW = YES TREEVIEW_WIDTH = 250 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. # # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be put in # front of it. If left blank `latex' will be used as the default path. # # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. # #--------------------------------------------------------------------------- GENERATE_LATEX = YES LATEX_OUTPUT = latex LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex COMPACT_LATEX = NO #--------------------------------------------------------------------------- # The PAPER_TYPE tag can be used to set the paper type that is used by the # printer. Possible values are: a4, a4wide, letter, legal and executive. If # left blank a4wide will be used. #--------------------------------------------------------------------------- PAPER_TYPE = a4wide #--------------------------------------------------------------------------- # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. #--------------------------------------------------------------------------- EXTRA_PACKAGES = #--------------------------------------------------------------------------- # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a standard # header. Notice: only use this tag if you know what you are doing! #--------------------------------------------------------------------------- LATEX_HEADER = #--------------------------------------------------------------------------- # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is # prepared for conversion to pdf (using ps2pdf). The pdf file will contain # links (just like the HTML output) instead of page references This makes # the output suitable for online browsing using a pdf viewer. #--------------------------------------------------------------------------- PDF_HYPERLINKS = YES #--------------------------------------------------------------------------- # If this tag is set to YES, pdflatex will be used instead of plain latex in # the generated Makefile. Set this option to YES to get a higher quality PDF # documentation. #--------------------------------------------------------------------------- USE_PDFLATEX = YES #--------------------------------------------------------------------------- # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the # \\batchmode. command to the generated LaTeX files. This will instruct # LaTeX to keep running if errors occur, instead of asking the user for # help. This option is also used when generating formulas in HTML. #--------------------------------------------------------------------------- LATEX_BATCHMODE = YES LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output The # RTF output is optimised for Word 97 and may not look very pretty with # other RTF readers or editors. # # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If # a relative path is entered the value of OUTPUT_DIRECTORY will be put in # front of it. If left blank `rtf' will be used as the default path. # # If the COMPACT_RTF tag is set to YES Doxygen generates more compact RTF # documents. This may be useful for small projects and may help to save some # trees in general. # #--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO #--------------------------------------------------------------------------- # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will # contain hyperlink fields. The RTF file will contain links (just like the # HTML output) instead of page references. This makes the output suitable # for online browsing using WORD or other programs which support those # fields. Note: wordpad (write) and others do not support links. #--------------------------------------------------------------------------- RTF_HYPERLINKS = NO #--------------------------------------------------------------------------- # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assigments. You only have to provide # replacements, missing definitions are set to their default value. #--------------------------------------------------------------------------- RTF_STYLESHEET_FILE = #--------------------------------------------------------------------------- # Set optional variables used in the generation of an rtf document. Syntax # is similar to doxygen's config file. #--------------------------------------------------------------------------- RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will generate # man pages # # The MAN_OUTPUT tag is used to specify where the man pages will be put. If # a relative path is entered the value of OUTPUT_DIRECTORY will be put in # front of it. If left blank `man' will be used as the default path. # # The MAN_EXTENSION tag determines the extension that is added to the # generated man pages (default is the subroutine's section .3) # # If the MAN_LINKS tag is set to YES and Doxygen generates man output, then # it will generate one additional man file for each entity documented in the # real man page(s). These additional files only source the real man page, # but without them the man command would be unable to find the correct page. # The default is NO. # #--------------------------------------------------------------------------- GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # # If the GENERATE_XML tag is set to YES Doxygen will generate an XML file # that captures the structure of the code including all documentation. Note # that this feature is still experimental and incomplete at the moment. # #--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will generate an # AutoGen Definitions (see autogen.sf.net) file that captures the structure # of the code including all documentation. Note that this feature is still # experimental and incomplete at the moment. # #--------------------------------------------------------------------------- GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- GENERATE_PERLMOD = NO PERLMOD_LATEX = NO PERLMOD_PRETTY = YES PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. # #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES #--------------------------------------------------------------------------- # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performered. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. #--------------------------------------------------------------------------- MACRO_EXPANSION = YES #--------------------------------------------------------------------------- # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_PREDEFINED tags. #--------------------------------------------------------------------------- EXPAND_ONLY_PREDEF = YES #--------------------------------------------------------------------------- # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be searched if a #include is found. # # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. (The Doxygen documentation actually specifies this # option as "@INCLUDE_PATH", and describes the actual inclusion as # # @INCLUDE = config_file_name # @INCLUDE_PATH = my_config_dir # # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. #--------------------------------------------------------------------------- SEARCH_INCLUDES = NO # YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = #--------------------------------------------------------------------------- # The PREDEFINED tag can be used to specify one or more macro names that are # defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name or # name=definition (no spaces). If the definition and the = are omitted =1 is # assumed. # # The DOXYGEN symbol is also defined to allow exposing non-POSIX # documentation where only a non-POSIX implementation exists. #--------------------------------------------------------------------------- PREDEFINED = \ DOXYGEN \ SEQ66_JACK_SESSION \ SEQ66_JACK_SUPPORT \ SEQ66_HAVE_LIBASOUND #--------------------------------------------------------------------------- # If the MACRO_EXPANSION and EXPAND_PREDEF_ONLY tags are set to YES then # this tag can be used to specify a list of macro names that should be # expanded. The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. #--------------------------------------------------------------------------- EXPAND_AS_DEFINED = #--------------------------------------------------------------------------- # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then doxygen's # preprocessor will remove all function-like macros that are alone on a line # and do not end with a semicolon. Such function macros are typically used # for boiler-plate code, and will confuse the parser if not removed. #--------------------------------------------------------------------------- SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # # The TAGFILES tag can be used to specify one or more tagfiles. A tagfile # is a .... (TODO) # # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. # #--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = #--------------------------------------------------------------------------- # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes will # be listed. #--------------------------------------------------------------------------- ALLEXTERNALS = NO EXTERNAL_GROUPS = NO # YES #--------------------------------------------------------------------------- # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). #--------------------------------------------------------------------------- PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in Html, RTF and LaTeX) for classes with # base or super classes. Setting the tag to NO turns the diagrams off. Note # that this option is superceded by the HAVE_DOT option below. This is only # a fallback. It is recommended to install and use dot, since it yield more # powerful graphs. # #--------------------------------------------------------------------------- CLASS_DIAGRAMS = NO # YES #--------------------------------------------------------------------------- # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented or is not a # class. #--------------------------------------------------------------------------- HIDE_UNDOC_RELATIONS = YES #--------------------------------------------------------------------------- # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool # is available from the path. This tool is part of Graphviz, a graph # visualization toolkit from AT&T and Lucent Bell Labs. The other options in # this section have no effect if this option is set to NO (the default). # This option requires the Helvetica font (otherwise, processing slows way # down with a lot of 'Error: fontconfig: Didn't find expected font family. # Perhaps URW Type 1 fonts need installing? : Helvetica' error messages.) #--------------------------------------------------------------------------- HAVE_DOT = YES #--------------------------------------------------------------------------- # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen will # generate a graph for each documented class showing the direct and indirect # inheritance relations. Setting this tag to YES will force the the # CLASS_DIAGRAMS tag to NO. #--------------------------------------------------------------------------- CLASS_GRAPH = NO # YES #--------------------------------------------------------------------------- # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and class # references variables) of the class with other documented classes. #--------------------------------------------------------------------------- COLLABORATION_GRAPH = NO # YES GROUP_GRAPHS = NO # YES UML_LOOK = NO # YES #--------------------------------------------------------------------------- # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. #--------------------------------------------------------------------------- TEMPLATE_RELATIONS = NO #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. #--------------------------------------------------------------------------- INCLUDE_GRAPH = NO # YES #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. #--------------------------------------------------------------------------- INCLUDED_BY_GRAPH = NO # YES #--------------------------------------------------------------------------- # If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will # generate a call dependency graph for every global function or class # method. Note that enabling this option will significantly increase the # time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. # # Indeed! It can make a 5-minute documentation build into an hour-long # wait. # # Also seems to be called "CALLER_GRAPH" and \callergraph. # #--------------------------------------------------------------------------- CALL_GRAPH = NO CALLER_GRAPH = NO #--------------------------------------------------------------------------- # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will create a graphical hierarchy of all classes instead of a textual one. #--------------------------------------------------------------------------- GRAPHICAL_HIERARCHY = NO # YES DIRECTORY_GRAPH = NO # YES DOT_IMAGE_FORMAT = png #--------------------------------------------------------------------------- # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found on the path. # # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the \dotfile # command). #--------------------------------------------------------------------------- DOT_PATH = DOTFILE_DIRS = #--------------------------------------------------------------------------- # The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width # (in pixels) of the graphs generated by dot. If a graph becomes larger than # this value, doxygen will try to truncate the graph, so that it fits within # the specified constraint. Beware that most browsers cannot cope with very # large images. # # The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height # (in pixels) of the graphs generated by dot. If a graph becomes larger than # this value, doxygen will try to truncate the graph, so that it fits within # the specified constraint. Beware that most browsers cannot cope with very # large images. # # Obsolete: # # MAX_DOT_GRAPH_WIDTH = 1024 # MAX_DOT_GRAPH_HEIGHT = 1024 #--------------------------------------------------------------------------- MAX_DOT_GRAPH_DEPTH = 1000 DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = NO #--------------------------------------------------------------------------- # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. #--------------------------------------------------------------------------- GENERATE_LEGEND = NO # YES #--------------------------------------------------------------------------- # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will remove the # intermedate dot files that are used to generate the various graphs. #--------------------------------------------------------------------------- DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- # # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. # #--------------------------------------------------------------------------- SEARCHENGINE = NO #***************************************************************************** # seq66/doc/dox/doxy-common.cfg #----------------------------------------------------------------------------- # vim: ts=3 sw=3 et ft=cfg #----------------------------------------------------------------------------- ================================================ FILE: doc/dox/libseq66/libseq66.cfg ================================================ #***************************************************************************** # seq66/doc/dox/libseq66.cfg #----------------------------------------------------------------------------- ## # \file libseq66.cfg # \library seq66 # \author Chris Ahlstrom) # \date 2019-05-11 # \update 2021-06-10 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This "Doxyfile" provides the configuration needed to build the XPC # library suite documentation. It describes the settings to be used by # the documentation system Doxygen (www.doxygen.org) for a project. # # \references # - http://www.stack.nl/~dimitri/doxygen/config.html # #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Project related configuration options #----------------------------------------------------------------------------- # # The PROJECT_NAME tag is a single word (or a /short/ sequence of words # surrounded by quotes) that should identify the project. This name is used # in the title of most generated pages and in a few other places. # #--------------------------------------------------------------------------- PROJECT_NAME = "Seq66 libseq66 Library" @INCLUDE_PATH = ".." @INCLUDE = "doxy-common.cfg" #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # # The INPUT tag can be used to specify the files and/or directories that # contain documented source files. You may enter file names like # "myfile.cpp" or directories like "/usr/src/myproject". Separate the files # or directories with spaces. # # The directory here specifies the relative path needed to get to the # project directory, so that /all/ of the libraries and applications can # be documented. (However, note that we want only our special *.dox files # to be included in the overarching front-matter XPC suite documentation.) # # There seems to be no difference in the ordering of pages when the # following is used. We'd really like to be able to control the page # ordering. # #--------------------------------------------------------------------------- INPUT = mainpage.dox \ ../../../libseq66/include/cfg/basesettings.hpp \ ../../../libseq66/include/cfg/cmdlineopts.hpp \ ../../../libseq66/include/cfg/comments.hpp \ ../../../libseq66/include/cfg/configfile.hpp \ ../../../libseq66/include/cfg/midicontrolfile.hpp \ ../../../libseq66/include/cfg/mutegroupsfile.hpp \ ../../../libseq66/include/cfg/rcfile.hpp \ ../../../libseq66/include/cfg/rcsettings.hpp \ ../../../libseq66/include/cfg/recent.hpp \ ../../../libseq66/include/cfg/scales.hpp \ ../../../libseq66/include/cfg/settings.hpp \ ../../../libseq66/include/cfg/userinstrument.hpp \ ../../../libseq66/include/cfg/usermidibus.hpp \ ../../../libseq66/include/cfg/usrfile.hpp \ ../../../libseq66/include/cfg/usrsettings.hpp \ ../../../libseq66/include/ctrl/automation.hpp \ ../../../libseq66/include/ctrl/keycontainer.hpp \ ../../../libseq66/include/ctrl/keycontrol.hpp \ ../../../libseq66/include/ctrl/keymap.hpp \ ../../../libseq66/include/ctrl/keystroke.hpp \ ../../../libseq66/include/ctrl/midicontrolin.hpp \ ../../../libseq66/include/ctrl/midicontrol.hpp \ ../../../libseq66/include/ctrl/midioperation.hpp \ ../../../libseq66/include/ctrl/opcontainer.hpp \ ../../../libseq66/include/ctrl/opcontrol.hpp \ ../../../libseq66/include/midi/businfo.hpp \ ../../../libseq66/include/midi/controllers.hpp \ ../../../libseq66/include/midi/editable_event.hpp \ ../../../libseq66/include/midi/editable_events.hpp \ ../../../libseq66/include/midi/event.hpp \ ../../../libseq66/include/midi/eventlist.hpp \ ../../../libseq66/include/midi/jack_assistant.hpp \ ../../../libseq66/include/midi/mastermidibase.hpp \ ../../../libseq66/include/midi/mastermidibus.hpp \ ../../../libseq66/include/midi/midi_splitter.hpp \ ../../../libseq66/include/midi/midi_vector.hpp \ ../../../libseq66/include/midi/midi_vector_base.hpp \ ../../../libseq66/include/midi/midibase.hpp \ ../../../libseq66/include/midi/midibus.hpp \ ../../../libseq66/include/midi/midibus_common.hpp \ ../../../libseq66/include/midi/midibytes.hpp \ ../../../libseq66/include/midi/midifile.hpp \ ../../../libseq66/include/midi/wrkfile.hpp \ ../../../libseq66/include/play/clockslist.hpp \ ../../../libseq66/include/play/inputslist.hpp \ ../../../libseq66/include/play/mutegroup.hpp \ ../../../libseq66/include/play/mutegroups.hpp \ ../../../libseq66/include/play/performer.hpp \ ../../../libseq66/include/play/playlist.hpp \ ../../../libseq66/include/play/screenset.hpp \ ../../../libseq66/include/play/seq.hpp \ ../../../libseq66/include/play/sequence.hpp \ ../../../libseq66/include/play/setmapper.hpp \ ../../../libseq66/include/play/triggers.hpp \ ../../../libseq66/include/seq66_features.h \ ../../../libseq66/include/seq66_features.hpp \ ../../../libseq66/include/seq66_platform_macros.h \ ../../../libseq66/include/os/daemonize.hpp \ ../../../libseq66/include/os/timing.hpp \ ../../../libseq66/include/util/automutex.hpp \ ../../../libseq66/include/util/basic_macros.h \ ../../../libseq66/include/util/basic_macros.hpp \ ../../../libseq66/include/util/calculations.hpp \ ../../../libseq66/include/util/condition.hpp \ ../../../libseq66/include/util/filefunctions.hpp \ ../../../libseq66/include/util/palette.hpp \ ../../../libseq66/include/util/recmutex.hpp \ ../../../libseq66/include/util/rect.hpp \ ../../../libseq66/include/util/strfunctions.hpp \ ../../../libseq66/src/cfg/basesettings.cpp \ ../../../libseq66/src/cfg/cmdlineopts.cpp \ ../../../libseq66/src/cfg/comments.cpp \ ../../../libseq66/src/cfg/configfile.cpp \ ../../../libseq66/src/cfg/midicontrolfile.cpp \ ../../../libseq66/src/cfg/mutegroupsfile.cpp \ ../../../libseq66/src/cfg/rcfile.cpp \ ../../../libseq66/src/cfg/rcsettings.cpp \ ../../../libseq66/src/cfg/recent.cpp \ ../../../libseq66/src/cfg/settings.cpp \ ../../../libseq66/src/cfg/userinstrument.cpp \ ../../../libseq66/src/cfg/usermidibus.cpp \ ../../../libseq66/src/cfg/usrfile.cpp \ ../../../libseq66/src/cfg/usrsettings.cpp \ ../../../libseq66/src/ctrl/automation.cpp \ ../../../libseq66/src/ctrl/keycontainer.cpp \ ../../../libseq66/src/ctrl/keycontrol.cpp \ ../../../libseq66/src/ctrl/keymap.cpp \ ../../../libseq66/src/ctrl/keystroke.cpp \ ../../../libseq66/src/ctrl/midicontrolin.cpp \ ../../../libseq66/src/ctrl/midicontrol.cpp \ ../../../libseq66/src/ctrl/midioperation.cpp \ ../../../libseq66/src/ctrl/opcontainer.cpp \ ../../../libseq66/src/ctrl/opcontrol.cpp \ ../../../libseq66/src/midi/businfo.cpp \ ../../../libseq66/src/midi/controllers.cpp \ ../../../libseq66/src/midi/editable_event.cpp \ ../../../libseq66/src/midi/editable_events.cpp \ ../../../libseq66/src/midi/event.cpp \ ../../../libseq66/src/midi/eventlist.cpp \ ../../../libseq66/src/midi/jack_assistant.cpp \ ../../../libseq66/src/midi/mastermidibase.cpp \ ../../../libseq66/src/midi/midi_splitter.cpp \ ../../../libseq66/src/midi/midi_vector.cpp \ ../../../libseq66/src/midi/midi_vector_base.cpp \ ../../../libseq66/src/midi/midibase.cpp \ ../../../libseq66/src/midi/midibytes.cpp \ ../../../libseq66/src/midi/midifile.cpp \ ../../../libseq66/src/midi/wrkfile.cpp \ ../../../libseq66/src/play/mutegroup.cpp \ ../../../libseq66/src/play/mutegroups.cpp \ ../../../libseq66/src/play/performer.cpp \ ../../../libseq66/src/play/playlist.cpp \ ../../../libseq66/src/play/screenset.cpp \ ../../../libseq66/src/play/seq.cpp \ ../../../libseq66/src/play/sequence.cpp \ ../../../libseq66/src/play/setmapper.cpp \ ../../../libseq66/src/play/triggers.cpp \ ../../../libseq66/src/seq66_features.cpp \ ../../../libseq66/src/os/daemonize.cpp \ ../../../libseq66/src/os/timing.cpp \ ../../../libseq66/src/util/automutex.cpp \ ../../../libseq66/src/util/basic_macros.cpp \ ../../../libseq66/src/util/calculations.cpp \ ../../../libseq66/src/util/condition.cpp \ ../../../libseq66/src/util/filefunctions.cpp \ ../../../libseq66/src/util/palette.cpp \ ../../../libseq66/src/util/recmutex.cpp \ ../../../libseq66/src/util/rect.cpp \ ../../../libseq66/src/util/strfunctions.cpp #***************************************************************************** # seq66/doc/dox/libseq66.cfg #----------------------------------------------------------------------------- # vim: ts=3 sw=3 et ft=cfg #----------------------------------------------------------------------------- ================================================ FILE: doc/dox/libseq66/mainpage.dox ================================================ /* * This module provides no code. It provides only a Doxygen "Doxygen * information" page. * * Since this documentation could be part of a more comprehensive * documentation package covering many libraries and applications, we * include a \mainpage directive in this stub file. * * This file will be copied to or clean from each documentation * subdirectory as needed. */ /** \file mainpage.dox This document describes the libseq66 library of the Seq66 application. \mainpage Seq66 Developer Notes for the libseq66 library. \author Chris Ahlstrom 2019-05-11 to 2019-05-11 \section libseq66_intro Introduction The current document, generated by Doxygen, describes the libseq66 library, which provides basic support for MIDI, sequences/loops, mute-groups, JACK, and more. */ /* * mainpage.dox * * vim: ts=3 sw=3 et syntax=doxygen */ ================================================ FILE: doc/dox/libsessions/libsessions.cfg ================================================ #***************************************************************************** # seq66/doc/dox/libsessions.cfg #----------------------------------------------------------------------------- ## # \file libsessions.cfg # \library seq66 # \author Chris Ahlstrom) # \date 2019-05-11 # \update 2020-10-02 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This "Doxyfile" provides the configuration needed to build the XPC # library suite documentation. It describes the settings to be used by # the documentation system Doxygen (www.doxygen.org) for a project. # # \references # - http://www.stack.nl/~dimitri/doxygen/config.html # #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Project related configuration options #----------------------------------------------------------------------------- # # The PROJECT_NAME tag is a single word (or a /short/ sequence of words # surrounded by quotes) that should identify the project. This name is used # in the title of most generated pages and in a few other places. # #--------------------------------------------------------------------------- PROJECT_NAME = "Seq66 libsessions Library" @INCLUDE_PATH = ".." @INCLUDE = "doxy-common.cfg" #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # # The INPUT tag can be used to specify the files and/or directories that # contain documented source files. You may enter file names like # "myfile.cpp" or directories like "/usr/src/myproject". Separate the files # or directories with spaces. # # The directory here specifies the relative path needed to get to the # project directory, so that /all/ of the libraries and applications can # be documented. (However, note that we want only our special *.dox files # to be included in the overarching front-matter XPC suite documentation.) # # There seems to be no difference in the ordering of pages when the # following is used. We'd really like to be able to control the page # ordering. # #--------------------------------------------------------------------------- INPUT = mainpage.dox \ ./libsessions/src/cfg/basesettings.cpp \ ./libsessions/src/util/strfunctions.cpp #***************************************************************************** # seq66/doc/dox/libsessions.cfg #----------------------------------------------------------------------------- # vim: ts=3 sw=3 et ft=cfg #----------------------------------------------------------------------------- ================================================ FILE: doc/dox/libsessions/mainpage.dox ================================================ /* * This module provides no code. It provides only a Doxygen "Doxygen * information" page. * * Since this documentation could be part of a more comprehensive * documentation package covering many libraries and applications, we * include a \mainpage directive in this stub file. * * This file will be copied to or clean from each documentation * subdirectory as needed. */ /** \file mainpage.dox This document describes the libsessions library of the Seq66 application. \mainpage Seq66 Developer Notes for the libsessions library. \author Chris Ahlstrom 2020-10-02 to 2020-10-02 \section libsessions_intro Introduction The current document, generated by Doxygen, describes the libsessions library, which provides basic support for MIDI, sequences/loops, mute-groups, JACK, and more. */ /* * mainpage.dox * * vim: ts=3 sw=3 et syntax=doxygen */ ================================================ FILE: doc/dox/make-helper ================================================ #!/bin/bash # #****************************************************************************** # make-helper (generic) #------------------------------------------------------------------------------ ## # \file make-helper # \library libseq66 directory # \author Chris Ahlstrom # \date 2019-05-11 # \update 2020-10-02 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This file creates the doxygen documentation for the $TARGET # subdirectory. # #------------------------------------------------------------------------------ DOXLOGFILE="dox-build-$1.log" TARGET="$1" cat << E_O_F > $DOXLOGFILE Running Doxygen on the $TARGET target, in the make-helper script. $ doxygen $TARGET.cfg &> $DOXLOGFILE E_O_F doxygen $TARGET.cfg &>> $DOXLOGFILE # sed -e 's/letterpaper,/letterpaper,margin=2cm,/' -i latex/refman.tex cat << E_O_F >> $DOXLOGFILE The PDF build breaks too easily, so now we descend and run the commands a few times. make --directory=latex pdf E_O_F make --directory=latex pdf &>> $DOXLOGFILE # Optimize the PDF to cut down on its size. cat << E_O_F >> $DOXLOGFILE ../optimize seq66_$TARGET >> dox-progress.log 2>> dox-errors.log cp latex/refman.pdf seq66_$TARGET.pdf E_O_F mv latex/refman.pdf seq66_lib_$TARGET.pdf #****************************************************************************** # make-helper (generic) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: doc/dox/make_dox ================================================ #!/bin/bash # #****************************************************************************** # make_dox (generic) #------------------------------------------------------------------------------ ## # \file make_dox # \library generic # \author Chris Ahlstrom # \date 2015-08-08 # \update 2019-05-05 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This file creates the doxygen documentation desired, by changing to # the original directory, running doxygen, and moving the results back # to the original directory. # # This is our way of handling out-of-source builds of documentation, # without getting into the complexities of GNU Autotools method of # supporting Doxygen. # # Apart from the complexity of this work, another reason for using a # script, instead of putting the code in Makefile.am, is that we need # some bash features (e.g. pushd/popd), and also need to be able to run # only one instance of bash -- 'make' runs each line of code on its own # instance. # # \param $1 # Provides the directory where the Doxygen files are stored. # # \param $2 # # This parameter will be named for one of the libraries provided by Seq66. # # \usage # The normal usage occurs in the Makefile.am file: # # $(srcdir)/make_dox $(srcdir) docs # # The usage of $(srcdir) is necessary because we did not try to set up # support for having automake copy this script and the *.dox documents # to the out-of-source directory. We really don't want to do Doxygen # the in the GNU Autotools way. # #------------------------------------------------------------------------------ #****************************************************************************** # To copy files or not? #------------------------------------------------------------------------------ # # This file should support in-source builds or out-of-source builds. # # For in-source builds, we cannot copy the results to the previous # directory, because they are the same directory, and an error will # occur. So, for an in-source build, where $(srcdir) == ".", we will # not try to copy files. # # For the out-of-source build, we will copy the files back to the # previous directory. # #------------------------------------------------------------------------------ DOCOPY="yes" NEWDIR="$1" TARGET="$2" ACTION="$3" LASTDIR="`pwd`" if [ "$1" == "help" ] || [ "$1" == "--help" ]; then cat << E_O_F Usage: ./make_dox srcdir dirname [ clean ] where TARGET is 'libseq66', 'libsessions', 'seq_portmidi', or 'seq_rtmidi'. E_O_F exit 0 fi if [ "$ACTION" == "clean" ] ; then pushd "$NEWDIR" pushd "$TARGET" rm -f *.log rm -rf latex/ popd popd exit 0 fi if [ "$TARGET" == "" ] ; then NEWDIR="." TARGET="$1" echo "Using current directory for target '$TARGET'..." fi if [ $NEWDIR == "." ] ; then DOCOPY="no" fi pushd $NEWDIR pushd $TARGET ../make-helper $TARGET popd popd #****************************************************************************** # make_dox (generic) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: doc/dox/optimize ================================================ #!/bin/bash # #****************************************************************************** # optimize #------------------------------------------------------------------------------ ## # \file optimize # \library Documentation # \author Chris Ahlstrom # \date 2019-04-14 # \update 2020-10-02 # \version $Revision$ # \license See license.dox # # This file attempts to optimize a PDF. # # $1 is the base name of the PDF to be generated. # #------------------------------------------------------------------------------ if test -f latex/refman.pdf ; then gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dNOPAUSE -dQUIET -dBATCH \ -sOutputFile="$1.pdf" latex/refman.pdf else echo "ERROR: "$1" PDF not generated!!!" exit 1 else echo "ERROR: latex/refman.pdf not generated!!!" fi #------------------------------------------------------------------------------ # vim: ts=3 sw=3 noet ft=sh #------------------------------------------------------------------------------ ================================================ FILE: doc/dox/seq_portmidi/mainpage.dox ================================================ /* * This module provides no code. It provides only a Doxygen "Doxygen * information" page. * * Since this documentation could be part of a more comprehensive * documentation package covering many libraries and applications, we * include a \mainpage directive in this stub file. * * This file will be copied to or clean from each documentation * subdirectory as needed. */ /** \file mainpage.dox This document describes the seq_portmidi library of the Seq66 application. \mainpage Seq66 Developer Notes for the seq_portmidi library. \author Chris Ahlstrom 2019-05-11 to 2019-05-11 \section seq_portmidi_intro Introduction The current document, generated by Doxygen, describes the seq_portmidi library, which provides basic support for MIDI for Windows, Linux, and (we hope) Mac OSX. This library is a port and cleanup of the PortMidi library, with the unnecessary support for Java (!) configuration file removed. */ /* * mainpage.dox * * vim: ts=3 sw=3 et syntax=doxygen */ ================================================ FILE: doc/dox/seq_portmidi/seq_portmidi.cfg ================================================ #***************************************************************************** # seq66/doc/dox/seq_portmidi.cfg #----------------------------------------------------------------------------- ## # \file seq_portmidi.cfg # \library seq66 # \author Chris Ahlstrom) # \date 2015-09-10 # \update 2020-10-02 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This "Doxyfile" provides the configuration needed to build the XPC # library suite documentation. It describes the settings to be used by # the documentation system Doxygen (www.doxygen.org) for a project. # # \references # - http://www.stack.nl/~dimitri/doxygen/config.html # #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Project related configuration options #----------------------------------------------------------------------------- # # The PROJECT_NAME tag is a single word (or a /short/ sequence of words # surrounded by quotes) that should identify the project. This name is used # in the title of most generated pages and in a few other places. # #--------------------------------------------------------------------------- PROJECT_NAME = "Seq66 seq_portmidi Library" @INCLUDE_PATH = ".." @INCLUDE = "doxy-common.cfg" #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # # The INPUT tag can be used to specify the files and/or directories that # contain documented source files. You may enter file names like # "myfile.cpp" or directories like "/usr/src/myproject". Separate the files # or directories with spaces. # # The directory here specifies the relative path needed to get to the # project directory, so that /all/ of the libraries and applications can # be documented. (However, note that we want only our special *.dox files # to be included in the overarching front-matter XPC suite documentation.) # # There seems to be no difference in the ordering of pages when the # following is used. We'd really like to be able to control the page # ordering. # #--------------------------------------------------------------------------- INPUT = mainpage.dox \ ./seq_portmidi/include/pmlinux.h \ ./seq_portmidi/include/pmerrmm.h \ ./seq_portmidi/include/portmidi.h \ ./seq_portmidi/include/pmmac.h \ ./seq_portmidi/include/porttime.h \ ./seq_portmidi/include/pmwinmm.h \ ./seq_portmidi/include/pminternal.h \ ./seq_portmidi/include/pmutil.h \ ./seq_portmidi/include/pmlinuxalsa.h \ ./seq_portmidi/include/mastermidibus_pm.hpp \ ./seq_portmidi/include/midibus_pm.hpp \ ./seq_portmidi/src/midibus.cpp \ ./seq_portmidi/src/mastermidibus.cpp #***************************************************************************** # seq66/doc/dox/seq_portmidi.cfg #----------------------------------------------------------------------------- # vim: ts=3 sw=3 et ft=cfg #----------------------------------------------------------------------------- ================================================ FILE: doc/dox/seq_rtmidi/mainpage.dox ================================================ /* * This module provides no code. It provides only a Doxygen "Doxygen * information" page. * * Since this documentation could be part of a more comprehensive * documentation package covering many libraries and applications, we * include a \mainpage directive in this stub file. * * This file will be copied to or clean from each documentation * subdirectory as needed. */ /** \file mainpage.dox This document describes the seq_rtmidi library of the Seq66 application. \mainpage Seq66 Developer Notes for the seq_rtmidi library. \author Chris Ahlstrom 2019-05-11 to 2019-05-11 \section seq_rtmidi_intro Introduction The current document, generated by Doxygen, describes the seq_rtmidi library, which provides basic support for MIDI for Linux using either ALSA or JACK, detected at run-time. This library is a port and cleanup of the RtMidi library, with some refactoring to fit the model that Seq66 uses. */ /* * mainpage.dox * * vim: ts=3 sw=3 et syntax=doxygen */ ================================================ FILE: doc/dox/seq_rtmidi/seq_rtmidi.cfg ================================================ #***************************************************************************** # seq66/doc/dox/seq_rtmidi.cfg #----------------------------------------------------------------------------- ## # \file seq_rtmidi.cfg # \library seq66 # \author Chris Ahlstrom) # \date 2015-09-10 # \update 2020-10-02 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This "Doxyfile" provides the configuration needed to build the seq_rtmidi # library suite documentation. It describes the settings to be used by the # documentation system Doxygen (www.doxygen.org) for a project. # # \references # - http://www.stack.nl/~dimitri/doxygen/config.html # #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Project related configuration options #----------------------------------------------------------------------------- # # The PROJECT_NAME tag is a single word (or a /short/ sequence of words # surrounded by quotes) that should identify the project. This name is used # in the title of most generated pages and in a few other places. # #--------------------------------------------------------------------------- PROJECT_NAME = "Seq66 seq_rtmidi Library" @INCLUDE_PATH = ".." @INCLUDE = "doxy-common.cfg" #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # # The INPUT tag can be used to specify the files and/or directories that # contain documented source files. You may enter file names like # "myfile.cpp" or directories like "/usr/src/myproject". Separate the files # or directories with spaces. # # The directory here specifies the relative path needed to get to the # project directory, so that /all/ of the libraries and applications can # be documented. (However, note that we want only our special *.dox files # to be included in the overarching front-matter XPC suite documentation.) # # There seems to be no difference in the ordering of pages when the # following is used. We'd really like to be able to control the page # ordering. # #--------------------------------------------------------------------------- INPUT = mainpage.dox \ ./seq_rtmidi/include/seq66_rtmidi_features.h \ ./seq_rtmidi/include/midi_probe.hpp \ ./seq_rtmidi/include/midi_jack.hpp \ ./seq_rtmidi/include/rtmidi.hpp \ ./seq_rtmidi/include/midibus_rm.hpp \ ./seq_rtmidi/include/midi_jack_info.hpp \ ./seq_rtmidi/include/midi_api.hpp \ ./seq_rtmidi/include/rterror.hpp \ ./seq_rtmidi/include/midi_alsa_info.hpp \ ./seq_rtmidi/include/midi_info.hpp \ ./seq_rtmidi/include/midi_jack_data.hpp \ ./seq_rtmidi/include/rtmidi_info.hpp \ ./seq_rtmidi/include/mastermidibus_rm.hpp \ ./seq_rtmidi/include/midi_alsa.hpp \ ./seq_rtmidi/include/rtmidi_types.hpp \ ./seq_rtmidi/src/rtmidi_info.cpp \ ./seq_rtmidi/src/rtmidi.cpp \ ./seq_rtmidi/src/midi_jack_info.cpp \ ./seq_rtmidi/src/midi_api.cpp \ ./seq_rtmidi/src/midi_alsa.cpp \ ./seq_rtmidi/src/midi_jack.cpp \ ./seq_rtmidi/src/midibus.cpp \ ./seq_rtmidi/src/rtmidi_types.cpp \ ./seq_rtmidi/src/midi_alsa_info.cpp \ ./seq_rtmidi/src/midi_probe.cpp \ ./seq_rtmidi/src/mastermidibus.cpp \ ./seq_rtmidi/src/midi_info.cpp #***************************************************************************** # seq66/doc/dox/seq_rtmidi.cfg #----------------------------------------------------------------------------- # vim: ts=3 sw=3 et ft=cfg #----------------------------------------------------------------------------- ================================================ FILE: doc/latex/Makefile.am ================================================ #******************************************************************************* # Makefile.am (latex) #------------------------------------------------------------------------------- ## # \file Makefile.am # \library doc/latex # \author Chris Ahlstrom # \date 2020-10-02 # \update 2020-11-25 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Makefile for the seq66/doc/latex sub-project. # #------------------------------------------------------------------------------- #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 SUBDIRS = tex #****************************************************************************** # Makefile.am (doc/latex) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 noet ft=automake #------------------------------------------------------------------------------ ================================================ FILE: doc/latex/README ================================================ README for Seq66 LaTeX Chris Ahlstrom 2019-08-13 to 2019-08-13 This directory holds the documentation for Seq66. The documentation is part of the project, and not a separate project. In addition, some basic documentation is not transferred from sequencer64-doc to here. Thus, sometimes we will refer the reader to that project for background information. # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: doc/latex/images/README ================================================ README for Seq66 Images Chris Ahlstrom 2019-08-13 to 2019-08-13 This directory holds the images used in the documentation for Seq66. # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: doc/latex/tex/Makefile.am ================================================ #******************************************************************************* # Makefile.am #------------------------------------------------------------------------------- ## # \file Makefile.am # \library Documents (doc/latex) # \author Chris Ahlstrom # \date 2020-10-02 # \update 2022-01-03 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Makefile for LaTeX-based documentation. It now uses latexmk (a # well-known Perl script) instead of using pdflatex and makeindex # directly. # #------------------------------------------------------------------------------- #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = *.tex #****************************************************************************** # Install directories #------------------------------------------------------------------------------ prefix = @prefix@ datadir = @datadir@ docdir = @docdir@ datarootdir = @datarootdir@ seq66docdir = @seq66docdir@ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ .PHONY: clean all: manual manual: tex-stamp latexmk -g -silent -pdf seq66-user-manual cp seq66-user-manual.pdf $(top_srcdir)/data/share/doc tex-stamp: touch tex-stamp # Or -C instead? clean: clean-dvi clean-pdf clean-ps latexmk -c rm -f *.4tc rm -f *.xref rm -f tex-stamp clean-dvi: -rm -f *.dvi clean-pdf: -rm -f *.pdf clean-ps: -rm -f *.ps #****************************************************************************** # Makefile.am (doc/latex) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 noet ft=automake #------------------------------------------------------------------------------ ================================================ FILE: doc/latex/tex/alsa.tex ================================================ %------------------------------------------------------------------------------- % alsa %------------------------------------------------------------------------------- % % \file alsa.tex % \library Documents % \author Chris Ahlstrom % \date 2021-06-16 % \update 2025-07-27 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides the ALSA page section of seq66-user-manual.tex. % %------------------------------------------------------------------------------- \section{ALSA} \label{sec:alsa} This section describes some details concerning the ALSA support of \textsl{Seq66}. Currently, it just includes some tricks that might be useful, plus some clarification. \subsection{ALSA / Through Ports} \label{subsec:alsa_through_ports} When running the commands \texttt{aplaymidi -l} or \texttt{arecordmidi -l}, one sees something like the following. Notice that there is only one Thru port. \begin{verbatim} Port Client name Port name 14:0 Midi Through Midi Through Port-0 28:0 nanoKEY2 nanoKEY2 MIDI 1 . . . \end{verbatim} Note that "14:0" consists of the ALSA client ID, 14, and the port ID of the "Midi Through" pseudo-device, 0. Note the spelling, "Midi". The MIDI Through port is useful for...? The MIDI Through port is claimed to be "everything-proof". Why...? They are virtualized hardware MIDI loopbacks that make it so programs that only output to "hardware" ports can use them to control and sequence other programs in an ALSA or JACK session. Also see how to increase the number of ALSA MIDI Thru ports in \cite{alsathru}. \textbf{Warnings}: \index{linux!system MIDI Thru} \index{MIDI Thru} One minor issue with the system MIDI Thru port is that, if it and another input port (e.g. VMPK) are both enabled, then each note emitted by VMPK is doubled. Be aware. Another issue is that, with a MIDI Through output port and a MIDI Through input port enabled at the same time, that, when recording and playing a pattern at the same time, the existing notes get sent to the Through output and are received on the Through input, where they are recorded again; this eventually leads to an "input FIFO overrun" message. This problem also occurs if one has set up an active 'ctrl' using the MIDI Through ports for control \textsl{and} display, \textsl{and} has set a non-empty \texttt{startup} macro. The macro goes out the display port and is received on the control port, and chaos ensues, including a likely application crash. A warning pops up if both in and out are set to a "Midi Through" port. If not changed, at the next startup this situation is detected and the control/display sections of the 'ctrl' file are disabled. \index{MIDI Thru!warning} \textsl{Never use the ALSA MIDI Through port for both control and display. Use an actual device port.} \begin{quote} "ALSA always creates 1 MIDI through port. Since I work with Windows music applications via Wine, and because MIDI through ports are everything-proof, how can I increase the amount of MIDI through ports created by ALSA?" \end{quote} To add more Thru ports, first use \texttt{modinfo} to see information about the kernel module \texttt{snd-seq-dummy}. Part of the output is shown here: \begin{verbatim} $ /sbin/modinfo snd-seq-dummy filename: /lib/modules/5.7-amd64/kernel/sound/core/seq/snd-seq-dummy.ko alias: snd-seq-client-14 description: ALSA sequencer MIDI-through client author: Takashi Iwai name: snd_seq_dummy parm: ports:number of ports to be created (int) parm: duplex:create DUPLEX ports (bool) \end{verbatim} Edit the following file (create it if necessary) to add a line to change the number of Thru ports. We use the '\#' prompt to indicate root access or usage of \texttt{sudo}. Save it. No need to reboot, this is \textsl{Linux}. Just remove and reinsert the module with the "mod" commands, as root: \begin{verbatim} # vi /etc/modprobe.d/alsa-base.conf options snd-seq-dummy ports=2 # rmmod snd_seq_dummy # modprobe snd_seq_dummy \end{verbatim} Then listing the ports will show: \begin{verbatim} Port Client name Port name 14:0 Midi Through Midi Through Port-0 14:1 Midi Through Midi Through Port-1 28:0 nanoKEY2 nanoKEY2 MIDI 1 . . . \end{verbatim} This will, of course, throw off the \textsl{Seq66} port numbering, unless one has implemented port-mapping (\sectionref{sec:port_mapping}). (But remember that using virtual ports will disable port-mapping.) \subsection{ALSA / Virtual MIDI Devices} \label{subsec:alsa_virtual_midi_devices} The "manual" ports of \textsl{Seq66} are "virtual" ports. From \textsl{The Linux MIDI-HOWTO} \cite{midihowto}: \begin{quote} MIDI sequencers like to output their notes to MIDI devices that normally route their events to the outside world, i.e., to hardware synths and samplers. With virtual MIDI devices one can keep the MIDI data inside the computer and let it control other software running on the same machine. This HOWTO describes all that is necessary to achieve this goal. \end{quote} While \textsl{Seq66} can auto-connect (if enabled) to other software such as \textsl{qsynth}) without using virtual ports, virtual MIDI ports can be used to connect several other MIDI applications running on the same computer, to allow one to connect standalone applications: DAWs, live-coding environments (Sonic-Pi, Supercollider, Pure Data), or visualization software. To use ALSA's virtual MIDI the \texttt{snd-card-virmidi} module must be present. \begin{verbatim} # Configure support for OSS /dev/sequencer and /dev/music (/dev/sequencer2) # (Takashi Iwai: unnecessary to alias beyond the first card, i.e., card 0) alias sound-service-0-1 snd-seq-oss alias sound-service-0-8 snd-seq-oss # Configure card 1 (second card) as a virtual MIDI card sound-slot-1 snd-card-1 alias snd-card-1 snd-virmidi \end{verbatim} A small, but useful application to connect ALSA ports is \texttt{aconnectgui}. Also see the article about ALSA and JACK MIDI \cite{midilinux}. \subsection{ALSA / Trouble-Shooting} \label{subsec:alsa_testing} This section describes some trouble-shooting that can be done with ALSA. \subsubsection{ALSA / Trouble-Shooting / ALSA Breakage} \label{subsubsec:alsa_testing_alsa_breakage} We have seen ALSA MIDI support break, perhaps due to a system update, on \textsl{Arch Linux}. The first symptom is a prompt when \textsl{Seq66} starts: \begin{verbatim} Error. Creating master bus failed; check MIDI drivers or reboot \end{verbatim} One can verify that it is not a \textsl{Seq66} issue by running \texttt{aplaymidi} or \texttt{arecordmidi}: \begin{verbatim} $ aplaymidi -l ALSA lib seq_hw.c:529:(snd_seq_hw_open) open /dev/snd/seq failed: No such device Cannot open sequencer - No such device \end{verbatim} If the command \texttt{modprobe snd\_seq} run as \texttt{root} does not work, a reboot will. \subsubsection{ALSA / Trouble-Shooting / MIDI Clock} \label{subsubsec:alsa_testing_midi_clock} MIDI clock is a signal broadcast to ensure several MIDI-enabled synthesizers or sequencer stay in synchronization. Clock events are sent at a rate of 24 pulses per quarter note. They maintain a synchronized tempo for synthesizers that have BPM-dependent voices, and for arpeggiator synchronization. \paragraph{ALSA / Trouble-Shooting / MIDI Clock / Send} \label{paragraph:alsa_testing_midi_clock_send} To verify that \textsl{Seq66} sends MIDI clock, the easiest way in ALSA is to run the following command and set \textsl{Seq66} to send MIDI Clock to the \texttt{aseqdump} port that appears after restarting \textsl{Seq66}: \begin{verbatim} $ aseqdump Waiting for data at port 129:0. Press Ctrl+C to end. Source Event Ch Data 0:1 Port subscribed 128:0 -> 129:0 \end{verbatim} Once set up, start playback on \textsl{Seq66}. The result should be a never-ending stream of rapid MIDI clock messages. \paragraph{ALSA / Trouble-Shooting / MIDI Clock / Receive} \label{paragraph:alsa_testing_midi_clock_receive} To verify that \textsl{Seq66} receives MIDI clock in ALSA, use a sequencer that can emit MIDI Clock in ALSA. The precursor to \textsl{Seq66}, \textsl{Seq24}, can be used, as well as \textsl{Seq66}. First, run one of the following commands: \begin{verbatim} $ seq24 -m $ qseq66 --alsa --manual --verbose \end{verbatim} This creates a bunch of virtual ALSA MIDI ports. In the \textbf{MIDI Clock} for either application, select the \textbf{On} option for the first port. Then run \textsl{a debug version of Seq66} in a terminal window with the following options: \begin{verbatim} $ qseq66 --alsa --auto-ports --verbose --client-name seq66debug \end{verbatim} This sets up to auto-connect ALSA MIDI ports. In \textbf{Edit / Preferences / MIDI Input}, check-mark the first "seq24" port. There should be no need to restart. Then start playback in \textsl{Seq24}. \textsl{Seq66} should display a rapid stream of MIDI Clock messages. If not, check the setup and, if correct, report a bug! \paragraph{VMPK Issues} \label{paragraph:alsa_testing_vmpk_issues} \index{VMPK} On our \textsl{Ubuntu 18} system, the \textsl{VMPK} virtual keyboard (version to be determined) has issues: \begin{verbatim} Seq66: input FIFO overrun VMPK: RtMidiOut::sendMessage: error sending MIDI message to port. RtMidiIn::alsaMidiHandler: unknown MIDI input error! \end{verbatim} In addition, we find a massive spewage of events in the pattern. This does not occur on our \textsl{Ubuntu 20} system with \textsl{VMPK v. 0.7.2}. However, with that version, one must go to its \textbf{Edit / Preferences} dialog and make sure that MIDI I/O is enabled and that both input and output are set to ALSA. Another issue occurred when we tried testing with multiple instances of VMPK. Both would appear in the MIDI Inputs list, with the same name but different client numbers. However, input would come in on the same buss number, no matter which VMPK instance was played. Thus, it's basically useless to run more than one VMPK instance. See the \textsl{VMPK} website \cite{vmpk}. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/concepts.tex ================================================ %------------------------------------------------------------------------------- % concepts %------------------------------------------------------------------------------- % % \file concepts.tex % \library Documents % \author Chris Ahlstrom % \date 2015-11-01 % \update 2025-05-15 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides some concepts and terms needed to understand Seq66. % %------------------------------------------------------------------------------- \section{Concepts} \label{sec:concepts} The \textsl{Seq66} program is a loop-player machine with a number of interfaces. This section is useful to present some concepts and definitions of terms as they are used in \textsl{Seq66}. Various terms have been used over the years to mean the same thing (e.g. "sequence", "pattern", "loop", "track", and "slot"), so it is good to clarify the terminology. \subsection{Concepts / Reload Session} \label{subsec:concepts_reload_session} While \textsl{Seq66} is flexible, changes in devices setups and other settings generally required the user to \textsl{reload session}. This reload can be done in two ways. \begin{itemize} \item \textbf{Manual Restart} Once a setting is changed, or a new arrangement of devices occurs, exit \textsl{Seq66}, examine or edit the various configuration files (if desired), and start \textsl{Seq66} again. \item \textbf{Edit / Preferences / Restart Seq66}. When settings in \textbf{Edit / Preferences} are changed, the \textbf{Reload Session} button becomes enabled. Click it, and the result is basically like restarting \textsl{Seq66}. \end{itemize} Note that some changes in settings will not take effect on this restart. For example, a style-sheet, if specified, is loaded when the application is created, not in the restartable loop. The solution is to exit \textsl{Seq66} manually and then start it again. In the future (version 2?), we will make \textsl{Seq66} able to better detect and reconfigure for system changes and preferences changes. \subsection{Concepts / Terms} \label{subsec:concepts_terms} This section doesn't provide comprehensive coverage of terms. It covers terms that might be puzzling. \subsubsection{Concepts / Terms / loop, pattern, track, sequence} \label{subsubsec:concepts_terms_loop} \index{loop} \index{pattern} \index{sequence} \textsl{Loop} is a synonym for \textsl{pattern}, \textsl{track}, or \textsl{sequence}; the terms are used interchangeably. Each loop is represented by a box (pattern slot) in the Pattern (main) window, also known as the "Patterns Panel" or "Live Grid".. A loop is a unit of melody or rhythm extending for a small number of measures (in most cases). Each loop is represented by a box in the patterns panel. Each loop is editable. All patterns can be layed out in a particular arrangement to generate a more complex song. \index{slot} A \textsl{slot} is a box in a pattern grid that holds a loop. Note that other sequencer applications use the term "sequence" to apply to the complete song, and not just to one track or pattern in the entire song. \subsubsection{Concepts / Terms / armed, muted} \label{subsubsec:concepts_terms_armed} \index{armed} An armed sequence is a MIDI pattern that will be heard. "Armed" is the opposite of "muted", and the same as "unmuted". An unarmed sequence will not be heard, and it has a normal background. Performing an \textsl{arm} operation in \textsl{Seq66} means clicking on an "unarmed" sequence in the patterns panel (the main window of \textsl{Seq66}). When the sequence is \textsl{armed}, it will be heard, and it has a more noticeable background. A sequence can be armed or unarmed in many ways: \begin{itemize} \item Clicking on a sequence/pattern box. \item Pressing the hot-key for that sequence/pattern box. \item Setting up a mute-group containing that pattern, and then activating it with its hot-key. \item Opening up the Song Editor and starting playback; the sequences arm/unarm depending on the layout of the sequences and triggers in the piano roll of the Song Editor. \item Using a MIDI control, as configured in a 'ctrl' file, to toggle the armed status of a pattern or select a mute-group. \end{itemize} \subsubsection{Concepts / Terms / bank, screenset, play-screen} \label{subsubsec:concepts_terms_bank} \index{screenset} A \textsl{screenset} is a set of patterns that fit within the \textbf{4 x 8} grid of loops/patterns in the patterns panel. \textsl{Seq66} supports multiple screens sets, up to 32 of them, and a name can be given to each for clarity. Some other sizes, such as \textbf{8 x 8} and \textbf{12 x 8}, are partly supported. \index{bank} The term "bank" is \textsl{Kepler34}'s name for "screenset". The term "scenes" from \textsl{Ableton Live} is very similar as well. \index{play screen} By default, only one set is active and playing at a time. This set is informally termed the "play screen". There are options to let more than one set play at once. \subsubsection{Concepts / Terms / buss, bus, port} \label{subsubsec:concepts_terms_buss} \index{bus} \index{buss} A \textsl{buss} (also spelled "bus" these days, see \url{https://en.wikipedia.org/wiki/Busbar}) is an entity onto which MIDI events can be placed, in order to be heard or to affect the playback, or into which MIDI events can be received, for recording. \textsl{Buss} is just another name for port. \textsl{Seq66} can also perform some mapping of I/O ports for a more flexible studio setup. \subsubsection{Concepts / Terms / performance, song, trigger} \label{subsubsec:concepts_terms_performance} In the jargon of \textsl{Seq66}, a \index{performance} \index{song} \textsl{performance} or \textsl{song} is an organized collection of patterns that play a full tune automatically. This layout of patterns is created using the song editor, sometimes called the "performance editor". This window controls the song playback in "Song Mode" (as opposed to "Live Mode"). \index{trigger} The playback of each track is controlled by a set of triggers created for that track. A \textsl{trigger} is indicates when a sequence/pattern/loop should be played, and how much of the sequence (including repeats) should be played. A song performance consists of a number of sequences, each triggered as the musician laid them out. \subsubsection{Concepts / Terms / Auto-step, Step-Edit} \label{subsubsec:concepts_terms_auto_step} \index{auto-step} \index{step-edit} Auto-step (step-edit) provides a way to add notes easily when a pattern is not playing. It works in two ways. In the first way, when drawing notes on the pattern-editor's piano roll, dragging the mouse automatically inserts notes of the configured length at intervals of the configured snap. In the second way, incoming MIDI notes (including chords) are automatically logged at the given snap interval, with a length a little less than the configured snap interval. There is also a one-shot step-edit mode that is useful for recording a drum pattern emitted by a machine and then stopping. \subsubsection{Concepts / Terms / export} \label{subsubsec:concepts_terms_export} \index{export} An \textsl{export} in \textsl{Seq66} is a way of writing a song-performance to a more standard MIDI file, so that it can be played exactly by other sequencers. An export-song operation collects all of the unmuted tracks that have performance information (triggers) associated with them, and creates one larger trigger for each track, repeating the events as indicated by the original performance. MIDI can also be exported to SMF 0 format. \subsubsection{Concepts / Terms / flattening} \label{subsubsec:concepts_terms_flattening} \index{flattening} "Flattening" describes consolidating all of the events reproduced in a track via triggers into a single monolithic track (with a single trigger) covering the whole track. \subsubsection{Concepts / Terms / group, mute-group} \label{subsubsec:concepts_terms_group} \index{group} \index{mute-group} A \textsl{mute-group} in \textsl{Seq66} is a set of patterns, that can arm/disarm all together. Every group contains all sequences in the active screen set. This concept is similar to mute/unmute groups in hardware sequencers. Mute-groups can be stored in the MIDI file or in a 'mutes' file. Each mute-group is associated with a keystroke or a MIDI control. \subsubsection{Concepts / Terms / play-set} \label{subsubsec:concepts_terms_playset} \index{play-set} A \textsl{play-set} is the current set of patterns that are available for playing. Normally, this is comprised of all the active the patterns in the current bank (play-screen), but \textsl{Seq66} can be configured (see \sectionref{subsec:configuration_rc}) to include patterns from other sets. For example, one can have each newly selected set get added to the play-set without removing the previous set's patterns from the play-set. \subsubsection{Concepts / Terms / PPQN, pulses, ticks, clocks, divisions} \label{subsubsec:concepts_terms_pulses} \index{pulses} The concept of "pulses per quarter note", or PPQN, is very important for MIDI timing. To make it a bit more confusing, sometimes these pulses are referred to as "ticks", "clocks", and "divisions". To make it even more confusing, there are separate timing concepts to understand, such as "tempo", "beats per measure", "beats per minute", and "MIDI clocks". And, when JACK is involved, one must remember that JACK "ticks" come at 10 times the rate of MIDI ticks. A full description of all these terms, and how they are calculated, is beyond the scope of this document. \subsubsection{Concepts / Terms / queue, keep queue, snapshot, oneshot} \label{subsubsec:concepts_terms_queue_mode} The concepts here refer to playback, not recording. The term "one-shot" in playback is differnt from the same term in "recording". To "queue" a pattern means to ready it for playback on the next repeat of a pattern. A pattern can be armed immediately, or it can be queued to play back the next time the pattern restarts. Pattern toggle occurs at the next play of the pattern, rather than being set immediately. A set of queued patterns can be temporarily stored, so that a different set of playbacks can occur, before the original set of playbacks is restored. \index{keep queue} \index{queue!keep} The "keep queue" functionality allows the queue to be held without holding down a button the whole time. Once this key is pressed, then the hot-keys for any pattern can be pressed, over and over, to queue each pattern. \index{snapshot} A \textsl{Seq66} \textsl{snapshot} is a briefly preserved state of the patterns. One can press a snapshot key, change the state of the patterns for live playback, and then release the snapshot key to revert to the state when it was first pressed. (One might call it a "revert" key.) \index{oneshot} \index{oneshot!playback} \index{one-shot} \index{one-shot!playback} \index{playback!one-shot} \textsl{Seq66} \textsl{one-shot playback} is a way to enable a pattern to be queued and then played only once. For recording, \index{oneshot} \index{oneshot!recording} \index{one-shot} \index{one-shot!recording} \index{recording!one-shot} \textsl{Seq66} \textsl{one-shot recording} is a way to record notes from a MIDI device with automatic stepping and snapping, which terminates at the end of the specified length of the pattern. \subsubsection{Concepts / Terms / trigger} \label{subsubsec:concepts_terms_trigger} \index{trigger} A "trigger" is box in the song editor that specifies the playing of the whole or part of a pattern. For example, a 1-measure pattern can be replicated with multiple triggers to play at various times in the song. A trigger has start and ending times, a time offset, and optional transposition value. \subsection{Concepts / Sound Subsystems} \label{subsec:concepts_sound_subsystems} \subsubsection{Concepts / Sound Subsystems / ALSA} \label{subsubsec:concepts_sound_alsa} \textsl{ALSA} is a audio/MIDI system for \textsl{Linux}, with components built into the \textsl{Linux} kernel. It is the main subsystem used by \textsl{Seq66}. It supports virtual port connections via the \texttt{aconnect} program. The name of the library used to build \textsl{ALSA} projects is \texttt{libasound} \cite{alsa}. \subsubsection{Concepts / Sound Subsystems / PortMIDI} \label{subsubsec:concepts_sound_portmidi} \textsl{PortMIDI} is a cross-platform API (applications programming interface) for MIDI refactored for \textsl{Seq66}. It is used in the "portmidi" C++ modules, and provides support for \textsl{Seq66} in \textsl{Microsoft Windows} (and potentially \textsl{Mac OSX}). See reference \cite{portmidi} for the PortMIDI home page; our version cuts out code that requires \textsl{Java}. \subsubsection{Concepts / Sound Subsystems / JACK} \label{subsubsec:concepts_sound_jack} \textsl{JACK} is a cross-platform API and infrastructure (with an emphasis on \textsl{Linux}) to make it easier to connect and reroute MIDI and audio event between various applications and hardware ports. It should be preferred over \textsl{ALSA}, and is selected automatically if running. It supports virtual port connections via the \texttt{qjackctl} program or the \textsl{Non Session Manager}. See reference \textsl{JACK Audio Connection Kit} \cite{jack}. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/configuration.tex ================================================ %------------------------------------------------------------------------------- % configuration %------------------------------------------------------------------------------- % % \file configuration.tex % \library Documents % \author Chris Ahlstrom % \date 2021-01-18 % \update 202510-266 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides the configuration information. % %------------------------------------------------------------------------------- \section{Seq66 Configuration} \label{sec:configuration} \textsl{Seq66} configuration has become more elaborate with time, with more configuration files. Configuration items are well documented in the \textsl{Seq66} "man" page and in the configuration files. Therefore, this new discussion will merely summarize the options and go into a few details about the configuration files. Here are the topics to discuss: \begin{itemize} \item \textbf{Command-line Options}. Useful with desktop shortcuts. \item \textbf{'rc' File}. Mostly I/O port options, JACK options, recent files. Also now specifies other files to be used, and whether they are active or not. \item \textbf{'usr' File}. Local names for busses and instruments, user-interface settings, sessions... Some options that fit either the GUI or the command-line application could be moved to the 'rc' file. \item \textbf{'ctrl' File}. The keystroke and MIDI control settings have been moved to a separate file for flexibility. \item \textbf{'mutes' File} The mute-groups settings have been moved to a separate file for flexibility. \item \textbf{'drums' File}. This file supports remapping percussion notes from older drum machines to General MIDI. \item \textbf{'playlist' File}. This new file specifies a file that contains one or more playlists and MIDI controls for them. \item \textbf{'palette' File}. This new file allows replacing the default piano-roll, time, data, and events drawing to be tailored separately from the Qt theme. \item \textbf{'qss' File}. Qt style-sheets can now tailor the appearance of Qt theme-drawn elements (e.g. the pattern-grid buttons and text controls). \end{itemize} After the first run and exit of \textsl{Seq66}, it generates a set of default configration files in the default \textsl{configuration} directory: \begin{verbatim} /home/user/.config/seq66/qseq66.rc /home/user/.config/seq66/qseq66.usr /home/user/.config/seq66/qseq66.ctrl /home/user/.config/seq66/qseq66.mutes /home/user/.config/seq66/qseq66.drums /home/user/.config/seq66/qseq66.playlist /home/user/.config/seq66/qseq66.palette \end{verbatim} The palette file is not automatically generated. It can be saved from the \textbf{Edit / Preferences / Display} tab. There are also samples installed in the \texttt{data/samples} directory. The style-sheet file can be specified in the 'rc' file's \texttt{[style-sheet-file]} section. There are also some 'keymap' files. They are not yet used, but may become a feature of \textsl{Seq66} in the future. For \textsl{Microsoft Windows}, the default base name of the files is \texttt{qpseq66}, and the default configuration directory is \begin{verbatim} C:/Users/user/AppData/Local/seq66/ \end{verbatim} When running \textsl{Seq66} from the \textsl{Non Session Manager} (Linux-only, see \sectionref{sec:sessions}), the configuration directory is automatically set to something like \begin{verbatim} /home/user/NSM Sessions/MySession/seq66.nRSIQ/config \end{verbatim} There is no palette file by default, but the user can create one. The color palettes are discussed in \sectionref{sec:palettes}. Other files contain the the data for remote MIDI control, computer keyboard control, MIDI clock, JACK transport, and a many other settings. Some of the settings can be modified in the \textbf{Edit / Preferences} dialog, or overridden from the command line. \textsl{Seq66} overwrites the most of these files upon exiting. One must therefore quit \textsl{Seq66} before making manual modifications to these files. \subsection{Configuration File Commonalities} \label{subsec:configuration_file_commonalities} All of the \textsl{Seq66} configuration files have the following in common: \begin{itemize} \item \textbf{[Seq66] Section} \item \textbf{[comments] Section} \item \textbf{Numeric Settings} \item \textbf{Boolean Settings} \item \textbf{Variables} \item \textbf{Stanzas} \end{itemize} Generally, each configuration file has its own specific set of sections, each section-name being enclosed in square brackets in a very strict format: No spaces inside the square brackets. Sections are looked up by this name, including the square brackets, and the name must be exact. \subsubsection{[Seq66] Section} \label{subsec:configuration_common_seq66_section} This section is informational. At a minimum, it holds two variables: \begin{itemize} \item \texttt{config-type}. This value indicates the type of the file, such as "ctrl" or 'rc'. \item \texttt{version}. This value indicates the version of the file. It is used in some cases when we have added a feature to the configuration file that cannot be automatically handled. When the internal version number of the file is incremented, it can be used to make adjustments for changes in configuration when reading older versions of files. \end{itemize} This section may also contain additional "global" variables specific to a given \texttt{config-type}. \subsubsection{[comments] Section} \label{subsec:configuration_common_comments_section} The "comments" section is also informational, but the user can edit this section to include information describing the purpose of the file. For example, a 'ctrl' file for a \textsl{Novation Launchpad} (see \sectionref{sec:launchpad_mini}) might describe the purpose of this file. The comments stop at the first blank (not even spaces) line. To skip a line in the comment, put a single space character on the blank line. \subsubsection{Numeric Settings} \label{subsec:configuration_common_numeric_settings} Numeric settings consist of a line containing one or more numbers, usually preceded by an explanatory comment, and followed by a standard script comment. \begin{verbatim} output-port-count = 8 # number of ports \end{verbatim} We have been replacing the original format of settings with the \texttt{name = value} style shown above. Some old-style settings still exist, such as the following from the port and port-mapping settings in the 'rc' file: \begin{verbatim} 4 # number of MIDI input (or control) buses 0 1 "[0] 0:1 system:ALSA Announce" \end{verbatim} \subsubsection{Boolean Settings} \label{subsec:configuration_common_boolean_settings} Boolean settings are the same as numerical settings, but have only two values: "false" or "true". \begin{verbatim} record-by-buss = false # route MIDI event to slot by buss number \end{verbatim} Some old-style settings still exist, such as the following from the port and port-mapping settings in the 'rc' file: \begin{verbatim} 1 # map is active \end{verbatim} \subsubsection{Variables} \label{subsec:configuration_common_variables} Variable are a new style of value setting, and can encompass not only booleans and numeric values, but string values, which may correspond to enumerated values in the source code. These values are specified by a section-name plus variable name/value pair. Here are some sample \texttt{name = value} pairs: \begin{verbatim} load-mute-groups = true save-mutes-to = both window-scale = 1.0 default-ppqn = 1920 directory = "/home/user/Seq66 files/midi" \end{verbatim} If the variable value is a string value that is the name of a file or directory or path, it must be surrounded with quotes, in case the path has spaces in it. \subsubsection{Stanzas} \label{subsec:configuration_common_stanzas} We have streamlined the control-file stanza by eliminating the "enabled" and "channel" columns, since they can be encoded in the event/status byte (e.g. 0x90) instead. Older versions of the 'ctrl' file will be upgraded automatically. A stanza in a \textsl{Seq66} configuration file consists of some data at the beginning, a set of values inside square brackets, and a comment at the end. The values inside the square brackets are numeric, and can be in decimal format, hexadecimal format, or binary "0/1" format. See \sectionref{subsubsec:configuration_ctrl_loop_control}, which describes the details of this layout. \begin{verbatim} 0 "1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 0 1 "q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 1 2 "a" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 2 3 "z" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 3 . . . \end{verbatim} \subsection{Command Line} \label{subsec:configuration_command_line} Command-line options are well-described in the \textsl{Seq66} "man" page. Here, we will present a brief note about each option, and, where applicable, a reference to the corresponding configuration file option. Here is the basic command line: \begin{verbatim} qseq66 [options list] [MIDI filename] \end{verbatim} \optionline{-h}{-{}-help} Display a list of all command-line options, then exit. \optionline{-V}{-{}-version} Display the program version, then exit. \optionline{-v}{-{}-verbose} During execution, write more information to the console. Useful for trouble-shooting. \optionline{-n}{-{}-nsm} Activate a simple simulation of the built-in NSM (\textsl{Non Session Manager}) support, for debugging. As of version 0.99.3, \textsl{Seq66} detects if its parent process is the \texttt{nsmd} daemon automatically (on Linux). \optionline{-T}{-{}-no-nsm} Ignore the NSM setting in the 'usr' file. ('T' for 'typical'). Not too useful at this time. \optionline{-X}{-{}-playlist [filename]} This option loads the given file-name as a play-list file. See \sectionref{sec:playlist}. \configref{rc}{playlist}{name} \configref{playlist}{playlist}{full} \optionline{-m}{-{}-manual-ports} \textsl{Seq66} won't attach the system's existing ALSA or JACK MIDI ports. Instead, it will create is own set of \textsl{virtual} input and output busses/ports. The default number of ports is 1 for input, and 16 for output, but these values can be changed in the 'rc' file. Manual ports disable port-mapping. \configref{rc}{manual-ports}{flag for manual/virtual ports} \optionline{-a}{-{}-auto-ports} \textsl{Seq66} will automatically attach to the system's existing ALSA/JACK ports. \configref{rc}{manual-ports}{flag for manual/virtual ports} \optionline{-r}{-{}-reveal-ports} \textsl{Seq66} will show the names of the ALSA/JACK ports that the system defines, rather than the names defined in the 'usr' configuration file. \optionline{-R}{-{}-hide-ports} \textsl{Seq66} will show the names of the ALSA port that the 'user' configuration file define, rather than the names defined by ALSA. \configref{rc}{reveal-ports}{flag for reveal ports} \configref{usr}{user-midi-bus-definitions}{number of user-defined busses} \optionline{-A}{-{}-alsa} \textsl{Seq66} will run with ALSA, even if JACK is running. This options is "sticky" (they are saved). \optionline{-b}{-{}-bus [buss]} Modifies the output buss number on \textsl{all} tracks when a MIDI file is read. Useful for testing or quick-and-dirty setup, and for adapting a playlist to route to a specific MIDI output. \optionline{-l}{-{}-client-name} Replaces the normal client-name, \texttt{seq66}, with the given name. Probably best to make sure "seq66" is part of the name, for clarity. Also note that, if active, the \textsl{Non Session Manager} will override this name, e.g. using something like \textsl{seq66.nXYZT}. \optionline{-q}{-{}-ppqn [ppqn]} Supports modifying the PPQN value of \textsl{Seq66}; it defaults to a value of 192. This setting is written into the MIDI file when it is saved. The PPQN value can range from 24 to 19200, or be set to 0 to use the PPQN from the loaded file. 960 PPQN at 120 BPM is about 2 time more accurate in timing than a MIDI port can handle. \configref{usr}{user-midi-settings}{midi\_ppqn}. \configref{usr}{user-session}{session} \optionline{-s}{-{}-show-midi} Dumps incoming MIDI to the screen. \optionline{-p}{-{}-priority} Runs at higher priority with a FIFO scheduler. % \optionline{n/a}{-{}-pass-sysex} % Passes any incoming SYSEX messages to all outputs. % Not yet fully implemented. \optionline{-k}{-{}-show-keys} Prints pressed key value. \optionline{-K}{-{}-inverse} Changes the color palette for the sequence editor and performance editor piano rolls. Also note that the palette is highly configurable. And there is also an option to use a Qt style-sheet. \configref{palette}{palette}{inverse} The following options can be configured in the 'rc' file. (But note that, since version \textsl{Seq66} 0.94.0, the format of that section of the file has been simplified. See \sectionref{subsubsec:configuration_rc_jack_transport}. \optionline{-t}{-{}-jack-midi} \textsl{Seq66} will run with JACK, for MIDI if JACK is running. \configref{rc}{jack-transport}{jack-midi = true} \optionline{-N}{-{}-no-jack-midi} \textsl{Seq66} will not run with JACK for MIDI, even if JACK is running. \configref{rc}{jack-transport}{jack-midi = false} \optionline{-W}{-{}-jack-connect} \textsl{Seq66} will connect automatically to JACK ports discovered on the system. This is the long-standing default with Seq66. \configref{rc}{jack-transport}{jack-connect = true} \optionline{-w}{-{}-no-jack-connect} \textsl{Seq66} will not automatically connect to other JACK ports. This option is useful if a session manager is in charge of the connections. This option is not available with the other MIDI engines. \configref{rc}{jack-transport}{jack-connect = false} \optionline{-S}{-{}-jack-slave} \optionline{-S}{-{}-jack-slave} \textsl{Seq66} will sync to JACK transport as a "slave", if \textsl{JACK} is running. \optionline{-j}{-{}-jack-transport} \index{deprecated} This option is the old, \textbf{deprecated} version of \texttt{-{}-jack-slave}. \configref{rc}{jack-transport}{transport-type = slave} \optionline{-g}{-{}-no-jack-transport} \textsl{Seq66} will not sync to JACK transport. This disables JACK transport if it had been enabled previously. \configref{rc}{jack-transport}{transport-type = none} \optionline{-J}{-{}-jack-master} \textsl{Seq66} will try to be JACK master. \configref{rc}{jack-transport}{transport-type = master} \optionline{-C}{-{}-jack-master-cond} JACK master will fail if there is already a master. \configref{rc}{jack-transport}{transport-type = conditional} \optionline{-M}{-{}-jack-start-mode [x]} When \textsl{Seq66} is synced to JACK, the following play modes are available: "live" = live mode; "song" = song mode, and "auto" means use song mode if triggers are present in the loaded MIDI file. "auto" is the default. \configref{rc}{jack-transport}{song-start-mode = auto} \optionline{-U}{-{}-jack-session-uuid [uuid]} Set the UUID for the JACK session. This value is useful when running \textsl{Seq66} from within a \textsl{JACK} session. The session controller (e.g. \textsl{QjackCtl}) uses this value in the command-line when starting \textsl{Seq66}. See \sectionref{subsec:sessions_jack}. \optionline{-0}{-{}-smf-0} Normally, SMF 0 (single-track MIDI) files are split into separate tracks when read into Seq66. This 'usr' option preserve the file as an SMF 0 file. \optionline{-u}{-{}-user-save} Save the 'usr' configuration file when exiting. Normally, it is saved only if not present in the configuration directory, so as not to get stuck with temporary settings such as the -{}-bus option. \configref{rc}{auto-option-save}{auto-save-rc} \optionline{-H}{-{}-home [directory]} Change the "home" configuration directory from \texttt{\$HOME.config/seq66} The configuration files are loaded from or saved to the specified directory. If the directory is a full path (such as \texttt{\textasciitilde/test/import}), then the directory is used as is. If the directory is a simple directory (such as \texttt{test} or \texttt{test/import}), then it is placed in the normal "home" configuration directory and is used to contain the configuration. \optionline{-c}{-{}-config basename} Use a different configuration file base name for the 'rc' and 'usr' files. For example, one can specify a full configuration for "testing", for "jack", or for "alsa", to set up \texttt{testing.rc} and \texttt{testing.usr}, \texttt{jack.rc} and \texttt{jack.usr}, \texttt{alsa.rc} and \texttt{alsa.usr}. But note that recent versions of \textsl{Seq66} use the 'rc' file to control the names and active state of all the other configuration files. \optionline{-f}{-{}-rc filename} Use a different 'rc' configuration file. It must be a file in \texttt{\$HOME/.config/seq66} directory or the directory specified by the \texttt{-{}-home} option. The \texttt{.rc} extension is added if necessary. \optionline{-F}{-{}-usr filename} Use a different 'usr' configuration file. Similar to the \texttt{-{}-rc} option. But note that this can be controlled in the 'rc' file as well. \optionline{-S}{-{}-session-tag section} Use one of the sections present in \texttt{\$HOME/.config/seq66/sessions.rc} to set up an alternate configuration. For details, see \sectionref{subsec:sessions_sessions_rc}. \optionline{-L}{-{}-locale localename} Set a different locale for running \textsl{Seq66}. If the current locale uses the comma as a decimal point, then try to use \texttt{en\_US.UTF-8} (for example). \optionline{-o}{-{}-option opvalue} Provides additional options, since the application is running out of single-character options. The \texttt{opvalue} set supported is: \begin{itemize} \item \texttt{daemonize} and \texttt{no-daemonize}. \index{daemonize} Sets up the \texttt{seq66cli} application to fork to the background the \textsl{next time} it is run. This is done by modifying the \texttt{seq66cli.usr} file to specify that \texttt{seq66cli} runs as a daemon. It is good to specify a log-file as well. \index{no-daemonize} undoes the setup of \texttt{seq66cli} daemonization. The application can then be run in a console so that log output can be seen. \item \texttt{log=filename.log}. \index{log} Reroutes standard error and standard output messages to a log-file located in the configuration directory. If this file is present, log information is appended. The default log-file name is specified in the \texttt{[user-options]} section of the 'usr' file. If using daemonization, it pays to also set up a log file with the same command. \item \texttt{sets=8x8}. \index{variset} This option, informally known as "variset", allow some changes in the set size and layout from the default 4x8 = 32 sets layout. Consider this option to be experimental. Expect problems (and please report them). To save these options to the 'usr' file, add the \texttt{-{}-user-save} option to the command line, or edit the 'usr' file before running \textsl{Seq66}. In that file, the options modified are \texttt{mainwnd\_rows} and \texttt{mainwnd\_cols}. \item \texttt{scale=WxH}. \index{scaling} This option scales the main window by the given factors, ranging from 0.5 to 3.0. A 1920x1080 screen can be completely filled via \texttt{-{}-option scale=2.175x1.75}. Even when scaled down, the user can use the mouse of window-manager keystrokes to shrink the window even further. Note that the other tabs in the main window do not adjust appropriately... this feature is meant for live usage of a touch-screen. \item \texttt{mutes=value}. Specifies the saving of mute-groups to the 'mutes' file, 'midi' file, or 'both' files. \item \texttt{virtual=o,i}. Set up the manual-ports option with 'o' output ports and 'i' input ports. \end{itemize} \subsection{'rc' File} \label{subsec:configuration_rc} \begin{verbatim} /home/user/.config/seq66/qseq66.rc \end{verbatim} The 'rc' configuration file has undergone a lot of changes, including off-loading the keyboard control, MIDI control, and mutes control sections into their own files, and adding a few "variable" settings. Rather than repeating information already present in the self-documenting 'rc' file, we will summarize the settings and refer the reader to the sample files for more information. The 'rc' file adds these \texttt{[Seq66]} options to the common data for all configuration files: \begin{verbatim} sets-mode = normal port-namng = short \end{verbatim} \index{sets-mode} The \texttt{sets-mode} option determines how patterns are muted when the play-screen (current set) changes. Its values are: \begin{itemize} \item \textbf{normal}. \index{sets-mode!normal} When moving to another set, the patterns in the current play-screen are muted, as are the patterns in the destination set. \item \textbf{autoarm}. \index{sets-mode!autoarm} Similar to normal, but the patterns in the destination set are automatically unmuted. \item \textbf{additive}. \index{sets-mode!additive} When moving to another set, the destination set is added to the play-set, so that both sets are playing. This option was requested by users. \item \textbf{allsets}. \index{sets-mode!allsets} When a song is loaded, all patterns in all sets are unmuted and will play. \end{itemize} \index{port-naming} The \texttt{port-naming} option determines how much detail is shown in port names as shown in the user-interface. Port-naming values are: \begin{itemize} \item \textbf{short}. \index{port-naming!short} The brief name is shown; this is just the bare device name. \item \textbf{pair}. \index{port-naming!pair} The ALSA client number and port are shown along with the device name. Example "128:0 VMPK Input" \item \textbf{long}. \index{port-naming!long} The internal number of the clients and ports are shown. These are a sequence of port numbers: 0, 1, 2, 3, etc. \end{itemize} For \textsl{Windows} (the "portmidi" build), the 'long' option works better. \textbf{Important}: Note that port-naming setting are ignored if port-mapping (\sectionref{sec:port_mapping}) is enabled. Note that port-mapping is now the default. The names used in port-mapping are shown as is. Note that changes in ports might require the user to edit the mapping sections of the 'rc' file. \subsubsection{'rc' File / MIDI Control} \label{subsubsec:configuration_rc_midi_control} \index{[midi-control-file]} \textsl{Seq66} offloads MIDI control to a separate file. Move or create the \texttt{[midi-control]} section to a separate file in the \textsl{Seq66} configuration directory, and add the following snippet: \begin{verbatim} [midi-control-file] active = true # true means to use and to rewrite at exit name = "qseq66.ctrl" # contains a [midi-control] section, and more \end{verbatim} As with the 'rc' file, the 'ctrl' file can be rewritten upon exit, \textsl{except} that it is not written unless it is \textsl{active}, or \textsl{does not exist} (as during the first run of \textsl{Seq66}). For the details of the 'ctrl' file, see \sectionref{subsec:configuration_ctrl}. \subsubsection{'rc' File / Mute Groups} \label{subsubsec:configuration_rc_mute_groups} The 'rc' file has been modified so that mute-group are now stored in a separate file. The following section specifies that file, which should be located in the session/configuration directory. \begin{verbatim} [midi-group-file] active = false name = "qseq66.mutes" # contains a [mute-groups] section \end{verbatim} The mutes-groups themselves are loaded from the 'mutes' file dependent on the \texttt{load-mute-groups} option in the mute-groups file. The mutes-groups section is written on exit dependent on the \texttt{save-mutes-to} option in the mute-groups file. It can go to the MIDI file, the 'mutes' file, or both. \subsubsection{'rc' File / Color Palette} \label{subsubsec:configuration_rc_color_palette} \begin{verbatim} [palette-file] active = false name = "qseq66.palette" \end{verbatim} The only need for a palette file is when not satisfied with the default palette for the patterns, inverse colors, hatching, or grid-lines for some pattern piano roll items. There is a button to save the current/default palette for later modification in the \textbf{Edit / Preferences / Display} tab. \subsubsection{'rc' File / Style Sheet} \label{subsubsec:configuration_rc_style_sheet} \index{style sheet} The \texttt{[style-sheet-file]} option, if active and non-empty, causes a user-designed style-sheet to be applied. This is useful in expanding the tab-sizes, making disabled text easier to read in some Qt themes, or fixing some minor faults in the current Qt theme. See \texttt{data/samples/textfix.qss} for an example. The style sheet file is assumed to reside in the normal \textsl{Seq66} configuration directory. % However, it can have a path component so that the same style sheet % can be used by many applications more readily. Here is an example using the style sheet file \texttt{data/samples/qseq66.qss}: \begin{figure}[H] \centering \includegraphics[scale=0.75]{main-window/main-window-stylesheet.png} \caption{Seq66 View with a Minimal Style-Sheet Applied} \label{fig:view_with_style_sheet_applied} \end{figure} Note the blue text fields. Also note the larger tabs, which could be useful on a touch-screen. There is a full-scale dark theme stored in \texttt{data/win/dark-theme.qss}. One might want to save and tweak the 'palette' file to match. The following is a more comprehensive example using the \texttt{incrypt-66.qss} style-sheet to control the appearance of \textsl{Qt}-drawn items, plus the \texttt{incrypt-66.palette} file to alter the colors of the drawn elements (grids and live/song backgrounds) that are not subject to style-sheet alterations. \begin{figure}[H] \centering \includegraphics[scale=0.50]{misc/incrypt-66.png} \caption{Enhanced "Incrypt" Style-Sheet} \label{fig:view_with_enhanced_incrypt_style_sheet} \end{figure} The following shows the \textbf{Song} tab and a sample \textbf{Preferences} tab using the same style-sheet and palette. \begin{figure}[H] \centering \includegraphics[scale=0.50]{misc/incrypt-66-2.png} \caption{Enhanced "Incrypt" Style-Sheet Part 2} \label{fig:view_with_enhanced_incrypt_style_sheet_2} \end{figure} The thing to note here is that we must limit the width of combo-boxes so that all elements in a horizontal bar can be seen. This has the unfortunate side-effect of showing only part of long port names (e.g. "Midi Through" in the figure above). Another interesting style-sheet in \texttt{data/samples} is \texttt{perstfix-66.qss} and its palette file, which is a nice blue theme. See the title page of this manual. Also check out \texttt{monogreen.qss} and its palette file if you're fond of the the green screen. \subsubsection{'rc' File / Note Mapper} \label{subsubsec:configuration_rc_note_mapper} \begin{verbatim} [note-mapper] active = false name = "GM_DD-11.drums" \end{verbatim} This file can be used transform the existing drum (non-transposable) tracks into another set of drum tracks. A lot of work has been done in the past with non-General-MIDI instruments (particularly consumer instruments like the \textsl{Yamaha PSS-780} or \textsl{Yamaha DD-11}. This option is useful for transformation older MIDI files into GM format. For the usage of the note-mapper, see \figureref{fig:pattern_editor_oneshot_recording}, and the surrounding discussion. \subsubsection{'rc' File / Patches (Program Changes)} \label{subsubsec:configuration_rc_patches} \begin{verbatim} [patches-file] active = false name = "PSS-790.patches" \end{verbatim} The built-in GM patch mapping provides for showing the instrument name when dragging a Program Change up or down. The patches-file allows one to show the instrument names for an old pre-GM MIDI synthesizer. See the file \texttt{PSS-790} in the \texttt{data/samples} directory. The usage of these instrument names is not yet universal in the user interface. More work to do. Note that one can also assign alternate instrument names and alternate controller values in the 'usr' file. At some point we might take on the usage of the MIDINAM format. \subsubsection{'rc' File / MIDI-Clock Section} \label{subsec:configuration_rc_midi_clock} \index{[midi-clock]} The MIDI Clock tab contains the clocking state from the last time \textsl{Seq66} was run, their status, and their names. It also specifies the MIDI output ports, which can be disabled. Disable the port with a -1, turn off the clock with a 0, or turn it on with a 1 (which means to send \textbf{MIDI Song Position}, and \textbf{MIDI Continue} if starting after tick 0), or on with positioning with a 2, which sends \textbf{MIDI Start} and then begins clocking after the position reaches a modulo of the \textbf{Clock Start Modulo value}. Luckily, the user-interface makes it easy to select the desire value, and has tool-tips to instruct the user. This configuration item is represented in the \textbf{Edit / Preferences / MIDI Clock} tab. \begin{verbatim} [midi-clock] 5 # number of MIDI clocks (output busses) 0 0 "[0] 14:0 Midi Through Port-0" 1 0 "[1] 128:0 TiMidity port 0" 2 0 "[2] 128:1 TiMidity port 1" . . . \end{verbatim} If there are USB MIDI devices plugged in (on \textsl{Linux} using JACK), they will show up with system names that have no meaning to the user. In this case, an "alias" looked up by JACK is appended as a comment in the 'rc' files, and also shows up in the port lists in the user interface. For example: \begin{verbatim} 0 0 "[0] 0:0 system:midi_capture_1" # Midi-Through 1 1 "[1] 0:1 seq66:system midi_capture_5" # Launchpad-Mini 2 0 "[2] 0:2 system:midi_capture_3" # USB-Midi 3 0 "[3] 0:3 system:midi_capture_4" # nanoKEY2 \end{verbatim} Note the difference in naming of the enabled port (Launchpad-Mini). The appended label is \textsl{not} part of the port name; it is there simply to help the user select the correct system port. This same setup applies to MIDI input ports as well. Note that the 'rc' file has a port-mapping option, described elsewhere. Ports created by \texttt{a2jmidid --export-hw} do not have JACK aliases. Ports created by Seq66 do not have JACK aliases. Ports created by MIDI applications such as \textsl{Qsynth} do not have JACK aliases. Ports created by ALSA or \textsl{Windows} do not have aliases. On some recent \textsl{Linux} systems, these USB system ports can be accessed without exposing the ports via \texttt{a2jmidid}, since JACK does it. One can see all the aliases on a JACK setup with the following command: \begin{verbatim} $ jack_lsp --aliases \end{verbatim} For output port-mapping, which is now the default for \textsl{Seq66}, see \sectionref{subsec:output_port_mapping}. \subsubsection{'rc' File / MIDI Clock Mod Ticks} \label{subsubsec:configuration_rc_midi_cmt} \index{[midi-clock-mod-ticks]} This configuration item is the same as the \textbf{Edit / Preferences / MIDI Clock / Clock Start Modulo} option. \begin{verbatim} [midi-clock-mod-ticks] ticks = 64 record-by-buss = false record-by-channel = false \end{verbatim} The record-by-buss flag, if true, causes recording to route events to the first pattern that specifies an input buss number matching the buss number of the event. The record-by-channel flag, if true, causes recording to route events to the first recording pattern that has an output channel matching the event's MIDI channel. These two options can be set in the \textbf{Edit / Preferences / MIDI Input} tab. To use these features, the user must \begin{enumber} \item Provide an existing pattern. \item Set its input buss or its output channel as desired. \item Enable recording for that pattern. \end{enumber} To facilitate this operation, a MIDI file \texttt{data/midi/16-blank-patterns.midi} is provided, with output channels already set. Make a copy of it and start with that copy. Be aware that events will go only into patterns for which recording has been enabled. Also be aware that record-by-buss and record-by-channel are mutually exclusive. See \sectionref{sec:recording}, which details how the record-by features work. \subsubsection{'rc' File / MIDI File Tweaks} \label{subsubsec:configuration_rc_midi_file_tweaks} This section provides some modifications to how MIDI events are handled, specifically, at present, in regard to the failures of handling running status. \begin{verbatim} [midi-file-tweaks] running-status-action = recover \end{verbatim} The options available are: \begin{itemize} \item \textbf{recover}. Tries to recover the running status when a data byte is encountered. The previous running status byte is saved, and then used when a data byte is encountered. This option allows the MIDI file parsing for the track to continue, and then the rest of the tracks are processed. \item \textbf{skip} ignores the rest of the bytes in the track, and allows the rest of the tracks to be processed. The main difference between this option and \textbf{recover} is that the former allows \textsl{Seq66 SeqSpec} events to be created. \item \textbf{proceed} keeps going in spite of errors, reporting them. Try it with \texttt{contrib/midi/trilogy.mid} to see what messages appear in the console and what patterns actually get loaded. \item \textbf{abort} just exits the parsing, which is the old and undesirable behavior. It causes patterns to be dropped. \end{itemize} Try each option with the \texttt{trilogy.mid} file, in which running status is started, only to be ended by the 0xF0 SysEx event. But then the events after the SysEx event still require running-status to be in effect. This section is not yet accessible from an \textbf{Edit / Preferences} tab. \subsubsection{'rc' File / MIDI-Meta-Events Section} \label{subsubsec:configuration_rc_midi_meta_events} \index{[midi-meta-events]} The MIDI Meta events section is the start of additional options supporting meta events as normal events in \textsl{Seq66}; it defines only the tempo MIDI meta-event at present. Normally, tempo events are supposed to occur in the first track (pattern 0). But one can move this track elsewhere to accomodate one's existing body of tunes. It affects where tempo events are recorded. The default value is 0, the maximum is 1023. A pattern must exist at this number for it to work. \index{tempo-track-number} \begin{verbatim} [midi-meta-events] tempo-track = 0 \end{verbatim} As per the MIDI specification, the first track (track 1 in track numbering, or pattern 0 in \textsl{Seq66} numbering) is \textsl{the} official track for certain MIDI meta events, such as \textbf{Set Tempo} and \textbf{Time Signature}. But we allow the user to and change the tempo track to another pattern. \subsubsection{'rc' File / MIDI Input} \label{subsubsec:configuration_rc_midi_input} \index{[midi-input]} This configuration item is represented in the \textbf{MIDI Input} tab in the \textbf{Edit / Preferences}. The first number is a line count, and equals the number of supported input ports. After that, this 'rc' entry here has two variables; the first is the port number, and the second number indicates whether it is disabled (0), or enabled (1). The next lines show the input busses present on the system (normally). \begin{verbatim} [midi-input] 2 # number of MIDI busses 0 1 "[0] 0:1 system:announce" 1 0 "[1] 14:0 Midi Through Port-0" 2 0 "[2] 28:0 nanoKEY2 MIDI 1" 3 0 "[3] 48:0 Launchpad Mini MIDI 1" \end{verbatim} Note that the "system:announce" buss is present only for ALSA, not for JACK. This will change the port numbering. Again, see the 'rc' file itself for more information. \index{linux!system MIDI Thru} \index{MIDI Thru} One minor issue with the system MIDI Thru port is that, if it and another input port (e.g. VMPK) are both enabled, then each note emitted by VMPK is doubled. Be aware. For input port-mapping, which is now the default for \textsl{Seq66}, see \sectionref{subsec:input_port_mapping}. \subsubsection{'rc' File / Manual (Virtual) Ports} \label{subsubsec:configuration_rc_manual_ports} The name of this setting is a bit of a misnomer in a couple of ways: \index{ports!virtual} \index{ports!manual} \begin{enumerate} \item It refers to the usage of \textsl{virtual} MIDI ports. These are ports set up by the application so that other devices, applications, or session managers can connect \textsl{manually} to the MIDI application. \item This option is not just for ALSA. It can also be used when running in native JACK mode, to support virtual JACK ports that can be connected manually (e.g. in the \textsl{QJackCtl} application.) \item Manual ports disable port-mapping. \end{enumerate} \index{[manual-ports]} \begin{verbatim} [manual-ports] virtual-ports = false # 'true' = manual (virtual) ALSA or JACK ports output-port-count = 8 # number of manual/virtual output ports input-port-count = 4 # number of manual/virtual input ports \end{verbatim} \index{--auto-ports} The opposite of \texttt{-{}-manual-ports} is \texttt{-{}-auto-ports}, which is the normal mode of running \textsl{Seq66}. In this mode, system MIDI input/output devices are discovered and automatically connected. It will create port names as per the settings in the 'usr' configuration file's sections: \begin{verbatim} [user-midi-bus-definitions] [user-midi-bus-N] \end{verbatim} These definitions can be used by JACK for connection, and these definitions can be used to specifically rename the ports that exist in the system. (Roughly similar to the MIDInam feature, but not compatible.) This option is misleading if one wants to have access to the actual ALSA/JACK ports that exist on the system. The next option gets around that issue. \subsubsection{'rc' File / Port Map} \label{subsubsec:configuration_rc_port_map} The 'rc' file also supports a port map, which maps I/O ports from a permanent buss number to the actual system buss number. The pattern is set to output to a specific buss number; this buss number is associated with a port name; this port name is looked up to see what system buss number is the one to be used. When the system setup changes, the pattern does not need to changed; only the port mapping may need to be changed. If the port map covers all I/O ports ever possible on a system, it will not need to be changed. See \sectionref{sec:port_mapping}; it contains more details. \subsubsection{'rc' File / Keyboard Control Section} \label{subsubsec:configuration_rc_keyboard_control} The keyboard control section has been merged into the MIDI control section and been moved into the 'ctrl' file. See \sectionref{subsec:configuration_ctrl}. There is no user-interface to change the keyboard control, for two reasons: (1) It is pretty easy to read, understand, and edit the 'ctrl' file, and (2) There are many more controls in \textsl{seq66}, and creating a user-interface to edit them might not be worth the effort. We suspect most users will be happy enough with the default settings, and users of internationaly keyboards will find the 'ctrl' file easy enough to edit with a programmer's editor. As an example, \index{keys!AZERTY} see \texttt{qseq66-azerty.ctrl} in the \texttt{data/linux} directory. \subsubsection{'rc' File / JACK Transport} \label{subsubsec:configuration_rc_jack_transport} This section holds the settings for both JACK transport and for native JACK MIDI mode. \index{[jack-transport]} The JACK Transport options are also command-line options. See \sectionref{subsec:configuration_command_line}. For \textsl{Seq66} \textsl{before} 0.94.0, a number of boolean digit settings were used. For \textsl{Seq66} \textsl{after} 0.94.0, the settings are more elegant. \begin{verbatim} [jack-transport] transport-type = slave # 'none', 'slave', 'master', or 'conditional' song-start-mode = song # 'song' = 'true' or 'live' = 'false' jack-midi = true # 'true' or 'false' jack-auto-connect = true # 'true' or 'false', default is true \end{verbatim} 'conditional' value tells \textsl{Seq66} to try to become JACK Master. If a master already exists, then \textsl{Seq66} falls back to JACK Slave. Note that JACK transport is separately configurable from JACK MIDI. Transport and MIDI use different JACK clients internally. \texttt{song-start-mode} is set to 'true' or 'song' to force the usage of the track layout in the song editor (see \sectionref{sec:song_editor}). If 'false' or 'live' then the live mode is in force, where the musician controls the arming of sequences. If 'auto', then the mode is set to 'song' if the loaded file has a track layout (triggers), and 'live' otherwise. It is probably best to leave it at 'auto'. \texttt{jack-midi} set to 'true' turns on the usage of JACK for MIDI playback. If running in normal (not "manual/virtual" port) mode, then \textsl{Seq66} connects automatically to all JACK MIDI ports it finds on the system at startup. \texttt{jack-auto-connect} is normally set to 'true'. If set to false, then the automatic connect discussed in the previous paragraph will not be performed. This option is useful if one want to rely on a session manager to make the connections, or do it onself, without using the manual/virtual port feature. \subsubsection{'rc' File / Reveal Ports} \label{subsubsec:configuration_rc_reveal_ports} This option applies to both ALSA and JACK. \index{[reveal-ports]} \begin{verbatim} [reveal-ports] show-system-ports = true # flag for reveal ALSA ports \end{verbatim} \index{jack!reveal-ports} Turning on the reveal-ports option is necessary if one wants to see the actual port names defined by the system. It ignores the settings in the 'usr' configuration file's \texttt{user-midi-bus-definitions} and \texttt{user-midi-bus-N} sections. If this option is turned on, the definitions in the 'usr' configuration file are \textsl{not} read from that file. \subsubsection{'rc' File / Metronome} \label{subsubsec:configuration_rc_metronome} A very configurable metronome is supported. After running \textsl{Seq66} and then exiting, the following section is present: \begin{verbatim} [metronome] output-buss = 1 output-channel = 0 beats-per-bar = 4 beat-width = 4 main-patch = 0 main-note = 72 main-note-velocity = 120 main-note-length = 0 sub-patch = 0 sub-note = 60 sub-note-velocity = 84 sub-note-length = 0 count-in-active = false count-in-measures = 1 count-in-recording = true recording-buss = 2 recording-measures = 0 \end{verbatim} The \texttt{main} settings apply to the first note of the metronome. The type of tone is given by the patch (program) number. The actual note is given by a MIDI note number. The velocity can be set. The note-length is controlled by a scale factor: 0 means an automatic calculation based on the beat width which equates to a value of 0.5; 1.0 means the note length is exactly equal to the beat width. The minimum is 0.125, and the maximum is 2.0. Try different values and see. The \texttt{sub} settings apply to the rest of the notes of the metronome, but are otherwise the same. The settings \texttt{count-in-active} and \texttt{count-in-measures} control metronome count-in, where the metronome starts playing before the song. The rest of the settings are discussed in the following section on \textbf{Background Recording}. There is a user-interface in \textbf{Edit / Preferences} to configure the settings of the metronome (and the recorder). See \sectionref{subsection:edit_preferences_metronome}. \subsubsection{'rc' File / Count-In Recording} \label{subsubsec:configuration_rc_background_recording} Count-in recording allows recording to be made without having to create a pattern and turn on recording. It is useful when using a metronome and count-in measures. The following settings activate this feature and determine the input port to be used. \begin{verbatim} [metronome] . . . count-in-recording = true recording-buss = 2 recording-measures = 0 \end{verbatim} The \texttt{count-in-recording} setting enables/disables count-in recording. The \texttt{recording-buss} setting specifies the input buss/port. That buss \textsl{must be enabled} (in the 'rc' file). The \texttt{recording-measures} sets the number of measures to record. The style of recording is currently \textsl{merge}, where notes accumulate as each loop occurs. We might adjust that feaure per user feedback. If \texttt{recording-measures} is set to 0, then the \textsl{expanding} mode of recording is used, where the count-in-recording sequence gets longer and longers as playback and playing continues. \subsubsection{'rc' File / Interaction Method} \label{subsubsec:configuration_rc_interaction} \begin{verbatim} [interaction-method] snap-split = false click-edit = true \end{verbatim} The \texttt{Mod4} ("Windows" key) option is no longer available, and not necessary. Also removed is the option for the "fruity" option of \textsl{Seq24}. It will be added back if there is a clamor for it. \index{[snap-split]} \index{new!snap-split} This option comes from the \textsl{seq32} project. It allows for pattern-splitting in the Song editor at snap points, rather than just at the middle of the pattern. \index{[allow-click-edit]} \index{new!allow-click-edit} This option allows one to enable/disable the ability to double-click in a pattern slot in the main window to bring it up for editing. This can interfere with a live performance where muting/unmuting come fast enough to be seen as a double-click. \subsubsection{'rc' File / Auto Option Save} \label{subsubsec:configuration_rc_auto_option_save} \index{[auto-option-save]} This item determines if the 'rc' configuration file (and other files) is saved upon exit of \textsl{Seq66}. The normal behavior is to save it, which can sometimes be inconvenient when one is just trying out some command-line options. \begin{verbatim} [auto-option-save] auto-save-rc = true save-old-triggers = false save-old-mutes = false \end{verbatim} The 'save' options can be set to true to save triggers in the old formats. \textsl{Seq66} now saves triggers with a "transpose" value, so that clips can be transposed in the song editor for more extensive re-use (see the "Kraftwerk" MIDI file for an example). The 'mutes' are now save as a single byte, rather than a 4-byte value, to save some space. \subsubsection{'rc' File / Last Used Directory} \label{subsubsec:configuration_rc_last_used_dir} The following item refers to the last directory in which one opened or saved a MIDI file. \index{[last-used-dir]} \begin{verbatim} [last-used-dir] "/home/user/seq66/contrib/midi/" \end{verbatim} It is used to select the initial directory in a save/open file dialog. It is updated whenever a MIDI file is loaded or saved, and hence causes a saving of the 'rc' file at exit of \textsl{Seq66}. \subsubsection{'rc' File / Recent Files} \label{subsubsec:configuration_rc_recent_files} The following item preserves a list of the last few MIDI files loaded. It is not filled when a MIDI file is loaded via a play-list. The first number is the count of recent-files. The second number is a boolean to determine if the most-recent file should be loaded when \textsl{Seq66} starts. This option is useful as part of restoring a session. \index{[recent-files]} \begin{verbatim} [recent-files] count = 2 load-most-recent = true "/home/user/seq66-alternate/contrib/midi/2Bars.midi" "contrib/midi/b4uacuse-seq24.midi" \end{verbatim} \subsubsection{'rc' File / Play-List} \label{subsubsec:configuration_rc_playlist} This item provides a configured set of named play-lists in a play-list file, and a flag to activate it. Having a playlist makes it easy to load song after song from pre-determined lists. \index{[playlist]} \begin{verbatim} [playlist] active = false "/home/user/.config/seq66/sample.playlist" \end{verbatim} See \sectionref{sec:playlist}. It describes the setup, layout, and usage of a \textsl{Seq66} playlist file containing one or more playlists. \subsection{'usr' File} \label{subsec:configuration_usr} This section describes the \textsl{Seq66} 'usr' (or "user") file. The main part of the \textsl{Seq66} 'usr' configuration file provides a way to give more informative names to the MIDI busses, MIDI channels, and MIDI controllers of a given system setup. This configuration overrides the default values of the \textbf{Event} drop-down list and the menu items in the pattern editor, and makes them reflect the names of the MIDI Control (CC) values of one's devices. In \textsl{Seq66} it, also includes some items that affect the user-interface's look, and many other new configuration items. % At some point we will likely split this file into another configuration file % ("qseq66.ui"?) \index{qseq66.usr} After one runs \textsl{Seq66} for the first time (or after deleting the configuration files), it will generate a \texttt{qseq66.usr} file in one's "HOME" directory: \begin{verbatim} /home/user/.config/seq66/qseq66.usr (Linux) C:/Users/user/AppData/Local/seq66/qpseq66.usr (Windows) \end{verbatim} In a session manager, the files will be created in the session directory. \index{usr!-u} \index{usr!--user-save} Unlike the 'rc' file, the 'usr' file is \textsl{not} written every time \textsl{Seq66} exits. If the 'usr' files does not exist, one is created, but it is normally not overwritten thereafter. The important exception is if we have updated the 'usr' file with new option and have incremented the version number of this file. The update should occur automatically at exit, but if not, run \textsl{Seq66} with the \texttt{-u} or \texttt{-{}-user-save} option: \begin{verbatim} $ qseq66 --user-save \end{verbatim} One usually must edit the 'usr' file manually. There are a few items that can be tweaked in \textbf{Edit / Preferences}, and, if modified, the user-save flag is turned on. By default, the list of MIDI devices that \textsl{Seq66} shows depends on one's system setup and whether the manual-port option is specified or not. Here's our system, with the the \texttt{[manual-port]} option turned off, shown in a composite view with all menus one can look at for MIDI settings: \begin{figure}[H] \centering \includegraphics[scale=0.75]{configuration/usr/default-event-bus-channel-menus.png} \caption{Seq66 Composite View of Native Devices} \label{fig:default_event_bus_channel_menus} \end{figure} At the top, the buss dropdown menu contains the MIDI busses/ports active on this computer. At right, the MIDI channel shows the channels numbers that can be picked for buss 0. At bottom left, we see the default controller values that \textsl{Seq66} includes. We have no idea if these correspond to the controllers that the selected MIDI device supports. We \textsl{can} use this dropdown to see if any such controller events are in the loaded MIDI file, of course; a solid black square indicates that such an event was found in the pattern. To change the default lists, we can create sections for busses and instruments in the 'usr' file. The discussion here relies on the reader opening the file \texttt{sample.usr}, which is included in the shared \texttt{data/samples} directory provided once \textsl{Seq66} is installed. Assume that we have 3 MIDI "buss" devices hooked to our system: two Model "2x2" MIDI port devices, and an old PCR-30 MIDI controller keyboard. Let's number them, using the convention that buss numbers and channel numbers start at 0, not 1: \begin{itemize} \item 0. Model 2x2 A \item 1. Model 2x2 B \item 2. PCR-30 \end{itemize} Then assume that we have nine different MIDI instruments in our kit. let's number them, too: \begin{itemize} \item 0. Waldorf Micro Q \item 1. SuperNova \item 2. DrumStation \item 3. TX81Z \item 4. WaveStation \item 5. ESI-2000 \item 6. ES-1 \item 7. ER-1 \item 8. TB-303 \end{itemize} The \textsl{Waldorf Micro Q}, the \textsl{SuperNova}, and the \textsl{DrumStation} all have a large number of special MIDI controller values for modifying the sound they produce. The \textsl{DrumStation} accepts MIDI controllers that change various features of the sound of each type of drum it supports. The buss devices shown here can be configured to route certain MIDI channels to certain MIDI devices. Assume we have them set up this way: \begin{enumerate} \item Bus 0: Model 2x2 A \begin{itemize} \item SuperNova: channels 0 to 7 \item TX81Z: channels 8 to 10 \item Waldorf Micro Q: channels 11 to 14 \item DrumStation: channel 15 \end{itemize} \item Bus 1: Model 2x2 B \begin{itemize} \item WaveStation: channels 0 to 3 \item ESI-2000: channels 4 to 13 \item ES-1: channel 14 \item ER-1: channel 15 \end{itemize} \item Bus 2: PCR-30 \begin{itemize} \item TB-303: channel 0 \end{itemize} \end{enumerate} We use the \textbf{'usr' configuration file}. to show these items with the proper names associated with each device, channel, and controller value The process for setting up the 'usr' file is to: \begin{enumber} \item Define one or more MIDI busses, the name of each, and what instruments are on which channels. Each buss is configured in a section of the form "\texttt{[user-midi-bus-X]}", where "X" ranges from 0 on up. Each buss then defines up to 16 channel entries. Each entry includes the channel number and the number of a section in the user-instrument section described next. \item Define all of the instruments and their controller names, if they have them. Each instrument is configured in a section of the form "\texttt{[user-instrument-X]}", where "X" ranges from 0 on up. Up to 128 controllers can be defined. \end{enumber} Let's walk through the structure of this setup, since it is a little bit tricky. Peruse the next couple of sections to understand a bit about the format of this file, following along in the sample 'usr' file. \subsubsection{'usr' File / MIDI Bus Definitions} \label{subsubsec:usr_file_midi_bus_definitions} \index{usr!user-midi-bus-definitions} \index{[user-midi-bus-definitions]} This section begins with an "INI" group marker \texttt{[user-midi-bus-definitions]}. It defines the number of user busses that will be configured in this file. This section contains an number of \texttt{[user-midi-bus-N]} sections, where "N" ranges from 0 on upward. These correspond to the MIDI \textsl{output} busses expected to be in the system (ignoring the ALSA "announce" buss if present). \begin{verbatim} [user-midi-bus-definitions] 3 # number of user-defined MIDI busses \end{verbatim} \index{usr!user-midi-bus-n} \index{[user-midi-bus-n]} This means that the 'usr' file will have three MIDI buss sections: \texttt{[user-midi-bus-0]}, \texttt{[user-midi-bus-1]}, and \texttt{[user-midi-bus-2]}. Here's is an example of one such buss section: \begin{verbatim} [user-midi-bus-0] 2x2 A (SuperNova,Q,TX81Z,DrumStation) 16 0 1 # "channel" and "instrument number" 1 1 # Instrument #1 of the [user-instrument-definitions] section . . . 8 3 # Instrument #3 of the [user-instrument-definitions] section 9 3 . . . 11 0 # Instrument #0 of the [user-instrument-definitions] section 12 0 # This is the Waldorf Micro Q device . . . 15 2 # Instrument #2 of the [user-instrument-definitions] section \end{verbatim} Each instrument is setup as a "channel" in a particular "buss". These instrument-definition sections are described in the next section. They are read from the 'usr' configuration file only if the "reveal ports" option is \textsl{off} ("0"); this option can also be specified in the \texttt{[reveal-ports]} section of the 'rc' file. Otherwise, the actual port names reported by ALSA/JACK are shown. The \texttt{user-midi-bus-definitions} and \texttt{user-midi-bus-N} sections can be misleading if one wants to have access to the actual MIDI port names that exist on the system. It is left as an exercise for the reader to try these different combinations of show-port options. Or one can consult the \textsl{Sequencer64 User Manual} to see the figures. \begin{itemize} \item Clocks View, -m (-{}-manual-ports) \item Inputs View, -m (-{}-manual-ports) \item Clocks View, -m (-{}-manual-ports) and -R (-{}-hide-ports) \item Clocks View, -r (-{}-reveal-ports) \item Inputs View, -r (-{}-reveal-ports) \item Clocks View, -R (-{}-hide-ports) \end{itemize} \subsubsection{'usr' File / MIDI Instrument Definitions} \label{subsubsec:usr_file_midi_instrument_definitions} \index{usr!user-instrument-definitions} \index{[user-instrument-definitions]} This section begins with an "INI" group marker \texttt{[user-instrument-definitions]}. It defines the number of user instruments that will be configured in this file. This section defines characteristics, such as the meanings of MIDI controller values, of the instruments themselves, not the MIDI busses to which they attached. \begin{verbatim} [user-instrument-definitions] 9 # number of user instruments \end{verbatim} \index{usr!user-instrument-n} \index{[user-instrument-n]} So this 'usr' file will define 9 instruments. We provide only one section as an example. Note that items without text default to the values prescribed by the General MIDI (GM) specification. Each instrument contains up to 128 controller values; these controller values are available in the \textbf{Event} button in the Pattern Editor, and their names are shown. \begin{verbatim} [user-instrument-0] Waldorf Micro Q # name of instrument 128 # number of MIDI controllers 0 # first controller value, unnamed 1 Modulation Wheel 2 Breath Control 3 4 Foot Control . . . 123 All Notes Off (0) 124 # defaults to GM 125 Unsupported 126 Unsupported 127 # defaults to GM \end{verbatim} Note the unnamed control numbers above. An unnamed control number might be an unsupported control number. It is termed to be "inactive". In this case, the \textbf{Event} menu of the Pattern editor will show the default name of this controller. Again, though, the function denoted by this name might not be supported by the device. In that case, it might be better to call it "Unsupported". See the examples above. See the figure below for one example as set up using the \texttt{sample.usr} file: \begin{figure}[H] \centering \includegraphics[scale=0.65]{configuration/usr/sample-usr-event-bus-channel-menus.png} \caption{Seq66 Composite View of Devices As Set in "sample.usr"} \label{fig:sample_usr_event_bus_channel_menus} \end{figure} \subsubsection{'usr' File / User Interface Settings} \label{subsubsec:usr_file_user_interface_settings} \index{usr!user-interface-settings} \index{[user-interface-settings]} \index{usr!interface settings} This section, new to \textsl{Seq66}, begins with an "INI" group marker \texttt{[user-interface-settings]}. It provides additional specification of the appearance of the user-interface. Try the settings and see what looks good. Refer to either the sample file or the file generated when \textsl{Seq66} first runs. \index{variset} \begin{verbatim} [user-interface-settings] swap-coordinates = false mainwnd-rows = 4 mainwnd-columns = 8 mainwnd-spacing = 2 default-zoom = 2 global-seq-feature = true progress-bar-thick = true progress-bar-thickness = 3 progress-box-elliptical = false follow-progress = true gridlines-thick = true inverse-colors = false time-fg-color = "lime" # same as "default" time-bg-color = "black" # same as "default" dark-theme = false window-redraw-rate = 40 window-scale = 1 window-scale-y = 1 enable-learn-confirmation = true \end{verbatim} Note that some themes will not respect the time-color options. (This issue also occurs with other themed elements.) \texttt{swap-coordinates} allows for an alternate mapping of pattern numbers. (Later, it will also apply to mappings for set-numbers and mute-group numbers.) Here is the pattern layout: \begin{verbatim} [ 0 ] [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ] [ 9 ] [ 10 ] [ 11 ] [ 12 ] [ 13 ] [ 14 ] [ 15 ] [ 16 ] [ 17 ] [ 18 ] [ 19 ] [ 20 ] [ 21 ] [ 22 ] [ 23 ] [ 24 ] [ 25 ] [ 26 ] [ 27 ] [ 28 ] [ 29 ] [ 30 ] [ 31 ] \end{verbatim} Compare it to the layout shown in \sectionref{paragraph:patterns_pattern_keys}. \texttt{progress-bar-thick} also applies to the following: \begin{enumerate} \item The vertical play-head in the pattern slots. \item The vertical play-head in the pattern editor. \item The vertical play-head in the song editor. \item The font in the pattern slots (Live grid buttons) is made bold and made larger when this option is active. \end{enumerate} It also activates the \texttt{progress-bar-thickness} value. Try a number like "8" just for fun. The \texttt{gridlines-thick} option, if true, makes the song editor lines look thick. This can be bothersome with green lines on a dark background (e.g. with \texttt{monogreen.palette} and \texttt{monogreen.qss} files active); in that case, set it to false to make the lines thinner or dotted. There are a number of additional user-interface options. See the generated or sample 'usr' file for descriptions. Also see the chapter on palettes. \subsubsection{'usr' File / User MIDI PPQN} \label{subsubsec:usr_file_user_midi_ppqn} The long-standing PPQN for \textsl{Seq24} was a value of 192, and \textsl{Seq66} sticks with that default. This value is good for most tunes. But other sequencers allow for higher values. \textsl{Seq66} allows for some crazy values, ranging from 32 to 19200. If a MIDI file has a different PPQN, it will be rescaled to the default PPQN. However, one might want to stick with the PPQN specified in the MIDI file, so \textsl{Seq66} allows that as well. It is probably best to set \texttt{use-file-ppqn = true}, but that is up to the user. \index{usr!midi-ppqn} \index{default PPQN} \index{file PPQN} \begin{verbatim} [user-midi-ppqn] default-ppqn = 192 use-file-ppqn = true \end{verbatim} The PPQN can be set to \begin{verbatim} 32 48 96 120 192 240 384 768 960 1920 2400 3840 7680 9600 19200 \end{verbatim} There's probably no need to go above 960, but one can try it anyway and see if the computer can keep up. Even the default, 192, is likely to be enough. One might find a MIDI tune with a PPQN not in the usual set. In this case, \textsl{Seq66} does not display that properly. This is a difficult issue to fix at present. If an issue, one can change the PPQN in the main window PPQN dropdown, and save the file (after making a backup, of course). \subsubsection{'usr' File / User Randomization} \label{subsubsec:usr_file_user_randomization} The \textbf{Jitter} and \textbf{Randomize} commands available in the \textbf{Tools} menu in the pattern editor depend on two configurable values. \index{usr!midi-ppqn} \index{default PPQN} \index{file PPQN} \begin{verbatim} [user-randomization] jitter-divisor = 8 amplitude = 8 \end{verbatim} The \texttt{jitter-divisor} value is used to limit the amount of time jitter in jittering note events. If \texttt{J} is the jitter divisor, than the maximum range of time randomization R is: \texttt{R = S / J}, where \texttt{S} is the current value of note snap in the pattern editor. Thus, the time can be varied by an amount from minus R to plus R. The \texttt{amplitude} value is the maximum range (plus or minus) by which to modify amplitude values such as note velocity or channel pressure. Keep this value small, as the numbers it affects range only from 0 to 127. One minor bug persists in randomization. As the randomization is applied repeatedly, the amplitude tends toward zero. \subsubsection{'usr' File / User MIDI Settings} \label{subsubsec:usr_file_user_midi_settings} \index{[user-midi-settings]} This section begins with an "INI" group marker \texttt{[user-midi-settings]}. It allows one to specify the global defaults for tempo, beats per measure, and so on. \index{usr!convert-to-smf-1} \index{usr!beats-per-bar} \index{usr!beats-per-minute} \index{usr!beat-width} \index{usr!buss-override} \index{usr!velocity-override} \index{usr!bpm-precision} \index{usr!bpm-step-increment} \index{usr!bpm-page-increment} \index{usr!bpm-minimum} \index{usr!bpm-maximum} \begin{verbatim} [user-midi-settings] convert-to-smf-1 = true beats-per-bar = 4 beats-per-minute = 120 beat-width = 4 buss-override = -1 velocity-override = 80 # velocity_override (-1 = 'Free') bpm-precision = 1 # 0, 1, or 2 bpm-step-increment = 0.1 bpm-page-increment = 5.0 bpm-minimum = 0 bpm-maximum = 127 \end{verbatim} The \texttt{convert-to-smf-1} option is normally true. This causes \textsl{Seq66} to convert single track MIDI files (in SMF 0 format) to multi-track SMF 1 files. \index{port!override} \index{buss!override} The \texttt{buss-override} option causes the buss-value (port number) to be applied to each pattern in a MIDI song that is loaded. This allows the tune to be directed to one's favorite synthesizer/application. Unlike the global port override in the main window (see \sectionref{subsubsec:introduction_sets_buss_override}), this application does not modify the file, though one can still opt to save it, which locks in the new buss number. The \texttt{velocity-override} option fixes a long standing (from \textsl{Seq24}) bug where the actual incoming note velocity was always replaced by a hard-wired value. A value of "-1" corresponds to the "Free" setting, which preserves the incoming velocity. The \texttt{bpm-precision}, \texttt{bpm-step-increment}, and \texttt{bpm-page-increment} values allow more precise control over tempo, which makes it easier to match the tempo of external music sources. Note that the step-increment is used by the up/down arrow buttons, the up/down arrow keys, and the MIDI BPM control values. The page-increment is used if the BPM field has focus and the Page-Up/Page-Down keys are pressed, and new MIDI control values have been added to support coarse MIDI control of tempo. The \texttt{bpm-minimum} and \texttt{bpm-maximum} settings are used in scaling the display of Tempo events. By adjusting these values, one can more easily see the variations in tempo. In a main window pattern slot, or in the song editor tempo track, this range is scaled to the full range of note values, 0 to 127. Generally, one wants to select a range that keeps the main tempo line at the middle height of the pattern display. To obtain these new settings, remember to backup the existing \textsl{seq66.usr}, then run \textsl{Seq66} with the \texttt{-{}-user-save} option, and then do a "diff" on the new file and the original to merge any old values that need to be preserved. Then make any further tweaks to the new values. \subsubsection{'usr' File / User Options} \label{subsubsec:usr_file_user_options} \index{[user-options]} This section begins with an "INI" group marker \texttt{[user-options]}. It provides for additional options keyed by the \texttt{-o}/\texttt{-{}-option} options. This group of options serves to expand the options that are available, since \textsl{Seq66} is running out of single-character options. This group of options are shown below. \index{usr!option-daemonize} \index{usr!option-logfile} \index{usr!option-pdf-viewer} \index{usr!option-browser} \begin{verbatim} [user-options] daemonize = false log = "seq66.log" pdf-viewer = "/usr/bin/zathura" browser = "/usr/bin/lynx" \end{verbatim} If this option is not used when running \texttt{seq66cli}, then the application stays in the console window and dumps informational output to it. If this option is in force, then the only way to affect \texttt{seq66cli} is to send a signal (e.g. SIGKILL) to it, or use MIDI control. One can also run \textsl{Seq66} \texttt{seq66cli} in the background from a console or from a desktop shortcut. The log-file, if specified, is written to the same directory as the 'usr' file, the \textsl{Seq66} configuration directory. If empty, then a valid file-name can be specified in the \texttt{-{}-option log=filename.log} option. % There's more to the 'usr' configuration file than we've exposed here. The PDF viewer and browser options are used if non-empty. Otherwise, the system default applications are used. \subsubsection{'usr' File / Additional Options} \label{subsubsec:usr_file_added_options} \textbf{\texttt{[user-work-arounds]}} is a section that is a relic from older versions of this application. It can be ignored. More useful options are described below. \paragraph{'usr' File / Additional Options / [user-ui-tweaks]} \label{paragraph:user_file_added_options_tweaks} \texttt{[user-ui-tweaks]} provides a small number of tweaks to the user-interface. \begin{verbatim} [user-ui-tweaks] key-height = 10 key-view = octave-letters note-resume = false fingerprint-size = 128 progress-box-width = 0.8 progress-box-height = 0.4 progress-box-shown = true progress-note-min = 20 progress-note-max = 100 lock-main-window = true \end{verbatim} % style-sheet = "qseq66.qss" # optional, can include a path \index{key height} The \texttt{key-height} option affects the default "width" of the piano keys in the pattern sequence editor. Defaults to 12 (pixels). This option is also editable in the \textbf{Preferences} dialog. There are vertical zoom buttons, and the \texttt{v/0/V} keystrokes to change this on the fly, but those changes are not saved. \index{key view} The \texttt{key-view} option determines the default lettering/numbering of the keys. This can be changed during the run by right-clicking in the virtual keyboard. \index{new seqedit} \textsl{Seq66} can present the pattern editor in the 'Edit' tab versus an external window.. When used in the \textbf{Edit} tab instead of an external window, it is shrunk slight vertically to fit controls in the smaller window. The following options adopt the new convention for setting variables, in which the format is \texttt{item-name = value}. \index{note resume} The \texttt{note-resume} option, if active, causes any notes in progress to be resumed when the pattern is toggled back on. Note: the style-sheet option has been moved to the 'rc' file. See \sectionref{subsubsec:configuration_rc_style_sheet}. \index{fingerprint} The \texttt{fingerprint} option provides a way to reduce the amount of drawing in the pattern grid. The pattern box in each button is small, and, for patterns longer than the fingerprint size, it makes no sense to draw hundreds of notes. Instead, patterns shorter than the fingerprint size are drawn normally, while longer patterns are drawn with a "fingerprint", a compressed representation of the pattern. \index{progress box!width} \index{progress box!height} The \texttt{progress-box-width} and The \texttt{progress-box-height} options provide a way to expand or reduce the size of the progess box in each grid button. It is purely a user preference. The width ranges from 0.5 to 1.0 (full size), with a default of 0.8. The height ranges from 0.1 to 1.0 (full size), with a default 0f 0.3. Try setting both the width and height to 0.9 for an interesting effect. % If either is 0, then the box isn't drawn, and the pattern appears directly % on the button. \index{progress box!shown} If \texttt{progress-box-shown} is false, then the progress boxes are not drawn, and the pattern appears directly on the button. \index{progress-note-min} \index{progress!note-min} \index{progress-note-max} \index{progress!note-max} The \texttt{progress-note-min} and The \texttt{progress-note-max} options change the note range in the progress box to control where in "pitch" the notes are shown. \index{progress-box-show-cc} \texttt{progress-box-show-cc} allows the progress box to show continuous controllers as dots. \index{lock-main-window} The \texttt{lock-main-window} option, if true, prevents the main window from being resized. It can still be moved, and the external pattern and song editors can still be resized. \paragraph{'usr' File / Additional Options / [user-session]} \label{paragraph:user_file_added_options_session} \texttt{[user-session]} provides a way to cooperate with the \textsl{Non Session Manager}. See \sectionref{subsec:sessions_nsm_before_using_nsm}; it goes into great detail. \begin{verbatim} [user-session] session = none url = "" \end{verbatim} \paragraph{'usr' File / Additional Options / [pattern-editor]} \label{paragraph:user_file_added_options_pattern_editor} \texttt{[pattern-editor]} contains settings values for recording when a new pattern editor is opened. A new pattern is indicated when the loop has the default name, \textsl{Unititled}. These values save time during a live recording session. The valid values for record-style are \texttt{merge}, \texttt{overwrite}, and \texttt{expand}. \begin{verbatim} [pattern-editor] escape-pattern = false armed = false thru = false record = false qrecord = false record-style = merge wrap-around = false \end{verbatim} The \texttt{escape-pattern} option applies to an open pattern window. If enabled, the \texttt{Esc} key can close the pattern window if patterns are not playing and if not in paint mode. If both are true, then the first \texttt{Esc} stops playing, the second \texttt{Esc} exits paint mode, and the third \texttt{Esc} closes the window. The use must enable this option deliberately. The record options can be surprising. If false, and one opens the pattern window for a pattern set to record, the record will be turned off. To solve this problem, set \textbf{Edit / Preferences / Pattern / Apply only to new}. Also included is a flag to allow notes to wrap around, where the Note On comes near the end of the pattern, but the Note Off appears before the Note On. In this case the end of the note is colored magenta. If wrap-around is false, then the note is clipped to the end of the pattern. \textbf{Warning}: If a song with wrapped notes is re-opened after wrap-around is changed to false, then the wrapped notes will be truncated. % If false, then two unlinked note events appear in the piano roll, colored % magenta. % The "u" command in the piano roll will remove them. If one does not want to deal with wrap-around at all, then set the pattern length to the desired measure count \textsl{plus one} while recording, until satisfied with the recording. Shrink or delete any notes that bleed into the extra measure. Then set the pattern back to the desired length. \subsection{'ctrl' File} \label{subsec:configuration_ctrl} \textsl{Seq66} provides a way to control the application to some extent via a MIDI controller, such as a MIDI keyboard or a MIDI pad. In addition, it defines the keystrokes used to activate various functions. Although somewhat similar to the keystrokes used in \textsl{Seq24}, \textsl{Seq66} greatly expands the number of control items. Unfortunately, there is no GUI editor of "learn" function for the 'ctrl' file. However, it is well-documented and can be edited manually. Also added to the 'ctrl' file are sections to define MIDI output events to toggle status lights on an external device, and output macros that can be defined to set up an external device. The current section describes the 'ctrl' file; additional resources and ideas can be found at \url{linuxaudio.org} and its discussion of MIDI control with \textsl{Seq24} \cite{midicontrol}. Also see the tutorial section \sectionref{sec:launchpad_mini}. An \textsl{Open Document Format} spreadsheet in the \texttt{doc} directory shows layouts for the default keystroke configuration (including pattern, mutes, and automation controls) and for a couple of \textsl{Launchpad Mini} configurations. Another spreadsheet lists the support names for keys; these names can be used in the 'ctrl' file, and include some changes for French AZERTY keyboards. The spreadsheets are \texttt{control\_keys.ods} and \texttt{launchpad-mini.ods}. The 'ctrl' file provides settings for keyboard control, MIDI input control, and for specifying MIDI output to reflect \textsl{automation} commands in a device such as the \textsl{LaunchPad Mini}. The name of this file and its active status are specfied in the 'rc' file as noted earlier. \subsubsection{'ctrl' File / MIDI Control Settings} \label{subsubsec:configuration_ctrl_midi_control_settings} \begin{verbatim} /home/user/.config/seq66/qseq66.ctrl (Linux) C:/Users/user/AppData/Local/seq66/qpseq66.ctrl (Windows) \end{verbatim} This file offloads the control settings from the 'rc' file, for a more flexible setup. It starts with the sections common to all \textsl{Seq66} configuration files. The first unique section defines some useful settings using the new variables feature of the configuration. Look at the sample or generated file to see the layout of these items. \begin{verbatim} [midi-control-settings] control-buss = 3 # or 0xff, or the port-mapped name midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 keyboard-layout = qwerty \end{verbatim} \begin{itemize} \item \texttt{control-buss}. The control-buss value ranges from 0 to the maximum buss provided by the hardware on the system. If set, then only that buss will be allowed to send MIDI control. A value of 255 or 0xff means any buss can send MIDI control. If port-mapping is enabled, the short name (nick-name) of the port can be used as well. \textsl{Never use the ALSA MIDI Through port for both control and display. Use an actual device port.} \item \texttt{midi-enabled}. If set to "true", then the MIDI controls will be used. It can be set to "false", while keeping the configuration in place for later usage. \item \texttt{button-offset}. This item provides a way to move a set of input controls (e.g. from a \textsl{Launchpad Mini}) to a different area of the input control device. Not yet supported. \item \texttt{button-rows}. Indicates the rows of the input control grid. Still in progress. \item \texttt{button-columns}. Indicates the columns of the input control grid. Still in progress. \item \texttt{keyboard-layout}. Provides a way to adapt to non-US keyboards. Currently, the only supported values are "normal" ("qwerty"), "qwertzy", and "azerty". \index{keys!AZERTY} For "azerty", the auto-shift feature of group-learning is disabled. The handling of keyboards can be quite complex, and differ between Linux distros and Windows. Especially problematic are the "dead keys" supported by some locales. \end{itemize} \subsubsection{'ctrl' File / Loop Control} \label{subsubsec:configuration_ctrl_loop_control} The loop-control group consists of 32 lines (0 to 31), one for each pattern slot shown in the patterns panel. It provides a way to control the arming/disarming (muting/unmuting) of each pattern shown in the patterns panel. It consolidates the keyboard and MIDI control settings into one table. Note that the main window shows the \textsl{active} screen-set. These MIDI controls affect the \textsl{active} screen-set. This block of matrix elements, numbered from 0 to 31, represent control functions (toggle, mute, unmute) for the 32 patterns of the active screen-set. These 32 rows correspond to the hot-keys assigned in the \textbf{File / Options / Keyboard / Control keys [keyboard-group]} configuration panel. \index{[loop-control]} The MIDI control section begins with the following "INI"-style group marker tag, followed by one stanza-line per loop: \begin{verbatim} [loop-control] # Control: Toggle On Off 0 "1" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 0 1 "q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 1 . . . 31 "," [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 31 \end{verbatim} The first column is an index number, starting at 0. It indicates what loop the control line will affect. \index{keys!control} The second column is the name of the keystroke that will act as a toggle or action key. \index{keys!blank} If the key name is \texttt{Blank}, then there is no keystroke for that pattern control. The user can specify multiple blank keys as desired. The numbers in the leftmost brackets define a \textsl{Toggle} control; the numbers in the middle brackets define an \textsl{On} control; the numbers in the rightmost brackets define an \textsl{Off} control. The numbers inside each set of brackets define six values that set up the control. The layout of each filter inside the brackets is as follows: \textbf{[INV STAT D1 D2min D2max]} \begin{itemize} \item \textbf{INV} = \textbf{inverse} \item \textbf{STAT} = \textbf{MIDI status byte} (channel included) \item \textbf{D1} = \textbf{data1} \item \textbf{D2min} = \textbf{data2 min} \item \textbf{D2max} = \textbf{data2 max} \end{itemize} If \textbf{STAT} is not 0x00, the control is enabled. \textsl{Seq66} will match the incoming MIDI event against the \textbf{STAT (MIDI status byte)} pattern (e.g. a Note On event), and perform the action (On/Off/Toggle) if the \textbf{D1} (e.g. a Note number), matches the incoming data, and the incoming parameters (e.g. Note velocity) falls in the specified \textbf{D2min} to \textbf{D2max} range. All data values are best specified in decimal. The \textbf{INV (inverse)} field will make the pattern perform the opposite action (\textsl{off} for \textsl{on}, \textsl{on} for \textsl{off}) if the data falls outside the specified range. This is cool because one can map several sequences to a knob or fader. The \textbf{STAT (MIDI status byte)} field is a MIDI status byte number in decimal or hexadecimal notation. Remember that it can include a channel. This channel is not overridden by the pattern's selected channel when a MIDI control matching event is received. One can look up the possible status values up in the MIDI messages tables; the relevant data can be found at \textsl{Summary of MIDI Messages} \cite{midicontroltable}. The last three fields describe the range of data that will match. The \textbf{D1 (data1)} field provides the actual MIDI event message number to detect, in decimal. This item could be a Note On/Off event or a Control/Mode change event, for example. The \textbf{D2min (data2 min)} field is the minimum value of the event for the filter to match. For Note On/Off events, this would be the velocity value, for example. The \textbf{D2max (data2 max)} field is the maximum value of the event for the filter to match. % This set of values is explained below. For each pattern, we can set up MIDI events to turn a pattern on, off, or to toggle it. The loop MIDI control setup resembles a matrix. The default matrix uses the central keys on the keyboard, laid out in a 4 x 8 grid matching the pattern buttons: \begin{verbatim} 1 2 3 4 5 6 7 8 q w e r t y u i a s d f g h j k z x c v b n m < \end{verbatim} Please note that the buttons on some (or most) MIDI controllers send a message on press and another message on release. One can set such buttons up as toggle buttons by defining only the "Toggle" (first) stanza. Or one can have the button active the function on press, and then deactivate it on release by defining the "On" (second) and "Off" (third) stanzas appropriately. \subsubsection{'ctrl' File / Mute-Group Control} \label{subsubsec:configuration_ctrl_mute_group_control} \index{mute-group control} This section provides controls for 32 groups of mutes. A group is a set of patterns that can toggle their playing state together. Every group contains all 32 sequences in the active screen set. So, this part of the MIDI Control section is used for muting and unmuting (and toggling) a group of patterns using a keystroke or MIDI control. The definitions are in the same format as the loop-control section. \begin{verbatim} [mute-group-control] 0 "!" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 0 1 "Q" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 1 . . . 31 "<" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Mute 31 \end{verbatim} All this section does is set up the controls to be used; the actual mute-group patterns are defined in the 'mutes' configuration file (or in the \textsl{Seq66} MIDI file itself. A key name of \texttt{Blank} can be used to disable a keystroke for that line. The mutes MIDI control setup resembles a matrix match the shifted versions of the loop control keys. The default matrix uses the central keys on the keyboard, laid out in a 4 x 8 grid matching the pattern buttons: \begin{verbatim} ! @ # $ % ^ & * Q W E R T Y U I A S D F G H J K Z X C V B N M < \end{verbatim} \subsubsection{'ctrl' File / Automation Control} \label{subsubsec:configuration_midi_ctrl_automation} This section provides ways to control \textsl{Seq66} push-button controls from a keyboard or from a MIDI device. These entries control \textsl{Seq66} actions like changing the BPM value, screen-set, record, solo, etc. Note that automation controls that depend upon a parameter (such as group number or loop number) can only work with a MIDI control. MIDI control can provide parameters via a control value, note number or value, etc. Key control can provide only a "press" or "release" status. Each item in this group consists of one line. Each line specifies a MIDI event that can cause a given \textsl{Seq66} user-interface operation to occur. These items are easy to view in the 'ctrl' configuration file, in the \texttt{[automation-control]} section. \begin{verbatim} [automation-control] 0 "'" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Up 1 ";" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # BPM Dn . . . 35 "Quit" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Loop 0 . . . 48 "0xfe" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # Reserved 48 . . . 80 "0x8f" [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] [ 0 0x00 0 0 0 ] # All Sets \end{verbatim} One thing to be careful of when editing this section is to make all the numbers (from 0 to 80) unique. When copy/pasting a line and forgetting to change the number (\textsl{mea culpa}), an error like the following can appear in the console output: \begin{verbatim} Duplicate mute slot # 43 : '0xe8' [seq66] Key controls: Error at line 232 ordinal 0xe8 key '0xe8' control 'Visibility' code 43 \end{verbatim} Note that "Quit" is not a real keystroke. It is a placeholder in the internal keystroke map for the functionality of quitting \textsl{Seq66} via MIDI control. Qt provides other ways to quit via keystroke. A key name of \texttt{Blank} can be used to disable a keystroke for a line. The stanzas meaning can change depending on the type of control. Here are the important styles: \begin{verbatim} Normal: [ Toggle ] [ On ] [ Off ] Playback: [ Pause Play ] [ Start Play ] [ Stop Play ] Play list: [ Select by D2 ] [ Select Next ] [ Select Previous ] Play song: [ Select by D2 ] [ Select Next ] [ Select Previous ] \end{verbatim} For selecting play-lists and songs by number, \textbf{D2} is used. Thus, one possible value to use to select would be to use a Note On event on channel 16 (0x9F) with a note number of 0 (a rarely-used note in any tune), and list/song numbers ranging from 0 to 127. Using note 0 for list selection and note 1 for song selection: \begin{verbatim} 24 "F2" [ 0 0x9F 0 0 127 ] [ . . . ] [ . . .] # Play List 25 "F3" [ 0 0x9F 1 0 127 ] [ . . . ] [ . . .] # Play Song \end{verbatim} Obviously, this requires a MIDI controller for which the velocity can be exactly specified. One can also reserve \textbf{D2} values of 126 for "previous" and 127 for "next". \paragraph{Automation / BPM Up and Down} \label{paragraph:configuration_midi_ctrl_bpmupdn} These controls increment or decrement the beats-per-minute setting, as if the up- or down-arrow has been clicked in the BPM combox-box, or the up- or down-arrow key pressed, in that combo-box. This increment is the \index{bpm!step increment} \index{usr!step increment} "step increment" which defaults to 1, but can be modified by changing the "bpm\_step\_increment" value in the 'usr' configuration file. \paragraph{Automation / Screen-Set Up and Down} \label{paragraph:configuration_midi_ctrl_ssupdn} Also abbreviated "Set Up" and "Set Down". This control increments / decrements to the next / previous screen-set. Once the screen-set has been altered, mute-groups and other actions apply to that screen set. \paragraph{Automation / Mod Replace} \label{paragraph:configuration_midi_ctrl_modrep} This entry controls the "replace" flag. Once set, when the user manually clicks a pattern slot, that pattern is unmuted, and all the rest are muted. Thus, this MIDI control is kind a of "Solo" function. It works whether in "Live" or "Song" mode. \paragraph{Automation / Mod Snaphot} \label{paragraph:configuration_midi_ctrl_modsnap} This control causes the playing statuses of all active (i.e. having data) patterns to be saved. When turned off, the original playing status is restored. % Thus, two MIDI events % need to be allocated to this functionality. \paragraph{Automation / Mod Queue} \label{paragraph:configuration_midi_ctrl_modqueue} This control sets up the "queue" status flag. Then, when the user manually clicks a pattern slot, that pattern is queued, and will play at the next cycle of the pattern. Here is an example from \textsl{Seq24} \cite{midicontrol}, which shows how to set up the "Sustain" control-change event to queue or un-queue a sequence: The \textsl{Akai MPK Mini} has a Sustain button and we can set the Sustain MIDI event (with MIDI status byte 176 [0xB0] to represent a Controller event, and control/mode change number 64 [0x40] to represent the Sustain or Pedal control) up as the queue modifier in the \texttt{mod queue} entry: \begin{verbatim} 6 "o" [ 0 0x00 0 0 0 ] [ 0 0xB0 64 127 127 ] [ 0 0xB0 64 0 0 ] # INV STA D1 mn mx INV STA D1 mn mx INV STA D1 mn mx # ^ ^ ^ ^ # | | | | # | ----Sustain----------- | # -------Control Change------- \end{verbatim} So when the Sustain button is held down, and one presses one of the pads on the \textsl{MPK Mini}, the corresponding sequence gets queued. Also included in the data directory are sample 'ctrl' files for other devices. For a more comprehensive discussion, see \sectionref{sec:launchpad_mini}. \paragraph{Automation / Mute Group ("Group Mute")} \label{paragraph:configuration_midi_ctrl_modgmute} A mute group is a set of playing patterns associated with an upper-case hot-key. Mute groups can be learned, stored, and then activated by the appropriate hot-key. See \sectionref{subsubsec:introduction_mute_group_learn_button}. \paragraph{Automation / Group Learn} \label{paragraph:configuration_midi_ctrl_modglean} This MIDI control sets up a "group learn". This control sets two internal flags on : "mode-group" and "group-learn". The first flag indicates that we will be handling mute-groups. The second flag indicates that we are learning these mute-groups, effectively recording the current status of all the patterns in all of the screen-sets. \index{L button} Note that this control corresponds to the "L" button in the main window user-interface. \index{keys!l} It can also be accessed by the default hot-key, \texttt{l}. Note that, once in learn-mode, there is no way to cancel learn-mode except by selecting an illegal mute-group keystroke. Also see \sectionref{sec:mutes_master}. \paragraph{Automation / Playing Set} \label{paragraph:configuration_playing_set} The playing set holds all of the patterns that should be heard. Normally, this is all the active patterns in the active set. It saves time by not including inactive patterns. It can be augmented with patterns from non-active sets using the "sets-mode" option in the 'rc' file. \paragraph{Automation / Playback} \label{paragraph:configuration_playback} This automation entry defaults to a period. It starts playback and stops (pauses) playback. Note that this applies to the live grid. For the pattern and song editor piano rolls, it is hardwired. \paragraph{Automation / Song Record} \label{paragraph:configuration_song_record} Initiates a "recording" of the musician's muting and unmutings as triggers in the song editor. There are still some wrinkles to work out with this, such as ignoring snap values in order to get an exact recording of the triggers. \paragraph{Automation / Solo} \label{paragraph:configuration_midi_ctrl_solo} Meant to "solo" a given track. Needs testing and further work. \paragraph{Automation / Thru} \label{paragraph:configuration_midi_ctrl_thru} Turns on the "MIDI Thru" function of the current pattern as displayed in a pattern editor. \paragraph{Automation / BPM Page Up and Page Down} \label{paragraph:configuration_midi_ctrl_bpmpageupdn} Similar to \sectionref{paragraph:configuration_midi_ctrl_bpmupdn}, but in larger steps. These controls increment or decrement the beats-per-minute setting in large steps, as if the Page-Up or Page-Down were pressed in the BPM combox-box. This increment is the \index{bpm!page increment} \index{usr!page increment} "page increment" which defaults to 10, but can be modified by changing the "bpm\_page\_increment" value in the 'usr' configuration file. \paragraph{Automation / Set a Set} \label{paragraph:configuration_midi_set_a_set} Changes to a set as given by the data parameter. Needs more testing and further work. % \paragraph{Automation / Screen-Set Play} % \label{paragraph:configuration_midi_ctrl_ssplay} % This MIDI control sets the playing screen-set. \paragraph{Automation / Loop Mode} \label{paragraph:configuration_midi_loop_mode} Toggles using the "L/R" markers as the beginning and ending of playback. See \sectionref{paragraph:configuration_bbthms_lr_loop}. \paragraph{Automation / Quan Record} \label{paragraph:configuration_midi_quanrecord} Toggles quantization of the incoming note events while recording. \paragraph{Automation / Reset Sets} \label{paragraph:configuration_midi_reset_sets} Resets the set counter to set 0. \paragraph{Automation / One-shot} \label{paragraph:configuration_midi_one_shot} Toggles one-shot recording mode. \paragraph{Automation / FF, Rewind, and Top} \label{paragraph:configuration_midi_ff_rewind_top} When activated, each command moves the playing tick value up or down by one-half of a measure. The "Top" command rewinds to the beginning immediately. \paragraph{Automation / Playlist Commands} \label{paragraph:configuration_midi_playlist_commands} The "Play List" and "Play Song" automation commands allow one to select a particular play-list or song, or increment/decrement to the next/previous one. \paragraph{Automation / Tap BPM} \label{paragraph:configuration_midi_tap_bpm} This command, when pressed repeatedly, sets the beats-per-minute value to the interval between the taps. \paragraph{Automation / Start} \label{paragraph:configuration_start} This automation entry defaults to a space. It starts playback and stops playback with a rewind to the beginning. Note that this applies to the live grid. For the pattern and song editor piano rolls, it is hardwired. \paragraph{Automation / Stop} \label{paragraph:configuration_stop} This automation entry defaults to an escape character. It stops playback with a rewind to the beginning. Note that this applies to the live grid. For the pattern and song editor piano rolls, it is hardwired, and this keystroke can be used to exit insert/paint mode. \paragraph{Automation / Toggle Mute} \label{paragraph:configuration_toggle_mute} Reverses the armed statuses of the current set. \paragraph{Automation / Song Position} \label{paragraph:configuration_song_position} This one needs work. We can't even remember what it means! \paragraph{Automation / Keep Queuue} \label{paragraph:configuration_keep_queue} When given, this command turns on keep-queue mode. \paragraph{Automation / Slot Shift} \label{paragraph:configuration_slot_shift} When given, this command allows for accessing patterns numbered from 32 to 63. When given again, this command allows for accessing patterns numbered from 64 to 95. Useful only when set sizes of 64 and 96 are configured. \paragraph{Automation / Record Modes and Quantization} \label{paragraph:configuration_midi_record_quan} \index{patterns-panel!modes} \index{grid modes} \textbf{Grid modes} provides the ability to use the live grid and its hot-keys and automation for more than just toggling patterns. The \textbf{Record} and \textbf{Quan Record} automation controls cycle through these modes. Additional automation commands provide direct access to each mode. Grid mode changes the function of the main window's patterns panel so that it can be used to initiate recording instead of toggling mute status, regardless of whether the toggling is done via the buttons, hot-keys, or MIDI controls. The following modes are supported: \begin{itemize} \item \textbf{Loop}. The normal toggling of the mute/armed status of a pattern the slot is clicked or activated by its hot-keys. \item \textbf{Mutes}. Applies the given mute-group armings when the slot is clicked. Since the "shifted" hot-keys can also be used, this mode is most useful with mouse-click control. \item \textbf{Record}. In this mode, a click on a slot or the use of its hot-key toggles recording for that pattern. \item \textbf{Copy}. Copies the pattern when that slot is clicked. \item \textbf{Paste}. Pastes the previously-copied pattern into the slot that is clicked. \item \textbf{Clear}. Clears the pattern in the clicked slot. The pattern remains in that slot, but it has no events. Careful! \item \textbf{Delete}. Deletes the pattern in the clicked slot. Careful! \item \textbf{Thru}. Enables the MIDI Thru function for that pattern. \item \textbf{Solo}. Solos the clicked pattern. \item \textbf{Cut}. Deletes the pattern in the clicked slot, while saving it in the clipboard. \item \textbf{Double}. Doubles the length (in measures) of the pattern in the slot that is clicked. \end{itemize} A click or a hot-key will cause the selected function above to be applied to the pattern denoted by the click/key. In \textbf{Record} mode, the following settings are enabled: \begin{itemize} \item \textbf{Overdub}. Called \textbf{Merge} in the pattern editor, this recording style just keeps adding events as they come in when the pattern repeats. \item \textbf{Overwrite}. When the pattern ends, then loops back to the beginning, an incoming note clears the pattern before being added. \item \textbf{Expand}. As notes come in, the pattern gets longer and longer. \item \textbf{One-shot}. Notes are added to the pattern as they come in, but recording stops when the end of the pattern's specified measures is reached. % TODO: fix this! % \item \textbf{One-shot Reset}. % Similar to one-shot, but clears the pattern. % (To do: find the exact process used here.) \end{itemize} Also supported is changing the mode of recording, that is, what happens to notes while incoming during recording: \begin{itemize} \item \textbf{No Quan}. Nothing is done to the incoming notes. \item \textbf{Quantize}. Incoming notes are quantized to the nearst snap value for the pattern. \item \textbf{Tighten}. Incoming notes are tightened (partially quantized) to the nearst snap value for the pattern. % These are not supported during recording: % \item \textbf{Randomize}. % The amplitude (velocity) of notes is randomized. % This is not done during recording, but can be applied later. % \item \textbf{Jitter}. % The timing of notes is randomized. % This is not done during recording, but can be applied later. \item \textbf{Note-map}. If active, the specified 'drums' file values are used to remap the notes to new notes. This is useful, along with setting MIDI Thru, to play on a pre-General-MIDI drum machine (e.g. Yamaha DD-11) and hear it transmuted to GM while recording. Can also be applied after the fact, if the pattern is marked as transposable. \end{itemize} Also see \sectionref{subsec:pattern_editor_bottom} for more information on these recording modes. \paragraph{Automation / BBT/HMS and LR Loop} \label{paragraph:configuration_bbthms_lr_loop} These commands toggle the display of bars:beats:ticks versus hours:minutes:seconds, and looping between the L/R markers. However, they do not have usable keystrokes; the 'ctrl' file needs to be modified. See \sectionref{paragraph:configuration_midi_loop_mode}. \paragraph{Automation / Undo and Redo} \label{paragraph:configuration_undo_redo} These commands undo or redo actions such as deleting notes. \textbf{Important}: These commands will affect \textsl{all} open pattern editors! \subsubsection{Automation / More MIDI Control} \label{subsubsec:configuration_midi_ctrl_automationex} Many additional control items were requested by users, to control additional features of the application. Too many to list here. See the 'ctrl' file samples for more information. We will ultimately mention them all. Some important ones to touch on: \begin{itemize} \item \textbf{Visibility}. Toggles the visibility of the user-interface. This can be useful in some circumstances. In addition, the session manager, if in force, might issue this command. \item \textbf{Quit}. Provides a way to exit \textsl{Seq66} via MIDI control. \end{itemize} \subsubsection{'ctrl' File / MIDI Control Output} \label{subsubsec:configuration_ctrl_midi_control_out} This section provides a way to have a MIDI device, such as the \textsl{Novation Launchpad Mini}, show the status of the patterns that are active, as well as other information. The first sub-section sets up some general settings. \begin{verbatim} [midi-control-out-settings] set-size = 32 output-buss = 1 # or 0xff, or the port-mapped name midi-enabled = true button-offset = 0 button-rows = 8 button-columns = 8 \end{verbatim} \begin{itemize} \item \texttt{set-size}. Provides the set size. The default is 32, in a 4 x 8 grid. \item \texttt{output-buss}. Indicates where automation-display controls are to be sent. Specify the output buss to which the display device is attached. If port-mapping is enabled, the short name (nick-name) of the port can be used. \textsl{Never use the ALSA MIDI Through port for both control and display. Use an actual device port.} \item \texttt{midi-enabled}. If set to "true", then the MIDI control outputs will be used. It can be set to "false", while keeping the configuration in place for later usage. \item \texttt{button-offset}. This item provides a way to move a set of output controls (e.g. from a \textsl{Launchpad Mini}) to a different area of the output control device. Not yet supported. \item \texttt{button-rows}. Indicates the rows of the output control grid. Still in progress. \item \texttt{button-columns}. Indicates the columns of the output control grid. Still in progress. \end{itemize} \begin{verbatim} [midi-control-out] 0 [ 0x90 0 60 ] [ 0x90 0 15 ] [ 0x90 0 62 ] [ 0 0x90 0 12 ] 1 [ 0x90 1 60 ] [ 0x90 1 15 ] [ 0x90 1 62 ] [ 0 0x90 1 12 ] . . . 31 [ 0x90 31 60 ] [ 0x90 31 15 ] [ 0x90 31 62 ] [ 0 0x90 31 12 ] \end{verbatim} The first number is the pattern number of the pattern whose armed/muted status is to be shown. There are samples in the \texttt{data/linux} directory for some devices that one can adapt to other equipment. The mute-group buttons and their status can also be shown: \begin{verbatim} [mute-control-out] 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] 1 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] . . . 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] \end{verbatim} There are additional automation controls whose status can be displayed: \begin{verbatim} [automation-control-out] 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Panic 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Stop . . . 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Tap_BPM 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Quit \end{verbatim} See the sample files for more detailed descriptions. Also see \sectionref{sec:launchpad_mini}; it showns a fairly comprehensive setup based on the file \texttt{data/linux/qseq66-lp-mini.ctrl}. \subsubsection{'ctrl' File / Macro Control Output} \label{subsubsec:configuration_ctrl_macro_control_out} This section provides a way to send setup information to a MIDI device, such as the \textsl{Novation Launchpad Pro Mk3}. There is too much involved (especially for that device) to discuss in detail here, but this feature can be used to put a device into "programmer" mode at \textsl{Seq66} startup and back into "normal" mode at \textsl{Seq66} shutdown. Here is a hypothetical sample for the Mk3: \begin{verbatim} [macro-control-out] footer = 0xf7 header =0xf0 0x00 0x20 0x29 0x02 0x0e reset = $shutdown middlec = 0x90 0x3C 0x40 | 0x80 0x3C 0x00 shutdown = $live-mode startup = $programmer-mode live-mode = $header 0x0e 0x00 $footer programmer-mode = $header 0x0e 0x01 $footer \end{verbatim} The macros "footer", "header", "reset", "startup", and "shutdown" are written to the 'ctrl' file if not already present, though they won't have any useful definition. The first three are useful in other macros, while "startup" and "shutdown", if defined with actual data, are sent at the launch and shutdown, respectively, of \textsl{Seq66}. Features to note: \begin{itemize} \item These macros can be re-used (via the dollar sign) in other macros, to increase the readability of the macros and to reduce the chance for mistakes. \item Many devices required a device-specific SysEx header to precede the command-data, and the 0xf7 End-of-SysEx "footer" to end the command. \item Do not include time deltas; they are not needed for sending. \item In the "middlec" macro, the "|" separates two events, a Note On followed by a Note Off. \item There is no limit to the number of macros that can be defined. \item To send any of them directly at any time, go to the \textbf{Session} tab and select a macro from the drop-down list. There is no way to send them via MIDI control :-(. \item Macros can also be inserted at the current "L" marker from the Tools menu of the pattern editor. \end{itemize} % For editability, the default keys are specified in another TeX file. \input{defaultkeys} \subsubsection{'ctrl' File / AZERTY and QWERTZ Keyboards} \label{subsubsec:configuration_ctrl_azerty} This section makes it clear how to adapt to an AZERTY or QWERTY keyboard. Keystrokes are mapped to loop, mute-group, and automation control in the \texttt{qseq66.ctrl} file in the \texttt{\$HOME/.config/seq66} directory. \index{AZERTY}. \textbf{AZERTY}. Exit from \texttt{qseq66}, then copy the \texttt{qseq66-azerty.ctrl} file to the configuration/session directory. Make sure to edit \texttt{qseq66.rc} so that it has: \begin{verbatim} [midi-control-file] active = true name = "qseq66-azerty.ctrl" \end{verbatim} Note that the \texttt{qseq66-azerty.ctrl} file specifies the following: \begin{verbatim} [midi-control-settings] keyboard-layout = azerty \end{verbatim} This code tells \textsl{Seq66} to (1) ignore the automatic shift-lock feature when learning mutes; and (2) slightly alters the internal key-map to place a few extended ASCII characters in it. This is necessary because of the extra keys and because one must use the \texttt{Shift} key to get the numbers on the numeric row of that keyboard layout. \index{QWERTZ} \textbf{QWERTZ}. We do not currently supply this layout, even though we support it, since it is quite similar to the default \textbf{QWERTY} layout; the user can easily edit it. Exit from \texttt{qseq66}, then copy the \texttt{qseq66.ctrl} file to \texttt{qseq66-qwertz.ctrl} (or a better name). Edit the latter to change "z" to "y", "y" to "z", "Z" to "Y", and "Y" to "Z". Make sure to edit \texttt{qseq66.rc} so that it has: \begin{verbatim} [midi-control-file] "qseq66-qwertz.ctrl" \end{verbatim} In the 'ctrl' file, make sure to specify the keyboard layout: \begin{verbatim} [midi-control-settings] keyboard-layout = qwerty \end{verbatim} Available layouts are "normal" (or "qwerty"), "qwertz", or "azerty". For now; more may be added as called for by users. After starting and then exiting \texttt{qseq66}, the 'ctrl' file one has specified should still have the new settings. As usual, keep all your configurations in a safe place, such as a tar-file or ZIP-file. One issue with some keyboard layouts are "dead keys". These keys do nothing but modify the next key that follows, and will not emit a useable key code. One will see some sample files with the extension 'keymap'. These files are not yet useful, but we anticipate calling them into play when more people are asking for support for their non-US keyboards. \subsection{'mutes' File} \label{subsubsec:configuration_mute_group_control} This file starts with: \begin{verbatim} [mute-group-flags] load-mute-groups = midi # load the mute-groups from MIDI file save-mutes-to = both # save to this file, MIDI file, or both mute-group-rows = 4 # for now, stick with this value mute-group-columns = 8 # for now, stick with this value mute-group-selected = -1 # if 0 to 31, load that group at startup groups-format = binary # binary versus hex format for bits toggle-active-only = false # if true, toggle only mute-active patterns \end{verbatim} These variables are described in the sample 'mutes' file. The mute-in group consists of 32 lines (32 to 63), one for each pattern box. It provides a way to control the mute groups. A group is a set of sequences that can arm their playing state together; every group contains all 32 sequences in the \textsl{active} screen-set. This section is delimited by the \texttt{[mute-group]} construct. It controls 32 groups of mutes in the same way as defined for \texttt{[midi-control]}. A group is set of sequences that can toggle their playing state together. Every group contains all 32 sequences in the active screen set. \begin{verbatim} [mute-groups] 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 ] 1 [ 0 0 0 0 0 0 0 0 ] [ 0 0 0 0 0 0 0 0 ] [ . . .] [ 0 0 0 0 0 0 0 0 ] . . . 31 [ 0 0 0 0 0 0 0 0 ] [ 0 0 0 0 0 0 0 0 ] [ . . .] [ 0 0 0 0 0 0 0 0 ] \end{verbatim} In this group are the definitions of the state of the 32 (or more, once the support for larger sets is completely worked out) sequences in the playing screen set when a group is selected. Each set of brackets defines a group. \subsection{'drums'/'notemap' File} \label{subsec:configuration_drums} The 'drums' file is based on a similar file created using the \texttt{midicvt} application (also available on \textsl{GitHub}. This file is also referred to as the 'note-mapper' file. The extension \texttt{.notemap} can be used to indicate that the mapping is not for drum-sets. \begin{verbatim} [notemap-flags] map-type = drums gm-channel = 10 reverse = false \end{verbatim} These settings are explained in the sample 'drums' files. In addition, the file includes a number of sections that define the number and name of the original "drum", and the \textsl{General MIDI} device to which it corresponds. \begin{verbatim} [Drum 36] dev-name = "Bass Drum Gated Reverb" gm-name = "Bass Drum 1" dev-note = 36 gm-note = 36 \end{verbatim} This file is useful mainly when obtaining drum tracks recorded with devices in the early days of MIDI, where each vendor provided their own peculiar layout of percussion sounds. \subsection{'patches' File} \label{subsec:configuration_patches} This file is similar to the 'drums' file, but it remaps Program/Patch names, but not the patch numbers. These names are used when displaying patch events. This is a section from the sample file \texttt{PSS-790.patches}. \begin{verbatim} [ Patch 4 ] gm-name = "Electric Piano 1" gm-patch = 4 dev-name = "Harpsichord 1" \end{verbatim} \subsection{'palette' File} \label{subsec:configuration_palette} This file is described in the chapter on palettes, \sectionref{sec:palettes}. It can have an associated Qt style sheet file (\texttt{.qss}. Also see \sectionref{subsubsec:configuration_rc_style_sheet}. \subsection{'playlist' File} \label{subsec:configuration_playlist} This file is described in the chapter on playlists, \sectionref{sec:playlist}. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/defaultkeys.tex ================================================ %------------------------------------------------------------------------------- % defaultkeys %------------------------------------------------------------------------------- % % \file defaultkeys.tex % \library Documents % \author Chris Ahlstrom % \date 2021-12-04 % \update 2023-10-25 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides the default-keys section of seq66-user-manual.tex. % %------------------------------------------------------------------------------- \subsubsection{'ctrl' File / Keyboard / Default Assignments} \label{subsubsec:ctrl_keyboard_default_assignments} This section provides a table of the functions, key numbers ("ordinals"), names, and other information about the default \textsl{Seq66} keyboard assignments. Also see the installed \texttt{control\_keys.ods} spreadsheet, which might be more up-to-date. The following status tags apply in this table. We're trying to support all keystrokes, but Qt and international keyboards make it sometimes difficult. \begin{itemize} \item \textbf{(X)}. Avoid using. Applies to the modifier keys Alt, Ctrl, Meta, Shift, etc. Also applies to tricky internal keys like the grave (backtick). One can try them, however, to see what happens. \item \textbf{(D)}. In the default configuration. Applies especially to the default loop-control and mute-group control keys that reside in the main part of the keyboard. \item \textbf{(d)}. In the default configuration, but no functionality yet. \item \textbf{(p)}. Available as a place-holder (a hex value). \item \textbf{(H)}. Hard-wired keys like Esc, Space, and the main arrow keys. Avoid using. \item \textbf{(A)}. Available for usage. \item \textbf{(!)}. Needs investigation. \item \textbf{(?)}. Needs investigation. \item \textbf{*}. An asterisk added means "to do", as does the word "Reserved". \end{itemize} Because of the size of the table, and not wanting to deal with \textsl{LaTEX} long-table issues, we break the table into sections. These tables follow, some of them moved into following pages. The first section is \tableref{table:key_defaults_ctrl_keys}. Because of potential conflicts with user-interface keys, we do not recommend configuring them. However, we do use some of them for controls that are not yet effective. And, go ahead a try them if you want. The user rules! \begin{table}[htb!] \centering \caption{Key Defaults. Control Keys} \label{table:key_defaults_ctrl_keys} \begin{tabular}{l l l l l} \textbf{Function} & \textbf{Status} & \textbf{Ordinal} & \textbf{Name} & \textbf{Modifier} \\ None & (X) & 0x00 & "NUL" & Ctrl \\ None & (X) & 0x01 & "SOH" & Ctrl \\ None & (X) & 0x02 & "STX" & Ctrl \\ None & (X) & 0x03 & "ETX" & Ctrl \\ None & (X) & 0x04 & "EOT" & Ctrl \\ None & (X) & 0x05 & "ENQ" & Ctrl \\ None & (X) & 0x06 & "ACK" & Ctrl \\ None & (X) & 0x07 & "BEL" & Ctrl \\ None & (!) & 0x08 & "BS" & Ctrl \\ None & (X) & 0x09 & "HT" & Ctrl \\ None & (!) & 0x0a & "LF" & Ctrl \\ None & (X) & 0x0b & "VT" & Ctrl \\ None & (X) & 0x0c & "FF" & Ctrl \\ None & (X) & 0x0d & "CR" & Ctrl \\ None & (X) & 0x0e & "SO" & Ctrl \\ None & (X) & 0x0f & "SI" & Ctrl \\ None & (X) & 0x10 & "DLE" & Ctrl \\ None & (X) & 0x11 & "DC1" & Ctrl \\ None & (X) & 0x12 & "DC2" & Ctrl \\ None & (X) & 0x13 & "DC3" & Ctrl \\ None & (X) & 0x14 & "DC4" & Ctrl \\ None & (X) & 0x15 & "NAK" & Ctrl \\ None & (X) & 0x16 & "SYN" & Ctrl \\ None & (X) & 0x17 & "ETB" & Ctrl \\ None & (X) & 0x18 & "CAN" & Ctrl \\ None & (X) & 0x19 & "EM" & Ctrl \\ None & (X) & 0x1a & "SUB" & Ctrl \\ None & (X) & 0x1b & "ESC" & Ctrl \\ None & (X) & 0x1c & "FS" & Ctrl \\ None & (X) & 0x1d & "GS" & Ctrl \\ None & (X) & 0x1e & "RS" & Ctrl-Shift \\ None & (X) & 0x1f & "US" & Ctrl-Shift \\ \end{tabular} \end{table} The next section is \tableref{table:key_defaults_ascii_keys_1}. These deal with some of the pattern hot-keys ("Loop") and their shifted mute-group ("Mutes") keys. We do not show the numbers, as they are logically laid out on the U.S. keyboard. The Space and Period keys are hardwired ("*") for Start/Stop/Pause in the pattern piano roll and the song-editor piano roll. \begin{table}[htb!] \centering \caption{Key Defaults. ASCII Keys 1} \label{table:key_defaults_ascii_keys_1} \begin{tabular}{l l l l l} \textbf{Function} & \textbf{Status} & \textbf{Ordinal} & \textbf{Name} & \textbf{Modifier} \\ Start/Stop * & (H) & 0x20 & "Space" & none \\ Mutes & (D) & 0x21 & "!" & Shift \\ None & (A) & 0x22 & "\"" & Shift \\ Mutes & (D) & 0x23 & "\#" & Shift \\ Mutes & (D) & 0x24 & "\$" & Shift \\ Mutes & (D) & 0x25 & "\%" & Shift \\ Mutes & (D) & 0x26 & "\&" & Shift \\ BPM Up & (D) & 0x27 & "'" & Shift \\ None & (A) & 0x28 & "(" & Shift \\ None & (A) & 0x29 & ")" & Shift \\ Mutes & (D) & 0x2a & "*" & Shift \\ None & (A) & 0x2b & "+" & Shift \\ None & (D) & 0x2c & "," & none \\ Event Edit & (D) & 0x2d & "-" & Set-mode \\ Play/Pause * & (H) & 0x2e & "." & none \\ Slot Shift & (H) & 0x2f & "/" & none \\ Clear Mutes & (D) & 0x30 & "0" & none \\ Loop & (D) & 0x31 & "1" & none \\ Loop & (D) & 0x32 & "2" & none \\ Loop & (D) & 0x33 & "3" & none \\ Loop & (D) & 0x34 & "4" & none \\ Loop & (D) & 0x35 & "5" & none \\ Loop & (D) & 0x36 & "6" & none \\ Loop & (D) & 0x37 & "7" & none \\ Loop & (D) & 0x38 & "8" & none \\ None & (A) & 0x39 & "9" & none \\ None & (A) & 0x3a & ":" & Shift \\ BPM Down & (D) & 0x3b & ";" & none \\ Loop & (D) & 0x3c & "<" & Shift \\ Pattern Edit & (D) & 0x3d & "=" & Set-mode \\ None & (A) & 0x3e & ">" & Shift \\ None & (A) & 0x3f & "?" & Shift \\ \end{tabular} \end{table} The next section is \tableref{table:key_defaults_ascii_keys_2}. These deal mainly with the shifted mute-group ("Mutes") keys. \begin{table}[htb!] \centering \caption{Key Defaults. ASCII Keys 2} \label{table:key_defaults_ascii_keys_2} \begin{tabular}{l l l l l} \textbf{Function} & \textbf{Status} & \textbf{Ordinal} & \textbf{Name} & \textbf{Modifier} \\ Mutes & (D) & 0x40 & "@" & Shift \\ Mutes & (D) & 0x41 & "A" & Shift \\ Mutes & (D) & 0x42 & "B" & Shift \\ Mutes & (D) & 0x43 & "C" & Shift \\ Mutes & (D) & 0x44 & "D" & Shift \\ Mutes & (D) & 0x45 & "E" & Shift \\ Mutes & (D) & 0x46 & "F" & Shift \\ Mutes & (D) & 0x47 & "G" & Shift \\ Mutes & (D) & 0x48 & "H" & Shift \\ Mutes & (D) & 0x49 & "I" & Shift \\ Mutes & (D) & 0x4a & "J" & Shift \\ Mutes & (D) & 0x4b & "K" & Shift \\ Mutes & (A) & 0x4c & "L" & Shift \\ Mutes & (D) & 0x4d & "M" & Shift \\ Mutes & (D) & 0x4e & "N" & Shift \\ None & (A) & 0x4f & "O" & Shift \\ Song Record & (D) & 0x50 & "P" & Shift \\ Mutes & (D) & 0x51 & "Q" & Shift \\ Mutes & (D) & 0x52 & "R" & Shift \\ Mutes & (D) & 0x53 & "S" & Shift \\ Mutes & (D) & 0x54 & "T" & Shift \\ Mutes & (D) & 0x55 & "U" & Shift \\ Mutes & (D) & 0x56 & "V" & Shift \\ Mutes & (D) & 0x57 & "W" & Shift \\ Mutes & (D) & 0x58 & "X" & Shift \\ Mutes & (D) & 0x59 & "Y" & Shift \\ Mutes & (D) & 0x5a & "Z" & Shift \\ Screenset Down & (D) & 0x5b & "[" & none \\ Keep Queue & (D) & 0x5c & "\\" & none \\ Screenset Up & (D) & 0x5d & "]" & none \\ Mutes & (D) & 0x5e & "\^" & Shift \\ Grid Mute Mode & (A) & 0x5f & "\_" & Shift \\ Group Mute & (D) & 0x60 & "`" & none \\ \end{tabular} \end{table} The next section is \tableref{table:key_defaults_ascii_keys_3}. These are mainly devoted to the "Loop" keys, which are laid out in a logical order on the keyboard. \begin{table}[htb!] \centering \caption{Key Defaults. ASCII Keys 3} \label{table:key_defaults_ascii_keys_3} \begin{tabular}{l l l l l} \textbf{Function} & \textbf{Status} & \textbf{Ordinal} & \textbf{Name} & \textbf{Modifier} \\ Loop & (D) & 0x61 & "a" & none \\ Loop & (D) & 0x62 & "b" & none \\ Loop & (D) & 0x63 & "c" & none \\ Loop & (D) & 0x64 & "d" & none \\ Loop & (D) & 0x65 & "e" & none \\ Loop & (D) & 0x66 & "f" & none \\ Loop & (D) & 0x67 & "g" & none \\ Loop & (D) & 0x68 & "h" & none \\ Loop & (D) & 0x69 & "i" & none \\ Loop & (D) & 0x6a & "j" & none \\ Loop & (D) & 0x6b & "k" & none \\ Group Learn & (D) & 0x6c & "l" & none \\ Loop & (D) & 0x6d & "m" & none \\ Loop & (D) & 0x6e & "n" & none \\ Queue & (D) & 0x6f & "o" & none \\ None & (A) & 0x70 & "p" & none \\ Loop & (D) & 0x71 & "q" & none \\ Loop & (D) & 0x72 & "r" & none \\ Loop & (D) & 0x73 & "s" & none \\ Loop & (D) & 0x74 & "t" & none \\ Loop & (D) & 0x75 & "u" & none \\ Loop & (D) & 0x76 & "v" & none \\ Loop & (D) & 0x77 & "w" & none \\ Loop & (D) & 0x78 & "x" & none \\ Loop & (D) & 0x79 & "y" & none \\ Loop & (D) & 0x7a & "z" & none \\ None & (A) & 0x7b & "\{" & Shift \\ Oneshot Queue & (D) & 0x7c & "|" & Shift \\ None & (A) & 0x7d & "\}" & Shift \\ Panic Button & (D) & 0x7e & "~" & Shift \\ None & (A) & 0x7f & "DEL" & none \\ \end{tabular} \end{table} The next section is \tableref{table:key_defaults_extended_keys_1}. Some of these keys (Esc and the arrow keys) are hardwired, as indicated by an asterisk ("*"). Do not redefine them. Also note the keys with hexadecimal number names (e.g. \texttt{0x88}). These are keys that we have not yet found mapped to a keystroke by the \textsl{Qt} keystroke system. Some of them are used as placeholders in the default key assignments of automation-control functions implemented only via MIDI control. \begin{table}[htb!] \centering \caption{Key Defaults. Extended Keys 1} \label{table:key_defaults_extended_keys_1} \begin{tabular}{l l l l l} \textbf{Function} & \textbf{Status} & \textbf{Ordinal} & \textbf{Name} & \textbf{Modifier} \\ Stop * & (H) & 0x80 & "Esc" & none \\ None & (A) & 0x81 & "Tab" & none \\ None & (A) & 0x82 & "BkTab" & Shift \\ Solo & (A) & 0x83 & "BkSpace" & none \\ None & (?) & 0x84 & "Return" & none \\ None & (?) & 0x85 & "Enter" & Keypad \\ Snapshot & (D) & 0x86 & "Ins" & none \\ None & (A) & 0x87 & "Del" & none \\ None & (p) & 0x88 & "0x88" & none \\ None & (p) & 0x89 & "0x89" & none \\ None & (X) & 0x8a & "SysReq" & none \\ None & (X) & 0x8b & "Clear" & none \\ None & (d) & 0x8c & "0x8c" & none \\ None & (d) & 0x8d & "0x8d" & none \\ None & (d) & 0x8e & "0x8e" & none \\ None & (d) & 0x8f & "0x8f" & none \\ Play Screenset & (D) & 0x90 & "Home" & none \\ None & (A) & 0x91 & "End" & none \\ Previous Song * & (H) & 0x92 & "Left" & none \\ Prev. Playlist * & (H) & 0x93 & "Up" & none \\ Next Song * & (H) & 0x94 & "Right" & none \\ Next Playlist .* & (H) & 0x95 & "Down" & none \\ BPM Page Up & (D) & 0x96 & "PageUp" & none \\ BPM Page Down & (D) & 0x97 & "PageDn" & none \\ None & (X) & 0x98 & "Shift\_L" & Shift \\ None & (X) & 0x99 & "Ctrl\_L" & Ctrl \\ None & (X) & 0x9a & "Meta" & Meta \\ None & (X) & 0x9b & "Alt\_L" & Alt \\ None & (X) & 0x9c & "CapsLk" & none \\ None & (X) & 0x9d & "NumLk" & none \\ None & (X) & 0x9e & "ScrlLk" & none \\ None & (p) & 0x9f & "0x9f" & none \\ \end{tabular} \end{table} The next section is \tableref{table:key_defaults_extended_keys_2}. The main definitions here are for the "Function" and "Shift-Function" keys. Also note that one should avoid overriding the modifier keys. (But hey, see what happens!) \begin{table}[htb!] \centering \caption{Key Defaults. Extended Keys 2} \label{table:key_defaults_extended_keys_2} \begin{tabular}{l l l l l} \textbf{Function} & \textbf{Status} & \textbf{Ordinal} & \textbf{Name} & \textbf{Modifier} \\ Top (beginning) & (D) & 0xa0 & "F1" & none \\ Next Playlist & (D) & 0xa1 & "F2" & none \\ Next Song & (D) & 0xa2 & "F3" & none \\ Follow Transport & (D) & 0xa3 & "F4" & none \\ Rew (depr) & (D) & 0xa4 & "F5" & none \\ FF (depr) & (D) & 0xa5 & "F6" & none \\ Song Pointer & (D) & 0xa6 & "F7" & none \\ Toggle Mutes & (D) & 0xa7 & "F8" & none \\ Tap BPM & (D) & 0xa8 & "F9" & none \\ Song/Live Mode & (D) & 0xa9 & "F10" & none \\ JACK Transport & (D) & 0xaa & "F11" & none \\ Menu Mode (depr) & (D) & 0xab & "F12" & none \\ None & (X) & 0xac & "Super\_L" & none \\ None & (X) & 0xad & "Super\_R" & none \\ None & (X) & 0xae & "Menu" & none \\ None & (X) & 0xaf & "Hyper\_L" & none \\ None & (X) & 0xb0 & "Hyper\_R" & none \\ None & (X) & 0xb1 & "Help" & none \\ None & (X) & 0xb2 & "Dir\_L" & none \\ None & (X) & 0xb3 & "Dir\_R" & none \\ Record Overdub & (D) & 0xb4 & "Sh\_F1" & Shift \\ Record Overwrite & (D) & 0xb5 & "Sh\_F2" & Shift \\ Record Expand & (D) & 0xb6 & "Sh\_F3" & Shift \\ Record One-shot & (D) & 0xb7 & "Sh\_F4" & Shift \\ Grid Loop Mode & (d) & 0xb8 & "Sh\_F5" & Shift \\ Grid Record Mode & (d) & 0xb9 & "Sh\_F6" & Shift-mode \\ Grid Copy Mode & (d) & 0xba & "Sh\_F7" & Shift-mode \\ Grid Paste Mode & (d) & 0xbb & "Sh\_F8" & Shift-mode \\ Grid Clear Mode & (d) & 0xbc & "Sh\_F9" & Shift-mode \\ Grid Delete Mode & (d) & 0xbd & "Sh\_F10" & Shift-mode \\ Grid Thru Mode & (d) & 0xbe & "Sh\_F11" & Shift-mode \\ Grid Solo Mode & (d) & 0xbf & "Sh\_F12" & Shift-mode \\ Reserved & (D) & 0xc0 & "KP\_Ins" & Keypad \\ \end{tabular} \end{table} The next section is \tableref{table:key_defaults_extended_keys_3}. Note the some of the keypad keys are assigned, but there are many available. Presumably the keypad arrow keys are distinct from the main arrow keys, but that has not yet been tested. \begin{table}[htb!] \centering \caption{Key Defaults. Extended Keys 3} \label{table:key_defaults_extended_keys_3} \begin{tabular}{l l l l l} \textbf{Function} & \textbf{Status} & \textbf{Ordinal} & \textbf{Name} & \textbf{Modifier} \\ None & (A) & 0xc1 & "KP\_Del" & Keypad \\ None & (X) & 0xc2 & "Pause" & Shift \\ None & (X) & 0xc3 & "Print" & Shift \\ Replace & (D) & 0xc4 & "KP\_Home" & Keypad \\ None & (A) & 0xc5 & "KP\_End" & Keypad \\ None & (?) & 0xc6 & "KP\_Left" & Keypad \\ None & (?) & 0xc7 & "KP\_Up" & Keypad \\ None & (?) & 0xc8 & "KP\_Right" & Keypad \\ None & (?) & 0xc9 & "KP\_Down" & Keypad \\ None & (A) & 0xca & "KP\_PageUp" & Keypad \\ None & (A) & 0xcb & "KP\_PageDn" & Keypad \\ None & (X) & 0xcc & "KP\_Begin" & none \\ None & (p) & 0xcd & "0xcd" & none \\ None & (p) & 0xce & "0xce" & none \\ None & (p) & 0xcf & "0xcf" & none \\ Record Increment & (D) & 0xd0 & "KP\_*" & Keypad \\ Reset Play-set & (D) & 0xd1 & "KP\_+" & Keypad \\ None & (X) & 0xd2 & "KP\_,", & Keypad \\ Quan Record Incr. & (D) & 0xd3 & "KP\_-" & Keypad \\ Set Screenset & (D) & 0xd4 & "KP\_." & Shift-Keypad \\ None & (A) & 0xd5 & "KP\_/" & Keypad \\ None & (p) & 0xd6 & "0xd6" & none \\ None & (X) & 0xd7 & "Shift\_R" & Shift \\ None & (X) & 0xd8 & "Ctrl\_R" & Ctrl \\ None & (D) & 0xd9 & "KP\_." & Keypad \\ None & (X) & 0xda & "Alt\_R" & Group \\ None & (X) & 0xdb & "Shift\_Lr" & none \\ None & (X) & 0xdc & "Shift\_Rr" & none \\ None & (X) & 0xdd & "Ctrl\_Lr" & none \\ None & (X) & 0xde & "Ctrl\_Rr" & none \\ Quit/Exit & (X) & 0xdf & "Quit" & MIDI-control-only \\ \end{tabular} \end{table} The next section is \tableref{table:key_defaults_extended_keys_4}. There are many functions assigned in this section, but no real \textsl{Qt} keys defined. So this section is somewhat reserved for additional MIDI controls that will not have corresponding keystrokes. There are a lot more MIDI controls than keystrokes, especially leaving out the Ctrl, Shift, Alt, Super, and Hyper key combinations, which should generally be reserved for the operating system, window manager, and \textsl{Qt} user interface. \begin{table}[htb!] \centering \caption{Key Defaults. Extended Keys 4} \label{table:key_defaults_extended_keys_4} \begin{tabular}{l l l l l} \textbf{Function} & \textbf{Status} & \textbf{Ordinal} & \textbf{Name} & \textbf{Modifier} \\ Grid Veloc. Mode & (d) & 0xe0 & "0xe0" & none \\ Grid Double Mode & (d) & 0xe1 & "0xe1" & none \\ Grid Quant None & (d*) & 0xe2 & "0xe2" & none \\ Grid Quant Full & (d*) & 0xe3 & "0xe3" & none \\ Grid Quant Tight & (d*) & 0xe4 & "0xe4" & none \\ Grid Quant Random & (d*) & 0xe5 & "0xe5" & none \\ Grid Quant Jitter & (d*) & 0xe6 & "0xe6" & none \\ Grid Quant Reser. & (d) & 0xe7 & "0xe7" & none \\ BBT/HMS & (d*) & 0xe8 & "0xe8" & none \\ L/R Loop Mode & (d*) & 0xe9 & "0xe9" & none \\ Undo Record & (d*) & 0xea & "0xea" & none \\ Redo Record & (d*) & 0xeb & "0xeb" & none \\ Transpose Song & (d*) & 0xec & "0xec" & none \\ Copy Set & (d*) & 0xed & "0xed" & none \\ Paste Set & (d*) & 0xee & "0xee" & none \\ Toggle Tracks & (d*) & 0xef & "0xef" & none \\ Set Mode Normal & (p*) & 0xf0 & "0xf0" & none \\ Set Mode Auto & (p*) & 0xf1 & "0xf1" & none \\ Set Mode Adding & (p*) & 0xf2 & "0xf2" & none \\ Set Mode All & (p*) & 0xf3 & "0xf3" & none \\ None & (p) & 0xf4 & "0xf4" & none \\ None & (p) & 0xf5 & "0xf5" & none \\ None & (p) & 0xf6 & "0xf6" & none \\ None & (p) & 0xf7 & "0xf7" & none \\ None & (p) & 0xf8 & "0xf8" & none \\ Visibility & (D) & 0xf9 & "0xf9" & none \\ Save Session & (D) & 0xfa & "0xfa" & none \\ Reserved & (D) & 0xfb & "0xfb" & none \\ Reserved & (D) & 0xfc & "0xfc" & none \\ Reserved & (D) & 0xfd & "0xfd" & none \\ Reserved & (D) & 0xfe & "0xfe" & none \\ Terminator & (X) & 0xff & "Null\_ff" & Illegal-value \\ \end{tabular} \end{table} %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/docs-structure.tex ================================================ %------------------------------------------------------------------------------- % docs-structure %------------------------------------------------------------------------------- % % \file docs-structure.tex % \library Documents % \author Chris Ahlstrom % \date 2015-04-20 % \update 2025-06-01 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % This "include file" provides LaTeX options for a document. % % Note that enumitem is an extension of enumerate, and comes from % Debian's texlive-latex-recommended package. % %------------------------------------------------------------------------------- \usepackage{enumitem} % setting the whitespace between and within lists \setlistdepth{9} \setlist{noitemsep} % spacing within the list \usepackage{comment} % For the comment macro \usepackage{color} % provide colors? \usepackage{nameref} % Provide references by name instead of number \usepackage[obeyspaces]{url} % Required for including URLs, ahead of hyperref \usepackage[colorlinks=true,linkcolor=webgreen,filecolor=webbrown,citecolor=webgreen]{hyperref} \definecolor{webgreen}{rgb}{0,.5,0} \definecolor{webbrown}{rgb}{.6,0,0} \usepackage{ragged2e} % For underfull boxes in the bibliography \usepackage{wasysym} % For smileys \usepackage{verbatim} % For the comment macro \usepackage{amsthm} % Helps avoid "destination with same \usepackage[hypcap]{caption} % make labels point to figure, not the caption \usepackage[pdftex]{graphicx} % Required for including images \graphicspath{{../images/}} % Set the default folder for images \usepackage{float} % For more control of location of Figures \usepackage[T1]{fontenc} % Remove font warnings for textleftbrace, etc. \usepackage{geometry} % Page & text layout \geometry{ letterpaper, top=2.5cm, bottom=2.5cm, left=2.5cm, right=2.5cm } % Experimental: remove indent from all paragraphs and set whitespace between % paragraphs. These don't work! But see seq66-user-manual.tex lines 80-81. % % \usepackage{parskip} % \setlength{\parindent}{0cm} % \setlength{\parskip}{2ex plus 0.5ex minus 0.2ex} % whitespace between paragraphs \usepackage{longtable} % For making multi-page tables \usepackage{makeidx} % For making an index % Try to reduce the space before or after verbatim sections. % Doesn't affect the spacing after the verbatim, though. % % Fonts sizes are "tiny", "scriptsize", "footnotesize", "small", % "normalsize", "large", "Large", and "LARGE". \usepackage{etoolbox} \makeatletter \preto{\@verbatim}{\topsep=6pt \partopsep=0pt} \patchcmd{\@verbatim} {\verbatim@font} {\verbatim@font\footnotesize} {}{} \makeatother % Let's try to reduce the size of quotations. \usepackage{relsize,etoolbox} % http://ctan.org/pkg/{relsize,etoolbox} \AtBeginEnvironment{quotation}{\smaller} % Step font down one size relatively % For the MIDI Implementation Chart \usepackage{makecell} % This package isn't available easily on CentOS: % % \usepackage[subtle]{savetrees} % For tightening document vertical spacing \hypersetup{ % HYPERLINKS % draft, % Uncomment removes links (e.g. for B&W printing) colorlinks=true, breaklinks=true, bookmarksnumbered, urlcolor=webbrown, linkcolor=blue, % RoyalBlue citecolor=webgreen, pdftitle={}, pdfauthor={\textcopyright}, pdfsubject={}, pdfkeywords={}, pdfcreator={pdfLaTeX}, pdfproducer={LaTeX with hyperref and ClassicThesis} } % Make an "enumber" style that makes all levels of enumerated lists show % arabic numerals. \newlist{enumber}{enumerate}{10} \setlist[enumber]{nolistsep,label=\arabic*.} % Make "paragraph" a fourth level, and make it shown in the table of % contents. \makeatletter \renewcommand\paragraph{\@startsection{paragraph}{4}{\z@}% {-2.5ex\@plus -1ex \@minus -.25ex}% {1.25ex \@plus .25ex}% {\normalfont\normalsize\bfseries}} \makeatother \setcounter{secnumdepth}{4} % how many sectioning levels to assign numbers to \setcounter{tocdepth}{4} % how many sectioning levels to show in ToC % Provide a way of counting user-interface items without putting them in an % enumberation. \newcounter{ItemCounter} % Makes a numbered paragraph out of an item, and allows two index entries % for it. \newcommand{\itempar}[2] { \noindent \stepcounter{ItemCounter} \textbf{\arabic{ItemCounter}. #1.} \index{#1} \index{#2} } % Provides for two forms of an option, as might be shown in a man page. \newcommand{\optionpar}[2] { \textbf{\texttt{#1}} \textbf{\texttt{#2}} \\ \index{#1} \index{#2} } % Similar, but with no line break. \newcommand{\optionline}[2] { \textbf{\texttt{#1}} \textbf{\texttt{#2}} \index{#1} \index{#2} } % Now deprecated in preference to \itempar \newcommand{\settingdesc}[2] { \textbf{#1} \index{#1} \index{#2} } % Reference to a configuration file setting % % \configref{xxx}{xxxxx}{xxxx}. \newcommand{\configref}[3] { \index{#1!#2} \-\hspace{2cm} \textsl{qseq66.#1}: \texttt{[#2] #3} } % Make a full reference to a figure using its number, its name, and its page % number. Very useful if you have a hard-copy of the document to deal with. \newcommand{\figureref}[1] { Figure~\ref{#1} "\nameref{#1}" on page~\pageref{#1}\ignorespaces } % Make a full reference to a section using its number, its name, and its page % number. Very useful if you have a hard-copy of the document to deal with. \newcommand{\sectionref}[1] {% section~\ref{#1} "\nameref{#1}", page~\pageref{#1}\ignorespaces } \newcommand{\Sectionref}[1] {% Section~\ref{#1} "\nameref{#1}", page~\pageref{#1}\ignorespaces } % Make a full reference to a "paragraph" using its number, its name, and % its page number. Very useful if you have a hard-copy of the document to % deal with. \newcommand{\paragraphref}[1] {% paragraph~\ref{#1} "\nameref{#1}" on page~\pageref{#1}\ignorespaces } % Make a full reference to a table using its number, its name, and its page % number. Very useful if you have a hard-copy of the document to deal with. \newcommand{\tableref}[1] {% table~\ref{#1} "\nameref{#1}" on page~\pageref{#1}\ignorespaces } % For lining up enumerated items. Doesn't really work well, better % to create a table. \newcommand{\itab}[1]{\hspace{0em}\rlap{#1}} \newcommand{\tab}[1]{\hspace{.1\textwidth}\rlap{#1}} % Change the fragction of the page that can be filled with graphics from 0.7 % to 0.9. \renewcommand\floatpagefraction{.9} \renewcommand\dblfloatpagefraction{.9} \renewcommand\topfraction{.9} \renewcommand\dbltopfraction{.9} \renewcommand\bottomfraction{.9} \raggedbottom % avoid excessive vertical justification %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/event_editor.tex ================================================ %------------------------------------------------------------------------------- % event_editor %------------------------------------------------------------------------------- % % \file event_editor.tex % \library Documents % \author Chris Ahlstrom % \date 2016-01-02 % \update 2026-04-14 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % %------------------------------------------------------------------------------- \section{Event Editor} \label{sec:event_editor} The \textsl{Seq66} \textbf{Event Editor} tab is used to view and edit, in detail, the events present in a loop / sequence / pattern / track. It is accessed by right-clicking on a pattern in the \textbf{Live} frame, then selecting the \textbf{Edit pattern in tab} menu entry. The default keystroke combination for this action is to use the \textsl{minus} key followed by the desired pattern's hot-key. The event editor has advanced a bit since its inception. It is a basic editor for simple edits, viewing, and trouble-shooting. It is disabled if recording a pattern, to avoid refresh issues while recording. Viewing and scrolling work; editing, deleting, and inserting events work. But there are many possible interactions between event links (Note Off events linked to Note On events, for example), performance triggers, and the pattern, performance, and event editor dialogs. Surely some bugs still lurk. If anything bad happens, do \textsl{not} press the \textbf{Save to Sequence} button! If the application aborts, let us know! Here are the major "issues": \begin{enumerate} \item It requires the user to know the details about MIDI events and data values, though now most MIDI events are shown by name in various drop-down menus. \item For safety, it does not detect any changes made to the sequence in the pattern editor; we might add a refresh button. \item It does not have an undo function. Instead, just don't save! \item It cannot mark more than one event for deletion or modification. However, if one note event is deleted, the corresponding linked note event is also deleted. \item There is no support for dragging and dropping of events. \end{enumerate} The event editor is a good way to see the events in a sequence, and to delete or modify problematic events. Additionally, it can be used to add \textbf{Set Tempo} and other meta events. \index{sequence extension} \index{pattern extension} If an event is added that has a time-stamp beyond the current length of the sequence, then the length of the sequence is extended. Unlike the event pane in the pattern editor, the event-editor dialog shows all types of events at once. \begin{figure}[H] \centering \includegraphics[scale=0.65]{event-editor/event-editor-tab.png} \caption{Event Editor Window} \label{fig:event_editor_window} \end{figure} The figure above shows an earlier version of the event editor. Additional functionality is shown later in this chapter. The event-editor dialog is fairly complex. For exposition, we break it down into a few sections: \begin{enumber} \item \textbf{Event Frame} \item \textbf{Info Panel} \item \textbf{Edit Fields} \item \textbf{Bottom Buttons} \end{enumber} The event frame is a list of events, which can be traversed, and edited. The fields in the right panel show the name of the pattern containing the events and other information about the pattern. The edit fields provide text fields for viewing and entering information about the current event, and buttons to delete, insert, and modify events. The bottom buttons allow changes to be saved and the editor to be closed. The following sections described these items in detail. \subsection{Event Editor / Event Frame} \label{subsec:event_editor_frame} The event frame is the event-list shown on the left side of the event editor. It is accompanied by a vertical scroll-bar, for moving one line or one page at a time. Mouse or touchpad scrolling can be used to move up and down in the event list. Depending on the window manager theme, the currently-selected event is highlighted. (We have been trying to get this table to auto-stretch vertically when the main window is vertically maximized, but have not succeeded so far. Qt!) \subsubsection{Event Frame / Data Items} \label{subsec:event_frame_data} The event frame shows a list of numbered events, one per line. The currently-selected event is shown in the edit fields. Here is an example of the data line for a MIDI event: \begin{verbatim} 17 003:3:128 Note On -- 3 69 107 003:4:96 \end{verbatim} This line consists of the following parts: \begin{enumber} \item \textbf{Index Number} \item \textbf{Time Stamp} \item \textbf{Event Name} \item \textbf{Bus Number} (new) \item \textbf{Channel Number} \item \textbf{Data Bytes D0 and D1} \item \textbf{Link} \end{enumber} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Index Number}{event editor!index number} Displays the index number of the event. This number is purely for reference, and is not part of the event. Events in the pattern are numbered from 1 to the number of events in the pattern. \itempar{Time Stamp}{event editor!time stamp} Displays the time stamp of the event, which indicates the cumulative time of the event in the pattern. It is displayed in the format of "measure:beat:divisions" (e.g. B:B:T). The measure values start from 1, and range up to the number of measures in the pattern. The beat values start from 1, and range up to the number of beats in the measure. The division values range from 0 up to one less than the \index{ppqn} PPQN (pulses per quarter note) value for the whole song. \index{ppqn!\$ shortcut} As a shortcut, one can use the dollar sign ("\$") to represent PPQN-1. If the \textbf{Tick Time} box is checked, then the timestamps are shown in units of "ticks" (MIDI pulses, also called "divisions"). \itempar{Event Name}{event editor!event name} Displays the name of the event. The event name indicates what kind of MIDI event it is. See \sectionref{subsec:event_editor_fields}. \itempar{Bus Number}{event editor!bus number} Shows the bus that the event came in on, or a hyphen if not applicable. This value is currently not saved with the event, so is of limited utility. \itempar{Channel Number}{event editor!channel number} Shows the channel number (for channel-events only) re 1, not 0. (For the user, MIDI channels always range from 1 to 16. Internally, they range from 0 to 15.) \itempar{Data Bytes D0 and D1}{event editor!data bytes} Shows the one or two data bytes for the event. The byte is shown in one two formats, hexadecimal and decimal, depending on the setting of the \textbf{Hex} check-box. For text events, a small portion of the text is shown. The full text is shown in the text field below the D0 and D1 values. Note Off, Note On, and Aftertouch events requires a byte for the key (0 to 127) and a byte for the velocity (also 0 to 127). Control Change events require a control code and a value for that control code. Pitch wheel events require two bytes to encode the full range of pitch changes. Program change events require only a byte value to pick the patch or program (instrument) to be used for the sequence. The Channel Pressure event requires only a one-byte value. Tempo requires a number (e.g. "120.3") to be typed in. \itempar{Links}{event editor!links} Note events are linked together; each Note On is linked to the corresponding Note Off, and vice versa. \subsubsection{Event Frame / Navigation} \label{subsec:event_frame_navigation} Moving about in the event frame is straightforward, but has some wrinkles to note. Navigation with the mouse is done by moving to the desired event and clicking on it. The event becomes highlighted, and its data items are shown in the "info panel". There is no support for dragging and dropping events in the event frame. There is no support for selecting multiple events. The scrollbar can be used to move within the frame, either by one line at a time, or by a page at a time. A page is defined as one frame's worth of lines, minus 5 lines, for some overlap in paging. Navigation with keystrokes is also supported, for the Up and Down arrows and the Page-Up and Page-Down keys. Note that using the Up and Down arrows by holding them down for awhile causes autorepeat to kick in. Use the scrollbar or page keys to move through multiple pages. Home and End also work. \subsection{Event Editor / Info Panel} \label{subsec:event_editor_info} The "info panel" is a read-only list of properties on the top right of the event editor. It serves to remind the used of the pattern being edited and some characteristics of the pattern and the whole song. It also includes one button. Five items are shown: \begin{enumber} \item \textbf{Sel Linked}. (New; shown below.) If this button is checked, then selecting a Note On or Off event also selects the opposite, linked note. This allows for easy deletion of a complete Note pair. \item \textbf{Sequence Number and Name}. Shows the number and name of the pattern. The name can be edited here or in the pattern editor. \item \textbf{Buss Number, Channel Number, Time Signature, PPQN}. Shows the output buss number, the channel number, the time signature, and the "parts per quarter note", or resolution of the whole song. The default PPQN of \textsl{Seq66} is 192. % \item \textbf{Sequence Channel}. % In \textsl{Seq66}, the channel number is a property of the % pattern. All channel events in the pattern get routed to the same % channel, even if somehow the event itself specifies a different % channel. \item \textbf{Sequence Size in Measures, ticks, and events}. Displays the current number of events in the pattern. This number changes as events are inserted or deleted. \end{enumber} \subsection{Event Editor / Edit Fields} \label{subsec:event_editor_fields} The edit fields show the values of the currently-selected event. They allow changing an event, adding a new event, or deleting the currently-selected event. These have been greatly updated for user convenience in the last few releases of \textsl{Seq66}. The next figure shows version 0.99.22's state. \begin{figure}[H] \centering \includegraphics[scale=0.65]{event-editor/event-editor-new.png} \caption{Event Editor / New Arrangement} \label{fig:event_editor_new_arrangement} \end{figure} \begin{enumber} \item \textbf{Category}. This dropdown shows what kind of event is selected, and can also be used to insert specific events. The event categories are: \begin{itemize} \item \textbf{Channel Message}. \item \textbf{System Message}. \item \textbf{Meta Event}. \item \textbf{SecSpec Event}. \end{itemize} See below for more information about supported events. \item \textbf{Time}. Shows the event timestamp in B:B:T notation. It can be edited to move an event in time. \item \textbf{Event}. This dropdown shows the event name. It can also be used to select an event that is appropriate for the selected event category. \item \textbf{Sel}. This "selection" button is disabled unless either Control Change or Program Change is selected. See the next two figures. \item \textbf{D0}. Shows data byte 1, e.g. the note number for a note event. It can also be edited. \item \textbf{D1}. Shows data byte 2, e.g. the velocity for a note event. It can also be edited. \item \textbf{Tick Time}. If check-marked, the \textbf{Time} column in the event list is shown as ticks (pulses, divisions) instead of B:B:T. \item \textbf{Hex}. If check-marked, the \textbf{D0} and \textbf{D1} columns in the event list, as well as the editable \textbf{D0} and \textbf{D1} fields, as shown in hexadecimal notation. For example, D0 = 67 is shown as "0x43". \item \textbf{Text}. This field is for the display of certain events, such as pitch data. It is also used for entering text data for various text events. \end{enumber} When the Control Change event is selected, the \textsl{Sel} button changes to \textsl{Ctrl}, and clicking it brings up a nested drop-down menu. This menu shows either the default MIDI control change events, or the event names as specified in the 'usr' file (see \sectionref{subsec:configuration_usr}). We have a tentative library for handling MIDNAM files, but it's not yet suitable for integration into \textsl{Seq66}. \begin{figure}[H] \centering \includegraphics[scale=0.65]{event-editor/control-change-button-dropdown.png} \caption{Control Change Button Drop-Down} \label{fig:event_editor_control_change_dropdown} \end{figure} When the Program Change event is selected, the \textsl{Sel} button changes to \textsl{Ctrl}, and clicking it brings up a nested drop-down menu. This menu shows either the default MIDI program change events, or the event names as specified in the 'patches' file (see \sectionref{subsec:configuration_patches}). We have a tentative library for handling MIDNAM files, but it's not yet suitable for integration into \textsl{Seq66}. \begin{figure}[H] \centering \includegraphics[scale=0.65]{event-editor/program-change-button-dropdown.png} \caption{Program Change Button Drop-Down} \label{fig:event_editor_program_change_dropdown} \end{figure} \textbf{Important}: changes made in the event editor are \textsl{not written} to the sequence until the \textbf{Save} button is clicked. If one messes up an edit field, just click on the event again; all the fields will be filled in again. That's as much "undo" as the event-editor offers at this time, other than closing without saving. \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Category}{event editor!event category} Displays the event category of the event. All channel events, most system messages and many meta events can be handled, even system-exclusive events. \begin{figure}[H] \centering \includegraphics[scale=0.65]{event-editor/message-category-dropdown.png} \caption{Event Category List} \label{fig:event_editor_category_dropdown} \end{figure} There are four types of messages handled by \textsl{Seq66}: Channel, System, Meta, and SeqSpec. Seqspec messages are stored when a pattern is saved, but they are not part of the pattern's event list. Hence this item is grayed out; these message values are shown in various parts of the user interface. See the table in \sectionref{subsec:midi_format_meta_format}. \itempar{Time}{event editor!event timestamp} Displays the timestamp of the selected event in B:B:T format. "measure:beat:division" format is fully supported. We allow editing (but not display) of the timestamp in pulse (divisions) format and "hour:minute:second.fraction" format, but there may be bugs to work out. If one wants to delete or modify an event, this field does not need to be modified. If this field is modified, and the \textbf{Modify} button is pressed, then the event will be moved This field can locate a new event at a specific time. If the time is not in the current frame, the frame will move to the location of the new event and make it the current event. \itempar{Time}{event editor!event time} Shows the time of the event in the format of "measure:beat:divisions" (only). This field can be edited to change the time of the event. \itempar{Event}{event editor!event name} Displays the name of the event, and allows entry of an event name. The event name indicates what kind of MIDI event it is. The following event names are supported for Channel events: \begin{enumber} \item \textbf{Note Off} \item \textbf{Note On} \item \textbf{Aftertouch} \item \textbf{Control} \item \textbf{Program} \item \textbf{Channel Pressure} \item \textbf{Pitch Wheel} \end{enumber} Selecting one of these names from the dropdown changes the kind of event if the event is modified. % Abbreviations and case-insensitivity can be used to % reduce the effort of typing. % Also, if \textbf{Control} or % \textbf{Program} are selected, then a data drop-down box is available % to select either the controller or % the instrument patch (program), and it can fill in the data values. If \textbf{Control} or \textbf{Program} are selected, then the name of item-number entered into \textbf{D0} is shown in the text area. In the future, we may make these configurable and add selection drop-downs, with the control-change following the settings in the 'usr' file, and the program-change being made from a (new) 'patches' file. Currently the programs are General MIDI. If the \textbf{Meta Event} category is selected, the following events are shown: \begin{figure}[H] \centering \includegraphics[scale=0.75]{event-editor/meta-event-dropdown.png} \caption{Meta Event List} \label{fig:event_editor_meta_dropdown} \end{figure} If the \textbf{System Message} category is selected, the following events are shown: \begin{figure}[H] \centering \includegraphics[scale=0.85]{event-editor/system-event-dropdown.png} \caption{System Message List} \label{fig:event_editor_system_dropdown} \end{figure} At present, many of these messages, especially the textual messages, should be editable. Report any issues. \itempar{Channel}{event editor!channel} This dropdown shows the channel of the current event, ranging from 1 to 16. If the event is not a channel message, then \textbf{None} is shown. \itempar{D0}{event editor!data byte 1} Allows modification of the first data byte of the event. One must know what one is doing. The scanning of the digits is very simple: start with the first digit, and convert until a non-digit is encountered. The data-byte value can be entered in decimal notation, or, if prepended with "0x", in hexadecimal notation. For \textbf{Control} or \textbf{Program}, the name of item-number in \textbf{D0} is shown in the text area. For a Tempo setting only this field is used; Data Byte 2 is ignored. Enter a Tempo value, such as "120", and then click \textbf{Insert}. The value is converted to the 3 bytes of a tempo event, and then added at the given timestamp. (Screen refresh is not perfect yet, but reloading the pattern shows the correct tempo.) \itempar{D1}{event editor!data byte 2} Allows modification of the second data byte of the event (if applicable to the event). One must know what one is doing. The scanning of the digits is as noted above. \itempar{Tick time}{event editor!tick time} Selecting this check-box shows the time-stamps in the frame in tick (pulses) format. \itempar{Hex}{event editor!hex data} Selecting this check-box shows all number items in hex format. \itempar{Text data}{event editor!text data} This unlabelled pane shows the text of a meta text event. This field can be edited to add or change a meta text event. The events supported are: \textsl{Text Event}, \textsl{Copyright}, \textsl{Instrument Name}, \textsl{Lyric}, \textsl{Program Name}, and \textsl{Device Name}. It also shows translated versions of the control or program numbers in \textbf{D0}. % \itempar{Delete}{event editor!delete event} % Causes the selected event to be deleted. % The frame display is updated to move following events upward. \subsection{Event Editor / Bottom Buttons} \label{subsec:event_editor_buttons} These are the buttons that act on the edit fields or current event selection: \begin{enumber} \item \textbf{Delete}. Delete the selected event. \item \textbf{Insert}. Create a new event based on the data in the fields. \item \textbf{Modify}. Officially modify the selected event. \item \textbf{Clear}. Clear all events. \item \textbf{Save}. Save all the changes back to the pattern. \item \textbf{Dump}. Dump the events to a file. \end{enumber} Note that \index{bugs!event delete key} \index{bugs!event insert key} \textsl{Seq66} does not support using the \texttt{Delete} and \texttt{Insert} keys to supplement the buttons; the \texttt{Delete} key is needed for editing the event data fields. \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Insert}{event editor!insert event} Inserts a new event, described by the \textbf{Event Timestamp}, \textbf{Event Name}, \textbf{Data Byte 1}, and \textbf{Data Byte 2} fields. The new event is placed in the appropriate location for the given timestamp. If the timestamp is at a time that is not visible in the frame, the frame moves to show the new event, so be careful. \itempar{Modify}{event editor!modify event} Deletes the current event, and inserts the modified event, which is placed in the appropriate location for the given timestamp. (This feature does not work with linked Note Ons and Note Offs). \itempar{Clear}{event editor!clear events} Deletes all of the events in the event table. As with all edits, does not become official until the \textbf{Save} button is clicked. \itempar{Save}{event editor!save events} Saves all of the events in the event table into the original sequence. There is no way to undo this action. This button does not close the dialog; further editing can be performed. The Save button is enabled only if some unsaved changes to the events exist. Any sequence/pattern editor that is open should be reflected in the pattern editor once this button is pressed. \itempar{Dump}{event editor!dump events} Write the events to a text file in the same directory as the MIDI file, very useful for troubleshooting. The name of the file is of the form: \begin{verbatim} midi_file_name-pattern-#.text \end{verbatim} where '\#' is the pattern number. For example, if the loaded file is \texttt{/home/user/miditunes/The\_Wild\_Bull.midi} and the pattern is 9, then the resulting dump-file is \texttt{/home/user/miditunes/The\_Wild\_Bull-pattern-9.text}. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/first_start.tex ================================================ %------------------------------------------------------------------------------- % first_start %------------------------------------------------------------------------------- % % \file first_start.tex % \library Documents % \author Chris Ahlstrom % \date 2015-11-01 % \update 2026-04-15 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % This document provides LaTeX documentation for Seq66. % %------------------------------------------------------------------------------- \section{Let's Go!} \label{sec:introduction_lets_go} \textsl{Seq66} requires ALSA or JACK on Linux; it does not support \index{pipewire} Pipewire. It might work with a Pipewire-JACK setup, but that usage has not been tested thoroughly. Another aspect to look into is interactions with PulseAudio-pw. On Windows, \textsl{Seq66} uses the \textsl{MultiMedia API} with a wrapper that is our modified version of \textsl{PortMIDI} \cite{portmidi}. Theoretically that library should support \textsl{Mac OSX}, but we have no Mac to build and test with. This manual assumes that one is running \textsl{Seq66} on Linux, for the most part. But see \sectionref{sec:windows}. For the first run, make sure all desired MIDI devices are plugged in and that all desired software synthesizers are running. Only ports present at startup will appear; this version of \textsl{Seq66} does not support automatic handling of port changes at run-time. In addition, port-mapping (see \sectionref{sec:port_mapping}) is enabled by default; this makes the port setup more flexible. However, sometimes exiting, editing the port map manually in the 'rc' file, and restarting is necessary. Start \textsl{Seq66}, verify that it comes up, and then exit it immediately. This first start creates the configuration files and a log file in \begin{verbatim} /home/your\_user\_name/.config/seq66/ (Linux) C:/Users/your\_user\_name/AppData/Local/seq66 (Windows) \end{verbatim} Note: we will refer to this directory as the "home" directory for \textsl{Seq66}. Thus, a log file, for example, might be referred to by \texttt{\$home/qseq66.log}. If a session manager (e.g. \textsl{NSM}) is used, the configuration directory is determined by the session manager, and is also called a "session" directory. The main configuration file is \texttt{qseq66.rc} (Linux) or \texttt{qpseq66.rc} (Windows). The '.rc' file contains ports and port-mapping for the devices initially on the system. It also specifies the names and active-status of a number of other configuration files. Now start \textsl{Seq66} to use JACK or ALSA for MIDI. For USB MIDI devices on JACK, the \texttt{a2jmidid} program might need to be running (see \sectionref{sec:jack}, for details.) On \textsl{Windows}, just run \texttt{qpseq66.exe}; see \sectionref{sec:windows}. For better trouble-shooting, run it via command-line in a terminal window, at first, and provide a MIDI file, to see what messages might appear. Also note that a log file (e.g. \texttt{qseq66.log} is created; by default most messages will be written to it. The port settings will depend on your system. On our system, the synthesizer (\textsl{Yoshimi}) comes up on MIDI buss 5. (On \textsl{Windows}, buss 0 is the "MIDI Mapper", while buss 1 is the built-in wavetable synthesizer, which is normally under control of buss 0.) The \texttt{-{}-buss} option remaps all events to the desired buss for the current run: \begin{verbatim} $ qseq66 --alsa --buss 5 \ /usr/local/share/seq66-0.99/midi/b4uacuse-gm-patchless.midi $ qseq66 --jack --buss 5 \ /usr/local/share/seq66-0.99/midi/b4uacuse-gm-patchless.midi \end{verbatim} Note that \texttt{/usr/} or \texttt{/usr/local/} is where the program and its data files are stored, and \texttt{-0.99} is the main version number. The buss-override setting can also be made via the port drop-down control in the main window. It \textsl{will modify} the MIDI file. The "midi" directory is a directory created in the installation area, along with other directories such as "samples", "win", "wrk", etc.: \begin{verbatim} /usr/share/seq66-0.99/ (Linux) C:/Program Files/Seq66/data/ (Windows) \end{verbatim} It contains sample MIDI files and configuration files. Some of the files in those directories apply to both operating systems, so be sure to look at them. The user configration files are stored in the user's \textsl{Seq66} "home" area: \begin{verbatim} /home/username/.config/seq66/qseq66.* (Linux) C:/Users/username/AppData/Local/seq66 (Windows) ~/NSM Sessions/sessioname/seq66.nXXXX/config (NSM, legacy) ~/.local/share/nsm/sessioname/seq66.nXXXX/config (NSM, new) \end{verbatim} The configuration files in the "home" directory are created after the first run of \textsl{Seq66}. If all is well, the "play" button will start playback and the tune sounds. If not, look into \textbf{Edit / Preferences} or the configuration files. Don't edit the configuration files while \textsl{Seq66} is running, as they might be re-saved at application exit. \textbf{Bug?} The first-start change of input port settings might not get saved to the 'rc' file. The workaround is to exit and rerun \textsl{Seq66}, and make the setting again. Mysterious. \subsection{Device Changes} \label{subsec:introduction_device_changes} After running \textsl{Seq66} and getting it to work, one might either add new MIDI devices and software synthesizers, or remove them. If port-mapping is enabled (the default, and recommended), then one can go to \textbf{Edit / Preferences / MIDI Clock} and click the \textbf{Make Maps} button. This action stores (again) the current set of MIDI devices. Restart \textsl{Seq66} and the new set of devices should appear. Sometimes one must completely exit and the start \textsl{Seq66}. If devices have been added or removed (disabled), the user might need to edit the port maps directly in the 'rc' file. \textsl{Seq66} will also detect, at startup, if there are unavailable ports or if there are now more real MIDI ports than mapped ports. It will prompt for a potential remapping and restart. Click that button if feeling lucky, otherwise exit and fix the 'rc' file with a text editor. Also see \sectionref{sec:port_mapping}. \textbf{Warning}: If one has hand-tailored the port maps to represent all possible devices one has, the remap or restart buttons might \textbf{remove} some of those devices. Always keep a \textbf{backup} of a known-good 'rc' file in a safe place. In version 2 of \textsl{Seq66}, a year or two from "now", we hope to make the adjustments automatic. \subsection{Ports in Seq66 MIDI Files} \label{subsec:introduction_ports} When opening a pre-existing \textsl{Seq66} which contains patterns with output port numbers that don't exist on the current system, a prompt will appear with option to remap-and-restart \textsl{Seq66}. Sometimes the best thing to do is change the port numbers to the numbers of existing ports, and then save the MIDI file. One way to do this is to use the "buss-override" feature (see \sectionref{subsubsec:introduction_sets_buss_override}), which affects all patterns shown on the Live grid. Another way is to use port-mapping; \sectionref{sec:port_mapping}. \subsection{Windows} \label{subsec:introduction_windows} % TODO: talk about LoopMIDI Details about \textsl{Windows} are in the file \texttt{C:/Program Files/Seq66/data/midi/readme.windows} and in \sectionref{sec:windows}. The first-start depends on if it is a bare \textsl{Windows} install, or if supplemental MIDI support such as the \textsl{CoolSoft MIDIMapper} and \textsl{VirtualMIDISynth} have been installed. If they haven't, the default MIDI setup will contain no MIDI inputs, and two MIDI outputs, the \textsl{MIDI Mapper} and the \textsl{Microsoft GS Wavetable} synthesizer. The former grabs control of the latter, making it unavailable! \textsl{Seq66} detects this situation and enables only the MIDI Mapper. Therefore, run \textsl{Seq66} for the first time, then exit it and restart it. One should then be able to play MIDI files to the MIDI Mapper, if nothing else. We hope to get some input from \textsl{Windows} users, as our main usage is \textsl{Linux} and we run \textsl{Windows} only when we absolutely must, for building and testing. As for virtual ports in \textsl{Windows}, a useful tool is \textsl{LoopMIDI} (\cite{loopmidi}), as noted in the section on \textsl{Windows}. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/headless.tex ================================================ %------------------------------------------------------------------------------- % seq66 headless %------------------------------------------------------------------------------- % % \file seq66 headless.tex % \library Documents % \author Chris Ahlstrom % \date 2018-09-30 % \update 2025-06-11 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides a discussion of the MIDI GUI headless that Seq66 % supports. % %------------------------------------------------------------------------------- \section{Seq66 Headless Version} \label{sec:headless} \textsl{Seq66} can be built as a command-line application. That is, \textsl{Seq66} can be run from the command-line, with no visible user interface. See the \texttt{INSTALL} file provided with the source code distribution. Note the \texttt{-{}-both} bootstrap option to build the GUI and CLI versions of \textsl{Seq66} at the same time. It can also be instantiated as a Linux daemon, for totally headless usage. Because there is no visibility into a headless process, the setup for \texttt{seq66cli} is tricky, and the musician must get used to blind MIDI control. Also see \sectionref{subsec:configuration_command_line} \subsection{Seq66 Headless Setup} \label{subsec:headless_setup} The first step in setting up a headless \texttt{seq66cli} session is to make sure that the GUI version (\texttt{seq66}) works as expected. The GUI and headless configurations need to do the following: \begin{enumerate} \item Access the correct inputs, especially a keyboard or pad controller that can be used for controlling the sequencer via MIDI, as well as inputing notes. \item The desired MIDI controller input buss must be \textsl{enabled} by setting the active bit to "1" for that device. \item The MIDI controller input must be configured with some \texttt{[automation-control]} values, so that the headless sequencer can stop and start playback, select the next playlist or song, or activate other sequencer controls. This is done by providing the name of a suitable \texttt{[midi-control-file]} ('ctrl') specified in the 'rc' file, and making sure it is marked as \texttt{active = true}. \item Configure a separate MIDI buss to use to record from another MIDI instrument. \item Access the desired outputs, in order to play sounds. This can sometimes be tricky, because \textsl{Seq66} can route all patterns to the same output, or can let the patterns decide the outputs for themselves. \item The desired output busses must be \textsl{enabled} by setting the active bit to "1" for those device. Note that some MIDI controllers can be used to show the status of the music, and would also be configured in the 'ctrl' file. \item Create a play-list. The headless sequencer can only select songs to play via a pre-configured play-list, or by placing a single song file on the command line. \item Run \texttt{seq66cli} and then stop it (Ctrl-C) to regenerate and verify the initial configuration files, \texttt{HOME/.config/seq66/seq66cli.*}. \end{enumerate} Once the above steps are proven for the \texttt{qseq66} configuration files, then the same settings can be made for the \texttt{seq66cli} configuration files. The easiest way to do this is to use a difference editor to make the settings match. For example: \begin{verbatim} $ gvimdiff ~/.config/seq66/seq66cli.rc ~/.config/seq66/qseq66.rc \end{verbatim} Sometimes odd problems, such as the output synthesizer not working or not appearing in the list of outputs, can be a real puzzle. Here are the steps used in this test, based on sample files provided in the \texttt{contrib/midi}, \texttt{data/midi}, and \texttt{data/samples} directory; adapt them to your setup. For simplicity, JACK is not running, and so ALSA is in force. \textbf{First}, after booting, plug in the MIDI keyboard or MIDI control pad. Our example here will use the \textsl{Korg nanoKEY2} keyboard. (For a more powerful and exhaustive setup, see \sectionref{sec:launchpad_mini}.) \textbf{Second}, start the desired (software) synthesizer. Here we use the synth \textsl{Yoshimi}, with a stock setup from the \textsl{Yoshimi Cookbook} project. The order of starting the keyboard/pad and the synthesizer will alter the port numbers of these items. Best to do things in the same order every time... be consistent (or use the port-mapping feature described in \sectionref{sec:port_mapping}). Run the following command in order to verify the ALSA buss numbers for the controller and the synthesizer: \begin{verbatim} $ aplaymidi -l # list ALSA output ports $ arecordmidi -l # list ALSA input ports (except the 'announce' port) \end{verbatim} Another way is to start \texttt{qseq66}, exit it, and see the results in the \texttt{qseq66.rc} file. \textbf{Third}, edit the \texttt{seq66cli.rc} file as described below so that the correct settings of \texttt{[midi-clock]}, \texttt{[midi-input]} and \texttt{[midi-control-file]} are entered into the 'rc' configuration. For this discussion, we use a MIDI-control file (\texttt{nanomap.ctrl}), which we set up in the \texttt{seq66cli.rc} file to be read. The \texttt{nanomap.ctrl} file sets up the \textsl{nanoKEY2} as shown in this figure: \begin{figure}[H] \centering \includegraphics[scale=1.5]{configuration/ctrl/nanokey-sample-rc.png} \caption{Sample nanoKEY2 Control Setup} \label{fig:headless_nanokey2_setup} \end{figure} In this figure, the \textbf{OCT -} button on the \textsl{nanoKEY2} is pressed until it is flashing (not seen in the figure). This means that the lowest note on the \textsl{nanoKEY2} is MIDI note 0, the lowest note possible. With these settings, the playlists and songs can be loaded, and then played and paused. The \texttt{seq66cli.rc} file is edited to specify the desired 'ctrl' file: \begin{verbatim} [midi-control-file] active = true name = "nanomap.ctrl" # assumed to reside in ~/.config/seq66 \end{verbatim} The \texttt{seq66cli.rc} file should also enable the desired MIDI input control device: \begin{verbatim} [midi-input] 4 1 "[1] 0:1 nanoKEY2 MIDI 1" \end{verbatim} This setting should match the \texttt{control-buss} setting in the the \texttt{nanomap.ctrl} file. The \texttt{nanomap.ctrl} file is included in the \texttt{data/samples} directory of the source-code package or the installation directory. The following initial settings in this file are relevant: \begin{verbatim} control-buss = 4 # adjust based on "aplaymidi -l" midi-enabled = true \end{verbatim} The various "midi-control-out" settings are not relevant for this test since the \texttt{nanoKEY2} cannot display statuses. For the rest of the setup, do these steps: \begin{enumerate} \item Copy the contents of of \texttt{data/seq66cli/} to \texttt{HOME/.config/seq66}. \item Copy \texttt{data/samples/sample.playlist} and \texttt{data/samples/sample.playlist} to \linebreak \texttt{HOME/.config/seq66}. \item In your HOME directory, create a soft link to the Seq66 project (source code and data) directory: \texttt{ln -s path/to/seq66}. \end{enumerate} \textbf{Fourth}, to validate the setup visibly, run a command from the command-line such as: \begin{verbatim} $ qseq66 --config seq66cli --buss 2 --verbose \end{verbatim} % --playlist data/sample.playlist The buss number ("2") may need to be different on your setup to get sound routed to the correct synthesizer. Also, the path to the playlist might need to be an absolute path; normally playlists are stored in the \texttt{HOME/.config/seq66} directory and accessed from there. Verify that the main window shows the playlist name, and that the arrow keys modify the play-list and song selection. If that works, verify that the MIDI keyboard or pad controller works to change the selection. Verify that the current song plays through the synthesizer that was started. Also verify that all songs have been directed to the desired port(s) for each song. If this setup works (MIDI controls have the proper effect and the tunes play through the synthesizer), proceed to the next step. \textbf{Fifth}, test the command-line \textsl{Seq66} by running the following command (your setup might vary) on the command line: \begin{verbatim} $ seq66cli --buss 2 --verbose --playlist data/sample.playlist \end{verbatim} There is a play-list option to automatically unmute the sets when a new song is selected. If set, then the first song should be ready to play. If it plays, and the play-list seems to work (as indicated by the console output and the proper playback), then set up to run \texttt{seq66cli} as a daemon: \begin{verbatim} $ seq66cli -o daemonize \end{verbatim} % The keyboard controls and sound output should work. % However, at present the daemon doesn't get the proper settings, so that is % something to work on for version 0.95. It is good to solidify the setup by editing the 'rc' and 'usr' files. One can create a shortcut to run the application in the background (i.e. not as a daemon). In a \textsl{Fluxbox} menu: \begin{verbatim} [exec] (Seq66 Headless) {/usr/bin/seq66cli --jack-midi --jack-master --bus 6} \end{verbatim} For a sophisticated control setup, see \sectionref{sec:launchpad_mini}. For a much more complete setup: \begin{enumber} \item Run the seq66cli program and hit \texttt{Ctrl-C} to exit. \item Verify that there are a number of \texttt{seq66cli.*} files in the \texttt{~/.config/seq66} directory. There will also be a log-file to examine to see if all is good. \item In \texttt{seq66cli.rc}: \begin{enumber} \item Verify that \texttt{[usr-file]} is active and specifies \texttt{seq66cli.usr}. \item Activate a \texttt{[midi-control-file]} that supports MIDI control and MIDI status display for your setup. Otherwise, the program will be nearly impossible to use. \item If you have mutes, playlist, and note-mapper (drums) files to use, specify them and activate them. \item Verify the ports and port-mapping are appropriate for both your setup and for the songs to be created or played. \item Verify that the JACK settings are correct (or set up to use ALSA). \end{enumber} \item In \texttt{seq66cli.usr}: \begin{enumber} \item Verify that the \texttt{[usr-midi-ppqn]} settings are as desired. \item Modify the \texttt{[user-options]} settings as desired. \item Set daemonize if desired. Might consider installing \texttt{seq66cli} as a service. We don't yet provide any real help for that use case. \item Change the log-file name if desired, or change it to "" to send output to /dev/null. \end{enumber} \end{enumber} \subsection{Seq66 Headless Stopping} \label{subsec:headless_stopping} If running \textsl{seq66cli} from a console, a simple \texttt{Ctrl-C} will stop the application. If it is running as a daemon, use the following command from a console, or set up a menu entry to run this command: \begin{verbatim} $ killall seq66cli \end{verbatim} (Not sure if it is worth adding a "-{}-kill" option to \textsl{seq66cli}... killall is fairly complex.) %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/jack.tex ================================================ %------------------------------------------------------------------------------- % jack %------------------------------------------------------------------------------- % % \file jack.tex % \library Documents % \author Chris Ahlstrom % \date 2016-01-28 % \update 2023-06-11 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides the JACK page section of seq66-user-manual.tex. % %------------------------------------------------------------------------------- \section{JACK} \label{sec:jack} This section describes some details concerning the JACK support of \textsl{Seq66}. As with \textsl{Seq24}, \textsl{Seq66} has JACK transport support. JACK works with \textsl{Windows}, but we do not provide a JACK MIDI engine for that system at this time. JACK support is very loosely based on the \textsl{RtMIDI} project \cite{rtmidi}. The JACK support falls back to ALSA if the JACK server is not running. \index{jack-dbus} \textsl{However}, \texttt{jack-dbus} may be running, but without the server active. The result is the same, a fallback to ALSA. The check for JACK opens a JACK client and determines if it can be activated, then removes that client. \index{jack!transport} To enable the JACK transport support at run-time on \textsl{Linux}, the options are: \begin{itemize} \item \texttt{-j}/\texttt{-{}-jack-transport}. Deprecated, a legacy option. \item \texttt{-S}/\texttt{-{}-jack-slave}. The new version of the transport option. \item \texttt{-J}/\texttt{-{}-jack-master}. Unconditionally tries to become a JACK transport master. \item \texttt{-C}/\texttt{-{}-jack-master-cond}. Conditionally tries to become a JACK transport master, falling back to slave if another master exists. \item \texttt{-g}/\texttt{-{}-no-jack-transport}. Disables JACK transport even it JACK is running. \end{itemize} Also see \sectionref{subsubsec:configuration_rc_jack_transport}. Note that the JACK transport options are \textsl{separate} from JACK MIDI. \index{jack!midi} The \texttt{--jack-midi} and \texttt{--jack} options both enable JACK MIDI. If one wants to use \textsl{Seq66} and USB devices with JACK MIDI, one needs to expose the USB ports to JACK using the command \texttt{a2jmidid -{}-export-hw}. If \texttt{jackdbus} (Linux) is installed on the system, it is recommended to use \texttt{jack\_control} or the wrapper script \texttt{jackctl} provided by \textsl{Seq66}. The following sections discuss the JACK transport support and the native JACK MIDI support. \subsection{JACK / Transport} \label{subsec:jack_transport} JACK transport support is \textsl{separate} from native JACK MIDI support, and uses a separate JACK client. The JACK transport client is an invisible client with the name "seq66master" or "seq66slave", while the JACK MIDI client is visible in \textsl{QJackCtl}, and the ports created are part of the "seq66" client. \textsl{Seq66} can be set up to run without JACK transport, with JACK transport as a "slave" (i.e. "client"), as JACK master, or as JACK master if there is no other master detected. Transport clients were formerly known as "transport slaves", and that term seems more clear than "client". The \textsl{timebase master} continuously updates extended position information, counting beats, timecode, etc., while the transport is rolling. There is at most one master active at a time. If no timebase master, frame numbers are the only position information available. If a new client takes over, the former master timebase is no longer used. Taking over the timebase may be done conditionally, in which case the takeover fails when there is a master already. The existing master can release it voluntarily, if desired. There are some playback functions that JACK transport does not address: \begin{itemize} \item Variable speed playback. \item Reverse playback. \item Looping. \end{itemize} As per the rules of JACK, any client can start and stop the transport, and the other clients will follow suit. When \textsl{Seq66} is a JACK client, it will accept beats/minute (BPM) changes from another client that is running as master. When \textsl{Seq66} is master, changes to its BPM will be transmitted to the other clients. \textbf{Rules of JACK Transport}: \begin{itemize} \item Any client can be master, no need for one master client. \item Multiple clients can change the transport state; any client can start/stop playback or seek to a new location. \item The transport timebase (tempo) is set and maintained by one of the clients. \end{itemize} Here are some test JACK Transport scenarios using \textsl{Seq66} and \textsl{Hydrogen} running under JACK. We currently have to restart Seq66 between each scenario. \textsl{Seq66 as JACK Slave, Hydrogen as JACK Slave} Either application can start and stop playback on both applications. The BPM (beats-per-minute) are changed independently on each application. \textsl{Seq66 as JACK Slave, Hydrogen as JACK Master} Either application can start and stop playback on both applications. Changing BPM on Hydrogen also changes it on Seq66, but not the other way around. Seq66 cannot change its BPM via the user-interface. \textsl{Seq66 as JACK Master, Hydrogen as JACK Slave} Either application can start and stop playback on both applications. Changing BPM on Seq66 also changes it on Hydrogen. If changed on Hydrogen, the BPM immediately returns to Seq66's BPM. \textsl{Seq66 as JACK Master, Hydrogen as JACK Master} If Hydrogen is already running, as soon as Seq66 starts, Hydrogren relinquishes JACK Master. If Hydrogen comes up after Seq66, it retains JACK Master status. Both applications can control start/stop and BPM Note that, in \textsl{Seq66}, a changed BPM resets to the file-value after restart. Bug or feature? \subsection{JACK / Native MIDI} \label{subsec:jack_native_midi} \textsl{Seq66} will connect to a JACK client automatically only at startup, where it will connect to all JACK clients that it finds. If it can't find a JACK client, then it will fail to register a JACK port, and cannot play via JACK. Note: to disable auto-connect, see \sectionref{subsection:edit_preferences_jack}. The other option is to set up virtual ports using the \texttt{-{}-manual-ports} or \texttt{-{}-options virtual=o,i} options, and then to manually connected these ports to the desired MIDI devices or applications using \textsl{QJackCtl} (for example). To run with JACK MIDI, just make sure JACK is running, then start \textsl{Seq66}, which will detect JACK and use it. If it instead opts to run with ALSA, edit the 'rc' file to set up \texttt{midi\_jack}, or add the \texttt{-t}, \texttt{-{}-jack-midi}, \texttt{-{}-jack} or option to the command-line. If \textsl{Seq66} doesn't find JACK, it will fall back to ALSA. \index{sticky options} The JACK and ALSA options are sticky. They are saved to the 'rc' configuration file at exit, so one does not have to specify them in subsequent \textsl{Seq66} sessions. \subsubsection{JACK / MIDI Output} \label{subsubsec:jack_midi_output} By default (or depending on the 'rc' configuration file), \textsl{Seq66} will \index{jack!auto-connect} \index{auto-connect} automatically connect the ports that it finds to \texttt{seq66}. The following figure shows connections to a number of USB MIDI devices (purple) that have been bridged to JACK (red) by the \texttt{a2jmidid} daemon. \begin{figure}[H] \centering \includegraphics[scale=0.40]{jack/qjackctl-a2j-graph.png} \caption{JACK MIDI Ports and Auto-Connect} \label{fig:jack_midi_ports_auto_connect} \end{figure} Note that the ports in \textsl{Seq66} are named after the devices to which they are connected. The output ports available are shown in \textsl{seq66}'s \textbf{Edit / Preferences / MIDI Clock} tab. If USB devices are not shown, that means that the \texttt{a2jmidid} is not running. There is a \texttt{bash} script, \texttt{data/linux/jackctl} that will run \texttt{jack\_control} and \texttt{a2j\_control} to start JACK and the \texttt{a2jmidid} daemon to provide full support. On our current setup, it creates devices with long names (abbreviated inside \textsl{Seq66}): \begin{verbatim} 6 # number of MIDI clocks (output busses) 0 0 "[0] 0:0 seq66:a2j Midi Through [14] (playback): Midi Through Port-0" 1 0 "[1] 0:1 seq66:a2j Launchpad Mini [28] (playback): Launchpad Mini MIDI 1" 2 0 "[2] 0:2 seq66:a2j E-MU XMidi1X1 Tab [32] (playback): E-MU ... Tab MIDI 1" 3 0 "[3] 0:3 seq66:a2j nanoKEY2 [36] (playback): nanoKEY2 MIDI 1" 4 0 "[4] 0:4 seq66:a2j USB Midi [40] (playback): USB Midi MIDI 1" 5 0 "[5] 1:5 seq66:yoshimi-01 midi in" \end{verbatim} A feature new with version 0.98.5 is that an output port can be enabled or disabled in the \textbf{Edit / Preferences / MIDI Clock} tab, and the change in status takes effect immediately. One minor issue is that, upon reenabling, a stray note or two might be emiited. \subsubsection{JACK / MIDI Input} \label{subsubsec:jack_midi_input} The input ports also end up with long names: \begin{verbatim} 5 # number of input MIDI busses 0 1 "[0] 0:0 seq66:a2j Midi Through [14] (capture): Midi Through Port-0" 1 1 "[1] 0:1 seq66:a2j Launchpad Mini [28] (capture): Launchpad Mini MIDI 1" 2 1 "[2] 0:2 seq66:a2j E-MU XMidi1X1 Tab [32] (capture): E-MU ... Tab MIDI 1" 3 0 "[3] 0:3 a2j:nanoKEY2 [36] (capture): nanoKEY2 MIDI 1" 4 0 "[4] 0:4 a2j:USB Midi [40] (capture): USB Midi MIDI 1" \end{verbatim} When the check-box for a buss is selected, that input can be captured by \texttt{seq66}. An output port can be enabled or disabled in the \textbf{Edit / Preferences / MIDI Input} tab. \subsubsection{JACK / MIDI Virtual Ports} \label{subsubsec:jack_midi_virtual_ports} \index{ports!manual} \index{ports!virtual} The manual-versus-normal port support for JACK MIDI is essentially the same as that for ALSA. The \texttt{-{}-manual-ports} and \texttt{-{}-options virtual=o,i} options provide "virtual ports". These are ports that do not represent hardware, but are created by applications to allow them to connect to other applications or MIDI devices. The difference between manual/virtual ports and normal ports is that, while normal ports are automatically connected to the remote ports that exist in the system, the manual/virtual ports are just created, and one must manually connect them via, for example, the \textsl{QJackCtl} connections dialog. So, if one wants \textsl{seq66} to automatically connect to all existing JACK MIDI ports, \textsl{do not} use the \texttt{-m}/\texttt{-{}-manual-ports} option... use the \texttt{-a}/\texttt{-{}-auto-ports} option. Both options apply to both ALSA and JACK. The \textbf{MIDI Clock} and \textbf{MIDI Input} tabs reflect what is seen in \textsl{QJackCtl}. \subsubsection{JACK / MIDI and a2jmidid} \label{subsubsec:jack_midi_a2jmidid} One thing we saw is that \texttt{seq66} can deal with the odd naming of JACK ports created by the \textsl{a2jmidid} application. One can see in the input and output lists shown earlier that that the \texttt{a2j} client creates entries for "Midi Through", software clients, and bridged USB MIDI devices. Again, if these automatic connections get in the way, run \texttt{seq66} in manual/virtual mode, or disable JACK auto-connect in the \texttt{[jack-transport] jack-auto-connect]} setting (\textbf{Edit / Preferences / JACK / JACK auto-connect}. Note that \texttt{a2jmidid -{}-export-hardware} is roughly equivalentto JACK started with the \texttt{-Xseq} option. To set up \textsl{JACK}, one can use the script shipped with \textsl{Seq66}, \texttt{data/linux/startjack}. It has the following requirements and dependencies: \begin{itemize} \item \texttt{qjackctl}. Provides a way to show the connections. It also can start \textsl{JACK}, but we use \texttt{jack\_control} for that in this script. \item \texttt{jack\_control}. Provides a way to start \textsl{JACK} and set up a number of \textsl{JACK} parameters. Part of the \textsl{Debian} \texttt{jack2d} package. \item \texttt{a2j\_control}. Provides a way to configure and start the \textsl{ALSA}-to-\textsl{JACK} bridge to create bridges for all the hardware MIDI ports on the computer. Part of the \textsl{Debian} \texttt{a2jmidid} package. \item \texttt{yoshimi}. Provides a software synthesizer for MIDI playback. \item \texttt{yoshimi-b4uacuse-gm.state}. Provides a "General MIDI" setup for \textsl{yoshimi}. Located in the \texttt{data/linux} directory. \item \textsl{Editing}. One must edit the script to change the value of \texttt{HWPORT} \end{itemize} One can also edit the script to use another software synthesizer. Once ready, Run \texttt{startjack} and wait patiently for it to set up. \subsection{JACK / Trouble-Shooting} \label{subsec:jack_testing} This section describes some trouble-shooting that can be done with JACK. \subsubsection{JACK / Trouble-Shooting / MIDI Clock} \label{subsubsec:jack_testing_midi_clock} There are three common timecode formats: LTC (Linear Time Code), MTC (MIDI Time Code), and MIDI Clock, as well as JACK transport. This section describes MIDI Clock. It is an alternative to using JACK transport to synchronize devices. MIDI Clock is not a timecode format but tempo-based time. The absolute reference point is expressed as beats-per-minute and bar:beat:tick. There is no concept of sample-locking for MIDI clock signals. \paragraph{JACK MIDI Clock Send} \label{paragraph:jack_testing_midi_clock_send} To verify that \textsl{Seq66} is sending MIDI clock, the easiest way (using JACK) is to start the following command and set \textsl{Seq66} to send MIDI Clock to the \texttt{jack\_mclk\_dump} port that appears after restarting \textsl{Seq66}: \begin{verbatim} $ jack_mclk_dump \end{verbatim} Once set up, start playback on \textsl{Seq66}. The result should be a 0xfa (MIDI Start) message, a never-ending stream of rapid MIDI clock messages, and then, upon stopping playback, a 0xfc (MIDI Stop) message. Do not try to use \textsl{gmidimonitor}... while it displays Start and Stop, it does not display MIDI Clock. \paragraph{JACK MIDI Clock Receive} \label{paragraph:jack_testing_midi_clock_receive} To verify that \textsl{Seq66} receives MIDI clock in JACK, use a program that can emit MIDI Clock in JACK. We've tried \texttt{jack\_midi\_clock} to generate time code, but it didn't seem to work, even when connected to the application \texttt{jack\_mclk\_dump} used in the previous section. However, since we verified that \textsl{Seq66} can send MIDI Clock, we can use it to test itself. First, run the following command using \textsl{debug version of Seq66}, so that we can see if it is receiving: \begin{verbatim} $ qseq66 --jack-midi --manual --verbose --client-name seq66debug \end{verbatim} This creates virtual JACK ports that show up in a JACK patchbay as part of an application called "seq66debug". This lets us distinguish it from the regular version. Run the following \textsl{Seq66} command: \begin{verbatim} $ qseq66 --jack-midi --manual --verbose \end{verbatim} Set MIDI Clock for "midi out 0" to "On" so that it will emit clocking. Restart \texttt{qseq66}. Then connect the "midi out" of "seq66" to the "midi in" of "seq66debug" in the JACK patchbay. Then start playback. \textsl{seq66debug} should display a rapid stream of MIDI Clock messages. If not, check the setup and, if correct, report a bug! Note that the \texttt{-{}-jack-midi} and \texttt{--jack} options both enable JACK MIDI. \subsubsection{JACK / Trouble-Shooting / JACK Ports} \label{subsubsec:jack_trouble_shooting_jack_ports} As noted in \sectionref{subsubsec:jack_midi_output}, and \sectionref{subsubsec:jack_midi_input}, \texttt{a2jmidid -{}-export-hw} can be used to bridge USB MIDI hardware to JACK. However, on some systems, this bridge can be avoided, at the expense of a little confusion. For example, look at this partial output of the \texttt{jack\_lsp} command: \begin{verbatim} system:midi_playback_1 system:midi_capture_2 system:midi_playback_2 system:midi_capture_3 system:midi_playback_3 system:midi_capture_4 system:midi_playback_4 fluidsynth-midi:midi_00 \end{verbatim} The "system" ports correspond to real MIDI devices on this system, but which ones? To find out, run \texttt{jack\_lsp --aliases}. Here is just one device output: \begin{verbatim} system:midi_capture_2 alsa_pcm:Launchpad-Mini/midi_playback_1 Launchpad-Mini:midi/playback_1 system:midi_playback_2 alsa_pcm:Launchpad-Mini/midi_capture_1 Launchpad-Mini:midi/capture_1 \end{verbatim} So system MIDI device 2 is the \textsl{Launchpad Mini}, and will show up in \textsl{Seq66} as an I/O device on port 1. (Port 0 is usually the system MIDI Thru port). With virtual ports, port-mapping cannot be used. % In the future, we may be able to ensure the aliases are always % present in \textsl{Seq66}. \subsection{JACK / QJackCtl} \label{subsec:jack_qjackctl} This section is a placeholder for discussion of \textsl{QJackCtl}, its usage of the (deprecated) \textsl{JACK Session API}, and its "patchbay" (see \cite{patchbay}). Its a good idea to put "jackd -S" instead of just "jackd" for the server path. Running JACK in synchronous mode creates less Xruns in JACK2, which is now the default. \subsection{JACK / PulseAudio} \label{subsec:jack_pulseaudio} After years of avoiding \textsl{PulseAudio} assiduously, we found a circumstance where we needed it. In using \textsl{OBS Studio} to make demonstration videos of \textsl{Seq66}, we struggled trying to get it to work with either \textsl{ALSA} or \textsl{JACK} directly. Setting up \textsl{PulseAudio} ended up being fairly easy on our \textsl{Debian Sid} system, not too cumbersome or intrusive (and also made it easier to use \textsl{Bluetooth} earbuds via \textsl{pulseaudio-module-bluetooth}). However, when we decided to a system reinstall on our main development laptop, we ended up installing \textsl{Ubuntu 20}, and it became a real pain. \index{pulseaudio} This section discusses using \textsl{JACK} with \textsl{PulseAudio}. After installing \textsl{PulseAudio} and getting it to work with one's system (e.g. working with \textsl{Seq66}, \textsl{mpd}, \textsl{smplayer} etc. using \textsl{ALSA}, the next step is to install \textsl{JACK} support. In \textsl{Debian}-based systems, install \textsl{pulseaudio-module-jack}, \textsl{pulseaudio-utils}, and \textsl{pavucontrol}. Then, assuming \textsl{qjackctl} is installed (which is useful for using \textsl{JACK} session management), then go to its menu \textbf{Setup... / Options / Execute script ...} and set up each of the "jack" scripts found in \texttt{data/linux/jack/pulseaudio}: \begin{itemize} \item \textbf{Execute script on Startup}: \texttt{/usr/local/bin/jack-pre-start.sh} \item \textbf{Execute script after Startup}: \texttt{/usr/local/bin/jack-post-start.sh} \item \textbf{Execute script on Shutdown}: \texttt{/usr/local/bin/jack-pre-stop.sh} \item \textbf{Execute script after Shutdown}: \texttt{/usr/local/bin/jack-post-stop.sh} \end{itemize} Other executable locations can be used if desired. These steps configure \textsl{qjackctl} to run those commands at the appropriate time. \textsl{PulseAudio} will recognize (via D-Bus) that \textsl{JACK} started, and will route audio to it. When \textsl{JACK} is stopped, \textsl{PulseAudio} reverts to normal routing. Other \textsl{qjackctl} options from the \textbf{Misc} tab: \begin{itemize} \item \textbf{Start JACK audio server on application startup}: Check-mark it, unless one prefers to use the \textbf{Stop} button. \item \textbf{Stop JACK audio server on application exit}: Check if available, as desired. \item \textbf{Single application instance}: Check-mark it. \item \textbf{Enable ALSA Sequencer support}: Recommended. \item \textbf{Enable D-Bus interface}: Can leave unchecked. \item \textbf{Enable JACK D-Bus interface}: Can leave unchecked. \item \textbf{Save JACK audio server configuration to}: Leave unchecked. If the \texttt{.jackdrc} file is found, then \textsl{QSynth} might try to start JACK itself. \end{itemize} The following scenario describes what we are seeing on \textsl{Ubuntu 20.04.3}; the main anomaly is that playback of an application through \textsl{PulseAudio} doesn't resume on its own. Let's assume we are running \textsl{mpd} and it is playing tunes via \textsl{PulseAudio}. Then we start \textsl{QJackctl} configured as above. This action will silence \textsl{mpd}, but starting playback again will work. One can then run \textsl{Seq66} using JACK MIDI and JACK transport. Once finish, use \textsl{QJackctl} to stop JACK. This will silence \textsl{mpd} again. Then we have to restart \textsl{PulseAudio} (e.g. via the \texttt{repulse} script supplied in \texttt{data/linux/jack/pulseaudio}). %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/kbd_mouse.tex ================================================ %------------------------------------------------------------------------------- % kbd_mouse %------------------------------------------------------------------------------- % % \file kbd_mouse.tex % \library Documents % \author Chris Ahlstrom % \date 2016-04-07 % \update 2023-11-15 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides tables for keyboard and mouse support in Seq66. % %------------------------------------------------------------------------------- \section{Seq66 Keyboard and Mouse Actions} \label{sec:kbd_mouse_actions} This section presents some tables summarizing keyboard and mouse actions available in \textsl{Seq66}. The mute keys and group keys are also well described in the keyboard options for the main window (\sectionref{subsubsec:patterns_pattern_keys_and_clicks}). It does not cover the "fruity" mouse actions, as this mode of mouse-handling is not supported in \textsl{Seq66}. % Any volunteers to fill in the table? This section describes the keystrokes that are currently hardwired in \textsl{Seq66}. This description only includes items not defined in the 'ctrl' file. That is, hardwired values. "KP" stands for "keypad". \index{keys!focus} The effect that keystrokes have depends upon which window has the keyboard/mouse focus. \index{keys!qt} For a possibly more up-to-date list, see the \textbf{Automation Defaults} and \textbf{Hardwired Keys} tabs in the \texttt{control\_keys.ods} spreadsheet which is installed in the installed shared-data directory (e.g. \texttt{/usr/share/doc/seq66-0.99}). \subsection{Keyboard Control} \label{subsec:kbd_mouse_keyboard_control} \textsl{Seq66} provides a plethora of keyboard controls for user-interface actions, note-modification, zooming, and pattern control. Most of these controls (not all) are easy to change by editing the appropriate 'ctrl' configuration file, stored in one of the following directories, depending on the operating system: \begin{verbatim} /home/username/.config/seq66/qseq66.ctrl C:/Users/username/AppData/Local/seq66/qpseq66.ctrl \end{verbatim} There are also some extended examples present in the \textsl{Seq66} \texttt{data/linux} and \texttt{data/samples} directory. Note that keyboard and MIDI control settings have been consolidated into a single table in the 'ctrl' file. The \texttt{[mute-group]} control section has been moved to it's own 'mutes' file. \index{keys!gotchas} There are a number of "gotchas" to be aware of when assigning keys to the actions in the 'ctrl' file: \begin{itemize} \item Some of the keystrokes are hard-wired, such as "arrow" keys (for controlling play-lists), "page up/down" keys, the "zoom" keys, and some of the "Ctrl" keys. \item \textsl{Seq66} has appropriated the \index{keys!shift} Shift key so that a a Shift-Left-click on a pattern slot opens up the corresponding set (based on pattern number) in an external live grid, and a Ctrl-Left-click requests a new pattern. \index{auto-shift} For the group-learn feature, the \texttt{Shift} key is automatically enabled, using an "auto-shift" feature. Thus, using characters that require the Shift key while clicking, such as \texttt{\{} and \texttt{\}}, becomes surprising. Instead, look to the remaining keys: \texttt{F11}, \texttt{F12}, and the "keypad" keys if more keystrokes are wanted. \end{itemize} \texttt{[keyboard-control]}. We won't attempt to cover every key-control item, just the categories. Some items might be discussed in other parts of this manual. Remember that most keys have been consolidated with the MIDI controls. Also remember that the 'ctrl' file contains comments and an orderly layout to make it easier to understand and to edit. \index{pause} An additional key definition is shown for the pause key. By default, the pause key is the period ("\texttt{.}."), but that can be changed in the 'ctrl' file. In the piano rolls it is a hard-wired keystroke. \index{pattern edit} A goal of \textsl{Seq66} is being able to edit a pattern using mainly the computer keyboard. \textsl{Seq66} supports two pattern-slot modifier keys. The first modifier key causes the usual pattern-toggle key (hot-key) for a given slot to instead bring up the pattern editor. By default, this key is the equals ("\texttt{=}") key. \index{event edit} The second modifier key causes the usual pattern-toggle key (hot-key) for a given slot to instead bring up the event editor. By default, this key is the minus ("\texttt{-}") key. Both of these keys are configurable. Some of the keys have positional mnemonic value. For example, for BPM control, the semicolon is at the left (down), and the apostrophe is at the right (up). \index{slot-shift} \index{keys!slot-shift} The \textbf{slot shift} key is useful when using pattern grids larger than 8 x 4 patterns. Pressing the slot-shift key basically adds 32 to the pattern number of the slot-key that is pressed. The default key is the forward slash ("\texttt{/}") key. \index{snapshot} \index{keys!snapshot} A \textbf{snapshot} is a briefly-preserved state of the patterns. One can press (and release) the snapshot key, change the state of the patterns for live playback, and then press the snapshot key again to revert to the state when the snapshot key was first pressed. The default key is the \texttt{Ins} key. % Holding 'Alt' will save the state of playing sequences % and restore them when 'Alt' is lifted. % % Holding 'Left Ctrl' and 'Alt' at the same time will enable % you to flip over to new sequences briefly and then % flip right back upon lifting 'Alt'. % % Is this Snapshot 1 versus Snapshot 2? In Seq24's code, either key % does exactly the same thing! \index{queue} \index{keys!queue} To \textbf{queue} a pattern means to ready it for playback upon the next repeat of a pattern. A pattern can be armed immediately with a hot-key, or it can be queued to play back the next time the pattern repeats. A pattern can be queued by holding the queue key (defined in the 'ctrl' file) and pressing a pattern-slot hot-key. Instead of the pattern turning on immediately, it turns on at the next repeat of the pattern. The default key is the "\texttt{o}" key. \index{keep queue} \index{keys!keep queue} \index{queue!keep} \textbf{Keep queue} allows the queue to be held without holding down the queue button the whole time. First, press the keep-queue key. Next, hitting any of the slot hot-keys, no matter how many, sets up the corresponding pattern slot to be queued. Also, in keep-queue mode, clicking on the pattern slot will queue the pattern. The keep-queue mode is disabled by hitting the "queue" key again (any currently active queues remain active until finished). The default key is the backslash key, "\texttt{\textbackslash}" key. There is also a "Q" button to toggle the keep-queue status. \index{oneshot} \index{keys!oneshot} \textbf{Oneshot} causes a slot to be queued for only a single playback. The default key is the pipe, "\texttt{|}" key. \itempar{Sequence toggle keys}{keyboard!sequence toggle keys} Each of these keys toggles the playing/muting of one of the 32 loop/pattern boxes. These keys are layed out logically on the keyboard by default, and can also be shown in each loop/pattern box. Please note that we often call them "shortcut keys" or "hot-keys" where the context makes it clear that they apply to the armed/unarmed state of a pattern. \itempar{Mute-group slots}{keyboard!mute-group slots} There can be up to 32 mute-groups. \index{playing set} When activated, a mute-group sets the muted/unmuted status of the current "playing set" to the pattern-muting statuses of the selected mute-group. Each of these keys operates on the mute-grouping of one of the 32 stored mute groups. These keys are layed out logically on the keyboard by default, and consist of \texttt{Shift} versions of the sequence-toggle (hot) keys. Note that a mute-group key will be memorized only when \textsl{Seq66} is in \index{group-learn} \textsl{group-learn} mode. % \index{mute-group} % One thing to explain is just what mute-grouping means. % \textsl{Mute groups} are shortcuts to play a defined group of patterns % on the current set, while stopping other patterns from the current set, and % all patterns from other sets. \itempar{Learn}{keyboard!learn} \index{group!learn} To define the group of patterns for one mute group, press and hold the configured Learn key (the "el", "\texttt{l}" key by default, the hard-wired \texttt{Ctrl-L} key, or the "\textbf{L}" button in the user-interface. Then pressing one of the mute group keys (\textsl{lower case} version) will save the currently-playing pattern slots into the corresponding mute group. \index{auto-shift} The default mute group keys must be the shifted version of the key, but one should not press the \texttt{Shift} key for this key. \textsl{Seq66} will automatically assign the corresponding key with \texttt{Shift} activated. Note that, once in learn-mode, there is no way to cancel learn-mode except by selecting a mute-group keystroke or toggling the \textbf{Learn} ("L") button. Also see \sectionref{sec:mutes_master}. % TO BE FIXED % Group-mute can be globally enabled or disabled (with default keys apostrophe % \texttt{'} \index{grave} \index{igrave} and igrave or grave \texttt{`}). % So make sure it is enabled before trying to use it. \itempar{Disable}{keyboard!disable} \index{keys!apostrophe} It is the inverse \textbf{apostrophe} key by default. \index{group!off} \index{keyboard!group off} This key is the \textsl{group off} key. \itempar{Enable}{keyboard!enable} \index{keyboard!igrave} It is the \textbf{igrave} (back-tick) key by default. \index{group!on} \index{keyboard!group on} This key is the \textsl{group on} key. A number of additional functions have been added to \textsl{Seq66}, and keystrokes have been provided for those new functions. \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \index{song mode} Note the \textbf{Song/Live toggle} key. The \textsl{song mode} normally is in effect only when playback is started from the \textbf{Song Editor}. Now this mode can be used from any window, if enabled by pressing this key. There is also a button in the main window for this function, which shows the current state of this flag. Note that this flag is also stored in the 'rc' configuration file, as well as this hot-key value, which defaults to \texttt{F10}. \index{toggle JACK} \index{JACK toggle} The \textsl{JACK mode} is set via the \textbf{Edit / Preferences / JACK / JACK Connect} or \textbf{JACK Disconnect} buttons. This keystroke will toggle between JACK connect and JACK disconnect. The \textbf{Song Editor} will also have a \textbf{JACK} button. The hot-key for this function defaults to \texttt{F2}. \index{menu mode} The \textsl{menu mode} indicates if the main menu of the main window is accessible or not. It is disabled during playback so that more hot-keys can be used without triggering menu functions. It can also be disabled by the user; the default hot-key is \texttt{F3}. This feature is needed because the original \textsl{Seq24} had numerous conflicts between the menu key bindings and the default key bindings for the main window. % Here is Stazed's explanation of the feature, mildly edited: % % \begin{quotation} % \textsl{"why disabling is needed when playing"} % The original seq24 had numerous conflicts between the menu key binding % and the default seq24 key binding for the mainwind sequence triggers. % For example: Ctrl-q (quits the program without prompt). If you place a % sequence in the default 'q' slot, you cannot use it with Ctrl-l or Ctrl-r % (default replace or queue) because the menu grabs the keys. Same goes for % the Alt-l or Alt-r (default snapshot 1 or 2). Try same as above with % Alt-f, Alt-v, Alt-h, Ctrl-n, Ctrl-o... etc. So I just shut off all the % menus by default when playing because it seems that they should not be % needed then... especially in a live performance. % % \textsl{"why a button?"} % On occasion I wanted to use the mainwnd key binding when stopped to set % the sequences to be ready before starting. It's also a sort of safety % feature as well, just toggle the menus off before going live so that you % don't hit Ctrl-q, Ctrl-n etc. forgetting things are not playing.... % \end{quotation} \index{follow jack} \textsl{Follow JACK} is a feature ported from \textsl{Seq32}. The default key is \texttt{F4}. It determines if \textsl{Seq66} follows JACK transport. \index{fast forward} \textsl{Fast forward} is a feature ported from \textsl{Seq32}. The default key is \texttt{F6}. While this key is held, the song pointer will fast-forward through the song. This feature does not have a corresponding button. \index{rewind} \textsl{Rewind} is a feature ported from \textsl{Seq32}. The default key is \texttt{F5}. While this key is held, the song pointer will rewind. This feature does not have a corresponding button. \index{pointer position} \textsl{Pointer position} is a feature ported from \textsl{Seq32}. The default key is \texttt{F7}. When this key is pressed, the song pointer will move to the current position of the mouse, snapped. This feature does not have a corresponding button. \index{toggle mutes} \textsl{Toggle mutes} toggles the mute status of every pattern on every screen-set. It corresponds to the \textbf{Edit / Toggle mute all tracks} or the \textbf{Song / Toggle All Tracks} menu entries. There is also a button in the main window for this function, which shows the current state of this flag. Note that this hot-key value is stored in the 'rc' configuration file, and defaults to \texttt{F8}. \index{tap bpm} \textsl{Tap BPM} allows the user to "tap" in time with some other music, and see the tap sequence translated into beats/minute (BPM). There is also a "0" button for this function. After 5 seconds, this feature resets automatically, so the user can try again if not satisfied. At least two taps are needed for the BPM to be registered. % VERIFY and the UNCOMMENT % % Tap BPM causes events to be logged to the tempo track which is the first % track (track 0) by default. \subsection{Main Window} \label{subsec:kbd_mouse_main_window} The main window keystrokes are all defined via the options dialog and "rc" configuration file, or are stock window-management keystrokes. The main window has a very complete setup for live control of the MIDI tune via keystrokes. These actions are not included in \tableref{table:main_window_support}. % There may be some other keystrokes to be documented at some point. \begin{table}[H] \centering \caption{Main Window Support} \label{table:main_window_support} \begin{tabular}{l l l l l l} \textbf{Action} & \textbf{Normal} & \textbf{Double} & \textbf{Shift} & \textbf{Ctrl} \\ \textbf{e} & --- & --- & --- & Open song editor \\ \textbf{l} (el) & --- & --- & --- & Enter Learn mode \\ Left-click slot & Mute/Unmute & New/Edit & Toggle other slots & --- \\ Right-click slot & Edit menu & --- & Edit menu & Edit Menu \\ \end{tabular} \end{table} The new mouse features of this window for \textsl{Seq66}, as noted in \sectionref{sec:patterns_panel}, are: \begin{itemize} \item \textsl{Shift-Left-click}: Over one pattern slot, this action toggles the mute/unmute (armed/unarmed) status of all other patterns (even the patterns in other, unseen sets). \item \textsl{Left-Double-click}: Over a pattern slot, this action quickly toggles the mute/unmute status, which is confusing. But it ultimately brings up the pattern editor (sequence editor) for that pattern. % It acts like Ctrl-Left-click. \end{itemize} \subsection{Performance Editor Window} \label{subsec:kbd_mouse_performance_editor_window} The "performance editor" window is also known as the "song editor" window. It's main sections are the "piano roll" (perfroll) and the "performance time" (perftime) sections, discussed in the following sections. Also, some keystrokes are handled by the frame of the window. \begin{itemize} \item \texttt{Ctrl-z}. Undo. \item \texttt{Ctrl-r}. Redo. \end{itemize} \subsubsection{Performance Editor Piano Roll} \label{subsubsec:kbd_mouse_performance_editor_piano_roll} % \begin{itemize} % \item \texttt{Ctrl-x}. Cut. % \item \texttt{Ctrl-c}. Copy. % \item \texttt{Ctrl-v}. Paste. % \item \texttt{Ctrl-z}. Undo. % \item \texttt{Ctrl-r}. Redo. % \item \texttt{Shift-Up}. Move backward one small unit (which is...?) % \item \texttt{Shift-Down}. Move forward one small unit (which is...?) % \item \texttt{Shift-Page Up}. Move backward one frame. % \item \texttt{Shift-Page Down}. Move forward one frame. % \item \texttt{Shift-Home, Shift-KP Home}. Move to beginning of piano roll. % \item \texttt{Shift-End, Shift-KP End}. Move to end of piano roll. % \item \texttt{Shift-z (Z)}. Zoom in. % \item \texttt{0}. Set default zoom. % \item \texttt{z}. Zoom out. % \item \texttt{Left}. Move item left one snap unit. % \item \texttt{Right}. Move item right one snap unit. % \item \texttt{Up}. Move frame up one small scroll unit. % \item \texttt{Down}. Move frame down one small scroll unit. % \item \texttt{Home}. Move to top of piano roll. % \item \texttt{End}. Move to bottom of piano roll. % \item \texttt{Page Up}. Move up one frame (page-increment). % \item \texttt{Page Down}. Move down one frame (page-increment). % \end{itemize} Note that the keystrokes in this table (see \tableref{table:perf_window_piano_roll}) require that the focus first be assigned to the piano roll by left-clicking in an empty area within it. Otherwise, another section of the performance editor might receive the keystroke. \begin{table}[H] \centering \caption{Performance Window Piano Roll} \label{table:perf_window_piano_roll} \begin{tabular}{l l l l l l} \textbf{Action} & \textbf{Normal} & \textbf{Double} & \textbf{Shift} & \textbf{Ctrl} \\ Space & Start playback & --- & --- & --- \\ Esc & Stop playback & --- & --- & --- \\ Period (.) & Pause playback & --- & --- & --- \\ Del & Cut section & --- & --- & --- \\ c key & --- & --- & --- & Copy \\ p key & Paint mode & --- & --- & --- \\ v key & --- & --- & --- & Paste \\ x key & Escape paint & --- & --- & Cut \\ z key & Zoom out & --- & --- & Undo \\ 0 key & Reset zoom & --- & --- & --- \\ Z key & Zoom in & --- & --- & Undo \\ Left-arrow & Move earlier & --- & --- & --- \\ Right-arrow & Move later & --- & --- & --- \\ Left-click & Select section & --- & --- & --- \\ Right-click & Paint mode & --- & Paint mode & Paint mode \\ Scroll-up & Scroll up & --- & Scroll Left & Scroll Up \\ Scroll-down & Scroll down & --- & Scroll Right & Scroll Down \\ \end{tabular} \end{table} This section of the performance editor also handles the start, stop, and pause keys. These can be modified in the \textbf{Options / Keyboard} page. A "section" in the performance editor is actually a box that specifies a trigger for the pattern in that sequence/pattern slot. Note that the "toggle other slots" action occurs only if \textsl{shift-Left-clicked} in the "names" area of the performance editor. \textsl{Left-click} is used to select performance blocks if clicked within a block, or to deselect them if clicked in an empty area of the piano roll. Also note that all scrolling is done by the internal horizontal and vertical step increments. Some features of this window for \textsl{Seq66}, as noted in \sectionref{sec:song_editor}, are explained here: \begin{itemize} \item \textsl{p}: Enters the paint mode, until right-click is pressed or until the "x" key is pressed. \item \textsl{x}: Exits the paint mode. Think of the made-up term "x-scape". \item \textsl{z}: Zooms out the performance view. It makes the view look smaller, so that more of the performance can be seen. Opening a second performance view is another way to see more of the performance. \item \textsl{0}: Resets the zoom to its normal value. \item \textsl{Z}: Zooms in the performance view, making the view larger, so that more details of the performance can be seen. % \item \textsl{.}: The period (configurable) is a new key devoted to the % new pause functionality. \item \textsl{Left Arrow}: Moves the selected item to the left (earlier in time) in the performance layout. \item \textsl{Right Arrow}: Moves the selected item to the right (later in time) in the performance layout. \item Once selected (rendered in grey), a pattern section (trigger) can be moved by the mouse. To move it using the left or right arrow keys, the paint mode must be entered, but only via the "p" key. % -- the right mouse button deselects the greyed pattern. % Too tricky, we might try fixing it later. \end{itemize} \subsubsection{Performance Editor Time Section} \label{subsubsec:kbd_mouse_performance_editor_time_section} \begin{itemize} \item \texttt{l}. Set to move L marker. \item \texttt{r}. Set to move R marker. \item \texttt{x}. Escape ("x-scape") the movement mode. \item \texttt{Left}. Move the selected marker left. \item \texttt{Right}. Move the selected marker right. \end{itemize} This section of the performance editor is also known as the "measure ruler" or the "bar indicator". \begin{table}[H] \centering \caption{Performance Editor Time Section} \label{table:performance_editor_time_section} \begin{tabular}{l l l l l l} \textbf{Action} & \textbf{Normal} & \textbf{Double} & \textbf{Shift} & \textbf{Ctrl} \\ l & Move L [1] & --- & --- & --- \\ r & Move R [1] & --- & --- & --- \\ x & Escape Move & --- & --- & --- \\ Left-Click & Set L [2] & --- & --- & --- \\ Middle-Click & --- & --- & --- & --- \\ Right-Click & Set R [2] & --- & --- & --- \\ \end{tabular} \end{table} \begin{enumerate} \item Activates movement of this marker using the left and right arrow keys. Movement is in increments of the snap value. This mode is exited by pressing the 'x' key. Also see note [2]. \item Controlled in the pertime section. \end{enumerate} The features of this window for \textsl{Seq66} are: \begin{itemize} \item \textsl{l}: Enters a mode where the left and right arrow keys move the L marker, until the "x" key is pressed. \item \textsl{r}: Enters a mode where the left and right arrow keys move the R marker, until the "x" key is pressed. \item \textsl{x}: Exits the marker-movement mode. \end{itemize} \subsubsection{Performance Editor Names Section} \label{subsubsec:kbd_mouse_performance_editor_names_section} \begin{table}[H] \centering \caption{Performance Editor Names Section} \label{table:performance_editor_names} \begin{tabular}{l l l l l l} \textbf{Action} & \textbf{Normal} & \textbf{Double} & \textbf{Shift} & \textbf{Ctrl} \\ Left-Click & Toggle track & --- & Toggle other tracks & --- \\ Middle-Click & --- & --- & --- & --- \\ Right-Click & New/Edit menu & --- & --- & --- \\ \end{tabular} \end{table} \subsection{Pattern Editor Piano Roll Keystrokes} \label{subsec:kbd_mouse_pattern_editor_piano_roll_keystrokes} The pattern/sequencer editor piano roll is a complex and powerful event editor; \tableref{table:pattern_editor_piano_roll}, doesn't begin to cover its functionality. Here are the keystrokes handled by the main frame of the piano roll: \begin{itemize} \item \texttt{Delete}. Deletes (not cuts) the currently-selected notes. \item \texttt{Backspace}. Same as \texttt{Delete}. \item \texttt{Left Arrow}, \texttt{Right Arrow}, \texttt{Up Arrow},and \texttt{Down Arrow}. Moves the selected notes by one semi-tone in pitch vertically, or one snap step horizontally. \item \texttt{Ctrl-Left Arrow} and \texttt{Ctrl-Right Arrow}. Absolute left/right movement by a snap step. To be explored. \item \texttt{z} and \texttt{Z}. Zoom out (smaller) and zoom in horizontally. \item \texttt{v} and \texttt{V}. Zoom out (smaller) and zoom in vertically. \item \texttt{0}. Reset horizontal or vertical zoom. \item \texttt{Ctrl-Home}. Scroll leftward to the beginning of the piano roll (time 0). \item \texttt{Ctrl-End}. Scroll rightward to the end of the piano roll (the full length of the pattern). \item \texttt{Ctrl-a}. Select all notes. The selected notes, events, and data values are drawn in orange (by default). \item \texttt{Ctrl-c}, \texttt{Ctrl-x}, \texttt{Ctrl-v}, and \texttt{Ctrl-z}. Copy, cut, paste, and undo. There is no redo key, but there are user-interface buttons for undo and redo. % \item \texttt{Ctrl-r}. Redo. \item \texttt{Page Down}. Scroll downward. \item \texttt{Page Up}. Scroll upward. \item \texttt{c}. Repitch the selected note according to the loaded note-mapper, if any. \item \texttt{f}. Edge-fix. To be determined. \item \texttt{o}. Toggle recording for the pattern. \item \texttt{p}. Enter paint/drawing mode for notes. \item \texttt{q}. Quantize selected notes. Currently \textbf{broken}. \item \texttt{t}. Tightened (partial quantize) selected notes. Currently \textbf{broken}. \item \texttt{r}. Randomize selected notes. Currently \textbf{broken}. \item \texttt{x}. Exit paint/drawing mode. \end{itemize} % \item \texttt{Ctrl-L}. Bring up the LFO event modulation editor. % \item \texttt{Ctrl-W}. Exit the sequence (pattern) editor. % \item \texttt{Ctrl-Page Up}. Zoom in. % \item \texttt{Ctrl-Page Down}. Zoom out. % \item \texttt{Shift-Page Up}. Scroll leftward. % \item \texttt{Shift-Page Down}. Scroll rightward. % \item \texttt{Shift-z (Z)}. Zoom in. % \item \texttt{0}. Set default zoom. % \item \texttt{z}. Zoom out. % \item \texttt{Home}. Scroll upward to the beginning. % \item \texttt{End}. Scroll downward to the end. \subsubsection{Pattern Editor Piano Roll} \label{subsubsec:kbd_mouse_pattern_editor_piano_roll} Here are the keystrokes handled by the piano roll: These keystrokes require that the focus be set to the piano roll by clicking in it with the mouse. \begin{itemize} \item \texttt{Ctrl-Left}. Shrink selected notes. \item \texttt{Ctrl-Right}. Grow selected notes. \item \texttt{Delete}. Remove selected notes. \item \texttt{Backspade}. Remove selected notes. \item \texttt{Home. Set sequence to beginnging of sequence}. (Verify!) % \item \texttt{Left}. Move selected notes one snap left. % \item \texttt{Down}. Move selected notes one pitch downward. % \item \texttt{Up}. Move selected notes one pitch upward. \item \texttt{Enter, Return}. Paste the selected notes at the current position. % \item \texttt{p}. Enter "paint" (also known as "adding") mode. % \item \texttt{x}. Escape ("x-scape") the paint mode. \end{itemize} And here is the table, which includes items not described above: \begin{table}[H] \centering \caption{Pattern Editor Piano Roll} \label{table:pattern_editor_piano_roll} \begin{tabular}{l l l l l l} \textbf{Action} & \textbf{Normal} & \textbf{Double} & \textbf{Shift} & \textbf{Ctrl} \\ Del & Delete Selected & --- & --- & --- \\ c & --- & --- & --- & Copy \\ o & Toggle record & --- & --- & --- \\ p & Paint mode & --- & --- & --- \\ v & --- & --- & --- & Paste \\ x & Escape Paint & --- & --- & Cut \\ z & Zoom Out & --- & Zoom In & Undo \\ 0 & Reset Zoom & --- & --- & --- \\ Left-Arrow & Move Earlier [1] & --- & --- & --- \\ Right-Arrow & Move Later [1] & --- & --- & --- \\ Up-Arrow & Increase Pitch & --- & --- & --- \\ Down-Arrow & Decrease Pitch & --- & --- & --- \\ Left-Click & Deselect & --- & --- & --- \\ Right-Click & Paint mode & --- & Edit Menu & Edit/Edit Menu \\ Left-Middle-Click & Grow Selected & --- & Stretch Sel. & --- \\ Scroll-Up & Zoom Time In & --- & Scroll Left & Zoom Time In \\ Scroll-Down & Zoom Time Out & --- & Scroll Right & Zoom Time Out \\ \end{tabular} \end{table} \begin{enumerate} \item Once selected (and thus rendered in grey), a pattern segment can be moved by the mouse. To move it using the left or right arrow keys, the paint mode must be entered, but only via the \texttt{p} key -- the right mouse button deselects the greyed pattern. Too tricky, we might try fixing it later. \end{enumerate} Features of this window section for \textsl{Seq66}, as noted in \sectionref{subsubsec:pattern_editor_piano_roll_items}, are: \begin{itemize} \item \textsl{p}: Enters the paint mode, until right-click is pressed or until the \texttt{x} key is pressed. Notes are added by clicking or click-dragging. \item \textsl{x}: Exits ("x-scapes") the paint mode. \item \textsl{z}: Zooms out. \item \textsl{0}: Resets zoom to its normal value. \item \textsl{Z}: Zooms in. \item \textsl{.}: The period (configurable) does the pause function. \item \textsl{Left Arrow}: Moves selected events to the left. \item \textsl{Right Arrow}: Moves selected events to the right. \item \textsl{Up Arrow}: Moves selected notes upward in pitch. \item \textsl{Down Arrow}: Moves selected notes downward in pitch. \end{itemize} \subsubsection{Pattern Editor Event Panel} \label{subsubsec:kbd_mouse_pattern_editor_event_panel} \begin{itemize} \item \texttt{Ctrl-x}. Cut. \item \texttt{Ctrl-c}. Copy. \item \texttt{Ctrl-v}. Paste. \item \texttt{Ctrl-z}. Undo. \item \texttt{Delete}. Delete (not cut!) the selected events. \item \texttt{p}. Enter "paint" (also known as "adding") mode. \item \texttt{x}. Escape ("x-scape") the paint mode. \end{itemize} \subsubsection{Pattern Editor Data Panel} \label{subsubsec:kbd_mouse_pattern_editor_data_panel} Currently, no keystroke support is provided in the data panel. One potential upgrade would be the ability to change the value of the event with the Up and Down arrow keys. \subsubsection{Pattern Editor Virtual Keyboard} \label{subsubsec:kbd_mouse_pattern_editor_virtual_keyboard} \begin{table}[H] \centering \caption{Pattern Editor Virtual Piano Keyboard} \label{table:pattern_editor_virtual_piano_keyboard} \begin{tabular}{l l l l l l} \textbf{Action} & \textbf{Normal} & \textbf{Double} & \textbf{Shift} & \textbf{Ctrl} \\ Left-Click & Play note & --- & --- & --- \\ Right-Click & Toggle labels & --- & --- & --- \\ \end{tabular} \end{table} \subsection{Event Editor} \label{subsec:kbd_mouse_event_editor} \begin{itemize} \item \texttt{Down}. Move one slot down. \item \texttt{Up}. Move one slot up. \item \texttt{Page Down}. Move one frame down. \item \texttt{Page Up}. Move one frame up. \item \texttt{Home}. Move to top frame. \item \texttt{End}. Move to bottom frame. \item \texttt{Asterisk, KP Multiply}. Delete the currently-selected event. \end{itemize} %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/kudos.tex ================================================ %------------------------------------------------------------------------------- % seq66-user-manual %------------------------------------------------------------------------------- % % \file kudos.tex % \library Documents % \author Chris Ahlstrom % \date 2016-08-29 % \update 2023-03-02 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % This document provides LaTeX documentation for Seq66. % %------------------------------------------------------------------------------- \section{Kudos} \label{sec:kudos} This section gives some credit where credit is due. We have contributors to acknowledge, and have not caught up with all the people who have helped this project: \begin{itemize} \item \textsl{Tim Deagan (tdeagan)}: Fixes to the mute-group support. \item \textsl{0rel}: An important fix to add and relink notes after a paste action in the pattern editor. \item \textsl{arnaud-jacquemin}: A bug report and fix for a regression in mute-groups support. Also suggestions for enhancing mute-group support. \item \textsl{Stan Preston (stazed)}: Ideas for many improvements based on his \textsl{seq32} project. A lot of ideas. And a lot of code! \item \textsl{Animtim}: A number of bug reports and a new logo for \textsl{Sequencer64}. \item \textsl{jean-emmanuel}: Scrollable main-window support, other features and reports. \item \textsl{Olivier Humbert (trebmuh)}: French translation for the desktop files. \item \textsl{Oli Kester}: The creator of \textsl{Kepler34}, from which we got many clues on porting the user-interface to Qt 5 and Windows. \item \textsl{Tripayou}: Sylvain helped improve support for the AZERTY French keyboard in the 'ctrl' file. \item \textsl{Winko Erades van den Berg}: Added the Phrygian scale to the scales module. \item \textsl{phuel}: Provided an upgrade to check-mark the selected buss and channel in a pattern's pop-up menu. \item \textsl{Milkii Brewster}: Always pointing out issues and upgrades, and publicizing Seq66 on various "bulletin boards". \item \textsl{All bug reporters}. Couldn't do this well withou y'all! \end{itemize} Additional bug-reporters and testers: \begin{itemize} \item \textsl{F0rth}: A request for scripting support, a possible future feature. \item \textsl{gimmeapill}: Testing, bug-reports, and, um, "marketing". \item \textsl{georgkrause}: A number of helpful bug reports. \item \textsl{goguetchapuisb}: Found that \textsl{Sequencer64} native JACK did not properly handle the copious Active Sensing messages emitted by Yamaha keyboards. \item \textsl{milkmiruku}: Mainwids issues and many ideas, suggestions, feature requests, and bug report. An endless source of suggestions! \item \textsl{muranyia}: Feature request for numbered piano keys and bug-reports. \item \textsl{simonvanderveldt}: Issues with window sizing and more. \item \textsl{ssj71}: A request for an LV2 plugin version, a possible future feature. \item \textsl{triss}: A request for OSC support, a possible future feature. We added some OSC support in order to play well with the \textsl{Non Session Manager} (\textsl{NSM}). \item \textsl{layk}: Some bug reports, and, we are pretty sure, some nice videos that demonstrate \textsl{Seq66} on \textsl{YouTube} \cite{layk}. \item \textsl{matt-bel}: Reported a regression from \textsl{Seq24}, which could use a MIDI control event to mute / unmute multiple patterns at once, a cool feature! \item \textsl{zigmhount}: A pending request for a control that would automatically set up a pattern for recording and playback with one "click". \item \textsl{grammoboy} and \textsl{J. Liles}: Bug reports and other help with \textsl{NSM} support. \item \textsl{Houston4444}: Similarly, help with \textsl{RaySession}, a work-alike of \textsl{NSM}, written in \textsl{Python}. \item \textsl{unfa}: Bug reports for coloring, and for inspiring the "*.palette" file feature, as well as making coloring more comprehensive. \item \textsl{slightlynasty}: Ideas for updating the song-record functionality and improving the handling of large numbers of MIDI ports. A lot to think about. \item \textsl{grammoboy2}: Lots of suggestions to make sure that \textsl{Seq66} is absolutely compliant with the API of \textsl{Non Session Manager}. \end{itemize} ... and there are many more to add to this list.... There are a number of authors of \textsl{Seq24}. ideas from other \textsl{Seq24} fans, and some deep history. All of these people, and more, have contributed to \textsl{Seq66}, whether they know it or not. The original author is Rob C. Buse. Without his work, we would never have started this years-long project: \begin{quotation} \textsl{Seq24} is a real-time MIDI sequencer. It was created to provide a very simple interface for editing and playing MIDI 'loops'. After searching for a software based sequencer that would provide the functionality needed for a live performance, there was little found in the software realm. I set out to create a very minimal sequencer that excludes the bloated features of the large software sequencers, and includes a small subset of features that I have found usable in performing. Written by Rob C. Buse. I wrote this program to fill a hole. I figure it would be a waste if I was the only one using it. So, I released it under the GPL. \end{quotation} Taking advantage of Rob's generosity, we've created a reboot, a refactoring, an improvement of \textsl{Seq24}. It preserves (somewhat) the lean nature of \textsl{Seq24}, while adding many useful features. Without \textsl{Seq24} and its authors, \textsl{Seq66} would never have come into being. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/launchpad_mini.tex ================================================ % launchpad_mini %------------------------------------------------------------------------------- % % \file launchpad_mini.tex % \library Documents % \author Chris Ahlstrom % \date 2021-01-24 % \update 2023-04-10 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides a discussion of the MIDI input/output control % Launchpad Mini that Seq66 supports. % %------------------------------------------------------------------------------- \section{Launchpad Mini} \label{sec:launchpad_mini} This section discusses the configuration and usage of the \textsl{Novation Launchpad Mini} (we'll call it the "Mini") for control of patterns and for showing the status of \textsl{Seq66}. We will describe one of the 'ctrl' files provided with \textsl{Seq66}, the setup of ports and connections under ALSA and under JACK, and some related topics. A picture of the \textsl{Mini} appears at the end of this section. Supplemental information is documented in \texttt{contrib/notes/launchpad.txt} and \texttt{contrib/notes/launchpad-mini.ods} (a spreadsheet). \subsection{Launchpad Mini Basics} \label{subsec:launchpad_mini_basics} Let's start with a guide to \textsl{Mini} programming. Some of this information was adopted from the PDF file \texttt{launchpad-programmers-reference.pdf}. That document notes that a \textsl{Mini} message is 3 bytes, and is of type Note Off (80h), Note On (90h), or a controller change (B0h). However, on our \textsl{Mini}, we do not receive Note Offs (in ALSA)... we receive Note Ons with velocity 0. The \textsl{Mini} has a top row of circular buttons numbered from 1 to 8. The next 8 rows start with 8 unlabeled square buttons on the left side with a circular button on the right, labelled with letters A through H. The top row's circular buttons (labeled "1" through "8") emit \texttt{0xB0 cc 0x7f} on press, and \texttt{0xB0 cc 0x00} on release, where: \begin{itemize} \item \textbf{0xB0} is a Control Change on channel 0. \item \textbf{cc} is a Control Change number, ranging from 0x68 (104 decimal) to 0x6f (111 decimal) which are in the range of \textsl{undefined} MIDI controllers. \end{itemize} The square buttons in the 8 x 8 matrix emit \texttt{0x90 nn 0x7f} on press, and \texttt{0x90 nn 0x0} on release, where: \begin{itemize} \item \textbf{0x90} is a Note On message on channel 0. \item \textbf{nn} is the hex value of the note, as shown by the two-digit hex values shown below. The first "n" is the row number (from "0" to "7"). The second "n" is the column (from "0" to "7", and "8" for the circular buttons. \end{itemize} The right columns's circular buttons (labeled "A" through "H"), emit the same kind of message, with note numbers of the form \texttt{n8}. There are two layouts available, \textbf{X-Y} and \textbf{Drum}. In \textsl{Seq66}, the drum layout is not used. See the file \texttt{contrib/notes/launchpad.txt}. X-Y Key Layout (mapping mode 1) in hexadecimal format: \begin{verbatim} 1 2 3 4 5 6 7 8 B0h: (68h) (69h) (6ah) (6bh) (6ch) (6dh) (6eh) (6fh) 90h: [00h] [01h] [02h] [03h] [04h] [05h] [06h] [07h] (08h) A [10h] [11h] [12h] [13h] [14h] [15h] [16h] [17h] (18h) B [20h] [21h] [22h] [23h] [24h] [25h] [26h] [27h] (28h) C [30h] [31h] [32h] [33h] [34h] [35h] [36h] [37h] (38h) D [40h] [41h] [42h] [43h] [44h] [45h] [46h] [47h] (48h) E [50h] [51h] [52h] [53h] [54h] [55h] [56h] [57h] (58h) F [60h] [61h] [62h] [63h] [64h] [65h] [66h] [67h] (68h) G [70h] [71h] [72h] [73h] [74h] [75h] [76h] [77h] (78h) H \end{verbatim} X-Y Key Layout in decimal format: \begin{verbatim} 1 2 3 4 5 6 7 8 176: (104) (105) (106) (107) (108) (109) (110) (111) 144: [ 0] [ 1] [ 2] [ 3] [ 4] [ 5] [ 6] [ 7] ( 8) A [ 16] [ 17] [ 18] [ 19] [ 20] [ 21] [ 22] [ 23] ( 24) B [ 32] [ 33] [ 34] [ 35] [ 36] [ 37] [ 38] [ 39] ( 40) C [ 48] [ 49] [ 50] [ 51] [ 52] [ 53] [ 54] [ 55] ( 56) D [ 64] [ 65] [ 66] [ 67] [ 68] [ 69] [ 70] [ 71] ( 72) E [ 80] [ 81] [ 82] [ 83] [ 84] [ 85] [ 86] [ 87] ( 88) F [ 96] [ 97] [ 98] [ 99] [100] [101] [102] [103] (104) G [112] [113] [114] [115] [116] [117] [118] [119] (120) H \end{verbatim} The colors of the grid-buttons LED can be set via the command \texttt{90h key vel}, where: \begin{itemize} \item \textbf{0x90} is a Note On message on channel 0. \item \textbf{key} is a hex value given in the active of the two layouts shown above. \item \textbf{vel} is a bit mask of the form \texttt{00GGCKRR} where the bits have these meanings: \begin{itemize} \item \texttt{GG} for Green brightness. \item \texttt{C} to clear the LED setting of the other buffer. There are two buffers; see below for an explanation. \item \texttt{K} to copy the data to both buffers. \item \texttt{RR} for Red brightness. \end{itemize} \end{itemize} The \textsl{Mini} has two buffers 0 and 1 which contain two separate LED states. For example, in one buffer, all LEDs can be red, and in the other buffer, all LEDs can be green. By default, buffer 0 is used for displaying and for writing. By alternating the buffers, the display can blink. The brightness values used for green and red range from 0 (off) to 3 (full brightness). \textsl{Seq66} uses these values to provide red, green, yellow, and amber lighting. \begin{verbatim} Hex MSB LSB 00GG CKRR Color Brightness Decimal Vel 0Ch 0000 1100 Off Off 12 0Dh 0000 1101 Red Low 13 0Eh 0000 1110 Red Medium 14 0Fh 0000 1111 Red Full 15 1Ch 0001 1100 Green Low 28 1Dh 0001 1101 Amber Low 29 2Ch 0010 1100 Green Medium 44 2Eh 0010 1110 Amber Medium 46 3Ch 0011 1100 Green Full 60 3Eh 0011 1110 Yellow Full 62 3Fh 0011 1111 Amber Full 63 \end{verbatim} There are some other commands, not used, documented in \texttt{contrib/notes/launchpad.txt}. Also shown is a decimal version of the X-Y key layout. We use the square grid for toggling and showing pattern muting, and also for toggling mute groups. The top row of buttons are used for \textsl{Seq66}. We start with the basic controls, mapped to the top row of circular buttons (tentative): \begin{verbatim} Panic Stop Pause Play (free) (free) Set Dn Set Up Dec 68h 69h 6ah 6bh 6ch 6dh 6eh 6fh Hex 104 105 106 107 108 109 110 111 \end{verbatim} The \textsl{Mini} also supports power levels, but that feature is not used by \textsl{Seq66}. \subsection{System Survey, ALSA} \label{subsec:launchpad_mini_survey_alsa} Let's start with ALSA. The following devices were discovered by running the commands \texttt{aconnect -lio} and \texttt{aplaymidi -l} and combining the information with the information shown on the \textbf{MIDI Clock} and \textbf{MIDI Input} tabs. \begin{verbatim} In Out Port Client name Port name 0:0 System Timer [0] 0:1 System Announce [1] [0] 14:0 Midi Through Midi Through Port-0 [2] [1] 28:0 Launchpad Mini Launchpad Mini MIDI 1 (card 3) [3] [2] 32:0 E-MU XMidi1X1 Tab E-MU XMidi1X1 Tab MIDI 1 (card 4) [4] [3] 36:0 nanoKEY2 nanoKEY2 MIDI 1 (card 5) [5] [4] 40:0 USB Midi USB Midi MIDI 1 (card 6) \end{verbatim} Note the "Timer" device, which \textsl{Seq66} does not show, and the "Announce" device, which it does show (as disabled). The device/port of interest is the \texttt{Launchpad Mini MIDI 1}, port 2 for input from the \textsl{Mini}, and port 1 for output to the \textsl{Mini}. \subsection{Control Setup} \label{subsec:launchpad_mini_control_setup} A couple of \textsl{Launchpad} control files are provided in the \texttt{/usr/share/seq66-0.93/data/linux} directory. Copy the \texttt{qseq66-lp-mini.ctrl} file to \texttt{\$HOME/.config/seq66}. Make sure to exit \textsl{Seq66} before the next steps. Open the \texttt{qseq66.rc} file. Change \begin{verbatim} [midi-control-file] active = false name = "qseq66.ctrl" \end{verbatim} to \begin{verbatim} [midi-control-file] active = true name = "qseq66-lp-mini.ctrl" \end{verbatim} In \texttt{qseq66-lp-mini.ctrl} or \textsl{qseq66-lp-mini-alt.ctrl}, first read through the file to get familiar with the format and purpose of this file. \subsubsection{Input Control Setup} \label{subsubsec:launchpad_mini_input_control_setup} We first want to use the \textsl{Mini} as a MIDI controller for the selection of loops, mute-groups, and various automation (user-interface) functions. In \texttt{qseq66-lp-mini.ctrl}, the only change to make for input-control is to change \texttt{0xff} to the proper \textsl{input} port. On our system, as noted above, that would be input port \texttt{[2]}. \index{QWERTZ} \index{AZERTY} \begin{verbatim} [midi-control-settings] drop-empty-controls = false # leave as false control-buss = 2 # changed 0xff to 2 midi-enabled = true # important! button-offset = 0 # leave as is button-rows = 4 # ditto button-columns = 8 # ditto keyboard-layout = qwerty # qwertz and azerty also supported \end{verbatim} Remember that \texttt{[midi-control-settings]} refers to controls \textsl{sent} to \textsl{Seq66} to control that application. Therefore, the control-buss is an \textbf{input} buss. Also remember that, in \textsl{ALSA}, \textsl{Seq66} detects and adds an "announce" buss as buss 0. This extra buss is not seen via \texttt{arecordmidi -l}, but must be accounted for... it adds 1 to the number of each input buss. It does not apply to \textsl{JACK}. There are three sets of controls: loops, mute-groups, and automation, as described in the following sections. \paragraph{[loop-control]} \label{paragraph:patterns_loop_control} In the \texttt{[loop-control]} section of \texttt{qseq66-lp-mini.ctrl}, keystrokes are assigned, and only the "Toggle" (first) stanza of each MIDI control line is enabled, although there are definitions for the On and Off stanzas should one want to enable them. Here are the 32 lines, truncated. Note that they no longer include the "enabled" and "channel" columns. Instead, the event/status is checked to be non-zero in order to be enabled, and the channel is encoded in the event/status. \begin{verbatim} [loop-control] 0 "1" [ 0 0x90 0 1 127 ] [ 0 0x00 0 1 127 ] [ 0 0x00 0 1 127 ] Loop 0 1 "q" [ 0 0x90 16 1 127 ] [ 0 0x00 0 1 127 ] [ 0 0x00 16 1 127 ] Loop 1 2 "a" [ 0 0x90 32 1 127 ] [ 0 0x00 32 1 127 ] [ 0 0x00 32 1 127 ] Loop 2 3 "z" [ 0 0x90 48 1 127 ] [ 0 0x00 48 1 127 ] [ 0 0x00 48 1 127 ] Loop 3 . . . 31 "," [ 0 0x90 55 1 127 ] [ 0 0x00 55 1 127 ] [ 0 0x00 55 1 127 ] Loop 31 \end{verbatim} The note values (0, 16, 32, 48) are in decimal. Why? Less to type, easier to understand. The whole \texttt{[loop-control]} section is 32 lines. Also note that, above, only the "Toggle" stanza is active. The "On" and "Off" stanzas use "0x00", which disables them, even if some data values are specified. If we want the loop armed only while the button is held, we would define something like: \begin{verbatim} [loop-control] 0 "1" [ 0 0x00 0 1 127 ] [ 0 0x90 0 1 127 ] [ 0 0x80 0 1 127 ] Loop 0 1 "q" [ 0 0x00 16 1 127 ] [ 0 0x90 0 1 127 ] [ 0 0x80 16 1 127 ] Loop 1 2 "a" [ 0 0x00 32 1 127 ] [ 0 0x90 32 1 127 ] [ 0 0x80 32 1 127 ] Loop 2 3 "z" [ 0 0x00 48 1 127 ] [ 0 0x90 48 1 127 ] [ 0 0x80 48 1 127 ] Loop 3 . . . \end{verbatim} The default pattern-number mapping for each \textsl{Mini} slot: \begin{verbatim} 1 2 3 4 5 6 7 8 [ 0 ] [ 4 ] [ 8 ] [ 12 ] [ 16 ] [ 20 ] [ 24 ] [ 28 ] A [ 1 ] [ 5 ] [ 9 ] [ 13 ] [ 17 ] [ 21 ] [ 25 ] [ 29 ] B [ 2 ] [ 6 ] [ 10 ] [ 14 ] [ 18 ] [ 22 ] [ 26 ] [ 30 ] C [ 3 ] [ 7 ] [ 11 ] [ 15 ] [ 19 ] [ 23 ] [ 27 ] [ 31 ] D \end{verbatim} By pressing the appropriate button on the \textsl{Mini}, a pattern toggles between being armed (green) and being muted (red). Also note that there is a loop-mode setting (see \sectionref{paragraph:patterns_loop_modes}) that changes the action of each button from arm/mute to various other actions. \paragraph{[mute-group-control]} \label{paragraph:patterns_mute_group_control} The mute-group controls are very similar to the loop controls. and Off stanzas at this time; they are all zeroes. \begin{verbatim} [mute-group-control] 0 "!" [ 0 0x90 64 1 127 ] ... 1 "Q" [ 0 0x90 80 1 127 ] ... 2 "A" [ 0 0x90 96 1 127 ] ... 3 "Z" [ 0 0x90 112 1 127 ] ... . . . 31 "<" [ 0 0x90 112 1 127 ] ... \end{verbatim} The mapping is the similar to loop-control, but offset by four rows. By pressing the appropriate button on the \textsl{Mini}, a mute-group toggles between being on (selected patterns armed) and off (all patterns muted). \paragraph{[automation-control]} \label{paragraph:patterns_automation_control} A large number of actions available from the user-interface can also be controlled by keystrokes or a MIDI device. Here is a brief sample. See the 'ctrl' file itself for more information. \begin{verbatim} [automation-control] 0 "'" [ 0 0x00 0 0 0 ] [ 0 0xb0 104 127 127 ] [ 0 0xb0 104 127 127 ] # BPM Up 1 ";" [ 0 0x00 0 0 0 ] [ 0 0xb0 105 127 127 ] [ 0 0xb0 105 127 127 ] # BPM Dn 2 "]" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Set Up 3 "[" [ 0 0x00 0 0 0 ] [ 0 0xb0 0 0 0 ] [ 0 0xb0 0 0 0 ] # Set Dn . . . \end{verbatim} In \texttt{qseq66-lp-mini.ctrl}, all 64 square buttons are defined, which leaves the 16 circular buttons available for MIDI control. Only a few of those are defined so far. \subsubsection{Output Control Setup} \label{subsubsec:launchpad_mini_output_control_setup} Here, we want \textsl{Seq66} to send information to the \textsl{Mini} so that the lights on the \textsl{Mini} match the unmuted loops and some of the \textsl{Seq66} controls. Here are the changes to make to the output settings (while \textsl{Seq64} is \textsl{not} running). Make settings like the following: \begin{verbatim} [midi-control-out-settings] set-size = 32 output-buss = 2 midi-enabled = true button-offset = 0 button-rows = 4 button-columns = 8 \end{verbatim} If the device starts lighting up mysteriously while playback is happen, make sure the music is not being \textsl{played} to the control device channel. Or, if your synth makes weird noises at startup/exit, make sure the output-buss setting is not pointing to your synth. (Been there, done that! \smiley{}) \paragraph{[midi-control-out]} \label{paragraph:patterns_midi_control_out} The \texttt{[midi-control-out]} section provides a way to see the status of each pattern/loop in the \textsl{Mini}'s grid. Here are a few entries. As per the section above, 60 is green, 15 red, 62 is yellow, and 12 is off. \begin{verbatim} [midi-control-out] 0 [ 0x90 0 60 ] [ 0x90 0 15] [ 0x90 0 62] [ 0x90 0 12] 1 [ 0x90 16 60 ] [ 0x90 16 15] [ 0x90 16 62] [ 0x90 16 12] 2 [ 0x90 32 60 ] [ 0x90 32 15] [ 0x90 32 62] [ 0x90 32 12] 3 [ 0x90 48 60 ] [ 0x90 48 15] [ 0x90 48 62] [ 0x90 48 12] . . . \end{verbatim} For the \texttt{qseq66-lp-mini.ctrl} file, only the upper 32 buttons and LEDS are used for this purpose, so there are 32 lines of data in this section. The four stanzas (numbers in square brackets) are: \begin{itemize} \item \textbf{Armed}. This stanza is configured to show unmuted pattern slots as green. \item \textbf{Muted}. This stanza is configured to show muted pattern slots as red. \item \textbf{(Un)Queued}. This stanza is configured to show a queued pattern slots as yellow. \item \textbf{Empty/Deleted}. This stanza is configured to show an empty pattern slots as off (dark). \end{itemize} \paragraph{[mute-control-out]} \label{paragraph:patterns_mute_control_out} With the \texttt{qseq66-lp-mini.ctrl} file, the lower 32 buttons can be used to see which mute-group is selected (as well as to select a mute-group). The layout is pretty simple; here are the first four of the 32 lines: \begin{verbatim} [mute-control-out] 1 [ 0x90 64 60 ] [ 0x90 64 15 ] [ 0x90 64 12 ] 2 [ 0x90 80 60 ] [ 0x90 80 15 ] [ 0x90 80 12 ] 3 [ 0x90 96 60 ] [ 0x90 96 15 ] [ 0x90 96 12 ] 4 [ 0x90 112 60 ] [ 0x90 112 15 ] [ 0x90 112 12 ] . . . 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] \end{verbatim} The slots are numbered; all of the entries in the section are always enabled. The first stanza indicates that the button the selected mute-group will be green. The second stanza indicates that the unselected mute-group buttons will all be red, as long as they have mutes defined in them. The third stanza indicates that the inactive (empty) mute-groups will be dark. \paragraph{[automation-control-out]} \label{paragraph:patterns_automation_control_out} This section allows for the following status to be shown. "0xb0" indicates a circular button in the top row. A "1" at the beginning of each line indicates that output is active. \begin{verbatim} [automation-control-out] 1 [ 0xb0 104 60 ] [ 0xb0 104 15 ] [ 0xb0 104 12 ] # Panic 0 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] # Slot_shift 1 [ 0x90 8 60 ] [ 0x90 8 15 ] [ 0x90 8 12 ] # Queue . . . and many more \end{verbatim} Note that the play, stop, and pause statuses are all shown on the same button, as green, red, or yellow. Some of these might still be in progress as you read this. The stanzas mostly show the coloring for on (60 = green), off (15 = red), and inactive/unconfigured (12 = off). Some of the buttons provide other meanings. Here is the complete table. The color is indicated by R, G, Y, or A, while "Inactive" is the most common entry for the third stanza and represents a darkened button. An number indicates we don't like something about the behavior and might fix it, or explain it. See the notes about those after the table. For example, currently, pressing "Panic" causes the \textbf{Panic} and \textbf{Stop buttons} to turn green, which seems silly in hindsight. \begin{table}[H] \centering \caption{Status Announcement Stanzas} \label{table:status_announcement Stanzas} \begin{tabular}{l l l l} \textbf{Action} & \textbf{Stanza 1} & \textbf{Stanza 2} & \textbf{Stanza 3} \\ \textbf{Panic 1} & On (R) & Off (R) & Inactive \\ \textbf{Stop 1} & On (R) & Off (R) & Inactive \\ \textbf{Pause 1} & On (Y) & Off (R) & Inactive \\ \textbf{Play 1} & On (G) & Off (R) & Inactive \\ \textbf{Toggle Mutes 2} & On (G) & Off (R) & Inactive \\ \textbf{Song Record 3} & On (G) & Off (R) & Inactive \\ \textbf{Queue 4} & On (G) & Off (R) & Inactive \\ \textbf{One-short 4} & On (G) & Off (R) & Inactive \\ \textbf{Replace 4} & On (G) & Off (R) & Inactive \\ \textbf{Snapshot 4} & On (G) & Off (R) & Inactive \\ \textbf{Song-mode 3} & On (G) & Off (R) & Inactive \\ \textbf{Learn 3} & On (G) & Off (R) & Inactive \\ \textbf{BPM-Up 5} & On (G) & Off (G) & Inactive \\ \textbf{BPM-Dn 5} & On (G) & Off (Y) & Inactive \\ \textbf{List-Up 5} & On (G) & Off (G) & Inactive \\ \textbf{List-Dn 5} & On (G) & Off (Y) & Inactive \\ \textbf{Song-Up 5} & On (G) & Off (G) & Inactive \\ \textbf{Song-Dn 5} & On (G) & Off (Y) & Inactive \\ \textbf{Set-Up 5} & On (G) & Off (G) & Inactive \\ \textbf{Set-Dn 5} & On (G) & Off (Y) & Inactive \\ \textbf{Tap\_BPM} & On (G) & Off (G) & Inactive \\ \textbf{Quit} & On (R) & Off (R) & Inactive \\ \textbf{Visibility} & On (A) & Off (A) & Inactive \\ \end{tabular} \end{table} Notes: \begin{enumerate} \item \textsl{Panic}, \textsl{Stop}, \textsl{Pause}, and \textsl{Play}. These needed improved coordination and appearance. So Panic and Stop are now always in "off" status. \item \textsl{Toggle Mutes}. There's no easy way to detect this transition, but this status is reflected in the loop buttons and is obvious. We could consider just toggling the color between two colors. \item \textsl{Song Record}, \textsl{Slot Shift}, \textsl{Song-mode}, \textsl{Learn}. These are state indicators, so it is green when in that state, and red when not in that state. \item \textsl{Queue}, \textsl{One-shot}, \textsl{One-shot}, \textsl{Replace}. These operate by striking the appropriate key and then selecting the pattern hot-key to effect. So we could turn the key green until the hot-key/pattern is selected. \item \textsl{BPM-Up}, \textsl{BPM-Dn}, \textsl{List-Up}, \textsl{List-Dn}, \textsl{Song-Up}, \textsl{Song-Dn}, \textsl{Set-Up}, \textsl{Set-Dn}. These are up-down pairs. We currently just show them in red, but probably more informative to show Up in green and Dn in yellow. \end{enumerate} \subsection{Test Run, ALSA} \label{subsubsec:launchpad_mini_test_run_alsa} Now that we're set up, start \textsl{Seq66}. \begin{figure}[H] \centering \includegraphics[scale=2.00]{configuration/ctrl/launchpad-mute-group-2.png} \caption{Launchpad Mini Running with Seq66} \label{fig:launchpad_mute_group_perspective} \end{figure} This picture shows that playback is paused (yellow), that mute-group 7 is active, and that all the patterns in that mute-group are green, except for one that got muted accidentally while taking the picture. If the \textbf{File / New} option is selected, all the patterns are turned off, but the four mute-group buttons at the bottom left remain, as the mute-groups are not erased. (Bug or feature?) What's next? First, add more controls and statuses to the configuration. Second, start working on a MIDI file to produce a light show! \subsection{System Survey, JACK} \label{subsec:launchpad_mini_survey_jack} Now let's see what we have to do in \textsl{JACK}. First peruse \sectionref{sec:jack}, to understand the basics about \textsl{JACK}, including the last section there that describes how to set up \textsl{ALSA}-to-\textsl{JACK} bridging. Run one of the following commands (they are identical in function), then verify the ports in \textbf{Edit / Preferences / MIDI Clock} and \textbf{MIDI Input}, and then exit. \begin{verbatim} $ qseq66 --jack-midi $ qseq66 --jack \end{verbatim} In \texttt{qseq66.rc}, in section \texttt{[jack-transport]}, one will find this setting: \begin{verbatim} jack-midi = true \end{verbatim} In \texttt{qseq66.rc}, in section \texttt{[midi-input]}, the MIDI inputs are shown, decorated with the "a2j" designation: \begin{verbatim} [midi-input] 5 # number of input MIDI busses 0 1 "[0] 0:0 seq66:a2j Midi Through [14] (capture): Midi Through Port-0" 1 0 "[1] 0:1 a2j:Launchpad Mini [28] (capture): Launchpad Mini MIDI 1" 2 0 "[2] 0:2 a2j:E-MU XMidi1X1 Tab [32] (capture): E-MU XMidi1X1 ..." 3 0 "[3] 0:3 a2j:nanoKEY2 [36] (capture): nanoKEY2 MIDI 1" 4 0 "[4] 0:4 a2j:USB Midi [40] (capture): USB Midi MIDI 1" \end{verbatim} This is very similar to the \textsl{ALSA} setup, except that there is \textsl{no} "announce" port in \textsl{JACK}. The \textsl{Mini}'s input buss has shifted from port 2 to port 1. And, of course, the port names are a lot longer. Similarly, for the MIDI outputs: \begin{verbatim} [midi-clock] 5 # number of MIDI clocks (output busses) 0 0 "[0] 0:0 seq66:a2j Midi Through [14] (playback): Midi Through. . ." 1 0 "[1] 0:1 seq66:a2j Launchpad Mini [28] (playback): Launchpad. . ." 2 0 "[2] 0:2 seq66:a2j E-MU XMidi1X1 Tab [32] (playback): E-MU . . ." 3 0 "[3] 0:3 seq66:a2j nanoKEY2 [36] (playback): nanoKEY2 MIDI 1" 4 0 "[4] 0:4 seq66:a2j USB Midi [40] (playback): USB Midi MIDI 1" \end{verbatim} We make sure that the correct \texttt{control-buss} and \texttt{output-buss} are set, and both have the setting \texttt{midi-enabled = true} in \texttt{qseq66-lp-mini.ctrl}. Then make sure that \texttt{qseq66.rc} has its \texttt{[midi-control-file]} set to: \begin{verbatim} "qseq66-lp-mini.ctrl" \end{verbatim} Run \texttt{qseq66} again, and make sure that the \textsl{Mini}'s input and output ports are enabled. (Unfortunately, if one has to enable them, the application will need to be restarted.) The results should be just like \sectionref{subsubsec:launchpad_mini_test_run_alsa}. One last reminder that the descriptions above will be slightly different when port-mapping is enabled (which is recommended). %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/live_grid.tex ================================================ %------------------------------------------------------------------------------- % live_grid %------------------------------------------------------------------------------- % % \file live_grid.tex % \library Documents % \author Chris Ahlstrom % \date 2015-11-01 % \update 2026-04-15 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % This document provides LaTeX documentation for Seq66. % %------------------------------------------------------------------------------- \section{Live Grid / Main Window and Tabs} \label{sec:live_grid} For our walk-through of the main window, the following figure shows it using blue labels for reference. \begin{figure}[H] \centering \includegraphics[scale=0.65]{main-window/main-window-annotated.png} \caption{Seq66 Main Screen, Annotated} \label{fig:main_screen_annotated} \end{figure} The \textsl{Seq66} main window (i.e. the "Live Grid") appears as shown above, though the look can be tailored, as noted below. This figure has many differences from the \textsl{Seq24} main window, but the functionality is similar. \textsl{Seq66} allows resizing, and can be configured to start with its size scaled up or down. There is also a button near the top left that can hide the menu and the bottom panels, to allow reducing the size of the window event further. The features of the main window, including the "look" of the application, can be configured via the 'rc', 'usr', and 'palette' configuration files, via command-line options, via desktop themes, and via Qt ('qss') style-sheets. We break the discussion into sections for the following groups shown in the figure above: \begin{itemize} \item \textbf{Tabs} \item \textbf{Main Top} \item \textbf{Main Bottom} \item \textbf{Menu} \end{itemize} \subsection{Tabs} \label{subsec:introduction_main_tabs} The following tabs are present in \textsl{Seq66}: \begin{itemize} \item \textbf{Live} \item \textbf{Song} \item \textbf{Editor} \item \textbf{Events} \item \textbf{Playlists} \item \textbf{Sets} \item \textbf{Mutes} \item \textbf{Session} \end{itemize} We present brief descriptions here, with links to sections discussing the tabs in detail. \subsubsection{Tabs / Live} \label{subsubsec:introduction_main_tabs_live} The \textbf{Live} tab is foremost in the application. It provides a grid of \textsl{patterns} (also called \textsl{slots}, \textsl{loops}, \textsl{tracks}, or \textsl{sequences}) that display recorded MIDI data, status information, and provide popup-menus for each pattern. The buttons can be colored via a palette, and the status is easy to see from the coloring of activated buttons and a label that says "Armed" versus "Muted". The buttons can be toggled by a keystroke, shown in the lower right corner of the button. Another name for the \textbf{Live} tab is the \textbf{Patterns Panel}; it can be replicated in multiple external windows. See \sectionref{sec:patterns_panel}. \subsubsection{Tabs / Song} \label{subsubsec:introduction_main_tabs_song} The \textbf{Song Editor} combines all patterns into a complete tune with controlled repetitions of each pattern. It provides a way to lay out \textsl{triggers} for each pattern that control playback without musician interaction. The editing capabilities of the song editor are extensive, including dragging triggers, addition transposition to triggers, and more. See \sectionref{sec:song_editor}. \subsubsection{Tabs / Editor} \label{subsubsec:introduction_main_tabs_editor} The \textbf{Pattern Editor} allows for the recording and editing of notes and other MIDI events. It can be shown in the \textbf{Editor} tab or in an external window. When opened in the tab, the pattern editor is compressed vertically by reducing the height of the "data" area and removing some buttons. Otherwise, the pattern editor works the same in the tab and in external windows. See \sectionref{sec:pattern_editor}. \subsubsection{Tabs / Events} \label{subsubsec:introduction_main_tabs_events} The \textbf{Event Editor} provides a way to see the details of events and to do some modifying of them. It's meant for light usage; there are some less-used events that it cannot edit. It is especially useful in trouble-shooting a song. See \sectionref{sec:event_editor}. \subsubsection{Tabs / Playlist} \label{subsubsec:introduction_main_tabs_playlist} The \textbf{Playlist Editor} is meant for the creation of play-lists. One can create multiple playlists with multiple songs in each playlist. The selection of playlists and songs can be done via keystrokes or MIDI control. Note that editing of the playlists can also be done by directly editing a \texttt{.playlist} file. See \sectionref{sec:playlist}. \subsubsection{Tabs / Sets} \label{subsubsec:introduction_main_tabs_sets} The \textbf{Sets Editor} allows one to see, edit, and rearrange the sets that a tune contains. Sets are also referred to as "banks". A useful feature of sets is that they can selected and cause a major change in the patterns that are playing. See \sectionref{sec:setmaster}. \subsubsection{Tabs / Mutes} \label{subsubsec:introduction_main_tabs_mutes} Mute-groups provide a way to turn on and turn off multiple patterns at once via a keystroke or a MIDI control; the \textbf{Mutes Editor} also provides a way to use buttons to control the mute-group. Mute-groups can be saved in the \texttt{.mutes} file or in the tune itself. See \sectionref{sec:mutes_master}. \subsubsection{Tabs / Session} \label{subsubsec:introduction_main_tabs_session} The \textbf{Session} tab displays some aspects of the running setup of \textsl{Seq66}. It displays the name of the session manager, if any, client IDs, etc. Here, a session log file can also be specified. If defined (the default), then all messages written to the terminal when \textsl{Seq66} is run from a terminal shell are instead redirected to this file. The default log file is \texttt{qseq66.log} specified to use the "home" directory. This file can be cleared using the button to the right of it; however, messages will continue to be logged to this file until \textsl{Seq66} is completely exited and then manually restarted. In addition, text data for patterns can be viewed and edited here. Select the desired pattern, enter the desired text, and click the \textbf{Save Info} button. The song is now modified and can be saved. Also see \sectionref{sec:sessions}. \subsection{Main Top Controls} \label{subsec:introduction_main_top_controls} Here, we first discuss the top and bottom \textbf{Main} controls, as shown in the following collapsed figure: \begin{figure}[H] \centering % \includegraphics[scale=0.75]{main-window/main-window-controls.png} \includegraphics[scale=0.75]{main-window/main-window-top.png} \caption{Main Window Top Controls} \label{fig:main_window_top_controls} \end{figure} See the following section and \sectionref{subsec:introduction_main_bottom_controls}. The top panel of the Pattern window is simple, consisting of the name of the program and a few controls. The top main control items are, from left to right: \begin{itemize} \item \textbf{Seq66 Logo} \item \textbf{Show/Hide Button} \item \textbf{Engine Status} \item \textbf{PPQN Selection} \item \textbf{Buss Override for Play-set} \item \textbf{Global Beats Per Measure} \item \textbf{Global Beat Width} \item \textbf{BBT/HMS Time Display} \item \textbf{Beat Indicator Bar} \end{itemize} The status indicators that can appear to the right of the logo: \begin{itemize} \item \textbf{ALSA} appears if running using ALSA for MIDI. \item \textbf{JACK} appears if running using JACK for MIDI. \item \textbf{JACK Master} appears if running using JACK transport, as transport master. JACK transport can be used even if using ALSA for MIDI. \item \textbf{JACK Slave} appears if using JACK transport as transport slave. \item \textbf{Portmidi} appears if using the internal portmidi-derived MIDI engine. This is currently always the case on \textsl{Windows}. \end{itemize} \subsubsection{Seq66 Logo} \label{subsubsec:introduction_seq66_logo} The \textsl{Seq66} logo is actually a button now. When clicked it closes all of the following external windows if they are open. A quick way to clear out the clutter. \begin{itemize} \item \textbf{Pattern Editors}. All open pattern editors are closed. The \textbf{Editor} tab is not affected. \item \textbf{Live Grids}. All open live grids are closed. \item \textbf{Song Editor}. The song editor is closed. The \textbf{Song} tab is not affected. \end{itemize} \subsubsection{Show/Hide Button} \label{subsubsec:introduction_show_hide_button} If present (it is a build option), this button at the left of the top bar allows the main menu and the bottom two rows of controls to be hidden. This measure can save a lot of space (e.g. in a \textsl{Raspberry Pi} setup. If your window manager (e.g. \textsl{Fluxbox}) allows hiding the window decorations, the smallest useful size of the main window can be about 450 x 320 pixels. A good 'ctrl' automation file (e.g. for the \textsl{LaunchPad}) is necessary to control \textsl{Seq66} in this tiny setup; main menu hot-keys are not accessible with the main menu hidden. \begin{figure}[H] \centering \includegraphics[scale=1.0]{main-window/main-window-tiny.png} \caption{Main Window Tiny (shown full size)} \label{fig:main_window_tiny} \end{figure} Most of the other tabs are not usable at this small size, as they are too "busy" with many editing elements, and \textsl{Seq66} does not shrink or expand them. % Might be enough for live play? % Perhaps we should add a 'usr' option for the "tiny" mode? % And hide the rest of the tabs? Switch some buttons around? \subsubsection{PPQN Selection} \label{subsubsec:introduction_ppqn_selection} This drop-down allows one to change the pulses-per-quarter-note (PPQN) of the loaded tune, and this change can then be saved, if desired, with the file. As with \textsl{Seq24}, the default PPQN is 192. Values: \texttt{24, 32, 48, 96, 120, 192, 240, 384, 480, 768, 960, 1920, 2400, 3840, 7680, 9600, and 19200}. The PPQN cannot be changed to values not in this list, but if a file with an unsupported PPQN is loaded, that will show up in the PPQN box at the bottom, assuming \textbf{Preferences / Play / Use file's PPQN...} is set. % Values, even weird ones, can be entered by typing them. % However, some values, such as 120, are not % displayed properly in the grids. % (Modify the file to use a supported value, if possible.) If a MIDI file is loaded without that setting, the event times and pattern triggers are rescaled to the base PPQN (192 by default, but modifiable in \textbf{Preferences / Play / PPQN}), without raising the "modified" flag. Also see \sectionref{subsection:edit_preferences_play_options}. Generally, MIDI recommends PPQN that are multiples of 24 ticks. The value 32 is included anyway, but, if selected, the displays of notes and grids are incorrect. We don't intend to fix this. 960 PPQN at 120 BPM is about 2 time more accurate in timing than a MIDI port can handle, so the higher values are there "for fun". Also remember that the \texttt{Z} key can be used to expand zoom beyond the settings in the pattern-editor zoom dropdown. \subsubsection{Buss Override for Play-set} \label{subsubsec:introduction_sets_buss_override} This drop-down allows for overriding the buss (port) number used by all of the patterns in the current play-set. Unlike the \texttt{buss-override} setting in the 'usr' file (see \sectionref{subsubsec:usr_file_user_midi_settings}), this action causes a modification of the file (and a prompt to save at exit). \index{play-set} The \textsl{play-set} is the current set of patterns to be played. Normally, this set holds only the active patterns in the current play-screen. However, it can also be configured to include patterns from other sets during playback. (See \sectionref{subsec:configuration_rc}.) The list of output busses is either the existing MIDI ports on the system, or, if port-mapping (see \sectionref{sec:port_mapping}) is active, the list of mapped output ports. Port-mapping is a useful way to redirect the set to a different output device; it can be used to provide a full set of virtual devices that any of the user's sequences can depend on. \index{--bus option} Another way to specify busses is the \texttt{-{}-buss n} command-line option. It causes \textsl{every} pattern in \textsl{every} set in the MIDI file to be directed to that buss number, and when a new sequence/pattern is created. This option is only for convenience in testing. Save the file, and it will have that buss number as part of each track's data, which makes the song file less portable, so be careful with both options. \subsubsection{Global Beats Per Measure} \label{subsubsec:introduction_global_beats_per_measure} This drop-down changes the global beats/measure for the song. Along with the beat-width setting, this set of values allows for a number of different time signatures. \textsl{Warning}: It modifies every pattern in the song, but is not otherwise saved in the configuration. Values: \texttt{1 to 16, 32}. \subsubsection{Global Beat Width} \label{subsubsec:introduction_global_beat_width} This drop-down changes the global beat width (time-signature denominator) for the song. Along with the beats-per-measure setting, this set of values allows for a number of different time signatures. \textsl{Warning}: It modifies every pattern in the song, but is not otherwise saved in the configuration. Because MIDI encodes only beat widths that are a power of 2, the values are limited. Values: \texttt{1, 2, 4, 8, 16, 32} \subsubsection{BBT or HMS Time Display} \label{subsubsec:introduction_time_display} This push-button shows the current time during playback. It can be shown in BBT (bars:beats:ticks) or HMS (hours:minutes:seconds), which can be switched by clicking this button. \textsl{The \textbf{BBT/HMS} button that was at the bottom of the window has been removed.} \subsubsection{Beat Indicator} \label{subsubsec:introduction_beat_indicator} The beat indicator is inspired by the \textsl{Kepler34} implementation. It shows the first beat in color, and the rest of the beats in the theme color. It does not adapt to changes in the time-signature until playback is stopped. \subsection{Patterns Panel (Live Grid)} \label{subsec:introduction_main_live_grid} In the center of the main window is the \textsl{patterns panel}, also known as the \textsl{live grid}, where patterns/loops/tracks are shown and where they can be controlled. A MIDI file can be dragged-and-dropped onto the live grid in order to open it. Also shown in the patterns panel are a background-record button, metronome button, a grid-mode dropdown to control how the grid is used, a button to choose how recording works (e.g. overdub versus merge), and a button to indicate how recording is quantized. The patterns panel is discussed in more detail in \sectionref{sec:patterns_panel}. \subsection{Main Bottom Controls, First Row} \label{subsec:introduction_main_bottom_controls} The bottom main control items take up two rows. Here is the first row and its contents: \begin{figure}[H] \centering \includegraphics[scale=0.75]{main-window/main-window-bottom-1.png} \caption{Main Window Bottom, First Row} \label{fig:main_window_bottom_1} \end{figure} \begin{itemize} \item \textbf{Set Name} \item \textbf{MG} (Mute Group) \item \textbf{Underrun Indicator} \item \textbf{Active Set Indicator} \item \textbf{Set Reset ("!")} \item \textbf{Set 0 Selector} \item \textbf{Set Changer} \end{itemize} \subsubsection{Set Name} \label{subsubsec:introduction_mg} Each of the 32 available screen-sets can be given a name by entering it into this field. This name is saved with the MIDI file. This text field shows the name of the current set, and also allows editing the set name. The existing sets can be seen in the \textbf{Sets} tab. \subsubsection{MG (Mute Group)} \label{subsubsec:introduction_mute_group} This text field shows the name of the current mute-group, if one is in force. The existing mute-groups, if any, can be seen in the \textbf{Mutes} tab, or in the active 'mutes' files specified in the 'rc' main configuration file. \subsubsection{Underrun Indicator} \label{subsubsec:introduction_underrun_indicator} This text field is empty under normal circumstances. However, if one opens a pattern editor for a pattern with a lot of events for playback, then this field might start flashing numbers, which indicate an underrun value in microseconds. This gives an indication that timing of playback might be off a bit, usually less than 10 milliseconds. This happens because the event-list is locked during redrawing, when recording, to avoid problems when new notes come in. \subsubsection{Set Reset ("!")} \label{subsubsec:introduction_set_reset} \textsl{Seq66} has a new feature whereby multiple sets can play at once. This button, an exclamation point, will reset the playback to the patterns in the current set. It clears all playing sets, and makes only the current set the playing set. This button is useful when in "all-sets" mode or when sets get added automatically via the "additive" mode, so that multiple sets are playing at once. See \sectionref{subsec:configuration_rc}. \subsubsection{Set Master Button} \label{subsubsec:introduction_set_master_button} \textsl{The Set Master button has been removed. The external window it used to bring up has been replaced by the \textbf{Set Master} tab.} \subsubsection{Active Set Indicator} \label{subsubsec:introduction_active_set_indicator} This read-only text field shows the set number of the currently active set and the total number of sets. One can open a number of external \textsl{Live Frames} by \textsl{shift-left-clicking} on pattern slots. The currently active set is then the set that has the mouse focus. This allows for working with multiple sets without a lot of mouse/keyboard navigation. Note that there is always at least one set in a \textsl{Seq66} tune. \subsubsection{Set 0 Selector} \label{subsubsec:introduction_set_zero} This button provides a fast way to get back to set 0. It is useful when creating an external live grid (see \sectionref{fig:multiple_live_grids}), which currently also sets the main grid to the same set. Click on this button, and the external set and set 0 can be seen at the same time. \subsubsection{Set Changer} \label{subsubsec:introduction_set_changer} This spin widget selects the current screen-set. The values in this field range from 0 to 31 (less if the set-size is a larger value), and default to 0. This spin-box moves the main window to another set. This set can be modified by adding new patterns, changing its name, or importing other MIDI files into the current set. Normally, changing the set silences the current set. But there are other options for a "sets mode". See \sectionref{subsection:edit_preferences_play_options}. \subsection{Main Bottom Controls, Second Row} \label{subsec:introduction_main_bottom_controls_2} The bottom panel of the main window provides ways to control the overall playback of the song. It has changed quite a bit over the last few versions of \textsl{Seq66}. % Refer to the diagram of the whole window, for now. Here is the second row and contents: \begin{figure}[H] \centering \includegraphics[scale=0.75]{main-window/main-window-bottom-2.png} \caption{Main Window Bottom, Second Row} \label{fig:main_window_bottom_2} \end{figure} \begin{itemize} \item \textbf{Panic!} \item \textbf{Stop} \item \textbf{Pause} \item \textbf{Play} \item \textbf{Record Button} \item \textbf{L/R Loop} \item \textbf{Live Song Record} \item \textbf{Keep Queue (Q)} \item \textbf{Mute Group Learn} \item \textbf{Developer Test} \item \textbf{Mute} (Toggle Mute Status) \item \textbf{Song Editor} \item \textbf{Live/Song} \item \textbf{PPQN Indicator} \item \textbf{Tap BPM} \item \textbf{Beats Per Minute Control} \end{itemize} Many of these controls have keystrokes and MIDI-control slots that can be set up in the 'ctrl' file. Note that \textbf{BBT/HMS Toggle Button} has been removed (version 0.99.9). One can now click on the \textbf{BBT/HMS Time Display} to change the format. \subsubsection{Panic} \label{subsubsec:introduction_panic_button} This button causes playback to stop, all patterns to mute, sends Note Off messages for all notes, and flushes the MIDI buss. There is a keystroke control and a MIDI control for this automation operation, plus a MIDI-announcement (output) configuration item for it. \subsubsection{Stop} \label{subsubsec:introduction_stop_button} This red square button stops playback and rewinds to the beginning of the song. \index{keys!esc (stop)} By default, the \texttt{Esc} key operates this function, and there is both a MIDI-control slot and a MIDI-announcement slot available for it. The \texttt{Space} keystroke will do the same thing if playback is in progress; it is effectively a playback toggle key. This key is also hardwired to pause and start playback in the pattern editor and the song editor. \subsubsection{Pause} \label{subsubsec:introduction_pause_button} This button stops playback, but does not rewind to the beginning of the song. It also resumes playback at the same point as the pause. By default, the \texttt{Period} key operates this function, and there is a MIDI-control slot and a MIDI-announcement slot available for it. This key is also hardwired to pause and start playback in the pattern editor and the song editor. \subsubsection{Play} \label{subsubsec:introduction_play_button} This green triangular button starts playback, either at the beginning or at the pause point. Also called the "start button". By default, the \texttt{Space} key operates this function, and there is both a MIDI-control slot and a MIDI-announcement slot available for it. This key is also hardwired to toggle playback in the pattern editor and the song editor. \subsubsection{Record Button} \label{subsubsec:introduction_record_button} This red circular button is normally disabled. It turns on recording for the various kinds of recording described in \sectionref{sec:recording}. Its color indicates the record-by feature in force: \begin{itemize} \item \textbf{Grey}. No pattern is selected, so this button is non-functional. \item \textbf{Red}. This color indicates the normal recording mode, which toggles only a single pattern for recording. This button is enabled and colored red only if a pattern is in recording mode. \item \textbf{Yellow}. This color indicates the record-by-channel recording mode, which toggles, for recording, all patterns that have an output channel set. This mode allows events to be redirected to the sequence whose number is the channel number (0-15) of the event. Incoming events that have a channel nybble are routed to that pattern. However, there must already be a pattern in that slot. See \sectionref{subsection:edit_preferences_midi_input}. \item \textbf{Green}. This color indicates the record-by-buss recording mode, which toggles, for recording, all patterns that have an input bus set. This button is green only if there is at least one such pattern. This mode allows events to be redirected to the sequence whose number is the buss number (0-31) of the event. A pattern can be assigned an input buss via its right-click menu, but only if \textbf{Edit / Preferences / MIDI Input / Record into patterns by bus} is enabled. See \sectionref{subsection:edit_preferences_midi_input}. \end{itemize} These modes are selected in the 'rc' file as described in \sectionref{subsubsec:configuration_rc_midi_cmt}. They are mutually exclusive. \subsubsection{L/R Loop} \label{subsubsec:introduction_loop_button} This button also appears in the "external" version of the Song editor. This looping mechanism is also available in Live mode as well. The \textbf{L/R} loop markers in the song editor can now be used in the pattern editor as well. This new feature makes it easier to focus in on a pattern and tinker repeatedly with the same small section. In addition, looping can now be done in both the Live and Song modes of playback. As with the "L" and "R" markers in the pattern editor, this can be placed via left and right mouse clicks. Note that the "L" and "R" markers can be selected via the keyboard using their respective shifted key. Once selected, the marker can be moved left or right using the left and right arrow keys. \index{loop mode} It activates loop mode; when play is active, it plays the song and loops between the \index{L marker} \index{R marker} \textbf{L marker} and the \textbf{R marker}. It is a state button, and its appearance indicates when it is depressed, and thus active. \textsl{If this button is deactivated during playback, then playback continues past the \textbf{R marker}.} Note that these markers can be placed using left and right mouse clicks, respectively, in the time/measures ruler. Furthermore, the \textbf{L/R} markers can also be set in a pattern editor, where they can be used to focus in on a small section of notes. \subsubsection{Live Song Record} \label{subsubsec:introduction_live_record_button} Song-recording in \textsl{Seq66} is adopted from the \textsl{Kepler34} project. This feature takes live muting changes and records them as triggers in the \textbf{Song Editor}. The default hot-key for this function is \texttt{P}. This feature does not honor queuing... rather than waiting until the end of the pattern when the queuing takes effect, the trigger recording starts immediately. This button causes a live playing session to be recorded to Song mode. That is, triggers are added to the song automatically as the musician mutes and unmutes patterns, and the triggers can then be seen as layouts in the \textsl{Song} editor. % FIXME: % By default, the \texttt{P} key operates this function. Using the \texttt{Ctrl} modifier while clicking this button turns off the recording-snap feature. This change can also be done in the song editor. Using the \texttt{Shift} modifier while clicking this button causes recording immediately after starting playback. This feature is useful in avoid delay at start up. \subsubsection{Keep Queue (Q)} \label{subsubsec:introduction_keep_queue_button} This button provides a visual way to know the current state of keep-queue, and is activated either by clicking on it or by pressing the assigned keep-queue key. It puts the application into a "sticky" queue mode. In this mode, pressing a pattern key does not do a mute/unmute function, but instead turns on queuing for the selected pattern. By default, the \texttt{Backslash} key operates this function, and there is a MIDI-control slot available for it. \subsubsection{Mute Group Learn} \label{subsubsec:introduction_mute_group_learn_button} \index{L button} Also called the "L" button. Sets up to learn the current set of active patterns ("mute group") into a mute-group. The default keystroke to control this button is lower-case "L". When in group-learn mode, the \texttt{Shift} key cannot be hit, so the group-learn mode automatically converts the keys to their shifted versions. This feature known as \textsl{shift-lock} or \textsl{auto-shift}. This feature is not used if a non-QWERTY keyboard layout is specified in the 'ctrl' file. After pressing the "L" button, the user can then press a keystroke, which is automatically shifted, and the pattern set is saved, and can be recalled by pressing that button, shifted, later. It can be saved in a 'mutes' file, as part of the MIDI tune, or in both places. \textsl{Example}: We have 5 patterns armed in the current set. Press the "L" button, and then press the "s" key. These pattern statuses are saved and can be recalled later by the "S" ("s"-shifted) key. By default, the \texttt{el} (lower-case "L") key also sets this function, and there is a MIDI-control slot available for it, as well as a MIDI-announcement slot. In addition to that, one can also press the \texttt{Ctrl-L} key. The "el" with it! Remember that groups work with the playing ("in-view") screen-set. One must change the screenset and give it the command to make it the playing one. \index{keys!Home} By default, the \texttt{Home} key is configured for this purpose. There is a setting in the 'mutes' file called \texttt{mute-group-selected}. If this value is set to a value from 0 to 31, then that mute group will be automatically applied when \textsl{Seq66} starts up. This is useful with the loading of the most-recent MIDI file (which is another feature of \textsl{Seq66}). Also see \sectionref{sec:mutes_master}. \subsubsection{Developer Test} \label{subsubsec:introduction_developer_test_button} This button is always disabled. Functionality is added temporarily when testing new features. Ignore this button. \subsubsection{Toggle Mute Status} \label{subsubsec:introduction_toggle_mute_status_button} The \textbf{Mute} toggles the mute/armed status of all of the patterns in the current set. Works the same as the \textbf{Edit / Toggle All Tracks} menu, the hard-wired \texttt{Ctrl-T} key, or the automation control entry for it, \texttt{F8} by default. \subsubsection{Song Editor} \label{subsubsec:introduction_song_editor_button} This button (with a "pencil" icon) brings up an external window for editing the Song/Performance information. If already up, it closes it. Works the same as the \textbf{Edit / Song Editor} menu or the hard-wired \texttt{Ctrl-E} key. \subsubsection{Live/Song Mode} \label{subsubsec:introduction_livesong_mode_button} This button toggles between the \textsl{Live} and \textsl{Song} performance mode. In the Live mode, the musician controls are muting/unmuting of each pattern. In the Song mode, the triggers layed out in the \textbf{Song Editor} control the playback. By default, the \texttt{F10} key operates this function, There is also a MIDI automation control for this button. Note that the default mode is configurable, and \textsl{Seq66} can go into song mode when a loaded MIDI file has triggers, if configured to do so. \begin{comment} \itempar{Toggle Tracks}{pattern!toggle tracks} \index{pattern!toggle tracks} This button changes the status of all of the \textsl{playing} tracks, reversing the mute status of each pattern that is playing. The next click will then unmute only those tracks. Because it can be confusing, this button is disabled in Song mode. The \texttt{Ctrl-M}, \texttt{Ctrl-U}, and \texttt{Ctrl-T} keys, as shown in the \textbf{Edit} menu, mute, unmute, and toggle all patterns. \end{comment} \subsubsection{PPQN Indicator} \label{subsubsec:introduction_ppqn_indicator} This read-only field displays the current PPQN for the current tune. \configref{usr}{user-midi-settings}{midi\_ppqn} \subsubsection{BBT/HMS Toggle (removed)} \label{subsubsec:introduction_time_format_toggle_button} The BBT/HMS toggle button \textsl{has been replaced}. See \sectionref{subsec:introduction_main_top_controls}. % Toggles the format of the current time displayed during playback. % It can be shown in B:B:T (bars:beats:ticks) or H:M:S (hours:minutes:seconds). \subsubsection{Tap BPM Button} \label{subsubsec:introduction_tap_bpm_button} This control is clicked in time with a tune, to set the tempo based on the tempo of the clicks. Once clicked, the label of this button increments with every click, and the \textbf{BPM} field updates to display the calculated tempo. If the user stops tapping for 5 seconds, the label reverts to 0, the BPM value keeps its final value, and the user can try tapping the tempo again, or accept the current value. Tapping can also be done using the keystroke defined in the 'ctrl' file. It defaults to the "\texttt{F9}" key, and there is also a MIDI-control slot for it. Tap this button with a regular beat to determine the beats-per-minute of the tapping. \subsubsection{Beats Per Minute Control} \label{subsubsec:introduction_bpm_control} The spin widget adjusts the "beats per minute" (BPM) value. The range of this field is from 1 bpm to 600 bpm, with a default value of 120 bpm. The following keys can also modify the BPM in small increments: \index{keys!semicolon} The \texttt{semicolon} reduces the BPM; \index{keys!apostrophe} The \texttt{apostrophe} increases the BPM. Also, if one \textsl{right-clicks} on the \textbf{Up} button, the BPM advances to its largest supported value, and if one \textsl{right-clicks} on the \textbf{Down} button, the BPM advances to its lowest value. MIDI control for this value is also available. The precision of the BPM value can be set to 0, 1, or 2 decimal places, and the increment values for the step size (small) or page size (large) of the BPM spinner can be configured in the 'usr' file. This control can be text-edited or spun to change the beats/minute value used in playing back the current song. This value is also saved to the file. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/menu.tex ================================================ %------------------------------------------------------------------------------- % menu %------------------------------------------------------------------------------- % % \file menu.tex % \library Documents % \author Chris Ahlstrom % \date 2015-08-31 % \update 2025-05-31 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides the Menu section of seq66-user-manual.tex. % %------------------------------------------------------------------------------- \section{Menu} \label{sec:menu} The \textsl{Seq66} menu structure is more complex than that of \textsl{Seq24}. In particular, the \textsl{File} menu has two variants: a normal file menu, and a file menu when \textsl{Seq66} is running under the \textsl{New/Non Session Manager}. (See \sectionref{subsec:sessions_nsm}.) \subsection{Menu / File} \label{subsec:menu_file} The \textbf{File} menu is used to save and load files in Standard MIDI Format 0 or 1, \textsl{Cakewalk} "WRK", and \textsl{Seq66} MIDI files. It also supports a list of recent files, and sub-menus for import and export functions, which have expanded quite a bit. The \textsl{Seq66} \textbf{File} menu contains the sub-items shown below. The next few sub-sections discuss the sub-items in the \textbf{File} menu. Please note that these entries are different if \textsl{Seq66} is started under the control of the \textsl{New/Non Session Manager}. See \sectionref{subsubsec:sessions_file_menu}. However, the import and export menus remain the same, although there are slight differences in how they work. \begin{figure}[H] \centering \includegraphics[scale=0.75]{main-menu/file/file-import-export-menus.png} \caption{Seq66 File Menu Plus Import/Export, Composite View} \label{fig:menu_file_items} \end{figure} \begin{enumber} \item \textbf{New} \item \textbf{Open} \item \textbf{Open Playlist} \item \textbf{Recent MIDI files} \item \textbf{Save} \item \textbf{Save As} \item \textbf{Import} \begin{enumber} \item \textbf{Project Configuration...} \item \textbf{MIDI to Current Set...} \item \textbf{Playlist...} \index{restart!automatic} Once the playlist is imported, \textsl{Seq66} is automatically \textsl{\textbf{restarted}} in order to load the playlist. Be careful! \end{enumber} \item \textbf{Export} \begin{enumber} \item \textbf{Project Configuration...} \item \textbf{MIDI Only...} \item \textbf{Song...} \item \textbf{SMF 0...} \end{enumber} \item \textbf{Quit} (\textbf{Exit} in \textsl{Windows}) \end{enumber} For information on the \textbf{File} menu when \textsl{Seq66} is running under the \textsl{Non Session Manager}, see \sectionref{subsubsec:sessions_file_menu}. \subsubsection{Menu / File / New} \label{subsec:menu_file_new} The \textbf{New} menu entry clears the current song. (A play-list or mute-groups setup, if loaded, are not affected.) If unsaved changes are pending, the user is prompted to save the changes. Prompting for changes is more comprehensive than \textsl{Seq24}. However, when in doubt, save! Keep backups of your tunes and configuration files! \subsubsection{Menu / File / Open} \label{subsubsec:menu_file_open} The \textbf{Open} menu entry opens a song (MIDI file or \textsl{Cakewalk} WRK file), replacing the current song (after a prompt if the song was modified). It opens up a standard file dialog: \begin{figure}[H] \centering \includegraphics[scale=0.65]{main-menu/file/light-menu-file-open.png} \caption{File / Open} \label{fig:menu_file_open} \end{figure} This dialog lets one type a file-name, highlighting the first file that matches the characters typed. \textsl{Seq66} can open \textsl{Seq66}, MIDI SMF 0, and SMF 1 files, and \textsl{Cakewalk} WRK files. If the file is an SMF 0 file, where all channels appear on one track, the track can be split so that each channel (0 to 15) is stored in the corresponding pattern, pattern 0 also holds meta events, and pattern 16 contains the original track. This feature is enabled via \textbf{Edit / Preferences / Pattern / Automatic conversion of SMF 0 to SMF 1} or the 'usr' setting \texttt{convert-to-smf-1 = true}. Note that a MIDI file can be drag-and-dropped from a file manager onto the grid to open a file. \subsubsection{Menu / File / Open Playlist} \label{subsubsec:menu_file_open_playlist} The \textbf{Open Playlist...} menu entry opens a \textsl{Seq66} play-list file. \begin{figure}[H] \centering \includegraphics[scale=0.65]{main-menu/file/dark-menu-file-open-playlist.png} \caption{File / Open Playlist} \label{fig:menu_file_open_playlist} \end{figure} The playlist file contains a list of "playlist sections", each listing a number of MIDI songs. These playlists and songs can be selected by the arrow keys or by MIDI control, and are displayed and editiable in the \textsl{Playlist} tab in the main window. See \sectionref{sec:playlist}. \subsubsection{Menu / File / Recent MIDI files} \label{subsubsec:menu_file_recent} This menu entry provides a list of the last few MIDI files created or opened; play-list selections are \textsl{not} included in this list. \begin{figure}[H] \centering \includegraphics[scale=0.65]{main-menu/file/menu-recent-files.png} \caption{Seq66 Menu File Recent Files} \label{fig:menu_file_recent_files} \end{figure} Here is the long form when the 'rc' file's \texttt{[recent-files] full-paths} value is set to true: \begin{figure}[H] \centering \includegraphics[scale=0.65]{main-menu/file/menu-recent-files-long.png} \caption{Seq66 Menu File Recent Files, Full Paths} \label{fig:menu_file_recent_files_full_paths} \end{figure} This list is saved in the \texttt{[recent-files]} section of the 'rc' configuration file. In the 'rc' file, the full path to the file-name is stored. This path is in "UNIX" format, using the forward slash (solidus), as the path separator, even in \textsl{Windows}. The \texttt{full-paths} option can be set to show the full path in the recent-files drop-down menu. Only unique entries are included in the recent-files list. The limit is 12 recent-file entries. This is a feature from \textsl{Kepler34} \cite{kepler34}. One can also set \textsl{Seq66} to load the most-recent file at startup. Here is an example from an 'rc' file: \begin{verbatim} [recent-files] full-paths = false load-most-recent = true count = 3 /home/user/git/seq66/data/b4uacuse-gm-patchless.midi /home/user/git/seq66/data/midi/colours.midi /home/user/git/Julian-data/TestBeeps.midi \end{verbatim} \subsubsection{Menu / File / Save and Save As} \label{subsubsec:menu_file_open_save_as} The \textbf{Save} menu entry saves the song under its current file-name. If there is no current file-name, it opens up a standard file dialog to name and save the file. The \textbf{Save As} menu entry saves a song under a different name. It opens up the following standard file dialog, very similar to the \textbf{File Open} dialog, with an additional \textbf{Name} text-edit field. The exact look of the dialog depends on system Qt settings or the current Qt theme. \begin{figure}[H] \centering \includegraphics[scale=0.65]{main-menu/file/dark-menu-file-save-as.png} \caption{File / Save As} \label{fig:menu_file_save_as} \end{figure} To save a new file or save the current file to a new name, enter the name in the name field, without an extension. \textsl{Seq66} will append a \texttt{.midi} extension to the filename. The file will be saved in a format that the Linux \textsl{file} command will tag as something like: \begin{verbatim} colours.midi: Standard MIDI data (format 1) using 16 tracks at 1/192 \end{verbatim} It looks like a simple MIDI file, and yet, if one re-opens it in \textsl{Seq66}, one sees that the mute-groups, labeling, pattern information, and song layout have been preserved in this file. This information is saved in a way that MIDI-compliant software should be able to use or ignore without failure. After the last track in the file, a number of \index{SeqSpec} sequencer-specific (SeqSpec) items are saved, to preserve the extra information that \textsl{Seq66} adds to the song. There is no way to save a \textsl{Cakewalk} "WRK" file. \textsl{Seq66} can only read them, and then save them as \textsl{Seq66} files. \index{Meta events} Meta events are now handled by \textsl{Seq66}. Meta events \textbf{Set Tempo} and \textbf{Time Signature} are now fully supported. Other meta events, such as \textbf{Meta MIDI Channel} and \textbf{Meta MIDI Port} are now read as events, and are saved back when the file is saved. They cannot be edited in \textsl{Seq66}, but they are not lost. (Channel and port meta events are considered \textsl{obsolete} in the MIDI standard.) Various meta text events, such as \textbf{Lyric}, can be edited and saved. \subsubsection{Menu / File / Import / Project Configuration} \label{subsubsec:menu_file_import_project_configuration} This command is useful to grab an existing project configuration (i.e. the set of \texttt{qseq66.*} files) and copy it to the current "home" configuration directory. This command is most useful in importing a project into a new NSM session. Previously, the "home" project would be imported automatically into a new NSM session, but this was deemed confusing by some users, and properly so! This command brings up a file dialog box. Navigate to the desired source directory and then select the desired 'rc' file. Any configuration files in the current "home" directory are removed and replaced with all files and subdirectories from the selected directory. If NSM is not running, \textsl{Seq66} restarts and loads the imported configuration. Be aware! Otherwise, a prompt to restart using the NSM client is shown. For more information, see \sectionref{subsubsec:midi_export_file_import_project}. \subsubsection{Menu / File / Import / MIDI to Current Set} \label{subsubsec:menu_file_import} The \textbf{Import} menu entry imports an SMF 0 or SMF 1 MIDI file as one or more patterns, one pattern per track, into the specified screen-set. This functionality is explained in detail in \sectionref{subsubsec:midi_export_file_import}. \subsubsection{Menu / File / Import / Playlist} \label{subsubsec:menu_file_import_playlisMIDI to Current Sett} A user can create a playlist that accesses MIDI files anywhere in the file system. However, in a session manager, it is preferable to have the configuration self-contained. Even without a session manager, it can be useful to copy a playlist to a subdirectory in order to separate it and its MIDI files from other playlists. Once a project has been imported or saved, then a playlist can also be imported, along with all of the MIDI files it references. This command brings up a file dialog box. Navigate to the desired source directory and then select the desired 'playlist' file. This menu entry copies the playlist file and its associated MIDI files; see \sectionref{subsubsec:midi_export_file_import_playlist}. \subsubsection{Menu / File / Export / Project Configuration} \label{subsubsec:menu_file_export_project} This menu entry lets the user select a destination directory. Then the project files from the current "home" directory are copied to that destination directory. Useful for backup or for making an experimental configuration official. See \sectionref{subsubsec:midi_export_configuration_export}. \subsubsection{Menu / File / Export / Song} \label{subsubsec:menu_file_export_song_as_midi} Thanks to the \textsl{Seq32} project, the ability to export songs to MIDI format has been added. In this export, a complete song performance is recoded so that other MIDI sequencers can play the performance properly. This functionality is explained in detail in \sectionref{subsubsec:midi_export_song_export}. \subsubsection{Menu / File / Export / MIDI Only} \label{subsubsec:menu_file_export_midi_only} Sometimes it might be useful to export only the non-vendor-specific (non-SeqSpec) data from a \textsl{Seq66} song, in order to reduce the size of the file or to accomodate non-compliant sequencers. This functionality is explained in detail in \sectionref{subsubsec:midi_export_file_export_midi_only}. \subsubsection{Menu / File / Export / SMF 0} \label{subsubsec:menu_file_export_smf_0} This feature allows all tracks in the song to be consolidated and exported in MIDI's SMF 0 format. It follows rules similar to song export. See \sectionref{subsubsec:midi_export_file_export_smf_0}. \subsection{Menu / Edit} \label{subsec:menu_edit} The \textbf{Edit} menu has undergone some expansion in \textsl{Seq66}. \begin{enumber} \item \textbf{Preferences...} \item \textbf{Song Editor} \item \textbf{Apply Song Transpose} \item \textbf{Clear Mute Groups} \item \textbf{Reload Mute Groups} \item \textbf{Mute All Tracks} \item \textbf{Unute All Tracks} \item \textbf{Toggle All Tracks} \item \textbf{Copy Current Set} \item \textbf{Paste To Current Set} \end{enumber} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Preferences}{edit!preferences} This entry brings up a \textbf{Preferences} menu entry, to allow viewing and tweaking MIDI I/O ports, displays options, JACK options, and more. It can also be brought up by \texttt{Ctrl-P}. It has a lot of configuration items, and is discussed in \sectionref{sec:edit_preferences}. \itempar{Song Editor}{edit!song editor} \index{performance editor} \index{song editor} This item toggles the presence of the main song/performance editor. Note that the song editor is also available in the \textbf{Song} tab in the main window. The song/performance editor allows specifying exact numbers of loop replays; this provides a canned rendition of all the patterns in theMIDI tune. \itempar{Apply Song Transpose}{edit!song transpose} \index{song transpose} Selecting this item applies the global song transposition value to all sequences / patterns marked as transposable. This actively changes the note / pitch value of all note and aftertouch events in every pattern. The change can range from -12 semitones to 12 semitones. For the setting of the global song transpose value, see \sectionref{sec:song_editor}. Normally, drum tracks are \textsl{not} transposable. This can be set by clicking the "transposable" button in the pattern editor. % Note that transpose can be enabled in the % in the sequence editor % (see \sectionref{sec:pattern_editor}). \itempar{Clear Mute Groups}{edit!clear mute groups} \index{mute groups} A feature of \textsl{Seq66} is that the mute groups can be saved in both the 'rc' file \textsl{and} in the "MIDI" file. This menu entry clears them. If this resulted in any mute-group sequences status being set to false, then the user is prompted to save the MIDI file, so that it will no longer have any mute-group information. And then, if the application exits, the cleared mute-group information is also saved to the 'rc' file. \itempar{Reload Mute Groups}{edit!load mute groups} \index{rc!mute groups} This menu entry reloads the mute-groups from the 'rc' file. So, if one loads a MIDI file that has its own mute groups that one does not like, this command will restore one's favorite mute-grouping from the 'rc' file. \itempar{Mute All Tracks}{edit!mute all tracks} \index{mute all} This menu entry, useful mostly in \textbf{Live} mode, immediately mutes \textsl{all} patterns in the entire song. The hard-wired menu short-cut for this action is \texttt{Ctrl-M}. \itempar{Unmute All Tracks}{edit!unmute all tracks} \index{unmute all} This menu entry, useful mostly in \textbf{Live} mode, immediately unmutes \textsl{all} patterns in the entire song. The hard-wired menu short-cut for this action is \texttt{Ctrl-U}. \itempar{Toggle All Tracks}{edit!toggle all} \index{toggle all} This option toggles the mute/armed status of \textbf{all} tracks. It is useful mostly \textbf{Live} mode, which overrides \textbf{Song} mode even if the Song Editor is focused. The hard-wired menu short-cut for this action is \texttt{Ctrl-T}. The default keystroke in the 'ctrl' file is \texttt{F8}. \itempar{Copy Current Set}{edit!copy set} \index{copy set} This item marks the current set (i.e. the play-set) for the copying of all its patterns to another set. After clicking this menu entry, one can move to another set to paste it, using the following menu entry. \itempar{Paste To Current Set}{edit!paste set} \index{paste set} Once a set has been copied into the internal set clipboard, then this menu item is enabled. Move to the desired set (whether empty or note), and then click this menu item. All of the patterns in the original set are pasted into the current set, \textsl{overwriting all patterns} already in the set. Also note that the set clipboard can be pasted after a \textbf{File / New} or \textbf{File / Open}, to copy it into another file. \subsection{Menu / Help} \label{subsec:menu_help} The usual \textbf{Help} dialog is provided. As of version 0.98.8, it has been beefed up with a way to access a tutorial and the user manual. These new help items are a work in progress, so please apprise us of any issues; include information on the operating system and, if \textsl{Linux}, the desktop/window manager in use. \subsubsection{Menu / Help / About...} \label{subsubsec:menu_help_about} \index{Help!about} This menu entry shows the "About" dialog. That dialog provides access to some credits for the program as well. authors and the project documentors, and active link to them. It also shows Git version-control information as well. \subsubsection{Menu / Help / Build Info...} \label{subsubsec:menu_help_build_info} \index{Help!build info} This menu entry shows the "Build Info" dialog. This list of build options enabled in the current application is the same list that it generated via this command line: \begin{verbatim} $ seq66 --version \end{verbatim} \subsubsection{Menu / Help / Song Summary File...} \label{subsubsec:menu_help_song_summary_file} \index{Help!song summary} This menu entry allows one to write a summary of the song data into a text file. It brings up a file dialog which defaults to the name of the currently-loaded MIDI file, with the extenstion \texttt{.text} and the directory from where the MIDI file was loaded. It shows the filename, the information about the sets and tracks, MIDI format (0 or 1), and the PPQN. It also shows each sequence: name, channel (128 mean there is no output channel), the time signature, buss number (and any mapping), the length in pulses, the event and trigger count, transposability, key and scale, and color number (if any). For each trigger in the pattern, its start, stop, offset, and transposition values are shown. This file can be helpful for trouble-shooting or solving puzzling effects in the tune. \subsubsection{Menu / Help / App Keys} \label{subsubsec:menu_help_app_keys} \index{Help!app keys} This entry brings up a dialog that shows brief descriptions of the non-automation keys available in various contexts. These keys are almost exclusively hardwired and currently cannot be changed via a configuration file. By pressing a button, the desired keystrokes can be quickly viewed. Note that the descriptions come from small HTML files that are part of the installation. \subsubsection{Menu / Help / Tutorial} \label{subsubsec:menu_help_tutorial} \index{Help!tutorial} This entry brings up a short tutorial of \textsl{Seq66} in the default browser. This tutorial is meant only to jump-start a new user of \textsl{Seq66}, and is a work in progress. It does not cover nearly as much as the user manual, so check that out in the next section. Normally, the tutorial will open a web page. If it does not, one might need to set up a default browser. On Linux, make sure that there is a "desktop" file for the browser, as in \begin{verbatim} /usr/share/applications/firefox.desktop \end{verbatim} If so, then run the following command, and then test it: \begin{verbatim} $ xdg-settings set default-web-browser firefox.desktop $ xdg-open https://ahlstromcj.github.io/docs/seq66/tutorial/index.html \end{verbatim} Windows will have its own methods, but in both systems, one can override the default applications by opening the specified 'usr' file (usually \texttt{qseq66.usr} or \texttt{qpseq66.usr} and specifying the full path to the desired applications (Linux paths shown here): \begin{verbatim} [user-options] log = "/home/user/.config/seq66/seq66.log" pdf-viewer = "/usr/bin/zathura" browser = "/usr/bin/google-chrome" \end{verbatim} See \sectionref{subsubsec:usr_file_user_options}. This setup can also be done in \textbf{Edit / Preferences / Session}; see \sectionref{subsection:edit_preferences_session}. \subsubsection{Menu / Help / User Manual} \label{subsubsec:menu_help_user_manual} \index{Help!user manual} This menu entry first tries to locate the user manual on the internet and open it in the default browser. If not found, or the network is down, then this entry brings up the full \textsl{Seq66} user manual in the default PDF viewer. It currently looks in the possible installation areas and in the \textsl{Seq66} source tree to find the PDF. On Linux, one can follow the setup procedure in the previous section and test it via the following command, which will show the manual in the default browser.: \begin{verbatim} $ xdg-open https://ahlstromcj.github.io/docs/seq66/seq66-user-manual.pdf \end{verbatim} %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/meta_events.tex ================================================ %------------------------------------------------------------------------------- % meta_events %------------------------------------------------------------------------------- % % \file meta_events.tex % \library Documents % \author Chris Ahlstrom % \date 2017-07-23 % \update 2023-12-11 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides a discussion of the MIDI GUI meta_events that Seq66 % supports. % %------------------------------------------------------------------------------- \section{Seq66 Meta Event / SysEx Support} \label{sec:meta_events} \textsl{Seq66} attempts better support for MIDI Meta and System Exclusive events and a Tempo track. It supports the display of Set Tempo and Time Signature events. They can also be added and edited, in various ways. For example, see \sectionref{sec:event_editor}. For the internal format of Meta events, see \sectionref{subsec:midi_format_meta_format}. For the various formats of SysEx events, see \sectionref{subsubsec:midi_format_meta_sysex_event}. Only the first Time Signature event is used to modify playback. System Exclusive support is also still in progress, but very incomplete. This section consolidates the description of the meta-event support. The following topics apply: \begin{enumerate} \item Tempo display min/max in 'usr' settings. \item Tempo display in main window. \item Tempo display in pattern editor. \item Tempo display in song editor. \item Tempo and Time signature display and editing in the event editor. \end{enumerate} First, we need to note \textsl{how} the tempo track is implemented in \textsl{Seq66}. Rather than make a SeqSpec track for the tempo events, we use the MIDI specification mandate that Tempo events should occur only in the first track. \textsl{Seq66} treats Set Tempo and Time Signature as full-fledged MIDI events that can be viewed (and later, edited) in the existing user-interface. Notes and other events can occur in the same track. % To reiterate, track 1 (pattern 0) is the only track where tempo events % can be placed and edited. \subsection{'usr' BPM Display Settings} \label{subsec:meta_events_usr} \textsl{Seq66} allows the tempo to range from 1 to 600 BPM (beats per minute). This range is hardwired into the application. To display tempo with a little more granularity, \textsl{Seq66} provides scaling for the tempo displays. These values are found in the 'usr' file: \begin{verbatim} 0 # midi_bpm_minimum 360 # midi_bpm_maximum \end{verbatim} This setting can only be made by editing the 'usr' file while \textsl{Seq66} is not running. Note that this setting affects the global BPM setting ("c\_bpmtag"). \begin{comment} \subsection{Composite Display of Tempos} \label{subsec:meta_events_composite_display} The following figure shows a composite picture of the various representations of Set Tempo events. \begin{figure}[H] \centering \includegraphics[scale=0.65]{roll.png} \caption{Various Tempo Displays} \label{fig:meta_events_tempo_displays} \end{figure} The \textsl{top} of the figure shows the magenta tempo lines in a pattern slot that is currently being edited. This view edited, but the event editor and the main window's BPM settings can be used to add, delete, or adjust the tempo. The \textsl{middle} panel shows the very similar representation of the tempo in the song editor. This view does not allow editing of the tempo events. The \textsl{bottom} shows tempo as an event (in the event strip) and a data value in the data pane. A tempo event can be added here by holding the Ctrl key and painting an event in the event strip, and it can then be modified by same method that note velocities can be edited. Tempo events are \textsl{always} shown in the event strip and the data pane, no matter what other \textbf{Event} type has been selected. \end{comment} \begin{comment} \subsection{Tempo in the Main Window} \label{subsec:meta_events_mainwnd} The tempo is shown as a solid magenta-colored line at the relative height for the tempo, based on the minimum and maximum values configured in the 'usr' file as discussed above. This pattern-slot tempo display is rudimentary. It doesn't allow for ramping of the tempo at present (except by recording while holding the BPM spin-control), and cannot be directly edited in this window. However, tempos can be logged or recorded via magenta-colored controls at the bottom of the main window. \begin{figure}[H] \centering \includegraphics[scale=0.65]{roll.png} \caption{Tempo Recording Controls} \label{fig:meta_events_mainwnd_tempo_recording} \end{figure} The 0th pattern slot shown in the figure is Track 1, the MIDI Tempo track. The magenta lines show the tempos already in that track. Now look at the BPM control. The first button to its right ("0") is the tempo-tap button, used for setting a tempo by tapping in time to music. The light-magenta button that comes next, when pressed while playback is occurring, logs a tempo event at the current progress location and the current BPM value in the BPM spin-field. The dark magenta button to the right of that toggles the mode of recording the changes to the BPM spin-button while playback is occurring. Although pattern 0 might start out with a length of only a measure or two, the timer continually ticks upward, and tempo events that are recorded after the end of the track at still recorded, and \textsl{they will extend the length of the tempo track}. The length of each track, in measures, is shown at the top right of each main window pattern slot, so it can be tracked by the user. Once tempo events have been recorded, they can be tweaked (or deleted) either in the pattern editor or in the event editor. Generally, they are treated like control events that are always available. Deleting all tempo events will not reduce the (possibly new) length of the sequence. The Tempo track will \textsl{not} change tempo unless that track is unmuted. This behavior is a feature, not a bug. \end{comment} %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/midi_export.tex ================================================ %------------------------------------------------------------------------------- % midi_export %------------------------------------------------------------------------------- % % \file midi_export.tex % \library Documents % \author Chris Ahlstrom % \date 2018-10-20 % \update 2025-05-30 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % This section discusses the details of the import/export functionality. % %------------------------------------------------------------------------------- \section{MIDI Import/Export} \label{sec:midi_import_export} This section explains the details of the MIDI import and export functionality, accessed by the main menu as noted in sections \ref{subsubsec:menu_file_import}, \ref{subsubsec:menu_file_export_project}, \ref{subsubsec:menu_file_export_song_as_midi}, and \ref{subsubsec:menu_file_export_midi_only}, on page \pageref{subsubsec:menu_file_import}. \subsection{File / Import Menu} \label{subsec:midi_export_file_import_menu} The actions for importing files have been move to the new \textbf{File / Import} menu in order to keep from cluttering the ever-expanding file menu. \subsubsection{Import Project Configuration} \label{subsubsec:midi_export_file_import_project} This operation allows one to import all of the \texttt{qseq66.*} configuration files from a given directory into the current run of \textsl{Seq66}. It is most useful when importing a configuration into a new \textsl{NSM} session. \index{restart!automatic} Once the files are copied, \textsl{Seq66} is automatically restarted, in order to load the new configuration. Be careful! Note that the names of the configuration files being impported should match the canonical names. That is, the base names should all be \texttt{qseq66} (or \texttt{qpseq66} for \textsl{Windows}.) If importing into an NSM session, one must use the NSM user-interface (\textsl{agordejo}, \textsl{RaySession}, or \textsl{nsm-legacy-gui} to \textsl{Stop} and then \textsl{Start} \textsl{Seq66}. \subsubsection{Import MIDI Into Current Set} \label{subsubsec:midi_export_file_import} The \textbf{File / Import / Import to Current Set} menu entry imports an SMF 0 or SMF 1 MIDI file as one or more patterns, one pattern per track, and imports them into the currently-active set. Even long tracks, that aren't short loops, are imported. The difference from \textbf{File / Open} is that the destination screen-set (bank) for the import can be specified, and the existing data in the already-loaded MIDI file is preserved. If the imported file is a \textsl{Seq66} MIDI file, it's proprietary sections will \textsl{not} be imported, in order to preserve the performance setup. The \textbf{Import} dialog is similar to the \textbf{Open} dialog. When imported, each track, whether music or information, is entered into its own loop/pattern box (slot). The import operation can handle reasonably complex files. When the file is imported, the sequence number for each track is adjusted to put the track into the desired screen-set. The import can place the imported data into any of the 32 available screen-sets. Quite large songs can be built by importing patterns. Import (and opening) also handles SMF 0 MIDI files. It parcels out the SMF 0 data into sequences/patterns for each of the 16 MIDI channels. Meta events, having no channel, are added to pattern 0. The SMF 0 import also puts \textsl{all} of the MIDI data into the 17th pattern (pattern 16), in case it is needed. Note that this slot is used no matter which screen-set one imports the file into. Bug, or feature? Also note that, since the file information has been modified by the import, the user will be prompted to save the file when exiting \textsl{Seq66}. Finally, automatic conversion to SMF 1 for SMF 0 files can be disabled using the 'usr' option \texttt{[user-midi-settings] convert-to-smf-1}. \subsubsection{Import Playlist} \label{subsubsec:midi_export_file_import_playlist} This operation allows one to copy a playlist and its MIDI files. from one directory to another. It is most useful when importing a configuration into a new \textsl{NSM} session. Here are the steps it performs: \begin{itemize} \item Copies the selected playlist file (e.g. \texttt{liveset.playlist}) into the current configuration directory. This directory is one of the following: \begin{itemize} \item The default "home" directory, \texttt{\textasciitilde/.config/seq66}. \item A "home" directory specified by the \texttt{-{}-home} option. \item The session directory created by \textsl{NSM}. \end{itemize} For discussion, this directory is called "HOME" or "home". \item In HOME, creates a directory called (for example) \texttt{playlist/liveset}, where "liveset" is the base part of the playlist name, as in \texttt{liveset.playlist}. \item The playlist is opened, parsed, and all of the MIDI tunes specified in that file are copied into the new playlist directory, preserving the directory directory structure of the original playlist. \item The 'rc' file is modified to specify the new playlist, specify that it is \texttt{active}, to specify the new \texttt{base-directory} for all of the tunes, and to specify that the playlist will be saved at exit. \item Lastly, if not running within NSM, \textsl{Seq66} will be \index{restart!automatic} \textsl{\textbf{restarted}} automatically to load the new, active playlist. Within NSM, the user must stop and restart \textsl{Seq66} manually in the NSM user-interface. Be careful! \end{itemize} \subsection{File / Export Menu} \label{subsec:midi_export_file_export_menu} The actions for exporting files have been moved to the \textbf{File / Export} menu in order to keep from cluttering the ever-expanding file menu. An important point to note is the pattern/channel behavior inherited from \textsl{Seq24}. When a pattern has a channel specified, all MIDI channel events (e.g. Note On/Off) are stamped with that channel when exported. This applies to exports to plain MIDI files, song exports, and exports to the SMF 0 format. If one wants to preserve the channel number for all events in a pattern, then one should set the channel to the \textbf{Free} value. See \sectionref{subsec:pattern_editor_first_row}, the section on \textbf{Channel Selection}. Also note that exporting a song or an SMF 0 file requires that the song be exportable (see the "Export Song" section). Exporting to a regular MIDI does not have this requirement. \subsubsection{Export Project Configuration} \label{subsubsec:midi_export_configuration_export} This option allows the various \texttt{qseq66.*} configuration files to be copied to another directory. This feature is useful for creating an alternate configuration or for backing up the current configuration. At the present time, any files specified in a play-list are \textsl{not} copied. \subsubsection{Export Song} \label{subsubsec:midi_export_song_export} Thanks to \textsl{Seq32}, exporting song performances (see the \textbf{Song Editor}) to standard MIDI format has been added. The \textbf{File / Export / Export Song} operation modifies the song in the following ways: \begin{itemize} \item Only tracks (sequences, loops, or patterns) \index{exportable} that are "exportable" are written. To be exportable, a track must have triggers (see \sectionref{subsubsec:concepts_terms_performance}) present in the \textbf{Song Editor}. Also \textsl{in the song editor}, it must \textsl{not be muted}. \item Each trigger generates the events, including repeats, offset-play of the events, and transposition. If there is a gap in the layout (e.g. due to an \textbf{Expand} operation in the \textbf{Song Editor}), then the corresponding gap in the events is exported. The result is a track that reconstructs the original playback/performance layout of that pattern. The events themselves are sufficient to play the performance exactly in any MIDI sequencer. The triggers are useful for further editing of the song/performance, so they are preserved in the triggers \textsl{SeqSpec} section, but they cover the whole song. The process of consolidating the trigger data is called "flattening". \item Empty pattern slots between tracks are removed. \item No matter what set the original track was in, it ends up in the first set; sets are consolidated. \item Other additions, such as time signature and tempo meta events, are written in the same manner as for a normal \textbf{File / Save} operation. \end{itemize} The \textbf{Export} dialog is similar to the \textbf{Open} dialog; one will likely want to change the name of the file so as not to overwrite it. If there are no exportable tracks, the following message is shown: \begin{figure}[H] \centering \includegraphics[scale=0.65]{main-menu/file/light-menu-file-song-unexportable.png} \caption{MIDI File Unexportable} \label{fig:midi_export_file_unexportable} \end{figure} Once the file is exported, reopen it to see the results of the export. The following figure shows a before and after picture of the export, as seen in the song editor. \begin{figure}[H] \centering \includegraphics[scale=0.75]{song-editor/song-layout-sample-2.png} \caption{MIDI File Layout Before/After Export} \label{fig:midi_export_file_before_after} \end{figure} The gaps in layouts in the song/performance data are reflected in the consolidated triggers. Here are the before/after triggers for pattern \#0, which was layed out with \textbf{Record Snap} \textsl{on}: \begin{verbatim} BEFORE: Sequence #0 'One Bar' AFTER: Sequence #0 'One Bar' Length (ticks): 768 Length (ticks): 5375 trigger: 768 to 1535 at 768 trigger: 0 to 5375 at 5375 trigger: 3840 to 5375 at 768 \end{verbatim} Note that 768, at PPQN = 192, is 4 beats (1 measure), while 5375 is 28 beats (7 measures). For each of these triggers, the first number is the start of the trigger in PPQNs, the second is the end of the triger, and the third, called the "offset", is actually the length of the pattern. Note how the "AFTER" trigger consolidates the "BEFORE" triggers, starts at time 0, extends to the end of the last trigger, and has a length equal to the end of the trigger. Now here is the before/after triggers for pattern \#5, which was layed out with \textbf{Record Snap} \textsl{off}: \begin{verbatim} BEFORE: Sequence #5 'Two Bars' AFTER: Sequence #5 'Two Bars' Length (ticks): 1536 Length (ticks): 6911 trigger: 2344 to 3879 at 1536 trigger: 0 to 6655 at 6911 trigger: 4944 to 6655 at 0 \end{verbatim} 6655 is a little over 34.5 beats, which is what the bottom grey trigger shows. 6911 is almost 36 beats (9 measures). Something to figure out. \subsubsection{Export MIDI Only} \label{subsubsec:midi_export_file_export_midi_only} Sometimes it might be useful to export only the non-sequencer-specific (non-SeqSpec) data from a \textsl{Seq66} song. For example, some buggy sequencers (hello \textsl{Windows Media Player}) might balk at some SeqSpec item in the song, and refuse to load the MIDI file. For such cases, the \textbf{File / Export / Export MIDI Only} menu item writes a file that does not contain the SeqSpec data for each track, and does not include all the SeqSpec data (such as mute groups) that is normally written to the end of the \textsl{Seq66} MIDI file. \subsubsection{Export SMF 0} \label{subsubsec:midi_export_file_export_smf_0} In some cases it might be useful to convert a \textsl{Seq66} MIDI file to a single-track SMF 0 file. As with exporting to a song (see \sectionref{subsubsec:midi_export_song_export}), the tracks to be exported (combined into a single track) must be unmuted. \textsl{However}, it is not worthwhile to export a MIDI tune that is in Song mode, unless all the patterns are the same length. To export in Song mode, first use Song export (see \sectionref{subsubsec:midi_export_song_export}), then export the modified tune to SMF 0. The \textbf{File / Export / Export SMF 0 } menu action removes all the existing tracks and merges them into track 0. The original tune can be opened again, if desired. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/midi_formats.tex ================================================ %------------------------------------------------------------------------------- % midi_formats %------------------------------------------------------------------------------- % % \file midi_formats.tex % \library Documents % \author Chris Ahlstrom % \date 2015-09-03 % \update 2025-10-26 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides a discussion of the MIDI message formats and how they are % handled, including the last, special track of an Seq24/Seq66 MIDI file. % %------------------------------------------------------------------------------- \section{MIDI Format and Other MIDI Notes} \label{sec:midi_format_and_midi_notes} \textsl{Seq66} tries to handle most MIDI files, and to provide song information in a format that does not break other MIDI-compliant sequencers. It can read SMF 0, 1, and \textsl{Cakewalk} WRK files. Another format, SMF 2, is a series of SMF 0 files for multiple songs. The MIDI data is stored in separate tracks, which are additionally wrapped in containers, so it's possible to have several tracks using the same MIDI channels. SMF 2 never caught on, and \textsl{Seq66} does not support it. MIDI SMF 0 files have all information on one track, which mixes together data on all channels of events included in the songe. MIDI SMF 1 files have channel data on separate tracks. \textsl{Seq66} can use this channel information, but its main mode is to ignore the channel information and replace it with the channel requested by the user. \textsl{Seq66} also embeds extra information into a song via the "Sequencer-Specific Meta-Event" mechanism described on page 139 of the following document: \url{http://www.shclemen.com/download/The Complete MIDI1.0 Detailed Spec.pdf} % This one requires an account login: % https://www.midi.org/specifications-old/item/the-midi-1-0-specification \subsection{Standard MIDI Format} \label{subsec:midi_format_smf} A MIDI file consists of a header chunk plus a number of track chunks. \subsubsection{MIDI File Header, MThd} \label{subsubsec:midi_format_header} The first thing in a MIDI file is The data of the header: \begin{verbatim} Header ID: "MThd" 0x00: 4 bytes MThd length: 6 0x04: 4 bytes Format: 0, 1, or 2 0x08: 2 bytes (format 2 is rare) No. of tracks: 1 or more 0x0a: 2 bytes (65535 maximum) PPQN: 192 0x0c: 2 bytes (bit 15 = 0) \end{verbatim} The header ID and its length are always the values above, "MThd" and 6. The formats that Seq66 supports are 0 or 1. SMF 0 has only one track, while SMF 1 can support an arbitary number of tracks. SMF 2 is rarely used, and \textsl{Seq66} does not support it. The next value is the number of tracks, 1 or more. The last value in the header is the PPQN value, which specifies the "pulses per quarter note", the basic time-resolution of events in the MIDI file. Common values are 96 or 192, but higher values are also common. (The highest possible value is 0x7FFF = 32767 but the MIDI functional limit is 31250, and anyway, \textsl{Seq66} limits it to 19200 for performance reasons. 960 PPQN is probably greater than the useful upper limit for PPQN.) \textsl{Seq66} and its precursor, \textsl{Seq24}, default to PPQN = 192. For \textsl{Seq66}, this can be changed in the 'rc' file. Any MIDI file can also be rescale to another PPQN, and saved. \subsubsection{MIDI Track, MTrk} \label{subsubsec:midi_format_track} The next part of the MIDI file consists of the tracks specified in the file. Each track is tagged by a standard chunk marker, "MTrk". Other markers are possible, and are to be ignored, if nothing else. Here are the values read at the beginning of a track: \begin{verbatim} Track ID: "MTrk" 4 bytes Track length: varies 4 bytes \end{verbatim} The track length is the number of bytes that need to be read in order to get all of the data in the track. In SMF 1 format, each track is assumed to cover a different MIDI channel, but the same MIDI buss. The MIDI buss \textsl{is} an important data item in the sequencer-specific section of \textsl{Seq66} MIDI files, however. \textbf{Delta time}. The amount time that passes from one event to the next is the \textsl{delta time}. For some events, the time doesn't matter, and is set to 0. This values is a \textsl{variable length value}, also known as a "VLV" or a "varinum". It provides a way of encoding arbitrarily large values, a byte at a time. \begin{verbatim} Delta time: varies 1 or more bytes \end{verbatim} The running-time accumulator is incremented by the delta-time. The current time is adjusted as per the PPQN ratio, if needed, and stored with each event that is read from the MIDI file. \subsubsection{Standard MIDI Format 0} \label{subsubsec:midi_format_smf_0_import} \index{SMF 0} \index{channel split} \textsl{Seq66} can read and import SMF 0 MIDI files, and splits the MIDI data into separate tracks by channel number. When SMF 0 format is detected, \textsl{Seq66} puts all of the events into the same sequence/pattern, pattern 16. As the file is processed, a list of the channels present in the track is maintained. Once the end-of-track is encountered in the SMF 0 file, a new empty slot is created for each channel found. The events in the main pattern are scanned and added to the the appropriate pattern. If the event is a channel event, then the event is inserted into the pattern that was created for that channel. If the event is a non-channel event, then each pattern gets a copy of that event. After processing, the MIDI buss information, track name, and other pieces of information are attached to each sequence. The imported SMF 0 track is preserved, in pattern slot \#16. One can delete this track before saving the file, or just keep it muted. The sequence number of each new track is the internal channel number (always the MIDI channel number, minus one). The time-signature of each track is set to the default, unless a time-signature event is encountered in the imported file. \index{tempo events} \index{time signature events} Tempo and Time Signature events are read, if present. When saving a \textsl{Seq66} MIDI file, the Tempo and Time Signature events are saved as MIDI events in the first track. This allows other sequencers to read a \textsl{Seq66} MIDI file. An example of an SMF 0 file, \texttt{CountryStrum.mid}, is included with the source code in the \texttt{contrib/midi} directory. \texttt{CountryStrum.midi} is the SMF 1 version of this file converted by \textsl{Seq66}. \subsection{Sequencer-Specific Meta-Events Format} \label{subsec:midi_format_meta_format} This data, also known as \texttt{SeqSpec} data, provides a way to encode information that a specific sequencer application needs, while marking it so that other sequences can safely ignore the information. The authors of \textsl{Seq24} took trouble to ensure that the format of the MIDI files it writes are compatible with other MIDI applications. \textsl{Seq24} also stores its "proprietary" (an unfortunate legacy term) information (triggers, mute-groups, MIDI control information, etc.) in the file, but marked as per the MIDI specification so that other sequencers can read the file and ignore its \textsl{Seq24}-specific information. \textsl{Seq66} continues that MIDI-compliant behavior, and improves it. Each sequence/pattern/loop can contain special information, such as the palette color assigned to that track. Application support: See \tableref{table:midi_file_support_table}. It is an (incomplete) survey of applications the do or do not handle \textsl{Seq66 SeqSpec} data properly, by ignoring it. \textsl{Windows MP} in this application table is the built-in \textsl{Windows Media Player}. \begin{table} \centering \caption{Application Support for Seq66 MIDI Files} \label{table:midi_file_support_table} \begin{tabular}{l l l} \textbf{Application} & \textbf{New} & \textbf{Original File} \\ ardour & TBD & TBD \\ composite & TBD & TBD \\ gsequencer & No & No \\ lmms & Yes & Yes \\ midi2ly & Yes & TBD \\ midicvt & Yes & Yes \\ midish & TBD & TBD \\ muse & TBD & TBD \\ playmidi & TBD & TBD \\ pmidi & TBD & TBD \\ qtractor & Yes & Yes \\ rosegarden & Yes & Yes \\ superlooper & TBD & TBD \\ timidity & Yes & Yes \\ Windows MP & No & TBD \\ \end{tabular} \end{table} \index{SeqSpec} In \textsl{Seq24} and \textsl{Seq66}, some these events are placed at the end of the song in the last pattern, and some are included in each pattern. Most MIDI applications handle this situation fine, but some (e.g. midicvt) do not. \textsl{Seq66} makes sure to wrap each data item in the 0xFF 0x7F wrapper. Note that the last track of a \textsl{Seq66} MIDI file contains only SeqSpec events, which are not counted as a pattern (track) and are not included in the track count. Other MIDI applications might count this track when saving a \textsl{Seq66} MIDI file, so \textsl{Seq66} must account for that. The \textsl{Seq66} SeqSpecs for Key, Scale, and Background Sequence) can also be stored with a particular sequence/track, as well as at the end of the song. Not sure if this bit of extra flexibility is useful, but it is there. \begin{verbatim} FF 7F len id data \end{verbatim} The first byte of the message is the "manufacturer ID", which \textsl{Seq24} set to "24". (This value corresponds to the manufactuer ID of \textsl{Hohner}!) Another "24" is added to make the number \texttt{0x2424} easy to search in a binary hex editor, such as \textsl{hexer} or \textsl{bvi}. Then a "00" is added. Finally, the last number, "nn" is added, and that specifies the type of data to be read. Here is the full encoded format: \begin{verbatim} delta FF 7F len 24 24 00 nn databyte(s) \end{verbatim} The "len" value includes the 4 bytes of the 0x242400nn SeqSpec marker and the number of data bytes. If the tag value "nn" is not recognized, a message is emitted and the \texttt{SeqSpec} is skipped. If the "24" value is something else, as it would be for another sequencer product, then the \texttt{SeqSpec} is skipped silently. All of the \texttt{SeqSpecs} are shown in the next table. It shows the name, length, and data for each one. A length of 0 means the \texttt{SeqSpec} is not implemented. The \texttt{c\_triggers} tag is obsolete, but still present, for backward compatibility. A named value (e.g. "buss") indicates a byte that specifies the value set by the \texttt{SeqSpec} . Please note that some of these values (for example, \texttt{c\_timesig} could be better implemented by standard MIDI meta-events. Legacy code! Some \texttt{SeqSpec} sections appear only if they are set to a non-default value. For example, patterns having no color would not likely have a \texttt{c\_seq\_color} \texttt{SeqSpec}. Flags are always one byte; the minimum "length" value is 5. Values with more than one byte are indicated by "S" (short, or two bytes), or "L" (long, or four bytes). An asterisk indicates a per-track setting, as opposed to a whole-song setting. A question-mark indicates that the tag is either deprecated or not yet implemented. For example, \texttt{c\_midictrl} is completely replaced by the 'ctrl' file, though \textsl{Seq66} will still read (and ignore) this \texttt{SeqSpec} if present. A \texttt{SeqSpec} named "gap" or "reserved" is not used. They are due to a minor error in \textsl{Seq24} or being used in \textsl{Seq32}. \index{SeqSpec} \index{SeqSpec!table} \begin{table}[htb] \centering \caption{All SeqSpec Items} \label{table:seqspec_items_all} \begin{tabular}{l l l l} \textbf{SeqSpec tag} & \textbf{Type} & \textbf{Length} & \textbf{Data} \\ \texttt{c\_midibus} & Track & 5 & \texttt{0x24240001 outbuss} \\ \texttt{c\_midichannel} & Track & 5 & \texttt{0x24240002 channel} \\ \texttt{c\_midiclocks} & Unused & 4+count & \texttt{0x24240003 count:L bussclocks} \\ \texttt{c\_triggers} & & 0 & \texttt{0x24240004 (see c\_triggers\_ex)} \\ \texttt{c\_notes} & & 2+variable & \texttt{0x24240005 setcount strings...} \\ \texttt{c\_timesig} & Track & 6 & \texttt{0x24240006 bpb bw} \\ \texttt{c\_bpmtag} & & 8 & \texttt{0x24240007 bpm:L } \\ \texttt{c\_triggers\_ex} & Track & 4+triggercount*12 & \texttt{0x24240008 triggers...} \\ \texttt{c\_mutegroups} & Global & 4+4*groups+4*seqs & \texttt{0x24240009 groups:S seqs:S data...} \\ \texttt{c\_gap\_A} & Unused & 4 & \texttt{0x2424000A} \\ \texttt{c\_gap\_B} & Unused & 4 & \texttt{0x2424000B} \\ \texttt{c\_gap\_C} & Unused & 4 & \texttt{0x2424000C} \\ \texttt{c\_gap\_D} & Unused & 4 & \texttt{0x2424000D} \\ \texttt{c\_gap\_E} & Unused & 4 & \texttt{0x2424000E} \\ \texttt{c\_gap\_F} & Unused & 4 & \texttt{0x2424000F} \\ \texttt{c\_midictrl} & Unused & 4+8*ctrls & \texttt{0x24240010 ctrls data...} \\ \texttt{c\_musickey} & Both & 5 & \texttt{0x24240011 key} \\ \texttt{c\_musicscale} & Both & 5 & \texttt{0x24240012 scale} \\ \texttt{c\_backsequence} & Both & 8 & \texttt{0x24240013 seqnumber:L} \\ \texttt{c\_transpose} & Track & 5 & \texttt{0x24240014 transpose} \\ \texttt{c\_perf\_bp\_mes} & Global & 8 & \texttt{0x24240015 bpb:L} \\ \texttt{c\_perf\_bw} & Global & 0 & \texttt{0x24240016 bw:L} \\ \texttt{c\_tempo\_map} & Seq32 & 0 & \texttt{0x24240017} \\ \texttt{c\_midiinbus} & Track & 5 & \texttt{0x24240018 inputbus} \\ \texttt{c\_musicchord} & Track & 5 & \texttt{0x24240019 chord} \\ \texttt{c\_tempo\_track} & Global & 8 & \texttt{0x2424001A track:L} \\ \texttt{c\_seq\_color} & Track & 5 & \texttt{0x2424001B color} \\ \texttt{c\_seq\_edit\_mode} & Kepler34 & 0 & \texttt{0x2424001C} \\ \texttt{c\_seq\_loopcount} & Track & 6 & \texttt{0x2424001D 00 00} \\ \texttt{c\_reserved\_3} & Unused & 0 & \texttt{0x2424001E} \\ \texttt{c\_reserved\_4} & Unused & 0 & \texttt{0x2424001F} \\ \texttt{c\_trig\_transpose} & Track & 4+triggercount*(12+1) & \texttt{0x24240020 triggers...} \\ \end{tabular} \end{table} Note that the base length is 4 bytes, the size of a \texttt{0x242400nn} value. Also note that some of the multi-byte (2 or 4 bytes) values that indicate counts are stored in big-endian (network order) format. The most-signifant byte is grabbed first, then left-shifted to get read for the next significant bit. See the functions \texttt{midifile::read\_short()} and \texttt{midifile::read\_long()}, and contrast them with \texttt{midifile::read\_varinum()}. \subsubsection{SeqSpec c\_midibus} \label{subsubsec:midi_format_track_seqspec_midibus} \index{SeqSpec!c\_midibus} \begin{description} \item \texttt{c\_midibus}: the desired output buss/port number for a track. \item \textsl{Length}: 5 \item \textsl{Format}: \texttt{0x24240001 buss} \end{description} The output buss value for a track can be set in the pattern editor or using the grid-slot popup menus in the main window. The buss value ranges from 0 to 47, though the practical range is under a dozen ports. Currently in \textsl{Seq66}, buss information is stored by number only. However, there is a port-mapping mechanism in the 'rc' file that lets one assign permanent port numbers by name, and translate those to actual port numbers based on partial matches of device names. See \sectionref{sec:port_mapping}. \subsubsection{SeqSpec c\_midiinbus} \label{subsubsec:midi_format_track_seqspec_midiinbus} \index{SeqSpec!c\_midiinbus} \begin{description} \item \texttt{c\_midiinbus}: the optional desired input buss/port number for a track. \item \textsl{Length}: 5 \item \textsl{Format}: \texttt{0x24240018 buss} \end{description} This value is similar to the output-buss specification in the previous section. It sets an input bus for the pattern. When recording, each pattern that specifies an input bus will receive MIDI events from that buss, so that multiple devices can be recorded at once. Only the first buss having that number receives the MIDI events. If there is no input buss specified, which is the normal case, then the input is considered "Free". To be seen in the grid-slot popup menu, \textbf{Record into patterns by bus} must be active. \subsubsection{SeqSpec c\_musicchord} \label{subsubsec:midi_format_track_seqspec_musicchord} \index{SeqSpec!c\_musicchord} \begin{description} \item \texttt{c\_musicchord}: the musical chord for a pattern/track. \item \textsl{Length}: 5 \item \textsl{Format}: \texttt{0x24240019 chord} \end{description} The value of \texttt{chord} ranges from 0 ("none") to 39 ("mmaj7add13"). This item specifies the musical chord for a pattern. The notes that are \textsl{not} in the musical chord are hash-marked; the hash-marking is affected by the \texttt{c\_musickey} setting. Filtering can be turned on so that painting notes with the mouse works only for notes in the chord. Recording from MIDI input is not affected. \subsubsection{SeqSpec c\_midichannel} \label{subsubsec:midi_format_track_seqspec_midichannel} \index{SeqSpec!c\_midichannel} \begin{description} \item \texttt{c\_midichannel}: the desired output channel number for a track. \item \textsl{Length}: 5 \item \textsl{Format}: \texttt{0x24240002 channel} \end{description} The channel value for a track can be set in the pattern editor. This channel value ranges from 0 to 15, and has an "null" value of 0x80. If the channel ranges from 0 to 15, that channel is applied to every channel event that goes out from that sequence. Otherwise, the channel of the MIDI event is used for output. Note that SMF 0 MIDI files have a single track and can have a mixture of different channels in its channel event. If \textsl{Seq66} detects SMF 0, it splits the channel events into different patterns. Also note that "channel" 0x80 is a flag to use the channel stored in each channel event. This value represents the "Free" setting in the pattern editor. \subsubsection{SeqSpec c\_midiclocks} \label{subsubsec:midi_format_track_seqspec_midiclocks} \index{SeqSpec!c\_midiclocks} \begin{description} \item \texttt{c\_midiclocks}: the clocking for the output busses, but is inactive. \item \textsl{Length}: 8 \item \textsl{Format}: \texttt{0x24240003 count:L bussclocks} \end{description} This item is a hold-over from \texttt{Seq24}. It was meant, presumably, to hold the clocking statuses of the output busses. However, it seems to have fallen by the wayside, and the only item read/written is 4 bytes of zeroes. Clocking is specified in the 'rc' file, and can be edited in the preferences dialog. See \sectionref{subsec:configuration_rc_midi_clock}. \subsubsection{SeqSpec c\_triggers} \label{subsubsec:midi_format_track_seqspec_triggers} \index{SeqSpec!c\_triggers} \begin{description} \item \texttt{c\_triggers}: the old format for song-performance triggers. \item \textsl{Length}: Indeterminate \item \textsl{Format}: \texttt{0x24240004 buss} \end{description} This \texttt{SeqSpec} is no longer used, but is still read and converted to a valid trigger, if encountered. We have not encountered a file containing this value yet. Instead, \texttt{c\_triggers\_ex} is used. \subsubsection{SeqSpec c\_notes} \label{subsubsec:midi_format_track_seqspec_notes} \index{SeqSpec!c\_notes} \begin{description} \item \texttt{c\_notes}: the names of the sets in the tune. \item \textsl{Length}: 2 + variable \item \textsl{Format}: \texttt{0x24240005 setcount [ length string ] [ ... ] } \end{description} Each set in a tune can have a name. The first value is a 2-byte value indicating the number of sets in the tune. This value can range from 0 to 31, for a total of 32 sets. \textsl{Seq66} limits the number of sets to 32 for practical reasons. See \sectionref{sec:setmaster}. After the set-count comes the list of set-name segments. Each segment starts with a 2-byte value indicating the size of the string, followed by that number of bytes for the text of the string. Presumably, the text must be in either ASCII encoding or UTF-8 encoding. \subsubsection{SeqSpec c\_timesig} \label{subsubsec:midi_format_track_seqspec_timesig} \index{SeqSpec!c\_timesig} \begin{description} \item \texttt{c\_timesig}: the time signature for a track. \item \textsl{Length}: 6 \item \textsl{Format}: \texttt{0x24240006 bpb bw} \end{description} This \texttt{SeqSpec} specifies the underlyng time-signature for the track in a 2-byte format, the beats-per-bar followed by the beat-width. This time-signature is meant for setting up the pattern editor to the user's preferences, and is not the time-signature for the song. It is \textsl{not} set globally in the \texttt{seq66::performer} class; that can be done in the main window. When a time-signature is added (see \sectionref{subsec:pattern_editor_first_row}, how it is stored depends if it is a MIDI-standard power-of-2 beat-width or not. If a power of 2, a time-signature event is added. Otherwise, the beat-width is set for the pattern, and is saved as the \texttt{c\_timesig} event. The normal time-signature meta-event is first obtained from values stored in the performer class, and has the format \texttt{0xFF 58 4 bpb bw cpm tpq}, where cpm is the clocks-per-metronome and tpq is the 32nds-per-quarter. \subsubsection{SeqSpec c\_bpmtag} \label{subsubsec:midi_format_track_seqspec_bpmtag} \index{SeqSpec!c\_bpmtag} \begin{description} \item \texttt{c\_bpmtag}: the BPM in a format allowing double precision. \item \textsl{Length}: 8 \item \textsl{Format}: \texttt{0x24240007 bpm:L} \end{description} Due to requests for higher precision in the beats-per-minute of a song, this value is the value of the BPM multiplied by 1000. When read, it is divided by 1000 to get the desired floating-point precision. The normal tempo meta-event format is \texttt{0xFF 51 03 tttttt}, where "tttttt" is 3 bytes representing the number of microseconds per quarter note. The function \texttt{tempo\_us\_from\_bytes()} calculates the microseconds from these three bytes; the "inverse" function is \texttt{tempo\_us\_to\_bytes()}. Generally, the MIDI tempo comes first in the file, and the \texttt{SeqSpec} tempo comes later. The last value obtained is the BPM that the performer module contains. The conversion between the \texttt{SeqSpec} format and the MIDI Tempo format is effected by the functions \texttt{bpm\_from\_tempo\_us()} and \texttt{tempo\_us\_from\_bpm()}. \subsubsection{SeqSpec c\_triggers\_ex} \label{subsubsec:midi_format_track_seqspec_triggers_ex} \index{SeqSpec!c\_triggers\_ex} \begin{description} \item \texttt{c\_triggers\_ex}: the triggers for a given track. \item \textsl{Length}: 4 + triggercount * 12 \item \textsl{Format}: \texttt{0x24240008 [trigger-on off offset ] [ ... ]} \end{description} The triggers in each pattern in a song determines the layout of the song in the \textbf{Song Editor}. The extent of each trigger is partly determined by the PPQN of the song and whether or not PPQN rescaling is needed (when PPQN != 192). The number of triggers is determined by dividing the \texttt{SeqSpec} \texttt{len} value by the size of a trigger, 12 bytes. The format of each trigger is three 4-byte (long) values: \texttt{on:L off:L offset:L}. Each trigger is represented by an \texttt{seq66::trigger} object. Each trigger has a start and an end tick value based on the recorded pattern loop, and an offset value that indicates how much the trigger is delayed as laid out in the song editor. Recent versions of \textsl{Seq66} use \texttt{c\_trig\_transpose} by default. \subsubsection{SeqSpec c\_trig\_transpose} \label{subsubsec:midi_format_track_seqspec_trig_transpose} \index{SeqSpec!c\_trig\_transpose} \begin{description} \item \texttt{c\_trig\_transpose}: the triggers for a given track, plus a transposition value. \item \textsl{Length}: 4 + triggercount * (12 + 1) \item \textsl{Format}: \texttt{0x24240020 [trigger-on off offset transpose] [ ... ]} \end{description} This is an extension to \texttt{c\_triggers\_ex} that adds a value to use to transpose the pattern at this particular trigger. Very useful for simple repetitive patterns like that in \textsl{Kraftwerk's "Europe Endless"}. If the trigger has a zero tranpose value, then \texttt{c\_trig\_transpose} is still written, but the extra byte is zero. In order to preserve some of the ability of older sequencers to read this section, if all of the triggers are non-transposed, then the old-style triggers (\texttt{c\_triggers\_ex}) are written for that pattern. The older trigger tags will be read if present in the \textsl{Seq66} MIDI file. \subsubsection{SeqSpec c\_mutegroups} \label{subsubsec:midi_format_track_seqspec_mutegroups} \index{SeqSpec!c\_mutegroups} Legacy format (for \textsl{Seq24} and early versions of \textsl{Seq66}: \begin{description} \item \texttt{c\_mutegroups}: the mute-groups in play in a song. The group and sequence counts are encoding in a "split-long" format (denoted by "S") compatible with the legacy (\textsl{Seq24}) format. \item \textsl{Length}: 4 + 4 * groupcount + 4 * seqcount \item \textsl{Format}: \texttt{0x24240009 groupcount:S seqcount:S groupdata...} \end{description} For newer versions of \textsl{Seq66}, a space-saving format (over \textsl{Seq24}) is used, which uses 1 byte, instead of 4, to encode each mute status. \begin{description} \item \texttt{c\_mutegroups}: As above, except that it includes an optional name for each group; the total size of these names is the length of each name plus the surrounding quote characters (namecount). The group and sequence counts are encoding in a "split-long" format (denoted by "S") compatible with the legacy (\textsl{Seq24}) format. \item \textsl{Length}: 4 + 1 * groupcount + 1 * seqcount + namecount \item \textsl{Format}: \texttt{0x24240009 groupcount:S seqcount:S groupdata...} \end{description} In the new format, we combine the group-count and pattern-count into one long value. This value is split, when read, into these counts. By default, the groupdata is formatted as follows, where all data items are encoded as bytes: \begin{verbatim} <32 values of 0 or 1 for mute statuses> Optional: <"name..."> \end{verbatim} Mute-groups enable the toggling of arming/muting for an arbitrary set of multiple patterns at once. \textsl{Seq66} supports up to 32 mute-groups of size 32 patterns. More flexibility is planned. Mute-groups can also be stored in a 'mutes' files, which is probably a better place to store them if one uses a consistent setup for all one's tunes. (See \sectionref{subsubsec:configuration_mute_group_control}, and \sectionref{fig:mutes_master_tab}). Here, we describe how they are encoded in the song. The first two values provide the group-count and the sequence-count. Then, the groups are looped through. Each group has the format \texttt{groupnumber bits} where "bits" is a string of up to thirty-two 4-byte (!) values indicating if the corresponding pattern is part of the group. This setup is a real space waster, and in newer versions of \textsl{Seq66}, the MIDI file encodes mute-groups using byte-size values. \subsubsection{SeqSpec c\_gap\_(ABCDEF)} \label{subsubsec:midi_format_track_seqspec_gap_abcdef} \index{SeqSpec!c\_gap\_(ABCDEF) (unused)} This set of six \texttt{SeqSpec} values is a gap created by skipping from \texttt{0x24240009} to \texttt{0x24240010} as if the numbers were decimal, a long-standing oversight from \textsl{Seq24}. We guarantee that \textsl{Seq66} will never use these values. \subsubsection{SeqSpec c\_midictrl} \label{subsubsec:midi_format_track_seqspec_midictrl} \index{SeqSpec!c\_midictrl (obsolete)} \begin{description} \item \texttt{c\_midictrl}: the MIDI controls to be used. \item \textsl{Length}: 4 + 8 * ctrls \item \textsl{Format}: \texttt{0x24240010 ctrls data...} \end{description} This section apparently provided a way to save MIDI controls for loops (toggle, on, and off) inside the song. However, it makes more sense to save them in the 'ctrl' file. This \texttt{SeqSpec} is parsed if present, but the data is thrown away, and this \texttt{SeqSpec} is never written. Oddly enough, \textsl{Seq24} would read this section, but would write only four bytes of zeroes. \subsubsection{SeqSpec c\_musickey} \label{subsubsec:midi_format_track_seqspec_musickey} \index{SeqSpec!c\_musickey} \begin{description} \item \texttt{c\_musickey}: the musical key for the song. \item \textsl{Length}: 5 \item \textsl{Format}: \texttt{0x24240011 key} \end{description} The value of \texttt{key} ranges from 0 ("C") to 11 ("B"). This item specifies the musical key for a song (globally), but it can also be specified inside each pattern, as well, so that patterns can have different keys. When provided globally, this option is stored in the \texttt{seq66::usrsettings} class. \subsubsection{SeqSpec c\_musicscale} \label{subsubsec:midi_format_track_seqspec_musicscale} \index{SeqSpec!c\_musicscale} \begin{description} \item \texttt{c\_musicscale}: the musical scale for the song. \item \textsl{Length}: 5 \item \textsl{Format}: \texttt{0x24240012 scale} \end{description} The value of \texttt{scale} ranges from 0 ("Off") to 8 ("Minor Pentatonic""). This item specifies the musical scale for a song (globally), but it can also be specified inside each pattern, as well, so that patterns can have different scales. When provided globally, this option is stored for the duration of the session in the \texttt{seq66::usrsettings} class. The notes that are \textsl{not} in the musical scale are hash-marked; the hash-marking is affected by the \texttt{c\_musickey} setting. Filtering can be turned on so that painting notes with the mouse works only for notes in the scale. Recording from MIDI input is not affected. \subsubsection{SeqSpec c\_backsequence} \label{subsubsec:midi_format_track_seqspec_backsequence} \index{SeqSpec!c\_backsequence} \begin{description} \item \texttt{c\_backsequence}: the background sequence to be shown in the pattern editor. \item \textsl{Length}: 8 \item \textsl{Format}: \texttt{0x24240013 backsequence:L} \end{description} This item specifies the background sequence to display in the pattern editor for a song (globally), but it can also be specified inside each pattern, as well, so that patterns can show different background sequences. When provided globally, this option is stored for the duration of the session in the \texttt{seq66::usrsettings} class. \subsubsection{SeqSpec c\_transpose} \label{subsubsec:midi_format_track_seqspec_transpose} \index{SeqSpec!c\_transpose} \begin{description} \item \texttt{c\_transpose}: specifies if a pattern can be transposed. \item \textsl{Length}: 5 \item \textsl{Format}: \texttt{0x24240014 transposable} \end{description} This \texttt{SeqSpec} applies to patterns only. Unlike \texttt{c\_trig\_transpose}, it applies not to the triggers, but to the whole pattern, and is merely a boolean value. It is set to a non-zero value (1) to indicate that a pattern can be transposed, either on the fly or via the note-mapping features (see \sectionref{subsec:configuration_drums}). A value of 0 is useful to mark a drum pattern and prevent it from being transposed. A value of 0 also prevents the drum pattern from being note-mapped. See \sectionref{subsubsec:configuration_rc_note_mapper}. The user must temporarily enable transposition in the pattern editor, and then press the \textbf{Map}. This should be done only once, otherwise the drum pattern will sound like a random set of percussive instruments. This section is always saved with the pattern. Should have called it \texttt{c\_transposable}. Doh! \subsubsection{SeqSpec c\_perf\_bp\_mes} \label{subsubsec:midi_format_track_seqspec_perf_bp_mes} \index{SeqSpec!c\_perf\_bp\_mes} \begin{description} \item \texttt{c\_perf\_bp\_mes}: the beats-per-bar for the performance. \item \textsl{Length}: 5 \item \textsl{Format}: \texttt{0x24240015 bpb:L} \end{description} Provides an override for the beats-per-bar from the Time Signature in track 0. Note that the beats-per-bar is currently settable from this value, a true MIDI time-signature event, and \texttt{c\_timesig}! This issue needs to be cleaned up. \subsubsection{SeqSpec c\_perf\_bw} \label{subsubsec:midi_format_track_seqspec_perf_bw} \index{SeqSpec!c\_perf\_bw} \begin{description} \item \texttt{c\_perf\_bw}: the beat-width for the performance. \item \textsl{Length}: 5 \item \textsl{Format}: \texttt{0x24240016 bpb:L} \end{description} Provides an override for the beat-width from the Time Signature in track 0. Note that the beats-per-bar is currently settable from this value, a true MIDI time-signature event, and \texttt{c\_timesig}! This issue needs to be cleaned up. \subsubsection{SeqSpec c\_tempo\_map} \label{subsubsec:midi_format_track_seqspec_tempo_map} \index{SeqSpec!c\_tempo\_map} \begin{description} \item \texttt{c\_tempo\_map}: Not implemented; from \textsl{Seq32} by \textsl{stazed}. \item \textsl{Length}: 5 \item \textsl{Format}: \texttt{0x24240017} \end{description} This section is an unimplemented \textsl{Seq32} feature. Tempo events can be added to pattern 0. \subsubsection{SeqSpec c\_reserved\_(12)} \label{subsubsec:midi_format_track_seqspec_reserved_1_2} \index{SeqSpec!c\_reserved\_(2)} \begin{description} \item \texttt{c\_reserved\_2}: Reserved for expansion. \item \textsl{Length}: Indeterminate \item \textsl{Format}: \texttt{0x24240019} \end{description} \subsubsection{SeqSpec c\_tempo\_track} \label{subsubsec:midi_format_track_seqspec_tempo_track} \index{SeqSpec!c\_tempo\_track} \begin{description} \item \texttt{c\_tempo\_track}: the alternate tempo track for a song. \item \textsl{Length}: 8 \item \textsl{Format}: \texttt{0x2424001A track:L} \end{description} Normally, the song tempo should be stored in the first track. This value can be set to move it to another pattern. \subsubsection{SeqSpec c\_seq\_color} \label{subsubsec:midi_format_track_seqspec_seq_color} \index{SeqSpec!c\_seq\_color} \begin{description} \item \texttt{c\_seq\_color}: the color for a pattern's progress box. \item \textsl{Length}: 5 \item \textsl{Format}: \texttt{0x2424001B colorcode} \end{description} This value specifies an index into the \textsl{Seq66} color palette. This feature is useful for distinguishing patterns more quickly in the pattern and song editors. Up to 32 colors (0 to 31) can be specified. See \sectionref{sec:palettes}. This section is saved only if a color has been specified. The lack of a color is given by a sequence color value of \texttt{c\_seq\_color\_none} (-1). \subsubsection{SeqSpec c\_seq\_edit\_mode} \label{subsubsec:midi_format_track_seqspec_seq_edit_mode} \index{SeqSpec!c\_seq\_edit\_mode (Kepler34)} \begin{description} \item \texttt{c\_seq\_edit\_mode}: the editing mode (normal vs. drum) of a pattern. \item \textsl{Length}: Indeterminate \item \textsl{Format}: \texttt{0x2424001C} \end{description} This would specify that a pattern should be edited in drum mote (1) or normal mode (0), a \textsl{Kepler34} feature. However, in \textsl{Seq66} it is not implemented, nor read nor written. It is also better determined by the transposable status of a pattern, which the user can change with a button click. \subsubsection{SeqSpec c\_seq\_loopcount} \label{subsubsec:midi_format_track_seqspec_seq_loopcount} \index{SeqSpec!c\_seq\_loopcount} \begin{description} \item \texttt{c\_seq\_loopcount}: the number of times a pattern should play. \item \textsl{Length}: 6 \item \textsl{Format}: \texttt{0x2424001D count:S} \end{description} This item is a feature to provide a user's request for a one-shot pattern, a pattern that plays once (or a few times) and never plays again. A count value of 0 would yield the normal behavior, playing endlessly during \textbf{Live} mode. Any other value would repeat the pattern the specified number of times. % TODO: INVESTIGATE % A loop-count is used in the one-shot step-edit feature used in % recording a device's drum pattern. See \sectionref{subsec:pattern_editor_bottom}. Note that setting a non-zero loop-count for a pattern is \textsl{not compatible} with playing the tune in \textbf{Song} mode. \subsection{MIDI Information} \label{subsec:midi_information} This section provides some useful, basic information about MIDI data. It can be helpful in troubleshooting. We tend to use the \texttt{hexer} or \texttt{bvi} tools for examining or fixing troublesome MIDI files byte-by-byte. \subsubsection{MIDI Variable-Length Value} \label{subsubsec:midi_variable_length_value} \index{midi!VLV} \index{variable-length value} A \textsl{variable-length value} (VLV) is a quantity that uses additional bytes and continuation bits to encode large numbers. See \url{https://en.wikipedia.org/wiki/Variable-length\_quantity}. It is essentially a base-128 quantity. The length of a VLV depends on the value it represents. Here is a list of the numbers that can be represented by a VLV: \begin{verbatim} 1 byte: 0x00 to 0x7F 2 bytes: 0x80 to 0x3FFF 3 bytes: 0x4000 to 0x001FFFFF 4 bytes: 0x200000 to 0x0FFFFFFF \end{verbatim} See the functions \texttt{varinum\_size()}, \texttt{write\_varinum()}, and \texttt{read\_varinum()}. \subsubsection{MIDI Track Chunk} \label{subsubsec:midi_track_chunk} Track chunk: \texttt{MTrk + length + track\_event [+ track\_event ...]} \begin{itemize} \item \texttt{MTrk} is 4 bytes representing the literal string "MTrk". This marks the beginning of a track. \item \texttt{length} is 4 bytes the number of bytes in the track chunk following this number. That is, the marker and length are not counted in the length value. \item \texttt{track\_event} denotes a sequenced track event; usually there are many track events in a track. However, some of the events may simply be informational, and not modify the audio output, especially in the first track of an SMF 1 file. \end{itemize} A track event consists of a delta-time since the last event, and one of three types of events. \texttt{track\_event = v\_time + midi\_event | meta\_event | sysex\_event} \begin{itemize} \item \texttt{v\_time} is the VLV for the elapsed time (delta time) from the previous event to this event. \item \texttt{midi\_event} is any MIDI channel message such as Note-On or Note-Off. \item \texttt{meta\_event} is an SMF meta event. \item \texttt{sysex\_event} is an SMF system exclusive event. \end{itemize} \subsubsection{MIDI System Events} \label{subsubsec:midi_system_events} For now, we simply summarize the System events. \textsl{Seq66} can read, insert, and write them. More to come. \begin{enumber} \item \texttt{F0 ...}: System Exclusive. \item \texttt{F1 ...}: Quarter Frame. \item \texttt{F2 ...}: Song Position. \item \texttt{F3 ...}: Song Select. \item \texttt{F4 ...}: Undefined. \item \texttt{F5 ...}: Undefined. \item \texttt{F6 ...}: Tune Select. \item \texttt{F7 ...}: SysEx Continue. \item \texttt{F8 ...}: Clock Event. \item \texttt{F9 ...}: Undefined. \item \texttt{FA ...}: Start. \item \texttt{FB ...}: Continue. \item \texttt{FC ...}: Stop. \item \texttt{FD ...}: Undefined. \item \texttt{FE ...}: Active Sensing Message. \item \texttt{FF ...}: Meta Message. \end{enumber} See \sectionref{subsec:event_editor_fields}; it describes some of the handling of system events. \subsubsection{MIDI Meta Events} \label{subsubsec:midi_meta_events} Meta events are non-MIDI data of various sorts consisting of a fixed prefix, an event type, a length field, and the event data. \textsl{Seq66} tries to load, store, and write most meta events. Meta events are never sent to a device. \texttt{meta\_event = 0xFF + meta\_type + v\_length + event\_data\_bytes} \begin{itemize} \item \texttt{meta\_type} is 1 byte, expressing one of the meta event types shown in the table that follows this list. \item \texttt{v\_length} is length of meta event data, a variable length value. \item \texttt{event\_data\_bytes} is the actual event data. \end{itemize} \begin{table} \centering \caption{MIDI Meta Event Types} \label{table:midi_meta_event_types} \begin{tabular}{l l l} \textbf{Type} & \textbf{Event} & \textbf{Seq66 Handling}\\ 0x00 & Sequence number & R/W \\ 0x01 & Text event & R/W \\ 0x02 & Copyright notice & R/W \\ 0x03 & Sequence or track name & R/W \\ 0x04 & Instrument name & R/W \\ 0x05 & Lyric text & R/W \\ 0x06 & Marker text & R/W \\ 0x07 & Cue point & R/W \\ 0x08-0x0F & Other text events & R/W \\ 0x20 & MIDI channel (deprecated) & R only \\ 0x21 & MIDI port (deprecated) & R only \\ 0x2F & End of track & R/W \\ 0x51 & Tempo setting & R/W and SeqSpec \\ 0x54 & SMPTE offset & R only \\ 0x58 & Time Signature & R/W + c\_timesig/c\_perf\_bp\_mes/c\_perf\_bw \\ 0x59 & Key Signature & R/W \\ 0x7F & Sequencer-Specific event & Seq66 data handled \\ \end{tabular} \end{table} Unfortunately, currently the processing of meta events is split between the \texttt{seq66::midifile} and \texttt{seq66::midi\_vector\_base}. "R/W" indicates both "Read" and "Written". Here, we summarize the MIDI meta events data. \begin{enumber} \item \texttt{FF 00 02 ssss}: Sequence Number. \item \texttt{FF 01 len text}: Text Event. \item \texttt{FF 02 len text}: Copyright Notice. \item \texttt{FF 03 len text}: Sequence/Track Name. \item \texttt{FF 04 len text}: Instrument Name. \item \texttt{FF 05 len text}: Lyric. \item \texttt{FF 06 len text}: Marker. \item \texttt{FF 07 len text}: Cue Point. \item \texttt{FF 08 through 0F len text}: Other kinds of text events. \item \texttt{FF 2F 00}: End of Track. \item \texttt{FF 51 03 tttttt}: Set Tempo, us/qn. \item \texttt{FF 54 05 hr mn se fr ff}: SMPTE Offset. \item \texttt{FF 58 04 nn dd cc bb}: Time Signature. \item \texttt{FF 59 02 sf mi}: Key Signature. \item \texttt{FF 7F len data}: Sequencer-Specific. \item \texttt{FF F0 len data F7}: System-Exclusive \end{enumber} We need to make sure we read, save, and restore the items above that are marked as "Skipped" or just "Read", even if \textsl{Seq66} doesn't use them. Some are deprecated in the MIDI standard, and \textsl{Seq66} encodes them in a \texttt{SeqSpec}. The next sections describe the events that \textsl{Sequencer} tries to handle. These are: \begin{itemize} \item Sequence Number (0x00) \item Track Name (0x03) \item End-of-Track (0x2F) \item Set Tempo (0x51) (Seq66 only) \item Time Signature (0x58) (Seq66 only) \item Sequencer-Specific (0x7F) (Handled differently in Seq66) \item System Exclusive (0xF0) Sort of handled, functionality incomplete. \end{itemize} Also, all the text events should be handled by \textsl{Seq66}, but a lot more testing is needed. \subsubsection{Sequence Number (0x00)} \label{subsubsec:midi_format_meta_sequence_number} \begin{verbatim} FF 00 02 ss ss \end{verbatim} This optional event must occur at the beginning of a track, before any non-zero delta-times, and before any transmittable MIDI events. It specifies the number of a sequence. \subsubsection{Track/Sequence Name (0x03)} \label{subsubsec:midi_format_meta_sequence_name} \begin{verbatim} FF 03 len text \end{verbatim} If in a format 0 track, or the first track in a format 1 file, the name of the sequence. Otherwise, the name of the track. \subsubsection{End of Track (0x2F)} \label{subsubsec:midi_format_meta_end_of_track} \begin{verbatim} FF 2F 00 \end{verbatim} This event is not optional. It is included so that an exact ending point may be specified for the track, so that it has an exact length, which is necessary for tracks which are looped or concatenated. \subsubsection{Set Tempo Event (0x51)} \label{subsubsec:midi_format_meta_set_tempo} The MIDI Set Tempo meta event sets the tempo of a MIDI sequence in terms of the microseconds per quarter note. This is a meta message, so this event is never sent over MIDI ports to a MIDI device. After the delta time, this event consists of six bytes of data: \begin{verbatim} FF 51 03 tt tt tt Example: FF 51 03 07 A1 20 \end{verbatim} \begin{enumber} \item 0xFF is the status byte that indicates this is a Meta event. \item 0x51 the meta event type that signifies this is a Set Tempo event. \item 0x03 is the length of the event, always 3 bytes. \item The remaining three bytes carry the number of microseconds per quarter note. For example, the three bytes above form the hexadecimal value 0x07A120 (500000 decimal), which means that there are 500,000 microseconds per quarter note. \end{enumber} Since there are 60,000,000 microseconds per minute, the event above translates to: set the tempo to 60,000,000 / 500,000 = 120 quarter notes per minute (120 beats per minute). This event normally appears in the first track. If not, the default tempo is 120 beats per minute. This event is important if the MIDI time division is specified in "pulses per quarter note", which does not itself define the length of the quarter note. The length of the quarter note is then determined by the Set Tempo meta event. Representing tempos as time per beat instead of beat per time allows absolutely exact DWORD-term synchronization with a time-based sync protocol such as SMPTE time code or MIDI time code. This amount of accuracy in the tempo resolution allows a four-minute piece at 120 beats per minute to be accurate within 500 usec at the end of the piece. \subsubsection{Time Signature Event (0x58)} \label{subsubsec:midi_format_meta_time_sig} After the delta time, this event consists of seven bytes of data: \begin{verbatim} FF 58 04 nn dd cc bb \end{verbatim} The time signature is expressed as four numbers. \texttt{nn} and \texttt{dd} represent the numerator and denominator of the time signature as it would be notated. The denominator is a negative power of two: 2 represents a quarter-note, 3 represents an eighth-note, etc. The \texttt{cc} parameter expresses the number of MIDI clocks in a metronome click. The \texttt{bb} parameter expresses the number of notated 32nd-notes in a MIDI quarter- note (24 MIDI Clocks). Example: \begin{verbatim} FF 58 04 04 02 18 08 \end{verbatim} \begin{enumber} \item 0xFF is the status byte that indicates this is a Meta event. \item 0x58 the meta event type that signifies this is a Time Signature event. \item 0x04 is the length of the event, always 4 bytes. \item 0x04 is the numerator of the time signature, and ranges from 0x00 to 0xFF. \item 0x02 is the log base 2 of the denominator, and is the power to which 2 must be raised to get the denominator. Here, the denominator is 2 to 0x02, or 4, so the time signature is 4/4. \item 0x18 is the metronome pulse in terms of the number of MIDI clock ticks per click. Assuming 24 MIDI clocks per quarter note, the value here (0x18 = 24) indidicates that the metronome will tick every 24/24 quarter note. If the value of the sixth byte were 0x30 = 48, the metronome clicks every two quarter notes, i.e. every half-note. \item 0x08 defines the number of 32nd notes per beat. This byte is usually 8 as there is usually one quarter note per beat, and one quarter note contains eight 32nd notes. \end{enumber} If a time signature event is not present in a MIDI sequence, a 4/4 signature is assumed. In \textsl{Seq66}, the \texttt{c\_timesig} \texttt{SeqSpec} event is given priority. The conventional time signature is used only if the \texttt{c\_timesig} \texttt{SeqSpec} is not present in the file. \subsubsection{SysEx Event (0xF0)} \label{subsubsec:midi_format_meta_sysex_event} If the meta event status value is 0xF0, it is called a "System-exclusive", or "SysEx" event. \textsl{Seq66} does store these messages, but there are complications to be dealt with. SysEx messages as sent and received are of the format: \begin{verbatim} F0 ID sysex data bytes F7 \end{verbatim} The ID is a manufacturer's ID. Normally a single byte, additional IDs can be represented by the sequence \texttt{00 xx yy}, to allow 16384 additional manufacturer IDs. Currently \textsl{Seq66} does not try to interpret the manufacturer ID. There are three additional special ID codes that are \textsl{not} manufacturer-specific: \begin{itemize} \item \textbf{0x7D}. Used for private non-commercial purposes only. It has no standard format. \item \textbf{0x7E}. Represents a non-realtime system exclusive message. All MIDI devices can respond to it, although not immediately. \item \textbf{0x7F}. Represents a non-realtime system exclusive message. All MIDI devices can respond to it, immediately. \end{itemize} For more information on these events, see \sectionref{subsubsec:midi_format_meta_universalsysex_event}. Now, when encoded for storage in a MIDI file, the format of the bare message (first line) is padded so that it starts with a delta time, and includes the length (a variable-length quantity) of the SysEx including the final F7, but excluding the F0 and the length bytes.: \begin{verbatim} F0 ID sysex data bytes F7 (message) delta F0 len ID sysex data bytes F7 (encoded) \end{verbatim} SysEx events can be encoded in the following ways: \begin{itemize} \item \textbf{Single SysEx Message}. \item \textbf{Continuation Events}. \item \textbf{Escape Sequence}. \end{itemize} \paragraph{Single SysEx Message} \label{paragraph:patterns_single_sysex_message} A SysEx message that is \textsl{sent to a device} is not quite the same as a SysEx message that is \textsl{encoded in a MIDI file}. SysEx messages encoded in a MIDI file are preceded by a variable-length delta time, the byte F0, a variable-length length value, and the message, and a terminating F7, which \textsl{must} be present, and is counted in the length value. In the following example, the first line is the actual SysEx message (a General MIDI Enable message), and the second line is its encoding in a MIDI file, ignoring the preceding delta time: \begin{verbatim} F0 7E 00 09 01 F7 (message) F0 05 7E 00 09 01 F7 (encoded) \end{verbatim} \paragraph{Continuation Events} \label{paragraph:patterns_continuation_events} Some equipment needs the SysEx to be split into smaller chunks for processing. This could be accomplished with a number of smaller single-SysEx messages, but some manufacturers treat SysEx as if it supported running status (it does not). So the first message starts with F0, the next messages have only the SysEx data, and the last message ends with F7. However, when encoding in the MIDI file, each sub-packet begins with an F7. In between are the delta times to use to delay the sub-packets when sending to a slow device. An unencoded message with 3 packets is shown, with a bar for a separator for this discussion: \begin{verbatim} F0 43 12 00 | 43 12 00 43 12 00 | 43 12 00 F7 \end{verbatim} Encoded with a 200-tick delta time (\texttt{81 48}) between each message, the F7 being used as a \textsl{continuation} byte, with length values: \begin{verbatim} 00 F0 03 43 12 00 81 48 F7 06 43 12 00 43 12 00 81 48 F7 04 43 12 00 F7 \end{verbatim} Since the first message is not terminated by F7 within the specified length, the next two F7s indicate a continuation. Note the F7 at the beginning of packets after the first one, and the F7 at the end of the last one. \paragraph{Escape Sequence} \label{paragraph:patterns_escape_sequence} An escape sequence is not SysEx, but it does use the F7 byte. It is used for encoding arbitrary bytes for messages such as Song Select. The first line shows such a message, and the second how it would be encoded. \begin{verbatim} F3 01 F7 02 F3 01 \end{verbatim} The format is \texttt{F7 len }. So how to figure out what F7 means? Its interpretation is as follows: \begin{itemize} \item An F0 without a terminal F7 within the specified length is a Casio-style multi-packet message, and a flag (call it \texttt{multi}) should be raised to indicate this status. \item If F7 is encountered while \texttt{multi} is set, it is a continuation. \item If this continuation ends with F7, it is the last packet and \texttt{multi} should be cleared. \item If F7 is encountered while \texttt{multi} is \textsl{not} set, then the event is an escape sequence. \end{itemize} \subsubsection{Universal SysEx Events (0xF0 0x7E and 0xF0 0x7F)} \label{subsubsec:midi_format_meta_universalsysex_event} As noted earlier, 0x7E and 0x7F provide for non-realtime and realtime SysEx message. Immediately following these value is the "channel", which can be a manufacturer's ID or any value ranging from 0x00 to 0x7F (which is a wild-card for "all devices"). \begin{verbatim} F7 7E channel subid1 subid2 data bytes F7 (non-realtime) F7 7F channel subid1 subid2 data bytes F7 (realtime) \end{verbatim} If \texttt{channel} is a device ID. If its value is F7, this is a global broadcast that all devices should heed. The \texttt{subid1} and \texttt{subid2} could be something like "01 00" for a long-form (full frame) time-code message. % F0 7E id 01 Channel Pressure (Aftertouch) % F0 7E id 01 or Sample Dump Header??? % F0 7E id 02 Polyphonic Key Pressure (Aftertouch) % F0 7E id 02 or Sample Data Packet??? % F0 7E id 03 Controller (Control Change) % F0 7E id 03 or Sample Dump Request??? % F0 7E id 09 nn F7 GM System Enable / Disable (01 / 00) % F0 7F 00 ??? % F0 7F id 01 01 hh mm ss ff F7 MIDI Full Frame % F0 7F id 04 01 mm nn F7 MIDI Master Volume (vs Controller Volume) A detailed description is beyond the scope of this document. Some messages supported by these messages are the \textsl{MIDI Master Volume}, \textsl{MIDI Full Frame}, and the \textsl{General MIDI System Enable/Disable} messages. An interesting subset of these messages is \textsl{MIDI Show Control} (MSC): \begin{verbatim} F7 7F deviceid 02 commandformat command data F7 (MSC) \end{verbatim} See \cite{msc}. Some simple controls can be included in the 'ctrl' file's set of macros. \paragraph{Seq66 SysEx Handling} \label{paragraph:midi_format_seq66_sysex_handling} \textsl{Seq66} is slowly gaining support for reading, storing, and sending SysEx events within a sequence. \textsl{Seq66} warns if the terminating 0xF7 SysEx terminator is not found at the expected length. Also, some malformed SysEx events have been encountered, and those are detected and handled as well. The format of a bare (i.e. not encoded in a MIDI file) SysEx message is like the following, complex case: \begin{verbatim} F0 manid devid modelid direction address data checksum terminator F0 0x41 0x10 0x42 0x12 0x40007F 0x00 0x41 0xF7 \end{verbatim} The "manid" is a manufacturer's identifier. 0x41 is Roland, 0x24 is Hohner. 0x00 is used for adding two more codes to greatly expand the ID list. 0x7E is used to denote a non-realtime message, and 0x7F denotes a realtime message. The "devid" is the device identifier (e.g. 0x10 for Roland devices). The "devid" indicates which devices will accept the SysEx message. The "modelid" is the model identifier (0x42 for most GS synths). The "direction" is 0x12 for sending information or 0x11 for requesting information. The "address" is a 3-byte value on which the SysEx message should act. Devices provide an address map to define what is at each address. For example, this address might define a "GS Reset", which performs an initialization. The "data" is either the data to send (one or more bytes), or the size of the data we are requesting. Some devices will include a "checksum" for data integrity. All SysEx messages end with an F7 byte. But see \sectionref{subsubsec:midi_format_meta_sysex_event}. \subsubsection{Non-Specific End of Sequence} \label{subsubsec:midi_format_meta_sequence_ends} Any other statuses are deemed unsupportable in \textsl{Seq66}, and abort parsing with an error. If the \texttt{-{}-bus} option is in force, it overrides the buss number (if any) stored with the sequence. This option is useful for testing a setup. Note that it also applies to new sequences. At the end, \textsl{Seq66} adds the sequence to the encoded tune. \subsection{More MIDI Information} \label{subsec:midi_information_more} This section goes into even more detail about the MIDI format, especially as it applies to the processing done by \textsl{Seq66}. The following sub-sections describe how \textsl{Seq66} parses a MIDI file. \subsubsection{Channel Events} \label{subsubsec:midi_format_channel_events} \textbf{Status}. The byte after the delta time is examined by masking it against 0x80 to check the high bit. If not set, it is a "running status", it is replaced with the "last status", which is 0 at first. \begin{verbatim} Status byte: varies 1 byte \end{verbatim} If the high bit is set, it is a status byte. What does the status mean? To find out, the channel part of the status is masked out using the 0xF0 mask. If it is a 2-data-byte event (note on, note off, aftertouch, control-change, or pitch-wheel), then the two data bytes are read: \begin{verbatim} Data byte 0: varies 1 byte Data byte 1: varies 1 byte \end{verbatim} If the status is a Note-On event, with velocity = data[1] = 0, then it is converted to a Note-Off event, a fix for the output quirks of some MIDI devices. If it is a 1-data-btye event (Program Change or Channel Pressure), then only data byte 0 is read. The one or two data bytes are added to the event, the event is added to the current sequence. When the event is played, and the MIDI channel of the sequence is used, unless the MIDI channel for the sequence is "Any". In that case, the event's channel is used. \subsubsection{Meta Events Revisited} \label{subsubsec:midi_format_meta_events_revisited} If the event status masks off to 0xF0 (0xF0 to 0xFF), then it is a Meta event. If the Meta event byte is 0xF7, it is called a "Sequencer-specific", or "SeqSpec" event. For this kind of event, then one to three manufacturer ID bytes and the length of the event are read. \begin{verbatim} Meta type: varies 1 byte Meta length: varies 1 or more bytes \end{verbatim} If the type of the \texttt{SeqSpec} (0xFF) meta event is 0x7F, parsing checks to see if it is one of the \textsl{Seq66}-specific events. These events are tagged with various values that mask off to 0x242400nn. The parser reads the tag: \begin{verbatim} Prop tag: 0x242400nn 4 bytes \end{verbatim} These tags provide a way to save and recover \textsl{Seq24/Seq66} properties from the MIDI file: MIDI buss, MIDI channel, time signature, sequence triggers, and the key, scale, and background sequence to use with the track/sequence. Any leftover data for the tagged event is let go. Unknown tags are skipped. If the type of the \texttt{SeqSpec} (0xFF) meta event is 0x2F, then it is the End-of-Track marker. The current time marks the length (in MIDI pulses) of the sequence. Parsing is done for that track. If the type of the \texttt{SeqSpec} (0xFF) meta event is 0x03, then it is the sequence name. The "length" number of bytes are read, and loaded as the sequence name. If the type of the \texttt{SeqSpec} (0xFF) meta event is 0x00, then it is the sequence number, which is read: \begin{verbatim} Seq number: varies 2 bytes \end{verbatim} Note that the sequence number might be modified latter to account for the current \textsl{Seq24} screenset in force for a file import operation. Any other \texttt{SeqSpec} type is simply skipped by reading the "length" number of bytes. The remaining sections simply describe MIDI meta events in more detail, for reference. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/mutes.tex ================================================ %------------------------------------------------------------------------------- % seq66 mutes %------------------------------------------------------------------------------- % % \file seq66 mutes.tex % \library Documents % \author Chris Ahlstrom % \date 2020-01-13 % \update 2023-11-06 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides a discussion of the MIDI GUI mutes that Seq66 supports. % %------------------------------------------------------------------------------- \section{Seq66 Mutes Master} \label{sec:mutes_master} The \textbf{Mutes} tab provides a global view of all the mute-groups in a \textsl{Seq66} MIDI file or global configuration. It also provides for manipulation of mute-groups, to supplement what can be done via the live grid.. \subsection{Mute Group} \label{sec:mutes_mute_group} A mute-group (see \sectionref{subsubsec:concepts_terms_group}) holds the desired statuses (armed/muted) of all of the patterns in a screenset/bank (see \sectionref{subsubsec:concepts_terms_bank}). By setting up multiple mute-groups, one can quickly transition to different arrangements or musical themes. A mute-group can be associated with a hot-key or MIDI control; when the mute-group is selected, the patterns that are armed changes. This makes it easy to change what the tune is playing in a big way. Mute-groups are \textsl{learned} by \begin{enumerate} \item Arming all of the desired patterns in the set. \item Clicking the \textbf{Learn} ("L") button. See \sectionref{subsubsec:introduction_mute_group_learn_button}. \item Clicking the keystroke, which is the shifted version of the hot-key to arm/mute the pattern corresponding to the mute-group number.. This is accomplished in the default keyboard configuration via the \index{auto-shift} auto-shift function, which shifts the keystroke automatically for group-learn. \end{enumerate} These steps can also be done via MIDI control. More tediously, mute-groups can be edited in a 'mutes' file. To learn more about mute-groups, see \sectionref{subsubsec:introduction_mute_group_learn_button}, and \sectionref{subsubsec:concepts_terms_group}. \subsection{Mute Master Tab} \label{sec:mutes_mute_master_tab} By themselves, mute groups are not easy to see except by activating each mute-group. The mute-master (\textbf{Mutes} tab provides a way to view them more systematically, as well as assigning names to them. % TODO: provide a way in the Live Grid, if room allows, to show the % mute-group name of the activated mutes. \begin{figure}[H] \centering \includegraphics[scale=0.50]{tabs/mutes/mute-master-tab-3.png} \caption{Mute Master Tab} \label{fig:mutes_master_tab} \end{figure} This diagram shows the \textbf{Mutes} tab after some mute-groups have been created. Mute-groups can be created in the main window's patterns panel, but it is difficult to know what each group comprises. This tab makes it easy to see the layout of the mute-groups, and also allows for some editing and live control of the mute-groups. Note that the Qt theme shown here (\textsl{kvantum}) shows that buttons are checkable; other themes often do not show that status. Annoying. \subsection{Mute-Groups Table} \label{sec:mutes_mute_groups_table} The most important user-interface element is the table at the right. Here, one can select a mute-group for editing and control. \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Mute-Groups Table}{mutes!table} At the right is a table that shows the assigned mute-group keys and some information about them: \begin{itemize} \item \textbf{Group}. This column holds the group numbers for each group, ranging from 0 to 31. Each row corresponds to a button in the \textbf{Mute-Groups} grid. \item \textbf{Active}. This column shows the number of activated patterns in the mute-group. A zero means the mute-group is inactive. \item \textbf{Key}. Indicates the keystroke that can be used to activate that mute-group. By default, these are shifted version of the corresponding mute/unmute pattern-slot hotkey. \item \textbf{Group Name}. Provides a mnemonic name for the mute group. A feature for the future. \end{itemize} Only the group-name column of This table is editable. The user can also edit \texttt{qseq66.mutes} with a text editor, or edit \texttt{qseq66.ctrl} to modify the the hot-keys or MIDI controls. Click on the desired group. \subsection{Mute-Groups Buttons} \label{sec:mutes_mute_groups_buttons} Once a mute-group has been selected, its mute-group button is activated. Click on the group button (perhaps twice), to be able to add pattern mute states via the pattern buttons, as an alternative to group-learn. \itempar{Mute-Groups}{mutes!groups} This grid is always of size \textbf{4 x 8}. It represents the maximum of 32 mute-groups that can be supported by \textsl{Seq66}. To start, all group buttons are \textsl{disabled} and \textsl{unchecked} (inactive). Where a mute-group exists, the button is made \textsl{checked} (active), but still disabled. Here, the user clicked on mute-group 2, which becomes active in the user-interface. % (But it is not made active in the patterns panel). The \textbf{2} button is also enabled, and can be clicked. Clicking once deactivates the button, which potentially flags that mute-group for removal. Clicking it again reactivates it and enables all of the buttons in the \textbf{Group Patterns} grid. Also, if the \textbf{Triggers} button has been activated, then these buttons can activate mute-groups, as if activated in the live grid. \subsection{Group-Patterns Buttons} \label{sec:mutes_mute_group_patterns_guttons} \itempar{Group Patterns}{mutes!patterns} Once this grid is enabled by clicking on a mute-groups button, each button here can be clicked to add a pattern to the mute-group, or remove a pattern from the mute-group. % \itempar{Update Group}{mutes!update group} % When a change in the mute-group status or the status of one of its patterns % is made, this button is enabled. % Once clicked, the current mute-group is modified internally, % where it can later be saved when \textsl{Seq66} % exits, or when the \textbf{Save All} button is clicked. \subsection{Other Controls} \label{sec:mutes_mute_other_controls} \itempar{Mutes File}{mutes!mutes file} The mutes-file read-only field shows the base-name of a file into which one can save the current-mute group setup, as a way to back up the setup. To change this file-name, do so in \textbf{Edit / Preferences / Session / .mutes}. \itempar{Save File}{mutes!save file} This button saves all of the mute-groups to the specified 'mutes' file. It is enabled when any change has been made to a mute-group or to the check-boxes. % and has been registered by pressing the \textbf{Update Group} button. If the user has provided a path in the \textbf{Edit / Preferences / Session / .mutes} field, the path is stripped; \textsl{Seq66} must not write configuration information outside of the session configuration directory. % The file is saved, but is not made official in the % 'rc' file; one must edit the 'rc' file to use the new 'mutes' file. % We might provide a button for that function at some point. \itempar{Load File}{mutes!load file} This button brings up a dialog to select an external mutes file. \itempar{Read/Write Format}{mutes!read/write format} This section provides the following features, which still need some work: \begin{itemize} \item \textbf{From MIDI}. If checked, the mute-groups are read from the MIDI file, if present. These mute-group override the one from an external 'mutes' file. \item \textbf{From Mutes}. If checked, the mute-groups are read from the specified 'mutes' file, if present and active. \item \textbf{Binary}. This flag indicates to save the mute-group information in binary format, which is the normal format. Each mute-group pattern's setting is indicated by a 0 or a 1. This is the default format for writing the mute-groups. \item \textbf{Hex}. In this format, each set of mute-group is written in 8-bit hexadecimal format (e.g. "0xff"). This format is useful if the user has opted to have large set sizes such as 64 and 96 patterns. Not well-supported yet. \item \textbf{To MIDI}. When a tune is closed, as when \textsl{Seq66} exits, this option indicates to write the mute-group information to the \textsl{Seq66}-style MIDI file. \item \textbf{To Mutes}. When a tune is closed, as when \textsl{Seq66} exits, this option indicates to write the mute-group information to the 'mutes' configuration file (e.g. \texttt{qseq66.mutes}). Mute-groups can be written to both. \item \textbf{Toggle Only Active}. Normally, if a musician turns on a mute-group, then toggles other patterns separately, all patterns are turned off when the musician selects another mute group. If this option is checked, only the patterns that are part of the mute-group are toggled; the other patterns remain on. \item \textbf{Strip If No Mutes}. If checked, and there are no defined mute-groups, then they are not saved to the song. \end{itemize} The only time one would want to read/write the mute-groups to a 'mutes' file is when one uses standard song sets for all of one's songs. \itempar{Triggers}{mutes!trigger mode} When activated, this option will enable the \textbf{Mute-Groups} buttons, deactive them all, and turn them into standard push-buttons. in the \textbf{Triggers} mode, when a button is clicked, the corresponding mute-group will be actived. \itempar{Zeroes}{mutes!clear all mutes} This button will clear every mute group, and replace them with mute-group where all patterns are off (zeroed). There are a couple of small buttons. The first is reserved for expansion. The second displays an asterisk when a mute-group or mute setting has changed. % \itempar{Fill}{mutes!fill mutes} % This button will create a set of empty mute-groups. % Currently, this alters the current MIDI tune, forcing a prompt to save. % This action creates 32 empty mute-groups. % If a single mute-group is created in the patterns panel, % then only that mute-group is saved. % \itempar{Pattern Offset}{mutes!pattern offset} % If the user has selected a larger set size that is a multiple of 32, this % item is enabled. It then allows the user to modify patterns with a % sequence number greater than 31. A future feature. % % \itempar{Up/Down Buttons}{mutes!up/down buttons} % A future feature to move mute-group around without % changing the keystroke for that mute group. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/palettes.tex ================================================ %------------------------------------------------------------------------------- % palettes %------------------------------------------------------------------------------- % % \file palettes.tex % \library Documents % \author Chris Ahlstrom % \date 2020-12-29 % \update 2026-04-21 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides a discussion of the MIDI GUI palettes that Seq66 % supports. % %------------------------------------------------------------------------------- \section{Palettes for Color, Brushes, and Pens} \label{sec:palettes} Many user-interface elements in \textsl{Seq66} are drawn independently of the Qt theme in force, and they have their own coloring. Also, patterns can be colored, and the color is stored (as a color number) in the pattern when the tune is saved. It can be tricky to get a good look, especially one that matches the style-sheet (if provided) or the active Qt theme. These palette entries can make for a better look. The palette file is somewhat self documenting and easy to edit. There are five palettes: \begin{itemize} \item \textbf{Pattern}. This palette contains 32 color entries, and each can be used to add color to a pattern in the \textsl{Live} grid or in the \textsl{Song} editor. The color of a pattern, if used, is saved with the pattern in the MIDI file. \item \textbf{Ui}. This palette contains 24 color entries. These color entries are used in drawing text, backgrounds, grid lines, background patterns, drum notes, and more. These colors each have a counterpart that is used with the \texttt{-{}-inverse} option is applied to a run of \textsl{Seq66}. \item \textbf{Inverse Ui}. This palette contains 24 color entries. These colors are used when the \texttt{-{}-inverse} option is applied to a run of \textsl{Seq66}. \item \textbf{Brushes}. This "palette" provides a way to specify the fill type for the drawing of notes, the scale (if shown) in the pattern editor, and the background sequence (if shown). It allows the user to select solid file, hatching, linear gradient, and other fill patterns. \item \textbf{Pens}. This "palette" provides a way to specify the drawing of vertical lines in the pattern and song editors. It supplements the \texttt{progress-bar-thick} and \texttt{gridlines-thick} options in the 'usr' file. \end{itemize} All palettes have default values built into the application. However, the user can also include 'palette' files to change the colors used. For example, the normal colored palette can be changed to a gray-scale palette. The name of the palette file is specified in the 'rc' file by lines like the following: \begin{verbatim} [palette-file] 1 # palette_active qseq66-alt-gray.palette \end{verbatim} If this palette file is active, it is loaded, changing all of the palettes, and thus the coloring of \textsl{Seq66}. In \sectionref{subsec:palettes_theming}, some weird behavior with the \textsl{qt5ct} configuration application and non-Qt-based window managers is discussed. \subsection{Palettes Setup} \label{subsec:palettes_setup} The palette file is a standard \textsl{Seq66} configuration file with a name something like \texttt{qseq66.palette}, plus these sections: \begin{verbatim} [Seq66] [comments] [hints] [palette] [ui-palette] [brushes] [pens] \end{verbatim} The "hints" section helps adapt to dark themes and dark palettes: \begin{verbatim} [hints] dark-theme = true dark-ui = true \end{verbatim} The fourth section is the "Pattern" palette; the second section is the "Ui" palette, which includes the inverse palette as well; and the third section defines brushes for drawing the interiors of some elements. \subsubsection{Palettes Setup / Pattern} \label{subsubsec:palettes_setup_pattern} The pattern palette is meant for changing the color of the progress area of the grid slot. This coloring helps in locating a pattern in the grid at a glance. The color selected for a pattern is also shown in the Song editor; both the name of the pattern and any triggers associated with the pattern are painted in the same color. Each color is numbered. When a color is applied to the pattern, that color number is stored in a SeqSpec in the pattern, and saved in the MIDI file. The following shows the pattern palette, with some entries elided for brevity: \begin{verbatim} [palette] 0 "Black" [ 0xFF000000 ] "White" [ 0xFFFFFFFF ] 1 "Red" [ 0xFFFF0000 ] "White" [ 0xFFFFFFFF ] 2 "Green" [ 0xFF008000 ] "White" [ 0xFFFFFFFF ] 3 "Yellow" [ 0xFFFFFF00 ] "Black" [ 0xFF000000 ] 4 "Blue" [ 0xFF0000FF ] "White" [ 0xFFFFFFFF ] ... ... ... ... ... 29 "Dark Violet" [ 0xFF9400D3 ] "Black" [ 0xFF000000 ] 30 "Light Grey" [ 0xFF778899 ] "Black" [ 0xFF000000 ] 31 "Dark Grey" [ 0xFF2F4F4F ] "Black" [ 0xFF000000 ] project. \end{verbatim} The names are color names, and these names are what show up in the popup color menus for the pattern buttons in the \textsl{Live} grid. The colors on the left are the background colors, and the colors on the right are the foreground colors, which are chosen for contrast with the background. The colors are in \texttt{\#AARRGGB} format, with the "\#" replaced by "0x" because "\#" starts a comment in \textsl{Seq66} configuration files. Note that all the alpha values are "FF" (opaque); we have not yet experimented with changing them. Lastly, only 32 entries are accepted. \subsubsection{Palettes Setup / Ui and Inverse Ui} \label{subsubsec:palettes_setup_ui} The UI palette applies to other elements drawn by \textsl{Seq66}: foreground lines, background paint that can differ between various backgrounds, text colors that can differ between slots, time-panels, the data panel, and the Song editor's name panel. If the user has chosen a particular Qt theme or has applied a style-sheet, a palette can be created to match. There are a couple of style-sheet/palette pairs in the \texttt{data/samples} directory. The following shows the pattern and song palette, with some entries elided for brevity: \begin{verbatim} [ui-palette] 0 "Foreground" [ 0xFF000000 ] "Foreground" [ 0xFFFFFFFF ] 1 "Background" [ 0xFFFFFFFF ] "Background" [ 0xFF000000 ] 2 "Label" [ 0xFF000000 ] "Label" [ 0xFFFFFFFF ] 3 "Selection" [ 0xFFFFA500 ] "Selection" [ 0xFFFF00FF ] 4 "Drum" [ 0xFFFF0000 ] "Drum" [ 0xFF000080 ] ... ... ... ... ... 29 "Slots Text" [ 0xFF000000 ] "Slots Text" [ 0xFFFFFFFF ] 30 "Extra 1" [ 0xFF000000 ] "Extra 1" [ 0xFFFFFFFF ] 31 "Extra 2" [ 0xFF000000 ] "Extra 2" [ 0xFFFFFFFF ] \end{verbatim} Here, the names are feature names, not color names. The first color is the normal color, and the second color is the inverse color. Only 32 entries are supported. The numbers have no meaning except to order the colors. \subsubsection{Palettes Setup / Brushes} \label{subsubsec:palettes_setup_brushes} This "palette" is small, allowing the fill-pattern of a few pattern-editor items to be changed. \begin{verbatim} [brushes] empty = solid # preferred note = lineargradient # default scale = dense3 backseq = dense2 chord = bdiag \end{verbatim} On the left of the equals sign is the item than can be filled, and on the right side is the \textsl{Qt} brush to be used. The defaults for most are solid fill. The set of legal values matches the set of value in the Qt::BrushStyles enumeration. The entry \texttt{empty} isn't too useful; best to leave it set to 'solid'. The entry \texttt{note} affects the fill of normal/selected notes. The best values are either 'lineargradient' (the default) or 'solid'. The entry \texttt{scale} affects the fill for the piano roll scale. The hatching used here makes it easier to recognize that the scale is just there for orientation. The entry \texttt{backseq} affects the fill of the background sequence. The hatching used here helps further distinguish the real notes from the background notes. \subsubsection{Palettes Setup / Pens} \label{subsubsec:palettes_setup_pens} The last "palette" is small, allowing the vertical grid lines of a few pattern-editor items to be changed. \begin{verbatim} [pens] measure = solid beat = solid fourth = dash step = dot \end{verbatim} The defaults are shown above. The set of legal values matches the set of value in the \texttt{Qt::PenStyles} enumeration. Note that one value is "nopen", which makes that line invisible. \subsection{Palettes Summary} \label{subsec:palettes_summary} There are some obvious enhancements to this scheme, including increasing the number of palette items, synchronizing the palette with the current desktop theme semi-automatically, and providing a user interface to drag-and-drop colors. \subsection{Theming} \label{subsec:palettes_theming} Theming for a mix of \textsl{Qt} and \textsl{Gtk-N} applications can be a bit tricky. The \textsl{qt5ct} application helps in obtaining nicely rendered widgets, though some combinations have visibility issues. \subsubsection{Qt5ct/Qt6ct} \label{subsubsec:palettes_theming_qt5ct} The \textsl{qt5ct} configuration application can be used to select installed \textsl{Qt5} themes. The \textsl{qt6ct} configuration application can also be used to select installed \textsl{Qt6} themes. When using a non-Qt-based window manager or desktop manager, such as our favorite, \textsl{Fluxbox}, in conjunction with \textsl{GTK+} themes, there can be issues on some \textsl{Linux} distros. First, if using a Gtk theme setter (e.g. \textsl{gtk-chtheme} or \textsl{lxappearance}), one needs to use \textsl{qt5ct} to set Qt to work with Gtk themes. For this to work well, use this setting in the \texttt{.bashrc} or \texttt{.profile} file: \begin{verbatim} export QT_QPA_PLATFORMTHEME=gtk2 \end{verbatim} Otherwise some GUI elements might be difficult to see. If not, use one of the following settings: \begin{verbatim} export QT_QPA_PLATFORMTHEME=qt5ct export QT_QPA_PLATFORMTHEME=qt6ct \end{verbatim} Another option is to provide an executable script like the following, giving it a name such as \texttt{dseq66} (for a dark themed \textsl{Seq66}) to distinguish it from the normal-themed \texttt{qseq66}. \begin{verbatim} #!/bin/sh # Use dark coloring on qseq66, as we have configured in qt5ct. QT_QPA_PLATFORMTHEME=qt5ct qseq66 \end{verbatim} One might still encounter the issue that, with a Gtk theme, applications can take about 20 to 30 seconds to start up on some computer setups! Another issue is that some Qt themes might upset the sizing of buttons or text. Also see the style-sheet discussion in \sectionref{sec:configuration}. \subsubsection{Kvantum} \label{subsubsec:palettes_theming_kvantum} The \textsl{Kvantum} theming engine uses SVG to draw theme elements. It has a number interesting themes. Once installed, the \texttt{kvantum} and the \texttt{kvantum-dark} styles can be selected in \textsl{qt5ct}. These styles provide a number of themes that can be accessed via the \textsl{kvantummanager} application. Some nice \textsl{Kvantum} themes are \texttt{KvCurves}, \texttt{KvCyan}, \texttt{KvOxygen}, and \texttt{KvYaru}. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/pattern_editor.tex ================================================ %------------------------------------------------------------------------------- % pattern_editor %------------------------------------------------------------------------------- % % \file pattern_editor.tex % \library Documents % \author Chris Ahlstrom % \date 2015-08-31 % \update 2026-04-21 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % %------------------------------------------------------------------------------- \section{Pattern Editor} \label{sec:pattern_editor} The \textsl{Seq66} \textbf{Pattern Editor} can edit and preview a pattern, configure its buss, channel, transpose, musical scale, and many other settings. A slightly trimmed version of the \textbf{Pattern Editor} appears in the \textbf{Edit} tab in the main window; some controls are hidden and the data pane is vertically compressed. The full version can be brought up in an external window. In the descriptions that follow, there is a mix of old and new figures. The older figures have a flat look, while the newer figures feature gradient-filled notes and circular grab handles in the data paint. The following figure is older. \begin{figure}[H] \centering \includegraphics[scale=1.00]{pattern-editor/pattern-edit-window.png} \caption{External Pattern Editor Window} \label{fig:pattern_editor_window} \end{figure} That figure does not show recent new features; they are shown in the figure below: \begin{itemize} \item For selected events, the orange data line is topped with a circular "grab handle" that can be used to change the amplitude of the data (e.g. velocity) being shown. \item Notes are, by default, shown with a gradient pattern for a textured appearance. \item New buttons at the right (including a button reserved for the future) to tweak some aspects of the pattern editor panels. \item Lines and numbers indicating MIDI values. The data pane has been expanded to allow easier dragging to the 0 and 127 extremes. \end{itemize} \begin{figure}[H] \centering \includegraphics[scale=1.00]{pattern-editor/pattern-edit-window-additions.png} \caption{External Pattern Editor Window Additions for 0.99.24} \label{fig:pattern_editor_window_additions} \end{figure} The \textbf{Pattern Editor} is complex, and we will discuss the external window only since its features are a superset of the \textbf{Edit} tab. For exposition, we break the window into the following sections: \begin{enumber} \item \textbf{First Row} \item \textbf{Second Row} \item \textbf{Time} \item \textbf{Piano Roll} \item \textbf{Events Pane} \item \textbf{Left Buttons} \item \textbf{Right Buttons} \item \textbf{Data Pane} \item \textbf{Bottom Row} \item \textbf{Common Actions} \end{enumber} Before we describe this window, there are some things to recognize. First, if the pattern is empty when play is started, the progress bar will still move, so that the user can play a MIDI instrument and record new notes. Second, to add a note with the mouse the following options turn on painting note: \begin{itemize} \item One must press \textsl{and hold} the \textsl{right} mouse button (the pointer changes to a pointing finger), press the left mouse button. \item Click in the pattern editor, press the \index{keys!i} \index{keys!p} \texttt{p} key to select the "pencil" or "paint" mode, or \texttt{i} key to select "insert" mode (a la vi), \item Click on the "pointing finger" button. \item \end{itemize} \index{mouse!left-click} Left-click to add a note or \index{mouse!left-click-drag} left-click-drag to add multiple notes as the mouse moves. To exit the painting mode, \index{keys!x} \index{keys!Esc} press or release the right mouse button, press \texttt{x} to "eXit" or "eXscape" from paint mode, or click the "pointing finger" button again. \texttt{Esc} also exits paint mode if the pattern is not playing. Notes are drawn only with the length selected by the "notes" button near the top of the pattern window. There are tricks to modifying the new notes, described later. \textsl{Seq66} automatically scrolls horizontally through the sequence/pattern editor window when playback moves the progress bar past the current frame of data. This feature makes it easier to follow patterns that are longer than a measure or two. One might want to print out the following figure to follow along. There is a lot of functionality in this window. Also, some items have changed slightly from when this diagram was made. \begin{figure}[H] \centering \includegraphics[scale=0.90]{pattern-editor/pattern-edit-window-annotated.png} \caption{Pattern Editor Window, Annotated} \label{fig:pattern_editor_window_annotated} \end{figure} The arrow keys can be used to move left, right, up, or down. \index{keys!vi} \index{keys!hjkl} For \textsl{vi} users, the \texttt{h} (left), \texttt{j} (down), \texttt{k} (up), and \texttt{l} (right) keys can be used while any pane is in focus, and they act like the arrow keys. The \texttt{Home} and \texttt{End} keys move to the beginning and end of the pattern. One can page vertically in the piano roll using the \index{keys!page-up} \texttt{Page Up} and \index{keys!page-down} \texttt{Page Down} keys. Note that there are recent additions to the data view (see \figureref{fig:pattern_editor_window_additions}). Refer to \sectionref{subsec:pattern_editor_right_buttons}. \subsection{Pattern Editor / First Row} \label{subsec:pattern_editor_first_row} The top bar (horizontal panel) of the Pattern (sequence) editor lets one change the name of the pattern/loop/sequence/track, the time signature of the piece, how long the track is, and some other configuration items. \begin{enumber} \item \textbf{Track Number} \item \textbf{Track Name} \item \textbf{Add Time Signature Event} (not pictured) \item \textbf{Beats Per Bar Reset} and \textbf{Beats Per Bar} \item \textbf{Beat Width Reset} and \textbf{Beat Width} \item \textbf{Pattern Length Reset} and \textbf{Pattern Length} \item \textbf{Chord Types} \item \textbf{Buss Reset} and \textbf{Buss Selection} \item \textbf{Channel Reset} and \textbf{Channel Selection} \end{enumber} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Track Number}{pattern editor!number} This item shows the sequence/track/pattern/loop number, to make it easier to pick it out when a lot of patterns are being edited at once. \itempar{Track Name}{pattern editor!name} Provides the name of the pattern. This name should be short and memorable. It is displayed in the \textbf{Live Grid} (the \textbf{Patterns Dialog}), on the top line of its pattern slot. \itempar{Add Time Signature Event}{pattern editor!time signature} This button is shown in the composite picture in \sectionref{subsec:pattern_editor_measures_ruler}, to the left of the beat selector. When either the beats/bar or beat width values (see below) are changed, and the progress marker is at time 0, then the change is saved as a Time Signature Meta event at time 0. Additional changes there overwrite that event. Otherwise, move the "L" marker and click this button to log another time-signature. See \sectionref{subsubsec:pattern_editor_measures_ruler_time_bar}. There are interactions between the beats/bar, beat-width, and the length (in measures) of the pattern. Generally, if both the beats/bar and beat-width are to be changed, it is better to change the beat-width first. In any case, the measures might have to be adjusted as well. It is better to get all the time signatures in place before adding notes. To add time signatures at later times in the tune, this must be done: \begin{enumber} \item Move the mouse cursor to the \textsl{top half} of the time pane; the cursor will change to a vertical arrow. Click at the desired time and observe the red progress line and "L" marker. \item Change the beats-per-measure, if desired. \item Change the beats-width, if desired. \item Click the \textbf{Add Time Signature} button to log the new time signature. \end{enumber} It adds a time-signature Meta event and adjusts the pattern length (if needed) at once. The new time signature should appear in the data and event panes as well. \itempar{Beats Per Bar}{pattern editor!beats/bar} \index{beats per bar} Specifies the number of beat units per bar in the time signature. The possible values range from 1 to 16, if the drop-down menu is used. Arbitrary values up to 32 can be entered by typing the number. The "Reset" button resets the value to 4. \itempar{Beat Width}{pattern editor!beat width} \index{beat width} Specifies the size of the bottom beat unit of the time signature: 1 for whole notes; 2 for half notes; 4 for quarter notes; 8 for eight notes; 16 for sixteenth notes; and 32 for thirty-second notes. The whole time signature is display at the bottom center of the corresponding pattern slot in the \textbf{Live Grid}. Arbitrary values up to 32 can be entered by typing the number. The "Reset" button resets the value to 4. Editing a non-power-of-2 beat width yields a warning prompt appears because MIDI tracks can store only time signatures where the beat width is a power of 2. If the prompt is OK'ed, then the non-standard beat-width is set, but it is \textsl{not added as a time-signature event}. Instead, it is stored as a SeqSpec event for \textsl{Seq66}. Also note that it is difficult to display these time-signatures correctly, since the pulse/beat calculations yield non-integers that are not detected in displaying a gird. \itempar{Pattern Length}{pattern editor!length} Sets the length of the current pattern, in measures. The possible values range from 1 to 64. Arbitrary values up to 1024 can be entered by typing the number. \textsl{However}, when opening or importing a non-\textsl{Seq66} MIDI tune, the length of each track will be used, and so other values are possible. Bringing up a pattern less than one measure or bar in length in the pattern editor will adjust the pattern to pad it to the length of one measure. \index{pattern editor!progress bar} \textsl{Seq66} will, when it reads such a short pattern from a MIDI file, \index{expand} \index{recording!expand} A feature from user \textsl{stazed} allows the pattern to expand indefinitely while the user inputs MIDI from a controller, via the \textbf{Expand} option of the \textbf{Loop Record Type}. This works in \textbf{Live} mode or \textbf{Song} mode. The pattern extends continually with or without note entry. Once playback starts, the progress bar moves forward. Notes are recorded they are played on a MIDI controller. Once the end of the measure is neared, another measure is added, whether or not more notes are struck. The measure-count at the top of the editor shows the current number of measures. Once playback (and recording) stops, the user can either accept the final length, or change the length to a lesser number. If the lesser length will drop notes, a prompt will appear for the user to accept or reject the length change. \itempar{Chord Types}{pattern editor!chord types} \index{chord generation} This setting allows one to select a chord type (e.g. "Major" or "minor"). When active, a note is treated like the base note of the selected chord type, and extra notes are generated to create that chord. The \textbf{Chord Generation Reset} button is at the left of the \textbf{Data Pane}. One can insert chords with one click. (This feature comes from user "stazed" and his \textsl{Seq32} project \cite{seq32}.) Select the desired chord type first. Once a value other than \textbf{Off} is selected, drawing mode will add multiple notes representing the chord created, with the clicked note value as the base of the chord. However, if the "filter notes" button at the right of the data pane is checked, then only one note is inserted, and only notes in that chord are inserted. \itempar{MIDI Out Device (Buss)}{pattern editor!midi out device} This setting specifies a virtual MIDI output buss or a MIDI output device set up by the computer and attached MIDI equipment. The button resets it to buss 0. Note that, if the pattern's selected buss is not found, this entry will be blank. The user must select a valid buss from this dropdown. \itempar{MIDI Out Channel}{pattern editor!midi out channel} The drop-down selects the MIDI output channel. The possible values range from 1 to 16, plus \textsl{Free}, which means that the channels of the events are preserved, and are used as the output channel, a bit like an SMF 0 track. The editing channel is always channel 1; the selected channel is applied on playback. If instruments are assigned in the 'usr' configuration file for that device and channel, their names will be shown in the dropdown. In addition, this setting determines the channel applied when painting notes in the piano roll. If set to "1" or "Free" (no channel), then channel 1 is applied. Otherwise, if set to "2" through "16", that channel is applied. \subsection{Pattern Editor / Second Row} \label{subsec:pattern_editor_second_row} The second horizontal panel of the Pattern Editor provides a number of additional settings and functions: \begin{enumber} \item \textbf{Undo} \item \textbf{Redo} \item \textbf{Quantize Selection} \item \textbf{Tools Popup} \item \textbf{Follow Progress} \item \textbf{Reset Snap} and \textbf{Grid Snap} \item \textbf{Note Length Reset} and \textbf{Note Length} \item \textbf{Zoom Reset} and \textbf{Zoom} \item \textbf{Key Reset} and \textbf{Key of Sequence} \item \textbf{Scale Reset} and \textbf{Musical Scale} \item \textbf{Background Sequence} \end{enumber} \subsubsection{Pattern Editor / Second Row / Undo/Redo} \label{subsubsec:pattern_editor_second_row_undo_redo} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Undo}{pattern editor!undo} The \textbf{Undo} button rolls back any changes to the pattern from this session. It will roll back one change each time pressed. \index{keys!ctrl-z} Pressing \texttt{Ctrl-Z} is the same as using the \textbf{Undo} button. \itempar{Redo}{pattern editor!redo} The \textbf{Redo} button will restore any undone changes to the pattern from this session. It will restore one change each time it is pressed. There is currently no redo key. \subsubsection{Pattern Editor / Second Row / Quantize Selection} \label{subsubsec:pattern_editor_second_row_quantize_selection} \index{quantize} \index{pattern editor!quantize} This button quantizes the selected events as per the \textbf{Grid Snap} setting. \subsubsection{Pattern Editor / Second Row / Tools Popup} \label{subsubsec:pattern_editor_second_row_tools_popup} This button brings up a nested menu of tools for modifying selected events and notes. Shown below are selection options, and the small dialog to select a pitch range. \begin{figure}[H] \centering \includegraphics[scale=1.00]{pattern-editor/selection_options.png} \caption{Pattern Editor Selection Options} \label{fig:pattern_editor_window_selection_options} \end{figure} \begin{enumber} \item \textbf{Insert macro at "L"}. Move the "L" marker, if desired, to set the time-stamp. then select a macro from the list to insert it at that time. The "footer" and "header" macros are always disabled, as they're not so useful on their own. \item \textbf{Select Notes...}. Selects Note Ons, Note Offs, and Aftertouch. In order for notes to be modified by quantization or randomization, they need to be selected first, otherwise some menu entries are disabled. Notes can be selected in the piano roll of via this menu. This menu provides two note-selection options: \begin{itemize} \item \textbf{Select all}. Selects all notes in the pattern; \index{keys!ctrl-a} \texttt{Ctrl-A} will also select all of the events in the pattern editor. \item \textbf{Invert selection}. Inverts the selection of notes. \item \textbf{Select pitch range...}. Brings up a small input dialog where the user can enter a single note value or a range of notes. The notes can be represented by either an integer (0-127) or a note name such as "C4". A single value selects an octave starting at that note. A range is notated as "32-64" or "A3-C\#4". The range is selected and can then be cut or moved. Also, an existing selection can be added to using this menu entry. \end{itemize} \item \textbf{Note timing/velocity...}. This menu offers four ways to tweak the timing of the selected notes: \begin{itemize} \item \textbf{Quantize}. \index{quantize} Quantizes the selected notes in time, the same way as the \textbf{Quantize} ("\textbf{Q}") button. \item \textbf{Tighten}. \index{tighten} This operation merely a less strict form of quantization. \item \textbf{Jitter}. \index{jitter} Jittering modifies the timing of a note by adding or subtracting a small random of time. \item \textbf{Randomize velocity}. \index{randomize} This operation modifies the velocity of a note by a small amount. \end{itemize} \item \textbf{Pitch transpose}. Allows uniform transpostion regardless of the key and scale in force for the pattern. \index{modify pitch} Selecting this item entry brings up a sub-menu. \item \textbf{Harmonic transpose}. Makes sure that all transpositions stay on the selected scale. If the scale selection is \textbf{Off}, this is the same as plain pitch transpose, and so is not enabled. \item \textbf{LFO}. Allows for modulating of control values with a low-frequency oscillator. See \sectionref{subsec:pattern_editor_bottom}. \item \textbf{Pattern fix}. Allows for fixing up a new pattern that was recorded with some timing errors, or transforming the pattern in various ways. The pattern fix dialog provides a way for more control over the modifications of a pattern. It's complex and deserves a section of its own. See \sectionref{subsec:pattern_editor_pattern_fix}. \end{enumber} \subsubsection{Pattern Editor / Second Row / Follow Progress} \label{subsubsec:pattern_editor_second_row_follow_progress} \index{follow progress} This button toggles whether or not the progress bar follows progress in long patterns. Turning off this feature is useful when one wants to concentrate on the current measure without the paging to subsequent measures that occurs with the "follow progess" feature. \subsubsection{Pattern Editor / Second Row / Snap Reset and Grid Snap} \label{subsubsec:pattern_editor_second_row_grid_snap} \index{grid snap} \index{pattern editor!grid snap} Grid snap selects where the notes will snap when \textsl{drawn} and when \textsl{moved} (but not when lengthened/shortenend). That is, it selects the snap-spacing for the notes. It also applies to moving selected notes horizontally with the arrow keys. The following values are supported: \textbf{1}, \textbf{1/2}, \textbf{1/4}, \textbf{1/8}, \textbf{1/16} (\textsl{the default value}), \textbf{1/32}, \textbf{1/64}, and \textbf{1/128}. Additional values are also supported: \textbf{1/3}, \textbf{1/6}, \textbf{1/12}, \textbf{1/24}, \textbf{1/48}, \textbf{1/96}, and \textbf{1/192}. The button to the left of this control resets it to the default value. Note: if the note does not snap close enough to where one clicks, simply reduce the snap value. \subsubsection{Pattern Editor / Second Row / Note Length Reset/Note Length} \label{subsubsec:pattern_editor_second_row_note_length} \index{note length} \index{pattern editor!note length} Note length determines the duration of inserted (drawn by the mouse)notes. Like the \textbf{Grid Snap} values, the following values are supported: \textbf{1}, \textbf{1/2}, \textbf{1/4}, \textbf{1/8}, \textbf{1/16} (\textsl{the default value}), \textbf{1/32}, \textbf{1/64}, and \textbf{1/128}. Additional values are also supported: \textbf{1/3}, \textbf{1/6}, \textbf{1/12}, \textbf{1/24}, \textbf{1/48}, \textbf{1/96}, and \textbf{1/192}. The button to the left of this control resets it to the default value. \subsubsection{Pattern Editor / Second Row / Zoom Reset/Zoom} \label{subsubsec:pattern_editor_second_row_zoom_reset} \index{zoom!horizontal} \index{pattern editor!horizontal zoom} Horizontal zoom is the ratio between MIDI pixels and ticks, written as "pixels:ticks", where "ticks" is the "pulses" in "PPQN". For example, 1:4 = 4 ticks per pixel. Supported values are \textbf{1:1}, \textbf{1:2} (\textsl{the default value}), \textbf{1:4}, \textbf{1:8}, \textbf{1:16}, and \textbf{1:32}, along with more values to support higher PPQN tunes: \textbf{1:64}, \textbf{1:128}, \textbf{1:256}, and \textbf{1:512}. The default zoom is 2 for the standard PPQN value, 192, but it increases for higher PPQN values, so that the default zoom looks sensible. As the right number (ticks) goes higher, the effect is to zoom out, and show more of the pattern. The button to the left of this control resets it to the default value. Zoom can also be changed via the \texttt{Z} and \texttt{Z} keys, and reset by the \texttt{0}. \subsubsection{Pattern Editor / Second Row / Key Reset/Key} \label{subsubsec:pattern_editor_second_row_musical_key} \index{key}{pattern editor!key} Selects the desired musical key for the pattern. The following keys are supported: \textbf{C}, \textbf{C\#}, \textbf{D}, \textbf{D\#}, \textbf{E}, \textbf{F}, \textbf{F\#}, \textbf{G}, \textbf{G\#}, \textbf{A}, \textbf{A\#}, and \textbf{B}. Changing the key shifts the marked note-row bars for the current scale or current chord selection. The keys pane at the left indicates the base notes of the key in a \textbf{bold} font. The small key button resets the key to \textbf{C}. \index{save musical key} The musical key that a sequence/pattern is set to is saved in the MIDI file along with the rest of the data for the sequence. \textbf{However}, a change made to the key, scale, or background sequence (not to be confused with background-recording) in the pattern editor can be saved in the whole song, so that opening another sequence will apply the same settings to that sequence. This is an optional feature, supported as noted below. Also see \textbf{Musical Scale} below for the scale-identification feature. \index{global-sequence} If the global-sequence feature is enabled, and the user selects a different key, scale, or background sequence in the pattern editor, then \textsl{all} patterns share the selected key, scale, or background sequence. Furthermore, these settings are saved in the "proprietary" section of the MIDI file, where they are available for all patterns. If the global-sequence feature is \textsl{not} enabled, and the user selects a different key, scale, or background sequence in the pattern editor, then only that pattern will use the selected key, scale, or background. The key, scale, or background sequence change will be saved in the MIDI file only for that pattern, as a SeqSpec meta event. The global-sequence feature setting can be made in the 'usr' configuration file. \subsubsection{Pattern Editor / Second Row / Scale Reset/Musical Scale} \label{subsubsec:pattern_editor_second_row_musical_scale} \index{scale} \index{pattern editor!musical scale} Selects the desired background scale for the pattern; it provides a way for someone to key in notes that are only in that scale. When a scale is selected, the following features are supported: \begin{itemize} \item The note grid lines that are \textsl{not} in the scale are painted grey in the piano roll. \item For harmonic transposition, the notes are shifted so that they remain in the selected scale. \item The exact notes that are considered "in-scale" shift according to the value of the selected \textbf{Key of Sequence}. \end{itemize} \index{musical scales} The following musical scales are supported so far: \begin{itemize} \item \textbf{Off (Chromatic)} \item \textbf{Major (Ionian)} \item \textbf{Minor (Aeolian)} \item \textbf{Harmonic Minor} \item \textbf{Melodic Minor} \item \textbf{Whole Tone} \item \textbf{Blues} \item \textbf{Major Pentatonic} \item \textbf{Minor Pentatonic} \item \textbf{Phrygian} \item \textbf{Enigmatic} \item \textbf{Diminished} \item \textbf{Dorian} \item \textbf{Mixolydian} \end{itemize} Please let us know of any mistakes in these scales. Note that the \textbf{Melodic Minor} scale is supposed to descend in the same way as the natural \textbf{Minor} scale, but there is no way to support that trick in \textsl{Seq66}. One can select which \textbf{Musical Scale} and \textbf{Key} the piece is in nominally, and \textsl{Seq66} will grey those keys on the piano-roll that are \textsl{not} in the selected scale for the selected key. This is purely visual; a user can still add off-key notes. This feature makes it easier to stay in key while playing and recording. The scale will shift when a different \textbf{Key} is selected. \index{save musical scale} The scale that a pattern is set to is saved in the MIDI file along with the rest of the data for the pattern. A change made to the key, scale, or background pattern in the pattern editor can be saved globally, so that opening another pattern apply the same settings to that pattern. This is a configurable feature in the 'usr' file; see "global-seq-feature". This option allows applying the key/scale/background-sequence either globally (all patterns) or locally (per-pattern), with each pattern holding its key, scale, and background-sequence settings in SeqSpec meta events. \index{scale identifier} \index{keys!ctrl-k} The pattern editor's piano roll has a little secret: the \textbf{Scale Identifier}. When the piano roll has focus and \texttt{Ctrl-K} is pressed, all of the notes in the pattern are analyzed to try to determine the both the key and the scale of the existing notes. The method is not sophisticated... the notes are counted and are matched against all of the keys (C to B) and scales supported by \textsl{Seq66}. The combinations with the highest number of notes are then shown in a message box. This simple analysis depends on having at least 8 notes in the pattern, and it is possible to get weird results if there are only a few \textsl{different} notes, as in a simple bass line. Don't expect miracles from this feature. A more sophisticated analysis, the \textsl{Krumhansl-Schmuckler} key-finding algorithm, could be used, but it is a bit too complex for our needs, which are basic. \subsubsection{Pattern Editor / Second Row / Background Sequence} \label{subsubsec:pattern_editor_second_row_background_sequence} \index{background sequence} \index{pattern editor!background sequence} One can select another pattern to draw on the background to help with writing corresponding parts. The button brings up a small menu with values of \textbf{Off} and \textbf{Set 0} (at a minimum). The 0 is a set number; sets are numbered from 0 to 31. Additional set numbers appear in the menu for each set that has data in it. Under the \textbf{Set 0} entry, a menu appears. Once the desired pattern is selected from that list, it appears as dark cyan note bars, along with the normal notes that are part of the pattern. \index{save background sequence} The background sequence that shows is saved in the MIDI file along with the rest of the data for the sequence/pattern. A change made to the key, scale, or background sequence in the pattern editor is saved in the editor, so that opening another sequence will apply the same settings to that sequence. This is an optional feature, as noted earlier. \subsection{Pattern Editor / Measures Ruler (Time)} \label{subsec:pattern_editor_measures_ruler} The measures ruler ("bar indicator", or "Time") consists of a \textsl{timeline} at the top and the \textbf{L marker} and \textbf{R marker} items. The following are the elements next to and on the \textbf{Time} line. \subsubsection{Pattern Editor / Measures Ruler / Vertical Zoom} \label{subsubsec:pattern_editor_measures_ruler_vertical_zoom} \index{vertical zoom} \index{pattern editor!vertical zoom} The vertical zoom buttons allow the user to compress, reset, or expand the piano roll vertically. The \texttt{v}, \texttt{V}, and \texttt{0} keys offer the same functions. A more permanent change in vertical grid scaling can be made by setting the \textbf{Editor Key Height} value in the \textbf{Edit / Preferences / Display} tab, or in the 'usr' file. \subsubsection{Pattern Editor / Measures Ruler / Time Bar} \label{subsubsec:pattern_editor_measures_ruler_time_bar} The \textbf{Time} (or \textbf{Measures}) bar provides an explicit count of beats and bars. It follows the horizontal zoom of the piano roll. It also has a couple tricks, which are shown in the diagram below. \begin{itemize} \item \textbf{In the upper half} of the time-line, the mouse pointer changes to a vertical pointer. Clicking there then shows a vertical bar and a red dot; these mark the starting position of playback. This is useful for reviewing some notes. \item \textbf{In the lower half} of the time-line, the mouse pointer changes to a "finger" icon. Left-clicking there then moves the "L" marker to that point. Right-clicking there moves the "R" marker to that point. ("R" will never precede "L", though). If the \textbf{Loop} button in the main window is active, then playback will loop between the "L" and "R" buttons. This looping now works with both Live and Song modes. \end{itemize} The following figure shows three steps in cursor movement, and the final result for setting a time-signature. \begin{figure}[H] \centering \includegraphics[scale=0.65]{pattern-editor/seqtime-cursor-composite.png} \caption{Setting a Time Signature} \label{fig:pattern_editor_seqtime_cursor_composite} \end{figure} The steps shown are \begin{enumber} \item The mouse cursor is in the piano roll, and has the normal appearance for the current mouse cursor theme. \item The mouse cursor is in the bottom half of the time line, and here it's a pointing finger. When left-clicked, the "L" marker is set there. When right-clicked, the "R" marker is set there. \item The mouse cursor is in the top half of the time line, and is shown as a vertical arrow. A left- or right-click sets the current position in the pattern, and a red vertical line marks that position. \item The time-signature button (the "4/8" shown in the top bar) is clicked, and a time-signature is added at that point. \item The data pane switches to show Time Signatures. These cannot be edited in the data pane, but in the event bar above it, they can be selected by drawing a box around them, and then be either deleted or moved with the Left/Right arrow keys. \end{enumber} Note that the "L" and "R" markers can be selected via the keyboard using their respective shifted key. Once selected, the marker can be moved left or right using the left and right arrow keys. Also note that the default position of the "R" marker is at the end of the fourth measure, so it might not be visible in the pattern editor without scrolling to it. \subsection{Pattern Editor / Piano Roll} \label{subsec:pattern_editor_piano_roll} The piano roll is the center of the pattern/loop/track/sequence editor. When a pattern is opened in the editor, the piano roll scrolls automatically to the first notes. The piano roll is accompanied by a thin "event bar" ("event pane") just below it, and a taller "data pane" or "data area" just below that. While the pattern editor is very similar to note editors in other sequencers, it is a bit different in feel. A mouse with three buttons is helpful for editing, but not necessary. Buttons and keystrokes enhance the ease of editing. The piano roll shows notes, and, optionally, a background pattern or a scale. Notes are shown as narrow rectanges; the background pattern and scale are shown as bars running the length of the piano roll. When the piano roll has keyboard focus, the \texttt{Space} key starts and stops playback, rewinding to the beginning when stopped. The \texttt{.} (period) key starts and pauses playback, without rewinding. The first \texttt{Esc} key stops playback; the second \texttt{Esc} key exits "paint" mode; and the third \texttt{Esc} key closes the pattern editor \textsl{if} the 'usr' \texttt{[pattern-editor] escape-pattern} option is set. This functionality is similar to that of the main window, but these keys are not reconfigurable in the piano roll. One can page vertically in the piano roll using the \index{keys!page-up} \texttt{Page Up} and \index{keys!page-down} \texttt{Page Down} keys. One can go to the leftmost position using the \index{keys!ctrl-home} \texttt{Home} key, and to the rightmost position using the \index{keys!ctrl-end} \texttt{End} key, The mouse scroll wheel can also be used to move the panes around. For implementation reasons, the scroll wheel is active \textsl{only} in the piano roll. \index{keys!arrows} \textsl{If no notes are selected}, the arrow keys will move the piano row up, down, left, and right in small steps. Otherwise, the selected notes are moved up, down, left, and right. \index{keys!hjkl} In addition, the "vi" keys \texttt{h}, \texttt{j}, \texttt{k}, and \texttt{l} will act like the arrow keys. This can be convenient, especially if the arrow-keys are unwieldly. For example, the \textsl{Microsoft Arc} keyboard puts all four arrows on one button! \index{keys!ctrl-arrows} \textsl{If no notes are selected}, then \texttt{Ctrl-Left-Arrow} and \texttt{Ctrl-Right-Arrow} will move the progress bar to the left or right by one snap value. \index{step} \index{note step} With the note-step feature, if one paints notes with the mouse, the note position advances with each click. If one paints notes via an external MIDI keyboard, the notes are painted and the note position advances. To preview notes entered via a MIDI device, click the \textbf{MIDI Thru} button to activate so that they will be passed to the sound generator or software synthesizer. \subsubsection{Pattern Editor / Piano Roll Items} \label{subsubsec:pattern_editor_piano_roll_items} The center of the pattern editor consists of a time panel at the top, a virtual keyboard at the left, a note grid, a vertical scrollbar, an event panel, and a data panel at the bottom. \begin{enumber} \item \textbf{Beat} \item \textbf{Measure} \item \textbf{Virtual Piano Keyboard} \item \textbf{Notes} \end{enumber} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Beat}{piano roll!beat} The light vertical lines represent the beats defined by the configuration for the pattern. The even lighter dotted lines between the beats are useful for snapping notes. \itempar{Measure}{piano roll!measure} The heavy vertical lines represent the measures (bars) defined by the configuration for the pattern. \index{pattern!end marker} Also note that the end of the pattern occurs at the end of a measure, and is marked by a blocky \textbf{END} marker. \itempar{Virtual Piano Keyboard}{piano roll!virtual piano keyboard} The virtual keyboard is a fairly powerful interface. It shows, by shadowing, which note on the keyboard will be drawn. It can be played with a mouse, using left-clicks, to preview a short motif. Every octave, a note letter and octave number are shown, as in "C4". If there is a difference scale in force, then the letter changes to match, as in "F\#5". \index{virtual keyboard!right-click} A right-click in the virtual keyboard area toggles the display between octave-note letters, MIDI note-numbers, and other views. The following figure shows all views, superimposed for comparison. \begin{figure}[H] \centering \includegraphics[scale=1.00]{pattern-editor/pattern-edit-window-key-numbers.png} \caption{Virtual Keyboard Number and Note Views} \label{fig:pattern_editor_key_numbers} \end{figure} \itempar{Notes}{piano roll!notes} Musical notes are indicated in the piano roll by thick horizontal bars with white centers. Each bar provides a visual representation of the pitch of a note and the length of a note. The current scale and background pattern can also be shown in the piano roll. \itempar{Time Scroll}{pattern editor!time scroll} Allows one to pan through the whole pattern, if it is too long to fit in the window horizontally. \subsubsection{Pattern Editor / Note Painting} \label{subsubsec:pattern_editor_note_painting} When we say "editing" in the context of the piano roll, in part we mean that we will "draw" \index{draw mode} \index{mode!draw} \index{paint mode} \index{mode!paint} or "paint" notes. Drawing, modifying, copying, and deleting notes is fairly easy in \textsl{Seq66}, though slightly different from other MIDI sequencers. The \textsl{Seq24} note-editing style is as expected for basic actions such as selecting and moving notes using the left mouse button. Drawing a note or event is a bit different, in that one must first enter the drawing mode ("paint mode"). One way is to \textsl{click and hold} the right mouse button, and then \textsl{click and drag} the mouse to insert notes. (Note that one can use the \textsl{Ctrl-left-click} as a middle click. ) \index{keys!p} Another way is to use the \texttt{p} key to enter the "paint" mode. To get out of the "paint" mode, press the \index{keys!x} \index{keys!Esc} \texttt{x} key while in the sequence editor. The \texttt{Esc} key also exits paint mode if the tune is not playing. Also available is a "finger" button (\textbf{Note Select/Note Entry}) to click to toggle the mode. \index{notes!inserting} Notes are inserted to be at the current length and grid-snap values for the sequence editor for as long as the buttons are pressed while the mouse is dragged. The length of the note will be that specified in the note-length setting (e.g. "1/16"). \index{auto-note} This is the "auto-note" feature. The auto-note feature also works with chord-generation. Notes are inserted only up to the specified sequence length. Once notes are inserted, moving the mouse with the left button still held down moves the notes to the new note value of the mouse. If one releases the left button, then presses and holds it again, more notes will be added in the same way. This is a good way to layer notes in a short sequence. The draw mode has the following features: \begin{itemize} \item Notes are continually added as the mouse is dragged ("auto-notes"). \item Notes cannot be added past the "END" marker of the pattern, which marks the \textbf{Sequence Length in bars} setting. \item As the mouse is dragged while the left button is held in draw mode, notes are either added, or, if already present at that note-on time, are moved up and down. \item If the draw mode is exited, and entered again, then the original notes will not be altered. Instead, new ones will be added. \item Notes can be added while the pattern is playing, and will be heard the next time the progress bar passes over them. \end{itemize} Drawing/painting can also be done while the sequence is playing, and notes will be added to be played the next time the progress bar crosses them. \subsubsection{Pattern Editor / Note Editing} \label{subsubsec:pattern_editor_note_editing} Once notes are in place, whether by recording or using "paint" mode, the piano roll provides a sophisticated set of note-editing actions. But, before editing, one can turn on \index{note!tooltips} "note tooltips", if desired. The thin button, labelled in tiny text as "- Tips -" next to the horizontal scroll bar toggles this mode. When enabled, moving the mouse over a note will show its value, time range in units of B:B:T, and its velocity. This view is quicker than opening up the \textbf{Event Editor}. Onward! \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Event Selection}{event!select} There are various ways to select events and copy, delete, or modify them using the mouse or the keyboard in the piano roll: \begin{itemize} \item \index{keys!ctrl-a} \index{selection!all} \textbf{\texttt{Ctrl-A}}. Pressing the \texttt{Ctrl-A} key will select all of the events in the pattern editor. \item \index{keys!ctrl-e} \index{selection!events by channel} \textbf{\texttt{Ctrl-E}}. Pressing the \texttt{Ctrl-E} key will select all of the events in the pattern editor that have the channel that is selected in the channel dropdown. This selection is useful if one wants to move events from one channel into another pattern. \item \index{keys!ctrl-n} \index{selection!notes by channel} \textbf{\texttt{Ctrl-N}}. Pressing the \texttt{Ctrl-N} key will select all of the notes in the pattern editor that have the channel that is selected in the channel dropdown. This selection is useful if one wants to move notes from one channel into another pattern. \item \index{mouse!left-click} \index{pattern editor!left click} \index{pattern editor!select note} \textbf{Left Click}. Pressing the left button on a note or a event deselects all other notes or events, and selects the item clicked on. The selected note will turn orange (or the configured palette color). \item \index{mouse!left-click-drag} \index{pattern editor!select multiple notes} \textbf{Left Click Drag}. Pressing the left mouse button and dragging also lets one select ("lasso") multiple events and notes. The selected notes will turn orange. Adjustments can be made to one or more notes by selecting one or more notes, and then applying one or more special \index{selection action} "selection actions" to the selection. Be careful! If you \texttt{Ctrl-left-click-drag} on an already-selected note, the drag will change the length of \textsl{all of the notes in the selection}. \item \index{mouse!ctrl-left-click} \textbf{Ctrl Left Click}. Pressing the \texttt{Ctrl} key and the left button on a note or an unselected event \textsl{adds} that event to the selection. \item \index{pattern editor!transpose notes} \textbf{Left Click Drag Selection Up/Down}. To move notes in pitch, once selected, grab one of the notes in the selection and drag it upward or downward. \index{down arrow} \index{up arrow} Also, when a selection is in force, the \texttt{Up} and \texttt{Down} arrow keys will change the pitch of every note in the selection. The smallest unit of pitch change is one MIDI note value. \item \index{pattern editor!move notes in time} \textbf{Left Click Drag Selection Left/Right}. To move notes in time, once selected, grab one of the notes in the selection and drag it leftward or rightward. \index{left arrow} \index{right arrow} Also, since a selection is in force, the \texttt{Left} and \texttt{Right} arrow keys can also be used to change the time of every note in the selection. The smallest unit of time change is the \textbf{Grid snap} value, which might be a 16th note, for example. \item \index{mouse!ctrl-left-click-drag} \textbf{Ctrl Left Click Drag}. \begin{itemize} \item Pressing the \texttt{Ctrl} while left-click-dragging \textsl{on unselected events} lets one make additional selections of multiple events and notes. \item Pressing the \texttt{Ctrl} while left-click-dragging \textsl{on an already-selected event} lets one stretch or compress the lengths of all notes in the selection. % Also achievable via a \textbf{Middle Click Drag}. \index{pattern editor!event stretch} \index{event!stretch} \index{pattern editor!event compression} \index{event!compression} This feature is called \textsl{event stretch} or \textsl{event compression}. Notes can be shortened below the default note length by event compression. There is currently no way to change the length of the note using a keystroke. \end{itemize} \item \index{pattern editor!deselect notes} \index{selection!deselect} \textbf{Deselect Notes} \end{itemize} \index{note!selection box} The selection, copying, and pasting of notes has some minor tricks to remember. When some notes are selected, the effective selection box goes from the first note to the last note, and from the top-most note to the bottom-most note. When pasting the notes, place the mouse cursor so that it lies on the desired row for the top-most note, and on the desired time location for the left-most note. After pasting, be sure to verify the notes in the new location. As shown in the following figure, "ghost notes" will appear as the mouse is moved, to aid in the placement of the notes. This feature is used for note movement and for pasting from the clipboard. \begin{figure}[H] \centering \includegraphics[scale=0.65]{pattern-editor/moving-notes.png} \caption{Note Movement and "Ghost Notes"} \label{fig:pattern_editor_moving_notes} \end{figure} \index{warning!wrap-around notes} \textbf{Warning}: Reducing or increasing the length of a note selection by too much causes the note or notes to "wrap-around" to the end of the pattern boundary and grow more from the beginning of the sequence. If it happens, one probably ought to undo it. The \textbf{Tools} button described in \sectionref{subsec:pattern_editor_second_row} can also be used to modify selections. Once one or more notes are selected, they can be modified in time, pitch, or length, as described above. \textbf{Warning:} \index{warning!down arrow} \index{warning!up arrow} \index{warning!note loss} If one moves the selection too low or too high in pitch, whether with the mouse or the arrow keys, any notes that go below the lowest MIDI pitch or above the highest MIDI pitch \textbf{will be lost}! If done using the mouse, the undo feature (\texttt{Ctrl-Z}) will work. If done using the arrow keys, the undo feature does not work! Be careful, especially if you have a fast keyboard repeat rate! Note that there is no possibility of note loss with a change in time. When a note disappears at one end of the pattern boundary, it wraps around to the other end. Cool. \itempar{Copy/Paste}{pattern editor!copy/paste} Copying, cutting, and pasting is supported by selecting a number of events or notes, and using the \index{pattern editor!cut} \index{keys!ctrl-x} Cut (\texttt{Ctrl-X}), \index{pattern editor!copy} \index{keys!ctrl-c} Copy (\texttt{Ctrl-C}), and \index{pattern editor!paste} \index{keys!ctrl-v} Paste (\texttt{Ctrl-V}) keys. When the notes are selected, \index{pattern editor!delete} \index{keys!del} \index{keys!backspace} one can delete them with the \texttt{Delete} or \texttt{Backspace} key. If the events are \textsl{cut}, using the \texttt{Ctrl-X} key, then they can be pasted, using the \texttt{Ctrl-V} key, then moving the cursor to the desired place, and clicking. One can move the selection box using the arrow keys, to the desired location, and then click to drop the notes at that location. Selected notes that are cut or copied can also be pasted into \textsl{other} pattern editor dialogs; that is, they can be pasted into other sequences. \subsubsection{Pattern Editor / Misc Keys} \label{subsubsec:pattern_editor_misc_keys} Here are some other, less standard,` keys useful in the pattern editor piano roll: \begin{itemize} \item \index{keys!c} \index{selection!repitch} \textbf{\texttt{c}}. Pressing the \texttt{c} key will attempt to use the note-mapper data (provided by a \texttt{*.drums} file) to change the notes in the pattern. This will work only if the pattern is marked as transposable, to add some safety against multiple pitch changes; these are useful once when converting from one drum machine to General MIDI. \item \index{keys!ctrl-d} \index{selection!clear} \textbf{\texttt{Ctrl-D}}. Pressing the \texttt{Ctrl-D} key will clear the pattern clipboard. \item \index{keys!f} \index{selection!edge fix} \textbf{\texttt{f}}. Pressing the \texttt{f} key will attempt to fix wrap-around notes by moving the note. \item \index{keys!ctrl-k} \index{selection!analyze} \textbf{\texttt{Ctrl-k}}. Pressing the \texttt{Ctrl-k} key will analyze all the notes in the pattern to try to guess its scale, as discussed earlier. \item \index{keys!o} \index{recording toggle} \textbf{\texttt{q}}. Pressing the \texttt{o} key will toggle recording for the pattern. \item \index{keys!q} \index{selection!quantize} \textbf{\texttt{q}}. Pressing the \texttt{q} key will quantize the selected notes. \item \index{keys!r} \index{selection!randomize} \textbf{\texttt{r}}. Pressing the \texttt{r} key will quantize the selected notes. \item \index{keys!t} \index{selection!tighten} \textbf{\texttt{t}}. Pressing the \texttt{t} key will partially quantize (tighten) the selected notes. \item \index{keys!u} \index{selection!remove unlinked notes} \textbf{\texttt{u}}. Unlinked notes no longer occur. But.... % Unlinked notes do not occur unless note-wrap-around occurs. % When they do occur, they are painted in magenta. Pressing the \texttt{u} key will remove any unlinked notes found in the pattern. This fix is a stop-gap until we can figure out the best way to prevent unlinked notes while handling recording of notes near the end of the pattern length. \item \index{keys!=} \index{selection!relink notes} \textbf{\texttt{=}}. Pressing the \texttt{=} key relinks any unlinked notes found in the pattern. This causes the notes that are unlinked to be linked, and thus wrap around. \item \index{keys!space (play)} The default keystroke for starting playback is the \texttt{Space} character. \item \index{keys!esc (stop)} The default keystroke for stopping playback is the \texttt{Escape} character. \item \index{keys!period (pause)} The default keystroke for pausing playback is the \texttt{Period} character. \end{itemize} \subsubsection{Pattern Editor / Zoom Keys} \label{subsubsec:pattern_editor_zoom_keys} \index{zoom keys} \index{keys!0} \index{keys!z} \index{keys!shift-z} After a left-click in the piano roll, the \textbf{z}, \textbf{Z}, and \textbf{0} can be used to zoom the piano-roll view \textsl{horizontally}. The \textbf{z} key zooms out (smaller), the \textbf{Z} key zooms in (larger), and the \textbf{0} key resets the zoom to the default value. The horizontal zoom feature also affects the time-line (measures indicator) and the data area. \index{keys!v} \index{keys!shift-v} The note display can also be zoomed vertically. The \textbf{v} key zooms out vertically to make the notes thinner, the \textbf{V} key zooms in vertically to make the notes fatter, and the \textbf{0} key resets the zoom to the value of the "key height" setting in the 'usr' configuration file. \subsection{Pattern Editor / Events Pane} \label{subsec:pattern_editor_events_pane} The narrow (a few pixels high) events strip shows discrete events, such as \texttt{Note On} and \texttt{Note Off}. The events that are shown are selectable in the \textbf{Event} category selector and the "Selector for Existing Data"" drop-downs at bottom left. \index{event strip} These and other events appear as small squares in the event strip, along with a black vertical bar in the \textbf{Data Pane} with a height proportional to the data-value of the event and a numeric representation of that value. The event value (data) editor (directly under the event strip) is used to change note velocities, channel pressure, control codes, patch select, etc. \textsl{We currently recommend being careful of editing or selecting events in that pane (feel free to disobey)}. Note On and Note Off events cannot be inserted in the event strip; it is too easy to screw up. Selection and editing in the events pane is disabled for \textbf{Note On}, \textbf{Note Off}, and \textbf{Aftertouch}. However, when in edit mode (the finger mode), other events, when selected in the event menus, can be inserted by a click in the event pane. Their values can be changed in the data pane. \subsubsection{Pattern Editor / Events Pane / Non-Note Events} \label{subsubsec:pattern_editor_events_pane_non_note_events} \index{events!insert} Other event types (including tempo) can be inserted via the event strip. To do that, first select the kind of event to insert using the \textbf{Event} button in the bottom panel; this process won't work for Note events. Then place the mouse cursor in the event strip and click to give it focus. Do one of the following: \begin{itemize} \item Right-click to make the drawing cursor appear at the exact spot where the event must go. While holding the right button, click the left button. A small square for the event will appear. Or click-drag to insert a series of events, each at a snap value. \item Or press \texttt{i} (insert) or \texttt{p} (paint). to make the drawing cursor appear. Then click the left button or drag to paint as above. \end{itemize} Drawing mode can be exited by releasing the right mouse button or by pressing \texttt{Esc} or \texttt{x} (exit). \textsl{Note: we might, in the future, repurpose \texttt{p} and \texttt{x} for more obvious operations.} Note that the "finger" button \textsl{does not} apply to the event panel; it applies only to inserting notes in the piano-roll. \textsl{Be careful} when using smaller snap values (1/32, 1/64, etc.) to insert events. Move the mouse cursor very slowly, otherwise some snap values might be skipped, leaving missing events. In that case, either remove the events and try again, or use the event editor to add events at the missing tick positions. To move the event(s) to a different time, select it/them via the left button. Then drag the selection left or right as desired. The left and right arrow keys can also be used. \index{todo!high precision events} it is currently not possible to move them to positions smaller than the beat size; temporarily reduce the beat size if desired. Also, for regularly-spaced events, selected events can be hidden when moved into the next non-selected event. The event values can be edited via the data panel, described in the next section. \subsubsection{Pattern Editor / Events Pane / Pattern Fix Button} \label{subsubsec:pattern_editor_events_pane_pattern_fix} A button at the far right of the events panel will bring up the Pattern Fix dialog. See \sectionref{subsec:pattern_editor_pattern_fix}. \subsection{Pattern Editor / Left Buttons} \label{subsec:pattern_editor_left_buttons} Once the events are in place, the next step is to modify the data values of the events as needed. But first, note the buttons at the left. \begin{enumber} \item \textbf{Transpose} \item \textbf{Note Map} \item \textbf{Drum Note Mode} \item \textbf{Chord Generation Reset} \item \textbf{Reserved for Expansion} \end{enumber} % \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \subsubsection{Pattern Editor / Left Buttons / Transposable} \label{subsubsec:pattern_editor_left_buttons_transposable} \index{transposable} \index{pattern editor!transpose toggle} This button toggles the ability of the sequence to be transposed. If transpose is enabled for that pattern, the button will be highlighted as per the current desktop theme. Patterns for drums should, in general, not be transposable. If not set, then note-mapping cannot be applied. \subsubsection{Pattern Editor / Left Buttons / Note Map} \label{subsubsec:pattern_editor_left_buttons_note_map} \index{note map} \index{pattern editor!note map} If the pattern is transposable, then this button is enabled. If clicked, it applies the note-mapper, if set to active in the 'rc' file, to all of the notes in the pattern. See \sectionref{subsec:configuration_drums}. It is most useful for converting percussion from older drum sets to General MIDI drums. Enable transposition, apply the mapping, and then disable transposition to avoid transposing again (e.g. by accident). \subsubsection{Pattern Editor / Left Buttons / Drum Mode} \label{subsubsec:pattern_editor_left_buttons_drum_mode} \index{drum mode} \index{pattern editor!drum mode} Drum mode is a feature adopted from \textsl{Kepler34}. This button changes from normal note mode to drum note mode. In the drum mode, the notes are drawn as small red diamonds without any duration, at the beginning of the note location. This mode treats the events as having a minimal length. This can be confusing if the selected note length is large, so select a small note length. If the drum notes are not placed close enough to the desired location, reduce the snap size. \subsubsection{Pattern Editor / Left Buttons / Chord Generation} \label{subsubsec:pattern_editor_left_buttons_chord_generation} \itempar{Chord Generation}{pattern editor!chord generation} \index{chord generation} This button resets the chord-generation feature to \textbf{Off}. It's located by the data pane in order to save space in the first row. \subsubsection{Pattern Editor / Left Buttons / Blank Button} \label{subsubsec:pattern_editor_left_buttons_blank_button} In order to get the height of the data pane to enlarge, after much head-banging, a button had to be added here for padding. It currently has no other purpose. \subsection{Pattern Editor / Right Buttons} \label{subsec:pattern_editor_right_buttons} This section describes some additional buttons as shown at the top of this chapter (see \figureref{fig:pattern_editor_window_additions}). \begin{enumber} \item \textbf{Toggle Scale/Chord Display} \item \textbf{Toggle Scale/Chord Note Filtering} \item \textbf{Toggle Hex Number Display} \item \textbf{Toggle Showing Grid Numbers Display} \item \textbf{Reserved for Expansion} \end{enumber} \subsubsection{Pattern Editor / Right Buttons / Toggle Scale/Chord Display} \label{subsubsec:pattern_editor_left_buttons_scale_chord_display} A new feature of \textsl{Seq66} is the ability to show the note bars that \textsl{are not} in the selected chord. This feature is similar to the display of the selected musical scale (See \sectionref{subsubsec:pattern_editor_second_row_musical_scale}.) One difference is that the display of the scale has priority over the display of the chord. This button gives the user the option to show or not show the scale or chord. \subsubsection{Pattern Editor / Right Buttons / Toggle Scale/Chord Note Filtering} \label{subsubsec:pattern_editor_left_buttons_scale_chord_filtering} When this button is active (checked) and a scale has been selected, all notes painted into the piano roll are restricted to notes in the scale. Otherwise, any note value can be painted. When this button is active and a chord has been selected, the insertion of a full chord is disabled, but all notes painted into the piano roll are restricted to notes in the chord. Otherwise, painting a note paints a chord as per previous behavior. \subsubsection{Pattern Editor / Right Buttons / Toggle Hex Number Display} \label{subsubsec:pattern_editor_left_buttons_hex_number_display} When this button is active (checked), some of the values in the data pane will be displayed in hexadecimal format. This is most useful when controller numbers are being displayed. \subsubsection{Pattern Editor / Right Buttons / Toggle Showing Grid Numbers Display} \label{subsubsec:pattern_editor_left_buttons_scale_chord_filtering} When this button is active (checked), the 5 level numbers are display at the left of the data pane. Toggle this button for a clearer view of numbers at the left. \subsection{Pattern Editor / Data Pane} \label{subsec:pattern_editor_data_view} Now on to the \textbf{Data Pane} itself, also known as the "data panel". The events that are shown in this pane are selectable in the \textbf{Event} category selector and the "Selector for Existing Data"" drop-downs at bottom left. \index{data pane} \textbf{Modify Event Data} offers a way to alter the event data values. Many different events can be altered in the data pane: Note On and Note Off velocities, program changes, aftertouch, channel pressure, pitch wheel, and tempo. \index{karaoke} \index{text events} Text events are also displayed (useful in karaoke), but they cannot be edited in this pane. (Instead see the \textbf{Session} tab's \textbf{Song Info} controls and the \textbf{Event Editor}.) The events values for the currently selected category of events are shown in this window as vertical lines of a height proportional to the value, \index{data!grab handle} topped by a circular grab handle. The exceptions are program changes and tempo, which are shown by small circles, red for program change and yellow for tempo. Also, the range of tempos in the data panel is set to match the \index{usr!bpm-minimum} \texttt{usr!bpm-minimum} and \index{usr!bpm-maximum} \texttt{usr!bpm-maximum} settings in the 'usr' file. This range is for display purposes. See \sectionref{subsubsec:usr_file_user_midi_settings}. \subsubsection{Pattern Editor / Data Pane / Editing} \label{subsubsec:pattern_editor_data_view_editing} These values can be easily modified by \index{mouse!left-click-drag} left-click-dragging the mouse past each line, to level it off at the given value. Easier to try it than explain it. \index{mouse!right-click-drag} Right-click-drag also works the same. \index{modify event-data} When notes are \textsl{selected}, and the mouse is used to change the values (heights) of the lines in the event-data area, \textsl{only the events that are selected} are changed. The data-values of \textsl{unselected} events are left unchanged. A cool feature from \textsl{Seq24}. Also, as the mouse passes over the data pane, events near the mouse acquire a grab handle, which can be used to select the event and modify its value by moving the mouse up or down. Note that some events are shown as small circles instead of a line; each circle has the numeric value next to it. \subsubsection{Pattern Editor / Data Pane / Pitchbend Events} \label{subsubsec:pattern_editor_data_view_pitchbend_events} \textbf{Pitchbend} events are shown as a vertical lines that go above and below the midline. The only way to modify pitch bend events here is by dragging a line. \begin{figure}[H] \centering \includegraphics[scale=0.75]{pattern-editor/data-pane-pitchbend.png} \caption{Data Pane Pitch Bend} \label{fig:pattern_editor_data_pane_pitchbend} \end{figure} Note that this figure has some new features not represented in the figures that follow. First, the data pane has been expanded slightly vertically so that the event grab handle circles can be used for dragging values near 0 or 127. Second, top and bottom horizontal lines show the limits of the data values. This includes numerical values shown at time 0. For the pitchbend value range, the values range from -2 to +2 semitones. \textsl{Seq66} currently does not know if an event has changed the pitchbend range for a given bus/channel. Third, a blank button at the left has been add to allow a greater height of the data pane. This kludge is needed because we don't completely grok the vagaries of Qt layouts. \subsubsection{Pattern Editor / Data Pane / Tempo Events} \label{subsubsec:pattern_editor_data_view_tempo_events} \textbf{Tempo} events are shown as a small yellow circle. This circle can be grabbed and moved up or down. \begin{figure}[H] \centering \includegraphics[scale=1.0]{pattern-editor/data-pane-tempo.png} \caption{Data Pane Tempo} \label{fig:pattern_editor_data_pane_tempo} \end{figure} A new feature is the ability to add tempo events in the data pane. To add a string of tempo events, first select \textbf{Tempo} from the \textbf{Event} dropdown. Move to the data pane, press and hold \texttt{Ctrl} and then left-click-drag the mouse to form a line as shown here. \begin{figure}[H] \centering \includegraphics[scale=1.0]{pattern-editor/data-pane-tempo-0.png} \caption{Data Pane Tempo Line Draw} \label{fig:pattern_editor_data_pane_tempo_line_draw} \end{figure} Note that there are no events shown yet. While still holding \texttt{Ctrl}, release the left mouse button, and the tempo events appear. Release the \texttt{Ctrl} key. If this line of tempos is fine, right-click to lock them. \begin{figure}[H] \centering \includegraphics[scale=1.0]{pattern-editor/data-pane-tempo-1.png} \caption{Data Pane Tempo Events Drawn} \label{fig:pattern_editor_data_pane_tempo_events_drawn} \end{figure} Otherwise, move the mouse around to change the tempo events, as shown here. Use the right-click to exit this mode. \begin{figure}[H] \centering \includegraphics[scale=1.0]{pattern-editor/data-pane-tempo-2.png} \caption{Data Pane Tempo Events Altered} \label{fig:pattern_editor_data_pane_tempo_events_altered} \end{figure} Again, note that the range of the tempo events is determined by the BPM minimum and maximum values specified in the 'usr' file. There is currently no \textbf{Preferences} setting for these values. Also note that, to delete tempo events in the pattern editor, select the events in the thin event pane, then press either \texttt{Delete} or \texttt{Ctrl-X}. They can also be deleted (slowly) in the event editor. Tempo event values can be modified by dragging the circle up and down. \subsubsection{Pattern Editor / Data Pane / Program/Patch Events} \label{subsubsec:pattern_editor_data_view_program_patch_events} \textbf{Program Change} events are shown as a small open circle with a numeric value and the default name of the program/patch. These names can be changed with a \texttt{.patches} file. See \sectionref{subsubsec:configuration_rc_patches}. Program Change event values can be modified by dragging the circle representing the event up or down. \begin{figure}[H] \centering \includegraphics[scale=1.0]{pattern-editor/data-pane-program.png} \caption{Data Pane Program Change} \label{fig:pattern_editor_data_pane_program_change} \end{figure} \textsl{Seq66} has added \textsl{Seq32}-style grab-handles to Note events in the data panel. They are shown only if an event is selected, or if the mouse is on top of the event line. As the mouse moves through the data panel, the grab-handles are shown as circles at the top of each line. If the line is clicked, it is selected. If Ctrl-clicked, the selected line is added to the selection. If an empty spot in the data pane is clicked, all events are unselected. When present, the circular grab-handle can be clicked-and-held and moved up and down to change the event value. This is true for tempo and program change events as well. \begin{figure}[H] \centering \includegraphics[scale=1.0]{pattern-editor/grab-handles.png} \caption{Data Pane Circular Grab-Handles} \label{fig:pattern_editor_data_pane_note_grab_handles} \end{figure} \index{event data editor!mouse wheel} Any events that are selected in the piano roll or event strip can have their values modified with the mouse wheel. Data values can also be modified using the \textbf{LFO} dialog (see below). \subsection{Pattern Editor / Bottom Row} \label{subsec:pattern_editor_bottom} The bottom row of the pattern editor provides for selecting events for viewing and editing, MIDI playback, pass-through, and recording. \begin{enumber} \item \textbf{Event Category Selector} \item \textbf{Selector for Existing Data} \item \textbf{Selected Event Name} \item \textbf{LFO Dialog} \item \textbf{Live Loop Count} \item \textbf{Armed/Muted Toggle} (Data To MIDI Buss) \item \textbf{MIDI Thru Toggle} \item \textbf{MIDI Record Toggle} \item \textbf{MIDI Record Quantized} \item \textbf{Loop Record Type} (Overdub, Replace, Expand, One-shot) \item \textbf{Record Velocity and Reset} \end{enumber} \subsubsection{Pattern Editor / Bottom Row / Event Selection Menus} \label{subsubsec:pattern_editor_bottom_event_selection_menus} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Event Category Selector}{pattern editor!event selector} This button brings up the following context menu, so that the user can select the category of events to view and edit. It shows all possible MIDI event categories, including Text events. \begin{figure}[H] \centering \includegraphics[scale=0.75]{pattern-editor/event-context-menu.png} \caption{Pattern Editor Event Button Context Menu} \label{fig:pattern_editor_bottom_event_context_menu} \end{figure} Note the squares. Some might be filled (black), most are empty. Filled squares indicate that the sequence has some events of that type. Otherwise, there are no such events in the sequence. Useful in deciding if it is worth selecting the event. The sub-menus of this context menu show 128 MIDI controller messages. The default names of these messages are shown. They also use the squares to indicate if there are any events of the type shown in the menu. These sub-menus can be modified by editing the 'usr' file: \begin{verbatim} $HOME/.config/seq66/seq66.usr \end{verbatim} to make it match one's instrument. \itempar{Existing Event Menu}{pattern editor!existing events} The existing-event selector is a small button (with a black-square icon) that brings up a menu with only existing events shown. Unlike the event-selector described above, this menu shows only the actual events existing in the track, for quicker selection. \itempar{Event Selection}{pattern editor!event selection} Shows the selection event, with its event number shown in hexadecimal notation, and the name of the event shown. \subsubsection{Pattern Editor / Bottom Row / LFO Button} \label{subsubsec:pattern_editor_bottom_lfo_button} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{LFO Dialog}{pattern editor!LFO} An LFO (low-frequency oscillator) allows data events to be modulated by some rudimentary wave functions. See \sectionref{subsec:pattern_editor_lfo_panel}. \subsubsection{Pattern Editor / Bottom Row / Loop Control} \label{subsubsec:pattern_editor_bottom_loop_control} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Live Loop Count}{pattern editor!live loop count} Normally, in Live mode, a pattern plays endlessly if left alone. If this counter is set to a value greater than 0, then the pattern will loop only that number of times in Live mode. For example, if set to 1, then the pattern acts like a "one-shot" loop. This can save having to use queuing quickly to handle an intro phrase. To loop \textsl{endlessly}, set this value to 0. Also set it to 0 when playing the MIDI tune in \textbf{Song} mode. Otherwise, weird behavior will be observed. \itempar{Armed/Mute Toggle}{pattern editor!data to midi buss} This button causes the pattern to be output to the selected MIDI output buss, which will normally be connected to a software or hardware synthesizer, to be heard. This item performs muting/unmuting (disarming/arming) in the same way a pressing the corresponding pattern button in the \textbf{Live} frame. \itempar{MIDI Thru Toggle}{pattern editor!midi data pass-through} This button routes incoming MIDI data through \textsl{Seq66}, which then writes it to the MIDI output buss. When a new pattern editor is opened, and the new-editor-editor settings (\sectionref{paragraph:user_file_added_options_pattern_editor}) are false, one can click the \index{thru} \textbf{Thru} button first to redirect MIDI controller input to the synthesizer port, and have it be heard, without arming the pattern or turning on MIDI Record. Note, though, that if MIDI Record is toggled on and off, the Thru function is effectively disabled. To restore it, toggle the Thru off, then on, again. Also note that Thru will remain enabled when the pattern editor is closed. \textsl{Warning:} If Thru is active, and the output port for the pattern is the same as the input port, then the struck note will be repeated for as long as recording is active. \itempar{MIDI Record Toggle}{pattern editor!record midi data} This button routes incoming MIDI data into \textsl{Seq66}, which then saves the data to the sequence, and also displays the new information (notes) in the piano roll view. Note that \textsl{Seq66}'s pattern grid can be put in various recording modes (e.g. overdub/merge versus overwrite) where, instead of muting/unmuting the patterns, it turns on recording (without opening the pattern editor). If expand-recording is active, this button shows double arrows, the second arrow indicating expandability. \itempar{MIDI Record Quantized}{pattern editor!quantized record} This button should be called "MIDI Record Altered", as it now supports more than just quantization. With every click, this button changes the type of alteration, cycling between these changes: \begin{enumber} \item \textbf{None}. No alteration of the recorded input is done. The button shows as unselected. \item \textbf{Tighten}. The recorded input will be lightly quantized. \item \textbf{Quantize}. The recorded input will be fully quantized. The quantization is to the current snap value. \item \textbf{Notemap}. If a note-map (see the 'drums' file) is loaded, the remapping of notes is applied on the fly. \end{enumber} Alteration can be turned on globally in \textsl{Seq66}'s pattern grid as well. When the pattern is opened, the alteration and recording style are set for that pattern. \textsl{Currently, changing the alteration and recording style in the grid changes the open pattern as well. We are not sure if that is a good feature or not.} \itempar{Loop Record Type}{pattern editor!recording type} In \textsl{Seq24}, the pattern recording worked by merging new notes played as the pattern to be recorded was looped. This method allows a loop to be built up bit-by-bit. \textsl{Seq66} adds two more methods from Stazed's \textsl{Seq32} project. The three methods are: \begin{enumber} \item \textbf{Overdub}. \index{merge} \index{overdub} \index{recording!merge} \index{recording!overdub} This is the \textsl{Seq66} standard style of recording loops, where played notes will accumulate as the loop repeats. % See \sectionref{paragraph:patterns_recording_modes} Note that this item used to be called "Merge". \item \textbf{Overwrite.} \index{overwrite} \index{recording!overwrite} When the loop starts over, and a note is pressed, then the existing notes in that loop are erased first, then the new notes are added. This provides a good way of correcting major mistakes, live. It will not work if adding notes while recording is on, but play is stopped.. This mode can cause incomplete notes if one holds the note and releases it in the next iteration, leaving a partially-drawn note behind. The workaround is to try again. \item \textbf{Expand}. \index{expand} \index{recording!expand} Expansion operates when playing and recording. Once the end of the loop is near, \textsl{whether or note notes are being input or not}, another measure is added to the length of the loop. This continues indefinitely while recording and play are active. In this mode, the record-button icon shows double arrows to indicate expand-recording. Once recording is done, the length can be reduced by typing it into the length combo-box at the top of the editor; if it will cut off recorded notes, a warning will occur. \index{recording!expand issue} This works best in \textbf{Live} mode. In \textbf{Song} mode, only one measure is added each time play is started, and playback stops when the song is complete. A workaround is to add a long track, perhaps empty, to the song. Awaiting user complaints to decide what to do. \item \textbf{Oneshot}. \index{oneshot} \index{recording!oneshot} There are two modes in one-shot recording: when play is stopped, auto-step recording is in force; when play is moving recording is normal, but stops at the end of the pattern. \begin{itemize} \item \textbf{Play stopped.} When this option is set, with the record button on, and no pattern playing, recording won't start until a note comes in, no matter how many repetitions of the loop have gone by. The progress bar starts at the left (time 0). When the first note comes in, the end of the pattern is marked. As each new set of notes at the same timestamp come in, the notes are recorded and the current time advances by one snap value. The length of each note is determined by the snap size for the spacing of drawn events. At the end of the pattern, recording stops automatically. See the figure below for a recording from a \textsl{Yamaha DD-11}. This is a useful way to record a pattern from a machine. \item \textbf{Play in progress.} With the patterns playing, nothing happens until the first note to be recorded is struck, even if many repeats of the pattern's length occurs. Once the first note is struck, the end of the current loop is marked. The notes are recorded until that end of the current loop. After that, no more recording in that pattern takes place. \end{itemize} \item \textbf{Oneshot Reset}. \index{oneshot} \index{recording!oneshot reset} One-shot reset is supposed to allow another one-shot recording, with the new events merged. It does not work yet; it overdubs during multiple loop repeats. \end{enumber} Remember that \textsl{Oneshot recording} is a kind of auto-step/step-edit recording, and that auto-step/step-edit can also be done by click-dragging the mouse. Do not confuse it with \textsl{Oneshot playback}. \begin{figure}[H] \centering \includegraphics[scale=0.65]{pattern-editor/oneshot-recording.png} \caption{One-Shot Pattern Recording} \label{fig:pattern_editor_oneshot_recording} \end{figure} In the figure above, we set up to record input from the port attached to a \textsl{Yamaha DD-11} drum machine. After some trial and error, we set: \begin{itemize} \item \textbf{Length} to 2 measures (see the red text in the figure). \item \textbf{Snap} to 1/8th, which sets the spacing of drawn notes as well. This determines the position of each new note. \item \textbf{Note Length} to 1/16th, which sets the length of drawn notes as well. \item \textbf{Recording Type} to \textbf{Oneshot}. \item \textbf{Recording} on. This is the button with the MIDI DIN connector pins going into the blue vertical rectangle. \end{itemize} Also, the \textbf{BPM} was set to 124 in the main window, to match the "39" tempo in the DD-11, which maps to 124 bpm. Finally, we picked the \textsl{Pop Rock 1} style. Once this setup was in place, clicking the DD-11 Start/Stop button started recording automatically at time 0, and it stopped automatically at the length/end of the pattern. \index{auto-step} \index{step-edit} \index{recording!auto-step} \index{recording!step-edit} \index{auto-step!recording} \index{step-edit!recording} Why is the snap used instead of the note-length? Because we're using the auto-step (step-edit) recording feature... the snap determines where the next note begins, and the length determines the length of the note to create. However, the note-length is a property of the piano roll, not the pattern itself. The pattern uses the snap-length during auto-step/step-edit. This kind of note recording can be done in two ways: \begin{enumerate} \item Recording events via auto-step. \begin{enumerate} \item Turn on recording without the pattern playing. \item If desired, set the "L" marker to a start time other than 0. \item Activate the MIDI device's play or play some notes. \end{enumerate} \item Drawing events with the mouse. \begin{enumerate} \item Left-click-drag the mouse to enter notes. \item Note-drawing stops when at end of the pattern. \item Dragging back and forth will draw more notes. \end{enumerate} \end{enumerate} Now, the DD-11 is an old instrument from the pre-General-MIDI days. So, in order to play back this pattern on something like \textsl{QSynth} or \textsl{Hydrogen}, we need to re-map the notes to GM drum notes. In the 'rc' file, the proper note-mapper file is specified: \begin{verbatim} [note-mapper] active = true name = "GM_DD-11.drums" \end{verbatim} We copy the recorded pattern and paste it into another slot for safety. We click the \textbf{Transposable} button for that pattern to enable the \textbf{Map} button. Then we click the \textbf{Map} button, and the notes shift. We click the \textbf{Transposable} button again to disable transposing, and save the file. Playing it into channel 10 of \textsl{QSynth} shows that it sounds a lot like the original DD-11 drums. \subsubsection{Pattern Editor / Bottom Row / Note Velocity} \label{subsubsec:pattern_editor_bottom_note_velocity} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Vol}{pattern editor!vol} This button resets the volume (velocity) of note recording to the \textbf{Free} setting. See the next item. \itempar{Velocity}{pattern editor!velocity} This dropdown allows setting the volume of the recording to either the incoming velocity or to the specified velocity. The velocity values are shown at the right side of each menu entry. These values correspond to MIDI volume levels from 127 down to 16. If the \textbf{Free} item is selected, then the incoming note velocity is preserved. \subsection{Pattern Editor / Pattern Fix Dialog} \label{subsec:pattern_editor_pattern_fix} This dialog provides for a number of overall modifications of a pattern. Before we start, some obvious limitations. Applying a fix, then deciding to change the fix, requires one to click the \textbf{Reset} before applying the next change. Now, if the change does not look correct, one can close the pattern editor and reopen it to see if the change took hold correctly. Also, once a change is made, the modify flag (asterisk) is shown, but currently a reset cannot remove that flag. \begin{figure}[H] \centering \includegraphics[scale=0.75]{pattern-editor/patternfix-randomize-pitch-setup.png} \caption{Pattern Fix Dialog} \label{fig:pattern_editor_pattern_fix} \end{figure} \index{pattern-editor!fix} When this dialog is used, the time-stamp of \textsl{every event} in the pattern can be changed. Many other changes can be made as well. Once selected, the pattern number is shown in the title-bar and in a read-only text field. The following sections describe the dialog. For more details, go to the source-code bundle, enter the \texttt{contrib/notes/} directory, and examine the \texttt{pattern-fix-test.text} file. \subsubsection{Pattern Editor / Pattern Fix / Length Change} \label{subsubsection:patterns_fix_length_change} The \textbf{Length Change} selector allows for the following actions: \begin{itemize} \item \textbf{None}. No length change will occur. Alignment, reversing, and quantization can still be done. \item \textbf{Measures/Time Sig}. Allows the measures (length) of the pattern to be changed in three ways, depending on how the number is entered: as an integer or floating-point number (e.g. 1, 1.0, 1.75), or as a simple time-signature fraction (e.g. "3/4"; the "/" is a key indicator of this kind of change). In some ways it is similar to \textbf{Scale Factor}, described below. \begin{itemize} \item \textbf{Measures}. Entering an integer or a floating-point scale factor will scale the pattern events up or down, and change the number of measures. The final number of measures is always an integer. If the measure value is less than \texttt{1.0}, the pattern will only be compressed. If less than 1 measure in length, the result will be 1 measure; \textsl{Seq66} cannot allow less than 1 measure. \textsl{Note that, unlike setting the measures in the pattern editor itself, this measures change will \textbf{not} truncate notes.} If the number of measures is greater, then the notes will be expanded. \item \textbf{Time Signature}. Entering a fraction such as \textbf{3/4} or \textbf{12/8} will change the time signature of the pattern and scale the pattern down or up appropriately. This action can also change the length of the pattern. \end{itemize} \item \textbf{Scale Factor}. Scales the pattern. For example, 2.0 doubles the length, and 0.5 halves the length. In some ways it is similar to \textbf{Measures}, described above. The \textbf{Measures} is changed to be enough to contain the new length, but only if the scale is greater than 1.0. One thing to be aware of is that, if the pattern is expanded, the new measure count depends on the location of the last event in the expanded pattern. \end{itemize} \subsubsection{Pattern Editor / Pattern Fix / Other Fixes} \label{subsubsection:patterns_fix_other_fixes} \textbf{Other Fixes} provides some minor items, as follows. \begin{itemize} \item \textbf{Align left}. Aligns the pattern to the left (i.e. the beginning) of the sequence, removing any delays. Useful when recording a pattern live and not quite hitting the first beat in time. \item \textbf{Align right}. Align the pattern to the right of the sequence; the last note ends at the end of the sequence. \item \textbf{Reverse measures}. Reverses all of the events in the whole pattern, flipping the measures completely. For example, if all the events occur near the end of the pattern, after reversal they will all appear near the beginning of the pattern. \item \textbf{Reverse in place}. This is probably a more natural reversal method. The relative location of a cluster of notes doesn't change, but the notes are simply reversed in place. \item \textbf{Preserve note length}. Prevents Note Offs from being scaled, which shortens or lengthens the notes, and in some case might cause notes to bleed past a measure. When scaling down, it is usually better to leave this option off. \end{itemize} \subsubsection{Pattern Editor / Pattern Fix / Alteration} \label{subsubsection:patterns_fix_alteration} The \textbf{Alteration} selector allows for various types of quantization, randomization, and note-mapping. It works similarly to the \textbf{Tool} entries of the same name (see \sectionref{subsec:pattern_editor_second_row}), but works on \textsl{all events}, not just selected events. Also included is a \textbf{Jitter} option, with a range for the jitter. This option slightly randomizes the time-stamps of note events. \begin{itemize} \item \textbf{None}. No "alteration" process is done. \item \textbf{Tight Q ticks}. Quantizes the note times by the amount shown (MIDI ticks) in the text-field next to it. Normally, one wants this to be half of the next value described. \item \textbf{Full Q ticks}. Quantizes the note times by the amount shown (MIDI ticks) in the text-field next to it. Normally, one wants this to be the size of a note, for example, a 16th note, which is 48 ticks by default. \item \textbf{Random amplitude}. Randomizes the velocity of notes by plus-or-minus the number of units specified in the text-field. \item \textbf{Random pitch}. Randomizes the pitch of notes by plus-or-minus the number of units specified in the text-field. The notes adhere to the musical scale set in the pattern editor, including the chromatic scale, which includes all twelve notes in an octave. See \figureref{fig:pattern_editor_pattern_fix}, which shows the setup for randomizing the pitches, plus the pattern to be randomize. \item \textbf{Time/tick jitter}. Randomizes the timing of each note by plus-or-minus the number of MIDI ticks shown in the text-field. Most useful to humanize a too-rigid rhythm. \item \textbf{Note-map}. Remaps the notes according to a \texttt{.drums} or \texttt{.notemap} file (both are identical in concept and layout). Unlike the one-the-fly note-mapping capability (where the note-map is created at the startup of the application), this is a temporary note map. The note-map file is read, all notes are re-mapped, and the note-map goes away. By default the note-map file is the one specified in the 'rc' file, though it need not be active for this operation. Clicking on the \texttt{...} allows one to select another file to use (but only for a pattern-fix). The note-map can be used to remap drum notes, or to produce special note-remappings, such as inversion. \item \textbf{Rev Note-map}. This is the same as note-mapping, but maps in the opposite direction. \end{itemize} % Question: would a vertical "pattern flip" (of the note values themselves) % be useful? This figure shows the result of randomizing notes to adhere to a Phrygian scale based on C. \begin{figure}[H] \centering \includegraphics[scale=0.75]{pattern-editor/patternfix-randomize-pitch-result.png} \caption{Result of Pattern Fix Note Pitch Randomization} \label{fig:pattern_editor_pattern_fix_pitch_randomization} \end{figure} \subsubsection{Pattern Editor / Pattern Fix / Applied Effects} \label{subsubsection:patterns_fix_applied_effects} \textbf{Applied Effects} does not do anything except show the effect of the changes. The fix process followed is: \begin{enumerate} \item Apply the left/right-alignment (if selected). \item Modify the number of measures (if selected). \item Do the scaling (if selected). \item Perform the quantization/tightening (if selected). \end{enumerate} The presumed effect of the change is shown, if applicable. Note that changing the measure in this dialog is different from changing the measures in the "length" dropdown, which only changes the measures. Changing the measures to a large number on a pattern of 1 measure will greatly expand the notes! Any accidental changes can be \textbf{Reset} in this dialog. Undo in the pattern-editor itself with \texttt{Ctrl-Z} affects only MIDI events, not things like the time-signature from the drop-downs. \subsection{Pattern Editor / LFO Dialog} \label{subsec:pattern_editor_lfo_panel} The LFO dialog allows for modulating note velocities and the control values of the event type in shown in the data pane. It can even modulate tempo and pitch-bend events. One recent change in this dialog is that selecting an option no longer resets (removes) the current changes. The \textbf{Reset} button must be clicked to do this. By clicking on the \textbf{LFO} button or using the \texttt{Ctrl-L} key, the following window appears, with a set of 4 vertical sliders: \begin{figure}[H] \centering % \includegraphics[scale=0.65]{pattern-editor/lfo.png} % \includegraphics[scale=0.50]{pattern-editor/lfo-2.png} \includegraphics[scale=0.50]{pattern-editor/lfo-3.png} \caption{Pattern Editor LFO} \label{fig:pattern_editor_bottom_lfo} \end{figure} The user-interface items function as follows. \begin{enumber} \item \textbf{Use Measure (vs Length)}. The length of a waveform period can be determined by the length of the pattern (2 measures as shown next to the label "LFO Data Editor") or by the length of a measure. This check-box determines that. Especially useful in modifying long patterns. \item \textbf{Multiply}. By default, the LFO waveform replaces the existing event "amplitude" values directly. With this option checked, the values of the existing events are \textsl{multiplied} by the waveform. One issue is that applying wave types multiple times tends to move all values toward zero. \item \textbf{DC Offset} (\textbf{D}). Provides a kind of DC offset for the data value. Starts at 64, and ranges from 0 to 127. In the diagram above, one can see that the "0-value" line for the sine wave is at 64. \item \textbf{Depth} (\textbf{R}). Controls the depth of modulation; i.e. the modulation range. Starts at 64, and ranges from 1 to 127. The data values range from \( y = DC - R \) to \( y = DC + R\). % For the sine-wave shown above, the range is 0 to 128 (actually 127). \item \textbf{Periods}. Indicates the number of periods per pattern length. For long patterns, this parameter should be set high, to even show an effect. It is also subject to an 'anti-aliasing' effect, especially for short patterns. Try it! \item \textbf{Phase} (\textbf{P}). Provides the phase shift within a period of the LFO wave. A value of 1 is a phase shift of 360 degrees (one whole period). Thus, the data at \( P = 0 \) would look exactly the same at phase \( P = 1\). \item \textbf{Wave Type}. Selects the kind of wave to use for the LFO: \begin{enumber} \item \textbf{None}. This setting is the initial setting. If selected after selecting on of the wave types below, all it does is reset the data, like the \textbf{Reset} button. \item \textbf{Sine}. Modulates via a sine wave. Change the \textbf{Phase Shift} to get a cosine function. If the \textbf{DC Offset} is below 64, then negative values are converted to positive. As an example, setting the DC offset to 0 will produce the absolute value of the \texttt{sin()} function. \item \textbf{Sawtooth}. Provides a ramp modulation. \item \textbf{Reverse Sawtooth}. Provides a ramp modulation in the opposite direction. \item \textbf{Triangle}. Modulates via a triangle wave; somewhat similar to a sine wave. \item \textbf{Exponential Rise}. Provides an exponential ramp modulation. This ramping is easiest to see if the DC offset is 0, and the Mod range is 127. Varying the \textbf{Phase} with the slider will move the peak of the curve left to right. \item \textbf{Exponential Fall}. Provides an exponential ramp modulation in the opposite direction. \item \textbf{DC Only}. Sets only the DC offset of the existing events. Does not work with the "Multiply" option. Problematic with pitch-bend events. One issue is that some precision in pitch-bend is lost as one changes the DC offset up and down, until clipping occurs. \end{enumber} Note that one might have to change one or more parameters slightly to see the effect of the new waveform. \item \textbf{Reset}. This button restores the initial pattern event data or the data stored by the \textbf{Lock} button. Useful when one applies modulation that one ultimately does not like. \item \textbf{Lock}. This button copies the current pattern changes to the backup data used by the \textbf{Reset} button. \item \textbf{Close}. Closes the LFO panel. \end{enumber} In addition to the \textbf{Reset Data} button, Ctrl-Z can be applied multiple times to undo changes one at a time. Every motion of a control causes a complete change. \subsection{Pattern Editor / Common Actions} \label{subsec:pattern_editor_common} This section is a catch-all for actions not described above. \subsubsection{Pattern Editor / Common Actions / Scrolling} \label{subsec:pattern_editor_scrolling} \textsl{We still need to work out whether or not to use the scroll wheel. in Seq66, as we need to keep multiple event panes in sync while scrolling.} Let us describe the actions that can be performed with a scroll wheel, or with the scrolling features of multi-touch touchpads. There are three major scrolling actions available when using mouse scrolling, with the mouse hovering in the piano-roll area: \begin{itemize} \item \textbf{Vertical Panning (Notes Panning)} \index{scroll!normal scroll} \index{scroll!vertical pan} \index{scroll!notes pan} \index{pan!seqroll notes} Using the vertical scroll action of a mouse or touchpad moves the view of the sequence/pattern notes up and down. One can also click in the piano roll, and then use the \texttt{Page-Up} \index{keys!page-up} and \texttt{Page-Down} \index{keys!page-down} keys to move the view up and down in pitch. \item \textbf{Horizontal Panning (Timeline Panning)} \index{scroll!shift scroll} \index{scroll!horizontal pan} \index{scroll!timeline pan} \index{pan!seqroll time} Holding the Shift key, and then using the vertical scroll action of a mouse or touchpad moves the view of the sequence/pattern time forward and backward. One can also click in the piano roll, and then use the \texttt{Shift Page-Up} \index{keys!shift page-up} and \texttt{Shift Page-Down} \index{keys!shift page-down} keys to move the view left and right in time. \item \textbf{Horizontal Zoom (Timeline Zoom)} \index{scroll!ctrl scroll} \index{scroll!horizontal zoom} \index{scroll!timeline zoom} \index{zoom!seqroll time} Holding the Ctrl key, and then using the vertical scroll action of a mouse or touchpad zooms the view of the sequence/pattern time to compress it or expand it. One can also click in the piano roll, and then use the \texttt{z} \index{keys!z}, \texttt{Z} \index{keys!Z}, and \texttt{0} \index{keys!0} keys to change the timeline zoom. \item \textbf{Vertical Zoom (Notes Zoom)} \index{scroll!vertical zoom} \index{scroll!notes zoom} \index{zoom!seqroll notes} Additional buttons for vertical zoom have been added: \textbf{-}, \textbf{0}, and \textbf{+}. One can also click in the piano roll, and then use the \texttt{v} \index{keys!v}, \texttt{V} \index{keys!V}, and \texttt{0} \index{keys!0} keys to change the notes zoom. The zoom can make the note-rows large enough to use on a touch screen. \end{itemize} The actions of this scrolling are smooth and fast. If an event is selected in the piano-roll area or the (thin) event area, then the scrolling increases or decreases the value of the event. In the case of a note, this increases or decreases the velocity of the note. For all events, this increases or decreases the length of the vertical line that represents the value of the event. \subsubsection{Pattern Editor / Common Actions / Close} \label{subsec:pattern_editor_close} \index{window!close} There is no \textbf{Close} button in the pattern editor. One can use window-manager actions, such as clicking on the \textbf{X} button of the window frame, or pressing the exit key defined in the window manager. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/patterns_panel.tex ================================================ %------------------------------------------------------------------------------- % patterns_panel %------------------------------------------------------------------------------- % % \file patterns_panel.tex % \library Documents % \author Chris Ahlstrom % \date 2015-08-31 % \update 2025-10-26 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides the concepts. % %------------------------------------------------------------------------------- \section{Patterns Panel} \label{sec:patterns_panel} The main window of \textsl{Seq66} is where one creates a set of patterns (a "screen-set"), manages the configuration, controls the playback rate, adds tempo events, and opens the pattern, song, event, mute-groups, or playlist editors. \index{Patterns Panel} \index{Live Grid} The \textbf{Patterns Panel}, also called the \textbf{Live Grid}, is the center of the \index{main window} \textbf{main window} of \textsl{Seq66}. It is also referred to as the "Live Grid", where each pattern is presented in a "slot". See \figureref{fig:main_screen_annotated}; see \sectionref{sec:live_grid}. \index{drag and drop} In addition, an external MIDI file can be dragged onto this grid, and dropped, to easily open a MIDI file: \begin{figure}[H] \centering \includegraphics[scale=0.75]{main-window/live-grid-drag-n-drop.png} \caption{Patterns Panel Drag-and-Drop} \label{fig:patterns_panel_drag_n_drop} \end{figure} % This drag-and-drop can be done even with "external" pattern editors. If a modified file is loaded, the user will be prompted to save, discard, or cancel. \textsl{Seq66} works with "patterns" (also known as "loops", "tracks", or "sequences") that are repeated throughout a song. One composes and edits small patterns in a grid, and combines them to create a full song. This is a powerful way to work, and makes one productive quickly. The musician can control the playback and muting/unmuting of each pattern in the song, while it is playing, from within this window. One can also switch to other screensets, to work with a different section of the song. % For exposition, we divide the patterns panel % into a menu bar, a top panel, a pattern panel (live frame/grid), % and a bottom panel. \subsection{Patterns / Main Panel} \label{subsec:patterns_panel_main} The main panel of the application provides a grid of empty boxes, or boxes that have patterns, as shown in \figureref{fig:patterns_panel_popup_menu}. These boxes are referred to as "slots", and are implemented by push-buttons. Each filled slot represents a loop, track, sequence, or pattern (interchangeable terms). One sees only 32 loops at a time in the main panel, but many more than 32 loops are supported by \textsl{Seq66}. \index{screen-set} This group of 32 loops is called a "screen-set". One can switch between sets by using these keys to move down and up: \index{keys![} \index{keys!screenset down} \index{keys!]} \index{keys!screenset up} \begin{verbatim} [ and ] \end{verbatim} One can also use the spin-widget labeled \textbf{Set}, or \index{keys!Home} \index{keys!screenset play} by hitting the (default) \texttt{Home} key to make it the playing screenset. There are a total of 32 sets, for a total of 1024 loops/patterns. Only one screen-set can be controlled at a time, in general. Multiple screensets can be playing at the same time, in some configurations. The \texttt{Page Up} and \texttt{Page Down}, and \texttt{Up/Down Arrow} keystrokes can be used if the \textbf{Set} spin-button has focus. It is important to note that incrementing/decrementing the screen-set will \textsl{not} wrap around. We consider this a feature rather than a bug, at this time. There are some other important considerations for set-handling. See \sectionref{sec:setmaster}. Note the dropdown for changing loop mode of the grid slots, and the button for toggling the flavor of recording, and for recording quantization. The normal arm/mute ("Loop") mode can be changed to other grid modes to make it easier to record, copy, paste, delete, and do other operations on a pattern. \textsl{Seq66}'s pattern grid can be put in various recording modes (e.g. overdub/merge versus overwrite) Quantization can be turned on globally in \textsl{Seq66}'s pattern grid as well. These operations are also available as automation controls. See \sectionref{paragraph:configuration_midi_record_quan}. \subsubsection{Pattern Slots} \label{subsubsec:patterns_pattern_slot} \index{pattern!slot} A \textbf{pattern slot} is a box/button that can hold a pattern. If a pattern is present in the slot, text information and MIDI events are drawn on the button. The top line shows the title of the pattern, the number of measures in the pattern, and indicate if the pattern has a loop-count (indicated by a \textbf{+} sign. In addition, \textsl{if the pattern has an input buss set}, that number appears before the measures number, separated by a colon. A \textsl{right-click} over a pattern button brings up a fairly extensive popup menu. % Also see \sectionref{subsubsec:patterns_pattern_filled}. When a pattern is left-clicked, in the default grid mode, its armed/muted status is toggled. This action can be changed via the "grid modes" discussed in \sectionref{paragraph:patterns_loop_modes}. % The slot at the bottom left of this figure shows the features: % % \begin{itemize} % \item The sequence number (from 0 on up) appears at the bottom left of % the slot. % \item The buss number (re 0) and the channel number (re 1) appears % to the right of the sequence number, in the format "0-1". % \item To the right of that, the time signature ("4/4") appears, at the % bottom. % \item The hot-key for muting/unmuting the pattern appears next, % at the bottom right of the slot. % \item The title of the sequence appears at the top left of the pattern % slot. % \item The length of the sequence, in number of measures (bars), appears % at the upper right of the slot. % \item The font is a \textsl{Qt} font. % \end{itemize} A pattern can show a number of different statuses based on the coloring of elements in the pattern slot. (However, some of the special coloring using in \textsl{Sequencer64} are not supported in \textsl{Seq66}). \begin{itemize} \item \textbf{Empty background}. When the default button coloring for the current \textsl{Qt} theme is shown, without a pattern box, this state indicates that the slot is unused. \item \textbf{Yellow pattern box}. This color is used when a pattern is first created by \textsl{double-clicking} on the slot. However, this color sticks even when notes are added. Feel free to change it to another color, or no color. \item \textbf{Normal background}. Unarmed (muted) patterns show the unactivated/unchecked state of the button as per the \textsl{Qt} theme. If a color is applied, it has a slight bit of alpha in the color so that the color appears muted. \item \textbf{Active background}. An armed (unmuted) pattern shows the activated/checked state of the button as per the \textsl{Qt} theme. If a color is applied, it has no transparency, and the color appears bright. \item \textbf{Line or dot events}. \index{event!note} Lines or dots indicate the presence of notes or control values. Depending on settings, the lines indicate the notes themselves, or a "fingerprint", a condensed indication of notes useful in reducing the overhead of drawing long patterns. \item \textbf{Red events}. \index{event!drum note} Indicates a pattern for which the transpose feature is disabled. Most useful with drum patterns. \item \textbf{Circular events}. \index{event!tempo} Small circles indicate tempo events. Generally, these events should appear only in the tempo track (which is normally track 0). \end{itemize} The user can also apply coloring to each sequence. This feature was adopted from \textsl{Kepler34} \cite{kepler34}. The color is more saturated when the pattern is unmuted. \index{pattern!color menu} \paragraph{Pattern Context Menus} \label{paragraph:patterns_context_menus} \index{pattern!right click} \index{slot!empty slot right-click} \textsl{Right-clicking} on an \textbf{empty slot} brings up a menu to create a new loop or open an external live grid, as well as some other operations. This is the menu for an empty slot: \begin{enumerate} \item \textbf{New pattern} \item \textbf{Live grid window for set N} \end{enumerate} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{New pattern}{pattern!new} Creates a new loop/pattern. Clicking this menu entry fills in the empty box with an untitled pattern. Another way to create a new loop (without using the menu), is to hold the \texttt{Ctrl} key and click on the slot. A third to create a new loop is to \textsl{double-left-click} on an empty slot; this also brings up an external pattern editor (discussed later). \itempar{Live grid window for set N}{live!external} This option brings up an external \textbf{Live} frame window, which is the same as the patterns panel, but can be used to show a different set in a multi-set project. Up to 32 external live frames can be shown. An external live grid can be activated by its \textbf{Activate} button, which sets the playing screen-set (and updates the main window to match). % Another way to bring up an external live frame % is to hold the \texttt{Shift} key and click on the slot. % An external MIDI file can be dragged onto this "external" grid, % and dropped, to easily open a MIDI file, so be careful! \begin{figure}[H] \centering \includegraphics[scale=0.65]{main-window/multiple-live-grids.png} \caption{Multiple Live Grids} \label{fig:multiple_live_grids} \end{figure} Note that the right-click slot menu has some items removed when the live grid is in an external window, or the main window is showing a set other than set 0, as indicated by the asterisks in the list below. % \textsl{We hope to rectify that in a future release.} Once a pattern is created in a slot, there are more right-click options for that slot. \index{pattern!right click} A \textsl{right-click} on a \textsl{slot with a pattern} brings up a menu to allow one to edit it, or perform a few other actions specified in the context menu. % \begin{figure}[H] % \centering % \includegraphics[scale=0.65]{main-window/slot-record-toggle.png} % \caption{Slot Popup Menu} % \label{fig:slot_record_toggle} % \end{figure} The following figure shows an older version of the menu (which can be achieved by editing a \texttt{C} macro as noted in the \texttt{INSTALL} file.) \begin{figure}[H] \centering \includegraphics[scale=0.75]{tabs/live/pattern-popup-menu-2.png} \caption{Patterns Panel Pop-up Menu} \label{fig:patterns_panel_popup_menu} \end{figure} This layout has changed; the entries are nested to save space, as shown below. \begin{enumerate} \item \textbf{New pattern} \item \textbf{Live grid window for set ...} \item \textbf{Record toggle} \item \textbf{Edit} \begin{enumerate} \item \textbf{Pattern in tab} \item \textbf{Pattern in window} \item \textbf{Events in tab} \end{enumerate} \item \textbf{Color} \item \textbf{Track} \begin{enumerate} \item \textbf{Flatten triggers} \item \textbf{Export} \item \textbf{Copy} \item \textbf{Cut} \item \textbf{Delete} \item \textbf{Clear events} \end{enumerate} \item \textbf{Merge into pattern} \item \textbf{Input Bus} \item \textbf{Output Bus} \item \textbf{Output Channel} \end{enumerate} The first menu entry creates a new pattern. However, if there is already a pattern present in the slot, the user is prompted before erasing the current pattern and creating a new one. \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{New Pattern}{pattern!new} Creates a new pattern in the empty slot. \index{slot!double-click} A double-click on an empty slot can also be used. \itempar{Live grid window for set N}{pattern!external live frame} This selection uses the pattern number to open the corresponding screenset number in an external \textbf{Live} frame. This allows viewing and interacting with a number of sets. \itempar{Record Toggle}{pattern!record toggle} Selecting this item toggles the recording status of the pattern. If the grid record mode button shows something other than \textbf{None} (e.g. \textbf{Quantize}), then that alteration is supplied. % (The loop-record style is not applied at this time. TODO). Note that recording can also be set in the pattern editor, or via the "record" loop-mode, or by automation. \index{recording shortcut} \index{keys!+} \index{keys!record toggle} The default automation key for toggling recording is \texttt{+}; press it and then select the desired slot via mouse click or via the slot's hot-key.) The following figure applies to the next three "Edit" items. Note that the layout of the pop-up menu has changed. \begin{figure}[H] \centering \includegraphics[scale=0.75]{tabs/live/pattern-popup-menu-3.png} \caption{Patterns Panel Pop-up Edit Menu (New Layout)} \label{fig:patterns_panel_popup_edit_menu} \end{figure} \itempar{Pattern in window}{pattern!edit in window} Selecting this item brings up the pattern in an external pattern editor that has a few additional controls over the \textbf{Edit} tab (where space is more constrained). \itempar{Pattern in tab}{pattern!edit in tab} Selecting this item activates the \textbf{Edit} tab and fills it with data from the selected pattern. Note that this editor is slightly simpler than the external pattern editor described just below. In addition to \textsl{right-click} and select \textbf{New}, the user can \index{empty slot double-click} \textsl{double-click} on the empty slot, to bring up a new instance of the sequence editor. For \textsl{double-click} on an existing pattern, the effect can be a bit confusing at first, because it also toggles the armed/muted status of the slot quickly twice (leaving it as it was). \index{editing shortcut} \index{keys!=} \index{keys!pattern edit} A nice feature is hitting the equals ("=") key, then hitting a pattern shortcut key (hot-key), to bring up a new sequence or edit an existing one in a \textbf{Pattern Editor} . \itempar{Events in tab}{pattern!events in tab} Edits an existing loop or pattern, but using a detailed \textbf{Event Editor} tab that shows events as text and numbers, and allows editing them as text and numbers. This editor is basic, meant for viewing MIDI events and making some minor edits or deletes. The \textbf{Event Editor} is most useful when trying to find events that are screwing up the performance of that pattern. See \sectionref{sec:event_editor}, for more information. \textbf{Important}: This menu entry is disabled if recording is enabled. Turn off recording for the pattern to enable it. \index{keys!-} \index{keys!event edit} Another feature is hitting the minus ("-") key, then the hot-key, to bring up the \textbf{Event Editor} tab. The configuration file settings for the the '=' and '-' keys can be altered in the 'ctrl' file. \itempar{Pattern Color}{pattern!color} Opens a menu to select a color for the pattern. This selects a color palette value (index) into the currently loaded color palette. 32 palette colors are supported, and the palette can be modified. See \sectionref{sec:palettes}. \begin{figure}[H] \centering \includegraphics[scale=0.75]{tabs/live/pattern-popup-menu-4.png} \caption{Patterns Panel Pop-up Color Menu} \label{fig:patterns_panel_popup_color_menu} \end{figure} The following figure applies to the next few "Track" items: \begin{figure}[H] \centering \includegraphics[scale=0.75]{tabs/live/pattern-popup-menu-5.png} \caption{Patterns Panel Pop-up Track Menu} \label{fig:patterns_panel_popup_track_menu} \end{figure} \itempar{Flatten triggers}{pattern!flatten} This item "flattens" a sequence that has triggers. This means that the triggers are used to create the events without the triggers, so that the track can be used in MIDI sequencers that do not support \textsl{Seq66} triggers. However, one trigger is created for the whole track, for potential future editing in \textsl{Seq66}. Other sequencers should simply ignore this sequencer-specific (SeqSpec) event; the tune can also be exported to a plain MIDI format. This operation is similar to the effect of the \textbf{Export Song} function, but for one track. \itempar{Export}{pattern!export pattern} This operation takes the current track, in whatever form it is (SMF 0, 1, and with or without triggers), and writes it to a new MIDI file in pattern zero (MIDI track 1). This makes it easy to import the track into many different tunes. (In future release, perhaps a destination track number will be specifiable....) \itempar{Copy}{pattern!copy} Copies the pattern underneath the mouse cursor. The pattern can then be pasted elsewhere in the Patterns panel. One can also drag-and-drop a pattern into another cell (there is no outline box during the drag, unfortunately). This action moves the pattern See \sectionref{subsubsec:patterns_pattern_slot}. Note that there is no \texttt{Ctrl-C} key for this operation in the live (main) window. \itempar{Cut}{pattern!cut} Cuts the pattern while copying it for later pasting. There is no \texttt{Ctrl-X} key for this operation. \itempar{Delete}{pattern!delete} Deletes the pattern. There is no "undo" for this operation. \itempar{Clear events}{pattern!clear} This selection leaves the pattern and its specifications in place, but removes all events from the pattern. The following two menu items are only available if a pattern has previously been copied or cut \itempar{Paste}{pattern!paste} Pastes a loop or pattern that was previously copied. This option is shown only when \textsl{right-clicking} over an empty pattern. It causes a cut or copied pattern to be replicated into the emptly slot. Note that there is no \texttt{Ctrl-V} key for this operation in the main window. Also note that the pattern clipboard can be pasted after a \textbf{File / New} or \textbf{File / Open}, to copy it to another file. \itempar{Merge}{pattern!merge} Like \textbf{Paste to pattern}, it pastes a patten that was cut or copied into the pattern slot where the mouse was \textsl{right-clicked}. However, the original notes remain. Thus, the merge option provides a way to build up a pattern by copying other patterns. This action work on an empty slot or an existing pattern. \itempar{Output Bus}{pattern!buss} This item allows one to select the output buss for a pattern without having to open the editor. Note that having an output buss setting for a pattern is mandatory. The output buss is stored as a simple buss number ranging from 0 on up to the number of MIDI output devices. \itempar{Output Channel}{pattern!channel} This item allows one to select the output channel for a pattern without having to open the editor. \itempar{Input Bus}{pattern!input buss} \textsl{Seq66} supports an \textsl{optional} input buss setting for each pattern. This setting is not available in the pattern editor due to lack of room, so this popup menu is the only way to get to this option. And it is available only if the \textbf{Edit / Preferences / MIDI Input / Record into patterns by bus} option is set. Normally, the "current" pattern, which is the one opened in the pattern editor, is the only one accepting input when recording is activated. \begin{figure}[H] \centering \includegraphics[scale=0.65]{main-window/slot-inputt-buss.png} \caption{Input Bus Popup Menu (old style)} \label{fig:slot_input_bus} \end{figure} By setting an input buss, incoming MIDI events can be routed to the pattern that is set to accept input from that buss. As the figure shows, the full set of input ports is shown. Some are disabled. (The "ALSA Announce" buss, should be disabled; this is a bug.) In the figure, the pattern will accept events from the \textsl{MPK mini Play mk3} keyboard. If the \textbf{Free} entry is selected (the default), then the pattern will accept input from any MIDI input port. Note that the input bus setting is saved (in a SeqSpec) with the MIDI tune. \textbf{Important}: \begin{itemize} \item If a MIDI input device is not enabled in the port list, it will not work as an input buss. \item If a MIDI input device is defined as an automation controller for \textsl{Seq66}, it will not work as an input buss. \item To disable the usage of input-buss routing, set the input buss to \textbf{Free} for all patterns, then save and reload the file. Or change the I/O options at \sectionref{subsection:edit_preferences_midi_input} and restart. \item To record normally to a pattern, open the pattern for recording and use a MIDI device that has \textsl{not} been assigned as a pattern's input buss. \end{itemize} One final note... the input buss setting overrides the record-by-channel setting. \paragraph{Pattern Labels} \label{paragraph:patterns_labels} A \textbf{filled pattern slot} is referred to as a \textsl{pattern} (or \textsl{track}, \textsl{loop}, or \textsl{sequence}). A pattern is shown in the pattern grid as a filled slot with a number of labels surrounding it. Here are the labels shown: \begin{itemize} \item \textbf{Pattern Name}. Top left. \index{pattern!name} This line, in the upper left of the pattern slot, contains the name or title of the pattern, for reference when juggling a number of patterns. \item \textbf{Pattern Status}. Second line. \index{pattern!status} Underneath the pattern-name is the status of the pattern, such as "New" (untitled with zero events), "Armed", "Muted", or "Queued". This status is useful when the Qt theme coloring makes the exact status difficult to determine. \item \textbf{Pattern Length}. Top right. \index{pattern!length} The length of the pattern, in measures, is shown in the upper right corner of the pattern slot. If the pattern has been modified, an \textbf{asterisk} appears after the number. Otherwise, if the loop-count for the pattern is greater than 0, then an \textbf{plus sign} is shown. Remember that a pattern loop-count of 0 means the pattern can repeat "forever". \item \textbf{Notes or Fingerprint}. Center progress box. \index{pattern!contents} The contents of the pattern, in the central box, provide a distinguishable representation of the notes or events in the pattern. The notes are shown in the center, inside a "progress box" that can also be colored, or not shown at all. Long patterns can be replaced by a much shorter "fingerprint", for faster drawing. Tempo events are indicated by a small circle. \index{empty pattern} An pattern with no playable events will not needlessly scroll during playback. However, if a pattern has even a single event (say, a program change), it will scroll. \item \textbf{Progress Cursor}. Center. At the left of each center box is a vertical line, waiting for playback to start so that it can move through the pattern, again and again. When the song is playing, this vertical bar tracks the position of the playback of the pattern or loop; it returns to the beginning of the box every time the pattern starts over. \item \textbf{Sequence Number}. Bottom left. This number is shown at the bottom left of the pattern slot. Pattern numbers, by default, range from 0 to 1023. Note how it varies fastest by row (top to bottom). There is a configuration item to change transpose them (but be aware of side effects). \item \textbf{Bus-Channel}. Bottom, second from left. \index{pattern!bus-channel} This pair of numbers shows the the MIDI buss number, a dash, and the MIDI output channel number. For example, "0-2" means MIDI buss 0 (re 0), channel 2 (re 1). \index{pattern!free channel} If the channel is an "F", this means that the pattern has no specified output channel, and can play on all channels. This "free" channel concept is useful for applying Program Changes and Volume controls to many channels at once. \item \textbf{Beat/Beat Width}. Bottom, third from left. \index{pattern!beat} This pair of numbers is the standard time-signature of the pattern, such as "4/4" or "3/4". The first number is the beats-per-measure, and the second is the size of the beat, here, a quarter note. \item \textbf{Shortcut Key}. Also known as the \textbf{hot key}, this symbol is shown at the bottom right of the slot, in square brackets for better visibility. The key noted in the lower-right corner of the pattern is a "hot-key" that can be pressed to toggle the mute/unmute status of that pattern. This action is an alternative to \textsl{left-click} on the pattern. This hot-key can also be used to open the pattern in a pattern editor or in the event editor. Other actions are supported by changing the \textbf{loop mode}. \item \textbf{Armed}. Highlight color of button. Button highlighting indicates that the pattern is armed (unmuted), and will play if playback is initiated in the pattern \index{live mode} window in live mode. An item is armed/disarmed by a \textsl{left-click} on it, or by using the button's hot-key. \end{itemize} \index{pattern!left click} \textsl{Left-click} on an filled pattern slot toggles the status of the pattern between muted (faded background) and unmuted (bright background). If the song is playing "live" (versus "song mode"), toggling this status makes turns the pattern on or off. The armed status can also be toggled using hot-keys and MIDI controls. \index{pattern!shift left click} If the \texttt{Shift} key is held during a \textsl{left-click} on a pattern, the corresponding set's \textbf{Live} frame is brought up. \subsubsection{Basic Pattern Control} \label{subsubsec:patterns_basic_pattern_control} In addition to the arm/mute function done by clicking on a pattern slot, there are some additional functions that can be enabled on the fly: \begin{itemize} \item \textbf{Queue}. Enables one pattern to be turned on or off as soon as the pattern loops to its beginning. \item \textbf{Keep-queue}. The same as Queue, but stays active until disabled. \item \textbf{Oneshot}. Similar to Queue, but it merely unmutes a pattern for one cycle, then mutes it. \item \textbf{Replace}. Soloes a pattern immediately. Can be repeated many times. Then restores the original status of patterns. \item \textbf{Solo}. Similar to Replace, but the process is queued. \item \textbf{Snapshot}. Similar to Queue, but arms a pattern, plays it once, and turns it off, then reverts to the previous playing statues. \item \textbf{Learn}. Maps a set of pattern arm/mute statuses to a keystroke. \end{itemize} These actions are described in detail below. They have some common preconditions and indicators that are listed here: \begin{itemize} \item \textbf{Live mode}. Make sure that the song is in \textbf{Live} mode. \item \textbf{Playback}. These features work best with playback already started. \item \textbf{Enabled patterns}. Make sure that some patterns are armed. In some cases, a snapshot of them is saved. \item \textbf{Status indicator}. When a control-mode is activated, a status indicator appears, with italics text indicating the status. It appears between the file-name and the Metronome button. \end{itemize} The keystrokes noted are mentioned in \sectionref{paragraph:patterns_pattern_keys}. \paragraph{Pattern Queue} \label{paragraph:patterns_pattern_queue} Pressing the "queue" key and then hitting a pattern hot-key will queue an on/off toggle for a pattern when the end of the loop is reached. This means that the change in state of the pattern will not take hold immediately, but will kick in when the pattern loops back to its beginning. This pending state is indicated by coloring the central box of the pattern grey. Also check out the "keep queue" functionality and the "one-shot queue" functionality described below. \begin{enumber} \item Press \texttt{o}; note that a "Queue" status indicator appears. Note that the obvious key, \texttt{q}, is already assigned as a hot-key for pattern \#1. \item Click a pattern. \item It is queued for arm or queued for mute (the opposite of its original status). \item It is in effect for only one pattern click, then must be reestablished, if desired, by going back to step 1. \end{enumber} % This is something to look into. Doesn't seem to work at all. Does it % work in Sequencer64. % % Queue also works for mute/unmute pattern sets ("groups"); in this case, % every sequence will toggle its status after its individual loop ends. \paragraph{Pattern Keep-Queue} \label{paragraph:patterns_pattern_keep_queue} Pressing the "keep queue" hot-key activates a "sticky" queue mode. In this mode, pressing a pattern key immediately turns on queuing. Multiple patterns can be handled serially in this way; keep-queue persists until one clicks the queue hot-key again, or changes the active (viewed) screen-set. \index{queue!cancel} Keep-queue” is cancelled by pressing the keep-queue hot-key again. There is also a \textbf{Q} button in the main window for the same purpose. Also note the "queued replace/solo" functionality, described a bit later. \begin{enumber} \item Press the backslash; note that a "Keep Q" status indicator appears. \item Click a pattern. \item It is queued for arm or queued for mute (opposite of original status) \item It is in effect for all pattern clicks until the keep-queue key is pressed again. The "Keep Q" status indicator disappears. \end{enumber} \paragraph{Pattern Oneshot} \label{paragraph:patterns_pattern_oneshot} Thanks to \textsl{Kepler34}, we have "one-shot queue" functionality. This one-shot setup queues a pattern up for unmuting only, and, once the pattern has played, it is automatically muted. This process is easier than having to unqueue the pattern manually before the next playback. A light-grey color is used to show a "one-shot" queue. The one-shot queue can only turn a pattern on, and it will force the pattern off after one play. \begin{enumber} \item Press the pipe key; note that a "Oneshot" status indicator appears. \item Click a pattern. \item It is queued for arm only. \item It plays once, and then stops. The "Oneshot" status indicator disappears automatically. \end{enumber} \paragraph{Pattern Replace} \label{paragraph:patterns_pattern_replace} The "replace" automation key/control ("Keypad Home") sets a form of muting/unmuting. When the "replace" hot-key is pressed, and then a pattern's hot-key is pressed, that sequence is unmuted, and all of the other sequences are muted. Other patterns can be clicked to be armed/muted; when the "replace" hot-key is clicked again, the original patterns armed before the first "replace" click are restored. "Replace", like other controls, is also implemented via MIDI control, where the MIDI control can be activated; but then the user has to select the desired sequence. \begin{enumber} \item Press the replace key; note that a "Replace" status indicator appears. \item Click a pattern; it either stays on or is turned on. \item All other patterns are saved as a snapshot, then are immediately muted (no queuing). \item More patterns can be soloed by clicks. \item Press 0 again, and the original patterns (snapshot) are restored. The "Replace" status indicator disappears. \end{enumber} \paragraph{Pattern Solo} \label{paragraph:patterns_pattern_solo} \textsl{Seq66} provides an extension to the replace functionality that is called "queued-replace" or "queued-solo". In this feature, the "keep queue" function is activated and the replace function is queued so that it does not occur until the next time the patterns loop. And queued-replace provides a form of snapshot, limited to the \textsl{current} screen-set. Here are the steps: \begin{enumerate} \item Be careful which key, "BS" (\texttt{Ctrl-H}) or "BkSpace" (\texttt{Backspace}), is configured. They are two different keystrokes. "BkSpace" is preferred and is the default. \item Start playback with some patterns on. \item Press and release the "solo" hot-key. This puts the application into "queued replace" mode. It is indicated via highlighting the "\textbf{Q}" button and by a "Solo" status indicator. \item Click the desired pattern hot-key. Observe that it arms or stays on, and that the other playing patterns show the "queued" color (grey). At the end of the loop, they turn off, and the "replace" pattern is now soloed. This step can be repeated with other patterns. % \item Click the same pattern hot-key again. Observe that the other % patterns that were toggled off are now queued to be toggled on at the % next loop. Step 4 can be repeated endlessly. \item To end \index{queue!clear} \index{queue!end} the "queued-replace" mode, click the "solo" hot-key. This restores the original statuses of the patterns. Also, changing the active screen-set ends "queue-replace" mode. % Make sure this works: % It does \textsl{not} end normal queue mode, to preserve the % behavior found in \textsl{Seq24}. % One needs to clear the queue mode in order to select another pattern to solo. \end{enumerate} \paragraph{Pattern Snapshot} \label{paragraph:patterns_pattern_snapshot} When a snapshot key is pressed and released the state of the patterns (armed versus unarmed) is saved as a snapshot. While the snapshot is in force, one can then change the state of the patterns (using keyboard, mouse, or MIDI controls) to change how the song plays. When the snapshot mode is exited by pressing the snapshot key again, the original saved state of the patterns is restored. \begin{enumber} \item Press \texttt{Ins}; note that a "Snapshot" status indicator appears. \item The first press of Ins saves the current state of the patterns. \item The user can mute/unmute any combinations of patterns. \item The next press of \texttt{Ins} restores the snapshot. The "Snapshot" status indicator disappears. \item The snapshot "buffer" is cleared to get ready for the next \texttt{Ins}. \item Snapshot mode is non-queued. \end{enumber} \paragraph{Pattern Learn} \label{paragraph:patterns_pattern_learn} Learn is activated by the \texttt{l} (lower-case L) key or the "L" button. Once activated, the next pattern hot-key (lower-case) pressed will cause the current set of armed/muted patterns in the screen-set to be stored with the upper-case (shifted) version of that hot-key. See \sectionref{subsubsec:introduction_mute_group_learn_button}. \subsubsection{Pattern Keys and Clicks} \label{subsubsec:patterns_pattern_keys_and_clicks} This section recapitulates all the clicks and keys that perform actions in the Pattern windows. Some additional clicks and keys are noted here as well. Also refer to \sectionref{sec:kbd_mouse_actions}. \paragraph{Pattern Keys} \label{paragraph:patterns_pattern_keys} \index{keys!hot} \index{keys!shortcut} Each pattern in the patterns panel can have a hot-key or shortcut-key associated with it. \index{keys!pattern toggle} \textbf{Pattern Toggle}. Like a \textsl{left-click}, for each pattern, its assigned hot-key will also toggle its status between muted/unmuted (armed/unarmed). Here is the normal layout of patterns, which was built into \textsl{Seq24}'s "DNA": \begin{verbatim} [ 0 ] [ 4 ] [ 8 ] [ 12 ] [ 16 ] [ 20 ] [ 24 ] [ 28 ] [ 1 ] [ 5 ] [ 9 ] [ 13 ] [ 17 ] [ 21 ] [ 25 ] [ 29 ] [ 2 ] [ 6 ] [ 10 ] [ 14 ] [ 18 ] [ 22 ] [ 26 ] [ 30 ] [ 3 ] [ 7 ] [ 11 ] [ 15 ] [ 19 ] [ 23 ] [ 27 ] [ 31 ] \end{verbatim} There is an alternate (and fairly new) mapping that can be enabled using the \texttt{swap-coordinates} option in the 'usr' file. See \sectionref{subsubsec:usr_file_user_interface_settings}. Below is the default keyboard "grid" that is mapped to the loops/patterns on the screen-set. \begin{verbatim} [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ][ 8 ] [ q ][ w ][ e ][ r ][ t ][ y ][ u ][ i ] [ a ][ s ][ d ][ f ][ g ][ h ][ j ][ k ] [ z ][ x ][ c ][ v ][ b ][ n ][ m ][ , ] \end{verbatim} These characters are shown in the lower right corner of each pattern, as an aid to memory. This grid can be changed in the 'ctrl' file in the \texttt{[loop-control]} section. However, it is best to leave this setup as is, except for the key-swapping needed for alternative keyboard layouts like "QWERTZ". \index{keys!pattern shift} \textbf{Pattern Shift}. A "shift" function is available for the mute/unmute hot-keys when a set is larger than 32 patterns. \index{keys!slash} \index{variset!slash key} Normally, pressing the \texttt{1} key will toggle sequence 0. If preceded by one slash key (\texttt{/}), then sequence 32 will be toggled. If preceded by two slash keys, then sequence 64 will be toggled. This features supports using set sizes of 32, 64, and 96 patterns. \index{keys![} \index{keys!decrement set} \index{keys!screenset down} \textbf{Screenset Increment and Decrement}. The \texttt{[} and \index{keys!]} \index{keys!increment set} \index{keys!screenset up} \texttt{]} keys on the keyboard decrement or increment the set number. \index{keys!alt} \index{keys!snapshot} \textbf{Snapshot}. Unlike in \textsl{Seq24}, the \texttt{Alt} keys are not used for snapshots. In addition, the snapshot key acts like a \textsl{toggle}... no need to hold it down. % Lastly, there is only one snapshot key slot. It's best to use something that does not trigger desktop/window commands; the default key is \texttt{Ins}. Configured in the 'ctrl' file \texttt{[automation-control]} section. \index{keys!right ctrl} \index{keys!queue} \index{queue!temporary} \index{keys!avoid ctrl/alt} We do \textbf{not} recommend using \texttt{Ctrl} or \texttt{Alt} keys for pattern control. They conflict with application or desktop settings. The default queue key is \texttt{o} (lower-case O). \index{keys!keep queue} \index{queue!keep} The default keep-queue key is the \texttt{Backslash}. \index{one-shot queue} \index{keys!one-shot queue} \index{queue!one-shot} The default one-shot queue key is \texttt{|}, the pipe character. \index{keys!replace} The default keep-queue key is \texttt{Keypad Home}. \index{queue!solo} The default solo key is \texttt{Backspace}. \begin{comment} Before pressing the "keep queue" key, patterns 33 ("\textbf{q}") and 34 ("\textbf{a}") are unmuted, while the desired replace pattern, 32 ("\textbf{1}") is off. Then the user presses (and holds) the "replace" key, then clicks the "\textbf{1}" key. This puts all unmuted patterns, plus the muted replace pattern as well, into queue mode, as shown by the grey panels. When the progress bar reaches the end of the pattern, pattern 32 will go on, and patterns 33 and 34 will go off. If the replace-pattern is already on, it is not queued, as there's no need to turn it on. If, while in queue mode, the replace key is held and "\textbf{1}" is pressed again, the other patterns will be queued, and will turn on again. Thus, the solo status of the replace pattern can be toggled at will, until queue mode is exited by pressing and releasing the normal "queue" key. If the replace key is \textsl{not} held down, and another pattern's replace hot-key is pressed, that pattern will be queued normally. If one wants to change the solo functionality to a different pattern, simple hold the replace key and click on a different pattern. The new arrangement of soloing is memorized. One can clear the queue mode by pressing the normal queue key. \end{comment} There are more keys defined in the 'ctrl' file, and it is worth figuring out what they do, if not documented here. Also see the installed \texttt{control\_keys.ods} spreadsheet. For a couple of short, but good, video tutorials about using arming, queuing, and snapshots in \textsl{Seq24}, see reference \cite{wootangent1}. \paragraph{Other Pattern Clicks} \label{paragraph:patterns_pattern_clicks} \index{pattern!left click} \index{pattern!mute toggle} \textsl{Left-click} on a pattern-filled box will change its state \index{pattern!mute} \index{pattern!unmute} from active to inactive. \index{pattern!left click-drag} \textsl{Left-click-hold-drag} on a pattern, drags it to a different pattern on the grid. The box disappears while dragged, and reappears in the new location when dropped. However, a pattern \textsl{cannot} be dragged if its \textbf{Pattern Editor} window is open. \index{pattern!right click} \textsl{Right-click} on a pattern brings up the appropriate context menus, depending on whether the pattern box is empty or filled. \index{pattern!middle click} \textsl{Middle-click} on a pattern will bring up the pattern in the \textbf{Editor} tab in the main window. \index{pattern!shift-left-click} \index{shift-left-click live-frame} \textsl{Shift-left-click} on a pattern will open up an external live-frame for the set having the same number as the pattern. \index{pattern!ctrl-left-click} \index{ctrl-left-click new pattern} \textsl{Ctrl-left-click} on a pattern will create a new pattern, just like double-click will (if enabled). \index{solo!true} \index{pattern!alt-left-click} \index{patterns panel!inverse muting} \index{patterns panel!solo} \index{alt-left-click solo} \textsl{Alt-left-click} is a "Solo" functionality in the Patterns Panel and the Song Editor. To "solo" a pattern, move the mouse cursor over the pattern, hold the \texttt{Alt} key, and left-click the pattern. This will turn off all the other patterns, so that the selected pattern ins the only one playing. Also see \sectionref{paragraph:patterns_loop_modes}; it notes another way to solo a pattern. One thing with solo is that it can be reversed (reenabling all the other patterns) only if that same pattern is clicked. Soloing another pattern will lose the list of previously-enabled patterns. \paragraph{Metronome} \label{paragraph:patterns_metronome} \index{metronome} Also shown in the figure is a \textbf{Metronome} button. This is a feature new with \textsl{Seq66} version 0.99.0, and still has a few minor issues to work out, but it works. The metronome has a number of configurable items. See \sectionref{subsubsec:configuration_rc_metronome}. Clicking the metronome button turns the metronome on and off. To use the metronome (once configured), click the button to enable it, then start playback. It can be turned on and off during playback. It is always available, stored as a hidden automatically-generated pattern. \paragraph{Count-in Recording} \label{paragraph:patterns_background_recording} \index{count-in recording} Also shown in the figure is a \textbf{Count-In Recording} button. This feature still has a few minor issues to work out, but it works. It is not \textbf{background recording}, which involves a thread continuously recording in the background. Some day. The recorder has a number of configurable items. See \sectionref{subsubsec:configuration_rc_background_recording}. Count-in recording needs to be enabled in the configuration. Otherwise, the count-in-recording button is disabled. When enabled, press the count-in-recording button. Now all events on the given input buss will be recorded, even if playback is not started. Better to start playback after enabling recording! Once one is satisfied with the recording, stop playback, then click the count-in-recording button again. If any events have been recorded, then they are pasted into a new pattern in the next available pattern slot. There, they can be edited or deleted. Note that the count-in pattern is muted while recording. We will change how it works based on user feedback. \paragraph{Pattern Loop-Record Modes} \label{paragraph:patterns_loop_modes} \textsl{Seq66} has always had the feature of turning on recording, including quantized recording, in the pattern editor. Now, it also provides for \index{loop!grid modes} loop-grid modes. With this feature, the normal loop muting/unmuting functionality of the \textbf{Live Grid} buttons can be changed to allow the toggling of recording or other actions to be applied to the affected pattern. This new functionality is enabled by two buttons in the main window grid and by two refactored keystroke/MIDI controls ("Record" and "Quan Record"). The normal mode (mute/unmute) is called \textbf{Loop}. \begin{itemize} \item \textbf{Loop}. This mode is the legacy and long-time standard mode of \textsl{Seq24}, \textsl{Sequencer64}, and \textsl{Seq66}. When in this mode, a click on a pattern slot, a loop-control keystroke, or a loop-control MIDI event will change the mute/unmute, armed/unarmed status of a pattern. \item \textbf{Mutes}. In this mode, a click on the pattern activates the mute group with the same number as the pattern. \item \textbf{Record}. A click on the pattern turns on recording for that pattern. This status is shown by the appearance of a red circle in the pattern button. \item \textbf{Copy}. Copies the selected pattern into the clipboard. \item \textbf{Paste}. Pastes the clipboard into the selected pattern. \item \textbf{Clear}. Removes the events from the selected pattern. Careful! \item \textbf{Delete}. Deletes the selected pattern. Careful! \item \textbf{Thru}. Turns on the MIDI Thru functionality of the selected pattern. \item \textbf{Solo}. Soloes the selected pattern. \item \textbf{Cut}. Deletes the selected pattern and copies it into the clipboard. \item \textbf{Double}. Doubles the length of the selected pattern. \end{itemize} Recording has been supplemented by four recording modes. Clicking on the button cycles between all the modes in this list. \begin{itemize} \item \textbf{Overdub}. \index{merge} \index{overdub} \index{record!merge} \index{record!overdub} Also known as \textbf{Merge}. % this mode is selected by clicking on the \textbf{Loop} button, % then using the \textbf{Record} control. Overdub is the same as the \textbf{Merge} option in the pattern editor. % Clicking a grid will turn on/off the overdub recording mode % for that pattern. It enables recording that accumulates note events in each pass of looping through the pattern. \item \textbf{Overwrite.} \index{overwrite} \index{recording!overwrite} This mode causes the grid button to turn on overwrite recording. When the loop restarts over and a note is pressed, then the existing notes in that loop are erased, and the new note is added. \item \textbf{Expand}. \index{expand} \index{recording!expand} This mode turns on expand recording. Once the end of the loop is near, whether or not any notes are being input, another measure is added to the length of the loop. \item \textbf{One-shot}. \index{one-shot} \index{recording!one-shot} When this option is set, with the record button on, and no pattern playing, recording won't start until a note comes in, and when the first note comes in, the progress bar starts at the left (time 0). As each new set of notes at the same timestamp come in, the notes are recorded and the current time advances by one snap value. Once the end of the pattern length is reached, recording is turned off. \item \textbf{One-shot Reset}. \index{one-shot reset} \index{recording!one-shot reset} When the \textbf{One-shot} option is selected, this option is enabled. When clicked, three things happen: \begin{itemize} \item One-short counters/flags are \textsl{reset}. \item Recording is turned \textsl{back on}. Thus, one can easily re-do a one-shot recording. \item The (note) events just recorded are \textsl{no longer erased}. Instead, one can use the slot popup menu entry \textbf{Clear events}. This seems safer, if slightly clumsy. \end{itemize} \end{itemize} These loop-record modes are applied when a pattern is opened in the pattern editor. Once the recording mode is turned on, whether here or in the pattern editor, then the recording type comes into play. The types of alteration that can come into play are set by clicking the button at the right labeled "None" until the desired recording mode is shown: \begin{itemize} \item \textbf{None}. Incoming events are recorded as is. Plain recording, events recorded with timestamps unaltered. Indicated by a red circle in the pattern slot. \item \textbf{Quantize}. When recording, quantize the incoming events. Incoming events are quantized to the snap value of the pattern. Indicated by a red circle and a "Q" inside it in a pattern slot. \item \textbf{Tighten}. A weaker version of \textbf{Quantize}. Incoming events are partially quantized to the snap value of the pattern. Indicated by a red circle and a "T" inside it in a pattern slot. This mode is accessible only via this recording mode; there is no such button in the pattern editor. \item \textbf{Note Map}. If a 'drums' file has been specified and is active, then incoming events are remapped to different notes. This feature is useful in transforming the notes of an old drum machine into, say, a \textsl{General MIDI} drum kit. Indicated by a red circle and an "N" inside it in a pattern slot. This mode is accessible only via this recording mode; there is no such button in the pattern editor. \end{itemize} These alterations are applied when a pattern is opened in the pattern editor. \subsection{Patterns / Bottom Panel} \label{subsec:patterns_panel_bottom} The first line of the bottom panel is discussed more completely in \sectionref{subsec:introduction_main_bottom_controls}. It is actually part of the main window. The second line of the bottom panel is discussed more completely in \sectionref{subsec:introduction_main_bottom_controls_2} It is actually part of the main window. \subsection{Patterns / Multiple Panels} \label{subsec:patterns_panel_multiple} Multiple patterns-panels can be created in addition to the one in the "Live" tab. The live set-up and set-down keystrokes, as well as their MIDI control counterparts (both defined in a 'ctrl' file). apply only to the main window. \subsection{Patterns / Variable Set Size} \label{subsec:patterns_panel_variset} \index{variset} This option, informally known as "variset", allow some changes in the set size and layout from the default 4x8 = 32 sets layout. The row count can be set from 4 to 8, and the column count can be set to 8 to 12. Note that the set size can only be \textsl{increased} by these settings. \textbf{Warning:} \textsl{seq24} was fairly hardwired for supporting 32 patterns per set, and there are still places where that is true. Thus, consider this option to be experimental. The \texttt{-o sets=8x8} option can be used to set this mode. These settings can be made permanent in the 'usr' file. In that file, the options modified are \texttt{mainwnd\_rows} and \texttt{mainwnd\_cols}. Generally, it is recommend to stick with the 4x8 (32 patterns/set), 8x8 (64 patterns/set), and 8x12 (96 patterns/set). This works best with the existing set of 32 hot-keys. Also note that the Qt 5 user-interface also supports "variset", whether in the main window or in the external live-frame. In addition, the Qt windows can be resized and still show reasonable renditions of the pattern-slots. \subsection{Patterns / Set Handling} \label{subsec:patterns_panel_set_handling} Let's go through an example using the \texttt{Home} key (or whatever key is configured as the \textbf{Set Playing Screenset} key.) The process here assumes that sets-mode is "normal", not "auto-arm". \begin{enumber} \item Load a song with more than one screen-set. \item Unmute the pattern(s) in the first set and start playback. \item Use the "\texttt{]}" (\textbf{Screenset Up}) key to move to the next set. Note that the first set stops playing. Also note that the now-current set is \textsl{not} playing. \item Press the \texttt{Home} key. Note that the first set remains off, and the current set turns on. These steps can be repeated at will. \item Finally, hit the \texttt{F8} (\textbf{Toggle Mutes}) key. Note that all tracks on all sets toggle muting each time this key is pressed. \end{enumber} %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/playlist.tex ================================================ %------------------------------------------------------------------------------- % playlist %------------------------------------------------------------------------------- % % \file playlist.tex % \library Documents % \author Chris Ahlstrom % \date 2018-09-15 % \update 2025-06-09 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides a discussion of the playlist functions that Seq66 supports. % %------------------------------------------------------------------------------- \section{Seq66 Play-Lists} \label{sec:playlist} \textsl{Seq66} supports play-lists. A play-list provides a way to step through and play a number of MIDI files without having to load each one individually. The format of the play-list file is a variation on the 'rc' file, conventionally ending with the extension \texttt{.playlist}. It contains a number of play-lists, each described by a \texttt{[playlist]} section. Each play-list section provides a human-readable title which is selectable via a MIDI data number, or by moving to the next or previous playlist in the list using the \index{keys!arrow} \texttt{Up} and \texttt{Down} arrow keys. Each playlist section contains a list of MIDI files, also selectable via a MIDI data number, or by moving to the next or previous song in the list using the \texttt{Left} and \texttt{Right} arrow keys. Movement between the playlists and the songs is accomplished via MIDI control or the arrow keys. Using MIDI control (\texttt{[midi-control]}, see \sectionref{subsec:configuration_ctrl}) makes it possible to use the \texttt{seq66cli} headless version of \textsl{Seq66} in a live setting. See \sectionref{sec:launchpad_mini}; it describes programming for the \textsl{Novation LaunchPad Mini} for \textsl{Seq66}. In the normal user-interface, play-list movement can also be done manually via the four arrow keys. The playlist file can be specified on the command-line, in the 'rc' file, or be loaded from the \textbf{File / Open Playlist} menu or in the \textbf{Playlist} tab. The playlist setup is written to the 'rc' file; the 'rc' file indicates the name of of the play-list file and if it is to be used or not. % It can be removed by specifying a blank (i.e. % two double-quotes, "") play-list name. % The file extension is \texttt{.playlist}. The \textsl{Qt} user-interface supports editing of the play-list, using the \textbf{Playlist} tab. For version 0.99.7, this tab has been revamped. It was somewhat confusing to use, so the fixes make the process much easier. The user can use a text editor to edit the play-list file, if careful. The play-list format is defined in the following section. Later sections describe the user-interface. \subsection{Seq66 Play-Lists / 'playlist' File Format} \label{subsec:playlist_setup} The play-list file, by convention, has a file-name of the form \texttt{sample.playlist}. The play-list file starts with a hardwired top banner. It can also have an optional comments section, much like the 'rc' and 'usr' files. It is \textsl{not} overwritten when \textsl{Seq66} exits, unless it has been modified in the \textbf{Playlist} tab. \begin{verbatim} [comments] Comments added to this section are preserved.... \end{verbatim} A blank line (without even a space) ends the comment section. Following the comments section is a \texttt{[playlist-options]} section. \begin{verbatim} [playlist-options] unmute-next-song = true # the next song selection unmutes its patterns auto-play = true # if true, unmute and start play automatically deep-verify = false # If true, every MIDI song is opened and verified \end{verbatim} The first option allows the load of the next song to unmute the patterns in that song. The second option causes the loaded song to start playing. This feature is useful for an unattended lengthy play-list. The thrid option causes each MIDI file to be opened to verify that it is an error-free play-list. This process can be time-consuming for large playlists. If set to false, \textsl{Seq66} still makes sure that each MIDI file in the play-list at least exists. Following the options section are one or more \texttt{[playlist]} sections. Each represents a complete play-list. Here is the layout of a sample playlist section. \begin{verbatim} [playlist] # Playlist number, arbitrary & unique. 0 to 127 recommended for MIDI control number = 126 name = "Music for Serious Dogs" # Display name of this play list directory = "contrib/midi/" # Storage directory for the tunes # Provides the song-control number and the base file-name of each song in # this playlist. The playlist directory is used, unless the # file-name contains a path. 70 "allofarow.mid" 71 "CountryStrum.midi" 72 "contrib/wrk/longhair.wrk" \end{verbatim} \index{playlist!tag} A play-list file can have more than one \texttt{[playlist]} section. This allows for partitioning songs into various groups that can be easily selected (e.g. based on the mood of the musician or the audience). \index{playlist!number} After the \texttt{[playlist]} tag comes the play-list number. In order to use MIDI control to select the playlist, this number is limited to the range 0 to 127. If there is more than one \texttt{[playlist]} section, they are ordered by this number, regardless of where they sit in the play-list file. \index{playlist!title} Next comes a human-readable name for the playlist, which is meant to be displayed in the user-interface. If surrounded by quotes, the quotes are removed before usage. \index{playlist!song-storage directory} Next is the song-storage directory. This directory is the default location in which to find the songs in that play-list. It can be an absolute directory or a relative directory. However, be wary of using relative directories, since they depend on where \textsl{Seq66} is run. Note: If a song's file-name has its own directory component, that overrides the default song-storage directory. Lastly, there is a list of MIDI song file-names, preceded by their numbers. As with the playlist numbers, they are restricted to the range of 0 to 127, for potential usage with MIDI control. The songs are ordered by this number, rather than by their position in the list. \subsection{Seq66 Play-Lists / 'rc' File} \label{subsec:playlist_rc_file} The most consistent way to specify a play-list is to add an entry like the following to the 'rc' file: \begin{verbatim} [playlist] active = true name = "/home/ahlstrom/.config/seq66/sample.playlist" base-directory = "" \end{verbatim} This setup allows a play-list file to be specified and activated. If the name of the play-list file does \textsl{not} contain a base-directory, then the play-list file is searched for in the user's \textsl{Seq66} configuration directory. If the play-list file-name is empty (i.e. set to \texttt{""}), then there is no play-list active. \subsection{Seq66 Play-Lists / 'ctrl' File / [midi-control]} \label{subsec:playlist_rc_file_midi_ctrl} The MIDI control stanzas for play-list and song-selection don't quite follow the toggle/on/off convention of the \texttt{[midi-control]} section, though the layout is the same: \begin{verbatim} Key Load-by-number Next Previous 24 "F2" [ 144 2 1 127 ] [ 144 4 1 127 ] [ 144 0 1 127 ] # Play List 25 "F3" [ 144 5 1 127 ] [ 144 3 1 127 ] [ 144 1 1 127 ] # Play Song \end{verbatim} \index{arrow keys} \index{keys!arrow} Note that the F2/F3 key assignments are not working at this time, but the four arrow keys are hard-wired for use in navigating the play-lists and songs. The \texttt{Up} and The \texttt{Down} keys move to the previous or next playlist. The \texttt{Left} and The \texttt{Right} keys move to the previous or next song in the current playlist. To use the arrow keys with the loaded playlist, first click on the live grid in the main window to give it focus. Both lines specify setting the next playlist or song according to a MIDI data value, or via "next" and "previous" controls. The "next" and "previous" controls can be implemented by any MIDI event, including \textsl{Note On} or \textsl{Program Change}. However, the "value" section requires a MIDI event that provides a \texttt{d1} (second data byte) value, such as velocity, because this value is used as the MIDI control number to select a playlist or song item. So, the following setting, \begin{verbatim} 24 "F2" [ 0x90 2 1 127] . . . \end{verbatim} specifies that a \textsl{Note On} event with channel 0 (144 = 0x90) on note \#2 with a velocity between the range 1 to 127 will select a play-list. However, this selection will be made only if the velocity ranges from 1 to 127, and there exists a selection with that velocity in the play-list file. This control requires a controller device that can be configured to provide the exact \textsl{Note On} event, including the exact velocity. \subsection{Seq66 Play-Lists / Command Line Invocation} \label{subsec:playlist_cmd_line} The command-line options to specify (and activate) the play-list feature are: \begin{verbatim} -X playlistfile --playlist playlistfile \end{verbatim} The play-list file is either a base-name (e.g. \texttt{sample.playlist}) or a name that includes the full or partial path to the play-list file (e.g. \texttt{data/sample.playlist}). If no path is specified, the directory is the currently set \textsl{Seq66} configuration-file directory. For session (e.g. NSM) support, one must stick with the configuration directory; do not provide an explicit directory-name. Please note that any play-list file specified on the command line, or loaded in the play-list user-interface, will be written into the 'rc' file's \texttt{[playlist]} section when \textsl{Seq66} exits. \subsection{Seq66 Play-Lists / Verification} \label{subsec:playlist_verify} When \textsl{Seq66} loads a play-list file, the \texttt{deep-verify} option allows every song in the play-list file to be verified by loading it. If any load fails, then the playlist will fail to load. This check can be slow when there are many large MIDI files specified in the play-list file. \subsection{Seq66 Play-Lists / User Interface} \label{subsec:playlist_uis} Playlists and songs can be selected or moved-to via keystrokes or user-interface actions, in addition to MIDI control. The \texttt{Up} and \texttt{Down} arrows move forward or backward through the list of play-lists, and the The \texttt{Right} and \texttt{Left} arrows move forward or backward through the list of songs for the currently-selected play-list. The Qt 5 user-interface supports the display, selection, and editing of the play-lists and the song-list for each play-list. There are still some minor issues to work out. If encountered, close \textsl{Seq66} and edit the \texttt{.playlist} file manually. It is self-documenting. \begin{figure}[H] \centering % \includegraphics[scale=0.65]{tabs/playlist/personal-playlist-light.png} \includegraphics[scale=0.65]{tabs/playlist/personal-playlist-new.png} \caption*{Seq66 Playlist Tab} \end{figure} There is a lot to talk about in this tab. It has recently been modified to some extent, so note the changes. Play-list specification: \begin{enumber} \item \textbf{Playlist File}. This field displays the path to the loaded play-list file. It is not editable. Remember that a play-list file can contain multiple play-lists. \item \textbf{Playlist File Selection}. These fields are editable, with the intent to use them to add a new play-list or modify the current one. \begin{enumber} \item \textbf{Selection}. This section has a button labelled \textbf{...} that replaces the old \textbf{Load List} button. It opens a file-dialog to select an existing play-list file, reading it in, and logging the play-list directory sub-play-lists into the following items. \item \textbf{Song Dir}. This field displays the main MIDI-file directory for the play-list currently selected in the table. The \textbf{Song Dir} is where the MIDI files reside by default. \item \textbf{MIDI \#}. This combo-box shows the MIDI control number for the current play-list, and the name of the selected play-list. Note that the \textbf{MIDI \#} combo-box provides values from 0 to 127 which can be selected or edited. The entries are ordered by this number in the table. When a new play-list is specified, this number is set to one past the highest existing play-list number. It can be modified as long as it's not the same as another MIDI number. \item \textbf{Name}. This field shows the user's name for the selected playlist. \end{enumber} % A file-name can include a different path, however. \item \textbf{Playlist Names}. This table shows the MIDI-control number and the name of each play-list. \item \textbf{List Buttons}. These buttons are described below. See \sectionref{subsubsec:playlist_ui_playlist_buttons}. Please note that, in some cases, the exact functionality might not be perfected. \end{enumber} Song specification: \begin{enumber} \item \textbf{Song Selection}. These fields display the MIDI-file directory, the MIDI control number, and the file-name of the selected play-list. \begin{enumber} \item \textbf{Selection}. This section has a button labelled \textbf{...} that replaces the old \textbf{Load Song} button. It opens a file-dialog to select an existing MIDI file and logging the file's directory and file-name into the following items. \item \textbf{MIDI Dir}. This field displays the directory holding the selected MIDI file. Note that the directory is normally the play-list directory, but a path present in the MIDI file-name overrides that directory, and then an asterisk is shown to flag that status. \item \textbf{MIDI \#}. The MIDI control number field is editable, with the intent to use it to add a new song. This combo-box shows the MIDI control number for the current MIDI file. Note that the \textbf{MIDI \#} combo-box provides values from 0 to 127 which can be selected or edited. The entries are ordered by this number in the table. When a new MIDI file is specified, this number is set to one past the highest existing MIDI file number. It can be modified as long as it's not the same as another MIDI number. \item \textbf{Name}. Holds the name of the selected MIDI file. It is not editable because it needs to be an existing MIDI file. To assign the MIDI file a readable base name, give it a long file-name (such as \texttt{Live Play Version of Opus 11.midi}). \end{enumber} \item \textbf{Song Files in List}. This table shows the MIDI-control number and the name of each song. \item \textbf{Song Files in List Buttons}. These buttons are described below. See \sectionref{subsubsec:playlist_ui_song_buttons}. Please note that, in some cases, the exact functionality is still being worked out or perfected. \end{enumber} \subsubsection{Seq66 Play-Lists / User Interfaces / Playlist Buttons} \label{subsubsec:playlist_ui_playlist_buttons} This section briefly describes the "List" buttons to the right of the play-list table. \begin{itemize} % \item \textbf{Load List}. \item \textbf{Create File}. \item \textbf{Add List}. \item \textbf{Modify}. \item \textbf{Delete}. \item \textbf{Save Lists}. \end{itemize} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. % \itempar{Load List}{playlist editor!load list} % This button brings up the "Open play-list file" dialog, using the % \textsl{Seq66} configuration directory as the default directory. % It is recommended to use only this directory, especially when running in a % session manager. If loaded from somewhere else, save the file back to the % configuration directory. \itempar{Create File}{playlist editor!create file} This button brings up file dialog; one selects the desired play-list directory and types in the name of the new play-list file. The file isn't actually created until \textbf{Save Lists} button is clicked. \itempar{Add}{playlist editor!add list} This button is enabled with the editing of the \textbf{Playlist Selection} fields. Once these three fields are correct, the new play-list can be added. The new play-list can then be populated with songs. \itempar{Modify}{playlist editor!add list} This button is enabled with the editing of the \textbf{Playlist Selection} fields. Once these three fields are correct, the list can be modified. \itempar{Delete}{playlist editor!delete list} This button removes the currently-selected play-list from the play-list file. This action doesn't take effect until the play-list file is saved or \textsl{Seq66} exits and does its normal saving. \itempar{Save Lists}{playlist editor!save lists} This button bring up a file dialog to save the current play-lists and songs into the specified play-list file. \subsubsection{Seq66 Play-Lists / User Interfaces / Song Buttons} \label{subsubsec:playlist_ui_song_buttons} This section briefly describes the "Song" buttons to the right of the song-list table. \begin{itemize} % \item \textbf{Load Song}. \item \textbf{Add Song}. \item \textbf{Modify}. \item \textbf{Delete}. \item \textbf{Lists Active}. \end{itemize} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. % \itempar{Load Song}{playlist editor!load song} % This button bring up a dialog to open a MIDI or WRK file from % the current song-directory or from an arbitrary directory. % Currently, be careful with this option; adding a file from an arbitrary % directory will generally prepend that directory to the MIDI file-name, % making the song list difficult to read. \itempar{Add Song}{playlist editor!add song} This button is meant to add a song already loaded in the \textbf{Live} frame into the play-list. Just open a new tune, test it, and then add it to the play list. Note that currently one may load a new tune into the playlist from anywhere a song is allowed to be loaded by the session. \itempar{Modify Song}{playlist editor!modify song} This button is meant to modify song information. However, the only item that can be altered is the MIDI control number. \itempar{Delete Song}{playlist editor!delete song} This button deletes the currently-selected song from the song list. \itempar{Lists Active}{playlist editor!lists active} If checked, the play-list is enabled, and the arrow keys, automation keys, and MIDI controls (if configured) can be used to move between play-lists and songs. Note that this check-box will not work unless there are play-lists and songs that exist in the tables and on the computer's file system. \subsubsection{Seq66 Play-Lists / User Interfaces / Info Fields} \label{subsubsec:playlist_ui_info_fields} The following read-only fields show some information about the file-system for the play-lists. \begin{itemize} \item \textbf{MIDI Base Directory}. Provides the top-most directory where all of the files in the play-list are stored. Currently read-only, in order not to interfere with session locations. \item \textbf{Current Song Path}. Shows the exact path the the currently-selected song. Currently read-only, in order not to interfere with session locations. \end{itemize} These items can be modified, however, by editing the play-list file directly. In addition, the following check-boxes set a couple of play-list features. \begin{itemize} \item \textbf{Lists active}. This items reflects the status of the play-list being active as configured in the 'rc' file. When a new play-list file is created, this item can be check-marked if the play-lists and their songs are basically valid. \item \textbf{Auto: Arm}. This item sets the \texttt{unmute-next-song} (unfortunate name!) item in the 'playlist' file. \item \textbf{Auto: Play}. This item sets the \texttt{auto-play} item in the 'playlist' file. If set, the the \textbf{Arm} setting is automatically set as well. When set, the next song starts playing as soon as it is loaded. \item \textbf{Auto: Advance}. This item sets the \texttt{auto-advance} item in the 'playlist' file. If set, the the \textbf{Play} and \textbf{Arm} settings are automatically set as well. When set, at the end of one song, the next song is automatically loaded and played as well. \end{itemize} There is currently no \texttt{deep-verify} check-box; edit the 'playlist' file directly. \subsubsection{Seq66 Play-Lists / Creating a Playlist File} \label{subsubsec:playlist_creating_playlist_file} The "programmer's method" for creating a play-list file is simply to copy one and carefully modify it in a nice programmer's editor. This section describes creating a new play-list in the \textbf{Playlist} tab. We might add pictures are some point. Let's assume this is the first run of \textsl{Seq66}. The file \textsl{qseq66.rc} is not created until after the first run exits. However, the configuration directory \texttt{\textasciitilde/.config/seq66/} is created at startup. Also, the default play-list file is set as \texttt{\textasciitilde/.config/seq66/qseq66.playlist}. This file starts as a skeleton file, containing no play-lists. But on our first run, we want to build a play-list, "from scratch", using the set of sample files files installed in \texttt{/usr/share/seq66-0.99/midi}. Start \textsl{Seq66} and select the \textbf{Playlist} tab. Note the \textbf{Playlist File} is set to \texttt{\textasciitilde/.config/seq66/qseq66.playlist}, and there are no play-lists and songs present. For this mini-tutorial we \textsl{ignore} the \textbf{Playlist File Selection} button (labelled \textbf{...}). Click the \textbf{Create File} button. A file dialog shows the default directory \texttt{\textasciitilde/.config/seq66/}, and the default file \texttt{qseq66.playlist}. For this tutorial, type in the file-name \texttt{tutorial.playlist}. % Obviously, we have created the directory % \texttt{\textasciitilde/.config/seq66/tute/} ahead of time. % Select the \texttt{tute} directory and change the % \textbf{File name} to \texttt{tute.playlist}. Click \textbf{Save} button. \textsl{Seq66} attempts to load the play-list file, but it does not yet exist. Shown in the \textbf{Playlist File} field is \begin{verbatim} ~/.config/seq66/tutorial.playlist \end{verbatim} Now click the \textbf{Song Dir} button, and navigate to the \texttt{/usr/share/seq66-0.99/} directory and select the \texttt{midi} directory. Just select this directory, do not descend into it. Click \textbf{OK} or \textbf{Choose}. Change the play-list \textbf{Name} from "midi MIDI Files" to "Installed MIDI Files". The \textbf{Add List} button becomes enabled; click it. Click on the \textbf{Song Selection} button. Navigate to where the desired play-list MIDI files are stored (the previously selected \texttt{midi} directory). Select the first desired MIDI file and click \textbf{OK} or \textbf{Open}. If it's the desired directory and file, then click the \textbf{Add Song} button. If the MIDI file's directory is \textsl{not} the same as the play-list's \textbf{Song Dir}, the song's \textbf{MIDI Dir} field holds the MIDI file's path, and there is an asterisk to indicate it's a MIDI-file-specific directory. Add more songs and play-lists as desired. Note that the \textbf{MIDI \#} number self-increments, but these can later be modified. Next, click \textbf{Lists active} to enable the new lists. Click \textbf{Auto arm} if desired. Click \textbf{Save Lists}. If desired, open the new play-list file in a text editor and see what it looks like. % Save list needs to make the playlist directory and properly write % the file to it. (No, we can't make a new directory here, only % look for it.) \subsubsection{Seq66 Play-Lists / Playlist Sample} \label{subsubsec:playlist_playlist_sample} This section describes the sample play-list and the MIDI data files it uses: \begin{verbatim} /usr/share/seq66-0.99/samples/ca_midi.playlist /usr/share/seq66-0.99/midi \end{verbatim} The \texttt{/usr/share/} directory might be \texttt{/usr/share/local/} if installed from source code. It has three play-lists. \begin{verbatim} [playlist] number = 0 name = "Legacy Midi Files" directory = "/usr/local/share/seq66-0.99/midi/FM/" 0 "brecluse.mid" 1 "carptsun.mid" 2 "cbflitfm.mid" \end{verbatim} and, without showing the MIDI files, \begin{verbatim} number = 1 name = "PSS-790 Midi Files" directory = "/usr/local/share/seq66-0.99/midi/PSS-790/" \end{verbatim} and \begin{verbatim} number = 3 name = "Live vs Song Files" directory = "/usr/local/share/seq66-0.99/midi/" \end{verbatim} Each play-list can be accessed by its number or by the "Play List" automation command (default key: \texttt{F2}). The MIDI control can be set up to move forward or backward, or access a list by number. Each song in the play-list can be accessed by its number or by the "Play Song" automation command (default key: \texttt{F3}). The MIDI control can be set up to move forward or backward, or access a song by number. See the sample \texttt{qseq66.ctrl} file's comments for the mappings. Play-list items can also be controlled from the \textbf{Playlists} tab. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/port_mapping.tex ================================================ %------------------------------------------------------------------------------- % port_mapping %------------------------------------------------------------------------------- % % \file port_mapping.tex % \library Documents % \author Chris Ahlstrom % \date 2020-12-29 % \update 2025-06-19 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides a discussion of the MIDI GUI port_mapping that Seq66 % supports. % %------------------------------------------------------------------------------- \section{Port Mapping} \label{sec:port_mapping} \textsl{Seq66}, like \textsl{Seq24}, bases its I/O port scheme on buss/port \textsl{numbers}. This port numbering applies whether \textsl{ALSA}, \textsl{JACK}, or \textsl{Windows Multimedia} are used as the MIDI engine, and whether \textsl{Seq66} is running with "automatic" ports or "manual" (virtual, software-created) ports. These buss numbers range from 0 on upward based on the number of I/O MIDI ports active in the system. In the normal "automatic" (non-virtual, non-manual) mode these ports represent the hardware MIDI ports and application MIDI ports. In "manual" mode, these ports represent virtual MIDI ports that the user can connect manually. When the set of MIDI devices and applications (e.g. \textsl{Qsynth} changes, and \textsl{Seq66} is restarted, this numbering changes. A MIDI tune the depends on the old port numbers will not play correctly. Port-mapping fixes this issue; it looks up the port number on which a pattern depends, finds the device name associated with this port, and looks up the actual port number by name. \textbf{Note}: Port-mapping is now the default; MIDI I/O port-maps are created at first start, and are always active if virtual ports are not in use. Once created, one can edit the 'rc' file to rearrange the mapping as desired. (There is no way to do this in the \textsl{Seq66} user interface.) Issues with new or unavailable ports are noted in a warning dialog at start-up. (The startup warning can be suppressed; see \sectionref{subsection:edit_preferences_display}.) The user can click the \textbf{Remap and restart} button, fix the loaded pattern to use existing ports, click \textbf{OK} to ignore the warning, or exit, determine the existing ports using \texttt{aplaymidi}, \texttt{arecordmidi}, or \texttt{jack\_lsp}, and edit the maps directly in the 'rc' file. The output bus or port for a given pattern can be determined by looking at the grid slots or by dumping a summary of the song to a text file. See \sectionref{subsubsec:menu_help_song_summary_file}. Also check the specified 'ctrl' file to see if it is using non-existent MIDI I/O ports. A pattern/loop/sequence is assigned to output to a given port via a buss number saved \textsl{in the pattern}, in the tune. When a tune is loaded, each pattern outputs to the port number specified in the pattern. A problem is that MIDI device setups can change, with devices being reordered, removed, or added to the MIDI devices available on the system. Or if the song is opened on someone else's computer. We do not want to store port names in the MIDI file. They can be too long, but, more importantly, they will differ between the systems of each user. They can even differ when switching from ALSA to JACK, or even versions of these MIDI engines. Better to let the user determine the port-mapping by editing the 'rc' file. Mapping allows the buss number stored with a pattern to be remapped to another buss number based on the "nick-name" (or JACK alias) of the port. It uses a simple lookup to map names to numbers. The "nick-name" is a shortened version of the MIDI device name assigned by the system. For example, the long name of a MIDI port might be \texttt{[5] 44:0 E-MU XMidi1X1 Tab MIDI 1}. The nick-name is \texttt{E-MU XMidi1X1 Tab MIDI 1}. In order find the correct port number, the long name is checked to see if it \textsl{contains} the nick-name, and, if so, the corresponding port number is returned. The user can edit the 'rc' file to shorten the nick-name, if desired; a nick-name \texttt{E-MU XMidi1X1} would work. In addition, under recent versions of \textsl{JACK}, there is a facility to get the "alias" of USB MIDI ports, even if the \textsl{a2jmidid} process is not running. This allows the user to see that the port name \texttt{system:midi\_capture\_5} is actually a "nanoKEY2" device. So, with port-mapping enabled, one can set up the tune to record and play MIDI using the mapped ports, and later move to another computer, modify the port-maps in the 'rc' file to match, manually, and record and play without issue. The easiest way to start port-mapping (which is now automatic) is to go to \textbf{Edit / Preferences / MIDI Clock}. See \sectionref{subsection:edit_preferences_midi_clock}. There, we see long MIDI port names made up by the system, along with the \textsl{JACK} aliases that can be retrieved, and which make it easy to see which devices are in play. \begin{comment} \begin{figure}[H] \centering \includegraphics[scale=0.75]{main-menu/edit/preferences/midi_clock_pre_portmap.png} \caption{Clocks List Without Port Mapping} \label{fig:clocks_list_before_port_mapping} \end{figure} Note the \texttt{system:midi\_playback} that is part of each port name. (There is a similar "capture" portion for input ports). \end{comment} Click on the \textbf{Make Maps} button. This creates the initial I/O maps internally. Then either restart \textsl{Seq66} or go to the \textbf{Restart Seq66} button; this will save the new version of the 'rc' file. Back in \textbf{Edit / Preferences / MIDI Clock}, one sees that the remapped names are in use. \begin{figure}[H] \centering \includegraphics[scale=0.50]{main-menu/edit/preferences/midi_clock_post_portmap.png} \caption{Clocks List With Port Mapping} \label{fig:clocks_list_with_port_mapping} \end{figure} % Note that the short names generated are partial names of either the ALSA % port name or are comprised of the JACK alias name for each port. At startup, \textsl{Seq66} matches the port-map to the ports that exist on the system. If there is no matching system port for a mapped port, then that mapped port will show up as disabled in the port lists, and a warning should appear. If it makes sense, click on the \textbf{Remap and restart} button. Or later, go to \textbf{Edit / Preferences / MIDI Clock}. There, to disable the port mappings if desired, unset the \textbf{MIDI I/O Port Maps} check-box. To reconstruct the current setup, click on the \textbf{Recreate} button to get the new mapping, and restart manually. As with the normal port listings, the port-mappings are saved and managed in the \textsl{Seq66} 'rc' file. One can also edit that file in a text editor to rearrange the mapped ports. Note that one can also deactivate port-mapping. Both input and output will always be in the same state (activated or deactivated). \subsection{Input Port Mapping} \label{subsec:input_port_mapping} The input ports are also port-mapped. Here's an initial system input setup: \begin{verbatim} [midi-input] 6 # number of MIDI input (or control) buses 0 1 "[0] 0:1 system:ALSA Announce" 1 1 "[1] 14:0 Midi Through Port-0" 2 1 "[2] 28:0 Launchpad Mini MIDI 1" 3 1 "[3] 32:0 Q25 MIDI 1" 4 1 "[4] 36:0 nanoKEY2 nanoKEY2 _ CTRL" 5 1 "[5] 40:0 MPK mini Play mk3 MIDI 1" \end{verbatim} % To see an example from ALSA, look at the sample file % \texttt{data/linux/qseq66.rc}. % ALSA does not support aliases. % (Neither do early versions of JACK.) Note that the ALSA system "announce" buss is always disabled, as \textsl{Seq66} does not use it. In a future version of \textsl{Seq66} it may be removed, as it means ports have to be renumbered when switching to JACK. Doh! Here is the stored input port-map: \begin{verbatim} [midi-input-map] 0 1 "ALSA Announce" 1 1 "Midi Through Port-0" 2 1 "Launchpad Mini MIDI 1" 3 1 "Q25 MIDI 1" 4 1 "nanoKEY2 nanoKEY2 _ CTRL" 5 1 "MPK mini Play mk3 MIDI 1" \end{verbatim} In the user interface dropdowns for input buss, if a map is active, it is put into the dropdown; any missing items are noted and are shown as disabled. If port-mapping is not active, then only the actual system input ports are shown. \subsection{Output Port Mapping} \label{subsec:output_port_mapping} Assume that the system has the following set of ports. These busses are stored in the 'rc' file when \textsl{Seq66} exits. % Note the \textsl{JACK} % aliases shown at the right as comments. \begin{verbatim} [midi-clock] 5 # number of MIDI clocks (output/display buses) 0 0 "[0] 14:0 Midi Through Port-0" 1 0 "[1] 28:0 Launchpad Mini MIDI 1" 2 0 "[2] 32:0 Q25 MIDI 1" 3 0 "[3] 36:0 nanoKEY2 nanoKEY2 _ CTRL" 4 0 "[4] 40:0 MPK mini Play mk3 MIDI 1" \end{verbatim} % To see an example from ALSA, look at the sample file % \texttt{data/linux/qseq66.rc}. If some items are unplugged, then this list will change, so we save it while still running \textsl{Seq66}: click the \textbf{Make Maps} button in the \textbf{Edit / Preferences/ MIDI Clock} dialog. The result is are new sections in the 'rc' file (one for clocks, one for inputs). Here is the clock map: \begin{verbatim} [midi-clock-map] 1 # map is active 0 0 "Midi Through Port-0" 1 0 "Launchpad Mini MIDI 1" 2 0 "Q25 MIDI 1" 3 0 "nanoKEY2 nanoKEY2 _ CTRL" 4 0 "MPK mini Play mk3 MIDI 1" \end{verbatim} The map shows the nick-names or aliases of the ports. These index numbers can be used as buss numbers: they can be stored in a pattern, and used to direct output to a specific device. If a pattern has stored a missing item as its output buss number, this number will not be found in the system list, so that the pattern will need to be remapped to an existing port. Note that the port-mapping can be disabled by setting the first value to 0. In that case, \textsl{Seq66} uses buss numbers in the normal way. In the user interface dropdowns for output buss, if a map is active, it is put into the dropdown; any missing items are noted and are shown as disabled. If the map is not active, then only the actual system output ports are shown in the user interface. \subsection{Port Mapping for MIDI Control/Display} \label{subsec:input_port_mapping_example} This example shows how MIDI control and MIDI status displays work with port mapping. First, we run \textsl{Seq66}, save the ports for remapping, and exit the application. Looking in the 'rc' file, we tweak the maps: \begin{verbatim} [midi-input-map] 1 # map is active 0 0 "announce" 1 0 "Midi Through Port-0" 2 0 "Launchpad Mini MIDI 1" 3 0 "nanoKEY2 MIDI 1" 4 0 "Q25 MIDI 1" 5 0 "E-MU XMidi1X1 Tab MIDI 1" \end{verbatim} And: \begin{verbatim} [midi-clock-map] 1 # map is active 0 0 "Midi Through Port-0" 1 0 "Launchpad Mini MIDI 1" 2 0 "nanoKEY2 MIDI 1" 3 0 "Q25 MIDI 1" 4 0 "E-MU XMidi1X1 Tab MIDI 1" \end{verbatim} These two maps reflect the configuration at the time they were saved. They reflect the output of \texttt{arecordmidi -{}-list} and \texttt{aplaymidi -{}-list}. After unplugging and replugging some devices, we see that the \textsl{Launchpad Mini} has moved: \begin{verbatim} 6 # number of input MIDI busses 3 0 "[3] 36:0 Launchpad Mini MIDI 1" 5 # number of MIDI clocks (output busses) 2 0 "[2] 36:0 Launchpad Mini MIDI 1" \end{verbatim} On input, it has moved from buss 2 to buss 3. On output it has moved from buss 1 to buss 2. This can be verified by running \textsl{Seq66}, immediately exiting, and checking the \texttt{qseq66.rc} file. We edit that file to add: \begin{verbatim} [midi-control-file] "qseq66-lp-mini-alt.ctrl" \end{verbatim} With the I/O maps shown above active in the 'rc' file, we can go to the 'ctrl' file (\texttt{qseq66-lp-mini-alt.ctrl}, available in the \texttt{data/linux} install/source directory) and set the following: \begin{verbatim} [midi-control-settings] control-buss = 2 # maps to system input buss 3 midi-enabled = true [midi-control-out-settings] output-buss = 1 # maps to system output buss 2 midi-enabled = true \end{verbatim} With this setup, the lights on the Mini light-up at start-up, and the buttons control the pattern, mute-groups, and automation features set up in the above-mentioned 'ctrl' file. The buss number will be replaced with the name of the device, e.g. \texttt{output-buss = "Launchpad Mini"}, if port-mapping is active. This is tricky, but once set up, one can reliably use these mapped port numbers to look up the actual system port numbers. For example, with the above setup, \textsl{Seq66} can be assured that output buss 1 will always go to the \textsl{Launchpad Mini}. \subsection{Port Setting SeqSpec} \label{subsec:port_seqspec} In the MIDI specification, there are two obsolete MIDI Meta events, "MIDI Channel" (0x20) and "MIDI Port" (0x21). These events were never endorsed by the MIDI Manufacturers Associated, but some versions of \textsl{Cakewalk} used them. In any case, one does not generally change the channel and port during playback. However, \textsl{Seq66} needs to set the channel and port in order to determined where events in a pattern are output. These are specified by sequencer-specific (SeqSpec) events stored with each pattern. If the patterns uses "Free" setting of channel (0x80), each channel event goes to whatever channel (0 through 15 internally) is specified in the event. If a channel is specified, then all channel events are redirected to that channel. See \sectionref{subsubsec:midi_format_track_seqspec_midichannel}. The port setting for each pattern is a number, as described earlier in this chapter. The output of a pattern goes to this numbered port in the midi-clock lists. Every pattern must have a port number. For imported tracks, this number defaults to 0. See \sectionref{subsubsec:midi_format_track_seqspec_midibus}. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/preferences.tex ================================================ %------------------------------------------------------------------------------- % preferences %------------------------------------------------------------------------------- % % \file preferences.tex % \library Documents % \author Chris Ahlstrom % \date 2015-08-31 % \update 2025-07-26 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides the Menu section of seq66-user-manual.tex. % %------------------------------------------------------------------------------- \section{Edit / Preferences} \label{sec:edit_preferences} \textbf{Preferences} provides a number of settings in one tabbed dialog, shown in the figures that follow. It allows one to set MIDI output (and clocking), MIDI Input, display tweaks, minor playback options, and some JACK parameters. Additional configuration items implemented in \textbf{Preferences} are incoming MIDI events to control the sequencer; outgoing MIDI events to light up the display on a MIDI controller; how the mouse works, and a few others. The MIDI and Key controls, far more numerous than in \textsl{Seq24}, have been consolidated into a 'ctrl' file and are fairly easy to edit with a text editor. \textsl{Seq66} does not support the 'fruity' mouse mode at this time. If you want it, ask us! \subsection{Edit / Preferences / MIDI Clock} \label{subsection:edit_preferences_midi_clock} \textbf{Note}: \textsl{The MIDI Clock tab is sometimes difficult to click on.} We are not sure why. It seems to be theme-dependent. In some themes the tab thumb changes color when it can work. Just keep clicking in various locations in the tab, or use the \texttt{Alt-C} key. Weird. The \textbf{MIDI Clock} tab provides a way to set MIDI clocking and the available MIDI output busses. It configures the output busses for MIDI clock and data. It shows the devices that can play music. The items that appear in this tab depend on: \begin{itemize} \item What MIDI sources and sinks are available: MIDI controllers, USB MIDI cables, applications with virtual ports, and other connected devices will add MIDI output devices (ports) to the system. This list will generally match the output of \texttt{aplaymidi -l} or \texttt{aconnect -lio}. \item The setting of the "manual-ports" option, which tells \textsl{Seq66} to set up virtual MIDI ports. It is enabled by the \texttt{-{}-manual-ports} command-line option or the \texttt{[manual-ports]} section of the \texttt{qseq66.rc} configuration file, or in the \textbf{MIDI Input} tab described below. \item The setting of the \textsl{Seq66}-specific "reveal ALSA ports" option, \texttt{-{}-reveal-ports} command-line option or the \texttt{[reveal-ports]} section of the \texttt{qseq66.rc} configuration file. \end{itemize} If \texttt{-{}-manual-ports} is on, this list shows the virtual MIDI output busses that \textsl{Seq66} can drive. One needs to use a JACK or ALSA MIDI connection application to connect a device on each of those outputs. (But \textsl{Seq66} itself can connect to another instance of \textsl{Seq66}. See \sectionref{sec:recording_seq_to_seq}. The fact that the the buss names can start with different numbers, depending on the system setup, can complicate the playing of MIDI in this manner. Also, the 'usr' configuration file can change the visible names of the ports to match specific equipment attached to the ports. \begin{figure}[H] \centering \includegraphics[scale=0.50]{main-menu/edit/preferences/midi_clock_tab.png} \caption{MIDI Clock (Output) Tab} \label{fig:midi_clock_tab} \end{figure} This diagram shows the tab for configuring MIDI output and clocking features. Port-mapping is the default, and when active, the names of the ports in the map are shown in this pane. If there are more than about a dozen output ports in the system, a vertical scrollbar appears. The following elements are present in this tab: \begin{enumber} \item \textbf{Ports and Clocking} \item \textbf{Clock Start Modulo} \item \textbf{Buss Override} \item \textbf{MIDI I/O Maps} \item \textbf{Create (maps)} \item \textbf{Clear (maps)} \item \textbf{MIDI Control Out Bus} \item \textbf{Meta Events} \item \textbf{BPM Precision} \item \textbf{Client Name:ID/UUID} \item \textbf{Restart Seq66!} \end{enumber} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Ports and Clocking}{output!ports and clocks} \index{output ports} This table shows the available MIDI outputs and their status. If the set of system MIDI devices and software devices has changed since the last run, this list could be in error. Restart the application and see if it is now correct. Currently, there is no way to edit the list except in the 'rc' file. The \textbf{Ports and Clocks} table contains the following elements, although some can be removed by specifying the \texttt{port-naming = short} option in the 'rc' file. This option is preferred when using JACK. \begin{enumber} \item \textbf{Index Number} (optional) \item \textbf{Client Number} (optional) \item \textbf{Port Number} (optional) \item \textbf{Buss Name} \item \textbf{Disabled} \item \textbf{Off} \item \textbf{On (Pos)} \item \textbf{On (Mod)} \item \textbf{Clock Start Modulo} \end{enumber} The format of the left side of the entry listing is like the following when the port-naming option is "long", and the MIDI subsystem is ALSA): \begin{verbatim} [5] 128:4 yoshimi:input ^ ^ ^ ^ ^ | | | | | | | | | ----- Port/buss name | | | ------------- Client name | | --------------- Port/buss number | ------------------- Client number ---------------------- Index number \end{verbatim} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Index Number}{midi clock!index number} \index{index number} The number in square brackets is an ordinal indicating the position of the output buss in the list. For all practical purposes in \textsl{Seq66}, it \textsl{is} the buss/port number. This number can be stored in a pattern in order to have the pattern's output go to that buss. This is true even if port-mapping is in place. \index{port!mapping} \index{buss!mapping} \index{port!override} \index{buss!override} It can be used with the \texttt{-b}, \texttt{-{}-buss}, or \texttt{ -{}-bus} options to redirect \textsl{all} pattern output to that buss, useful if only one buss is active or the \textsl{Seq66} patterns route to non-existent busses. (See \sectionref{subsubsec:introduction_sets_buss_override}, and \sectionref{subsubsec:usr_file_user_midi_settings}.) \itempar{Client Number}{midi clock!client number} \index{client number} The number that precedes the colon is the "client number". It is useful mainly in ALSA, where clients can have numbers like "14", "128", "129", etc. For native JACK mode, it matches the index number. \itempar{Port Number}{midi clock!port number} \index{port number} The number that follows the colon is the "port number". It is useful mainly in ALSA. For native JACK mode, it matches the index number. \itempar{Buss Name}{midi clock!buss name} \index{port name} \index{midi clock!port name} These labels indicate the output busses (ports) available. \textsl{Seq66} does not access devices by name, but by port number. However, a port-map can be created to make it possible to find the correct buss / port number by name lookup. \itempar{Disabled}{midi clock!port disabled} The \textbf{Disabled} clock choice marks an output port that the user does not want to use, or that the operating system (\textsl{Windows} \smiley) is locking or disabling. Normally, this inaccessible port would cause issues. With the port disabled, the inaccessible port is ignored. This feature also shows when a port-map cannot find a device in the system's device list. When the \textsl{Windows} version of \textsl{Seq66} (\texttt{qpseq66.exe}) is first started, it may error out. It will then write a default \texttt{qseq66.rc} or \texttt{qpseq66.rc} configuration file, which can be examined to find the offending buss, which can then be marked in the normal 'rc' file as disabled. \itempar{Off}{midi clock!off} Disables the MIDI \textsl{clock} function for the given output buss. MIDI output is still sent to those ports, and each port that has a device connected to it will play music. Some synthesizers may require this setting. \itempar{On (Pos)}{midi clock!on (pos)} MIDI clock will be sent to this buss. MIDI Song Position and MIDI Continue will be sent if playback starts at greater than tick 0 in Song mode. Otherwise, MIDI Start will be sent. Note: In case of trouble, see \sectionref{subsec:alsa_testing}. \itempar{On (Mod)}{midi clock!on (mod)} MIDI clock will be sent to this buss. MIDI Start will be sent, and clocking will begin once the Song Position has reached the start modulo of the specified size (see the next item's description). This setting is used for gear that does not respond to Song Position. Below the \textbf{Ports and Clocks Table} are more configuration elements. \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Clock Start Modulo (ticks)}{midi clock!clock start modulo} This value starts at 1 and ranges up to 16384, and defaults to 64 ticks. It is used by the \textbf{On (Mod)} setting discussed above. It is the \texttt{[midi-clock-mod-ticks]} option in the \textsl{Seq66} 'rc' file. \itempar{MIDI I/O Port Maps}{midi clock!port maps} If checked (the default), then port-mapping is employed. This makes it a bit easer to manage MIDI devices across systems and to store the port/buss numbers in each pattern. Note that both input and output port mappings are activated by this checkbox. % If changed, the \textbf{Restart Seq66!} button is enabled. \itempar{Create (maps)}{midi i/o!port mapping} \index{port mapping} \index{port!mapping} Pressing this button saves the current set of MIDI I/O ports to sections in the 'rc' file. These sections can be enabled in order to support port-mapping in subsequent runs of \textsl{Seq66}. After pressing this option, one might want to stop \textsl{Seq66}, rearrange the clock and input maps in the 'rc' file with a text editor, back up this file in a safe place, and restart \textsl{Seq66}. \itempar{Clear (maps)}{midi i/o!remove mapping} \index{remove mapping} \index{port! remove mapping} Pressing this button removes the port mapping. \index{restart!manual} Once done, either restart \textsl{Seq66} or go to the \textbf{Session} tab and click the \textbf{Restart} button. (See \sectionref{subsec:concepts_reload_session}.) \itempar{MIDI Control Out Bus}{midi control!output buss} \index{midi control!output} Use this control to select the output bus used to display application-automation status, loop status, and mute-group status. Requires a reload to take effect. The number of the buss is stored in the 'ctrl' file named in \sectionref{subsection:edit_preferences_session}, as the value of \texttt{output-buss}. If port mapping is enabled (now the default), the nick-name of the bus is stored instead of the number. The device on the output buss must have a corresponding 'ctrl' file output section properly defined. \index{MIDI Thru!warning} \textsl{Do not use the ALSA MIDI Through ports for both control and display. Use an actual device port.} \itempar{Meta Events}{midi clock!meta events} \index{tempo-track-number} This section consists of the following items: \begin{enumerate} \item \textbf{Tempo track number} \item \textbf{BPM Precision} \item \textbf{Set Tempo Track} \end{enumerate} \textbf{Tempo track number} allows the user to move the tempo track from pattern 0 to another pattern. Changing this option is not recommended, since track 1 (0) is the official track for tempo events, but \textsl{Seq66} allows the user to record tempo events to another track. \textsl{Seq66} will process tempo events in any pattern. \index{usr!bpm-precision} \textbf{Precision} allows setting the number of digits past the decimal point to 0, 1, or 2. This is also a 'usr' setting. See \sectionref{subsubsec:usr_file_user_midi_settings}. The BPM (tempo) is stored in the MIDI file multiplied by 1000 to accommodate the decimal places. \textbf{Set Tempo Track} Enabled when a valid tempo track number is given. It makes the tempo track official if it is not zero anymore. \itempar{Client Name:ID/UUID}{client ID} This read-only text field shows two things: \begin{enumerate} \item \textbf{Client Name}. This is the name of the client under ALSA or JACK. It defaults to \texttt{seq66}, but it can be altered by the command-line option \texttt{-{}-client-name} or by a session manager. Each instance of Seq66 run under ALSA will have a different client ID. \item \textbf{ID/UUID}. Under ALSA, the client number (client ID) is shown. Under JACK, the UUID that JACK assigned to \textsl{Seq66} is shown. \end{enumerate} \itempar{Restart Seq66!}{restart} Certain changes require a \textsl{Seq66} restart, unfortunately. When enabled, clicking this button does not exit \textsl{Seq66}, but it does cause most of the internal mechanisms to be recreated from scratch. In some cases, a change might require a complete exit and re-execution. % \index{todo!manual alsa gui option} % There is currently no user-interface item corresponding to the "manual-ports" % command-line and 'rc' configuration file option. % We should rename this option to "virtual" eventually. \subsection{Edit / Preferences / MIDI Input} \label{subsection:edit_preferences_midi_input} To set up \textsl{Seq66} to record MIDI from devices such as controllers and keyboards, the output of the ALSA MIDI recording command-line \texttt{arecordmidi -l} is relevant. Something like that listing appears in the Input tab: \begin{figure}[H] \centering \includegraphics[scale=0.50]{main-menu/edit/preferences/midi_input_tab-2.png} \caption{MIDI Input Tab} \label{fig:midi_input_tab} \end{figure} Port-mapping is the default, and when active, is shown in this pane. If there are more than about a dozen input ports in the system, then a vertical scrollbar appears. Any port checked allows \textsl{Seq66} to record MIDI from that source, which must be connected to this input port. \textbf{Warning:} \index{warnings!usr config} \index{usr config} If the \texttt{[user-midi-bus-definitions]} value in the 'usr' configuration file is non-zero, and the corresponding number of \texttt{[user-midi-bus-N]} settings are provided, then the list of existing hardware will be ignored, and those values will be shown instead. This feature can be overridden with the \texttt{-{}-reveal-ports} (\texttt{-r}) option. If you define these sections, they should match your hardware exactly, and your hardware should not change from session to session (or port-mapping should be enabled). If the "auto ALSA ports" option is turned on, via the \texttt{-a} or \texttt{-{}-auto-ports} option, then the input ports from the system are shown. \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Input Buses}{input buses} \textbf{Input Buses} delineates the MIDI input devices as noted above. \itempar{MIDI Control Input Bus}{midi control!input buss} \index{midi control!input} Use this control to select the input bus used for MIDI control automation of application actions, loop actions, mute-group actions and macros. Requires a reload to take effect. The number of the buss is stored in the 'ctrl' file named in \sectionref{subsection:edit_preferences_session}, as the value of \texttt{control-buss}. If port mapping is enabled (now the default), the nick-name of the bus is stored instead of the number. \index{MIDI Thru!warning} \textsl{Do not use the ALSA MIDI Through ports for both control and display. Use an actual device port.} \textbf{Input/Output Options}. \index{I/O options} \textbf{Input/Output Options} adds further refinements to MIDI input and output. It has the following settings: \itempar{Record-by-Bus}{record!by bus} \index{input by buss} \textbf{Record into patterns by buss} causes MIDI input from multiple busses to be distributed to each sequence according to MIDI input buss number. If set, it overrides the record-by-channel setting. \itempar{Record-by-Channel}{record!by channel} \index{input by channel} \textbf{Record into patterns by channel} causes MIDI input with multiple channels to be distributed to each sequence according to MIDI output channel number. Only one of these record-by options can be enabled at the same time. The record-by-buss option takes precedence. When these options are disabled, the normal recording behavior dumps all data into the current sequence, regardless of channel or buss. See \sectionref{sec:recording}, which describes recording in more detail. \itempar{Use virtual (manual) I/O ports}{ports!virtual} \textbf{Use virtual (manual) I/O ports} Virtual ports are called "manual ports" because the connections generally need to be made manually. This option allows for configuration of the manual-ports option from within the user-interace. Also included are settings for the number of manual input and output ports. (In \textsl{Seq24}, 16 output and 1 input port were created.) Once the option is enable A \textsl{reload session} (see \sectionref{subsec:concepts_reload_session}) is necessary for this option to take effect. \itempar{Auto-Enable virtual ports}{ports!virtual auto-enable} \textbf{Auto-enable virtual I/O ports} If set, the ports are all automatically enabled upon a restart. The following figure shows that a large number of virtual ports can be defined, and a vertical scroll-bar appears. \begin{figure}[H] \centering \includegraphics[scale=0.95]{main-menu/edit/preferences/midi_input_tab-virtual.png} \caption{MIDI Virtual Inputs} \label{fig:midi_input_tab_virtual} \end{figure} Note that the user is responsible for connecting the virtual MIDI ports, using something like \textsl{aconnect} (ALSA) or \textsl{qjackctl} (JACK). \subsection{Edit / Preferences / Keyboard (removed)} \label{subsection:edit_preferences_keyboard} Unlike \textsl{Seq24}, \textsl{Seq66} \textsl{does not} provide an options tab for setting up the keyboard. There are just too many new keystroke-automation functions to fit in a configuration dialog box. The default keyboard mappings follow \textsl{Seq24} fairly well, but adds a large number of additional controls; around 96 keystroke slots would need to be provided! The keystroke and MIDI controls are consolidated, and are easy to change by editing the appropriate 'ctrl' configuration file, stored in one of the following directories, depending on the operating system: \begin{verbatim} /home/username/.config/seq66/qseq66.ctrl (Linux) C:/Users/username/AppData/Local/seq66/qpseq66.ctrl (Windows) \end{verbatim} There are also some extended examples present in the \textsl{Seq66} \texttt{data/linux} and \texttt{data/samples} directory. Also see \sectionref{sec:launchpad_mini}. For more information on keystrokes, see \sectionref{subsec:kbd_mouse_keyboard_control}. One useful enhancement, though "costly", would be support of MIDI Learn. Currently the only "learnable" items are the mute groups. \subsection{Edit / Preferences / Mouse (removed)} \label{subsection:edit_preferences_mouse} Unlike \textsl{Seq24}, \textsl{Seq66} \textsl{does not} provide an options tab for the mouse-interaction method. It is not supported in \textsl{Seq66}... the \textbf{Fruity} interaction method is not available; only the \textbf{Seq24} interaction is available. \subsection{Edit / Preferences / Display} \label{subsection:edit_preferences_display} This dialog provides a few odds and ends to enhance the user-interface. Some of these items (plus a few more) can be configured by editing the 'usr' file. \begin{figure}[H] \centering \includegraphics[scale=0.50]{main-menu/edit/preferences/midi_display_tab.png} \caption{Display Options} \label{fig:midi_display_tab} \end{figure} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Editor Key Height}{key height} This option affects the pattern editor's piano roll. Smaller means a wider range of notes can be shown. There are also \textbf{-}, \textbf{0}, and \textbf{+} buttons in the pattern editor that provide vertical zoom. \itempar{Grid scaling \& spacing}{window scaling} These three items set scale factor for width and height of the main window, and adjust the spacing between the grid slots.. The lowest scale factor is 0.5, and the largest scale factor is 3.0. For the smallest window, the smallest practical values are 0.85 x 0.60. The spacing unit is pixels. \itempar{Set Size}{set-size} Provides a way to change the set size. The default is \textbf{4 x 8} (rows by columns), but we intend to support \textbf{4 x 4}, \textbf{8 x 8}, and \textbf{12 x 8} as well. \textbf{Warning}: A different set size alters the 'ctrl' file layout radically. We still have to work through all the implications of changing the set size, so back up your configuration and proceed with caution! \itempar{Progress Boxes}{progress-box size} Provides a way to change the size of the progress box in each button. Values are width and height fractions (up to 1.0) re the button size. This is a 'usr' option. The width can range from 0.5 to 1.0, and the height from 0.10 to 1.0. The default is 0.8 x 0.30. However ... \itempar{Progress Box Shown}{progress-box shown} ... If the \textbf{Shown} check-box is \textsl{unchecked}, then the progress boxes and pattern color are not shown. This is a 'usr' option. \itempar{Fingerprint Size}{fingerprint size} A "fingerprint" is just a reduced-resolution list of event values. This value, if set from 32 to 128, indicates the number of events above which a "fingerprint", rather than every note, will be drawn. It can save some CPU time in drawing the grid. If set to 0, the whole pattern is drawn, no matter how long the pattern is. In the \textbf{UI Boolean Options} section are some more options from the 'usr' file: \begin{itemize} \item Verbose console output. (Read only). \item Load most-recent file (startup). \item Show full path of recent files. \item Long port/buss names. \item Lock main window. \item Swap grid coordinates. \item Bold grid slot font/box. \item Thick grid lines. \item Suppress startup error messages. \item Double click for pattern editor. \item Global background/scale/key. \item Client:port buss names. \item Tweak color for dark theme. \item Elliptical progress box. \item Follow progress by default. \item \textsl{Reserved}. (For future expansion). \end{itemize} \itempar{Verbose Console Output}{verbose} This boolean makes more output appear if \textsl{Seq66} is run from a console/terminal. It will also increase the amount of data logged to the log file, if activated. It is only a temporary setting, just like its command-line counterpart, \texttt{-{}-verbose}; when \textsl{Seq66} exits completely, the setting remains false. \itempar{Load most-recent file (startup)}{load most-recent} If checked, the file at the top of the \texttt{[recent-files]} list in the 'rc' file is loaded at startup. Note that this option will not work if a \textsl{Seq66} play-list is active. \itempar{Show Full Path of Recent Files in Menu}{full paths} The full path of each file in the \texttt{[recent-files]} list is shown in the menu. Although they can be uncomfortably long, they can show files that have the same name, but in different directories. \itempar{Long Port/Buss Name}{buss names!long} \index{buss names!short} Controls how much port information is shown in the clocks and input listings. For the "portmidi" (e.g. \textsl{Windows}) implementation, keep this option checked. \itempar{Lock Main Window}{main window!lock} This item makes the window non-resizable after startup. (Exact results might vary with window manager.) \itempar{Swap Grid Coordinates}{grid!swap coordinates} Normally, \textsl{Seq66} displays the pattern and mute-groups grids where the pattern numbers increase fastest downward. Some might prefer to have pattern numbers increase fastest rightward. This setting make the patterns show in the more conventional manner. \textbf{Warning}: \begin{itemize} \item This setting requires the 'ctrl' file to be rewritten if one wants to preserve the normal layout for the pattern hot-keys and the mute-group hot-keys. \item This setting has not been rigorously tested, so be prepared for some issues to report. \end{itemize} A 'ctrl' file for the swapped setting is provided in \texttt{qseq66-swapped.ctrl} in the \texttt{data/linux} directory, but it might not be completely correct yet. \itempar{Bold Grid Slot Font/Box}{grid!bold} \index{font!bold} \index{progress bar!thick} This setting makes the font in the live grid bold, and it allows make the progress-bar thick in the grid and in the Live and Song piano rolls. It is the same as the \texttt{progress-bar-thick = true} option in the 'usr' file. See \sectionref{subsubsec:usr_file_user_interface_settings}. \itempar{Thick Grid Lines}{grid!thick} This option makes some lines in the pattern and song grid-panes stand out more. Try it and see what is preferable for the current Qt theme or style-sheet. \itempar{Suppress startup error messages}{quiet} Unlike the verbose setting, this one is sticky. It prevents the display of error prompts at startup. It is useful when the system keeps flagging the same problem, it cannot be fixed, and can be ignored. It is \textsl{not} the opposite of "verbose". \itempar{Double click for pattern editor}{grid!bold} \index{double-click!pattern slot} If set, a double-click on a grid button brings up the pattern for editing. It will also create a new pattern when done on a blank slot. Disable it if the effect is confusing. \itempar{Global background/scale/key}{globals!background etc.} \index{global pattern setting!background} \index{global pattern setting!key} \index{global pattern setting!scale} If set, setting the background sequence, scale to show, or the key of the track will apply to all pattern windows that are opened. \itempar{Client:port buss names}{buss!naming} \index{bus!naming} If checked the MIDI engine's "client:port" numbers are shown in the port listings. Most useful with ALSA. \itempar{Tweak Color for Dark Theme}{theme!dark} In dark themes, some interface items might be difficult to see. This option replaces some icons with brighter icons. Another option is to create a \textsl{Seq66} \texttt{.palette} file. \itempar{Elliptical Progress Box}{grid!elliptical} This bit of eye candy merely makes the live grid slot progress box elliptical. If the \textbf{Progress Boxes} width and height fractions are equal, the progress box is a circle. If use of a gradient brush is specified, the ellipse has a radial gradient. \itempar{Follow Progress By Default}{progress!default} This item can be turned off if one does not want the pattern or song editors to scroll as playback occurs. Buttons in the pattern and song editors can toggle this setting during the run. \subsection{Edit / Preferences / JACK} \label{subsection:edit_preferences_jack} This tab sets up JACK transport, if \textsl{Seq66} was built with JACK support (\textsl{Linux} only). \begin{figure}[H] \centering \includegraphics[scale=0.50]{main-menu/edit/preferences/midi_jack_tab.png} \caption{Edit / Preferences / JACK} \label{fig:midi_jack_tab} \end{figure} The main sections in this dialog are: \begin{enumber} \item \textbf{JACK Transport/MIDI} \item \textbf{JACK Start Mode} \item \textbf{JACK Transport Connect and Disconnect} \item \textbf{JACK Server Settings} \end{enumber} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Transport/MIDI}{jack sync!transport/midi} These settings are stored in the 'rc' file settings group \texttt{[jack-transport]}. This items collects the following settings: \begin{itemize} \item \textbf{Jack Transport}. \index{JACK!transport} Enables slave synchronization with JACK Transport. The command-line option is \texttt{-{}-jack-transport}. The behavior of this mode of operation is perhaps not quite correct. Even as a slave, \textsl{Seq66} can start and stop playback. \item \textbf{Transport Master}. \index{JACK!transport master} \textsl{Seq66} will attempt to serve as the JACK Master. The command-line option is \texttt{-{}-jack-master}. If this option is enabled the \textbf{JACK Transport} option is automatically enabled as well. \textbf{Tip}: Seq66 generally works better as JACK Master than JACK Slave. \item \textbf{Master Conditional}. \index{JACK!master conditional} \textsl{Seq66} will fail to serve as the JACK Master if there is already a Master. The command-line option is \texttt{-{}-jack-master-cond}. If this option is enabled the \textbf{JACK Transport} option is automatically enabled as well. \item \textbf{Native JACK MIDI}. \index{JACK!native midi} This option is for the \texttt{qseq66} (Linux) version of \textsl{Seq66}. If set, MIDI input and output use native JACK MIDI, rather than ALSA. However, if JACK is not running on the system, then \texttt{seq66} will fall back to ALSA mode. (However, if \texttt{jackdbus} is running, but the JACK engine is not, then a couple of non-working manual ports are created. To be fixed in the future.) The command-line option is \texttt{-{}-jack-midi} or \texttt{-{}-jack}. \item \textbf{JACK Auto-Connect}. \index{JACK!auto-connect} This option is true by default. It can be toggled off, to let the user or a session manager make the connections. \end{itemize} If one makes a change in the JACK transport settings, it is best to then press the \textbf{JACK Transport Disconnect} button, then the \textbf{JACK Transport Connect} button. Another option is to restart \textsl{Seq66}... the settings are automatically saved when \textsl{Seq66} exits. \itempar{JACK/ALSA Current Start mode}{jack sync!start mode} This item shows the current Live/Song setting. In the 'rc' file, \texttt{[jack-transport] song-statr-mode = auto} is the default. \begin{itemize} \item \textbf{Live Mode}. \index{JACK!live mode} \index{live mode} \index{non-playback mode} Playback is in Live mode, to allow muting and unmuting of patterns. The command-line option is \texttt{-{}-jack-start-mode live}. \item \textbf{Song Mode}. \index{JACK!song mode} \index{song mode} \index{playback mode} \index{performance mode} Playback will use only the Song Editor's data, although the user can toggle non-triggered patterns. The command-line option is \texttt{-{}-jack-start-mode song}. \end{itemize} The command-line option also allows for \texttt{-{}-jack-start-mode auto}. In this mode, \textsl{Seq66} also selects the playback modes according to whether or not the MIDI tune has song triggers. In live mode. The user can arm and mute patterns in the main window by clicking on sequences, using their hot-keys, and by using the group-mode and learn-mode features. The Song mode causes playback to be in performance mode. Also see \sectionref{subsection:edit_preferences_play_options}. \itempar{Connect}{jack sync!connect} Connect to JACK Sync. This button is useful to restart JACK sync when making changes to it, or when \textsl{Seq66} was started in ALSA mode. \itempar{Disconnect}{jack sync!disconnect} Disconnect from JACK Sync. This button is useful to stop JACK sync when making changes to it. JACK connection and disconnection are disabled during playback, but the buttons don't yet reflect that status. \itempar{JACK Server Settings}{jack server!settings} This read-only section shows the current settings of the JACK server, as much as possible. \subsection{Edit / Preferences / Play Options} \label{subsection:edit_preferences_play_options} This tab contains some disparate options related to playback. \begin{figure}[H] \centering \includegraphics[scale=0.50]{main-menu/edit/preferences/midi_play_options_tab.png} \caption{Play Options} \label{fig:midi_play_options_tab} \end{figure} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Resume Note Ons at start/stop or sequence toggle}{edit!resume notes} This option allows notes that had already started to be resumed when playback resumes. \itempar{Use file's PPQN for pre-existing files}{edit!use file ppqn} If checked, allows \textsl{Seq66} to run using the PPQN of the MIDI file rather than the default \textsl{Seq66} internal PPQN. This is the recommended option for most MIDI files. When this option is changed, the \texttt{-{}-user-save} option is turned on to preserve the setting when \textsl{Seq66} exits. \itempar{Song-record snap}{edit!song-record snap} Use the song snap value to tighten up the recording of song triggers. \itempar{Automatically add starting time-signature event}{edit!timesig} If checked, a time-signature event is added to the beginning of a track if none is present. \itempar{PPQN}{edit!default ppqn} Allows the standard \textsl{Seq66} PPQN, 192 pulses/quarter-note, to be changed to discrete values from 24 to 19200. 960 PPQN is probably the highest useful PPQN. When this option is changed, the \texttt{-{}-user-save} option is turned on to preserve the setting when \textsl{Seq66} exits. If there is a MIDI file loaded, it is modified to use the new PPQN, and the user is prompted to save it at exit. Best to have a backup, just in case. \itempar{Sets Mode}{edit!sets-mode} This item determines how play-sets are handled. Recall that a set is a number of patterns (up to 4x8) in the pattern grid, and that the current set is the one visible in the pattern grid. The way sets work in \textsl{Seq66} is that, when a set is selected, all the patterns in it are loaded into what is called the "play-set". When play starts only, patterns in the play-set are handled. The \textbf{Sets Mode} option allows special handling of the play-set. \begin{enumerate} \item \textbf{Normal}. In this mode, only the current set's patterns can be unmuted. When switching to another set, the current set's patterns become muted, and the new set's patterns are shown, unmuted. \item \textbf{Auto-Arm}. Here, when the new set is loaded, it is immediately unmuted. \item \textbf{Additive}. With this option, when a new set is loaded, the previous set keeps playing. This allows a build-up of patterns in playback. \item \textbf{All Sets}. Here, all sets in the tune are loaded and unmuted at once. Try this mode with the \texttt{b4uacuse-stress.midi} file in the \textsl{Sequencer64} project. It's a good test of \textsl{Seq66} and your hardware/software synthesizer! \end{enumerate} One can clear the out play-set, and set only the current set active, by clicking the exclamation point button to the left of the "Active" label at the bottom of the main windows. \itempar{Song Start Mode}{edit!song-start mode} This item determines the mode for playback. \begin{enumerate} \item \textbf{Live}. The arming and muting of patterns is controlled by the musician, by clicking the live grid's button, using the grid slot hot keys, or a MIDI controller configured for this purpose. \item \textbf{Song}. The arming and muting of patterns is controlled by the triggers layed out in the song editor. \item \textbf{Auto}. The Song mode is set if the MIDI tune contains any triggers; otherwise the Live mode is set. \end{enumerate} The current mode is shown on a button at the bottom of the main window. This button can be clicked to change the mode. \subsection{Edit / Preferences / Metronome Options} \label{subsection:edit_preferences_metronome} This tab contains options for the "metronome" and "background recording" features. However, note that "background recording" means merely that the user can start recording before the count-in value has been reached. We should perhaps call it "intro recording". \begin{figure}[H] \centering \includegraphics[scale=0.50]{main-menu/edit/preferences/midi_metro_options_tab.png} \caption{Metronome Options} \label{fig:midi_metro_options_tab} \end{figure} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. The metronome feature is enabled in the main live grid via a metronome button. The metronome is a standard \textsl{Seq66} pattern that is used for playback of the metronome, but it is never seen nor directly edited by the user. It is not saved with a song, so changing the metronome does not modify the song. The settings shown above are saved to a "metronome" section in the 'rc' file. The metronome is a pattern that first plays a main note once, and then plays "sub" notes for the rest of the measure. Here are the settings: \itempar{Beats/bar}{metronome!beats/bar} This setting sets the beats-per-measure for the metronome only. It currently does not affect the time-bar in the main window. Should it? There is a global beats/bar as well as beats/bar for each pattern. \itempar{Beat width}{metronome!beat width} This setting sets the beat width for the metronome only. The following settings are provided for the "main" note (the note that occurs on the beginning of the measure) and the "sub" notes (the notes that occur on each beat): \itempar{Patch}{metronome!patch} This item sets the program (patch) number for the note, which sets the instrument to play for the notes. We currently do not have a drop-down box to select the patch by name. The default patch is 0. As noted below, the default channel is 10, so this patch is the "Standard Drum Kit" for the device. Thus, by default the metronome can be implemented by two different drums. \itempar{Note}{metronome!note} This item provides the note value to be played. Recall that 60 is the same as "middle C". By default, the main note is 75, the "Clave" for the drum kit, and the sub note is 76, the "High Wood Block" for the drum kit. \itempar{Velocity}{metronome!velocity} This item provides the note velocity to be played, to provide an accent on the main note. \itempar{Length fraction}{metronome!length fraction} The length of the notes are specified as a fraction of the beat width, and this value ranges from 0.125 to 1.0 to 2.0. If set to 0, the length is half of the beat width. \itempar{Reload Metronome}{metronome!reload} This button pauses playback (if playing), loads in the new metronome settings, and continues playing (if it was playing). It is \textsl{not} enabled when the status/configuration of background recording changes. \itempar{Count-In Enabled}{metronome!count-in} Allows for a measure or two to be counted off by the metronome before the play-back of the tune commences. \itempar{Count-In Recording}{metronome!count-in} Allows for a measure or two to be recorded before the play-back of the tune commences. \itempar{Metro Buss}{metronome!buss} This value selects the output MIDI device to use to play the metronome. It \textsl{must} be enabled in the \textbf{MIDI Clock} list. \itempar{Channel}{metronome!channel} This value selects the channel to use to play the metronome. \itempar{Record Buss}{recorder!buss} \index{background recorder} This value selects the input device to use to record events into the background pattern. Note that this device \textsl{must be enabled} in the \textbf{MIDI Input} buss list. \itempar{Thru Buss}{record!thru buss} This value selects the output MIDI device to use to play the incoming background record notes. Otherwise they will not be heard. It \textsl{must} be enabled in the \textbf{MIDI Clock} list. \itempar{Thru Channel}{recorder!thru channel} This value selects the channel to use to play the recorded notes as they come in. We still have some more work to do to refine the metronome, the background recorder, and their configuration, pending user input. For more information about the metronome, see \sectionref{subsection:edit_preferences_metronome}. % For information on count-in background recording, see % \sectionref{subsection:edit_preferences_background_recording}. \subsection{Edit / Preferences / Pattern} \label{subsection:edit_preferences_pattern} This tab provides options for the status of newly-created patterns and for randomization of amplitude and jitter of time-stamps. \begin{figure}[H] \centering \includegraphics[scale=0.50]{main-menu/edit/preferences/midi_pattern_tab.png} \caption{Pattern Options} \label{fig:midi_pattern_options_tab} \end{figure} \itempar{Pattern}{edit!pattern} \textbf{Pattern} This tab provides a way to configure the status of a newly-created or newly-opened pattern. It also provides a way to change the range of amplitude randomization and time jittering of events. The first section is \textbf{Pattern Options}. It defines the statuses of a newly-created or newly-opened pattern. This can be convenient for live-recording. The status setting are: \begin{itemize} \item \textbf{Armed}. This setting causes the pattern to be armed when a new pattern is created. \item \textbf{Record}. The new pattern starts in record mode. \item \textbf{Tighten record}. The new pattern starts in tightened (partly quantized) record mode. \item \textbf{Quantize record}. The new pattern starts in quantized record mode. \item \textbf{Note-map record}. The new pattern starts in note-mapping record mode. Notes are translated live via a 'drums' file, if set active in the 'rc' file. \item \textbf{Wrap-around}. The new pattern will allow prolonged notes to wrap around so that the Note Off event precedes the Note On event in the pattern loop. \item \textbf{MIDI Thru}. The new pattern starts with MIDI Thru enabled. \item \textbf{Apply only to new}. If check-marked, then the settings are applied only to newly-created patterns, not to newly-opened patterns. Often one might not want to automatically record into an existing pattern, for example. \item \textbf{Record Style} This setting sets how the record mode works for the pattern. \begin{itemize} \item \textbf{Overdub}. Also known as "Merge". As recording and looping proceeds, new events merge with the existing events. Events accumulate. \item \textbf{Overwrite}. When the pattern loops back to its beginning, any existing events are deleted. A good way to try to get the right collection of notes. \item \textbf{Expand}. When recording as notes are recorded, the pattern expands to accomodate them. This results in a longer pattern than initially specified. \item \textbf{Oneshot}. Events are entered until the end is reached. Useful for recording stock patterns from a drum machine. \item \textbf{Oneshot Reset}. At the end of the specified length of the pattern, all events are cleared. Normal recording is set. Need to look into this as we cannot rememember all the details :-D. \end{itemize} \end{itemize} \textbf{Randomization}. These items set the ranges for jittering the events in time or randomizing their amplitude. The range of randomization is based on a range parameter, and goes from -range to +range. The concept of jitter means that the time-stamps of recorded events are randomized slightly. The concept of randomization means that the amplitudes of events are randomized slightly. The randomization settings are: \begin{itemize} \item \textbf{Jitter}. This value is a jitter divisor. It sets the fraction of of the current snap value that is used as the range of jittering the time. For example, "8" means that the range is 1/8th of the snap value. \item \textbf{Amplitude}. This value is used for various data values. For Notes On (but not Notes Off), this parameter affects the range of amplitude variation, when amplitudes are the standard MIDI range, 0 to 127. \end{itemize} One minor issue, which we're still trying to work around, is that our various randomization algorithms seem biased to emit negative numbers. If one clicks in a pattern editor piano roll, types \texttt{Ctrl-A} to select all notes (and aftertouch) and types the \texttt{r} key repeatedly to randomize note amplitudes, the overall velocities slowly descend to 0. Still not sure what's wrong with \textsl{Seq66} randomization, but it is only important if randomizing a large number of times. \textbf{Additional 'usr' Options}. Provides a couple of options. \begin{itemize} \item \textbf{Esc key in piano roll closes external editor}. If set (the default is false), then the \texttt{Esc} key can not only stop playing and exit paint mode, but can also close the active pattern window. Be careful. It's your call. \item \textbf{Automatic conversion of SMF 0 to SMF 1}. If set, when an SMF 0 file is opened, it is split across multiple slots on the basis of event channels. See \sectionref{subsubsec:midi_export_file_import}. \end{itemize} \subsection{Edit / Preferences / Session} \label{subsection:edit_preferences_session} This tab contains options related to session management and the configuration files. \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \begin{figure}[H] \centering \includegraphics[scale=0.50]{main-menu/edit/preferences/midi_session_tab.png} \caption{Session Options} \label{fig:midi_session_options_tab} \end{figure} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Session}{edit!session} This tab provides for three modes of session management: None, the Non/New Session Manager (NSM), and JACK Session management. None is the normal mode of operation, where the user has full control of where to put files, what other applications are to be run alongside \textsl{Seq66}, and what connections are to be made. \textbf{NSM} provides a rigorously-controlled session management, and directs \textsl{Seq66} what menu items to display, whether to hide the user-interface or not, where configuration files and MIDI files go, and what applications are run in a session. It can also (via \texttt{jackpatch}) keep a record of connections to reconstruct. If run by NSM, \textsl{Seq66} automatically uses the NSM setup. The option here is useful for trouble-shooting. \textbf{JACK Session} provides a location for file and a record of applications and connections, but otherwise lets the user mess things up. It is provided because some people still use it. For more information about session management, see \sectionref{sec:sessions}. \itempar{UUID}{edit!UUID} \textbf{UUID} is a read-only field that shows any UUID that's relevant to a session. Normally has a value only in a \textsl{JACK} or \textsl{NSM} session. Also see the \textbf{Session} tab in the main window (\sectionref{sec:sessions}). \itempar{Configuration Files}{edit!configuration} \textbf{Configuration Files} shows the status of the configuration files. (See \sectionref{subsec:configuration_rc}). The 'rc' and 'usr' files are always active. % The 'usr' file should also be active, but one can disable it, which is % currently an \textsl{experimental} and \textsl{untested} option. Normally, the 'usr' file is not saved at application exit (except after the first run on one's system or when a setting is changed). (See \sectionref{subsec:configuration_usr}). The rest of the configuration files are optional and are described elsewhere. \begin{itemize} \item \Sectionref{subsec:configuration_ctrl}. \item \Sectionref{subsubsec:configuration_mute_group_control}. \item \Sectionref{subsec:configuration_drums}. \item \Sectionref{sec:mutes_master}. \item \Sectionref{sec:playlist}. \item \Sectionref{sec:palettes}. \item \Sectionref{subsec:configuration_drums}. \item \Sectionref{subsubsec:configuration_rc_style_sheet}. \item \Sectionref{subsubsec:configuration_rc_patches}. \end{itemize} Note that activating a play-list file will deactivate the load-most-recent file option. \itempar{Store Palette}{palette} Normally, there is no palette file. Pushing this button creates one, which can then be modified and configured as the palette-file to use in the 'rc' file. The 'palette' file is always stored in the \textsl{Seq66} configuration directory. (See \sectionref{sec:palettes}, or \sectionref{subsubsec:configuration_rc_color_palette}.) \itempar{Store Patches}{patches} Normally, there is no patches file. Pushing this button creates one, which can then be modified and configured as the patches-file to use in the 'rc' file. It is similar to the 'Drums' file, but contains only the names of patches/programs for each patch number. See \sectionref{subsubsec:configuration_rc_patches}. \itempar{Browser}{browser} This field is text-editable, and can also be changed by using the button next to it to select a browser executable to use in the \textbf{Help / Tutorial} menu entry. If all possible browsers are available via one's \texttt{PATH}, then the simple name of the application (including \texttt{.exe} if running \textsl{Windows}) can simply be typed in. Otherwise, type in the complete path or use the button to bring up a file dialog. If one erases the file name, the default browser for the system will be used the next time \textsl{Seq66} is restarted. \itempar{PDF Viewer}{PDF viewer} This field is similar to the browser field, but specifies an alternate viewing application for PDFs. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/recording.tex ================================================ %------------------------------------------------------------------------------- % seq66 recording %------------------------------------------------------------------------------- % % \file seq66 recording.tex % \library Documents % \author Chris Ahlstrom % \date 2023-11-25 % \update 2025-07-02 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides a discussion of the MIDI GUI recording that Seq66 % supports. % %------------------------------------------------------------------------------- \section{Seq66 Recording In Depth} \label{sec:recording} Recording in \textsl{Seq66} has been greatly enhanced. Multiple patterns can be recorded at once, with events routed to particular patterns based on the buss number the event came in on, or its channel number. Additional ways to toggle recording have been added. Additional recording alterations have been added, such as on-the-fly note mapping. It can get a bit complex to keep track of, hence this section, which walks the user through some scenarios. Before getting started, note that recording also can be done during count-in via the metronome feature. See \sectionref{subsection:edit_preferences_metronome}. \subsection{Recording Scenarios} \label{sec:recording_scenarios} The following recording scenarios are available: \begin{itemize} \item \textbf{Standard Recording}. This provides the normal method of recording. Open a pattern in a window and enable recording, or right-click on its live-grid slot and enable recording.. The last slot selected for recording receives the events. Events are received from all enabled MIDI input ports. In this mode the record-button at the bottom of the main window is red. The slot popup-menu does not provide for setting an input buss in this mode. \item \textbf{Record-by-Bus}. In this mode, any pattern that specifies a specific input buss can be enabled for recording. Incoming events are routed to the first pattern that has an input buss matching the buss on which the event was recorded. In this mode the record-button at the bottom of the main window is green. To designate a recording buss, first go to \textbf{Edit / Preferences / MIDI Input} and enable \textbf{Record into patterns by bus}. Then right-click on the pattern and select an \textbf{Input bus}. (This popup menu entry only appears when record-by-bus is active.) \item \textbf{Record-by-Channel}. In this mode, one enables recording in patterns numbered from 0 to 15 (channels 1 to 16) with output channels set from 0 to 15. Incoming events are analyze for their channel and are recording into the corresponding pattern number. In this mode the record-button at the bottom of the main window is yellow. \item \textbf{One-shot Recording}. When one-shot mode is selected, recording of incoming events proceeds from the "L" marker to the end of the pattern length. \end{itemize} The "Record-by" options above are mutually exclusive. \subsubsection{Standard Recording} \label{subsubsec:recording_standard_recording} Standard recording allows the enabling of recording in a single pattern. It \textsl{requires} that the other two modes be turned off. Once a pattern is set to record, no other pattern can be set to record until the original pattern is set to not record. The single recording pattern gets all events from any MIDI input that is enabled. In standard recording there a many ways to enable recording into a pattern. \begin{itemize} \item Open the pattern editor in a window or tab or click on the pattern slot to record, then click the red record button at the bottom of the pattern editor. The pattern will also show a red circle to indicate that recording has been enabled. \item Right-click on the desired pattern in the grid and select \textbf{Record toggle} from the menu. \item Touch a pattern or click its hot key, then click the red record button at the bottom of the main window. \item Select the \textbf{Record} grid mode, then select a pattern. To change the selected slot, click on the red record button to disable recording, then click on the desired slot. Recording is active on that slot. (This option is more useful in the other recording scenarios.) \end{itemize} Again, remember that, with standard recording, only one pattern can accept events. Also note that this mode should be used if one expects to record SysEx events, which have no channel. Lastly, note that recording proceeds from the "L" marker, which can be moved from 0 first, if desired. \subsubsection{Route-Input-By-Buss} \label{subsubsec:recording_route_by_buss} \index{record!by buss} Route-input-by-buss (also known as record-by-buss) is enabled whenever a pattern in a song specifies an input buss \textsl{and} \textbf{Edit / Preferences / MIDI Input / Record into patterns by bus} is checked. It is an 'rc' file option. If it is enabled, it supercedes the \textbf{record-by-channel} option and disables that standard recording mode described above. When route-by-bus is enabled, an internal container is populated with all the patterns in the current play-set that specify an input buss. This container is rebuilt when a sequence is added or removed. It is stored in the \texttt{c\_midiinbus} SeqSpec in the song. If route-by-bus is enabled, a sequence with an input bus that matches the buss associated with the incoming event is looked up, and input is streamed to it. In this scenario, multiple patterns can be enabled for recording, and output from multiple input ports are routed appropriately. For the green record button to be enabled, at least one pattern must specify an input bus. Clicking on the green record button at the bottom of the main window will turn on recording for \textsl{all} patterns that specify an input buss. \subsubsection{Record-By-Channel} \label{subsubsec:recording_record_by_channel} Record-by-channel can be enabled in \textbf{Edit / Preferences / MIDI Input / Record into patterns by channel}, which is an 'rc' file option. It works by routing events to the first pattern that has specified an output (not input) channel that matches the channel (if applicable) of the incoming MIDI event. The patterns applicable are entered into a list of patterns with specified output channels. \textbf{IMPORTANT}: In order for a channel to be recorded, it's corresponding pattern must exist. Hence, for convenience, there is a MIDI file installed, called \textbf{16-blank-patterns}, which specifies all the output channels. It can be copied and used for the first recording from a device playing on multiple channels, or from multiple devices, each playing on a different channel. Clicking on the yellow record button at the bottom of the main window will turn on recording for \textsl{all} patterns, except for patterns that specify "Free" (i.e. no forced channel). Note that the route-by-buss option, described above, supercedes this option. \subsection{Recording Modes} \label{sec:recording_modes} Recording can also transform (alter) the incoming events. These transformations can also be applied after recording. Also see \sectionref{paragraph:configuration_midi_record_quan}. \begin{itemize} \item \textbf{Tighten}. Partial quantization to the current snap value. \item \textbf{Quantize}. Full quantization to the current snap value. \item \textbf{Note-map}. If a 'drums' file is active, then notes are changed in note-value according to that file. This is most useful for drums, but other effects are possible. \end{itemize} \subsection{Seq66-to-Seq66 Recording} \label{sec:recording_seq_to_seq} This section describes a way to have one instance of \textsl{Seq66} (\texttt{qseq66}) interact with another. The first \textsl{Seq66} to start runs with a different client name (optional) and a different 'rc' file name (to not affect the normal configuration of \textsl{Seq66}). The first \textsl{Seq66} runs with virtual ports; the second \textsl{Seq66} runs normally and can auto-connect to these ports. Run the first instance with the "virtual" option; the first number is the number of output ports to make, and the second is the number of input ports to make. \begin{verbatim} $ qseq66 --client first66 --rc first66 --option virtual=2,2 \end{verbatim} Then run another \textsl{Seq66} normally: \begin{verbatim} $ qseq66 \end{verbatim} Remap and restart if necessary. In the \textbf{MIDI Clock} tab should be "first66:midi out 0" and "first66:midi out 1". In the \textbf{MIDI Input} tab should be "first66:midi in 0" and "first66:midi in 1". Among the ports shown, along with plugged in hardware, are: \begin{verbatim} $ aplaymidi -l Port Client name Port name 44:0 MPK mini Play mk3 MPK mini Play mk3 MIDI 1 128:0 first66 midi in 0 128:1 first66 midi in 1 ahlstrom@mlstrycoo ~/Home/ca/mls/git/seq66 $ arecordmidi -l Port Client name Port name 44:0 MPK mini Play mk3 MPK mini Play mk3 MIDI 1 128:2 first66 midi out 0 128:3 first66 midi out 1 \end{verbatim} Leave this instance running, and run a plain \texttt{qseq66} command. It should show the "first66" output (clocks) and input ports. One should now be able to pass MIDI events between the two instances. % VERIFY this at some point. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/references.tex ================================================ %------------------------------------------------------------------------------- % references %------------------------------------------------------------------------------- % % \file references.tex % \library Documents % \author Chris Ahlstrom % \date 2015-08-31 % \update 2025-06-11 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides the References section of the Seq66 manual. Rather % than use the bibtex package, our small set of references uses a % simpler method. % % Potential additional references: % % cakewalk % WRK % %------------------------------------------------------------------------------- \section{Seq66 Reference List} \label{sec:references} {\RaggedRight \begin{thebibliography}{99} \bibitem{alsa} ALSA team. \emph{Advanced Linux Sound Architecture (ALSA) project homepage.} \url{http://www.alsa-project.org/}. ALSA tools through version 1.0.29. 2015. \bibitem{alsathru} superuser.com. \emph{How do I increase the number of MIDI through ports in ALSA?} \url{https://superuser.com/questions/973973/how-do-i-increase-the-number-of-midi-through-ports-in-alsa} 2015. \bibitem{agordejo} laborejo. \emph{Agordejo Session Manager, part of the Laborejo Software Suite} \url{https://www.laborejo.org/agordejo/}, \url{http://git.laborejo.org/lss/agordejo.git} 2021. \bibitem{AUR} Seq66 Arch Linux User Repository. \emph{Seq66 AUR PACKAGE file for version 0.99.4} \url{https://aur.archlinux.org/packages/seq66-git/} 2022. \bibitem{combine} Jay Capela Music. \emph{"Combine": A Seq24 Demonstration.} \url{https://www.youtube.com/watch?v=fUiXbVT0bJQ}. 2010. \bibitem{forks} Various. \emph{Forks of Seq66.} \url{https://github.com/sivecj/seq66}. 2021. \bibitem{jack} JACK team. \emph{JACK Audio Connection Kit.} \url{http://jackaudio.org/}. 2015. \bibitem{kepler34} Oli Kester. \emph{Kepler34: Seq24 for the 2010s.} \url{https://github.com/oli-kester/kepler34}. 2010-2016. \bibitem{lau} Linux Audio Users. \emph{Unofficial Linux audio users IRC channel.} \url{http://kiwiirc.com/nextclient/#irc://irc.freenode.net/#lau}. 2021. \bibitem{layk} Lassi Ylikojola. \emph{Many demo videos of Sequencer64.} \url{https://www.youtube.com/watch?v=YStYVjFv1TM}, \url{https://www.youtube.com/watch?v=GBlEP8Ffqss}, \url{https://www.youtube.com/watch?v=4gG8SvJxJkA&t=28s}, \url{https://www.youtube.com/watch?v=n4Z4WPK6FpA}. 2010-2017. \bibitem{libreav} Author unknown. \emph{A description of Seq66.} \url{https://libreav.org/software/seq66}. 2019. \bibitem{loopmidi} Tobias Erichsen. \emph{Private stuff \& software for audio, midi and more} \url{https://www.tobias-erichsen.de/software.html}. 2022. \bibitem{midihowto} Phil Kerr. \emph{The Linux MIDI-HOWTO.} \url{https://tldp.org/HOWTO/MIDI-HOWTO.html}. 2002. \bibitem{midilinux} Murks. \emph{ALSA and JACK MIDI explained (by a dummy for dummies).} \url{https://freeshell.de/~murks/posts/ALSA_and_JACK_MIDI_explained_(by_a_dummy_for_dummies)/}. 2010. \bibitem{midimapper} Coolsoft. \emph{Coolsoft MIDIMapper (Windows).} \url{https://coolsoft.altervista.org/en/midimapper}. 2018. \bibitem{midiox} Jamie O'Connell and Jerry Jorgenrud. \emph{MIDI OX and other related software (Windows).} \url{http://www.midiox.com/}. 2017. \bibitem{midisynth} Coolsoft. \emph{Coolsoft VirtualMIDISynth (Windows).} \url{https://coolsoft.altervista.org/en/virtualmidisynth}. 2018. \bibitem{midicontrol} linuxaudio.org. \emph{seq24: toggle sequences with a MIDI controller.} \url{http://wiki.linuxaudio.org/wiki/seq24togglemiditutorial}. 2013. \bibitem{midicontroltable} midi.org. \emph{Summary of MIDI Messages.} \url{https://www.midi.org/specifications/item/table-1-summary-of-midi-message#2}. Year unknown. \bibitem{midicvt} Chris Ahlstrom. \emph{Extension of midicomp/midi2text to convert between MIDI and ASCII text format.} \url{https://github.com/ahlstromcj/midicvt}. 2015-2016. \bibitem{msc} \emph{Digital Sound and Music: MIDI Show Control.} \url{https://digitalsoundandmusic.com/6-2-5-non-musical-applications-for-midi/}. 2023. \bibitem{nanobasket} Roy Vegard. \emph{Configurator software for the Korg nanoSERIES of MIDI controllers.} \url{https://github.com/royvegard/Nano-Basket}. Warning: this code may require an earlier version of Python than present in one's Linux distro or in Windows. 2015. \bibitem{nsm} JACK Audio. \emph{New Session Manager, an offshot of Non Session Manager.} \url{https://github.com/jackaudio/new-session-manager} 2021. \bibitem{osc} Stephen Sinclair \emph{Open Sound Control (library).} \url{https://github.com/radarsat1/liblo} 2019. \bibitem{patchbay} Simon W. Fielding. \emph{QjackCtl and the Patchbay.} \url{https://www.rncbc.org/drupal/node/76}. 2008. \bibitem{piseq} sferamusic \emph{HOW-TO: DIY hybrid Sequencer / MIDI USB Hub ....} \url{https://www.rncbc.org/drupal/node/7://steemit.com/music/@sferamusic/how-to-diy-hybrid-sequencer-midi-usb-hub-aka-piseq-using-raspberry-pi-and-external-midi-controllers-part-1}. 2017. \bibitem{portmidi} PortMedia team. \emph{Platform Independent Library for MIDI I/O.} \url{http://portmedia.sourceforge.net/}. 2010. \bibitem{ppqncalculator} Benjamin Robert Tubb \emph{MIDI PPQN Duration Calculator.} \url{https://demonstrations.wolfram.com/MIDIPPQNDurationCalculator/}. 2011. \bibitem{repositories} Dmitry Marakasov (Repology). \emph{Known Seq66 Repositories.} \url{https://repology.org/project/seq66/versions}. 2023. \bibitem{qjackctl} \emph{QjackCtl.} Rui Nuno Capelo (rncbc). \url{https://sourceforge.net/projects/qjackctl/}. \url{https://qjackctl.sourceforge.io/qjackctl-index.html}. \url{https://git.code.sf.net/p/qjackctl/code qjackctl-git}. 2021. \bibitem{raysession} Houston4444. \emph{RaySession Session Manager} \url{https://raysession.tuxfamily.org/en/} \url{https://github.com/Houston4444/RaySession} 2021. \bibitem{rtmidi} Gary P. Scavone. \emph{The RtMIDI Tutorial.} \url{https://www.music.mcgill.ca/~gary/rtmidi/}. 2016. \bibitem{seq24} Seq24 Team. \emph{The home site for the Seq24 looping sequencer.} \url{http://www.filter24.org/seq24/download.html}. 2010. \bibitem{seq24launchpad} Seq24 Team. \emph{The home site for the Seq66 looping sequencer.} \url{https://edge/launchpad.net/seq24}. 2016. \bibitem{seq24launchpadmapper} Excds. \emph{A simple mapping for toggling the LEDs on the Novation launchpad together with seq24.} \url{https://github.com/Excds/seq24-launchpad-mapper}. 2013. \bibitem{seq32} Stan Preston (stazed). \emph{The home site for the Seq32 looping sequencer.} \url{https://github.com/Stazed/seq32}. 2016. \bibitem{seq66} Chris Ahlstrom. \emph{A reboot of the Seq24 project as "Seq66".} \url{https://github.com/ahlstromcj/seq66/}. 2015-2024. \bibitem{seqtime} Unknown. \emph{Timing accuracy and resolution.} \url{http://midi.teragonaudio.com/tutr/seqtime.htm}. 2000?. \bibitem{timidity} Timidity++ Team. \emph{Download site for Timidity++ source code.} \url{http://sourceforge.net/projects/timidity/}. 2015. \bibitem{vmpk} VMPK Team. \emph{Virtual MIDI Piano Keyboard.} \url{http://vmpk.sourceforge.net/}. 2015. \bibitem{windowsmidi} Donya Quick \emph{Working with MIDI on Windows} \url{http://donyaquick.com/midi-on-windows/#x1-120004.1}. 2021. \bibitem{wootangent1} The Wootangent man. \emph{Seq24 Tutorial Videos, Part 1 and Part 2.} \url{http://wootangent.net/2010/10/linux-music-tutorial-seq24-part-1/}. \url{http://wootangent.net/2010/10/linux-music-tutorial-seq24-part-2/}. 2010. \bibitem{youtubelinks} Various. \emph{Links to other usages of the seq** applications.} \url{https://www.youtube.com/watch?v=LjHPtlvjgnM} AndrewsVintageKeyboards: Yamaha TG100 with seq66. \url{https://www.youtube.com/watch?v=ESHKiHx09AA&t=78s} AndrewsVintageKeyboards: Yamaha TG100 with sequencer66. \url{https://www.youtube.com/watch?v=\_r7qXOo7kgg} Julian L: Seq64 demo. \url{https://www.youtube.com/watch?v=VCga2X4lJfg} Julian L: Seq64 demo 2. \end{thebibliography} } %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/seq66-user-manual.tex ================================================ %------------------------------------------------------------------------------- % seq66-user-manual %------------------------------------------------------------------------------- % % \file seq66-user-manual.tex % \library Documents % \author Chris Ahlstrom % \date 2015-11-01 % \update 2026-05-01 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % This document provides LaTeX documentation for Seq66. % %------------------------------------------------------------------------------- % Replacing normal header/footer with a fancier version. These two symbols of % document class were showing up as "unused" in the log file. % % headinclude, % footinclude, % % So we add the fancyhdr package, clear the default layout, and set it up for % our wider pages. \documentclass[ 11pt, twoside, a4paper, final % versus draft ]{article} \input{docs-structure} % specifies document structure and layout \usepackage{fancyhdr} \pagestyle{fancy} \fancyhead{} \fancyfoot{} \fancyheadoffset{0.005\textwidth} \lhead{Seq66 Live-Loop MIDI Sequencer and Editor} \chead{} \rhead{User Manual} \lfoot{} \cfoot{\thepage} \rfoot{} % Removes the many "headheight is too small" warnings. \setlength{\headheight}{14.0pt} \makeindex \begin{document} \title{Seq66 User Manual v. 0.99.24} \author{Chris Ahlstrom \\ (\texttt{ahlstromcj@gmail.com})} \date{\today} \maketitle \begin{figure}[H] \centering \includegraphics[scale=0.45]{main-window/main-windows-perstfic.png} \caption*{Seq66 Windows with Perstfic Style-Sheet} \end{figure} \clearpage % moves Contents to next page \tableofcontents \listoffigures % print the list of figures \listoftables % print the list of tables % Changes the paragraph style to remove indenting and put a line between each % paragraph. This could be moved up into the preamble, but then would % affect the spacing of the TOC and LOF, LOT noted above. % % \setlength{\parindent}{2em} % \setlength{\parskip}{1ex plus 0.5ex minus 0.2ex} \parindent 0pt \parskip 9pt \rhead{\rightmark} % shows section number and section name \section{Introduction} \label{sec:introduction} Seq66 is a complete reboot of \textsl{Seq24} and a rewrite of \textsl{Sequencer64}. The following projects support \textsl{Seq66} and its documentation (see the \textbf{Help} menu): \begin{itemize} \item \url{https://github.com/ahlstromcj/seq66.git} \item \url{https://ahlstromcj.github.io/} \item \url{https://ahlstromcj/github.io/docs/seq66/seq66-user-manual.pdf} \end{itemize} Feel free to clone or fork it! If you're in a hurry to get going, proceed directly to \sectionref{sec:introduction_lets_go}. \subsection{Seq66: What!?} \label{subsec:what_is_seq66} \textsl{Seq66} is a complete reboot of \textsl{Seq24}, a live-looping sequencer with an interface similar to a hardware sequencer, refactored for newer versions of \textsl{C++} for faster and simpler code. Supports the \textsl{GNU} and \textsl{Clang} compilers. It drops the \textsl{Gtkmm} user-interface in favor of \textsl{Qt 5} and \textsl{Qt 6}, and has better handling of sets, mute-groups, sessions, configuration files, play-lists, and automation control. It supports the \textsl{Non/New Session Manager}, has a metronome function, multiple input port recording, a modifiable color and brush palette, and Qt style-sheets. Be prepared to note some significant differences between \textsl{Seq66} and our first reboot, \textsl{Sequencer64}. The basics are similar, though. \textsl{Seq66} is not a synthesizer. It requires hardware or software synthesizers. It does not handle audio data, just MIDI. It works with \textsl{ALSA}, \textsl{JACK}, and \textsl{Windows}. We have many contributors to acknowledge. Please see \sectionref{sec:kudos}. If your name is not there, ping us! \subsection{Seq66: Why!?} \label{subsec:introduction_vs_others} \textsl{Seq66} refactors \textsl{Sequencer64} to take advantage of things learned in responding to user reports; to use the new code as an opportunity to add new functionality such as \textsl{Non Session Manager} support; to tighten the code by using newer features of \textsl{C++17} and later; and to make the innumerable minor improvements that come to attention with time and testing. \subsection{Improvements} \label{subsec:improvements} The following improvements are some that have been made in \textsl{Seq66} versus \textsl{Sequencer64}. \begin{itemize} \item \textbf{Qt} 5 (and 6) as the standard user-interface offers many benefits: \begin{itemize} \item A better live frame using Qt push-buttons. \item The main window's size can be changed, via start-up options or by dragging the corners. \item Palette files to support the colors used for drawing grids, circles, rectangles, and text. \item Qt style-sheets for control of colors and fonts for many Qt-based user-interface items. \item A Qt linear-gradient default style for painting the progress boxes, notes, and triggers. \item Drag-and-drop support for opening a MIDI file in \textsl{Seq66}. \end{itemize} \item Improved the \textbf{song editor} for laying out patterns into a song. It includes transposable triggers, and can be opened in a tab or window. \item The \textbf{mutes editor} tab, improves mute-group handling and control. \item A \textbf{playlist editor} tab with improved flexibility. \item A \textbf{sets editor} tab to examine each set. \item A \textbf{events editor} tab examine each event. Useful to trouble-shoot and fix minor event issues and add meta text events. \item A \textbf{session} tab and \textbf{Edit / Preference} tabs) to run \textsl{Seq66} in the desired environment and configuation. \item \textbf{New/Non Session Manager} support. The \textbf{Sessions} tabs show the locations of configuration files for the session. \item \textbf{MIDI control/display automation}. Control by "launchpad" devices, and display of statuses. \item Repartitioning of \textbf{configuration files} into separate files for flexibility; added a \textbf{color palette} file, Qt \textbf{style-sheets} (\texttt{*.qss}); an enhanced keystroke and \textbf{MIDI 'ctrl'} file, with support for displaying pattern and action statuses. The main file is \texttt{qseq66.rc}. \item Improved \textbf{alternate} keyboard layout support, with some support for international keyboards. \item \textbf{Mapping} of port numbers to a consistent set of port names. \item Providing for \textbf{routing of MIDI input} events to patterns based on port number or channel number. \item \textbf{Export songs} to SMF 0 and 1 formats in various ways. \item \textbf{Internals}: More efficient lookups for using control maps and lambda functions. \item Configurable as a \textbf{command-line} application with the option to run as a headless \textsl{daemon}. \end{itemize} For developers, a \textsl{Seq66} build is customizable via C macros and by enabling/disabling options at 'configure' time. Distro maintainers may create their own build configurations. We cannot show all permutations of settings in this document, so don't be surprised if some screenshots don't quite match one's setup. \subsection{Document Structure} \label{subsec:introduction_document_structure} The structure of this document follows the user-interface of \textsl{Seq66}. To help the reader jump around this document, it provides multiple links, references, and index entries. Also note that the various screenshots in this documents reflect various Qt themes used during testing and figure generation. \subsection{Building Seq66} \label{subsec:introduction_building_seq66} There are a number of ways of building Seq66. \begin{itemize} \item \textbf{Autotools Build and Install}. Configure, make, and install. Works with \textsl{Linux} and \textsl{FreeBSD}. \item \textbf{Bootstrap Install}. Generate autotools files and build settings. \item \textbf{OpenSUSE and Fedora}. Specifics for those Linux distros. \item \textbf{Qmake-based Install}. Optional on Linux, mandatory for Windows. \item \textbf{Arch Linux}. There is an \textsl{AUR} package and some other packages noted at (\cite{repositories}). \end{itemize} The \texttt{INSTALL} file included with the source code and documentation goes into great detail about these methods. Also see \texttt{data/readme.windows} and \texttt{contrib/git/git.odt} for supplemental information. % First Start \input{first_start} % The main window and live grid. \input{live_grid} % Menu \input{menu} % Preferences \input{preferences} % Patterns Panel \input{patterns_panel} % Pattern Editor \input{pattern_editor} % Song Editor \input{song_editor} % Event Editor \input{event_editor} % Session Management \input{sessions} % Import/Export \input{midi_export} % Recording \input{recording} % Configuration files are now consolidated into one file \input{configuration} % Playlists \input{playlist} % Set Master \input{setmaster} % Mutes and mute-groups \input{mutes} % Palettes \input{palettes} % Tables of keyboard and mouse actions \input{kbd_mouse} % Meta-event support %%% \input{meta_events} % Windows \input{windows} % Discussion of ALSA support \input{alsa} % Discussion of JACK support \input{jack} % Port-Mapping \input{port_mapping} % Headless version \input{headless} % Setup for Launchpad Mini \input{launchpad_mini} % Important Concepts \input{concepts} % Discussion of MIDI formats related to Seq24 and Seq66 \input{midi_formats} % Acknowledgments \input{kudos} \section{Summary} \label{sec:summary} Contact: If you have ideas about \textsl{Seq66} or a bug report, please email us (at \url{mailto:ahlstromcj@gmail.com}). If it's a bug report, please add \textbf{[BUG]} to the Subject, or use the GitHub bug-reporting interface. % References \input{references} \printindex \end{document} %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/sessions.tex ================================================ %------------------------------------------------------------------------------- % seq66 sessions %------------------------------------------------------------------------------- % % \file sessions.tex % \library Documents % \author Chris Ahlstrom % \date 2020-10-03 % \update 2025-06-07 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides a discussion of how Seq66 supports session management, specifically % the Non Session Manager. % %------------------------------------------------------------------------------- \section{Session Management} \label{sec:sessions} A session is a group of applications and their configuration and connections. Session management recreates complex setups and provides some uniformity of application control in a session. The first thing to do for session management is to make sure that the application is capable of various levels of session management, from \textsl{UNIX} signals to a complete session manager like the \textsl{Non/New Session Manager}. Basic session management consist of being able to properly start the application and let it run properly during its life-cyle, whether it is a command-line application or a graphical application. \textsl{Seq66} supports session management in three ways: \begin{enumber} \item \textbf{Signals}. During a normal run, \textsl{Seq66} will respond to signals to save and to quit. The normal configuration files and command-line options will be used, and can be marked to be saved at exit. This mode is useful with \textsl{nsm-proxy}, a way to script applications that don't have \textsl{NSM} support. \item \textbf{JACK Session} Deprecated, but implemented nonetheless. Many still use it. This session manager allows the configuration files to be stored in a separate directory, for \textsl{Seq66} to be started, and files to be saved. No restrictions on where the MIDI files can be stored. \item \textbf{Non Session Manager} Known as \textsl{NSM}, and in the form of a fork of that project, \textsl{New Session Manager}, it provides a replacement for \textsl{JACK Session}. It requires all files to be stored in a session directory, and provides commands for saving, quitting, hiding/showing the user-interface, and more. Like \textsl{JACK Session}, it allows control over the startup of multiple applications, the process of saving a session, and provides a way to save their patching (connections) in \textsl{JACK}. However, it supports more functionality and has strict requirements the application must follow. Development of NSM has, for various reasons, been suspended, but offshoots such as \textsl{Agordejo} (\cite{agordejo}) and \textsl{RaySession} (\cite{raysession}), which are front ends for the \textsl{New/Non Session Manager} (\cite{nsm}), continue to advance. \end{enumber} For session management, \textsl{NSM} is the way to go. \textsl{JACK} session management is provided for those who still use it. There are other session solutions, such as \textsl{aj-snapshot}, \textsl{Claudia}, and \textsl{Chino}. For now, we do not discuss them. The desired session can be set in the \textbf{Edit / Preferences / Session} tab. But note that, if started by \textsl{NSM}, \textsl{Seq66} will detect it and nonetheless set up for NSM usage. The \textsl{NSM} setting is useful for attaching to a pre-existing known session. \textsl{JACK} session management events are processed only if \textsl{JACK} is selected. \textsl{JACK} session management will still start \textsl{Seq66} in an existing session, if \textsl{JACK} is not selected. Also note that sometimes one will want the session manager to make the JACK connections. In this case, go to \textbf{Edit / Preferences / JACK / Jack Auto-Connect}, uncheck that option, and restart \textsl{Seq66}. This option, \texttt{jack-auto-connect} can also be changed in the 'rc' file. The \textsl{NSM} tool called \textsl{jackpatch} can also be used to manage connections. \subsection{Session Management / Signals} \label{subsec:sessions_signals} \index{sessions!signals} By default, the basic form of session management in \textsl{Seq66} occurs by signals. A session manager can start \textsl{Seq66}, and it can tell \textsl{Seq66} to save or stop. Starting is done by a system call to spawn the application. The save and stop actions are supported by sending the following signals to the application: \begin{itemize} \item \texttt{SIGINT}. This signal stops \textsl{Seq66}. It corresponds to using \texttt{Ctrl-C} from the command-line to stop \textsl{Seq66}. This signal should work for both the graphical and command-line application. As \textsl{Seq66} shuts down, it does its normal saving of the current state of the configuration. \item \texttt{SIGTERM}. This signal also stops \textsl{Seq66}. It can be sent by an application to exit \textsl{Seq66}. \item \texttt{SIGUSR1}. This signal tells \textsl{Seq66} to save. This action will save the current MIDI file. \end{itemize} One application that can control \textsl{Seq66} via these signals, when not in session mode, is \textsl{nsm-proxy}: \url{https://non.tuxfamily.org/wiki/nsm-proxy} \textsl{NSM-Proxy} is a simple \textsl{NSM} client for wrapping non-NSM capable programs. It enables the use of programs supporting LADISH Level 0 and 1, and programs which accept their configuration via command-line arguments. There is a command-line version and a graphical version. % More to come on how to use \texttt{nsm-proxy}. \subsection{Session Management / JACK Session} \label{subsec:sessions_jack} Although deprecated by the \textsl{JACK} authors in favor of \textsl{NSM}, we are implementing \textsl{JACK} session (JS) management for the benefit of people who either do not know of \textsl{NSM} or do not want to implement or use it. \textsl{Seq66}, as a JS-aware applications, is set up to \begin{enumerate} \item Register with a JS manager. \item Respond to messages from the JS manager. \item Be startable with session information. \end{enumerate} A response to a JS message will do one of the following: \begin{itemize} \item Save the application's state into a file, where the directory is supplied by the session manager. \item Reply to the session manager with a command-line that starts the application, with information to restore its state, such as the name of the file holding its state information. \end{itemize} JS-aware clients identify themselves to the session manager by a UUID (unique universal identifier). The session manager provides it to the client application as an integer represented as a string. This can be passed to the session manager when registering, but \textsl{Seq66} just uses the value given to it (for now). % but should also be passed back to the client when it is restarted %by the session manager. This is done by a command line argument to the %application, and the format of the command line is also up to the client. For this discussion, we will use the \textsl{JACK} session implementation in the \textsl{QjackCtl} application. Also, read the script stored in \texttt{seq66/data/linux/jack/startqjack} to set up \textsl{QjackCtl} to run \textsl{JACK} and kick off \textsl{a2jmidid}; it should be added to the \textsl{QjackCtl} configuration. A more recent script, \texttt{seq66/data/linux/jack/jackctl}, is what we tend to use, but not for this demonstration. Once that setup is made (installing the script and configuring \texttt{qjackctl}, then start \texttt{qjackctl}. Verify that there are a number of system audio and MIDI playback and capture port, \textsl{PulseAudio JACK} sinks and sources if the system uses \textsl{PulseAudio}, and that there are "a2j" MIDI ports for all of your USB hardware devices. Then start \textsl{Qsynth} so it uses \textsl{jack} for MIDI and \textsl{jack} (or \textsl{pulseaudio}) for audio. Then run \textsl{Seq66} with \textsl{JACK} for slave transport and for MIDI (either command works the same): \begin{verbatim} $ qseq66 --jack-slave --jack-midi $ qseq66 --jack-slave --jack \end{verbatim} Load a file, make sure its MIDI output goes to "fluidsynth" or "qsynth", and plays. In your desired location (e.g. \texttt{~/.config/seq66/sessions}, create a new session directory (e.g. \texttt{qtest}). In \textsl{qjackctl}, open the \textbf{Sessions} dialog. Click \textbf{Save}, and choose the directory just created. In the dialog should appear entries for MIDI capture and playback for "fluidsynth" and "seq66", all the "a2j" USB devices, plus an entry for \textsl{JACK} client \textsl{seq66master} or \textsl{seq66slave} that shows something like: \begin{verbatim} qseq66 --jack --jack-master --jack-session-uuid 84670 --home ${SESSION_DIR} qseq66 --jack --jack-slave --jack-session-uuid 84670 --home ${SESSION_DIR} \end{verbatim} In the \textbf{Connections} dialog of \textsl{QjackCtl}, all of these ports will be shown in the MIDI tab, auto-connected appropriately. In the sessions directory that was created, will be seen an empty \texttt{seq66master} or \texttt{seq66slave} directory, and a \texttt{sessions.xml} configuration file containing the information shown in the sessions dialog. Exit \textsl{Seq66}, \textsl{QSynth} and \textsl{QjackCtl} (in that order). One issue is that \textsl{QSynth} does not support \textsl{JACK Session}. Try it with \textsl{Yoshimi}, which does support it. \subsection{Seq66 Session Management / NSM} \label{subsec:sessions_nsm} \index{sessions!nsm} The \textsl{Non Session Manager} is an API implementation for session management for Linux audio/MIDI. \textsl{NSM} clients use a well-defined \index{sessions!OSC} \textsl{OSC} protocol (\cite{osc}) to communicate with the session management daemon. Note that \textsl{Non Session Manager} is in a state of suspended development, and has been reimplemented as a \textsl{GitHub} project, the \textsl{New Session Manager}. The applications it manages should be installed normally (that is, for system-wide usage, in \texttt{/usr/bin/} or \texttt{/usr/local/bin}). Other locations, if used, must be added to the \texttt{PATH} environment variable. The following is the recommended setup for get ready for NSM usage: \begin{itemize} \item Set up all of the \textsl{hardware} MIDI input and output ports, including USB MIDI. \item Start JACK. Important! The \texttt{jackctl} script can be used to start (and stop) JACK and to run \textsl{a2jmidid}. \item Start the JACK-using software synthesizers. \item Run \textsl{Seq66} stand-alone after installation. \item Set it to use JACK for MIDI (and also for Transport, if desired). \item If the NSM program \textsl{jackpatch} is going to be used for saving and restoring connections, make sure that \textsl{Seq66}'s auto-connect feature is \textsl{disabled}. Another option is to use \textsl{virtual ports} (the "manual" option where the user makes the connections). \item Run one of the following GUI applications to create a new NSM session: \textsl{agordejo}; \textsl{raysession}; or the legacy NSM application. \item Once the session is running, verify the ports and MIDI settings that have been copied from \textsl{\textasciitilde/.config/seq66} to \textsl{\textasciitilde/.local/share/nsm/sessionname}. \end{itemize} \subsubsection{Session Management / NSM / First Run Without NSM} \label{subsec:sessions_nsm_first_run_without_nsm} This section discusses what happens when \textsl{Seq66} is installed, then run outside of any session from the console or an application menu. For a discussion where \textsl{Seq66} is run for the first time under \textsl{NSM}, see \sectionref{subsec:sessions_nsm_first_run_in_nsm}. Generally, after installing \textsl{Seq66}, or when creating a new setup (such as a play-list) it is good to run it normally first, to simplify trouble-shooting. This action creates the configuration files in the default location, \texttt{/home/user/.config/seq66}: \begin{verbatim} $ qseq66 No 'rc' file, will create: qseq66.rc/ctrl/midi/mutes No 'usr' file, will create: /home/user/.config/seq66/qseq66.usr File exists: /home/user/.config/seq66/qseq66.rc Saving initial config files to session directory! Writing 'rc': /home/user/.config/seq66/qseq66.rc Writing 'ctrl': /home/user/.config/seq66/qseq66.ctrl Writing 'mutes': /home/user/.config/seq66/qseq66.mutes Writing 'usr': /home/user/.config/seq66/qseq66.usr . . . \end{verbatim} Then exit \textsl{Seq66} to ensure the configuration files are created. Optionally, in this initial setup, one can also create a 'playlist' file and a 'drums' file, or copy them from: \begin{verbatim} /usr/share/seq66-0.91/data/samples \end{verbatim} to \begin{verbatim} /home/user/.config/seq66 \end{verbatim} and modify them appropriately. Another first-time modification to consider is setting up \textsl{Seq66} to use the \textsl{JACK} audio/MIDI subsystem (on \textsl{Linux}). In the 'rc' file, look for the following line: \begin{verbatim} [jack-transport] jack-midi = false \end{verbatim} And change it to: \begin{verbatim} [jack-transport] jack-midi = true \end{verbatim} Another first-time modification to consider is using virtual ports (option \texttt{-{}-manual-ports}) versus the automatic port connections \textsl{Seq66} normally makes. This setup allows the user to manually make connections between \textsl{Seq66} and other MIDI applications. In the 'rc' file, look for the following lines: \begin{verbatim} [manual-ports] virtual-ports = false # 'true' = manual (virtual) ALSA or JACK ports output-port-count = 8 # number of manual/virtual output ports input-port-count = 4 # number of manual/virtual input ports \end{verbatim} And change the virtual-ports line to: \begin{verbatim} [manual-ports] virtual-ports = true # 'true' = manual (virtual) ALSA or JACK ports \end{verbatim} Most of these settings can be made in \texttt{Edit / Preferences}. It is then important to start \texttt{qseq66} in the normal manner again, and verify that everything works as expected. We have added a command, \textbf{File / Import / Project Configuration} command to import the configuration files from one directory into the current \textsl{NSM} session configuration directory. This import used to be automatic, but is too surprising to an unsuspecting user. \subsubsection{Seq66 Session Management / NSM / Run in NSM} \label{subsec:sessions_nsm_first_run_in_nsm} Note: When \textsl{Seq66} is run in \textsl{NSM} for the first time, a stock default configuration is saved when \textsl{Seq66} exits. This is different from earlier behavior, where the home configuration was imported automatically. Now, the user must use the \textbf{File / Import / Project Configuration...} command to import an existing setup.. For illustration, we run \textsl{NSM} from a terminal window, which can be very helpful when problems occur. \begin{verbatim} $ non-session-manager [non-session-manager] Starting daemon... [nsmd] Session root is: /home/user/NSM Sessions NSM_URL=osc.udp://mycomputer.mls:19625/ [nsmd] Listing sessions \end{verbatim} \index{sessions!non-starter} \index{sessions!liblo library} If \textsl{NSM} refuses to start, make sure that the \texttt{liblo} library from the OSC project is installed. \index{sessions!/etc/hosts} \index{sessions!loopback interface} If it is installed, then check the \texttt{/etc/hosts} file to make sure that a loopback interface is defined. In some versions of \textsl{Linux}, it isn't defined properly, and the \textsl{NSM} daemon (\texttt{nsmd}) will not start. Here is an example of the loopback installed in \textsl{Debian Sid}; \begin{verbatim} 127.0.0.1 localhost 127.0.1.1 mycomputer.mls mycomputer \end{verbatim} The NSM user-interface (not shown here) that comes up is empty at first. So create a session by clicking the \textsl{NSM} \textsl{New} button, and entering a session name (here, "\textbf{Seq66}") in the prompt that comes up. In the console window, a couple of \texttt{/nsm/server/new} \textsl{OSC} messages about the creation of the session appear. \begin{verbatim} [non-session-manager] Sending new for: Seq66 [nsmd] Creating new session "Seq66" [non-session-manager] /nsm/server/new says Created. [non-session-manager] /nsm/server/new says Session created \end{verbatim} Next, click the \textsl{Add Client to Session}, and, since \texttt{qseq66} has been installed system-wide, it is in the \texttt{PATH} and its executable name can be entered simply: "\texttt{qseq66}". A number of console messages from \textsl{Seq66} appear, plus some messages from \textsl{NSM}. \begin{verbatim} [non-session-manager] Sending add for: qseq66 [nsmd] Process has pid: 2797436 [nsmd] Launching qseq66 [nsmd] Got announce from seq66 [nsmd] Client was expected. [nsmd] Process has pid: 2797436 [nsmd] The client "seq66" at "osc.udp://127.0.0.1:13318/" informs us it's ready to receive commands. \end{verbatim} Important: the \textsl{Seq66} user-interface will not show at first. It is hidden so that the screen is not inundated with the windows of all the applications that are (eventually) running under the session. This is especially annoying with tiled window managers. In order to see the \textsl{Seq77} user-interface, click on the \textbf{GUI} button in the session line shown in the \textsl{NSM} window to make \textsl{Seq66} visible. Once \textsl{Seq66} is running under \textsl{NSM}, then click the \textbf{Save} button at the top of the \textsl{NSM} interface in order to save the session information. This is an \textsl{important} step. After \textsl{Seq66} exits, one can see what has been created to support the session; the directory that \textsl{NSM} creates by default is \texttt{/home/user/.local/share/nsm}. (It used to be \texttt{/home/user/NSM Sessions}.) \index{nsm!old session} \begin{verbatim} $ pwd /home/user/.local/share/nsm $ lstree Seq66 Seq66/ +-- seq66.nGJDW/ | +-- config/ | | +-- qseq66.ctrl | | +-- qseq66.drums | | +-- qseq66.mutes | | +-- qseq66.palette | | +-- qseq66.playlist | | +-- qseq66.rc | | +-- qseq66.usr | +-- midi/ +-- session.nsm \end{verbatim} \textbf{Reminder}: If one is running a recent "New" version of the \textsl{Non Session Manager}, then the session data is not stored in \texttt{/home/user/NSM Sessions}, but in in \texttt{/home/user/.local/share/nsm}, \index{nsm!new session} So \textsl{NSM} has created a directory with the session name we gave it: \texttt{Seq66}. \textsl{Side note: the \texttt{nsmd} daemon creates a file named after its process ID in \texttt{\$XDG\_RUNTIME\_DIR/nsm/d}, e.g. \texttt{/run/usr/1000/nsm/d/2170}.} Under that directory is a file, \texttt{session.nsm}, which contains information like the following: \begin{verbatim} seq66:qseq66:nGJDW \end{verbatim} The format of this text is \texttt{appname:exename:nXYZT}, where \texttt{XYZT} is a 4-letter randomly-generated token generated by \textsl{NSM}. Also created is a directory, \texttt{seq66.nXYZT}, which is the root of the \texttt{Seq66} session. The rest of the directories, \texttt{config} and \texttt{midi}, are generated by \textsl{Seq66} The \texttt{config} directory is used instead of \texttt{/home/user/.config/seq66}) and the \texttt{midi} directory contains new MIDI files, imported MIDI files, or MIDI files from a play-list. The new \texttt{config} directory contains versions of the various configuration files that will always be used to start up \textsl{Seq66} during the session. One can also add valid play-list, palette, and drums/note-mapping files to that directory later. If before running \textsl{NSM}, one had set up a play-list file and provided the proper "MIDI base directory" in the 'rc' file, then all the MIDI files are copied to the \textsl{NSM} session \texttt{midi} directory, preserving all relative directories. When the \textsl{Non Session Manager} is started the next time, and the "Seq66" session is clicked, this starts \textsl{Seq66}, and the play-list can be seen in the \textsl{Playlist} tab. Note that the \textbf{Save} button on the session's row in the \textsl{NSM} user-interface sends a message to \textsl{Seq66} to tell it to save its state. One last thing to note is that, when viewing the MIDI ports created by \textsl{Seq66}, they will be named "seq66" when not in session management, and "seq66.nXYZT" (for example) when under session management. This makes it possible to run multiple instances of \textsl{Seq66}. \subsubsection{Session Management / NSM / Run with Remote NSM} \label{subsec:sessions_nsm_before_using_nsm} As described in the \textsl{NSM} documentation, the \texttt{nsmd} daemon can be run stand-alone, and can also be ran on a remote computer. The \texttt{qseq66.usr} file can be edited to allow \textsl{Seq66} to use a pre-planned \textsl{NSM} and specify the URL to connect. Look for the following lines in the 'usr' file: \begin{verbatim} [user-session] session = none url = "" \end{verbatim} Now assume we've run the daemon as follows: \begin{verbatim} $ nsmd --osc-port 9999 [nsmd] Session root is: /home/user/NSM Sessions NSM_URL=osc.udp://mycomputer.mls:9999/ \end{verbatim} Change the \texttt{session} lines to allow the usage of \textsl{NSM} at that URL: \begin{verbatim} [user-session] session = nsm url = "osc.udp://mycomputer.mls:9999" \end{verbatim} The \texttt{url} is not used if running \textsl{Seq66} from the \textsl{NSM} GUI... the application will get the URL from the \textsl{NSM} environment. Note that \texttt{qseq66} can still be run outside of a session manager. It will detect the absense of the session manager and run normally. \subsubsection{Session Management / Sessions Tab} \label{subsubsec:sessions_tab} The \textsl{Session} tab is a mostly \textsl{read-only} tab provided to orient the user to the setup supported by the session. When not running in a session, the normal configuration directory and files are shown. When running in an \textsl{NSM} section, the configuration information received from \textsl{NSM} is displayed. It is meant to display information to help the user understand what is happening in the run. Two screenshots are shown below. % The first shows a \textbf{Session Log} pane, but this has been removed % as non-functional. \begin{figure}[H] \centering \includegraphics[scale=0.65]{tabs/session/session-song-info.png} \caption*{Session Tab with Song Info} \end{figure} \index{sessions!ui} This section describes the \textsl{Session} tab in the main \textsl{Seq66} window. This tab displays mostly informative and \textsl{read-only} information (except for the name of the log file and the editable song-info pane). It displays the following bits of information that \textsl{Seq66} has received from \textsl{NSM} via the \texttt{nmsd} daemon: \begin{itemize} \item \textbf{Session Manager}. The name of the session manager as sent by the session manager. \item \textbf{Session path}. The root ("home") configuration directory of the session. All data goes into this directory. The name of the "home" directory is of the form \texttt{HOME/nsmroot/sessionname/uniqueid}. \texttt{HOME} is the usual UNIX home directory for the user. "nsmroot" depends on which version of the New/NSM session manager is used. \index{session!non} For the original \textsl{NSM}, this directory is \texttt{NSM Sessions}. \index{session!new} For the \textsl{New Session Manager}, this directory is \texttt{.local/share/nsm}. The session name is provided by the user when creating the session. The unique ID is generated by the non/new session manager. If not running in a session, the active configuration directory is shown. \item \textbf{Server URL}. The session's \textbf{OSC URL}, which includes the port number. Generally, the port number is selected at run-time, but it is also possible to configure \textsl{NSM} to use a specific port number. \item \textbf{Config File}. This is either the display name for the session, the sub-directory that contains the session configuration, or the base name of the main configuration file, such as \texttt{qseq66.rc}. \item The generated \textbf{client ID/UUID} for the session. \item \textbf{Macro Execution}. This drop-down contains all of the named MIDI macros defined in the 'ctrl' file's \texttt{[macro-control-out]} section. By selecting one, it is automatically sent out via the \texttt{[output-buss]} port defined in the 'ctrl' file. The "startup" and "shutdown" macros, if defined, are sent automatically. "Startup" is useful to put a MIDI controller into the proper mode for controlling and displaying information in \textsl{Seq66}, and "shutdown" can return the controller to its normal operating mode. \item \textbf{Session Log File}. The editable name of the log file to which to redirect warning and error messages during the action of \textsl{qseq66}. Normally, the text is shown in the console window (when running in a console window). This name is only a base-name (e.g. \texttt{seq66.log}); it is always stored in the "home" configuration directory, whether normal or the NSM session directory. \item \textbf{Song Path}. Holds the full file-specification of the currently-loaded MIDI file. \item \textbf{Last Directory}. Shows the directory from where the last MIDI file was loaded. \item \textbf{Restart}. \index{restart!manual} After editing some of the preferences in the \textbf{Edit / Preferences} dialog, one can (later) visit this tab and press this button to essentially restart \textsl{Seq66}, reloading the new configuration. Be careful! \item \textbf{Song Info}. \index{song!info} \index{meta text} This item is a plaintext edit-control that allows the viewing and editing of "song info". The song info is merely the first Meta Text event, if any, found in pattern 0. The pattern number can be changed if desired, to show text in other patterns. This field can be edited with information such as date, composer, playback notes, etc. up to about 32000 ASCII characters. Extended ASCII characters are encoded as three hexadecimal bytes: \texttt{$\backslash$xx}. \item \textbf{Pattern No}. This spin box allows for viewing and editing the text in a selected pattern. \item \textbf{Save Info}. When clicked, the meta text is copied to the selected pattern, thus modifying the file, which can then be saved. \index{karaoke} For tracks (such as a karaoke lyric track) with many text events, they are all shown, separated by semi-colons. Don't try to edit karaoke data, except in the \textsl{Event Editor}. A simple description for each pattern is fine to edit. An example of a karaoke song is found in the \texttt{contrib/midi} directory: \texttt{Carpet\_of\_the\_Sun\_karaoke\_meta\_text.mid}. \item \textbf{Restart}. When in NSM, this button is enabled when a restartable change is made here. \end{itemize} Note that there are many implementations of NSM clients: \textsl{Agordejo} \cite{agordejo}, \textsl{RaySession} \cite{raysession}, and the \textsl{New/Non Session Manager} \cite{nsm} with the JACK project's \texttt{nsm-legacy-gui}. \subsubsection{Seq66 Session Management / NSM / File Menu} \label{subsubsec:sessions_file_menu} The author of \textsl{NSM} has provided documentation for session-management which provides very strict instructions on how an application must behave under session management. \textsl{Seq66} tries very hard to stick to these instructions. One major adjustment an application must make is to adhere to the "File menu" guidelines. \begin{figure}[H] \centering \includegraphics[scale=0.65]{tabs/session/nsm-qseq66-menus-2.png} \caption*{File Menu Under NSM, Composite View} \end{figure} Not (yet) shown in the figure are the \textbf{Project Configuration} options for import and export. (See \sectionref{subsec:menu_file}.) This has been changed for 0.98.6; the \textbf{Quit} menu entry becomes \textbf{Hide}, as per the NSM protocol. Also have fixed a bug that disables the load-most-recent option under NSM. We will update the figure above eventually. The following items describe the menu entries. \begin{itemize} \item \textbf{New MIDI File}. This function prompts for the name of a new MIDI file and clears the current MIDI file. The file-name must not include a full-path to the file. The path is hardwired by the session. A relative path can be included. This name is needed because there is no "Save As" option when running in an \textsl{NSM} session. \item \textbf{Import / Project Configuration...} Imports a whole project configuration into the current NSM session. This functionality used to be automatic (importing the "home" configuration), but it is better left to the user to do. \index{restart!automatic} However, the restart of \textsl{Seq66} after this operation is automatic. Be careful! \item \textbf{Import / MIDI to Current Set...} This action works the same as in normal mode. This item allows the user to grab a MIDI file from anywhere and import it into the current set. The default directory that comes up in the prompt is the "last-used directory" from the session 'rc' file. \item \textbf{Import / Playlist...} This action works the same as in normal mode. The destination is the NSM session directory. \index{restart!automatic} Once the playlist is imported, \textsl{Seq66} is automatically \textsl{\textbf{restarted}} in order to load the playlist. Be careful! \item \textbf{Import / Import into Session...} Prompts the user for a MIDI file to be imported (copied) into the current session. The path to the file is then adjusted to use the \textsl{NSM} \texttt{midi} subdirectory. \item \textbf{Export / Song...} Allows exporting the current song as a stock MIDI file, using the performance information (triggers) to write the MIDI data as it would be played in "song" mode. The default directory that comes up in the prompt is the "last-used directory" from the session 'rc' file. \item \textbf{Export / MIDI Only...} Allows exporting the current song as a stock MIDI file. The "proprietary" SeqSpec data is \textsl{not} written. The default directory that comes up in the prompt is the "last-used directory" from the session 'rc' file. \item \textbf{Export / SMF 0...} This action works the same as in normal mode. It converts the destination file to SMF 0 format. \item \textbf{Hide}. This menu item replaces the \textbf{Quit} item. It hides the main window and tells NSM about it. \end{itemize} At some point we would like to present a small tutorial showing a session under \textsl{JACK}. Also note that NSM can invoke or kill applications via \textsl{signals}, as explained in \sectionref{subsec:sessions_signals}. \subsubsection{Seq66 Session Management / NSM / Debugging} \label{subsubsec:sessions_debugging} This section is oriented towards advanced users who found a problem running \textsl{Seq66} and want to track it down themselves. The issue is that we need to start the application under the debugger, or start it under NSM and somehow attach to \textsl{Seq66} before it starts running. Another issue is that we have found that, at least on the same host, an NSM session \textsl{must} be open before \textsl{Seq66} can attach to it, even if the correct \texttt{NSM\_URL} is provided. So we have to open a session, get the proper URL, configure it in the 'usr' file, and then start \textsl{Seq66} under the debugger. Here are the steps: \begin{enumerate} \item Start \textsl{non-session-manager} from a command-line console. Write down the URL that it advertises. \item Prepare a session for the executable as per earlier instructions. Once \texttt{qseq66} starts, immediately exit it, and leave the session open. \item Open the proper 'usr' file (usually \texttt{qseq66.usr}) in a text editor. Set variable "session = nsm", and set the variable "url" to the value that was advertised. \item Now start \texttt{qseq66} in a debugger. \item Set a breakpoint in \texttt{clinsmanager::detect\_session()}. \end{enumerate} Now you can step through and see where NSM and Seq66 are getting mixed up. Also check the session directory afterward to make the configuration (and any MIDI files) are in good shape. \subsection{Seq66 Session Management / LASH} \label{subsec:sessions_lash} \index{sessions!lash} LASH support has been removed. Use the \textsl{NSM Session Manager} or the \textsl{JACK Session Manager}. \subsection{Seq66 Session Management / sessions.rc} \label{subsec:sessions_sessions_rc} \texttt{Seq66} also supports a more simplistic type of "session", where a whole different set of configuration files can be selected. One can use the \texttt{-{}-home} and \texttt{-{}-config} options to specify alternate locations and names for the configuration files. However, if one has a number of configurations (e.g. for different sets of equipment. playlists, style-sheets, and palettes), it's tedious to type these options. The \texttt{sessions.rc} file provides a way to set up a number of configurations and select one with one option. It is always located in the default "home" directory, but can refer to directories anywhere. It can also specify a different MIDI client-name (option \texttt{-{}-client-name}) and log-file (option \texttt{-{}-option log=filename}). The user must manually text-edit the sessions.rc to specify tag sections like the following: \begin{verbatim} [test] home = "~/.config/seq66/test/" config = "test66" client-name = "test66" log = "test66.log" \end{verbatim} \index{-{}-session-tag} \index{session-tag} This section is accessed using the \texttt{-S} or \texttt{-{}-session-tag} option, as shown here: \begin{verbatim} $ qseq66 --session-tag test $ qseq66 -S test \end{verbatim} If this is the first time this command has been run, the home directory is created. (At present, though, the initial configuration files are not created until \textsl{Seq66} exits.) \textsl{Seq66} runs with a MIDI client-name of \texttt{test66}. The base-name of each configuration file is \texttt{test66} (so we have, for example, \texttt{test66.rc}). The log file will be \texttt{~/.config/seq66/test/test66.log}. (If no log file is wanted, then set it to \texttt{""} later.) At the end of the run, all of the configuration files are saved in \texttt{~/.config/seq66/test/}. These can be edited to suit the "test" configuration. The setup can also be created via \textbf{File / Export / Project Configuration}, and then be added to \texttt{sessions.rc}. The next time \texttt{qseq66 -{}-session-tag test} is run, the test configuration is loaded and used. If the specified session tag does not exist in \texttt{sessions.rc}, then a message is shown; the user should exit \textbf{Seq66} immediately and fix \texttt{sessions.rc}. Note that the \texttt{log} option overrides the log-file setting present in the 'usr' file. Use \texttt{log = ""} to see console output. Lastly, an environment variable can be used to set a session-tag that will be used for quite awhile. It can be exported in a \texttt{bash} script, a profile, or set on the command-line (which is more difficult that using the option): \index{SEQ66\_SESSION\_TAG} \begin{verbatim} $ SEQ66_SESSION_TAG="test" qseq66 \end{verbatim} %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/setmaster.tex ================================================ %------------------------------------------------------------------------------- % seq66 setmaster %------------------------------------------------------------------------------- % % \file seq66 setmaster.tex % \library Documents % \author Chris Ahlstrom % \date 2020-01-13 % \update 2023-11-25 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides a discussion of the MIDI GUI setmaster that Seq66 % supports. % %------------------------------------------------------------------------------- \section{Seq66 Set Master} \label{sec:setmaster} The \textbf{Set Master} is a way to get a global view of all the screensets in a \textsl{Seq66} MIDI file, and to be able to do some simple operations (movement, naming, etc.) with the sets. In the latest version of \textsl{Seq66}, there are always 32 sets. This simplifies the handling of sets. Some sets may be empty. \begin{figure}[H] \centering \includegraphics[scale=0.50]{tabs/sets/setmaster-tab.png} \caption{Sets Tab} \label{fig:setmaster_tab} \end{figure} The operations that can be done consist of viewing the sets, making a screenset active, rearranging the sets, clearing sets, and getting a survey of the contents of the sets. \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Sets Grid}{set master!grid} The set grid is always 4 x 8. Like the mute-groups, there's not a lot of benefit supporting more sets. The reader may wish to email us arguing for a different point of view. \index{play screen} This grid shows the non-empty sets that are present in the MIDI file, represented by enabled buttons. With \textbf{Triggers} mode set, this allows one to choose which set is currently active (i.e. which is the "play screen"). Click on it and it is active. Also remember that the grid keystrokes ("[" and "]" by default) in the main window can move the playscreen to the previous or next set. \itempar{Set List}{set master!list} The table at the right shows the set numbers, how many patterns/sequences are in each set, and the set name. The set name is editable once it is double-clicked. \itempar{Set Number/Name Fields}{set master!number/name} Currently just shows the number and name of the selected set. Eventually, these fields will be editable. \itempar{Up/Down Buttons}{set master!up/down buttons} These buttons allow the user to move the selected set up and down in the list. \itempar{Set 0}{set master!set 0} If trigger mode is in effect, clicking this button makes set 0 active. It is an alternative to clicking on grid button 0. \itempar{Show Set Info}{set master!show set info} Clicking this button lists all the sets and their tracks in the read-only edit box at the left. \itempar{Clear Set}{set master!clear set} Clicking this button deletes the patterns from the set selected in the table. Note that the 0th set cannot be cleared. Would one ever want to do that? In \textsl{Seq66}, there must always be a set 0. \itempar{Triggers}{set master!set triggers} This checkable button toggles the action done by clicking a button in the set grid. If checked, then clicking an active set button will make that set active during playback. That effect depends on whether the sets are configured to auto-arm when selected, or not. If not checked, then clicking an active set button merely summarizes the patterns in the set, in the text field below the set grid. \subsection{Set Handling} \label{subsec:setmaster_handling} This section talks about how sets work. The topics are \begin{itemize} \item \textbf{Set Management} \item \textbf{Empty-Set Handling} \end{itemize} \subsubsection{Set Management} \label{subsubsec:setmaster_management} This section will discuss the work-flows of using sets to organize a song and to control playback. MORE TO COME Real Soon Now. \subsubsection{Empty Set Handling} \label{subsubsec:setmaster_empty_sets} When \textsl{Seq66} loads a song, it loads the existing sets in the song and sets their names as stored in the \texttt{c\_notes} \textsl{SeqSpec} (see \sectionref{subsec:midi_format_meta_format}). In addition, one dummy and invisible set is created for internal management purposes. When a new song is created, one usable set, Set \#0, is always created, as a starting point. One generally starts with this set and adds patterns to it. When one selects the next set (e.g. using the \textbf{Live} frame's \textbf{Set} spin control), that set does not exist, but is immediately created. So now the song has two sets, with the second one being empty. If the song is now saved, so is the empty set's file name. However, empty sets are not saved; a set must be populated with at least one pattern to be saved. The following figure shows what happens when a song with 4 sets (0, 1, 2, and 7) is loaded, and then the user increments the spin-button all the way to set 8. \begin{figure}[H] \centering \includegraphics[scale=0.85]{tabs/sets/setmaster-with-additional-sets.png} \caption{New Sets Creation} \label{fig:setmaster_set_creation} \end{figure} There are new sets 3, 4, 5, 6, and 8. However, if one saves and then reloads this song, the empty sets are gone. Just something to be aware of. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/song_editor.tex ================================================ %------------------------------------------------------------------------------- % song_editor %------------------------------------------------------------------------------- % % \file song_editor.tex % \library Documents % \author Chris Ahlstrom % \date 2015-08-31 % \update 2025-06-06 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides the concepts. % %------------------------------------------------------------------------------- \section{Song Editor} \label{sec:song_editor} The \textbf{Song Editor} is also known as the "arrangement panel" or "performance editor". The \textbf{Song Editor} combines all patterns into a complete tune with controlled repetitions of each pattern. It shows one row per pattern/loop/sequence, with the placement of each pattern at various and possibly multiple time locations in the song. \index{performance} In \textsl{Seq24} parlance, the song editor creates a \textsl{performance}, and the performance is implemented by a set of triggers. Triggers are internal timing items stored with each pattern when a \textsl{Seq66} MIDI tune is saved. \index{song mode} In \textbf{Song} mode, these triggers, not the user, control playback. \begin{quotation} \textbf{Tip} In the installed \texttt{data/midi} directory, there are sample files for the tunes "Europe Endless" and "Peter Gunn" that illustrate what can be done with the song editor. They are accompanied by descriptive text files. Be sure to check them out. \end{quotation} \index{song editor!dual} Two song editor windows can be brought onscreen, one in the \textbf{Song} tab, and one in an external window. The \textbf{Song} tab and a \textbf{Song} window can be shown at the same time. % The \textbf{Song} editor activates % the \textbf{Song} mode of \textsl{Seq66}. % When the song editor has the focus of the application, it % takes over control from the patterns panel, and controls playback. Once playback is started in the song editor (using the \texttt{Space} or \texttt{.} keys), live mode is disabled. The song editor takes over the arming/unarming (unmuting/muting) shown in the patterns panel. The highlighting of armed/unarmed patterns changes according to whether the pattern is triggered in the song editor. If one tries to change the muting in the patterns panel, the song editor immediately returns the pattern to the state it has in the song editor. The only way to manually change the muting then is to click the pattern's label in the song editor. Both the song editor and the patterns panel both reflect the change in muting in the user-interface. \begin{figure}[H] \centering \includegraphics[scale=1.0]{song-editor/song-editor-annotated.png} \caption{Song Editor Window, Annotated} \label{fig:song_editor_window_annotated} \end{figure} This is another diagram one might want to print out for reference. Note the major items shown: \begin{enumber} \item \textbf{Top Panel} (recent updates not yet shown) \item \textbf{Measures Ruler} \item \textbf{Patterns (Names) Panel} \item \textbf{Song Roll} \item \textbf{Bottom Panel} \end{enumber} The estimated duration of the tune appears at the right of the top panel. It is calculated from the length of patterns and the song triggers that may be present. Here are some of the features for the song editor: \begin{itemize} \item Toggling of the mute state of multiple patterns via the name fields of the patterns. \item Optional pattern coloring (selected in the Patterns panel) \item A configurable progress bar. \item \textbf{Undo} and \textbf{Redo} buttons. \item A \textbf{Transpose} button and transposition drop-down selector. \item Red coloring of events for patterns that are not transposable, such as drum tracks. \item Horizontal zoom (and a simple vertical zoom) via buttons and keystrokes. \item Opening a pattern editor via a double-click on any occupied track. \item Creating a pattern via a double-click on any unoccupied track. \end{itemize} The song editor is a bit complex; for exposition, we break it into sections, starting with playback keystrokes. \subsection{Song Editor / Playback Keystrokes} \label{subsec:song_editor_playback_keystrokes} The song roll (center panel) of the song editor provides keystrokes for starting, stopping, and pausing playback. Other keystrokes are described in \sectionref{subsubsec:song_editor_song_roll_keystrokes}. % \itempar{Stop}{song editor!stop} % Stops the playback of the song. % \index{keys!esc (stop)} % The keystroke for stopping playback is the \texttt{Esc} character. % It can be configured to be another character (such as \texttt{Space}, which % would make the space-bar toggle the playback status). % \itempar{Stop/Pause/Play}{song editor!play} \index{keys!space (toggle play)} The keystroke for starting playback is the \texttt{Space} character. \index{L marker} It starts the playback of the song at the \textbf{L marker}. The \textbf{L marker} serves as the start position for playback in the song editor. One can change the start position only when the performance is not playing. The \texttt{Space} character also toggles playback. This character also rewinds the song to the beginning when stopping. Another keystroke for stopping playback is the \texttt{Esc} character. This character also rewinds the song to the beginning when stopping. (It also can exit paint mode, and, \textsl{if} the 'usr' \texttt{[pattern-editor] escape-pattern} option is set, will close an external song-editor window. \index{keys!period (pause)} The keystroke for pausing playback is the \texttt{Period} character. When used, it keeps the progress at the same spot as before. Note that there are no stop, pause, and play buttons in the song editor. They are provided by the main window. The \textbf{Song} tab can be activated in the main window to keep the playback buttons nearby. \subsection{Song Editor / Top Panel} \label{subsec:song_editor_top} The top panel shown earlier provides quick access to actions and configuration. \begin{enumber} \item \textbf{Undo} \item \textbf{Redo} \item \textbf{Follow Progress} \item \textbf{Zoom Out and Zoom In} \item \textbf{Toggle Selection/Trigger Painting} \item \textbf{Record Snap} \item \textbf{Grid Snap} \item \textbf{Transpose} \item \textbf{L/R Loop} \item \textbf{L/R Collapse} \item \textbf{L/R Expand} \item \textbf{L/R Expand and Copy} \item \textbf{Expand Song Roll} (not shown in figure) \item \textbf{Trigger Transpose} \item \textbf{Song Duration} \end{enumber} \setcounter{ItemCounter}{0} % Reset the ItemCounter for this list. \itempar{Undo}{song editor!undo} The \textbf{Undo} button rolls back the last change in the layout of a pattern. Each time it is clicked, the most recent change is undone. Also implemented via \texttt{Ctrl-Z}. \itempar{Redo}{song editor!redo} The \textbf{Redo} button reapplies the last change undone by the \textbf{Undo} button. Also implemented via \texttt{Shift-Ctrl-Z}. \itempar{Follow Progress}{song editor!follow} Toggles the mode of following progress for longer songs. WHen active, the song roll pages right to keep up with the progress bar. \itempar{Zoom Out/Zoom In}{song editor!zoom} These buttons change the horizontal zoom. Zoom can also be changed via the keystrokes \texttt{z}, \texttt{0}, and \texttt{Z}. Vertical zoom is also supported, by buttons to the left of the time-line and by keystrokes, as discussed below. \itempar{Toggle Selection/Trigger Painting}{song editor!paint} Toggles the ability to drag the mouse along the pattern's timeline to create triggers to indicate when the pattern will play. Short patterns will be duplicated one or more times as the mouse is dragged. This mode can alsp be changed via the keystrokes \texttt{p} ("paint"), \texttt{i} ("insert"), \texttt{x} ("excape").and \texttt{Esc}. \itempar{Grid/Record Snap}{song editor!grid/record snap} The \textbf{Movement Sizing/Snap} setting, if enabled, allows not only full clips of a pattern to be added, but smaller intervals listed below. It turns on record-snap for recording live performance triggers. It also enables the grid snap functionality, which ndicates the horizontal grid snap for movement actions and trigger drawing. If disabled, it allows the trigger to be placed and to be smoothly extended in either direction, without snapping, when the mouse is moved left or right. It allows exact recording of the musician's arming/muting of patterns. Unlike the \textbf{Grid Snap} of the pattern editor, the units of the song editor snap value are in fractions of a measure length. The following values are supported: \textsl{1/1, 1/2, 1/4, 1/8, 1/16, and 1/32} Note that arming/muting can be done in the "names" panel using a \textsl{right-click} on the pattern name. Also note that changes that occur within the snap value will cause odd recording, so be sure to set the snap value low enough. When creating triggers for patterns longer than a measure, the pattern may wrap, so that beginning notes appear at the end of the trigger, and notes can wrap around. To avoid this, a trick is needed: \begin{enumerate} \item Undo that trigger insertion. \item Select the \texttt{Length} Snap value. \item Insert the trigger. \item Click another snap value. \item Drag the trigger, usually to the left, until it reaches the desired snap location. \item Verify that the whole pattern is in place with the notes exactly placed as in the pattern. \end{enumerate} This trick is annoying, and we're not sure if note wrap-around is a feature or a bug. \itempar{Transpose}{song editor!transpose} \index{global transpose} \textbf{Transpose} consists of two controls: a combo-box to select the transpose direction and amount, and a \textbf{TT} button to reset the transposition value to 0. Global (song) ransposition ranges from -12 to +12. It can be locked in using the \textbf{Edit / Apply Song Transpose} menu entry. \itempar{L/R Loop}{song editor!play loop} This button is also present in the main window. It sets up playback to move only between the "L" and "R" markers. For a description of this button, see \sectionref{subsubsec:introduction_loop_button}. \itempar{L/R Collapse}{song editor!collapse} This button collapses the song between the \textbf{L marker} and the \textbf{R marker}. What this means is that, if there is song material (patterns) before the \textbf{L marker} and after the \textbf{R marker}, and the \textbf{Collapse} button is pressed, any song material between the L and R markers is erased, and the song material after the \textbf{R marker} is moved leftward to the \textbf{L marker}. Collapsing occurs in all tracks present in the song editor. \itempar{L/R Expand}{song editor!expand} This button expands the song between the \textbf{L marker} and the \textbf{R marker}. It inserts blank space between these markers, moving the song material that is after the \textbf{R marker} to the right by the duration of the blank space. Expansion occurs in all tracks present in the song editor. \itempar{L/R Expand and Copy}{song editor!expand and copy} This button expands the song between the \textbf{L marker} and the \textbf{R marker} much like the \textbf{Expand} button. However, it also copies the original data that is present after the \textbf{R marker}, and pastes it into the newly-available space between the L and R markers. \itempar{Expand Song Roll}{song editor!expand song roll} Sometimes one might come to the end of the song and still want to add more triggers. Clicking this button expands the song roll to the right, adding more room for triggers. \itempar{Trigger Transpose}{song editor!trigger transpose} Not to be confused with song transpose, this button and spin-box make trigger selections or segments transposable during play-back. This feature is very useful for patterns that repeat many times, but are shifted in pitch at various points. The transposition value ranges from -60 to 0 to +60, in units of semitones. The button resets the value to 0. To apply a transposition value, first set it in the spin-box. Then carefully \textsl{shift-left-click} on the desired segment(s) to transpose. A number with a plus-or-minus will appear at the left of the segment to indicate a non-zero transposition. The transposition value will be saved with the trigger when the song is saved. For a good example, see the \textsl{Kraftwerk "Europe Endless"} demonstration MIDI file in the \texttt{data/midi} directory. \itempar{Song Duration}{song editor!duration} Shows the full duration of the song based on the longest pattern or the trigger layout. Toggle between H:M:S and B:B:T notation by clicking on it. \subsection{Song Editor / Measures Ruler} \label{subsec:song_editor_measures_ruler} \index{measures ruler} The measures ruler ("bar indicator", or "Time") consists of a \textsl{timeline} at the top and the \textbf{L marker} and \textbf{R marker} mentioned above. It also includes an \textbf{END} marker to indicate the end of the song. The \textsl{measures ruler} is the ruled and numbered section at the top of the arrangement panel. It provides a place to put the left and right markers. In the \textsl{Seq24} documentation, it is called the "bar indicator". There are some hidden details (tricks) in the measures panel used to set the markers and the current play-head position. \begin{itemize} \item \textbf{In the upper half} of the time-line, the mouse pointer changes to a vertical pointer. Clicking there then shows a red dot; these mark the starting position of playback. This is useful for review. \item \textbf{In the lower half} of the time-line, the mouse pointer changes to a "finger" icon. \textsl{Left-clicking} there then moves the \textbf{L} marker to that point. \textsl{Right-clicking} there moves the \textbf{R} marker to that point. (\textbf{R} will never precede \textbf{L}, though). If the \textbf{Loop} button in the main window is active, then playback will loop between the \textbf{L} and \textbf{R} buttons. This looping now works with both Live and Song modes. \end{itemize} \index{measures ruler!left-click} \textsl{Left-click} in the bottom-half of the measures ruler to move and drop an \index{L anchor} \index{L marker} \textbf{L marker} (\textbf{L anchor}) on the measures ruler. \index{measures ruler!right-click} \textsl{Right-click} in the bottom-half of the measures ruler to drop an \index{R anchor} \index{R marker} \textbf{L marker} (\textbf{R anchor}) on the measures ruler. These markers denote the time interval from the left of the \textbf{L} marker to the right of the \textbf{R} marker. Once these markers are in place, one can then use the \textsl{Collapse} and \textsl{Expand} buttons to modify the placement of the pattern events. Note that the \textbf{L marker} serves as the start position for playback in the song editor even when not looping. One can change the start position only when the performance is not playing. \index{marker!mode} \index{marker!movement} Another way to move the \textbf{L} and \textbf{R} markers has been added. To select which marker will move, click the upper half of the time strip (otherwise, the \textbf{L} will move, prematurely) to give it keyboard focus. Then press the lower-case \index{keys!l} \texttt{l} key or the lower-case \index{keys!r} \texttt{r} key. \textsl{There is no visual feedback that one is in the movement mode.} Then press the \texttt{Left-Arrow} or \texttt{Right-Arrow} key to move the selected marker. Also included at the same level as the measures ruler are the buttons \textbf{-}, \textbf{0}, and \textbf{+}, which are used for vertical zoom, as described below. \subsection{Song Editor / Pattern Names Panel} \label{subsec:song_editor_patterns_panel} The patterns panel is at the left of the song roll. Here are the items to note in the pattern-names panel: \begin{enumber} \item \textbf{Number}. The number of the screen-set. \item \textbf{Title}. \index{pattern!title} \index{pattern!name} The title is the name of the pattern, for easy reference. \item \textbf{Color}. If a color is provided for the pattern, then that color is shown. Also, as an editing aid, the pattern over which the mouse is hovering is shown in a brighter version of the color. \item \textbf{Measures}. \index{pattern!channel} The number of measures in the pattern appears at the right of the title. \item \textbf{Pattern Number}. The pattern number is on the left of the second line of text. \item \textbf{Buss-Channel}. \index{pattern!buss-channel} This pair of numbers shows the MIDI buss number used in the pattern and the channel used for the pattern. \item \textbf{Beat/Measure}. \index{pattern!beat} This pair of numbers is the standard time-signature of the pattern. \item \textbf{Trigger Count}. \index{pattern!trigger count} This number shows the number of triggers present in the pattern. \item \textbf{Mute Indicator}. \index{song editor!mute indicator} The letter \textbf{M} is in a grey box if the track/pattern is muted via song playback, and a white (or colored) box if it is unmuted in song playback. \textsl{Left-clicking} on the \textbf{M} (or the name of the pattern) mutes/unmutes the pattern. \index{left click} Song muting is effected via a \textsl{left-click} on the pattern name. This is also useful in song-recording (where the triggers are recorded). \index{shift left click} If the Shift key is held while \textsl{left-clicking} on the M or the pattern name, then the mute/unmute state of every other active pattern is toggled. This feature is useful for isolating a single track or pattern. \index{right click} Normally, one records song triggers using the grid buttons or MIDI control to turn patterns on and off. One can also \textsl{right-click} on the pattern name during song-record and thereby see the trigger(s) being created. \item \textbf{Empty Track}. Completely empty tracks (no track events or meta events) are indicated by a dark-gray filling in the pattern column. Tracks that have only meta information, but no playable event, are indicated by a yellow filling in the pattern column. \end{enumber} The patterns column shows a list of all of the patterns that have been created in the current song. Each pattern in this list has a track of pattern layouts associated with it in the piano roll section. \index{patterns column!left-click} \index{patterns column!ctrl-left-click} \index{song editor!muting} \textsl{Left-clicking} on the pattern name or the \textbf{M} toggles the muting (arming) status of the track. It does the same thing if the \texttt{Ctrl} key is held at the same time. \index{pattern!shift-left-click} \index{song editor!inverse muting} \index{song editor!solo} \index{shift-left-click solo} \textsl{Shift-left-clicking} on the pattern name or the \textbf{M} button toggles the muting (arming) status of \textsl{all other tracks} except the track that was selected. This action is useful for quickly listening to a single sequence in isoloation. \index{patterns column!double-click} \textsl{Double-clicking} on the pattern name will either create a new pattern or open up the corresponding pattern window. This feature saves having to move to the live grid. % Think about implementing this feature for real. At least partially. % % \index{patterns column!right-click} % \textsl{Right-clicking} on the pattern name or % the \textbf{M} button brings up the same % pattern editing menu as discussed in % \sectionref{subsubsec:patterns_pattern_filled}. % Recall that this context menu has the following entries: % \textbf{Edit...}, \textbf{Event Edit...}, \textbf{Cut}, \textbf{Copy}, % \textbf{Song}, \textbf{Disable Transpose}, and \textbf{MIDI Bus}. \subsection{Song Editor / Song Roll} \label{subsec:song_editor_song_roll} The "Song Roll" section of the arrangement panel is where patterns or subsections are inserted, deleted, shrunk, lengthened, or moved. Actions can be done via the mouse or keyboard. \textbf{Warning}. There is a horizontal and a vertical scrollbar for the song roll. However, some patterns may hide the scrollbars or obscure them. For example, the Qt "kvantum" themes replace the scrollbar with just its "thumb", and show it in the roll, but only when the mouse is near the bottom or right borders. Weird! \subsubsection{Song Editor / Song Roll / Layout} \label{subsubsec:song_editor_song_roll_layout} The song/piano roll provides one line (track) per pattern. It provides another way (besides the live grid) to control and lay out patterns. Patterns can be set up with multiple triggers and can be brought up for editing. Here are features to note in the annotated piano roll area: \begin{enumber} \item \textbf{Note Events}. \index{song editor!note events} Note in the pattern are shown as thin horizontal lines. They can be edited in the pattern editor's piano roll, and in the event editor. \item \textbf{Tempo Events}. \index{song editor!tempo events} Tempo events in the pattern are shown as very small squares. They can be edited in the pattern editor's data panel, and in the event editor. \item \textbf{Pattern Access}. Double-click-edit (if enabled in the 'rc' file) on a track representing an existing pattern will bring up an external pattern editor windows. Double-clicking on an empty track will create a new pattern and bring up its pattern editor. This new functionality is merely for convenience. \item \textbf{Trigger Creation}. By click-dragging the mouse on a track, in paint mode, a series of triggers can be created; they indicate where the track will be unmuted and playing. See below for more information about triggers. \item \textbf{Selection}. Clicking inside a trigger selects it. Selection is denoted by an orange color in the trigger. A pattern subsection selection can be moved clicking and holding the mouse near the middle of the selection first. A pattern subsection selection can be deleted by keystrokes. At present, selection of more than one trigger at a time (via drawing a rectangle on them) is \textsl{not supported}. \item \textbf{De-selection}. \index{song editor!section deselection} \textsl{Left-clicking} or \textsl{right-clicking} in an empty area of the song roll will deselect the selection. \item \textbf{Selection Movement}. \index{song editor!selection movement} If one grabs (\textsl{left-click}) inside the pattern or pattern subsection, that item can be moved horizontally via the left and right arrow keys, as long as there is room. \item \textbf{Section Length ("handle")}. \index{song editor!handle} \index{song editor!section length} The small squares in two corners of the patterns are the section "handles". By grabbing a handle with a \textsl{left-click}, the handle can be moved horizontally to either lengthen or shorten the pattern to the nearest snap position, if there is room to move in the desired direction. \item \textbf{Pattern Subsectioning}. \index{song editor!split pattern} \index{song editor!middle click} \index{pattern subsection} A \textsl{middle-click} (or \textsl{ctrl-left-click}) inside a pattern inserts a selection position marker in it, breaking the pattern into two equal pieces. This division can be done over and over. There are also options for splitting at the nearest snap point. See below. \item \textbf{Expansion}. \index{song editor!section expansion} Originally, all the long patterns of this sample song were continuous. But, by setting the L and R markers, and using the \textbf{Expand} button, we opened up some silent space in the song, just to be able to show it off. \end{enumber} The \textsl{Seq24} help files refer to work in the song editor as the "Performance Editor" or "Performance Mode". Adding a pattern in this window is a bit like adding a note in the pattern editor. One clicks, holds, and drags the mouse to insert a copy or copies of the pattern associated with the row in which one is dragging. The longer one drags, the more copies of the pattern that are inserted. \index{song editor!right-click-hold} \index{song editor!draw} \index{paint mode} \textsl{Right-click} on the arrangement panel (roll) to enter paint mode, and hold the button. Paint mode does not work while the sequence is playing. Another way to turn on painting is to make sure that the performance editor piano roll has the keyboard focus by \textsl{left-clicking} in it, then press the \texttt{p} or \texttt{i} key to enter the paint/insert mode, and \texttt{x} (or \texttt{Esc} if not playing) to escape it. See \sectionref{subsubsec:song_editor_song_roll_keystrokes}. \index{zoom} \index{song editor!horizontal zoom} The song editor supports horizontal zoom in the piano roll. This feature is accessible via the "magnifying glass" buttons, and also accessible via the keystrokes \textbf{z}, \textbf{0}, and \textbf{Z}. The zoom feature also modifies the time-line. \index{zoom} \index{song editor!vertical zoom} The song editor supports limited vertical zoom in the piano roll. This feature is accessible via the \textbf{-}, \textbf{0}, and \textbf{+} buttons, and also accessible via the keystrokes \textbf{v}, \textbf{0}, and \textbf{V}. \index{song editor!left-click-right-hold} \index{song editor!insert} A \textsl{left-click} with a simultaneous \textsl{right-click-hold} inserts one copy of the pattern. The inserted pattern shows up as a box with a tiny representation of the notes visible inside. Some patterns can be less than a measure in length, resulting in a tiny box. \index{song editor!right-left-hold-drag} \index{song editor!multiple insert} To keep adding more copies of the pattern, continue to hold both buttons and drag the mouse rightward. \index{song editor!middle-click} \textsl{Middle-click} (or \textsl{ctrl-left-click}) on a trigger in a pattern row to splits the trigger into two triggers. \index{pattern!split} \index{song editor!pattern subsection} This splits the pattern into two equal \textsl{pattern subsections}. Each \textsl{middle-click} on the pattern adds a new selection position, halving the size of the subsections as more pattern subsections are added. The \texttt{allow\_snap\_split} option in the 'rc' file allows the split to be made at the nearest snap point instead of in the middle. \index{song editor!left-click} \index{song editor!selection} When a pattern or a pattern subsection is \textsl{left-clicked} in the piano roll, it is marked with an orange background. It can then be moved horizontally if there is room, or be deleted or copied for later pasting. \index{song editor!right left click} \index{song editor!deletion} When a \textsl{right-left-click} action is done in this gray area, the result is to \textsl{delete} that pattern section or subsection. \index{keys!delete} One can also hit the \texttt{Delete} key. \index{song editor!double-click} \textsl{Double-click} on one of the trigger bars will either create a new pattern or open up the corresponding pattern window. This feature saves having to move to the live grid. \subsubsection{Song Editor / Song Roll / Keystrokes} \label{subsubsec:song_editor_song_roll_keystrokes} There are a number of useful keystrokes in the song roll that can be used once is has focus, by clicking in it. \begin{itemize} \item Enter "paint" mode. The \texttt{p} or \texttt{i} keys enter paint mode, where additional triggers can be added by click-dragging on a pattern row. The \texttt{x} or \texttt{Esc} keys leave this mode. The "finger" button and the mouse cursor both indicate the status. \item Start/Pause button functionality. When the song roll has keyboard focus, the \texttt{Space} key starts and stops playback, rewinding to the beginning when stopped. The \texttt{.} (period) key starts and pauses playback, without rewinding. This functionality is similar to that of the main window, but these keys are not reconfigurable in the song roll. \item Undo / Redo / Cut / Copy / Paste of a selected section. Provided by buttons and by these keystrokes: \begin{itemize} \item \texttt{Ctrl-Z}. Undo. \item \texttt{Shift-Ctrl-Z}. Redo. \item \texttt{Ctrl-X}. Cut. Removes the selection. \index{keys!backspace} \index{keys!delete} \index{keys!ctrl-x} Can also be done with the \texttt{Delete} and \texttt{Backspace} keys. The deletion can be undone. \item \texttt{Ctrl-C}. Copy. \index{keys!ctrl-c} \index{keys!copy} Copies the trigger for later usage. \item \texttt{Ctrl-V}. Paste. \index{keys!ctrl-v} \index{keys!paste} Puts the roll into paste mode. When inserted, each insert goes immediately after the current item or the previous insertion. The same can be done for whole patterns. \end{itemize} \item Horizontal (Time) Zoom. These keystrokes work similarly to the pattern editor's piano roll. Provided by buttons and by these keystrokes: \index{keys!shift-z} \texttt{Z}. Zoom in horizontally (i.e. in time). \index{keys!z} \texttt{z}. Zoom out horizontally. \index{keys!0} \texttt{0}. Reset zoom both horizontally and vertically. \item Vertical (Time) Zoom. These keystrokes work similarly to the pattern editor's piano roll. However, there are only three levels of vertical zoom: half-size, normal, and double-size. \index{keys!shift-V} \texttt{V}. Zoom in vertically to get a better view of the patterns and a large grab handle. \index{keys!v} \texttt{v}. Zoom out vertically to see more tracks. \texttt{0}. Reset zoom both horizontally and vertically. \item Scrolling Horizontally/Vertically. \index{keys!arrows} The arrow keys will move the piano row up, down, left, and right. \index{keys!hjkl} In addition, the "vi" keys \texttt{h}, \texttt{j}, \texttt{k}, and \texttt{l} will act like the arrow keys. The mouse scroll wheel can also be used to move the panes around. For implementation reasons, the scroll wheel is active \textsl{only} in the piano roll. \item Jumping to the Beginning/End. \index{keys!Home} \index{keys!End} The \texttt{Home} and \texttt{End} keys act as normal, jumping to the beginning or end of the song roll The \texttt{Ctrl-Home} key acts just like \texttt{Home}. The \texttt{Ctrl-End} key is like \texttt{End}, but it leaves the end of the song in view in the middle of the song roll. \item Paging. One can page up and down vertically in the arrangement panel using the \index{keys!page-up} \texttt{Page Up} and \index{keys!page-down} \texttt{Page Down} keys. One can page left and right horizontally in the arrangement panel using the \index{keys!up-arrow} \texttt{Up-Arrow} and \index{keys!down-arrow} \texttt{Down-Arrow} keys. \end{itemize} \subsection{Song Editor / Bottom Panel} \label{subsec:song_editor_bottom} The bottom panel is simple, consisting of a stock horizontal scroll bar. \begin{comment} ...and a small button, called the \textbf{Grow} button, labelled with a "\textbf{$>$}". \index{grow button} \index{song editor!grow} The \textbf{Grow} button adds to the number of measures that exist in the song editor. The visual effect is very subtle, resulting only in a small change in the thumb of the horizontal scroll-bar, unless one is at the right end of the piano roll. Then, one can see the added measures. Usually about 128 at a time are added, but this depends on the value of PPQN in force. \end{comment} %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: doc/latex/tex/windows.tex ================================================ %------------------------------------------------------------------------------- % seq66 windows %------------------------------------------------------------------------------- % % \file seq66 windows.tex % \library Documents % \author Chris Ahlstrom % \date 2021-02-13 % \update 2025-06-10 % \version $Revision$ % \license $XPC_GPL_LICENSE$ % % Provides a discussion of starting up Seq66 in Windows. % %------------------------------------------------------------------------------- \section{Seq66 In Windows} \label{sec:windows} This section discusses installing and using the basics of \textsl{Seq66} in \textsl{Microsoft Windows}. Additional trouble-shooting information can be found in the installed file data directory. \begin{verbatim} C:/Program Files/Seq66/data/readme.windows \end{verbatim} Another useful reference is \textsl{Working with MIDI on Windows} \cite{windowsmidi}. \subsection{Windows / Seq66 Installation} \label{subsec:windows_seq66_installation} For \textsl{Seq66}, the installer executable is now part of each "major" release (currently 0.99.6). There are tricks to getting the installer (e.g. \texttt{seq66\_setup\_0.99.6-x64.exe}) properly. One can't just right-click and save the link. Instead, click on the link. Then look for a "Download" button, and click that. Installation itself is straightforward. Run the installer (e.g. \texttt{seq66\_setup\_0.99.6-x64.exe}). Accept the license terms (\textsl{GNU GPL 2 or 3}), make sure all components are selected, accept the default install directory, and click through until the installation is done. (Note that there might also be a \texttt{qpseq66-release-package-0.99.6.7z} portable installer than can be downloaded. If not, request one! Just extract that file where desired. Note that the \textsl{Windows} version is a 64-bit application. We do not currently build a 32-bit version, due to our lack of a 32-bit Windows platform.) \textsl{Seq66} is installed at \texttt{C:/Program Files/Seq66/qpseq66.exe}. One might want to create a desktop shortcut or a shortcut button on the \textsl{Quick Launch} bar; one can also go to the "Start" menu and search for "qpseq66.exe" or "Seq66".) Before we discuss usage, we have to talk a bit about a Windows issue. \subsection{Windows / MIDI Mapper} \label{subsec:windows_midi_mapper} \index{windows!midi mapper} Windows has a \textbf{MIDI Mapper}. This is a subsystem that redirects MIDI to a specific device. Supposedly it was removed in Windows 8 and beyond, but it still exists in Windows 10 and 11. However, the alleged Control Panel applet for the MIDI Mapper no longer exists. In the Windows XP era, MIDI was exposed in the OS, and it had a setup applet in the \textsl{Sound and Multimedia} \textsl{Control Panel}. Users could select a default MIDI Out device from a list of all installed MIDI devices. All programs outputting a MIDI data stream (and no selected a specific MIDI Out device) played their stream to that device. The MIDI Mapper is not real device but a "pipe"; it receives MIDI on input and sends it to an user-configured MIDI Out device. It was bundled with Windows, installed as MIDI Out device 0, preconfigured to use the first available device, the built-in \textsl{Microsoft GS Wavetable Synth}. It is a low quality software wave synth, installed as MIDI Out device 1. So on Windows, programmers had 2 well-known devices. This works for MIDI software without a configurable output device; they use MIDI Out 0, and the wavetable synth generates the sound. This chain worked well: default users had working MIDI synthesis out of the box. Then Microsoft removed it in Windows 7. But it resurfaced in Windows 10, but without the MIDI Control Panel applet. It gets more interesting. Assume we have a LaunchPad Mini and a Korg nanoKEY2 plugged in. Internally, Windows see the following devices (based on a trek in the debugger): \begin{itemize} \item \textbf{0}. MIDI Mapper. \item \textbf{3}. GS Wavetable Synth. \item \textbf{4}. NanoKEY2. \item \textbf{5}. LaunchPad Mini. \end{itemize} The \textsl{Seq66} "portmidi" implementation opens them in the same order, but numbers them sequentially: \begin{itemize} \item \textbf{0}. MIDI Mapper. \item \textbf{1}. GS Wavetable Synth. \item \textbf{2}. NanoKEY2. \item \textbf{3}. LaunchPad Mini. \end{itemize} The MIDI Mapper, if present, grabs the wavetable synth, which prevents \textsl{Seq66} from opening it. So the \textsl{Seq66} code detects that the MIDI Mapper (port 0) exists, and when opening the wavetable synth (port 1) fails, it marks that port as "unavailable" and "locked", in order to ghost it in the user-interface and to ignore it for error reporting (otherwise every startup would bring up an annoying and meaningless error message). As an aside, for MIDI input, Windows does no mapping, and there are only two input devices (given the two plugged in above). \begin{itemize} \item \textbf{0}. NanoKEY2. \item \textbf{1}. LaunchPad Mini. \end{itemize} There are applications that try to get around this issue, such as the \textsl{CoolSoft MIDIMapper} and \textsl{VirtualMIDISynth} \cite{midimapper} or \textsl{MIDI OX} \cite{midiox}. See the \texttt{readme.windows} file for more information. \subsection{Windows / Virtual Ports} \label{subsec:windows_virtual_ports} Virtual ports are software MIDI ports that can be created for input and output, as noted in \sectionref{subsection:edit_preferences_midi_clock} and \sectionref{subsection:edit_preferences_midi_input}. To provide virtual ports in \textsl{Windows}, special software needs to be installed. One useful applications is \textsl{LoopMIDI} (see \cite{loopmidi}). \begin{quote} Virtual loopback MIDI cable for Windows 7 up to Windows 10, 32 and 64 bit. This software can be used to create virtual loopback MIDI-ports to interconnect applications on Windows that want to open hardware-MIDI-ports for communication. \end{quote} With this software, one can create a number of extra MIDI ports. The main difference from \textsl{Linux} here is that the \texttt{--manual-port} option is \textsl{not} used. The virtual ports are treated as real ports in \textsl{Windows}. \subsection{Windows / Seq66 Startup} \label{subsec:windows_seq66_startup} Now run \texttt{C:/Program Files/Seq66/qpseq66.exe}. It might generate an error, though we believe we have suppressed this error: \begin{figure}[H] \centering \includegraphics[scale=0.65]{windows/windows-first-startup.png} \caption{Seq66 First Startup in Windows} \label{fig:windows_first_startup} \end{figure} This error occurs on \textsl{Windows 10} because the 0th port, the \textsl{Microsoft MIDI Mapper}, grabs access to the 1st port, the \textsl{Microsoft GS Wavetable Synth}. Thus, the wavetable synth is "unavailable" and "locked". This message will appear at startup for "unavailable" ports, but not for a "locked" port; one can also just click \textbf{OK}, but startup messages can also be suppressed using an option in \textbf{Edit / Preferences / Display}. Run the \textsl{qpseq66.exe} shortcut and load a tune from the directory \texttt{C:/Program Files/Seq66/data/midi}. Navigate in the file explorer to \texttt{C:/Users/your\_user\_name/AppData/Local/seq66} and open \texttt{qpseq66.rc}, the main configuration file for \textsl{Seq66}. It will look like this: \begin{figure}[H] \centering \includegraphics[scale=0.75]{windows/rc-file-post-first-startup.png} \caption{'rc' File After Exiting First Startup} \label{fig:windows_rc_file_post_first_startup} \end{figure} The \texttt{[midi-input]} section indicates there are no input ports in Windows when no MIDI device is connected to the computer. The \texttt{[midi-clock]} section indicates there are two output ports, and that port 1 is disabled. So one should be able to play a tune to the MIDI Mapper and hear it, if output is directed to port 0. Go to \textbf{Edit / Preferences / MIDI Clock}. It will look like this: \begin{figure}[H] \centering \includegraphics[scale=0.85]{windows/edit-preferences.png} \caption{MIDI Output Settings at Second Startup} \label{fig:windows_output_settings_second_startup} \end{figure} Next select \textbf{File / Open} and select this sample tune: \begin{verbatim} C:/Program Files/Seq66/data/midi/b4uacuse-gm-patchless.midi \end{verbatim} (In the following figure, the " (x86)" is due to an early incorrect installation; \textsl{Seq66} on Windows is a 64-bit application only at this time.) \begin{figure}[H] \centering \includegraphics[scale=0.65]{windows/open-installed-midi-file.png} \caption{MIDI File Selection} \label{fig:windows_open_installed_midi_file} \end{figure} After clicking \textbf{Open}, the following set of patterns is shown. Note the two highlighted areas, "Output Selector" and "Song/Live Button". \begin{figure}[H] \centering \includegraphics[scale=0.65]{windows/open-midi-file.png} \caption{Opened MIDI File} \label{fig:windows_open_midi_file} \end{figure} At the top, select port 0 (the MIDI Mapper) from the "Output Selector". This \textsl{modifies} the MIDI file so that all MIDI output will go to port 0. At the bottom, click the "Song/Live Button" until it reads "Song". This will access track layouts that turn on all of the patterns. These layouts can be seen by selecting the \textbf{Song} tab. Now click the play button (green triangle). The song should play properly. (On our test Windows 10 setup in a virtual machine, playback is ragged, but fine on a normal Windows installation on hardware.) Overall, the \textsl{Windows} version and the \textsl{Linux} version work essentially the same. The \textsl{Linux} version can use the \textsl{ALSA} and \textsl{JACK} MIDI engines, while the \textsl{Windows} version uses a refactored \textsl{PortMidi} engine that is part of the \textsl{Seq66} project. The \textsl{PortMidi} engine should also work with \textsl{MacOSX}, but, since we don't have a Mac, we haven't been able to build and test on that platform. Again, for trouble-shooting, also see the installed text file: \begin{verbatim} C:/Program Files/Seq66/data/readme.windows \end{verbatim} \subsection{Windows / Keyboard Issues} \label{subsec:windows_keyboard_issues} The keystroke processing of \textsl{Seq66} is based on looking up \textsl{Qt} key scan codes and then finding the matching key modifiers (e.g. Shift or Ctrl) and native virtual key numbers. When a match is found, a \textsl{Seq66}-specific ordinal value ranging from 0 to 255 is found, and that is used to look up the command or function associated with that keystroke. The native virtual key numbers are how the operating system encodes the various hardware keys on the keyboard. These numbers vary between \textsl{Linux}, \textsl{Windows}, and \textsl{Mac}. All these numbers are in a table, and there is (currently) a table for \textsl{Linux} and a table for \textsl{Windows}. The table for \textsl{Windows} was recently completed, but it is likely that there are some native virtual key numbers that were not assigned correctly. Report any issues. There is also a table that corrects for \index{keys!AZERTY} the French AZERTY layout. It is a good bet that this table also has errors. Report any issues. %------------------------------------------------------------------------------- % vim: ts=3 sw=3 et ft=tex %------------------------------------------------------------------------------- ================================================ FILE: include/cli/seq66-config.h ================================================ #ifndef _INCLUDE_SEQ___CONFIG_H #define _INCLUDE_SEQ___CONFIG_H 1 /* include/seq66-config.h. Generated automatically at end of configure. */ /* include/config.h. Generated from config.h.in by configure. */ /* include/config.h.in. Generated from configure.ac by autoheader. */ #ifndef SEQ66_VERSION_DATE_SHORT #define SEQ66_VERSION_DATE_SHORT "2025-07-28" #endif #ifndef SEQ66_VERSION #define SEQ66_VERSION "0.99.22" #endif /* "Distro of build" */ #ifndef SEQ66_APP_BUILD_ISSUE #define SEQ66_APP_BUILD_ISSUE "Ubuntu 20.04.4 LTS" #endif /* "OS/kernel where build was done" */ #ifndef SEQ66_APP_BUILD_OS #define SEQ66_APP_BUILD_OS "'Linux 5.13.0-30-generic x86_64'" #endif /* Indicate the CLI version */ #ifndef SEQ66_APP_CLI #define SEQ66_APP_CLI 1 #endif /* Name of the MIDI engine */ #ifndef SEQ66_APP_ENGINE #define SEQ66_APP_ENGINE "rtmidi" #endif /* Name of this application */ #ifndef SEQ66_APP_NAME #define SEQ66_APP_NAME "seq66cli" #endif /* Name of the UI */ #ifndef SEQ66_APP_TYPE #define SEQ66_APP_TYPE "cli" #endif /* "Name to display as client/port" */ #ifndef SEQ66_CLIENT_NAME #define SEQ66_CLIENT_NAME "seq66" #endif /* Configuration sub-directory */ #ifndef SEQ66_CONFIG_DIR_NAME #define SEQ66_CONFIG_DIR_NAME "seq66" #endif /* Configuration file name */ #ifndef SEQ66_CONFIG_NAME #define SEQ66_CONFIG_NAME "seq66cli" #endif /* Define COVFLAGS=-fprofile-arcs -ftest-coverage if coverage support is wanted. */ #ifndef SEQ66_COVFLAGS #define SEQ66_COVFLAGS #endif /* Define DBGFLAGS=-g -O0 -DDEBUG -fno-inline if debug support is wanted. */ #ifndef SEQ66_DBGFLAGS #define SEQ66_DBGFLAGS #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_CTYPE_H #define SEQ66_HAVE_CTYPE_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_DLFCN_H #define SEQ66_HAVE_DLFCN_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_ERRNO_H #define SEQ66_HAVE_ERRNO_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_FCNTL_H #define SEQ66_HAVE_FCNTL_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_GETOPT_H #define SEQ66_HAVE_GETOPT_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_INTTYPES_H #define SEQ66_HAVE_INTTYPES_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_JACK_JACK_H #define SEQ66_HAVE_JACK_JACK_H 1 #endif /* Define to 1 if you have the `asound' library (-lasound). */ #ifndef SEQ66_HAVE_LIBASOUND #define SEQ66_HAVE_LIBASOUND 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_LIMITS_H #define SEQ66_HAVE_LIMITS_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_MEMORY_H #define SEQ66_HAVE_MEMORY_H 1 #endif /* Define if you have POSIX threads libraries and header files. */ #ifndef SEQ66_HAVE_PTHREAD #define SEQ66_HAVE_PTHREAD 1 #endif /* Have PTHREAD_PRIO_INHERIT. */ #ifndef SEQ66_HAVE_PTHREAD_PRIO_INHERIT #define SEQ66_HAVE_PTHREAD_PRIO_INHERIT 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_STDARG_H #define SEQ66_HAVE_STDARG_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_STDDEF_H #define SEQ66_HAVE_STDDEF_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_STDINT_H #define SEQ66_HAVE_STDINT_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_STDIO_H #define SEQ66_HAVE_STDIO_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_STDLIB_H #define SEQ66_HAVE_STDLIB_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_STRINGS_H #define SEQ66_HAVE_STRINGS_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_STRING_H #define SEQ66_HAVE_STRING_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_SYSLOG_H #define SEQ66_HAVE_SYSLOG_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_SYS_STAT_H #define SEQ66_HAVE_SYS_STAT_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_SYS_SYSCTL_H #define SEQ66_HAVE_SYS_SYSCTL_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_SYS_TIME_H #define SEQ66_HAVE_SYS_TIME_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_SYS_TYPES_H #define SEQ66_HAVE_SYS_TYPES_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_TIME_H #define SEQ66_HAVE_TIME_H 1 #endif /* Define to 1 if you have the header file. */ #ifndef SEQ66_HAVE_UNISTD_H #define SEQ66_HAVE_UNISTD_H 1 #endif /* Canonical icon name for Freedesktop */ #ifndef SEQ66_ICON_NAME #define SEQ66_ICON_NAME "qseq66" #endif /* Enable JACK version string */ #ifndef SEQ66_JACK_GET_VERSION_STRING #define SEQ66_JACK_GET_VERSION_STRING 1 #endif /* Define to enable JACK metadata */ #ifndef SEQ66_JACK_METADATA #define SEQ66_JACK_METADATA 1 #endif /* Define to enable JACK session */ #ifndef SEQ66_JACK_SESSION #define SEQ66_JACK_SESSION 1 #endif /* Define to enable JACK driver */ #ifndef SEQ66_JACK_SUPPORT #define SEQ66_JACK_SUPPORT 1 #endif /* Define if LIBLO library is available */ #ifndef SEQ66_LIBLO_SUPPORT #define SEQ66_LIBLO_SUPPORT 1 #endif /* Define to the sub-directory where libtool stores uninstalled libraries. */ #ifndef SEQ66_LT_OBJDIR #define SEQ66_LT_OBJDIR ".libs/" #endif /* Define to enable JACK port refresh */ /* #undef MIDI_PORT_REFRESH */ /* Define to enable NSM */ #ifndef SEQ66_NSM_SUPPORT #define SEQ66_NSM_SUPPORT 1 #endif /* Name of package */ #ifndef SEQ66_PACKAGE #define SEQ66_PACKAGE "seq66" #endif /* Define to the address where bug reports for this package should be sent. */ #ifndef SEQ66_PACKAGE_BUGREPORT #define SEQ66_PACKAGE_BUGREPORT "ahlstromcj@gmail.com" #endif /* Define to the full name of this package. */ #ifndef SEQ66_PACKAGE_NAME #define SEQ66_PACKAGE_NAME "Seq66" #endif /* Define to the full name and version of this package. */ #ifndef SEQ66_PACKAGE_STRING #define SEQ66_PACKAGE_STRING "Seq66 0.99.17" #endif /* Define to the one symbol short name of this package. */ #ifndef SEQ66_PACKAGE_TARNAME #define SEQ66_PACKAGE_TARNAME "seq66" #endif /* Define to the home page for this package. */ #ifndef SEQ66_PACKAGE_URL #define SEQ66_PACKAGE_URL "" #endif /* Define to the version of this package. */ #ifndef SEQ66_PACKAGE_VERSION #define SEQ66_PACKAGE_VERSION "0.99.17" #endif /* Indicates if portmidi is enabled */ /* #undef PORTMIDI_SUPPORT */ /* Define PROFLAGS=-pg (gprof) or -p (prof) if profile support is wanted. */ #ifndef SEQ66_PROFLAGS #define SEQ66_PROFLAGS #endif /* Define to necessary symbol if this constant uses a non-standard name on your system. */ /* #undef PTHREAD_CREATE_JOINABLE */ /* Indicates that rtmidi is enabled */ #ifndef SEQ66_RTMIDI_SUPPORT #define SEQ66_RTMIDI_SUPPORT 1 #endif /* Define to 1 if you have the ANSI C header files. */ #ifndef SEQ66_STDC_HEADERS #define SEQ66_STDC_HEADERS 1 #endif /* Version number of package */ #ifndef SEQ66_VERSION #define SEQ66_VERSION "0.99.17" #endif /* Define to 1 if the X Window System is missing or not being used. */ /* #undef X_DISPLAY_MISSING */ /* gnu source */ #ifndef SEQ66__GNU_SOURCE #define SEQ66__GNU_SOURCE 1 #endif /* Define to empty if `const' does not conform to ANSI C. */ /* #undef const */ #if defined SEQ66_PORTMIDI_SUPPORT #/**/undef/**/ SEQ66_RTMIDI_SUPPORT #endif #if defined SEQ66_WINDOWS_SUPPORT #/**/undef/**/ SEQ66_RTMIDI_SUPPORT #endif /* once: _INCLUDE_SEQ___CONFIG_H */ #endif ================================================ FILE: include/config.h.in ================================================ /* include/config.h.in. Generated from configure.ac by autoheader. */ #define VERSION_DATE_SHORT "2026-05-01" #define API_VERSION "0.99" #define VERSION "0.99.24" /* Seq66 API version */ #undef API_VERSION /* Operating system of build */ #undef APP_BUILD_ISSUE /* OS/kernel of build */ #undef APP_BUILD_OS /* Indicate the CLI version */ #undef APP_CLI /* Name of the MIDI engine */ #undef APP_ENGINE /* Name of the CLI application */ #undef APP_NAME /* Name of the GUI/UI */ #undef APP_TYPE /* Define if clang is active */ #undef CLANG_SUPPORT /* Client/port base name */ #undef CLIENT_NAME /* Configuration sub-directory */ #undef CONFIG_DIR_NAME /* Configuration base file name */ #undef CONFIG_NAME /* Define COVFLAGS=-fprofile-arcs -ftest-coverage if coverage support is wanted. */ #undef COVFLAGS /* Define DBGFLAGS=-g -O0 -DDEBUG -fno-inline if debug support is wanted. */ #undef DBGFLAGS /* Define to 1 if you have the header file. */ #undef HAVE_CTYPE_H /* Define to 1 if you have the header file. */ #undef HAVE_DLFCN_H /* Define to 1 if you have the header file. */ #undef HAVE_ERRNO_H /* Define to 1 if you have the header file. */ #undef HAVE_FCNTL_H /* Define to 1 if you have the header file. */ #undef HAVE_GETOPT_H /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H /* Define to 1 if you have the header file. */ #undef HAVE_JACK_JACK_H /* Define to 1 if you have the 'asound' library (-lasound). */ #undef HAVE_LIBASOUND /* Define to 1 if you have the 'atopology' library (-latopology). */ #undef HAVE_LIBATOPOLOGY /* Define to 1 if you have the header file. */ #undef HAVE_LIMITS_H /* Define if you have POSIX threads libraries and header files. */ #undef HAVE_PTHREAD /* Have PTHREAD_PRIO_INHERIT. */ #undef HAVE_PTHREAD_PRIO_INHERIT /* Define to 1 if you have the header file. */ #undef HAVE_STDARG_H /* Define to 1 if you have the header file. */ #undef HAVE_STDDEF_H /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H /* Define to 1 if you have the header file. */ #undef HAVE_STDIO_H /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H /* Define to 1 if you have the header file. */ #undef HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H /* Define to 1 if you have the header file. */ #undef HAVE_SYSLOG_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_SYSCTL_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TIME_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H /* Define to 1 if you have the header file. */ #undef HAVE_TIME_H /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H /* Icon name for Freedesktop */ #undef ICON_NAME /* Enable JACK version string */ #undef JACK_GET_VERSION_STRING /* Define to enable JACK metadata */ #undef JACK_METADATA /* Define to enable JACK session */ #undef JACK_SESSION /* Define to enable JACK driver */ #undef JACK_SUPPORT /* Define if LIBLO library is available */ #undef LIBLO_SUPPORT /* Define to the sub-directory where libtool stores uninstalled libraries. */ #undef LT_OBJDIR /* Define to enable JACK port refresh */ #undef MIDI_PORT_REFRESH /* Define if NSM support is available */ #undef NSM_SUPPORT /* Name of package */ #undef PACKAGE /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT /* Define to the full name of this package. */ #undef PACKAGE_NAME /* Define to the full name and version of this package. */ #undef PACKAGE_STRING /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME /* Define to the home page for this package. */ #undef PACKAGE_URL /* Define to the version of this package. */ #undef PACKAGE_VERSION /* Indicates if portmidi is enabled */ #undef PORTMIDI_SUPPORT /* Define PROFLAGS=-pg (gprof) or -p (prof) if profile support is wanted. */ #undef PROFLAGS /* Define to necessary symbol if this constant uses a non-standard name on your system. */ #undef PTHREAD_CREATE_JOINABLE /* Indicates that rtmidi is enabled */ #undef RTMIDI_SUPPORT /* Define to 1 if all of the C89 standard headers exist (not just the ones required in a freestanding environment). This macro is provided for backward compatibility; new code need not use it. */ #undef STDC_HEADERS /* Version number of package */ #undef VERSION /* Define to 1 if the X Window System is missing or not being used. */ #undef X_DISPLAY_MISSING /* gnu source */ #undef _GNU_SOURCE /* Define to empty if 'const' does not conform to ANSI C. */ #undef const #if defined SEQ66_PORTMIDI_SUPPORT #/**/undef/**/ SEQ66_RTMIDI_SUPPORT #endif #if defined SEQ66_WINDOWS_SUPPORT #/**/undef/**/ SEQ66_RTMIDI_SUPPORT #endif ================================================ FILE: include/qt/portmidi/seq66-config.h ================================================ #if defined SEQ66_PORTMIDI_SUPPORT #if ! defined SEQ66_QT_MIDILIB_CONFIG #define SEQ66_QT_MIDILIB_CONFIG #undef SEQ66_RTMIDI_SUPPORT /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file seq66-config.h for Qt/PortMidi * * This module provides platform/build-specific configuration that is not * modifiable via a "configure" operation. It is meant for the hardwired * qmake build of the PortMidi Linux and Windows versions. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-10 * \updates 2026-05-01 * \license GNU GPLv2 or above * * Qt Portmidi Linux and Windows versions, hardwired for use with * qtcreator/qmake. That build uses PortMidi in order to support both Linux * and Windows. Hence no support for JACK or NSM, for example. * However, it still defines some things that are available on GNU/Linux/MingW * systems. * * Note that there is a native (i.e. automake) Linux Qt build that uses * RtMidi, so that JACK is supported. * * Note: This header file is NOT auto-generated for the portmidi build. * Therefore, the date and version information below must be edited by hand * when needed. * * SEQ66_VERSION_DATE_SHORT * SEQ66_PACKAGE_STRING * SEQ66_PACKAGE_VERSION * SEQ66_VERSION * * Updates to configure.ac might need to be incorporated into this file! */ #ifdef _INCLUDE_SEQ___CONFIG_H #error Automake-generated include file seq66-config.h already included. #endif #if ! defined SEQ66_VERSION_DATE_SHORT #define SEQ66_VERSION_DATE_SHORT "2026-05-01" #endif #if ! defined SEQ66_API_VERSION #define SEQ66_API_VERSION "0.99" #endif #if ! defined SEQ66_VERSION #define SEQ66_VERSION "0.99.24" #endif #if ! defined SEQ66_GIT_VERSION #define SEQ66_GIT_VERSION SEQ66_VERSION #endif #if ! defined SEQ66_PACKAGE_VERSION #define SEQ66_PACKAGE_VERSION SEQ66_VERSION #endif /** * This macro helps us adapt our "ui" includes to freaking qmake's * conventions. We used "userinterface.ui.h", while qmake is stuck on * "ui_user_interface.h". * * It's almost enough to make you use Cmake. :-D */ #if ! defined SEQ66_QMAKE_RULES #define SEQ66_QMAKE_RULES #endif /* "Distro where build was done" */ #if ! defined SEQ66_APP_BUILD_OS #define SEQ66_APP_BUILD_OS "'qmake'" #endif #if ! defined SEQ66_APP_BUILD_ISSUE #define SEQ66_APP_BUILD_ISSUE "'Linux/Windows/Mac'" #endif /** * Names this version of application, plus the engine in use, and the type of * application. Useful in Help / Build Info. * "qp" means "Qmake/Qt PortMidi-based". */ #if ! defined SEQ66_APP_ENGINE #define SEQ66_APP_ENGINE "portmidi" #endif #if ! defined SEQ66_APP_NAME #define SEQ66_APP_NAME "qpseq66" #endif #if ! defined SEQ66_APP_TYPE #define SEQ66_APP_TYPE "qt5" #endif #undef SEQ66_APP_CLI /* * "The name to display as client/port". This name is the same no matter what * is the name of the executable. This is also the name of the default * configuration directory in the "home" area of the user. */ #if ! defined SEQ66_CLIENT_NAME #define SEQ66_CLIENT_NAME "seq66" #endif /* * Configuration sub-directory */ #ifndef SEQ66_CONFIG_DIR_NAME #define SEQ66_CONFIG_DIR_NAME "seq66" #endif /* * The LIBLO library is unavailable on Windows. Don't know about Mac! */ #undef SEQ66_LIBLO_SUPPORT /* * Names the configuration file for this version of application. The "q" * stands for Qt, and the "p" stands for "portmidi". */ #if ! defined SEQ66_CONFIG_NAME #define SEQ66_CONFIG_NAME "qpseq66" #endif #if ! defined SEQ66_ICON_NAME #define SEQ66_ICON_NAME "qseq66" #endif /* * Define COVFLAGS=-fprofile-arcs -ftest-coverage if coverage support is * wanted. */ #undef SEQ66_COVFLAGS /* * Define DBGFLAGS=-ggdb -O0 -DDEBUG -fno-inline if debug support is wanted. */ #if ! defined SEQ66_DBGFLAGS #define SEQ66_DBGFLAGS -O3 -DDEBUG -D_DEBUG -fno-inline #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_CTYPE_H #define SEQ66_HAVE_CTYPE_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_DLFCN_H #define SEQ66_HAVE_DLFCN_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_ERRNO_H #define SEQ66_HAVE_ERRNO_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_FCNTL_H #define SEQ66_HAVE_FCNTL_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_GETOPT_H #define SEQ66_HAVE_GETOPT_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_INTTYPES_H #define SEQ66_HAVE_INTTYPES_H 1 #endif /* Define to 1 if you have the `asound' library (-lasound). */ #if ! defined SEQ66_HAVE_LIBASOUND #define SEQ66_HAVE_LIBASOUND 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_LIMITS_H #define SEQ66_HAVE_LIMITS_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_MEMORY_H #define SEQ66_HAVE_MEMORY_H 1 #endif /* Define if you have POSIX threads libraries and header files. */ #if ! defined SEQ66_HAVE_PTHREAD #define SEQ66_HAVE_PTHREAD 1 #endif /* Have PTHREAD_PRIO_INHERIT. */ #if ! defined SEQ66_HAVE_PTHREAD_PRIO_INHERIT #define SEQ66_HAVE_PTHREAD_PRIO_INHERIT 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STDARG_H #define SEQ66_HAVE_STDARG_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STDDEF_H #define SEQ66_HAVE_STDDEF_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STDINT_H #define SEQ66_HAVE_STDINT_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STDIO_H #define SEQ66_HAVE_STDIO_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STDLIB_H #define SEQ66_HAVE_STDLIB_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STRINGS_H #define SEQ66_HAVE_STRINGS_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STRING_H #define SEQ66_HAVE_STRING_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_SYSLOG_H #define SEQ66_HAVE_SYSLOG_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_SYS_STAT_H #define SEQ66_HAVE_SYS_STAT_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_SYS_SYSCTL_H #define SEQ66_HAVE_SYS_SYSCTL_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_SYS_TIME_H #define SEQ66_HAVE_SYS_TIME_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_SYS_TYPES_H #define SEQ66_HAVE_SYS_TYPES_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_TIME_H #define SEQ66_HAVE_TIME_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_UNISTD_H #define SEQ66_HAVE_UNISTD_H 1 #endif /* * Define to value 1 to enable JACK session. Purely experimental, probably * won't work. */ #undef SEQ66_JACK_SUPPORT #undef SEQ66_JACK_SESSION #undef SEQ66_JACK_GET_VERSION_STRING #undef SEQ66_JACK_METADATA #undef SEQ66_NSM_SUPPORT /* Define to the sub-directory where libtool stores uninstalled libraries. */ #if ! defined SEQ66_LT_OBJDIR #define SEQ66_LT_OBJDIR ".libs/" #endif /* * Define to enable multiple main windows. Not to be supported in Seq66. */ #undef SEQ66_MULTI_MAINWID /* Name of package */ #if ! defined SEQ66_PACKAGE #define SEQ66_PACKAGE "seq66" #endif /* Define to the address where bug reports for this package should be sent. */ #if ! defined SEQ66_PACKAGE_BUGREPORT #define SEQ66_PACKAGE_BUGREPORT "ahlstromcj@gmail.com" #endif /* Define to the full name of this package. */ #if ! defined SEQ66_PACKAGE_NAME #define SEQ66_PACKAGE_NAME "Seq66" #endif /* Define to the full name and version of this package. */ #if ! defined SEQ66_PACKAGE_STRING #define SEQ66_PACKAGE_STRING "Seq66 0.99.24" #endif /* Define to the one symbol short name of this package. */ #if ! defined SEQ66_PACKAGE_TARNAME #define SEQ66_PACKAGE_TARNAME "seq66" #endif /* Define to the home page for this package. */ #if ! defined SEQ66_PACKAGE_URL #define SEQ66_PACKAGE_URL "" #endif /* * Define PROFLAGS=-pg (gprof) or -p (prof) if profile support is wanted. */ #if ! defined SEQ66_PROFLAGS #define SEQ66_PROFLAGS #endif /* * Indicates that Qt5 is enabled. Currently not yet in configure.ac nor used in * any module, but.... */ #if ! defined SEQ66_QTMIDI_SUPPORT #define SEQ66_QTMIDI_SUPPORT 1 #endif #if ! defined SEQ66_STDC_HEADERS #define SEQ66_STDC_HEADERS 1 #endif #if ! defined SEQ66__GNU_SOURCE #define SEQ66__GNU_SOURCE 1 #endif #endif // SEQ66_QT_MIDILIB_CONFIG #endif // SEQ66_PORTMIDI_SUPPORT 1 /* * seq66-config.h for Qt/PortMidi * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: include/qt/rtmidi/seq66-config.h ================================================ #if defined SEQ66_RTMIDI_SUPPORT #if ! defined SEQ66_QT_MIDILIB_CONFIG #define SEQ66_QT_MIDILIB_CONFIG #undef SEQ66_PORTMIDI_SUPPORT /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file seq66-config.h for Qt/RtMidi * * This module provides platform/build-specific configuration that is not * modifiable via a "configure" operation. It is meant for those who do not * want to use automake to build the Linux/Qt version of seq66. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-05-31 * \updates 2026-05-01 * \license GNU GPLv2 or above * * Qt Rtmidi Linux and Windows versions, hardwired for use with * qtcreator/qmake instead of using GNU autotools. * * One motivation for creating a Qt build for this version of seq66 is that, * one a Debian Sid/Unstable laptop with gcc/g++ version 9 as the compiler, * we get this error when building for debug more (but not for release mode): * * /usr/bin/ld: * /home/.../seq66/seq_qt5/src/.libs/libseq_qt5.a(qloopbutton.o): * undefined reference to symbol * '_ZN8QPainter8drawTextERK6QRectFiRK7QStringPS0_@@Qt_5' * /usr/bin/ld: * /usr/lib/x86_64-linux-gnu/libQt5Gui.so: error adding symbols: DSO * missing from command line * * Why is that "Qt_5" namespace tacked onto the end of that symbol? * * Note: This header file is NOT auto-generated for the rtmidi build. * Therefore, the date and version information below must be edited by hand * when needed. * * SEQ66_VERSION_DATE_SHORT * SEQ66_PACKAGE_STRING * SEQ66_PACKAGE_VERSION * SEQ66_VERSION * * Updates to configure.ac might need to be incorporated into this file! */ #if defined _INCLUDE_SEQ___CONFIG_H #error Automake-generated include file seq66-config.h already included. #endif #if ! defined SEQ66_VERSION_DATE_SHORT #define SEQ66_VERSION_DATE_SHORT "2026-05-01" #endif #if ! defined SEQ66_API_VERSION #define SEQ66_API_VERSION "0.99" #endif #if ! defined SEQ66_VERSION #define SEQ66_VERSION "0.99.24" #endif #if ! defined SEQ66_GIT_VERSION #define SEQ66_GIT_VERSION SEQ66_VERSION #endif #if ! defined SEQ66_PACKAGE_VERSION #define SEQ66_PACKAGE_VERSION SEQ66_VERSION #endif /** * This macro helps us adapt our "ui" includes to freaking qmake's * conventions. We used "userinterface.ui.h", while qmake is stuck on * "ui_user_interface.h". * * It's almost enough to make you use Cmake. :-D */ #if ! defined SEQ66_QMAKE_RULES #define SEQ66_QMAKE_RULES #endif /* "Distro where build was done" */ #if ! defined SEQ66_APP_BUILD_OS #define SEQ66_APP_BUILD_OS "'qmake'" #endif #if ! defined SEQ66_APP_BUILD_ISSUE #define SEQ66_APP_BUILD_ISSUE "'Linux'" #endif /** * Names this version of application, plus the engine in use, and the type of * application. Useful in Help / Build Info. * "qr" means "Qmake/Qt PortMidi-based". */ #if ! defined SEQ66_APP_ENGINE #define SEQ66_APP_ENGINE "rtmidi" #endif #if ! defined SEQ66_APP_NAME #define SEQ66_APP_NAME "qseq66" #endif #if ! defined SEQ66_APP_TYPE #define SEQ66_APP_TYPE "qt5" #endif #undef SEQ66_APP_CLI /* * "The name to display as client/port". This name is the same no matter what * is the name of the executable. This is also the name of the default * configuration directory in the "home" area of the user. */ #if ! defined SEQ66_CLIENT_NAME #define SEQ66_CLIENT_NAME "seq66" #endif /* * Configuration sub-directory */ #ifndef SEQ66_CONFIG_DIR_NAME #define SEQ66_CONFIG_DIR_NAME "seq66" #endif /* * Define if LIBLO library is available. If you get an error, either undefine * this value or install the liblo-dev package. */ #if ! defined SEQ66_LIBLO_SUPPORT #define SEQ66_LIBLO_SUPPORT 1 #endif /* * Names the configuration file for this version of application. The "q" * stands for Qt, and the "p" stands for "portmidi". */ #if ! defined SEQ66_CONFIG_NAME #define SEQ66_CONFIG_NAME "qseq66" #endif #if ! defined SEQ66_ICON_NAME #define SEQ66_ICON_NAME "qseq66" #endif /* * Define COVFLAGS=-fprofile-arcs -ftest-coverage if coverage support is * wanted. */ #undef SEQ66_COVFLAGS /* * Define DBGFLAGS=-g -O0 -DDEBUG -fno-inline if debug support is wanted. */ #if ! defined SEQ66_DBGFLAGS #define SEQ66_DBGFLAGS -O0 -DDEBUG -D_DEBUG -fno-inline #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_CTYPE_H #define SEQ66_HAVE_CTYPE_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_DLFCN_H #define SEQ66_HAVE_DLFCN_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_ERRNO_H #define SEQ66_HAVE_ERRNO_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_FCNTL_H #define SEQ66_HAVE_FCNTL_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_GETOPT_H #define SEQ66_HAVE_GETOPT_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_INTTYPES_H #define SEQ66_HAVE_INTTYPES_H 1 #endif /* Define to 1 if you have the `asound' library (-lasound). */ #if ! defined SEQ66_HAVE_LIBASOUND #define SEQ66_HAVE_LIBASOUND 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_LIMITS_H #define SEQ66_HAVE_LIMITS_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_MEMORY_H #define SEQ66_HAVE_MEMORY_H 1 #endif /* Define if you have POSIX threads libraries and header files. */ #if ! defined SEQ66_HAVE_PTHREAD #define SEQ66_HAVE_PTHREAD 1 #endif /* Have PTHREAD_PRIO_INHERIT. */ #if ! defined SEQ66_HAVE_PTHREAD_PRIO_INHERIT #define SEQ66_HAVE_PTHREAD_PRIO_INHERIT 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STDARG_H #define SEQ66_HAVE_STDARG_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STDDEF_H #define SEQ66_HAVE_STDDEF_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STDINT_H #define SEQ66_HAVE_STDINT_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STDIO_H #define SEQ66_HAVE_STDIO_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STDLIB_H #define SEQ66_HAVE_STDLIB_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STRINGS_H #define SEQ66_HAVE_STRINGS_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_STRING_H #define SEQ66_HAVE_STRING_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_SYSLOG_H #define SEQ66_HAVE_SYSLOG_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_SYS_STAT_H #define SEQ66_HAVE_SYS_STAT_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_SYS_SYSCTL_H #define SEQ66_HAVE_SYS_SYSCTL_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_SYS_TIME_H #define SEQ66_HAVE_SYS_TIME_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_SYS_TYPES_H #define SEQ66_HAVE_SYS_TYPES_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_TIME_H #define SEQ66_HAVE_TIME_H 1 #endif /* Define to 1 if you have the header file. */ #if ! defined SEQ66_HAVE_UNISTD_H #define SEQ66_HAVE_UNISTD_H 1 #endif /* Enable JACK version string */ #if ! defined SEQ66_JACK_GET_VERSION_STRING #define SEQ66_JACK_GET_VERSION_STRING 1 #endif /* Define to enable JACK metadata */ #if ! defined SEQ66_JACK_METADATA #define SEQ66_JACK_METADATA 1 #endif /* * Define to enable JACK session. It is deprecated by the JACK developers, * who now recommend using the Non Session Manager (NSM). However, we * still want to enable it in Linux (rtmidi) debug builds. */ #if ! defined SEQ66_JACK_SESSION #define SEQ66_JACK_SESSION 1 #endif /* * Define to enable JACK driver. */ #if ! defined SEQ66_JACK_SUPPORT #define SEQ66_JACK_SUPPORT 1 #endif /* * Define to enable JACK port refresh */ #undef SEQ66_MIDI_PORT_REFRESH /* * Define if NSM support is available. */ #if ! defined SEQ66_NSM_SUPPORT #define SEQ66_NSM_SUPPORT 1 #endif /* * Define to the sub-directory where libtool stores uninstalled libraries. * Useless for qmake, but keep it for now. */ #if ! defined SEQ66_LT_OBJDIR #define SEQ66_LT_OBJDIR ".libs/" #endif #undef SEQ66_MULTI_MAINWID /* Name of package */ #if ! defined SEQ66_PACKAGE #define SEQ66_PACKAGE "seq66" #endif /* Define to the address where bug reports for this package should be sent. */ #if ! defined SEQ66_PACKAGE_BUGREPORT #define SEQ66_PACKAGE_BUGREPORT "ahlstromcj@gmail.com" #endif /* Define to the full name of this package. */ #if ! defined SEQ66_PACKAGE_NAME #define SEQ66_PACKAGE_NAME "Seq66" #endif /* Define to the full name and version of this package. */ #if ! defined SEQ66_PACKAGE_STRING #define SEQ66_PACKAGE_STRING "Seq66 0.99.21" #endif /* Define to the one symbol short name of this package. */ #if ! defined SEQ66_PACKAGE_TARNAME #define SEQ66_PACKAGE_TARNAME "seq66" #endif /* Define to the home page for this package. */ #if ! defined SEQ66_PACKAGE_URL #define SEQ66_PACKAGE_URL "" #endif /* * Define PROFLAGS=-pg (gprof) or -p (prof) if profile support is wanted. */ #if ! defined SEQ66_PROFLAGS #define SEQ66_PROFLAGS #endif /* * Indicates that Qt5 is enabled. Currently not yet in configure.ac nor used in * any module, but.... */ #if ! defined SEQ66_QTMIDI_SUPPORT #define SEQ66_QTMIDI_SUPPORT 1 #endif /* * Define to 1 if you have the ANSI C header files. */ #if ! defined SEQ66_STDC_HEADERS #define SEQ66_STDC_HEADERS 1 #endif #if ! defined SEQ66__GNU_SOURCE #define SEQ66__GNU_SOURCE 1 #endif #endif // SEQ66_QT_MIDILIB_CONFIG #endif // SEQ66_RTMIDI_SUPPORT /* * seq66-config.h for Qt/RtMidi * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: libseq66/Makefile.am ================================================ #***************************************************************************** # Makefile.am (libseq66) #----------------------------------------------------------------------------- ## # \file Makefile.am # \library libseq66 # \author Chris Ahlstrom # \date 2018-11-11 # \updates 2018-12-23 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This file is a makefile for the libseq66 library project. This # makefile provides the skeleton needed to build the libseq66 project # directory using GNU autotools. # #----------------------------------------------------------------------------- #***************************************************************************** # Packing targets. #----------------------------------------------------------------------------- # # Always use Automake in foreign mode (adding foreign to # AUTOMAKE_OPTIONS in Makefile.am). Otherwise, it requires too many # boilerplate files from the GNU coding standards that aren't useful to # us. # #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 subdir-objects MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #***************************************************************************** # EXTRA_DIST #----------------------------------------------------------------------------- EXTRA_DIST = #***************************************************************************** # SUBDIRS #----------------------------------------------------------------------------- SUBDIRS = include src #***************************************************************************** # DIST_SUBDIRS #----------------------------------------------------------------------------- # # DIST_SUBDIRS is used by targets that need to recurse into /all/ # directories, even those which have been conditionally left out of the # build. # # Precisely, DIST_SUBDIRS is used by: # # - make dist # - make distclean # - make maintainer-clean. # # All other recursive targets use SUBDIRS. # #----------------------------------------------------------------------------- DIST_SUBDIRS = $(SUBDIRS) #***************************************************************************** # all-local #----------------------------------------------------------------------------- all-local: @echo "Top source-directory 'top_srcdir' is $(top_srcdir)" @echo "* * * * * All libseq66 build items completed * * * * *" #***************************************************************************** # Makefile.am (libseq66) #----------------------------------------------------------------------------- # vim: ts=3 sw=3 noet ft=automake #----------------------------------------------------------------------------- ================================================ FILE: libseq66/README ================================================ README for Sequencer66: Port of libseq64 to libseq66 Chris Ahlstrom 2015-09-10 to 2020-07-06 While doing this port, we are separating related clumps of code into subdirectories of libseq66/include and libseq66/src, so that it is easier to understand the various groups of functionality represented by the cpp and hpp files. . = libseq66/include: Used for generic code and app-limits. cfg = libseq66/include/cfg: For configuration data and files. ctrl = libseq66/include/ctrl: Code for keyboard and MIDI control of play. midi = libseq66/include/midi: Code for basic MIDI data and structures. play = libseq66/include/play: Code used for patterns and playback. qt = libseq66/include/qt: Minor items specific to Qt usage. os = libseq66/include/os: Utility code for Linux/Mac/Windows. util = libseq66/include/util: Code for calculations, strings, files, etc. We are also changing the names of some of the classes, mostly to reduce the number of underscores and enforce more uniform naming for the classes. # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: libseq66/include/Makefile.am ================================================ #****************************************************************************** # Makefile.am (libseq66) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library libseq66 library # \author Chris Ahlstrom # \date 2018-11-11 # \update 2026-02-16 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the seq66 C/C++ # library. # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 subdir-objects MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) CLEANFILES = *.gc* MOSTLYCLEANFILES = *~ #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = *.h *.hpp #****************************************************************************** # Items from configure.ac #------------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ GIT_VERSION = @GIT_VERSION@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ prefix = @prefix@ includedir = @seq66includedir@ libdir = @seq66libdir@ datadir = @datadir@ datarootdir = @datarootdir@ seq66includedir = @seq66includedir@ seq66libdir = @seq66libdir@ #****************************************************************************** # Source files #---------------------------------------------------------------------------- # # We have replaced pkginclude_HEADERS with the nobase version in order to have # the header subdirectories created and used during "make install". This # is necessary so that the #include statements are valid. # #---------------------------------------------------------------------------- nobase_include_HEADERS = \ base64_images.hpp \ seq66_features.h \ seq66_features.hpp \ seq66_platform_macros.h \ cfg/basesettings.hpp \ cfg/cmdlineopts.hpp \ cfg/comments.hpp \ cfg/configfile.hpp \ cfg/midicontrolfile.hpp \ cfg/mutegroupsfile.hpp \ cfg/notemapfile.hpp \ cfg/patchesfile.hpp \ cfg/playlistfile.hpp \ cfg/rcfile.hpp \ cfg/rcsettings.hpp \ cfg/recent.hpp \ cfg/scales.hpp \ cfg/sessionfile.hpp \ cfg/settings.hpp \ cfg/userinstrument.hpp \ cfg/usermidibus.hpp \ cfg/usrfile.hpp \ cfg/usrsettings.hpp \ cfg/zoomer.hpp \ ctrl/automation.hpp \ ctrl/keycontrol.hpp \ ctrl/keycontainer.hpp \ ctrl/keymap.hpp \ ctrl/keystroke.hpp \ ctrl/midicontrolin.hpp \ ctrl/midicontrol.hpp \ ctrl/midicontrolbase.hpp \ ctrl/midicontrolout.hpp \ ctrl/midimacro.hpp \ ctrl/midimacros.hpp \ ctrl/midioperation.hpp \ ctrl/opcontainer.hpp \ ctrl/opcontrol.hpp \ midi/businfo.hpp \ midi/calculations.hpp \ midi/controllers.hpp \ midi/drums.hpp \ midi/editable_event.hpp \ midi/editable_events.hpp \ midi/event.hpp \ midi/eventlist.hpp \ midi/jack_assistant.hpp \ midi/mastermidibase.hpp \ midi/mastermidibus.hpp \ midi/midibase.hpp \ midi/midibus_common.hpp \ midi/midibus.hpp \ midi/midibytes.hpp \ midi/midifile.hpp \ midi/midi_splitter.hpp \ midi/midi_vector_base.hpp \ midi/midi_vector.hpp \ midi/patches.hpp \ midi/wrkfile.hpp \ play/clockslist.hpp \ play/inputslist.hpp \ play/metro.hpp \ play/mutegroup.hpp \ play/mutegroups.hpp \ play/notemapper.hpp \ play/performer.hpp \ play/playlist.hpp \ play/portslist.hpp \ play/screenset.hpp \ play/seq.hpp \ play/sequence.hpp \ play/setmapper.hpp \ play/setmaster.hpp \ play/songsummary.hpp \ play/triggers.hpp \ sessions/clinsmanager.hpp \ sessions/smanager.hpp \ os/daemonize.hpp \ os/shellexecute.hpp \ os/timing.hpp \ util/automutex.hpp \ util/basic_macros.h \ util/basic_macros.hpp \ util/condition.hpp \ util/filefunctions.hpp \ util/named_bools.hpp \ util/palette.hpp \ util/recmutex.hpp \ util/rect.hpp \ util/ring_buffer.hpp \ util/strfunctions.hpp #****************************************************************************** # uninstall-hook #------------------------------------------------------------------------------ uninstall-hook: @echo "Note: you may want to remove $(pkgincludedir) manually" #****************************************************************************** # Makefile.am (libseq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: libseq66/include/base64_images.hpp ================================================ #if ! defined SEQ66_BASE64_IMAGES_HPP #define SEQ66_BASE64_IMAGES_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file base64_image.hpp * \author Chris Ahlstrom * \date 2022-01-04 * \updates 2022-01-04 * \license GNU GPLv2 or above * * This file provides two sizes of the qseq66 logo, encoded in base64 * format. */ #include /* std::string */ namespace seq66 { static const std::string qseq66_32x32 { "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAQMAAABJtOi3AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5gEDDCAXPFGMYAAAAAZQTFRFAAAA////pdmf3QAAAENJREFUCNdj+A8EDGCiwYCFEYno//inEIloMLBBJv4Z/kEm0PQCjXvM8ICRgRlE8EOJ/+z/oQRCDEw8ZIcRqBIgVwEAyEs6odvCCJwAAAAASUVORK5CYII=" }; static const std::string qseq66_128x128 { "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAA4hHpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjarZ1pkpy3kmz/YxV3CZiH5WA06x308vs4kFWkeEUNz54oqcgaMvEBER7uMYBm/+//HPOf//zHheSyianU3HK2/BNbbL7zm2p//LM/H9/nnI33/1//uM//nfnTL3g+Bj6G98kSP18Nn89/fX/+/sgL/ckXXPrlB8L32/if37j0z+e99X9YUZwh2p//qT/+O2fVc/Z7uh4z25DfQ723MF8vwzcOXircH8v8KvyX+H25vxq/qu12umiXnXbwa7rmvAv2uOiWcd0dt93i43STNUa/feGj99OH+7kaim9+BhtciPrlji+hhRVq8GH6HUKIJvjvtbj7vu2+33SVd16Ob/WOF3P8yF/+Mn/3Df/k1znTskfO8fTus1esy3udg9M2Bv2fb+MI3PmcW7ob/PXr+x/z08EGTjDdba48YLfjvcRI7odthWsAge9LfHxn7MrSqflrJZH3TizGBY7A5mv1zhbvi3Mx+MoBdVbuQ/SDE3Ap+cUifQwhczYVO+K9+Zni7vf65N/ncRXOJ4UcCmfTQuewYkzYT4kVG+oppJhSyqmkmlrqJoccc8o5lyyf6yWUWFLJpZRaWuk11FhTzbXUWlvtzbeAS6aWW2m1tdY779mj6anz053v6H34EUYcaeRRRh1t9In5zDjTzLPMOtvsy6+w4korr7LqaqtvtzEls+NOO++y6267H2zthBNPOvmUU087/fvUPqf6X7/+xam5z6n5e1L6vvJ9any2FL3QfQknnEk6M07MR8eJF50ABu11Zra6GL1OTmdmm8crkmeRSWeznO3GZY4wbufTcd9n9+Pk/vG5Gfb6787N/5OTMzq6/w8n580Ov5zbn5zaEhLOe2LPC7WnNuB9fE/3lX/ZbP1mrlVKOiuEGVJPcXRecpVwchmhDjd2PH6141q2PVQO7KTsVsfZV197lF4M+xBiaXxhpc7y5snB5nWCT6vOM0rBQ4VunteKxJRUJsh3co2Hoz2T72PJ3vh1WOL2cehjtG1hlPxunrDG2WPMsRZPsAsL7qk4vm7H9i73wO9CqpGNbNGcEdivwRZNDqGlcjzI2nmvNn1vebao1wzd9l36OjGPM3p1mSO2a7mRgXQOyuzKm8wVZ0+u9jEbL5vrsn2kneaI3bM/swVe2B3OrZYxk+uh1N4ne7vrYGNyN4QJraKtMBohoNqwUjw7peHSsXXX1nfuabux0rYrldhG1WPs6fTcrWQ/9lqGlcTD2VYOzLnlxyonBrYvjGQHL73t4BxGTGfzZjuyiJx2WTy2OyH4GTESYr+fvIq3ZRBUrv0DfPABB9gdx2NYTIF3x9BzYZdaC7NhgTxNbBhIhTDsiHeYzsavjn2zgzmFZrOMIyYX2fwUa+ZNYy2YT28zYYa7dkxWxlgT52r5w67ZmXvUzQ/F41DYYGDKyTBKaivmuXJYrsR9WFWbfHLMEwcW5gOv1P2EYcQiYFvRus33YWRucChlYipZO3JWxECxPU6do9ubpwklr5n3GbUDFQsjKytjPNbwnDuMU2c/I7W2NyfOm+45h8/YV0rttGTnwiGJ++Hg0gXn97ZhVxnb0beXZAqnqsdLLG3nhd/7dnANzJU3bgQbtphAE/kfHoC15D5ObttVjmFg7z0NHMvUmddmC/dJ2Cb+u3sCqEIqbXD6pcaJAwXXMMyuSAcm+JPK8IsXkqXwR54Jy17sLsvEWrIcbxAUdRyseqy9A4/Jm5eFC9skQNlD28GDDADDprR2O6WYXloMmA1YOHj+sgH/UPlme/iBVDYGzsZuXw8ODeAJyRJePOMASVYd+Xj8yBSf6mFRoAIm6zNQQrjFEyY+u3AdYLViTyOuEjPuxZLBUqDpWM56OUE8qGBAFdaIGcPsrDasgTdFhoK1+5k2D3MmHjGWE+zpiO06FWdpOyyClee4RzI43BGyEYQKKMbOnIIhlg3u8ouFTWxZaMorCkytok1dvCibBxzdx40Y5Ow+TlnfnHjfxCWwingOtrmLvFOmyiafGQaPwIrPIIryVOCU0IWF4E1mgf0Bk95nZi3IL0AMqBX1PSA0TwhkFgLIKHEAa2BFwcwzeFcnDgDTnH06k1hh30ABJgI0Hp1pxC3zcfxwIgTwDkQUAp5+qhEziUuTKOeJmjmv1VlwzgYUKX6OMfSDkTPjeFbAO8AGOBUHMNg0BSaI7CyyuI7xE2qxoBGIibH3lJvpWDG2ABinPgJgfwDT47wc3m1cIcG4QMTc3Q0Kcsk5Aq/btHwhXLc7epNLPWkSoYmJgJQrhAoM2tbjN1/C8CuHMAn4gWcehB/H2VeeOsK6/Sqdg6lt4Wt4Jhavd+MHzkmVP2CrFkPNHiYyeRowwecG+PmcXIs+ymwJJLz7jbStGe+EhkCfPqYp0/enAlkAE9DtnSecJ94J7hpe0GLjI7iYh39Ba9gEzy6KWpx1F4p6V3gdYbw7ogJF4oGgOvBLfjKOAEoRfUAukGnih9VvvJI1m36gVWgLECTM7Lqsw7PZGGMZmAHhhIfPvELklETkzlo8HlESn1PYJ1qxBEMYY+chS1nhiqDDEZU5Fa563hzJB9LcZMnVpXF6yNV7fgT/7QkPwp063r95b8KGdBCWmhNWAMnzg7OCqW+eZezsUoAG8TK8Np/Cyw522Dz0DDgkUiwzgtNOsK+YSU1iC70kW5os3RLhWCOnsEEWuBPAMob4E6bkZw9NYTDjMslg/TwZVERvDm1bIChkqWNoxA5WD1fbGafAKFoFZ3i9KIDETTD47Hd3MKds8CNcsYQx4MQgKnFw5zHgX2DajEu8MALnMly4hC1V5pAgng1u1m+o5MmGATEi50LEYJf7sHr1hs/k89bgJuyrAiadhXbMEeao7QDhwX1ZRYBb1GEIebYv0QI3bqyFEWyemi3Beho/ygJD1zlILwE/JWEbgDLsokEbROQqmtY6PTPskdclCK7M4QDPh1Czat4NjsvKZsWyJmgDAbHlHrADXgZkWOENyzcWIHycIAzXJvQtErPHHtAuqNBuAFo5GLiCdYANn6Z4ePfhXA/F5UABMxXJW7U6jiKfSh1mjPHjUNif4sDxOYJFPU82A+uNeoi1sAELDsMxRtyN4ydigz1e8XGKXMMF8uRVMGQ7sRDvhyg5VAKCADXsUIezYEo8awLJeUssykwse+s8qtAG5d73IZw0DBo9wBESBjmFuRwU51QYLuofqGRBkEF4IeFf6zegFMyoCv7AJVf01PAhEWhM+P15Wf/sJWf/yPQeTRg7YG2LCEAEMtpHLAIDxUQs25QRUVDQSeCbsiFocBhoGSw0FQUIXB9bCM+ugX1ZHuAPHMMesWsQeFy7xpv4Og8futhwwHs5ApYnpD5isgrx8JeleLwWQhyqaHJcvq63SsILKLEnHjC9yITV6xD2WhfStLCbR2cc1gDRqIXov9FWQH90BhznS4nYD3RizDXKOEVBKuxNJJ69hnHgPB2WXxZUsjniV0CdlNmVcPHFBnOcZKIAhOWORNgn9k1YFCQcbgaHKbhMLA47sY7NbBJtTpEFTu7qVoohj2lwBRE/+Z9rCd4Ig58rBdSUnrZD5TJsk5DVO4KAsCWKsEWSkR2IVcADNjA4/kuQbZDFE8+FfPpMuwct9QE/zxPEwvMgt20RhnBUyAbec6XGJHYEA5vj3KE+bO8C7oEzwl3gp8Q/5gVG9Bqx2HngSkfLzhAm2PHK2WereOXgkAuzEB8VBREfbbiAgt5GZKUsPOcb8TAPG8Jr5PpE6xqJg2zrIXbgCrsbvjEFqPyAAmDA0ETPV2eB7azZ4AzIU+jJdrwyL3n3zTWCDGyQkyFoiFisBKs9CXUOIhFi4iZug1pTFMThMzjdj9ciqG4BMFSJYCyFiPSGUnJ0oRuAgzjXkuJHXBJ514IDgM2WEXlZYB3SqS1CQbrYsXJhAEZjy0Hr653HNHSrgCzFiDzbE9oDbRB6ISGHKIrHLfGLrHwAUmkvKItjofb+n6dCnpZpYIF1J+EQ5gJzqMND0kWTUDFsyvRF58HP495BZHtEZDd+IjWzGo4AlwYhcQzwCmLFN0PAgKLknloLFUsApCGx2Bfg0UD8URMWCIjzRG6vDzMZFsuGdxBHpis8KJuKBRCjo/xftBd2Ujo42fvug/1/zCSfxhrhWoTTFH1FNxlRg2Sl6CEi0EysLIm1JdFf+QHEGc5NmF+Kumw+zh8wlAraQoI5m1bbribwzd4CRGA1y2LjJ7jKyQnseN5ZPV/bsM649K/op2MzkH2IdWILkoBgP4xCPcfPgUCY0ItK0KDwN//h3gAZglSkAk5hF8FaBk0Mjr1h+XgH3zdctsNYzNEqo4Q4IUYQmzkeyKqsw8KubhLGy3zsJy/z9RHpL9jnsVHg+Jpdo8i1MaZy1R7CcsneVmMj0rZNgh7M4IvEJHRACpwABKLCbdCIUyF5GWAAiOY4sabFz1gUGyQ16BfRJ6OX3dg+LjYUkn/zWkVSlP26hIeoRBgfJsNzm1JVDtnUI48gUdouJjkp73oZPMGPRUEq8fSgQ7+Pnh1nfJS+yIbogUv34HoKGKSotlItxNDKb9Jxq2oBHB80Cv9u0HkOCJv2kuEhthKxV2c6IVMrF3lhv/g0yg4JFLBA5MtbuAwBUg3QW9kcttclVC1rx029PTWaIdvj4EausPUhOqw0CtERJ8cSEHCcCn7vagC4kd3ESYezKAUJPsDpQOfZDJG8wrQiz4KUZRfQTbLItZX5gzOgXpxYRm0jXciokWAHyU9EA/AWl94xE7KJOvx5jKKzHlUKGwkKn1F4EYXlEffLoECH4BscxVIyT+kbmOTizDl1KUidI+qfnRmcYbzc/IvOiDJDYOB8klhK32505F3ZFD1siYX65btBz4PQ0+WE6RaPO81BLEII4aqzKQMF5WlK9TQZz5a2HCIrUI8sbo7IxecQxxB7Pk9URupC7EWGoNN8P7uk9/DocwLuFKPDBRrU450phxLtlJIJFvAPWeIuL/aPwJwmKhkm4BAcmAU7NLv+G6LubPsuBEyCW4kL+Zpz2MeCtzUb5S42YkdsfAnWCQaXTIE3XXkVl+DtA6IwIdFD+7Dgnw1BF4uPwEkHG2IzwIuSQFm5WfYN8bCHyE3g7AsOjulVUEcH1J0ys7hpP9hSuukUj5Kf8nsUJO47lf2A0dTraXBbKf3Iy4ekRGrXAsQNuigXUWinCadRDI0FKYdwW0YerzQRKKHcOMCo58kiKXm+tKtyLj5hIVHaBESATaCsCF4VgkCY5B2nwXSwTOXLK5bODmJWCSYBX0AJC+jAJ5wJ9intMjgTt5XT4iBlyE0WPScBErRCgyq5xJnrAEpIPBoeE5Utz6lttEhRtgiuhxbJinx8AbLZprNsua1hGbCUrdsYHPaIIkc8sUTnJlyrqQaDksbueAb+Ax9LTU+PWwKkfTEQoubM/Y37I42Yl2sQWeOvnARNtNEcEK8WE4A7LBYlMXaMvJ7oxTeVMSMrwmjA9UrMWF4liCbfvo+Qd7nsGkGqWoaMaXF0YAiHZNgDHgCuwnbOQRwcj+NjzcgF4NLftAlxQvkWOD+my8o3ERkTe+fqWLrh9RSC5c0CDjg68Tg5C6MaBEC2Eg2qBBSwdhyC1I5piSAgMSEkKyuWpYhMdi8rcusWQzlPkG/AgjPoIIqH2QdUKL+U04bkYICn81pbuZPgoJEHazDglfK0HudunvjrFESwDoSksN8rD6VAwtER4qDeyCnCdpYqF8FUDh1iWI0kbIKlwmH3VG4xDndlA0FG0VWVjqMyn9IcYI/rmJf8TDLAKWkBgrG5Bnh0VYCGveKpiwiJs/IWK8ZMVInDFzSWvymmFYXIBLkEwML4QVz0MzE0FoOqhpUpR4HB4uIL0L00AotJ12zheFA6J32dRe0AlgVNDNGeIbHJ1tmYjbLdRQU6tJIIzwRFiD5HKaYGmkDC8CGYhQXCUMcEXMCTZQH5YCJg3JxHmBtZO96Y6g9rf8a+MqF+8hIXSxATxA4ocpQ6PUN0A62DgStBAb4YANTPCVoXIodSkj+r0KdBnwLFFiUvgCHF5YKujERni1AIElKmS45LiiiBTIB0eSvrgwlh2Mogdq+QtPSwXqUfYuVV+4QZgoSe3CPlo8Fit3JFjbiZlMBC1UK6UL4BZYltCxsATo6p5YaL7jGENcL5rRYA1qispxHLYg1FcQs4Q35tnfMk2m8R7carO6BcNdSL3ohe/EVH7eAaH2EJsME7bGTfjseh1FrRgR3OHkTcTtR6DwGcB/U6oSyy+uDZ3ApbzCxFK0eDHEPIwjoGfozzsCGEEJgKduQmxobogiQrWE6vVOYNjAcI+SRrYu0/Z2vcYxnEpf10Y8cSeeA5IWYT0Lk05M8IxT3iLd1plOpX9lBfJkSXmHLLRJGpXIMy2bBC1ZOl0jY8D73GJkS4UVBOjaODm8GTDSikIiKHgOXNAB7e0owixFF6AATdyJFzKzTKu8N7ukjjANiVGCE2wXmbuUnzkyWOdjgFWRQgFa2q8IdicOIT4C96ASNcA9V6aSjfIHK5iYUHWTM8MIKpIfNVvYMVqC4ScH2CNgFq9MyJsePEa4yAn5U2XAvXWEp/gumKRUJ0UyIw3TqM0EWAE+2KFiIipepmzKytlAFBB4I6qwXTIXd5onOGyr2csPJqrMxIlO+vID+IodriF+RBZe+x/hfko0R7ILYQFdUyIvIjIAFwBp5glHIOKS1RB1A3J9k0/M1Lz6EZwa6kFPdEUEPhEBECs4NMZENwmJs7WUH1Nf4osogTlFo5Bq+iIg/8GmMkW+RZKiRCRKq8LWsrAIWtOt2VgX4RIEFpC07WXpFSDgqgGgLy1onDiao55XPEBJWAVxfCFlqnEXjIbdnWrXIGW8mC4HO5wx+xMadqNsKa8MO5Sm7GjUCDryJFswtwvrxvvaNJKPMsw8fozULO6BcUux8bO1HKobyzkvY+KlO6UBGCrNOILkc8Rq+5dyHMFpAiIvP6NkrsKi2OE2GtaCgWEPFWICvg4TYph/Jo4WCjM6YuKpmULb0CENKADLDmF1noO6ZXtEWSrzwghGPyQEF1UMv6oFJVaf7II0sAsonuFljSjftPhWPDyUlLXRVO0E31pjWVLuqCtY2gBy2TinNHykPJ45vDLmharIugADCiHI7NMxCcXX/yBR8G08NFtLPSlDy4sXmA7nhfEVvEBw6WDb7jaURoZSFxuKsNFWTE8iXRlHSESMSjehM2t5RJ3V4yCtXwugSONR4AxMxZvvJT4EGURr8PxoMoc+2iat8Qg7AlKfBo9P2YUEj2U5uNYizV+E8xD14TRFUXvAFQx/XjLczDXZT+QzJDs9HRxGUC8nBexrKVDxjWoaIJ2YgfuTzAbWOzSiGBJGmqWspxrSi2A/oi9IhzSOMqj0dh+qaSiqqejWWYoDPA86HJgagHQMJCmhObn6Lk2AMyF4Izbn8IyjgrcQCjzDbAojE3qG/p6P7OjvFY+A4Ma/fmlKTCqjGPI7RVGAjXSGxXAROa+nLImNvlm5Jv1SQVDZQfPiolnV67KurlEsJwnRSaAW73W9RUfv3GGev0+mUoIdZUADVqSKg3miNTss6+WZTDOSKfSmwifVhffXZVw5Ohry0JlafsCKJ+VQMjPBLCYAso7JaclRdRD8Ifl3FufxjLcF+L+KyB9bEGk19y4rMMdczwiR/L+FrEXQKB8edF/GEN5q8W8b0Xvy7iTzbC/Lud+P1GmH+3E7/fCPPvduL3G2H+3U78fiPMv9uJ32+E+Xc78fuNMP92Eb/bCPO3O8GrCBlQIcvd8gY0MCT7CbTqdPHK8phHSxJBnsg4XfmrxdmBBHeqFyX/qhdViSDvYQwIv6o2ngm55smRA1+nhZwCHmBlUYStqXFnKIfVw6yXyB3hhFItt4xsegjLEv/vtn1ASK1eP2Coo/I/yCJcQaj8cdlI4VoKmw0ThTYg6bf17xSgoXDZWj8FLuVGJPo+jgOzHv6/zs58DOjy3P+n9QCGKL9mnnOm8Oo1shfH4jpBdiJXvyqdEwBu5a8A2Pw5Av8TAI6ftbylmJ/XYn9dyi8L+eWhf0QCVmFkB78uwtrPMjKBjqil5MzmdENTexJGnbpSI+gJPhMJQ3HBs9ntoRBWVY6HHt2FwwrhRSo83vaCuLLeWl1X5XYXJFV3vYpAXXQZ3mQCr4FEt6gcQilUGikw0Zwok6kum5u8yGFdOVpP4eXEIa/dKXRjOMoLL4ObsTWQld4zoZjd8T9ae+5HEUhk0MjKJLD6JUZBnG1S9E4PMGx2ZikdFdHiiVeL+Bz/2RKR2BP3m7faN9F5FgvOkYdRG+Fc1o1x+0Qgkxbp8hJRn66Rz0fIpDusQAcEV4clsAaL1kGNyckmwgFKmPFcmEbW20JJzEpKTED9bomH4PzACpwZNb+XT/a/Hvfnj4hqxKdBgRclx4/yyjA/5caD6NqIs6nmg0jOIStn1maTYVXEW29N6X3VbpUAdgvCvrYg5FU/bkHwPaKqd6pv5/IgmJNfCdoMextqsxsBvcKyu2ghnNdA6VGIGAryyG/sTClVzLhegYcNo7v54aXOtoflrcmmF+tGF6W7l/NGER7mwcdc112yihkjqYrMa8xpodW/snE3btsjBtf29pJaJsZW11xVBbagjiEtCZEH3dSqfPdFHa+ZfYI5I/3ciIjTrdRbQCLFNBB1DZ7dMRjfSwBMEewjqm7HdtTlq/oSlGRF9CsLqPTjTWaeARXPP/I7BZOBsKvKeIF884ky1M0Gns9t1QaXlR5TZxkSxDf8O1uWgIMjxrbaFkDVilrQHs0S5C/80sJzsco7BfVKZyUEPP4EwgyrDM5QJbUgObE4XqWHVcWJUwwGHd0AovcK8mg1ukq4BaemIwc3bnxq3ErO5EfRZASpHjrmjXmoCqMan1F+RQm5QahKUy19YDqxcGvkY/kuK4mT2FdHqAGyjmTFEFh+q1f2NFHxDUIe2LIaIMAAJU+lMpp0MoE4J15wq4Tpz5IaQjQpZ7VVSClzzrKP8nOSmyax42pMxqowRqsGg6QUOJ6xEj+g1J1UKVuzUMZ4qQ5GojZHnniqGYdFjrsiXLQiIvycnvg+WwQ51qtLFABSeBdHyii57gd6we5rmOPEGerHoc0fPXmAIJLmnOgMN/ck3MgdX74NNUsZG2VUFdSynwhzPSjRz7Cf9pSb9cbetEGEhnpdFOEFd2DTprqGVNhy6EOMZsrwUIWc6q0Lq7nXeNXTW7B7vywyz7GrGsaBI5WnuuoMTUiutm93LUfAzteUgLFKIKgX1fAj4VZsRxb0zo5R9eoID0fO0Nia0IbyzZCBlHpEHuFLXo0rBK1FvG85jmE6EAIIVvXYoPGdZ6OJVmvNjHriDRDrVmUMNRvknaygXGyK9wKThLqEPBTk1NCOUyJKHe7+Ig5miXXv28Y52qs3sR0D9f+jmzTtJGBWVRZ5lkxROo8dqKjgdb9nKG+JrR4ZBYSmpfYSuGn1aJXR45Q16lABIrVe4e39mNiBLdTzcUBoBl7SlD3uoOwOnxhqhIiaTGgA9/IqHvXjV1JXPnGm4VeEjmEiXqBG4d+gJHs51KBQLaZhR1+83a0aLagtv9sd7RdGL8T+gSWmTDRTgXLIGldZQgqeaU33S+ArczW10Ck1RjxXWi3t5qyJTc1U6uXHuXwbSQwuqBCbdqxKEBUdg31dAcsRSdpkh2/dtqlI5tRsVExSe7KzS/1yRSMeDn8MRHnxQXWuq0wM6mIjYPLWlEZXxOdgiOYVsw2qfR6DAam7G62+2D4QlSfeajTmu7JyUrkutSrIH88Kk526vy1yyLRv2RUvwUU4Cz8VXYcSGAQ5WJs6lY6v+eAgrYrKiKnPMoOVv6ryP9TuXNR/MiEZexuWCqeKW32U5QPlOD5GmcIHx4+aqj44nrVidQ1eHHdoATXJhS4OqdQh0oZwdnFKFZnMtnsFPogn+KTG06y+XAAakGzxNp7GmqbNX72r5ooGdIFTU9YIXeMTfAVfKV5l95Kb00dikYtj+8tNOeF8iWG8nNYRWaF+copXzeqqwl77sXibIhl4P18jv3+7nYJX4z7nz06shkEkzZOk/CYPxoA/qHEcLMMCCBYfaoS0ifbXislo97BupS01sHCA0GobSho0GCpTrHN74vNWz7h6/SV8hlrjcRTY42VydtkQ+zeTu0F5KTMqKqfpAGGwqJwq9EWNClDbeqZVVyCMBKMjeqagANPERIBEvqkrA+9XN1EZU3YD0q7ULFpQbTachKqusamnDhMiXsLRu0o5S/2kKiM7Bbq8IZMqh5k+xppeu3nwXZ2MhIHSahFg8JdPE02P2wI4Fhd4ShnO7BNQws885uvU6dtuVT+9F5LaAfubOH9tqvexCCUQ87odR+rdWiqTRR51tFRVhXYEPHPapUOEKtgSsSrwHBJB/gp1WOoTe2z6XjiRH0k5uE9U5KVCi/f3RpFR8TNmlZ2apmHUqn5dOaoVeVcVqAFcD0SiStRiC9DviaHBZhEZyrUGA9BHAqr6dtVFLDIiyqF+J9XA2WFNdDhb1GzIdg9tqAZpWHqcs8Z9Z5qSQTyWaXVKKyiLb6e631VZ8SXCI5vQOwCLWSgqMvyNdJpveEvlSM0UmGeVc1HoXpL+luGvXRMbhYz5Me3tPdQJ4wpB56CWanU/4zVKoBt1sA3Hu647RtEuZPepSs/QCMZMkDLOn30LGqTc8nDwWZNbWJ8KnpjzDqZOTVGBWre9gchhFc6jcgbw2jilHNmCEVLSXOwiOmh84+bgI+KJmIwFrWmCqxZDzfbcZDBsRKVuOAwooBowoq/ypIdgAUX+uPJyjYdLSjsPqCmnaI0aVkB7jNQCffKx1eEcKlS7bon/9vZER7djam8cCx1J1IcdQACJz7cB6hhASENN7hJAXJPHQO5qtk19r3DJ+eoEU+1/uUfkOS8j/31tD6/XbxWjfoG08IrcKvaOTDqxZqIEcKXCifoH8Sz8AMzS2EcTTnKQkENNedwBBK/SoQCCLWQvbk0QGjJrU7mpO3BQ8KTm2GR5caQq2DS6GFje6mBRe3m4g3zmsqzGbv1MsjDoV0wMmkkiQL0k1kfKq5goHggxxOCtxgY0nLOmqpmxi7QtDZr0AgpAi1IO6vf3QRV9/KL3v3pxg4JyKj5tmE8liACAyFDnVQhV0QLHQ2wN8W81bMG8bq+nWEF7g0yH+DSqGQTP0NBWuKdnUVEF65v7CFZFPKwvxVvwDZpYGxxDhRdIrRBJ5PLKCLRjtvoC3W1hQmFq4osAviU2FFGAXSI7ho5iADAnDF19uNA4z2rXNQRUT9WKYFvhScxRHM+3C3xFWvFYlLFmC0eMqstfVaHPFzV7HDBGQxp778srjeC3KOuAboeXiUgflb+TejJ5IoIyDgp0sXI564rq1UpsAa5kg5hkGRXM5mE0LIDLV48rbeVi8CFNlinQq51d3XvAdmz4bN06bSd59BTf60RTzv8qPplUXPUpvs9saFFBQsX7pI6mGxnhX70NImPcynGAAkt9CAqMRkmODAsH6NhWHFftMuB1EzDK9/DvlUVwuoq5+F1RFU76FSxXs9UBB0M2mF+bysPuz+jKHcb7aXTlTeP1K2PFRJr6xF6vvf3qwylXHMOD6t84xAb5tscHkGBqPYXOcPzf2WaEiTXqa//4ABAg+EOe23JVizSF2mGrznuLXCZNNGiaWXqix9eUuELfReEIo0GGsn0Y2JQiybLcYQmERyU1mN9RUQlJC+MlYKtJfIFft2mYd85wQogWnEzNbMTZKyCTene0PE0VNmjY7AFFXT58CZX2xd3ZCrC9NSe0NlBUtZnyAZKE6EVOiWEfjRdo0PZuKwopc2RYc1XG6BS4q8aQDwFMfaNZGfaDPdaOWIKnRKjBzZR0xDLkUQO4+POd9oH8EJrAdrXs9Us6gHjEZ7wDPaZrRo/4vtUSPVSe06PV27JlQdIc5PZWzHigg/ha0mxqvkiu3mL1l+M/BoPVVF9RbXTd2TJC6FYaH64D1EtzK80CanrF1e5AeoKsV3+y+r9SvH2dBseSHDwigyGC/kmzB0cIrbnsCvfySMEC58bEMTacrw4eXGOpwE2YCuqxYkc9233H7jA1ZSjU9wLpDoBc1QwnITTpuCcqhnDipCyQJJriwXf4SDw82aCmt0ZWOSyI6GoE9pennBrE/VZpiBzIl0xODX4aWVEzibqHGo+76jG3i4aAOSZi04UNR9p5qtBts9I9S2NkBfRdr6gAIb30/pF8FRAirumHQc+xzj6xtKChXGiyGCRxTYZVgSCLoF32ThrgRyCgW6mqtTl2DRqA4EqaaqKO51kaYRTTzekqsFCTV1f7+Opq55mUG1CCA0jRYMXN/h6pM0gQe8SD4RDYWoDw1SR3v7276FD1Qk+EMUdGiGXPlHRLICVIphFUDR/puyHae5gCvHblZUJA3QgEUnqgQjBRer/bmzVhK+K9VoPQFKRF6qsRqAuOLWy3Pf9lH9T0ASEBdAkN7Q0IFWUWePArlBIMIRLZxRxBI2UH8ZShvsIyTW1JM+kcYHFs8bySbH4SihwQbpBPjB8WCVkk8t7UjmYgByEajaB+aY5/zkhYfS2GHEUSQdDVCgk4wjfgrbtrzFpou9XRE0/UsEOovoHWAWBiR4xaRXWVyluB+js0tqiumHEFmMLx2CI8xOJZ+8uwec3vakh66AqDgtQzBLqZOzSTKMLW9CGLU8k845oiRtLWSieeo0YDyGD7ykA5TRS2TzwymkiUrrmNg2rSVcNec5JAyBbVRDRqmIhniSAhfVR0zQHvw74vjZBdj4Kw6xy3RgiuvEsaxSDmnnN734NkVcbu0ptUEhOAt6DuvIwViuLU1YutGDggASeCV9EH9a0/OZaTR8PfpnBlyPyrBChz7XStglVTl1dMPRIER/X+j92pH3yrH/za3esHV2qni0pyWho8fDUch7TvM2r0oz8ioASe+bMM3m8TeAMsskTxNAlWvEFXm4SgvByDvWwlLCaRSJ2EvFjFJXEvD1aMCPkLU+EBgQ2sIQvRxTFbTb4kYDYEOfCJJksy4AMYKGqDX9rVcNMbT18jgZIHs92d//mkN7CXftMbSdPD1eFrabrb0a0IhFQdoqAQPU1OIJLnA/6qmyA+gZfFHuUAFT/uCCNmChQZJTFUOHJTKsdNYmVXU5dAFZNQSr5cxyB4IOWIG0RL5WckdGC245MRNJuYasWlolpo4HS+y8FxpTunisfcKVGcQ/MrtQKfhafXoVYoA4GBIwRLDRYI9A1NkHVVnLFecYEVXi8HGrRjbtgZeylC7XnQfAdkdT3EsgHHQN4SRVQPmcIcxI0w8F4ikIKMnfOZ5QrBee82Qgcg2ZQ7sLqKAC6ll2eN6RGt9ekCFSGFmqgjC41rNTCgw9IVAcASRP7eiqIIIkoRNGlBCGvqJ1YCAVzJsmgeCCGkF8VHeROUlNPVLp+8UdzueypH/uwxyenkIA+AkBAI+az2dxHyqaO62KSEp1KTGvqQ1vuJIbDpBJOUtJctojAQhNOswOLgmstHTUUTcmAZQN3XO/FGF+p+fiOpjq+3qf29jfnxPk45HMjQkw/YIPLhlI98uIlplMeVD+p4bGCC5MNWcFbWL1/5QDy3fOtYNeLKR1MfqrtkjbGJw0/lr/nncYGsCU/oHtsF4yKC7haM0iJw07A/NcN+7vCZi0pZqUOtaJbj1gwnZw0Jx9r/RLSYf6xaEC03Jai7YSSvpIWHugCzNMEwuoZCsmBp6FUhifBcZGwq6Uv8Z70l79vUpDaEuhpP3rregM1Rf+lt/TJFvV+ZyDutsgdKMBNQpy5I8BMwwsw9MkZXwajy0/Su4CA7ybGMl7AWUTCaH3VLaQE9PDuk5nFgmaNRH6FXugDIIJor0w5xFTHuhB7UkC5EqDyfu/3Z+h0sAG9SJvE1Leq44KOYGx6tpb0SWWb/Etj2SmR3Pi/PT4nM3AE9tatVBVbXljrMploQcayEJnkXw6jgOTg+3aMWqlDKyoPnt5Izv5dyf6LkwIekVk3MYhKso/IUShPwxOpj0/y0pmGk/utXYgfhMn9E7Df9fImTYvZTnzfRfXMvWI1xt1BOzP5Z5r2YjaISFWaveX2e6RYR79JxRNXbi+7xKOz8Wst49BQOjOHaKuz2YicYZ9IFD0Op5Kx9ywSDptaBewFQuayI+CgxJIlxJuoo8l1qR+d/SvwoCfLTN/z31yH9GtUq+7XpyFhciGZG+Fmx50PTq6R7irqlaWtUTmWfooxZJDrne363zxeVx4mpiSjjOr6g+8e2IyhwY3T6d6i7g4Clhm02HUJYdJUC0BNxwSkSkdkVza5CvpWpXbbJIJXzvpd2qJVX1zh4Nad+eiwfUY1yk5TvhT1JxanrJqoKVBUlQ4xKaDpNKvoAyGmvYTaVw4TVNDQcr8PP1akMHhSh3WkIOZlaVrpV6nBhqwuKbh5BFGoOfwumdx79K/wivVRwckDzPdQFdkF70VxOOu1IBusmh42oWUk7WlpXjbZ8SaP+R2nUrjSKXjLeoiTRsuqtndJY5ZYbzbVMpNFZwWkUqspohWJdrocOOUJTLFJ9Abc3G2MWDb0V9LgI5WCxh40A9va2egngsSBVfZSxVvaDrZ/40COwt3qfBxxc1fuAYlZSvaLBiV3baOxKRbmpFWqO0qWeqlpmj2XdQ8ljaWkMQQxOfRdDc/yEX2XegnbTpmHNHVWILWskDJTdUaWQoBRe06VahGoeLGq4PUd/K5PrTtQmDQRrcDDfPBJ7dF1ZAxXfiSRVW3hHIGjejM29msEqvQ7pqirTaf53cpBewIMzQhCNCsCaUdMbS1oriYWHqyBLDH27Lf/H9rXbn36FWyn79Ct0PXI395YUp0nSp2AL1BFmA99WuUzshccsL3UM8YcbZk3VdzVL2KNrc1qDFRfIqPxYDOY5A+iE8Y7gQlKBLRIRNTcD+BVlCpOSNVi0TtBpJLGzfpEUMzSuxLHpyL1mNcEJDw1rnyRXeEMwSZfprEwEQXAOdWBp2BlRhRcSiGxWA0rW1U5O1XFOWgwADognhKFUci4fLVEfw4ifXsmbojxX/GtSuxjlKIOyWtjZzVEm0QurJIpuZ7kpyku1KmHeq78FSgaaN6/yBuQ0qGROyL7dWbevoN47li611nVeGsUXdrgr/TIcpuiSIkGC7nuxvSCjnW50Cbp8xdypsS6aL+78JguyFu3ePjWoh2gkbP5OdmMEUBeJu64L1sq94AocMU6DFBp2qUkdYxpyVtcjmn3qzo+u6rnXHQG6s2IKMZVIvZOfsWQJNs1jpKC5I7FmVVYBoq6bG9CaKA5Oobc7ZF8yb0KghDAlHu3iwacx4hsPpI6eiSrBenWVu3ggXRVugrWA+UpCw9LUHI5ncsjSvtJyYicIN1iAWVUhYnBWIKkutgpQORz/3Ew7BhA+DFRWtdXy+XWRWlf1DBjX/GOYBs70EpBJFQUeMsrylb3mpNQTgQnqgHV72AlqP1xQRM3twTT4f1OPFmpA92gtzQ7dCR/dTabbZfCIQMyF2KjDfSuDNx9dRp9tpI+CJsp8cNBsXUtY9ldyYquVP5ZbWYzqElLVUoB7e6lCAFRwEf1XdCvVuLo9qdRalI9fRpcIOL43x5u2BhxfQbcNJT73p8tqPcrhXj33+yJFXSxyuc7uiOMftW5C6Y9atyMOfxW6ZT/fZW5dsqVmOJZ9XVazv9GZrTl5WJ1VAlHz2klj9dIJGi5Tw8WWWgJ0d7zDpiWn9ekQwpICUqcqsWXAuKvzraYbw0qWTVn33i74N1RvE6Ca3hgCf5wmdrL1PQDyPt3CLO/m0tL82pTKCZqburNq0Ga7NJyNgs1S1/CpqPZSGITG/KPz6jRRgdQJL5FlhKBoMIp3i15bGkQdtQa1wDVdUgmgRWzjw6dx2m3do9MotVvd0nhqCSq5GkKNbu/Y9tGmyGc1aoqNIBDVstKu5rVXpCvJm0M9n34vBJWuqcMVQjVdZQTCJ7zn6FarooiHDA9xqYVxeM2aaELsDoKm20m11JydbohVWpvnXdZoxlSXjtquG2p9uP1FvAcIosGgSERhE5eK8RB+9mm2d/EJ1kugseA4NB0FuXWdTbBbGhJ7SDKHRTTYDSmibTma+Q1X1/KTGrYa8Kmhqxcw0vhpQdWEr64MKyM+Mo1UUNYsQpSuVJif0naeKf9U2hbdg7LEXNX0mfEgtAjElRjVlMyF2oF9+xXsABwcwn0KdllKLYRPwQ4IGxJR/PTn5kGzkq4DUdHAdZ2fIh8hQ03xyqvcSqVGtDEI3Y1HqNnvyrJbYlTpR5xCvTUFJa2Cyt66Z1UXMLCc2V4C9d3mOfyMEBZCAWY61SWtHNlX4eeNPhqfXq8B+ny8ig2+PXWLmebEcBRdvuZVjw6q/JRKlHWYB3sQVRfoUuOcCwjJGw58QJ1mQc1OXkl9NSlNfBhPhT1WVTiKLqmB/vR3TEE36wD/tlldW2Z0vZ9mFZeuv8yqqAYRtU8xsdf2ncjQNRNDfUqfwtDD5Zu1IGgb9ffedrVXG7rQ7HUr1RY0Z+lJ6Lsy4g6qp07LAfHbLyOuOwg1jMemGNiXMuLqRSa4IQlwPnsz4iq2KefpGn7Qapy3ixMWy7n02TQMpxRv3LACC9FCiypzDorwOtjA2SrN64qAeF7IvjelaghT9bv5Lkp1G2PJt0fRbgDpGFXc7VQzNPa/dC2o7t9i27zugVVBX5n3lR+JUqUa4+4cmSr3W32YUfqbPYIKvRJvqp/LP6vTxajEBpdui4ZVj4sqxrJWUHhn1WyVOtPlHlUzGyqwfF2nqNsUd1a2Xjm9dCUc34sIxRUseyeJpSqrnjQhtAZYDEtg3WptM9CU/mmmLiPn91vd8FsXOk51ViEKUaOqKUzYr/tZszRg/1kDmkDULHAOXS9Y1PlWhSdQRGBVxfwNMdiggC4xcOqzlPLbuqwCM1fV309e3TqDWdeg/KFaR4itp926c5ZcJ9YMDfo69dBoKHFx5GrfHT3pQtusq+EiGLXLMDiML2pZvw1DBRfWha8OZ7g3XtxLRNSNNaTqIHGfOriuy3w5/xI/mVGrjOWnjUsjN65oVvXeQ6yzHKvbmV6T2VDoFdKBmppxUN274ZO89TBw7nqQoVk5zam9XS7oCtyq0oEmFXVdUMJL1N1SB2RWg4S3rQr4YcOm2ukBtnLuBaooGhEHXfOn3kIVnb1ulNQ1BRACixDDVNFR1ar0dipH2XXrhq4PrqeaAgEBZtGLLwOra0kkkTQvoXuYfb0pdm/lGHCz2yqsGzLV3CrpvHQdcUmIGoiwdh6nzG/EA/qAhtCs8p1AH7owHVeH9XjOCQ5E1J9BxSSBLDE0WNSRRjLARMJPuu3t/vU7XJZZdFdo/soZdv9rc/3PH83vvvD5iH+qzoBOZAFOl2brWtB9by0FfpWY5yTc9mr3EJQ7jOWOPhHq5Zn8CbYLjurmiFR/dZmqrlzXrKtdUxND8yJHNJF4NdAHOMhYNekKgalGnKDbh7OWBeh7Xb4rXtvYkz2vverahjnV22OUB+wS5JfHAegaBgi6S6/Bk9XmKWXYb9u3bscBkCFSXfdgOl2hljVQ0HX8Dp2va74Q/jsGyNm7piPNUqzEy3aFdRCVJi9QVPzR9YVuEPWrn5oaWV1X8iDCddfzaHeGQYpbl2AvlYu+zusPYyn3I4yTWDxREha4W7qa1qR1mT7qWpKkggyfq3SVCHdVAlBZLiKc1T17oqccGYZGmCFU+iOhkVM2CDkYWNec29C9D8haFbYBj5tZ0hXrSs47e0uRunwsNI2Pd12l8Aaf8bTKo+G/mnx+/XjhJu7Uj6c5Zl07e/tsi+4u2xqb0LXASRc84LhbtwLfUX9d79CU5iviF5KinO/R354B3KMgiUhITo34cLaJcDoeT8DDlIbiHSReI7uFOrqVNVdeK0nR9QW66K2EoKw/PP0+DyzQq83ylj0wdO+QXU42rraNqmYuc1Ne6Ar1v6BRAFhl4SqWnwmn0BC1oGscXmegiyLgTfj2+vRzawW6qioa8Zc2rRrBxpZmuoM4794YTVgn92ZrVN1KsKWjiSgn1TctUhgTIt6GNERGdUmh7vzRFYGEZXylvnvIO+S7ua68m+5Xxrk6yBWIrUpPqrP7NptE1M5gs5UrdhXDqSKNrwKpOoL+EgbBna7S1bwhNgXIaGI8aVRc1T1EtqpmukDdBP0VBS9Z6+7dywCnQvqFbaXMpI8tqi/c6p7SdJoG05WKumrnFgtGimiRKRo7170Qd4byauramjdnT6jTbUCIOzu7bonDhHSZZdTf4oHvq9tEfalE2ntfkMo9cPIo/erVC1M0Rd3RuakS6MEfvZoSub0Tmpv6eo4LEZZdlAWuxmmaXXfflArOtPu3G0iGq7cDvQc/0XERCDvflKaGiVLXfR3qRtOFYLqsP4KQTdfqqZl3AWNJF3FJTd7L4WK9x6Rug6T7o+y7Fuv7ohD0MeanK1JSrWar8RUmr/E/IeXQjT5qCLUhqxmjo0HvDW0sTDNeW30fhL0cYapBFw+iqLJzRhkPNWk1zb91dfjxTuoYjEQlyBXrnClINHCmk4gJSOhqOiK6auKim8RE8OhdFHupE2iw07j9Vbjn0u0eG6xy6pQpulag6W8TcJCXewuoell4l5l1d1jWlI8am6XaW/AAZ/VbsDB0jVb9ydfZxdXvbTnvjiM5P8quet3NsTwSImGtXrM+5eoFNcAC+qpC8mxbCEQQx+lav12+Iq5IFnxAd7eHcVvC8zRZdzWqU7xLpLdyEwYsLepCJl0dpE7LXN3NQ2fNA3Z0q70XxAEzDdWZ1SJibk5dCWFNL96/KyCqLGfVPF7UBVcJPFOTSVY3LugmTAs6s324gfrRCG8Js+WFdH9JBhh4GXVFzk9XJDHotkAQEXWR665/HdyN/5tv+PooSNPIkZLt8gLdhOCTuhMhIfiv0VCHmNFpcOwGrT/33megWGm+LNNV+qeON3UC7Q5Z9/EIiSFsd4RPfw2D0bSC2P9jiewtpzeG1DmkQremC6p+9H16t5ouBxdTHq592CFfNLq/NpcypKHKdcX8ID7oEmLdOANbSrruSn+BAowLm1T6RXeqAVFOPasbBktc4wh0WeyC1DrdhkHwQhZvr/5stROlfmf9bsuu5kvl3xjBjDjwhwx4JeuUF16qkEf9zSSg3GtfzIQ9yR671L9WWqr36u5YFYeVDg26YBGete/4bjNru6m/jkIt6/orDPR3cny6p3lhXf/03T0dCE9AWuoueWWF3yt8XsD8u1f4w5jGHz6a331BQkA5JvN/vVsnXNovyr4AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfmAQMRIScXbb2uAAAAN3RFWHRDb21tZW50AFVTIHJvdXRlIDY2IHNpZ24sIHNoaWVsZCBzaWduIHdpdGggcm91dGUgbnVtYmVylzuQsAAAAZhQTFRF8fHx8/Pz9fX19vb2+Pj4+fn5+/v7/f39/v7+////7+/v7u7u7Ozs6urqAAAAAAD/AQEBAgICAgL9AwMDBAQEBQUFBgYGBwcHCAgICQnyCgoKCwvwDAwMDAzuDQ0NDg4ODw8PEBDnEhLlFRUVFRXeFhYWGRnWHBwcHh4eHx8fISEhIyO8KCioKSkpKioqKysrKyuVLCwsLS2ILi4uLy9IMTExMjIyNjY2OTk5Ozs7QUFBS0tLTC8vTExMTU3QU1NTVC8vV1fQWlpaW1tbXFxcXy8vX19fYGBgYmJiZWVlZmZmaWlpbGxsbW1tdXV1fn5+f39/hISEh4eHjIyMj4/Wkiwsk5OTl5eXmJiYmZmZmpqam5ubn5+foKCgo6OjpaWlpqamqKioqqqqrKysrq6usyUlu7u7wcHByMjIyMjmysrKzB0dzMzMzc3Nzs7O0h4e0tLS1NTU1dXV19fX2NjY2dnZ2tra3Nzc3d3d3t7e39/f4ODg4eHh4uLi5OTk5OTx5sjI5ubm5+fn6Ojo7e329QgI/wAA////O4MHOQAAAA50Uk5TAAAAAAAAAAAAACJEiMy2yLAmAAAGKUlEQVR42tWb5ZscNwzGp3wuvCPHs3dpU27KjCkmhRRThqTMzJBSeuXW/rf7YdAzBnlg96m+JLurk38j27LGtjIxSU4WUyVzfnvxDu+vgeJPluIuZSkA2wBxzEoABIbiMQDH+QAAADwXf3wJABKPxzUB0EdcgNIuEO6G4wVq2UvfhTR/UpXe3TwAQiteo8/WWtW/6mmvalG37+6tPsAZlVsr2Vl5XQpAQeuGtzjq1yQ87HuiHsAuAILSWqvWEy+0I33rm/qZIQGttda6+QaEb1tT39/Z/gCttQKACyMA7xMAqMpw85yAJAAg6vSP1LVc1nYHgQiyp7rT2iMVBLijwdVaa/0BvEKkLSGv5jZsJRUAqB6jaxhwmLZVKrlKOVpX0tIBYRBhssGA6Vt+qRqURBWN9kozLQgE5eAshgSZFdYg79WLSjGYC5kdfvTSogCATh0CEAAUsT83xgR/ZRCU/fx1H8Dd/cPmjakZjLE+2j/GCe6zAcrBzWzfCWD6QAEC2R0ImRBCEDHb7zjabrD+D88H1CHI6vaR2P/WR2OM+crCZPRCUQH8CNb4iwJoPoC+v+xzIYTIjpYBU3MAusatLuj8wgKonCCFEBlvAAzGYAiAOxvlO+UYWPnCuw/hj+kAAIAT9SAEzwVtq4ExwAOQALDbTkOVEIUjAOxZoN7oxIEtBeB8PsFUAAKkFYjE35xAHADQ/YAU64CCXGsBcxZ0mnJ8ird/I3VzgqyTjXABGB9jI+CVAcAP8ZkQiAO9GRKUz+yUpElIFIB/p6YbzBGI03wpmV4DQbMM9QGKWTKyKMEKwOlOgI+H2f4S4k1KORNhDQBrEd+LybubBhAbB6D1tP9k0AP5skKhl1NxAMAlywIE347Fh8u7IAwgABTLAsgowMY9QIu2f+1wp2y9HsD/HMDwWzILABjDBvCqMgAoYJRL4Fe9YLwHDB+gVD04IhS/CUCGjLIIgqphD8DrAcMHMEHdcCCiaPtxAhMFwJHk5Xhc+4Y3DToAX3omQUL7eUz1isBecZkQjTCapApAZv6smJZuP5eA8qTlX7gdMG/7Ofp9kFlDcBgF9oyZgbH1yA3gHgKzRYBaVj2CBuAxAtSUDmCy9vsg6zqgmMUBjJzgLQeAnKsDYorKvUPiHoJLAOR+AExoP2G9JNklqACeCk6BT+Z0QA7XNh3BMQceWMQBZR/cbAOcEnTA/lkdULoANoAMB6F4NDAJunlOAM4enJqxjPrsJqj2omEmhBCHUwBcdhNU+zOxPi+gBKtmimoD8HwHAIkAZoJqnud5fot9bnh7Wg84zJpUArIAtlMd0DdrTCqBAnCW6BxaqVQAM1J1kB1nQvwiox6IJMgJqt3ktAYAgNu4CY7z2/bL/dxkpklLMiEokg3Hv05Q7fbBXx0PJET3AMCL/BUCBDzTeiAFYN/wh2iACixIJYBMWV9nAWiicbQLEgDy8QBqIwA0twfm7YIUq2MG4XiAwDRMSNQSAMYHot8DPUAAXg8D/MMPxXmCaj8nyoS4Orozcm7MqGvlMWyALXckygdLamCNTVAdroac3bEpKZEJvqFmoQ3K5bJCADizAfjNu0W7VGZ+Q3u7NQvvkicYTclJpZ0VBzaJnWYPJsBGjw2y5v7GPq5Z3kZtSJUAKoY3Knlm2VvFwd1SOO8RRS0nnFeYyOll7/VcHFnDwXXTvmuLRoCWPrS0RuAT7n3CdfhgBfseTwPwkFoLweAORda7W79w+5cidotmYQICQO/5zozk0gR7JAAc9h9cbgOAOm/Z/j8UqjFREv7Dy6lyEQDQsXCVTX3VZoH2y7Kdk2J1RnfVlUszz/6yTEemVFoR5u17oLiSV2t2jWyvVCnsndR093KW5Jf7DWt7Et1xfVssVpdlFUkFj+LBPoEEASo6Oq+DYlxgYwA43VDWapGPAuS7DXciueSzlk8vdz+R7DPc5KrHIkh1z7ia0768BlBBVr9KxzBvS97w6ueTil498nP1YFW9W695CQLh0XPmqLoNyYHt5mGLPM9zrOpHX81X9huRt5sBmgfLORcD6N8/lcWvMxc+8xBUPRjmr7zmSQXwiNgUgAjHuDUAiFuxLTYKIF4WGwaYLv8BGSkm8G3B25MAAAAASUVORK5CYII=" }; } // namespace seq66 #endif // SEQ66_BASE64_IMAGES_HPP /* * vim: ts=4 sw=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/basesettings.hpp ================================================ #if ! defined SEQ66_BASESETTINGS_HPP #define SEQ66_BASESETTINGS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file basesettings.hpp * * This module declares/defines just some of the global (gasp!) variables * in this application. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-01-17 * \updates 2022-04-06 * \license GNU GPLv2 or above * * This module defines some items common to all configuration files that get * written. */ #include #include #include "cfg/comments.hpp" /* seq66::comments class */ namespace seq66 { /** * Holds the current values of sequence settings and settings that can modify * the number of sequences and the configuration of the user-interface. * These settings will eventually be made part of the "user" settings file. */ class basesettings { private: /** * Indicates if the settings have been modified (in the user interface). * Starts out false. */ bool m_is_modified; /** * A [Seq66] marker section indicates the ordinal version of the file. * Starts at 0, and is incremented when a new feature is added or a * change is made. */ int m_ordinal_version; /** * [comments] * * Provides a way to embed comments in the "usr" file and not lose * them when the "usr" file is auto-saved. */ comments m_comments_block; /** * Provides an optional name for the settings object. */ std::string m_file_name; /** * Holds a buffer of error message(s). */ mutable std::string m_error_message; /** * Indicates if the error message buffer contains error messages. */ mutable bool m_is_error; public: basesettings (const std::string & name = ""); basesettings (const basesettings & rhs) = default; basesettings & operator = (const basesettings & rhs) = default; virtual ~basesettings () { // default, member automatically deleted } virtual void set_defaults (); virtual void normalize (); public: bool is_modified () const { return m_is_modified; } void modify () { m_is_modified = true; } void unmodify () { m_is_modified = false; } int ordinal_version () const { return m_ordinal_version; } const comments & comments_block () const { return m_comments_block; } comments & comments_block () { return m_comments_block; } const std::string & error_message () const { return m_error_message; } virtual bool set_error_message (const std::string & em) const; bool is_error () const { return m_is_error; } const std::string & file_name () const { return m_file_name; } void file_name (const std::string & fn) { m_file_name = fn; } protected: void ordinal_version (int value) { m_ordinal_version = value; } void increment_ordinal_version () { ++m_ordinal_version; } }; // class basesettings } // namespace seq66 #endif // SEQ66_BASESETTINGS_HPP /* * basesettings.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/cmdlineopts.hpp ================================================ #if ! defined SEQ66_CMDLINEOPTS_HPP #define SEQ66_CMDLINEOPTS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file cmdlineopts.hpp * * Provides the declarations for safe replacements for some C++ * file functions. * * \author Chris Ahlstrom * \date 2015-11-20 * \updates 2023-12-13 * \version $Revision$ * * Also see the filefunctions.cpp and strfunctions modules. * These modules together simplify the main() module considerably, which * will be useful when we have more than one "Seq66" application. * * Note that this module handles command-line options AND the main * options files ('rc' and 'usr'). */ #include #include "seq66_features.hpp" /* seq66::seq_version_text(), etc. */ #if defined SEQ66_PLATFORM_MING_OR_UNIX #include /* struct option s_long_options [] */ #endif /* * This is the main namespace of Seq66. Do not attempt to * Doxygenate the documentation here; it breaks Doxygen. */ namespace seq66 { class rcsettings; /** * A wrapper class so that the whole class can be a friend. */ class cmdlineopts { private: static const std::string s_versiontext; static struct option s_long_options []; static const std::string s_optstring; static bool s_no_cmd_line_options; public: cmdlineopts () { // no code } cmdlineopts (const cmdlineopts &) = default; cmdlineopts & operator = (const cmdlineopts &) = default; cmdlineopts (cmdlineopts &&) = default; cmdlineopts & operator = (cmdlineopts &&) = default; ~cmdlineopts () = default; static bool cmd_line_options (); static bool help_check (int argc, char * argv []); static bool kill_check (int argc, char * argv []); static bool verbose_check (int argc, char * argv []); static bool parse_options_files (std::string & errmessage); static bool parse_rc_file ( const std::string & filespec, std::string & errmessage ); static bool get_usr_file (); static bool parse_usr_file ( const std::string & filespec, std::string & errmessage ); static bool parse_daemonization ( bool & startdaemon, std::string & logfile ); static bool parse_o_options (int argc, char * argv []); static bool parse_o_sets (const std::string & arg); static bool parse_o_mutes (const std::string & arg); static bool parse_o_virtual (const std::string & arg); static bool parse_log_option (int argc, char * argv []); static int parse_command_line_options (int argc, char * argv []); static std::string env_session_tag (); static void show_locale (); static bool set_global_locale (const std::string & lname = ""); static bool write_options_files (const std::string & filename = ""); static bool write_rc_file (const std::string & filename = ""); static bool write_usr_file (const std::string & filename = ""); static bool alt_write_rc_file (const std::string & filebase); static bool alt_write_usr_file (const std::string & filebase); private: static void show_help (); static std::string get_compound_option ( const std::string & compound, std::string & optionname ); }; // class cmdlineopts #endif // SEQ66_CMDLINEOPTS_HPP } // namespace seq66 /* * cmdlineopts.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/comments.hpp ================================================ #if ! defined SEQ66_COMMENTS_HPP #define SEQ66_COMMENTS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file comments.hpp * * This module declares/defines an object to hold "[comments]", which are * present in three different classes now. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-26 * \updates 2020-08-04 * \license GNU GPLv2 or above * */ #include namespace seq66 { /** * This class is a wrapper for comments-management. */ class comments { private: /** * [comments] * * Provides a way to embed comments in the "rc" file and not lose * them when the "rc" file is auto-saved. */ std::string m_comments_block; /** * Indicates if some caller called the set() function. */ bool m_comment_is_set; public: comments (const std::string & comtext = ""); comments (const comments & rhs) = default; comments & operator = (const comments & rhs) = default; const std::string & text () const { return m_comments_block; } void clear (); void set (const std::string & block); void append (const std::string & line) { m_comments_block += line; } bool is_set () const { return m_comment_is_set; } }; // class comments } // namespace seq66 #endif // SEQ66_COMMENTS_HPP /* * comments.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/configfile.hpp ================================================ #if ! defined SEQ66_CONFIGFILE_HPP #define SEQ66_CONFIGFILE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file configfile.hpp * * This module declares the abstract base class for configuration and * options files. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-23 * \updates 2025-01-19 * \license GNU GPLv2 or above * * This is actually an elegant little parser, and works well as long as one * respects its limitations. */ #include /* std::streampos */ #include /* std::string, the ubiquitous one */ #include "util/basic_macros.hpp" /* seq66::tokenization vector */ #include "util/strfunctions.hpp" /* seq66::string_to_int() */ namespace seq66 { class rcsettings; /* * Currently these strings are just for reference, and not yet used in the * code for each type of configuration file. * * std::string filetag = "config-type"; * std::string ctrltag = "ctrl"; * std::string mutetag = "mutes"; * std::string paltag = "palette"; * std::string playtag = "playlist"; * std::string rctag = "rc"; * std::string usrtag = "usr"; */ /** * This class is the abstract base class for rcfile and usrfile. */ class configfile { #if ! defined SEQ66_KEEP_RC_FILE_LIST friend bool delete_configuration ( const std::string & path, const std::string & basename ); friend bool copy_configuration ( const std::string & source, const std::string & basename, const std::string & destination ); #endif private: /** * Holds the last error message, if any. Not a 100% foolproof yet. */ static std::string sm_error_message; /** * Indicates if we are in an error status. */ static bool sm_is_error; /** * Provides a numerical flag to use a default value for an integer. */ static int sm_int_missing; static int sm_int_default; /** * Provides a numerical flag to use a default value for a double or float * value. */ static float sm_float_missing; static float sm_float_default; /** * Supported ile-extensions. The qss extension is included, but only * canoncial names like qseq66.qss will be manipulated (apart from being * read). */ static tokenization sm_file_extensions; private: /** * Hold a reference to the "rc" settings object. */ rcsettings & m_rc; /** * The file extension of the configuration file. */ std::string m_file_extension; /** * Provides the name of the configuration or other file being parsed. * This will normally be a full-path specification. */ std::string m_name; /** * Provides the current version of the derived configuration file format. * This value is set in the constructor of the configfile-derived object, * and is incremented in that object whenever a new way of reading, * writing, or formatting the configuration file is created. For * example, a new version of the MIDI control file code might be * incremented to "3". If the user's MIDI control file specifies * "version = 2", that means that the code for this file must revert to * the old format for reading the data. When saved, the old file is * upgraded to the new version. Also useful to turn on the "--user-save" * option for changes in the format of the "usr" file. */ std::string m_version; /** * The actual version specified in the configuration file, which could be * older than the newest version supported in the code. */ std::string m_file_version; protected: /** * The current line of text being processed. This member receives * an input line, and so needs to be a character buffer. */ std::string m_line; /** * Provides the current line number, useful in troubleshooting. */ int m_line_number; /** * Holds the stream position before a line is obtained. */ std::streampos m_line_pos; public: configfile ( const std::string & name, rcsettings & rcs, const std::string & fileext ); configfile () = delete; configfile (const configfile &) = delete; configfile & operator = (const configfile &) = delete; /** * A rote destructor needed for a base class. */ virtual ~configfile() { // empty body } virtual bool parse () = 0; virtual bool write () = 0; std::string parse_comments (std::ifstream & file); std::string parse_version (std::ifstream & file); bool file_version_is_old (std::ifstream & file); const std::string & file_extension () const { return m_file_extension; } const std::string & name () const { return m_name; } void name (const std::string & n) { m_name = n; } const std::string & version () const { return m_version; } int version_number () const { return version().empty() ? 0 : string_to_int(version()) ; } const std::string & file_version () const { return m_file_version; } int file_version_number () const { return file_version().empty() ? 0 : string_to_int(file_version()) ; } bool bad_position (int p) const { return p < 0; } int line_position () const { return int(std::streamoff(m_line_pos)); } static const std::string & get_error_message () { return sm_error_message; } static bool is_error () { return sm_is_error; } static bool is_default (int value) { return value == sm_int_default; } static bool is_missing (int value) { return value == sm_int_missing; } static bool is_default (float value) { return value == sm_float_default; } static bool is_missing (float value) { return value == sm_float_missing; } protected: bool set_up_ifstream (std::ifstream & instream); static void append_error_message (const std::string & msg); static bool make_error_message ( const std::string & sectionname, const std::string & additional = "" ); static bool version_error_message ( const std::string & configtype, int vnumber ); void file_version (const std::string & v) { if (! v.empty()) m_file_version = v; } void version (const std::string & v) { if (! v.empty()) m_version = v; } void version (int v) { m_version = std::to_string(v); } rcsettings & rc_ref () { return m_rc; } /** * Sometimes we need to know if there are new data lines at the end of an * existing section. One clue that there is not is that we're at the * next section marker. This function tests for that condition. * * \return * Returns true if m_line[0] is the left-bracket character. */ bool at_section_start () const { return m_line[0] == '['; } /** * Provides the input string, to keep m_line private. * * \return * Returns a constant reference to m_line. */ const std::string & line () const { return m_line; } int line_number () const { return m_line_number; } std::string trimline () const; /** * Provides a pointer to the input string in a form that sscanf() can use. * * \return * Returns a constant character pointer to the data in m_line. */ const char * scanline () const { return m_line.c_str(); } bool get_line (std::ifstream & file, bool strip = true); bool line_after ( std::ifstream & file, const std::string & tag, int position = 0, bool strip = true ); int find_tag (std::ifstream & file, const std::string & tag); int get_tag_value (const std::string & tag); void write_date (std::ofstream & file, const std::string & tag); bool next_data_line (std::ifstream & file, bool strip = true); bool next_section (std::ifstream & file, const std::string & tag); std::string get_variable ( std::ifstream & file, const std::string & tag, const std::string & variablename, int position = 0 ); std::string extract_variable ( const std::string & line, const std::string & variablename ); void write_seq66_header ( std::ofstream & file, const std::string & configtype, const std::string & version ); void write_seq66_footer (std::ofstream & file); bool get_boolean ( std::ifstream & file, const std::string & tag, const std::string & variablename, int position = 0, bool defalt = false ); void write_boolean ( std::ofstream & file, const std::string & name, bool status ); int get_integer ( std::ifstream & file, const std::string & tag, const std::string & variablename, int position = 0 ); void write_integer ( std::ofstream & file, const std::string & name, int value, bool usehex = false ); float get_float ( std::ifstream & file, const std::string & tag, const std::string & variablename, int position = 0 ); void write_float ( std::ofstream & file, const std::string & name, float value ); void write_string ( std::ofstream & file, const std::string & name, std::string value, bool quote_it = false ); bool get_file_status ( std::ifstream & file, const std::string & tag, std::string & filename, /* a side-effect for returning name */ int position = 0 ); void write_file_status ( std::ofstream & file, const std::string & tag, const std::string & filename, bool status ); void write_comment /* the opposite of parse_comments() */ ( std::ofstream & file, const std::string & commenttext ); }; // class configfile /* * Free functions. */ #if ! defined SEQ66_KEEP_RC_FILE_LIST extern bool delete_configuration ( const std::string & path, const std::string & basename ); extern bool copy_configuration ( const std::string & source, const std::string & basename, const std::string & destination ); #endif // ! defined SEQ66_KEEP_RC_FILE_LIST extern std::string get_current_date_time (); } // namespace seq66 #endif // SEQ66_CONFIGFILE_HPP /* * configfile.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/midicontrolfile.hpp ================================================ #if ! defined SEQ66_MIDICONTROLFILE_HPP #define SEQ66_MIDICONTROLFILE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midicontrolfile.hpp * * This module declares/defines the base class for managing the MIDI control * configuration file. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-13 * \updates 2022-07-22 * \license GNU GPLv2 or above * */ #include /* std::cout, ifstream, ofstream */ #include /* std::map<> */ #include "cfg/configfile.hpp" /* seq66::configfile base class */ #include "ctrl/keycontainer.hpp" /* seq66::keycontainer class */ #include "ctrl/midicontrolin.hpp" /* seq66::midicontrolin class */ #include "ctrl/midicontrolout.hpp" /* seq66::midicontrolout class */ #include "ctrl/opcontrol.hpp" /* seq66::optcontrol and automation */ namespace seq66 { /** * Provides a file for reading and writing the application's main * configuration file. The settings that are passed around are provided * or used by the performer class. */ class midicontrolfile final : public configfile { friend class rcfile; /* * This class provides an alternate key for a midicontrol useful to write * out the controls in the proper order for a configuration file. */ class key { private: automation::category m_category; /* for grouping into sections */ int m_slot_control; /* pattern/group number, slot */ public: key (const midicontrol & mc); bool operator < (const key & rhs) const; std::string category_name () const { return opcontrol::category_name(m_category); } int slot_control () const { return m_slot_control; } }; /* midicontrolfile::key */ class stanza { private: automation::category m_category; std::string m_key_name; std::string m_op_name; int m_slot_number; int m_settings[automation::ACTCOUNT][automation::SUBCOUNT]; public: stanza (const midicontrol & mc); bool set (const midicontrol & mc); automation::category category_code () const { return m_category; } std::string category_name () const { return opcontrol::category_name(m_category); } std::string key_name () const { return m_key_name; } std::string op_name () const { return m_op_name; } int slot_number () const { return m_slot_number; } int setting (unsigned action, unsigned part) const { return action < automation::ACTCOUNT && part < automation::SUBCOUNT ? m_settings[action][part] : 0 ; } }; /* midicontrolfile::stanza */ public: using storage = std::map; private: /** * Provides a default-filled keycontrol container. */ keycontainer m_temp_key_controls; /** * Provides a default-filled midicontrol container. */ midicontrolin m_temp_midi_ctrl_in; /** * Provides the storage for the mute-groups data. */ storage m_stanzas; public: midicontrolfile () = delete; midicontrolfile ( const std::string & filename, rcsettings & rcs ); midicontrolfile (const midicontrolfile &) = delete; midicontrolfile & operator = (const midicontrolfile &) = delete; virtual ~midicontrolfile (); virtual bool parse () override; virtual bool write () override; bool parse_stream (std::ifstream & file); bool write_stream (std::ofstream & file); bool parse_control_sizes ( std::ifstream & file, const std::string & mctag, int & offset, int & rows, int & columns ); public: bool container_to_stanzas (const midicontrolin & mc); void show_stanzas () const; private: static bool keycontrol_error_message ( const keycontrol & kc, ctrlkey ordinal, int lineno ); bool parse_control_stanza (automation::category opcat, int index = 0); bool parse_midi_control_out (std::ifstream & file); bool add_default_automation_stanzas (int count); void show_stanza (const stanza & stan) const; bussbyte get_buss_number ( std::ifstream & file, bool isoutputport, const std::string & tag, const std::string & varname ); void write_buss_info ( std::ofstream & file, bool isoutputport, const std::string & varname, bussbyte nominalbuss ); storage & stanzas () { return m_stanzas; } bool write_midi_control (std::ofstream & file); bool write_midi_control_out (std::ofstream & file); bool read_triples ( std::ifstream & file, midicontrolout & mctrl, midicontrolout::uiaction a ); bool write_triples ( std::ofstream & file, const midicontrolout & mctrl, midicontrolout::uiaction a ); bool read_mutes_triple ( std::ifstream & file, midicontrolout & mctrl, int group ); bool write_mutes_triple ( std::ofstream & file, const midicontrolout & mco, int group ); }; // class midicontrolfile /* * Free functions for the control file. */ extern bool read_midi_control_file ( const std::string & fname, rcsettings & rcs ); extern bool write_midi_control_file ( const std::string & mcfname, rcsettings & rcs ); } // namespace seq66 #endif // SEQ66_MIDICONTROLFILE_HPP /* * midicontrolfile.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/mutegroupsfile.hpp ================================================ #if ! defined SEQ66_MUTEGROUPSFILE_HPP #define SEQ66_MUTEGROUPSFILE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file mutegroupsfile.hpp * * This module declares/defines the base class for managind the qseq66.mutes * configuration file. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-30 * \updates 2022-06-28 * \license GNU GPLv2 or above * * Provides support for a mute-groups configuration file. */ #include "cfg/configfile.hpp" /* seq66::configfile class */ namespace seq66 { class mutegroups; /* forward reference to class */ /** * Provides a file for reading and writing the application's mute-group * configuration file. The settings that are passed around are provided * or used by the performer class. */ class mutegroupsfile final : public configfile { friend class rcfile; private: /** * The mute group object to work on. */ mutegroups & m_mute_groups; public: mutegroupsfile () = delete; mutegroupsfile (const std::string & filename, mutegroups & mutes); mutegroupsfile (const mutegroupsfile &) = delete; mutegroupsfile & operator = (const mutegroupsfile &) = delete; virtual ~mutegroupsfile (); virtual bool parse () override; virtual bool write () override; bool parse_stream (std::ifstream & file); bool write_stream (std::ofstream & file); private: bool parse_mutes_stanza (mutegroups & mutes); bool write_mute_groups (std::ofstream & file); mutegroups & mutes () { return m_mute_groups; } const mutegroups & mutes () const { return m_mute_groups; } }; // class mutegroupsfile /* * Free functions in the seq66 namespace. */ extern bool open_mutegroups ( const std::string & source, mutegroups & mutes ); extern bool save_mutegroups ( const std::string & destfile, const mutegroups & mutes ); } // namespace seq66 #endif // SEQ66_MUTEGROUPSFILE_HPP /* * mutegroupsfile.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/notemapfile.hpp ================================================ #if ! defined SEQ66_NOTEMAPFILE_HPP #define SEQ66_NOTEMAPFILE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file notemapfile.hpp * * This module declares/defines the base class for managind the qseq66.drums * configuration file. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-11-05 * \updates 2020-10-20 * \license GNU GPLv2 or above * * Provides support for a mute-groups configuration file. */ #include /* std::ofstream and ifstream */ #include "cfg/configfile.hpp" /* seq66::configfile class */ #include "play/notemapper.hpp" /* seq66::notemapper */ namespace seq66 { /** * Provides a file for reading and writing the application's mute-group * configuration file. The settings that are passed around are provided * or used by the performer class. */ class notemapfile final : public configfile { private: /** * Holds a reference to the notemapper object to be acted upon by this * class. */ notemapper & m_note_mapper; public: notemapfile ( notemapper & m_note_mapper, const std::string & filename, rcsettings & rcs ); notemapfile () = delete; notemapfile (const notemapfile &) = delete; notemapfile & operator = (const notemapfile &) = delete; virtual ~notemapfile (); virtual bool parse () override; virtual bool write () override; bool parse_stream (std::ifstream & file); bool write_stream (std::ofstream & file); private: bool write_map_entries (std::ofstream & file) const; notemapper & mapper () { return m_note_mapper; } }; // class notemapfile /* * Free functions for working with note-mapfiles. */ extern bool copy_notemapper ( notemapper & pl, const std::string & source, const std::string & destination ); } // namespace seq66 #endif // SEQ66_NOTEMAPFILE_HPP /* * notemapfile.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/patchesfile.hpp ================================================ #if ! defined SEQ66_PATCHESFILE_HPP #define SEQ66_PATCHESFILE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file patchesfile.hpp * * This module declares/defines the base class for managind the qseq66.drums * configuration file. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-11-05 * \updates 2025-02-19 * \license GNU GPLv2 or above * * Provides support for a mute-groups configuration file. */ #include /* std::ofstream and ifstream */ #include "cfg/configfile.hpp" /* seq66::configfile class */ #include "midi/patches.hpp" /* seq66::patches */ namespace seq66 { /** * Provides a file for reading and writing the application's mute-group * configuration file. The settings that are passed around are provided * or used by the performer class. */ class patchesfile final : public configfile { private: /** * Holds a reference to the patches object to be acted upon by this * class. However, currently we access the patches class solely * via public free functions. See the patches modules. * * patches & m_note_mapper; */ public: patchesfile ( const std::string & filename, rcsettings & rcs ); patchesfile () = delete; patchesfile (const patchesfile &) = delete; patchesfile & operator = (const patchesfile &) = delete; virtual ~patchesfile (); virtual bool parse () override; virtual bool write () override; bool parse_stream (std::ifstream & file); bool write_stream (std::ofstream & file); private: bool write_map_entries (std::ofstream & file) const; }; // class patchesfile /* * Free functions */ extern bool open_patches (const std::string & source); extern bool save_patches (const std::string & destination); extern bool save_patches ( const std::string & source, const std::string & destination ); #if 0 extern bool copy_patches ( patches & pl, const std::string & source, const std::string & destination ); #endif } // namespace seq66 #endif // SEQ66_PATCHESFILE_HPP /* * patchesfile.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/playlistfile.hpp ================================================ #if ! defined SEQ66_PLAYLISTFILE_HPP #define SEQ66_PLAYLISTFILE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file playlistfile.hpp * * This module declares/defines the class for playlist file I/O. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-09-19 * \updates 2021-06-09 * \license GNU GPLv2 or above * */ #include "cfg/configfile.hpp" namespace seq66 { class playlist; /** * Provides a file for reading and writing the application' main * configuration file. */ class playlistfile final : public configfile { /** * The playlist object to be filled or read from. */ playlist & m_play_list; /** * If true, write the lists/songs to standard output. This is * useful to test the CLI/daemon version of Seq66. */ bool m_show_on_stdout; public: /* * Only the friend class performer is able to call this function. */ playlistfile ( const std::string & filename, playlist & pl, rcsettings & rcs, bool show_on_stdout = false ); playlistfile () = delete; playlistfile (const playlistfile &) = delete; playlistfile & operator = (const playlistfile &) = delete; /* * WTF? * playlistfile (playlistfile &&) = default; playlistfile & operator = (playlistfile &&) = default; */ virtual ~playlistfile (); // how to hide this??? virtual bool parse (); virtual bool write (); void clear (); bool open (bool verify_it = true); private: playlist & play_list() { return m_play_list; } bool set_error_message (const std::string & additional); bool scan_song_file (int & song_number, std::string & song_file); }; // class playlistfile /* * Free functions for working with play-list files. */ extern bool open_playlist ( playlist & pl, const std::string & source, bool show_on_stdout = false ); extern bool save_playlist ( playlist & pl, const std::string & destfile ); extern bool save_playlist ( playlist & pl, const std::string & source, const std::string & destination ); extern bool copy_playlist_songs ( playlist & pl, const std::string & source, const std::string & destination ); } // namespace seq66 #endif // SEQ66_PLAYLISTFILE_HPP /* * playlistfile.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/rcfile.hpp ================================================ #if ! defined SEQ66_RCFILE_HPP #define SEQ66_RCFILE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file rcfile.hpp * * This module declares/defines the base class for managing the qseq66.rc * configuration file. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2025-05-25 * \license GNU GPLv2 or above * * The ~/.seq66rc or ~/.config/seq66.rc files are referred to as the "rc" * files. */ #include "cfg/configfile.hpp" namespace seq66 { /** * Provides a file for reading and writing the application' main * configuration file. The settings that are passed around are provided * or used by the performer class. */ class rcfile final : public configfile { private: /* * Currently no additional members. */ public: rcfile () = delete; rcfile (const std::string & name, rcsettings & rcs); rcfile (const rcfile &) = delete; rcfile & operator = (const rcfile &) = delete; virtual ~rcfile () = default; virtual bool parse () override; virtual bool write () override; bool parse_midi_control_section (const std::string & fname); bool get_usr_file (); }; // class rcfile /* * Free functions. */ extern bool write_rc_file (const std::string & filebase); #if defined SEQ66_KEEP_RC_FILE_LIST extern bool delete_configuration ( const std::string & path, const std::string & basename ); extern bool copy_configuration ( const std::string & source, const std::string & basename, const std::string & destination ); #endif // defined SEQ66_KEEP_RC_FILE_LIST } // namespace seq66 #endif // SEQ66_RCFILE_HPP /* * rcfile.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/rcsettings.hpp ================================================ #if ! defined SEQ66_RCSETTINGS_HPP #define SEQ66_RCSETTINGS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file rcsettings.hpp * * This module declares/defines a settings class that is also exposed * globally in this application. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-09-22 * \updates 2026-04-17 * \license GNU GPLv2 or above * * This collection of variables describes the options of the application, * accessible from the command-line or from the 'rc' file. */ #include /* std::string class */ #include "cfg/basesettings.hpp" /* seq66::basesettings class */ #include "cfg/recent.hpp" /* seq66::recent class */ #include "ctrl/keycontainer.hpp" /* seq66::keycontainer class */ #include "ctrl/midicontrolin.hpp" /* seq66::midicontrolin class */ #include "ctrl/midicontrolout.hpp" /* seq66::midicontrolout class */ #include "play/clockslist.hpp" /* list of seq66::e_clock settings */ #include "play/inputslist.hpp" /* list of boolean input settings */ #include "play/metro.hpp" /* seq66::metrosettings class */ #include "play/mutegroups.hpp" /* map of seqq66::mutes stanzas */ #include "util/named_bools.hpp" /* map of booleans keyed by strings */ /** * Keep a list of the full file-specifications of each of the configureation * files that can be set up in the 'rc' file. We can eliminate the dependency * on configuration file-extensions in copying and deleting a configuration. */ #if defined SEQ66_KEEP_RC_FILE_LIST #include /* std::map class */ #endif namespace seq66 { /** * This high-priority value is used if the --priority option is specified. * Needs more testing, we really haven't needed it yet. */ static const int c_thread_priority = 10; /** * These control sizes. We'll try changing them and see what happens. * Increasing these value spreads out the pattern grids a little bit and * makes the Patterns panel slightly bigger. Seems like it would be * useful to make these values user-configurable. */ /** * The number of default virtual ALSA input busses supported in the * manual-ports mode. This value used to implicitly be 1, but it would be * useful to allow a few more. Now expanded per user request. Let the user * beware! See issue #42. */ const int c_input_buss_max = 48; const int c_input_buss_default = 4; /** * The number of ALSA I/O busses supported. See mastermidibus::init(). * Currently, this is also the default number of "manual" (virtual) output * ports created in the manual-ports mode. Now expanded per user request. * Let the user beware! See issue #42. */ const int c_output_buss_max = 48; const int c_output_buss_default = 8; /** * Maximum number of groups that can be supported. Basically, the number of * groups set in the 'rc' file. 32 groups can be filled. This is a permanent * maximum because we really can't support more than 32 keystrokes to support * selecting a mute-group. */ const int c_max_groups = 32; /** * Maximum number of screen sets that can be supported. Basically, the number * of times the Patterns Panel can be filled. Up to 32 sets can be created. * This is a permanent maximum because we really can't support more than 32 * keystrokes to support selecting a screenset. */ const int c_max_sets = 32; /** * Default value of number of slot toggle keys (shortcut keys) that can be * defined. Even if we end up adding more slots to a set, this would be about * the maximum number of keys we could really support. Maximum number of set * keys that can be supported. 32 keys can be assigned in the Options / * Keyboard tab and 'rc' file. This value applies to the "[keyboard-group]" * and "[keyboard-control]" sections. */ const int c_max_set_keys = 32; /** * Indicates whether Seq66 or another program is the JACK timebase master. * * \var none * JACK transport is not being used. * * \var slave * An external program is timebase master and we disregard all local * tempo information. Instead, we use onl the BPM provided by JACK. * * \var master * Whether by force or conditionally, this program is JACK master. * * \var conditional * This value is just for requesting conditional master in the 'rc' file. */ enum class timebase { none, slave, master, conditional }; /** * We need to offer some options for handling running-status issues in * some MIDI files. See the midifile class. * * \var recover * Try to recover the running-status value. * * \var skip * Skip the rest of the track. * * \var proceed * Allow running status errors to cascade. * * \var abort * Stop processing the rest of the tracks. */ enum class rsaction { recover, /* Try to recover the running-status value. */ skip, /* Skip the rest of the track. */ proceed, /* Allow running status errors to cascade. */ abort /* Stop processing the rest of the tracks. */ }; /** * This class contains "global" options that can be read from the 'rc' file * (class rcfile) and its related "mutes" and "ctrl" files. */ class rcsettings final : public basesettings { public: /** * Provides mutually-exclusive codes for the mouse-handling used by the * application. Moved here from the globals.h module. The fruity mode * will probably never be supported in seq66, though. */ enum class interaction { seq24, /**< Use the normal mouse interactions. */ fruity, /**< The "fruity" mouse interactions. To do. */ max /**< Keep this last... a size value. */ }; /** * Experimental to change how set changes work in regard to muting/arming * of sequences in a set. */ enum class setsmode { normal, /**< Set change mutes current, loads new set. */ autoarm, /**< Mute current set, load and unmute new set. */ additive, /**< Keep current set armed when changing sets. */ allsets, /**< Arm all sets at once. */ max /**< Keep this last... a size value. */ }; #if defined SEQ66_KEEP_RC_FILE_LIST /** * Provides a map of file-specification strings keyed by the type of * configuration file: * * - ctrl * - drums (also covers .notemap, the same kind of configuration) * - mutes * - palette * - patches * - playlist * - qss * - rc * - usr */ using files = std::map; /** * Holds a map of full file specifications, for use in copying a * configuration without caring about the file-extension. */ files m_config_files; #endif private: /** * The list of output clocks. */ clockslist m_clocks; /** * The list of input bus statuses. */ inputslist m_inputs; /** * Settings for the metronome. */ metrosettings m_metro_settings; /** * Holds the saving type on behalf of the mutegroups, which is now * owned by performer. */ mutegroups::saving m_mute_group_save; /** * Holds the key-container. */ keycontainer m_keycontainer; /** * New. If true, leave empty (inactive) MIDI control entries out of the * "[midi-control-settings]" container. This has the side effect of * causing empty entries to not be written to the 'ctrl' file, * potentially confusing. However, it can reduce the size of the control * container dramatically, saving look-up time and memory. */ bool m_drop_empty_in_controls; /** * Provides a way to pick which input buss will be used as the MIDI * controller device. */ bussbyte m_midi_control_buss; /** * Holds all of the MIDI controls stanzas, even one that are inactive. * This is necessary for being able to write out a full section of * keystroke controls and MIDI controls. */ midicontrolin m_midi_control_in; /** * Holds the MIDI control out stanzas. */ midicontrolout m_midi_control_out; /** * Provides the Song Position, in 16th notes, at which MIDI clocking will * begin if a MIDI buss is set the the MIDI Clock Mode setting. * * Held for midibase::set_clock_mod(). */ bool m_clock_mod; bool m_verbose; /**< Console message showing setting. */ bool m_quiet; /**< Disables startup error prompts. */ bool m_investigate; /**< An option for the test of the day. */ std::string m_session_tag; /**< Picks an alternate configuration. */ /** * A replacement for m_auto_option_save and all "save" options except for * MIDI files. We add another flag that is set to true only when * the 'rc' file is found to not exist. */ bool m_first_run_in_progress; named_bools m_save_list; bool m_save_old_triggers; /**< Save c_triggers_ex, no transpose. */ bool m_save_old_mutes; /**< Save mutes as bytes, not longs. */ bool m_allow_mod4_mode; /**< Allow Mod4 to hold drawing mode. */ bool m_allow_snap_split; /**< Allow snap-split of a trigger. */ bool m_allow_click_edit; /**< Allow double-click edit pattern. */ bool m_show_midi; /**< Show MIDI events to console. */ bool m_priority; /**< Run at high priority (Linux only). */ int m_thread_priority; /**< The desired priority (Linux only). */ bool m_pass_sysex; /**< Pass SysEx to outputs, not ready. */ bool m_with_jack_transport; /**< Enable synchrony with JACK. */ bool m_with_jack_master; /**< Serve as a JACK transport Master. */ bool m_with_jack_master_cond; /**< Serve as JACK Master if possible. */ bool m_with_jack_midi; /**< Use JACK MIDI. */ bool m_with_alsa_midi; /**< Use ALSA MIDI. */ bool m_jack_auto_connect; /**< Connect JACK ports in normal mode. */ bool m_jack_use_offset; /**< Try to calculate output offset. */ int m_jack_buffer_size; /**< The desired power-of-2 size, or 0. */ sequence::playback m_song_start_mode; /**< Song mode versus Live mode. */ bool m_song_start_is_auto; /**< True if "auto" read from 'rc'. */ bool m_record_by_buss; /**< Record into sequence w/input-buss. */ bool m_record_by_channel; /**< Record into sequence with channel. */ bool m_manual_ports; /**< [manual-ports] setting. */ bool m_manual_auto_enable; /**< [manual-port] auto-enable. */ int m_manual_port_count; /**< [manual-ports] output port count. */ int m_manual_in_port_count; /**< [manual-ports] input port count. */ bool m_reveal_ports; /**< [reveal-ports] setting. */ bool m_init_disabled_ports; /**< A new test option. EXPERIMENTAL. */ bool m_print_keys; /**< Show hot-key in main window slot. */ interaction m_interaction_method; /**< Interaction method: no support. */ setsmode m_sets_mode; /**< How to handle set changes. */ portname m_port_naming; /**< How to display port names. */ /** * Provides the name of current MIDI file. Under normal usage, it is the * full file specification, including the path to the file. Under session * management, it is the base name (e.g. "song.midi") of the file with * the new m_midi_filepath variable prepended to the base name. */ std::string m_midi_filename; /** * Provides the base name for MIDI files. This value is meant to be used * only under session management, where all files must be read and written * from the same non-standard (get the pun?) directory. It will be empty * under normal operation. */ std::string m_midi_filepath; /** * Indicates what to do with running-status irregularities. */ rsaction m_running_status_action; /** * Holds the JACK UUID value that makes this JACK connection unique. */ std::string m_jack_session_uuid; /** * Indicates if the JACK session callback was invoked. */ bool m_jack_session_active; /** * Holds the directory from which the last MIDI file was opened (or * saved). */ std::string m_last_used_dir; /** * Holds the current 'rc' and 'usr' configuration base directory. This * value is ".config/seq66" by default. For usage, it is normally * expanded to a full path. * * For NSM usage, it is the full path returned by the NSM daemon. */ std::string m_session_directory; /** * An optional appendage to the base configuration directory. It is * appended to the default session/configuration path. The setter, * config_subdirectory, is meant to work only once. It is set only * by the --home option. The boolean makes sure the appending is done only * once (due to processing command-line options multiple times, :-(.) */ mutable bool m_config_subdirectory_set; std::string m_config_subdirectory; /** * Holds the current 'rc' configuration filename. This value is * "qseq66.rc" by default, and is always a basename. */ std::string m_config_filename; /** * The full expanded path to the configuration directory. This value is * created, by default, by concatenating $HOME and ".config/seq66". * However it can be reset completely by the full_config_directory() * function to whatever the user needs (e.g. for usage with the Non/New * Session Manager, RaySession, and Agordejo). */ mutable std::string m_full_config_directory; /** * Indicates if the 'usr' file is actually to be used. * Useful for temporarily disabling a radically modified file. */ bool m_user_file_active; /** * Holds the current 'usr' configuration filename. This value is * "qseq66.usr" by default. */ std::string m_user_filename; /** * Indicates if the user wants to use a [midi-control] section from a * separate file, for the convenience of changing the MIDI control setup * without a lot of editing. This value is now permanently set to true. * * bool m_use_midi_control_file; */ /** * A new flag for indicating if MIDI Control I/O is to be active. * Useful for temporarily disabling a 'ctrl' file. */ bool m_midi_control_active; /** * The base name of the MIDI control file, if applicable. This file is * located only in the specific 'rc'/'usr' HOME directory, * m_session_directory. */ std::string m_midi_control_filename; /** * Indicates if the user wants to use a [mute-group] section from a * separate file, for the convenience of changing the setup * without a lot of editing. This value is now permanently * set to true. A user with an old setup will have to adapt manually if * necessary. * * bool m_use_mute_group_file; */ /** * Indicates if the mute-group file is actually to be used. * Useful for enabling/disabling a 'mutes' file as opposed to * using the mutes stored in a Seq66 file. */ bool m_mute_group_file_active; /** * The base name of the mute-group file, if applicable. This file is * located only in the specific 'rc'/'usr' HOME directory, * m_session_directory. */ std::string m_mute_group_filename; /** * Indicates if the user wants to use the play-list stored in the 'rc' * file. This value is stored as well. It is cleared if there was a * problem such as the play-list file-name not existing. */ bool m_playlist_active; /** * Provides the full name of a play-list file, such as "tunes.playlist" * or "/home/dude/.config/seq66/tunes.playlist". * * It is used only if playlist mode is active. This file is always * located in the configuration directory (which can be modified from the * command-line). */ std::string m_playlist_filename; /** * Holds the base directory of all the MIDI files in all the playlists. * Normally useful when MIDI files are in an NSM session directory or a * directory separate from where Seq66 is run. Normally empty. */ std::string m_playlist_midi_base; /** * Indicates if the user wants to use the note-mapper stored in the 'rc' * file. This value is stored as well. */ bool m_notemap_active; /** * Provides the name of a note-mapping file to use. This is a feature * adapted and modified from our "midicvt" project. */ std::string m_notemap_filename; /** * Indicates if the user wants to use the patches stored in the 'patches' * file. This value is stored as well. */ bool m_patches_active; /** * Provides the name of a patches file to use. This is a feature * adapted and modified from our "midicvt" project. */ std::string m_patches_filename; /** * Indicates if the user wants to use the palette file stored in the 'rc' * file value. */ bool m_palette_active; /** * Provides the base name of a palette file to use. */ std::string m_palette_filename; /** * Indicates if the style-sheet will be used. Move from 'usr' to the 'rc' * file. */ bool m_style_sheet_active; /** * Provides the name of an optional Qt style-sheet, located in the active * Seq66 configuration directory. By default, this name is empty and not * used. It present, it contains the base name of the sheet (e.g. * "qseq66.qss". It can also contain a path, in order to support a * universal style-sheet. Move from 'usr' to the 'rc' file. */ std::string m_style_sheet_filename; /** * Holds the application name, e.g. "seq66", "qpseq66", "seq66cli", or, * most commonly, "qseq66". This is a constant, set to SEQ66_APP_NAME, * but obtained via the seq_app_name() function. Do not confuse it with * the client name, which defaults to "seq66" no matter what the * application name. */ std::string m_application_name; /** * New value to allow the user to violate the MIDI specification and use a * track other than the first track (#0) as the MIDI tempo track. * Holds the number of the official tempo track for this performance. * Normally 0, it can be changed to any value from 1 to 1023 via the * tempo-track-number setting in the 'rc' file, and that can be overriden * by the c_tempo_track SeqSpec possibly present in the song's MIDI file. */ int m_tempo_track_number; /** * Holds a few MIDI file-names most recently used. Although this is a * vector, we do not let it grow past SEQ66_RECENT_FILES_MAX. * New feature from Oli Kester's kepler34 project. */ recent m_recent_files; /** * If true, this flag indicates to open the most recent MIDI file, which is * the first in the list. This flag is set and used only at startup time, * after the "session" is created. */ bool m_load_most_recent; /** * If true, show the full directory path in the most-recent-file list, in * order to distinguish identical tunes in different sub-directories. * Defaults to false. */ bool m_full_recent_paths; /** * Indicates that the "[midi-input-map]" and "[midi-clock-map]" sections * were found, which indicates they should not be recreated again, * regardless of the status of "portmaps active". */ bool m_portmaps_present; /** * Indicates if both the input and output port-maps are active. Needed as * a convenience for callers that need to know the statuses of both * ports. */ bool m_portmaps_active; public: rcsettings (); rcsettings (const rcsettings & rhs) = default; rcsettings & operator = (const rcsettings & rhs) = default; virtual ~rcsettings () = default; std::string no_name () const { return std::string("No name"); } std::string make_config_filespec ( const std::string & base, const std::string & ext = "" ) const; std::string config_filespec () const; std::string config_filespec (const std::string & altname) const; std::string user_filespec () const; std::string user_filespec (const std::string & altname) const; std::string midi_control_filespec () const; std::string mute_group_filespec () const; std::string playlist_filespec () const; void clear_playlist (bool disable = false); std::string notemap_filespec () const; std::string patches_filespec () const; std::string palette_filespec () const; std::string style_sheet_filespec () const; virtual void set_defaults () override; #if defined SEQ66_KEEP_RC_FILE_LIST const files & config_files () const { return m_config_files; } bool add_config_filespec ( const std::string & key, const std::string & fspec ); #endif const clockslist & clocks () const { return m_clocks; } clockslist & clocks () { return m_clocks; } const inputslist & inputs () const { return m_inputs; } inputslist & inputs () { return m_inputs; } metrosettings & metro_settings () { return m_metro_settings; } const metrosettings & metro_settings () const { return m_metro_settings; } mutegroups::saving mute_group_save () const { return m_mute_group_save; } const keycontainer & key_controls () const { return m_keycontainer; } keycontainer & key_controls () { return m_keycontainer; } bool drop_empty_in_controls () const { return m_drop_empty_in_controls; } bussbyte midi_control_buss () const { return m_midi_control_buss; } const midicontrolin & midi_control_in () const { return m_midi_control_in; } midicontrolin & midi_control_in () { return m_midi_control_in; } const midicontrolout & midi_control_out () const { return m_midi_control_out; } midicontrolout & midi_control_out () { return m_midi_control_out; } int get_clock_mod () const { return m_clock_mod; } bool verbose () const { return m_verbose; } bool quiet () const { return m_quiet; } bool investigate () const { return m_investigate; } bool investigate_disabled () const { return false; } const std::string & session_tag () const { return m_session_tag; } bool alt_session () const { return ! m_session_tag.empty(); } bool first_run_in_progress () const { return m_first_run_in_progress; } bool auto_options_save () const; bool auto_rc_save () const { return m_save_list.get("rc"); } bool auto_usr_save () const { return m_save_list.get("usr"); } bool auto_mutes_save () const { return m_save_list.get("mutes"); } bool auto_playlist_save () const { return m_save_list.get("playlist"); } /** * Actually, since we cannot edit keystroke/MIDI control, drums * (note-mapping), and style-sheets in the application, these functions * are moot. Although the palettes can be saved by a button in * the session preferences tab. */ bool auto_ctrl_save () const { return m_save_list.get("ctrl"); } bool auto_drums_save () const { return m_save_list.get("drums"); } /** * Unused. Style-sheet not used by default, so not saved, even at * first-start. Kept for :-) consistency. */ bool auto_qss_save () const { return m_save_list.get("qss"); } bool auto_palette_save () const { return m_save_list.get("palette"); } bool save_old_triggers () const { return m_save_old_triggers; } bool save_old_mutes () const { return m_save_old_mutes; } bool allow_mod4_mode () const { return m_allow_mod4_mode; } bool allow_snap_split () const { return m_allow_snap_split; } bool allow_click_edit () const { return m_allow_click_edit; } bool show_midi () const { return m_show_midi; } bool priority () const { return m_priority; } int thread_priority () const { return m_thread_priority; } bool pass_sysex () const { return m_pass_sysex; } bool with_jack_transport () const { return m_with_jack_transport; } bool with_jack_master () const { return m_with_jack_master; } bool with_jack_master_cond () const { return m_with_jack_master_cond; } bool with_jack_midi () const { return m_with_jack_midi; } bool with_alsa_midi () const { return m_with_alsa_midi; } bool with_port_midi () const { #if defined SEQ66_PORTMIDI_SUPPORT return true; #else return false; #endif } bool sequence_lookup_support () const; bool jack_auto_connect () const { return m_jack_auto_connect; } bool jack_use_offset () const { return m_jack_use_offset; } int jack_buffer_size () const { return m_jack_buffer_size; } bool song_start_mode () const { return m_song_start_mode == sequence::playback::song; } sequence::playback get_song_start_mode () const { return m_song_start_mode; } /** * Was returning m_song_start_mode == sequence::playback::automatic, but * this conflates run-time mode with desired initial mode. */ bool song_start_auto () const { return m_song_start_is_auto; } std::string song_mode_string () const; void set_jack_transport (const std::string & value); void with_jack_transport (bool flag) { m_with_jack_transport = flag; } void with_jack_master (bool flag) { m_with_jack_master = flag; if (flag) m_with_jack_transport = true; } void with_jack_master_cond (bool flag) { m_with_jack_master_cond = flag; if (flag) m_with_jack_transport = true; } void with_jack_midi (bool flag) { m_with_jack_midi = flag; } void with_alsa_midi (bool flag) { m_with_alsa_midi = flag; } void jack_auto_connect (bool flag) { m_jack_auto_connect = flag; } void jack_use_offset (bool flag) { m_jack_use_offset = flag; } /* * This check is the same as is_power_of_2() in the calculations module. */ void jack_buffer_size (int sz) { if (((sz != 0) && (! (sz & (sz - 1)))) || (sz == 0)) m_jack_buffer_size = sz; } /** * \getter m_with_jack_transport m_with_jack_master, and * m_with_jack_master_cond, to save client code some trouble. Do not * confuse these original options with the new "no JACK MIDI" option. */ bool with_jack () const { return ( m_with_jack_transport || m_with_jack_master || m_with_jack_master_cond ); } bool record_by_buss () const { return m_record_by_buss; } bool record_by_channel () const { return m_record_by_channel; } bool manual_ports () const { return m_manual_ports; } bool manual_auto_enable () const { return m_manual_auto_enable; } int manual_port_count () const { return m_manual_port_count; } int manual_in_port_count () const { return m_manual_in_port_count; } bool reveal_ports () const { return m_reveal_ports; } bool init_disabled_ports () const { return m_init_disabled_ports; } bool print_keys () const { return m_print_keys; } /* * Currently not supported in Seq66. */ interaction interaction_method () const { return m_interaction_method; } setsmode sets_mode () const { return m_sets_mode; } bool is_setsmode_normal () const { return m_sets_mode == setsmode::normal; } bool is_setsmode_autoarm () const { return m_sets_mode == setsmode::autoarm; } bool is_setsmode_additive () const { return m_sets_mode == setsmode::additive; } bool is_setsmode_allsets () const { return m_sets_mode == setsmode::allsets; } bool is_setsmode_clear () const { return m_sets_mode == setsmode::normal || m_sets_mode == setsmode::autoarm; } std::string sets_mode_string () const; std::string sets_mode_string (setsmode v) const; portname port_naming () const { return m_port_naming; } std::string port_naming_string () const; std::string port_naming_string (portname v) const; const std::string & midi_filename () const { return m_midi_filename; } void midi_filename (const std::string & value) { m_midi_filename = value; } void clear_midi_filename () { m_midi_filename.clear(); } void session_midi_filename (const std::string & value); const std::string & midi_filepath () const { return m_midi_filepath; } void midi_filepath (const std::string & value) { m_midi_filepath = value; } void running_status_action (const std::string & value); std::string running_status_action_name () const; rsaction running_status_action () const { return m_running_status_action; } const std::string & jack_session () const { return m_jack_session_uuid; } bool jack_session_active () const { return m_jack_session_active; } const std::string & last_used_dir () const { return m_last_used_dir; } void last_used_dir (const std::string & value, bool userchange = true); bool add_recent_file (const std::string & filename); bool append_recent_file (const std::string & filename) { return m_recent_files.append(filename); } bool remove_recent_file (const std::string & filename) { return m_recent_files.remove(filename); } void clear_recent_files () { m_recent_files.clear(); } bool load_most_recent () const { return m_load_most_recent && ! playlist_active(); } bool full_recent_paths () const { return m_full_recent_paths; } bool portmaps_present () const { return m_portmaps_present; } bool portmaps_active () const { return m_portmaps_active; } const std::string & session_directory () const { return m_session_directory; } void set_config_files (const std::string & value); bool has_home_config_path (const std::string & name); std::string default_session_path () const; std::string home_config_directory () const; std::string trim_home_directory (const std::string & filepath); const std::string & config_filename () const { return m_config_filename; } bool playlist_active () const { return m_playlist_active; } bool notemap_active () const { return m_notemap_active; } bool patches_active () const { return m_patches_active; } bool palette_active () const { return m_palette_active; } bool style_sheet_active () const { return m_style_sheet_active; } const std::string & style_sheet_filename () const { return m_style_sheet_filename; } const std::string & playlist_filename () const { return m_playlist_filename; } const std::string & midi_base_directory () const { return m_playlist_midi_base; } const std::string & notemap_filename () const { return m_notemap_filename; } const std::string & patches_filename () const { return m_patches_filename; } const std::string & palette_filename () const { return m_palette_filename; } bool user_file_active () const { return m_user_file_active; } const std::string & user_filename () const { return m_user_filename; } bool use_midi_control_file () const { return true; } bool midi_control_active () const { return m_midi_control_active; } const std::string & midi_control_filename () const { return m_midi_control_filename; } bool mute_group_file_active () const { return m_mute_group_file_active; } const std::string & mute_group_filename () const { return m_mute_group_filename; } bool use_mute_group_file () const { return true; } const std::string application_name () const { return m_application_name; } const std::string & app_client_name () const; void app_client_name (const std::string & n) const; int tempo_track_number () const { return m_tempo_track_number; } std::string recent_file (int index, bool shorten = true) const; int recent_file_count () const { return m_recent_files.count(); } int recent_file_max () const { return m_recent_files.maximum(); } public: void mute_group_save (mutegroups::saving ms) { m_mute_group_save = ms; } void drop_empty_in_controls (bool flag) { m_drop_empty_in_controls = flag; } void midi_control_buss (bussbyte b) { m_midi_control_buss = b; } /** * Set the clock mod to the given value, if legal. * * \param clockmod * If this value is not equal to 0, it is used to set the static * member m_clock_mod. */ void set_clock_mod (int clockmod) { if (clockmod != 0) m_clock_mod = clockmod; } void session_tag (const std::string & t) { m_session_tag = t; } void quiet (bool flag) { m_quiet = flag; } void verbose (bool flag); void investigate (bool flag); void set_imported_playlist ( const std::string & sourcepath, const std::string & midipath ); void first_run_in_progress (bool flag) { m_first_run_in_progress = flag; } void auto_rc_save (bool flag); void auto_usr_save (bool flag) { m_save_list.set("usr", flag); } void auto_mutes_save (bool flag) { m_save_list.set("mutes", flag); } void auto_playlist_save (bool flag) { m_save_list.set("playlist", flag); } void auto_ctrl_save (bool flag) { m_save_list.set("ctrl", flag); } /* * Used in smanager and set in qseditoptions. */ void auto_drums_save (bool flag) { m_save_list.set("drums", flag); } /* * Used in qt5nsmanager, but not set anywhere. */ void auto_palette_save (bool flag) { m_save_list.set("palette", flag); } /* * void auto_palette_save (bool flag) * { * m_save_list.set("palette", flag); * } */ void save_old_triggers (bool flag) { m_save_old_triggers = flag; } void save_old_mutes (bool flag) { m_save_old_mutes = flag; } void allow_mod4_mode (bool /*flag*/) { m_allow_mod4_mode = false; } void allow_snap_split (bool flag) { m_allow_snap_split = flag; } void allow_click_edit (bool flag) { m_allow_click_edit = flag; } void show_midi (bool flag) { m_show_midi = flag; } void priority (bool flag) { m_priority = flag; } void thread_priority (int p) { if (p == 0) p = c_thread_priority; m_thread_priority = p; } void pass_sysex (bool flag) { m_pass_sysex = flag; } void song_start_mode (bool flag) { m_song_start_mode = flag ? sequence::playback::song : sequence::playback::live ; } void song_start_mode_by_string (const std::string & s); void record_by_buss (bool flag); void record_by_channel (bool flag); void manual_ports (bool flag) { m_manual_ports = flag; } void manual_auto_enable (bool flag) { m_manual_auto_enable = flag; } void manual_port_count (int count) { if (count <= 0 || count > c_output_buss_max) count = c_output_buss_default; m_manual_port_count = count; } void default_manual_port_counts () { m_manual_port_count = c_output_buss_default; m_manual_in_port_count = c_input_buss_default; } void manual_in_port_count (int count) { if (count <= 0 || count > c_input_buss_max) count = c_input_buss_default; m_manual_in_port_count = count; } void reveal_ports (bool flag) { m_reveal_ports = flag; } void init_disabled_ports (bool flag) { m_init_disabled_ports = flag; } void print_keys (bool flag) { m_print_keys = flag; } void midi_control_active (bool flag) { m_midi_control_active = flag; } void midi_control_filename (const std::string & name); void mute_group_file_active (bool flag) { m_mute_group_file_active = flag; } void mute_group_filename (const std::string & name); void playlist_active (bool flag) { m_playlist_active = flag; } void notemap_active (bool flag) { m_notemap_active = flag; } void patches_active (bool flag) { m_patches_active = flag; } void palette_active (bool flag) { m_palette_active = flag; } void style_sheet_active (bool flag) { m_style_sheet_active = flag; } void midi_base_directory (const std::string & mbd) { m_playlist_midi_base = mbd; } void load_most_recent (bool f) { m_load_most_recent = f; } void full_recent_paths (bool f) { m_full_recent_paths = f; } void portmaps_present (bool f) { m_portmaps_present = f; } void portmaps_active (bool f) { m_portmaps_active = f; } bool interaction_method (int v) { return interaction_method(static_cast(v)); } void sets_mode (setsmode sm) { m_sets_mode = sm; } void sets_mode (const std::string & v); void port_naming (const std::string & v); /* * The setters for non-bool values, defined in the cpp file because * they do some heavier validation. */ void tempo_track_number (int track); bool interaction_method (interaction value); void jack_session (const std::string & uuid); void jack_session_activate () { m_jack_session_active = true; } void set_config_directory (const std::string & value); void full_config_directory (const std::string & value); void session_directory (const std::string & value); void config_subdirectory (const std::string & value); void config_filename (const std::string & value); void playlist_filename (const std::string & value); bool playlist_filename_checked (const std::string & value); void user_filename (const std::string & value); void notemap_filename (const std::string & value); void patches_filename (const std::string & value); void palette_filename (const std::string & value); void style_sheet_filename (const std::string & value); void create_config_names (const std::string & base = ""); void set_save_list (bool state); void disable_save_list (); void set_save (const std::string & name, bool value); std::string filespec_helper (const std::string & baseext) const; void home_config_directory (const std::string & hcd) { m_full_config_directory = hcd; } void user_file_active (bool flag) { m_user_file_active = flag; } private: std::string filename_base_fix ( const std::string & filename, const std::string & ext ) const; }; // class rcsettings } // namespace seq66 #endif // SEQ66_RCSETTINGS_HPP /* * rcsettings.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/recent.hpp ================================================ #if ! defined SEQ66_RECENT_HPP #define SEQ66_RECENT_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file recent.hpp * * This module declares/defines a container for "recent" entries. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-03-29 * \updates 2018-11-24 * \license GNU GPLv2 or above * */ #include #include namespace seq66 { /** * This class provides a standard container and operations for a recent-files * list. The container should, if possible, contain only unique strings... * no file-path should be included more than once. */ class recent { private: /** * Provide the type definition for the recent-files container. It is * currently a standard deque. A deque (pronounced like "deck") is an * irregular acronym of double-ended queue. Double-ended queues are * sequence containers with dynamic sizes that can be expanded or * contracted on both ends (either its front or its back), like a deck of * cards where the dirty dealer can deal from the bottom. */ using container = std::deque; private: /** * Holds the list of recent files. */ container m_recent_list; /** * Holds the constraint on the number of recent files. Usually a value * like 10. */ const int m_maximum_size; public: recent (); recent (const recent & source) = default; recent & operator = (const recent & source); ~recent (); void clear () { m_recent_list.clear(); } int count () const { return int(m_recent_list.size()); } int maximum () const { return m_maximum_size; } std::string get (int index) const; bool append (const std::string & item); bool add (const std::string & item); bool remove (const std::string & item); bool is_in_list (const std::string & path); }; // class recent } // namespace seq66 #endif // SEQ66_RECENT_HPP /* * recent.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/scales.hpp ================================================ #if ! defined SEQ66_SCALES_HPP #define SEQ66_SCALES_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file scales.hpp * * This module declares/defines the scales-related global variables, types, * and functions. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-23 * \updates 2026-01-07 * \license GNU GPLv2 or above * * These values were moved from the Seq64 globals module. Includes the * chord-generation data. * * Phrygian scales added by user WinkoErades. Thank you! */ #include #include /* holds the scale analyses results */ #include "midi/midibytes.hpp" /* seq66::midibytes */ #include "util/basic_macros.hpp" /* seq66::tokenization container */ namespace seq66 { class eventlist; /* forward reference */ /** * Provides the list of musical key signatures, using sharps. */ enum class keys { C, /* 0 */ Csharp, /* 1 */ Dflat = Csharp, D, /* 2 */ Dsharp, /* 3 */ Eflat = Dsharp, E, /* 4 */ F, /* 5 */ Fsharp, /* 6 */ Gflat = Fsharp, G, /* 7 */ Gsharp, /* 8 */ Aflat = Gsharp, A, /* 9 */ Asharp, /* 10 */ Bflat = Asharp, B, /* 11 */ max /* size of set */ }; /** * A manifest constant for the normal number of semitones in an * equally-tempered octave. */ const int c_octave_size { 12 }; /** * A constant for clarification of the value of zero, which, in the context * of a musical key, is the default key of C. */ const int c_key_of_C { static_cast(keys::C) }; /** * A constant for clarification of the value of zero, which, in the context * of a musical key, is the default key of C. */ const int c_key_of_max { static_cast(keys::max) }; /** * An inline function to test that an integer is a legal key-name index * value. */ inline bool legal_key (int k) { return k >= c_key_of_C && k < c_octave_size; } inline keys int_to_key (int k) { return legal_key(k) ? static_cast(k) : keys::C ; } inline int key_to_int (keys k) { return static_cast(k); } inline bool legal_note (int note) { return note >= 0 && note < 128; } /** * Corresponds to the small number of musical scales that the application * can handle. Scales can be shown in the piano roll as gray bars for * reference purposes. * * We've added three more scales; there are still a number of them that could * be fruitfully added to the list of scales. * * It would be good to offload this stuff into a new "scale" class. * * For now we peel off the "c_scale_" and let enum-class take care of the * scope. */ enum class scales { off, chromatic = off, major, minor, /* Natural Minor scale */ harmonic_minor, melodic_minor, /* Just the ascending version */ c_whole_tone, minor_blues, major_pentatonic, minor_pentatonic, phrygian, enigmatic, diminished, dorian, mixolydian, /* Same as descending melodic minor */ max /* a "maximum" or "size of set" value */ }; /** * Avoids a cast in order to use scales::max as an initializer. */ const int c_scales_off { static_cast(scales::off) }; /** * Avoids a cast in order to use scales::max as an array size. */ const int c_scales_max { static_cast(scales::max) }; /** * An inline function to test that an integer in a legal scale value. */ inline bool legal_scale (int s) { return s >= c_scales_off && s < c_scales_max; /* * return s >= scale_to_int(scales::off) && s < scale_to_int(scales::max); */ } inline scales int_to_scale (int s) { return legal_scale(s) ? static_cast(s) : scales::off ; } inline int scale_to_int (scales s) { return static_cast(s); } /** * Supported chords. */ enum class chords { none, major, majb5, minor, minb5, sus2, sus4, aug, augsus4, tri, sixth, sixthsus4, sixthadd9, m6, m6add9, seventh, seventhsus4, seventh_5, seventhb5, seventh_9, seventhb9, seventh_5_9, seventh_5b9, seventhb5b9, seventhadd11, seventhadd13, seventh_11, maj7, maj7b5, maj7_5, maj7_11, maj7add13, m7, m7b5, m7b9, m7add11, m7add13, mmaj7, mmaj7add11, mmaj7add13, max }; inline chords int_to_chord (int c) { return c >= 0 && c < static_cast(chords::max) ? static_cast(c) : chords::none ; } inline int chord_to_int (chords c) { return static_cast(c); } inline bool legal_chord (int s) { return s >= chord_to_int(chords::major) && s < chord_to_int(chords::max); } /** * Provides the number of chord values in each chord's specification * array. */ const int c_chord_number { 40 }; const int c_chord_size { 6 }; const int c_interval_size { 15 }; const int c_harmonic_size { 8 }; /** * Provides a short vector containing the chord values in each chord's * specification array. The valid values stop at -1. */ using chord_notes = std::vector; /* all init'd to size 6 */ /* * Free functions for scales. */ extern std::string musical_note_name (int n); extern std::string musical_key_name (keys k); extern std::string musical_scale_name (scales s); extern const char * interval_name_ptr (int interval); extern bool harmonic_number_valid (int number); extern const char * harmonic_interval_name_ptr (int interval); extern bool chord_number_valid (int number); extern const char * chord_name_ptr (int number); extern const chord_notes & chord_entry (int number); extern std::string chord_intervals (chords c); extern bool note_in_chord (chords chord, keys key, int note); extern bool scales_policy (scales s, int k); extern bool scales_policy (scales s, keys keyofpattern, int k); extern const int * scales_up (int scale, int key = 0); extern const int * scales_down (int scale, int key = 0); extern double midi_note_frequency (midibyte note); extern int analyze_notes ( const eventlist & evlist, std::vector & outkey, std::vector & outscale ); extern std::string key_signature_string (int sfcount, bool isminor); extern bool key_signature_bytes ( const std::string & keysigname, midibytes & keysigbytes ); extern bool note_name_translation ( const std::string & notename, int & notenumber, int & octavenumber, int & basenumber ); extern bool get_pitch_range ( const tokenization & values, int & lowest, int & highest ); } // namespace seq66 #endif // SEQ66_SCALES_HPP /* * scales.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/sessionfile.hpp ================================================ #if ! defined SEQ66_SESSIONFILE_HPP #define SEQ66_SESSIONFILE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file sessionfile.hpp * * This module declares/defines the base class for managing the special * read-only session.rc configuration file. * * \library seq66 application * \author Chris Ahlstrom * \date 2021-12-29 * \updates 2021-12-30 * \license GNU GPLv2 or above * */ #include "cfg/configfile.hpp" namespace seq66 { /** * Provides a file for reading a special session setup, usually for testing. */ class sessionfile final : public configfile { private: /** * Provides the tag section from which to get the values. If empty, then * the session file is disabled. The name is returned wrapped by square * brackets, for direct use in the scanning. */ std::string m_tag_name; public: sessionfile ( const std::string & filename, const std::string & tag, rcsettings & rcs ); sessionfile () = delete; sessionfile (const sessionfile &) = delete; sessionfile & operator = (const sessionfile &) = delete; virtual ~sessionfile () = default; virtual bool parse () override; virtual bool write () override { return false; } std::string tag_name () const; }; // class sessionfile } // namespace seq66 #endif // SEQ66_SESSIONFILE_HPP /* * sessionfile.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/settings.hpp ================================================ #if ! defined SEQ66_SETTINGS_HPP #define SEQ66_SETTINGS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file settings.hpp * * This module declares/defines just some of the global (gasp!) variables * and functions used in this application. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-05-17 * \updates 2025-07-17 * \license GNU GPLv2 or above * * A couple of universal helper functions remain as inline functions in the * module. The rest have been moved to the calculations module. */ #include "cfg/rcsettings.hpp" /* seq66::rcsettings, std::string */ #include "cfg/usrsettings.hpp" /* seq66::usrsettings, std::vector */ namespace seq66 { /** * Provides an encapsulation of editable combo-box support. The 0th item * in this combo list always contains the default or current value. */ class combolist { private: /** * The list of values. Starts out at size 1, with the first item being an * empty string that can be replaced with a default value. Items apart * from the first item can only be added, not changed or removed. caller. */ tokenization m_list_items; /** * If true, use the first item as a blank item, to be filled in later as * the default or current value. */ bool m_use_current; public: combolist (bool use_current = false); combolist (const tokenization & slist, bool use_current = false); combolist (const combolist &) = default; combolist & operator = (const combolist &) = default; ~combolist () = default; std::string at (int index) const; int ctoi (int index) const; bool valid (const std::string & target) const; int index (const std::string & target) const; int index (int value) const; void current (const std::string & s) const; /* tricky */ void current (int v) const; /* tricky */ std::string current () const { return m_list_items[0]; } void set (const std::string & s, int index = 0) const; int count () const { return int(m_list_items.size()); } bool use_current () const { return m_use_current; } void add (const std::string & s) { m_list_items.push_back(s); } void add (int v) { std::string s = std::to_string(v); add(s); } }; // class combolist /* * Returns a reference to the global rcsettings and usrsettings objects. * Why a function instead of direct variable access? Encapsulation. We are * then free to change the way "global" settings are accessed, without * changing client code. */ class rcsettings; class usrsettings; extern rcsettings & rc (); extern usrsettings & usr (); extern int choose_ppqn (int ppqn = (-1)); /* c_use_default_ppqn */ extern const tokenization & supported_ppqns (); extern const tokenization & jack_buffer_size_list (); extern const tokenization & measure_items (); extern const tokenization & beats_per_bar_items (); extern const tokenization & beatwidth_items (); extern const tokenization & snap_items (); extern const tokenization & perf_snap_items (); extern const tokenization & zoom_items (); extern int zoom_item (int i); extern const tokenization & expanded_zoom_items (); extern int expanded_zoom_item (int i); extern const tokenization & rec_vol_items (); extern const tokenization & rec_style_items (); extern void set_configuration_defaults (); extern bool ppqn_in_range (int ppqn); extern bool open_user_manual (); extern bool open_tutorial (); extern const tokenization & doc_folder_list (); extern const tokenization & tutorial_folder_list (); extern const tokenization & share_doc_folder_list ( const std::string & path_end = "" ); extern std::string open_share_doc_file ( const std::string & filename, const std::string & path_end = "" ); } // namespace seq66 #endif // SEQ66_SETTINGS_HPP /* * settings.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/userinstrument.hpp ================================================ #if ! defined SEQ66_USERINSTRUMENT_HPP #define SEQ66_USERINSTRUMENT_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file userinstrument.hpp * * This module declares/defines the user instrument section of the "user" * configuration file. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-24 * \updates 2021-01-21 * \license GNU GPLv2 or above * */ #include #include "util/basic_macros.hpp" /* seq66_platform_macros.h too */ namespace seq66 { /** * Provides the maximum number of instruments that can be defined in the * ~/.seq66usr or * ~/.config/seq66.rc * file. With a value of 64, this is more of a sanity-check than a * realistic number of instruments defined by a user. */ const int c_max_instruments = 64; /** * Manifest constant for the maximum value limit of a MIDI byte when used * to limit the size of an array. Here, it is the upper limit on the * number of MIDI controllers that can be supported. */ const int c_midi_controller_max = 128; /** * This structure corresponds to [user-instrument-N] * definitions in the ~/.seq66usr or * ~/.config/seq66.usr file. */ struct userinstrument_t { /** * Provides the name of the "instrument" being supported. Do not confuse * "instrument" with "program" here. An "instrument" is most likely * a hardware MIDI sound-box (though it could be a software synthesizer * as well. */ std::string instrument; /** * Provides a list of up to 128 controllers (e.g. "Modulation"). * If a controller isn't present, or if General MIDI is in force, * this name might be empty. */ std::string controllers[c_midi_controller_max]; /** * Provides a flag that indicates if each of up to 128 controller is * active and supported. If false, it might be an unsupported controller * or a General MIDI device. */ bool controllers_active[c_midi_controller_max]; }; /** * Provides data about the MIDI instruments, readable from the "user" * configuration file. Will later make the size adjustable, if it * makes sense to do so. */ class userinstrument { /** * Provides a validity flag, useful in returning a reference to a * bogus object for internal error-check. Callers should check * this flag via the is_valid() accessor before using this object. * This flag is set to true when any valid member assignment occurs * via a public setter call. However, setting an empty name for the * instrument member will render the object invalid. */ bool m_is_valid; /** * Provides the actual number of non-default controllers actually * set. Often, the "user" configuration file has only a few out of * the 128 assigned explicitly. */ int m_controller_count; /** * The instance of the structure that this class wraps. */ userinstrument_t m_instrument_def; public: userinstrument (const std::string & name = ""); userinstrument (const userinstrument & rhs); userinstrument & operator = (const userinstrument & rhs); bool is_valid () const { return m_is_valid; } void clear (); const std::string & name () const { return m_instrument_def.instrument; } /** * This function returns the number of active controllers. */ int controller_count () const { return m_controller_count; } /** * This function returns the maximum number of controllers, active or * inactive. Remember that the controller numbers for each MIDI * instrument range from 0 to 127 (MIDI_CONTROLLER_MAX-1). */ int controller_max () const { return c_midi_controller_max; } const std::string & controller_name (int c) const; // getter bool controller_active (int c) const; // getter bool set_controller // setter ( int c, const std::string & cname, bool isactive ); private: void set_name (const std::string & instname); void copy_definitions (const userinstrument & rhs); }; } // namespace seq66 #endif // SEQ66_USERINSTRUMENT_HPP /* * userinstrument.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/usermidibus.hpp ================================================ #if ! defined SEQ66_USERMIDIBUS_HPP #define SEQ66_USERMIDIBUS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file usermidibus.hpp * * This module declares/defines the user MIDI-buss section of the "user" * configuration file. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-24 * \updates 2018-11-24 * \license GNU GPLv2 or above * * This class replaces an global_usermidibus_definitions[] array element * with a wrapper class for better safety. */ #include #include "midi/midibytes.hpp" /* seq66::c_midichannel_max */ namespace seq66 { /** * This structure corresponds to [user-midi-bus-0] * definitions in the ~/.seq66usr ("user") file * ( ~/.config/seq66.usr in the latest * version of the application). */ struct usermidibus_t { /** * Provides the user's desired name for the MIDI bus. For example, * "2x2 A" for some kind of MIDI card or USB MIDI cable. If * manual-ports is enabled, this could be something like * "[0] seq66 0", and that is what should be shown in that case. */ std::string alias; /** * Provides an implicit list of MIDI channels from 0 to 15 (1 to 16) and * the "instrument" number assigned to each channel. Note that the * "instrument" is not a MIDI program number. Instead, it is the number * associated with a [user-instrument-definitions] section in the "user" * configuration file. */ int instrument[c_midichannel_max]; }; /** * Provides data about the MIDI busses, readable from the "user" * configuration file. Will later make the size adjustable, if it * makes sense to do so. * */ class usermidibus { /** * Provides a validity flag, useful in returning a reference to a * bogus object for internal error-check. Callers should check * this flag via the is_valid() accessor before using this object. * This flag is set to true when any valid member assignment occurs * via a public setter call. */ bool m_is_valid; /** * Provides the actual number of non-default buss channels actually * set. Often, the "user" configuration file has only a few out of * the 16 assigned explicitly. */ int m_channel_count; /** * The instance of the structure that this class wraps. */ usermidibus_t m_midi_bus_def; public: usermidibus (const std::string & name = ""); usermidibus (const usermidibus & rhs); usermidibus & operator = (const usermidibus & rhs); /** * \getter m_is_valid */ bool is_valid () const { return m_is_valid; } void clear (); /** * \getter m_midi_bus_def.alias (name of alias) */ const std::string & name () const { return m_midi_bus_def.alias; } /** * \getter m_channel_count * \return * This function returns the actual number of channels. This is * different from before, when the maximum number was always * returned. */ int channel_count () const { return m_channel_count; } /** * \setter m_channel_count */ void channel_count (int count) { m_channel_count = count; } /** * \getter c_midichannel_max * \return * Returns the maximum number of MIDI channels. * Remember that the instrument channels for each MIDI buss * range from 0 to 15 (c_midichannel_max - 1). */ int channel_max () const { return c_midichannel_max; } int instrument (int channel) const; // getter bool set_instrument (int channel, int instrum); // setter std::string instrument_name (int channel) const; private: /** * \setter m_midi_bus_def.alias (name of alias) * Also sets the validity flag according to the emptiness of the * name parameter. */ void set_name (const std::string & name) { m_midi_bus_def.alias = name; m_is_valid = ! name.empty(); } void copy_definitions (const usermidibus & rhs); }; } // namespace seq66 #endif // SEQ66_USERMIDIBUS_HPP /* * usermidibus.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/usrfile.hpp ================================================ #if ! defined SEQ66_USRFILE_HPP #define SEQ66_USRFILE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file usrfile.hpp * * This module declares/defines the base class for managing the user's * qseq66.usr configuration file. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-24 * \updates 2023-03-22 * \license GNU GPLv2 or above * */ #include #include "cfg/configfile.hpp" namespace seq66 { /** * A manifest constant for controlling the length of a line-reading array for * use as a destination in a sscanf() call while parsing a configuration file. * The value of 1024 should be more than enough, even given the parsing of long * file-specifications. But be careful!!! A line longer than this causes * std::ifstream::getline() to go off into the ozone. This variable is now * used only on the usrfile class. */ const int SEQ66_LINE_MAX = 1024; /* 132 is *not* enough for paths */ /** * Supports the user's ~/.config/seq66/seq66.usr * configuration file. */ class usrfile final : public configfile { public: usrfile (const std::string & name, rcsettings & rcs); usrfile () = delete; usrfile (const usrfile &) = delete; usrfile & operator = (const usrfile &) = delete; virtual ~usrfile () = default; virtual bool parse () override; virtual bool write () override; bool parse_daemonization (bool & startdaemon, std::string & logfile); private: void dump_setting_summary (); }; // class usrfile } // namespace seq66 #endif // SEQ66_USRFILE_HPP /* * usrfile.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/usrsettings.hpp ================================================ #if ! defined SEQ66_USRSETTINGS_HPP #define SEQ66_USRSETTINGS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file usrsettings.hpp * * This module declares/defines just some of the global (gasp!) variables * in this application. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-09-22 * \updates 2025-07-19 * \license GNU GPLv2 or above * * This module defines the following categories of "global" variables that * are good to collect in one place, especially for settings stored in the * 'usr' configuration file ( seq66.usr ): * * - The [user-midi-bus] settings, collected in the usermidibus * class. * - The [use-instrument] settings, collected in the usrinstrument * class. * - The [user-interface-settings] settings, a small collection of * variables that describe some facets of the "Patterns Panel" or * "Sequences Window", which is visually presented by the * Gtk::Window-derived class called mainwnd. These variables define * the limits and resolution of various MIDI-to-GUI and application * control parameters. * - The [user-midi-settings] settings, a collection of variables that * will replaced hard-wired global MIDI parameters with modifiable * parameters better suited to a range of MIDI files. * * The Patterns Panel contains an 8-by-4 grid of "pattern boxes" or * "sequence boxes". All of the patterns in this grid comprise what is * called a "set" (in the musical sense) or a "screen set". * * We want to be able to change these defaults. We will let you know when we * are finished, and what you can do with these variables. */ #include #include #include "cfg/basesettings.hpp" /* seq66::basesettings class */ #include "cfg/scales.hpp" /* seq66::legal_key() and scale() */ #include "cfg/userinstrument.hpp" /* seq24's version of MIDNAM :-) */ #include "cfg/usermidibus.hpp" /* ditto */ #include "midi/calculations.hpp" /* seq66::alteration enum class */ namespace seq66 { /** * Constant values. Taken from the eliminated app limits header file, and * redundantly defined in the qeditbase header file. Provides the minimum * zoom value, currently a constant. It represents the minimum number of * pixels per tick. * * Compare to c_minimum_zoom in the zoomer module. */ const int c_min_zoom = 1; /** * Provides the maximum zoom value, currently a constant. It's value was * 32, but is now 512, to allow for better presentation of high PPQN * valued sequences. Units are pixels per tick. * * Compare to c_maximum_zoom in the zoomer module. */ const int c_max_zoom = 1024; /** * Permanent storage for the baseline, default PPQN used by Seq24. * This value is necessary in order to keep user-interface elements * stable when different PPQNs are used. */ const int c_base_ppqn = 192; /* enshrined in SeqXX history */ /** * This value indicates to use the default value of PPQN and ignore (to some * extent) what value is specified in the MIDI file. Note that the default * default PPQN is given by the global ppqn (192) or, if the "--ppqn qn" * option is specified on the command-line or the "midi_ppqn" setting in the * "usr" file. * * However, if the "midi_ppqn" setting is 0, then the default PPQN is * whatever the MIDI file specifies. */ const int c_use_default_ppqn = (-1); /** * Use the PPQN from the loaded file, rather than converting to the active * default PPQN of the application. */ const int c_use_file_ppqn = 0; /** * Provides settings for tempo recording. Currently not used, though the * functionality of logging and recording tempo is in place. */ enum class recordtempo { log_event, on, off, max }; /** * Provides the supported loop recording modes. These values are used * by the seqedit class, which provides a button with a popup menu to * select one of these recording modes. These correspond to automation * slots record_overdub (merge), record_overwrite, record_expand, and * record_oneshot. * * Note that we show the label "Overdub" now for recordstyle::merge. * Also, in the 'usr' file, either "merge" or "overdub" can be used * for "record-style", but now "overdub" is what gets written. */ enum class recordstyle { merge, /**< Incoming events are merged into the loop. */ overwrite, /**< Incoming events overwrite the loop. */ expand, /**< Incoming events increase size of loop. */ oneshot, /**< Stop when length of loop is reached. */ oneshot_reset, /**< Reset pattern ticks & re-enable one-shot. */ max /**< Provides an illegal/length value. */ }; /** * These enumerations correspond to the automation slots: grid_loop, * grid_record, grid_copy, ... grid_double. */ enum class gridmode { loop, /**< Normal grid-slot mode. */ mutes, /**< Slot-click activates a mute group. */ record, /**< Use one of the available recording modes. */ copy, /**< Copy any pattern that is selected. */ paste, /**< Paste the copied pattern to selected slot. */ clear, /**< Clear all events in selected pattern slot. */ remove, /**< Delete the pattern from the selected slot. */ thru, /**< Set MIDI Thru for the selected pattern. */ solo, /**< Solo the selected pattern. */ cut, /**< Cut a pattern (copy and delete). */ double_length, /**< Double the length of the selected pattern. */ max /**< Provides an illegal/length value. */ }; /** * Provides an indication of how to show the piano-key labels in the pattern * editor. */ enum class showkeys { octave_letters, /**< Show only the octave letters for key note. */ even_letters, /**< Show every other note name. */ all_letters, /**< Show every note name (can get cramped!) */ even_numbers, /**< Show every other MIDI note number. */ all_numbers /**< Show every other MIDI note number. */ }; /** * Holds the current values of sequence settings and settings that can * modify the number of sequences and the configuration of the * user-interface. These settings will eventually be made part of the * 'usr' settings file. */ class usrsettings final : public basesettings { friend class cmdlineopts; /* access for parse_o_options() */ friend class midifile; /* allow access to midi_bpm_maximum() */ friend class qseditoptions; /* access for certain setter functions */ friend class usrfile; /* allow protected access to file parser */ private: /** * Provides bits to be set so that key command-line options are not later * modified by entries in the 'usr' file. */ enum option_bits { option_none = 0x0000, option_rows = 0x0001, option_columns = 0x0002, option_scale = 0x0004, option_daemon = 0x0008, option_log = 0x0010, option_buss = 0x0020, option_inverse = 0x0040, option_session_mgr = 0x0080, option_ppqn = 0x0100 }; /** * Indicates what, if any, session manager will be used. */ enum class session { none, /**< Normal user-controlled session. */ nsm, /**< Non Session Manager. */ jack, /**< JACK Session API. */ max /**< The usual illegal list terminator. */ }; /** * [user-midi-bus-definitions] * * Internal types for the container of usermidibus objects. * Sorry about the "confusion" about "bus" versus "buss". * See Google for arguments about it. */ using Busses = std::vector; /** * [user-instrument-definitions] * * Internal type for the container of userinstrument objects. */ using Instruments = std::vector; /** * Provides data about the MIDI busses, readable from the 'usr' * configuration file. Since this object is a vector, its size is * adjustable. */ Busses m_midi_buses; /** * Provides data about the MIDI instruments, readable from the 'usr' * configuration file. The size is adjustable, and grows as objects * are added. */ Instruments m_instruments; /** * [user-interface-settings] * * These are not labelled, but are present in the 'usr' configuration * file in the following order: * * -# mainwnd-rows * -# mainwnd-cols * -# zoom * -# global-seq-feature * -# progress-bar-thick (progress-bar-thickness) * -# window-redraw-rate-ms */ /** * Indicates if some settings were already made. See the setter and * getter for the enum option_bits list. */ int m_option_bits; /** * Number of rows in the Patterns Panel. The current value is 4, and if * changed, many other values depend on it. Together with * m_mainwnd_cols, this value fixes the patterns grid into a 4 x 8 set of * patterns known as a "screen set". We would like to be able to change * this value from 4 to 8, and maybe allow the values of 5, 6, and 7 as * well. But if we could just get 8 working, then well would Seq66 * deserve the 64 in its name, and it would also match the layouts of the * Launchpad series of controllers. */ int m_mainwnd_rows; /** * Number of columns in the Patterns Panel. The current value is 8, and * probably won't change, since other values depend on it and it is a * common grid size. Together with m_mainwnd_rows, this value fixes the * patterns grid into a 4 x 8 set of patterns known as a "screen set". */ int m_mainwnd_cols; /** * Experimental option to swap rows and columns. See the function * swap_coordinates(). This swap doesn't apply to the number of rows and * columns, but to whether incrementing the sequence number moves to the * next or othe next column. */ bool m_swap_coordinates; /** * Provide a scale factor to increase the size of the main window * and its internals. Should be limited from 1.0 to 3.0, probably. * Right now we allow 0.5 to 3.0 (c_window_scale_min to * c_window_scale_default). This value is used by the following * functions: * * - window_scale() * - window_scaled_up() * - window_scaled_down() * - window_is_scaled() * - scale_size() */ float m_window_scale; /** * A new item to allow scaling window width and height separately. If in * the legal range, this item will scale the height. Otherwise, the same * value of m_window_scale will be used for both dimensions. */ float m_window_scale_y; /** * These control sizes. We'll try changing them and see what * happens. Increasing these value spreads out the pattern grids a * little bit and makes the Patterns panel slightly bigger. Seems * like it would be useful to make these values user-configurable. */ int m_mainwnd_spacing; /* c_mainwnd_spacing = 2; try 4 or 6 instead */ /** * Provides the initial zoom value, in units of ticks per pixel. The * original default value was 2 ticks per pixel, but larger PPQN values * need higher values, and we will have to adapt the default zoom to the * PPQN value. Also, the zoom can never be zero, as it can appear as the * divisor in scaling equations. */ int m_base_zoom; /** * The amount of default timestamp jitter. This is the fraction of the * current snap. Examples: * * - The default snap of 1/16th and 192 PPQN translates to 48 * ticks. At the default zoom, this is 24 pixels. * - At a base PPQN of 1920, this is 480 ticks. * * A reasonable jitter would be a few pixels. A reasonable way to get * this is to use a fraction of a snap value, like 1/8 or 1/16. * This number is the denominator of such a fraction. */ int m_jitter_divisor; /** * The amount to use for randomization (of "amplitude"). Note that * various MIDI amplitudes range from 0 to 127. */ int m_randomization_amount; /** * If true, this value provide a bit of backward-compatibility with the * global key/scale/background-sequence persistence feature. In this * feature, applying one of these three changes to a sequence causes them * to also be applied to sequences that are subsequently opened for * editing. However, we improve on this feature by allowing the changes * to be saved in the global, proprietary part of the saved MIDI file. * * If false, the user can still save the key/scale/background-sequence * values with each individual sequence, so they can be different. * * This value will be true by default, unless changed in the 'usr' * configuration file. */ bool m_global_seq_feature_save; /** * Replaces seqedit::m_initial_scale as the repository for the scale to * apply when a sequence is loaded into the sequence editor. Its default * value is scales::off. Although this value is now stored in the * usrsettings class, it always comes from the currently loaded MIDI * file, if present. If m_global_seq_feature_save is true, this variable * is stored in the "proprietary" track at the end of the file, under the * control tag c_musicscale, and will be applied to any sequence that is * edited. If m_global_seq_feature_save is false, this variable is * stored, if used, in the meta-data for the sequence to which it * applies, and, again, is tagged with the control tag c_musicscale. */ int m_seqedit_scale; /** * Replaces seqedit::m_initial_key as the repository for the key to apply * when a sequence is loaded into the sequence editor. Its default value * is c_key_of_C. Although this value is now stored in the usrsettings * class, it always comes from the currently loaded MIDI file, if * present. If m_global_seq_feature_save is true, this variable is * stored in the "proprietary" track at the end of the file, under the * control tag c_musickey, and will be applied to any sequence that is * edited. If m_global_seq_feature_save is false, this variable is * stored, if used, in the meta-data for the sequence to which it * applies, and, again, is tagged with the control tag c_musickey. */ int m_seqedit_key; /** * The repository for the background sequence to apply when a sequence is * loaded into the sequence editor. Its default value is seq::limit(). * Although this value is now stored in the usrsettings class, it always * comes from the currently loaded MIDI file, if present. If * m_global_seq_feature_save is true, this variable is stored, if it has * a valid (but not "legal") value, in the "proprietary" track at the end * of the file, under the control tag c_backsequence, and will be applied * to any sequence that is edited. If m_global_seq_feature_save is * false, this variable is stored, if used, in the meta-data for the * sequence to which it applies, and, again, is tagged with the control * tag c_backsequence. */ int m_seqedit_bgsequence; /** * If set, makes progress bars thicker than 1 pixel... 2 pixels. * It isn't useful to support anything thicker. The default is now to * use 1 pixel. Also, this setting now applies to the progress box * itself. The default value is true. */ bool m_progress_bar_thick; /* * The new variable, thickness, is used regardless of the boolean * value. The default value is 2. */ int m_progress_bar_thickness; /** * Instead of a rectangular progress box, use an elliptical one. * An interesting look. */ bool m_progress_box_elliptical; /** * For the pattern and song windows, set the default status of * following progress (scrolling to the next section of the piano rolls). */ bool m_follow_progress; /** * We now offer a way to modify the grid-lines in the piano rolls. * If true (the default), then the measure lines are thick and the * beat lines are solid. Otherwise, the measure lines are thin * and the beat lines are dotted. This option can be set to false * (the default) if the grid lines are too glaring, or true. */ bool m_gridlines_thick; /** * If set, use an alternate, neo-inverse color palette. Not all colors * are reversed, though. */ bool m_inverse_colors; /** * Provides the text and background colors for timer values. * Lotta other apps use green-on-black. These are currently * values usable with HTML style-sheets. */ std::string m_time_fg_color; /* e.g. "green" */ std::string m_time_bg_color; /* e.g. "black" */ /** * If set, adjust some items (mainly icons) to a dark-theme. This * setting can also be made true by the same option in an active * palette file. This setting no long applies to "ui" items, that is, * the items drawn separately from the Qt them. See the "dark-ui" * option in the GUI palette file. */ bool m_dark_theme; /** * Provides the global setting for redraw rate of windows. Not all * windows use this yet. The default is 40 ms (c_redraw_ms, which is 25 * ms in Windows builds)), but some windows originally used 25 ms, so * beware of side-effects. */ int m_window_redraw_rate_ms; /** * Constants for the mainwnd class. The m_seqchars_x and * m_seqchars_y constants help define the "seqarea" size. These look * like the number of characters per line and the number of lines of * characters, in a pattern/sequence box. * * UNUSED! */ int m_seqchars_x; /* c_seqchars_x = 15 */ int m_seqchars_y; /* c_seqchars_y = 5 */ /* * [user-midi-settings] */ /** * If true (the default), the file is converted to SMF 1 (with a * free-channel track) when read. */ bool m_convert_to_smf_1; /** * Provides the default PPQN for the application. This PPQN is used when * creating a new MIDI file or when reading an existing file with the * m_use_file_ppqn value set. This value defaults to 192 (the legacy * Seq24 value). */ int m_default_ppqn; /** * Provides the universal PPQN setting for the duration of this session. * It is either the default PPQN or the MIDI file's PPQN. The default * value of this setting is 192 parts-per-quarter-note (PPQN). There is * still a lot of work to get a different PPQN to work properly in speed * of playback, scaling of the user interface, and other issues. Note * that this value can be changed by the --ppqn option, as well as in the * 'rc' file. */ int m_midi_ppqn; /* PPQN, parts per QN */ /** * If true, ignore Seq66's default PPQN value and use the file's PPQN, * leaving the file unscaled. */ bool m_use_file_ppqn; /** * Holds the PPQN read from the file, for use in file conversion if we're * not using the file's PPQN. */ int m_file_ppqn; /** * Provides the universal and unambiguous MIDI value for beats per * measure, also called "beats per bar" (BPB). This variable will * replace the global beats per measure. The default value of this * variable is 4. For external access, we will call this value "beats * per bar", abbreviate it "BPB", and use "bpb" in any accessor function * names. Now, although it applies to the whole session, we should be * able to continue seq66's tradition of allowing each sequence to have * its own time signature. Also, there are a number of places where the * number 4 appears and looks like it might be a hardwired BPB value, * either for MIDI purposes or for drawing the piano-roll grids. So we * might need a couple different versions of this variable. */ int m_midi_beats_per_measure; /* BPB, or beats per bar */ /** * Provides the minimum beats per minute, purely for providing the scale * for drawing the tempo. Defaults to 0. */ midibpm m_midi_bpm_minimum; /** * Provides the universal and unambiguous MIDI value for beats per minute * (BPM). This variable will replace the global beats per minute. The * default value of this variable is c_def_beats_per_minute (120). This * variable should apply to the whole session; there's probably no way to * support a diffent tempo for each sequence. But we shall see. For * external access, we will call this value "beats per minute", abbreviate * it "BPM", and use "bpm" in any accessor function names. */ midibpm m_midi_beats_per_minute; /* BPM, or beats per minute */ /** * Provides the maximum beats per minute, purely for providing the scale * for drawing the tempo. Defaults to 127. */ midibpm m_midi_bpm_maximum; /** * Provides the universal MIDI value for beats width (BW). This variable * will replace the global beat_width. The default value of this * variable is 4. Now, although it applies to the whole session, we * should be able to continue seq24's tradition of allowing each sequence * to have its own time signature. Also, there are a number of places * where the number 4 appears and looks like it might be a hardwired BW * value, either for MIDI purposes or for drawing the user-interface. So * we might need a couple different versions of this variable. For * external access, we will call this value "beat width", abbreviate it * "BW", and use "bw" in any accessor function names. */ int m_midi_beat_width; /* BW, or beat width */ /** * Provides a universal override of the buss number for all sequences, for * the purpose of convenience of of testing. This variable replaces the * global buss-override variable, and is set via the command-line option * --bus. */ bussbyte m_midi_buss_override; /* --bus n option */ /** * Sets the default velocity for note adding. The preserve-velocity * value (-1) preserves the velocity of incoming notes, so that nuances * in live playing can be preserved. The popup-menu for the "Vol" button * in the seqedit window shows this value as the "Free" menu entry. The * rest of the values in the menu show a few select velocities, but any * velocity from 0 to 127 can be entered here. Of course, 0 is not * recommended. */ short m_velocity_override; /** * Sets the precision of the BPM (beats-per-minute) setting. The * original value was effectively 0, but we need to be able to support * the following values: * * - 0. The legacy default. * - 1. One decimal place in the BPM spinner (and MIDI control). * - 2. Two decimal places in the BPM spinner (and MIDI control). */ int m_bpm_precision; /** * The step increment value for BPM, regardless of the decimal precision. * The default value is the legacy value, 1, for a BPM precision value of * 0. The default value is 0.1 if one decimal place of precision is in * force, and 0.01 if two decimal places of precision is in force. * This is the increment that is performed in the BPM field of the main * window when the arrow-buttons are clicked, the up/down arrow keys are * pressed, or the BPM MIDI controls are processed. */ midibpm m_bpm_step_increment; /** * This is the larger increment for paging the BPM. Currently, the only * way to use this increment is to click in the BPM field of the main * window and then use the Page-Up and Page-Down keys. */ midibpm m_bpm_page_increment; /** * If set (which is now the default), then each new pattern automatically * gets a time signature *event* at time 0. */ bool m_auto_add_time_sig; /* * Values calculated from other member values in the normalize() function. */ /** * The maximum number of patterns supported is given by the number of * patterns supported in the panel (32) times the maximum number of * sets (32), or 1024 patterns. It is basically the same value as * m_max_sequence by default. */ int m_total_seqs; /* not included in .usr file */ /** * Number of patterns/sequences in the Patterns Panel, also known as * a "set" or "screen set". This value is 4 x 8 = 32 by default. * * \warning * Currently implicit/explicit in a number of the "rc" file and * rcsettings. Would probably want the left 32 or the first 32 * items in the main window only to be subject to keystroke control. * This value is calculated by the normalize() function, and is * not part of the 'usr' configuration file. */ int m_seqs_in_set; /* not include in .usr file */ /** * Number of group-mute tracks/sequences/patterns that can be supported, * which is m_seqs_in_set squared, or 1024. This value is not * part of the 'usr' configuration file; it is calculated by the * normalize() function. */ int m_gmute_tracks; /* not included in .usr file */ /** * The maximum number of patterns supported is given by the number of * patterns supported in the panel (32) times the maximum number of * sets (32), or 1024 patterns. */ int m_max_sequence; /** * The hardwired base width of the whole main window. If m_window_scale * is significantly different from 1.0, then the accessor will scale this * value. */ int m_mainwnd_x; /** * The hardwired base height of the whole main window. Like * m_mainwnd_x, this value is scaled by the accessor, however, only if * less than 1.0; otherwise, the top buttons expand way too much. */ int m_mainwnd_y; /* * All constant (unchanging) values go here. They are not saved or read. */ /* * [user-options] */ /** * Provides a temporary variable that can be set from the command line to * cause the 'usr' state to be saved into the 'usr' configuration file. * * Normally, this state is not saved. It is not saved because there is * currently no user-interface for editing it, and because it can pick up * some command-line options, and it is not right to have them written * to the 'usr' configuration file. * * (The "rc" configuration file is a different case, having historically * always been saved, and having a number of command-line options, such * as JACK settings that should generally be permanent on a given * system.) * * Anyway, this flag can be set by the --user-save option. This setting * is never saved. But note that, if no 'usr' configuration file is * found, it is then saved anyway. * * Now see the named_bools list rcsettings::m_save_list instead. */ /** * Indicates if the application is running headless. That is, from the * seq66cli command-line application/daemon. We can do things when * headless without having to worry about signalling Qt. * * This is now covered by calling seq66::set_app_cli(true) in main(). * * bool m_app_is_headless; */ /** * Indicates if the application should be daemonized. All options that * begin with "option_" are options specific to a particular version of * Seq66. We don't anticipate having a lot of such options, * so there's no need for a separate class to handle them. These options * are flagged on the command-line by the strings "-o" or "--option". */ bool m_user_option_daemonize; /** * This option is set only from the command-line. If set, then * the user-save flag is raised, and the application does nothing * but save the user-file and exit with a message to that effect. */ bool m_user_save_daemonize; /** * If true, this value means that "-o log=..." (where the "..." is an * optional filename) was specified on the command line. */ bool m_user_use_logfile; /** * If not empty, this file will be set up as the destination for all * logging done by the errprint(), infoprint(), warnprint(), and printf() * functions. In other words, stdout and stderr will go to a log * file instead. Unless a full path is provided, this filename will be a * base filename, with the path given by rc().config_directory() * prepended to it. That path is normally ~/.config/seq66, but can * be modified on the command line via the -H (--home) option. * * This file can also be specified by the "-o log=filename" option. */ std::string m_user_option_logfile; /** * The full path to PDF and browser executables, in case the system * defaults are not present or are not suitable. */ std::string m_user_pdf_viewer; std::string m_user_browser; /* * [user-ui-tweaks] */ /** * Defines the key height in the Kepler34 sequence editor. Defaults to * 12 pixels (8 is actually a bit nicer IMHO). */ int m_user_ui_key_height; /** * Indicates the default mode for showing the piano-key labels. */ showkeys m_user_ui_key_view; /** * Turns on the replacement of the Qt 5 qseqeditframe (now moved to * contrib/code) with the larger and more functional qseqeditframe64, in * the "Edit" tab. A Kepler34 adaptation. Now permanently true. */ bool m_user_ui_seqedit_in_tab; #if defined USE_USR_STYLE_SHEET /** * Indicates if the style-sheet will be used. */ bool m_user_ui_style_active; /** * Provides the name of an optional Qt style-sheet, located in the active * Seq66 configuration directory. By default, this name is empty and not * used. It present, it contains the base name of the sheet (e.g. * "qseq66.qss". It can also contain a path, in order to support a * universal style-sheet. */ std::string m_user_ui_style_sheet; #endif /** * Indicates to resume notes that are "in progress" upon a sequence * toggle. A Kepler34 adaptation. */ bool m_resume_note_ons; /** * The size of the fingerprint to use. The default size is 32, but can * be made larger, at the expense of slowing down drawing slightly. */ int m_fingerprint_size; /** * Lets the progress-box in the loop-buttons be tailored in size, or even * not drawn at all. The defaults are -1, which means use the internal * defaults in qloopbutton. We add a boolean for showing the progress * boxes, as the 0.0 juggling causes confusion. */ double m_progress_box_width; double m_progress_box_height; bool m_progress_box_shown; /** * If set (normally true), then MIDI Control Change and Pitch Wheel * events are shown as dots in the progress box, to make them easy * to see when recording. */ bool m_progress_box_show_cc; /** * Lets the range (in pitch) of the progress box be tailored. If the * maximum is 0, neither value is used; instead the min/max are * determined separately for each sequence and the patterns are centered * vertically in the progress box. */ int m_progress_note_min; int m_progress_note_max; /** * If true, locks the size of the window so that it cannot be changed by * the user. Works with the "scale" options as well. */ bool m_lock_main_window; /** * [user-session] * * This value indicates to create and use a Non Session Manager (or New * Session Manager) client. The name of the option in the "usr" file is * "session", as in "session = nsm". * * However, at the moment we actually detect if the application is a * child of the NSM daemon, and then later set the "in-nsm-session" flag * once connected and acknowledged by it. * * So currently this value is used as a flag to indicate we want to * process as if running under NSM, for easier debugging. */ session m_session_manager; /** * This optional value can be used to attach to an existing name session. * This feature is mostly for trouble-shooting. This option, "url", if * set, provides a URL such as this sample for NSM: * "osc.udp://mlsasus:15344". To use it, one can change the normally * pseudo-random port number to a set value: * * nsmd --osc-port 15344 * * The daemon will emit the full URL to the console, and this value can * be placed in qseq66.usr via the value "url = osc.udp://mlsasus:15344". */ std::string m_session_url; /** * Indicates if a session was able to be activated. This item is not * stored in the "usr" file. It is treated like a pseudo-global flag. */ bool m_in_nsm_session; /** * Indicates the visibility status of the application. It is normally * always true. However, if the user toggles invisibility while in an * NSM session, then it can be set to false, so that invisibility can be * restored when started again by NSM. */ bool m_session_visibility; /** * [pattern-editor] * * Options for a pattern that is newly-created, plus a new option to let * the Esc key close a pattern editor window. */ bool m_escape_pattern; bool m_pattern_armed; bool m_pattern_thru; bool m_pattern_record; bool m_pattern_tighten; bool m_pattern_qrecord; bool m_pattern_notemap; bool m_pattern_new_only; /** * Provides the default recording style (merge/overdub, overwrite, etc.) * at startup. Compare to the current recording style. */ recordstyle m_pattern_record_style; /** * If true, allow notes that wrap-around in a pattern. That is, the Note * On is at a later timestamp than the corresponding Note Off. This * feature is support by Stazed code, but can introduce issues. */ bool m_pattern_wraparound; /** * Normal (none = no alteration), tighten, quantize, or note-map (jitter * and random are not supported during recording at this time). * Indicates if notes recorded into a sequence will be altered or not. */ alteration m_record_alteration; /** * Indicates the global selected mode for the main-window's grid. * * Modes consist of loop (the normal used of the grid to do muting and * unmuting), record (use the grid to turn recording on for a pattern, * copy (use the grid to copy patterns), and more. This is a run-time * value; it is not stored. */ gridmode m_grid_mode; /** * If true (the default), then a prompt is shown (in the GUI) when * a mute-group learn operation succeeds. */ bool m_enable_learn_confirmation; public: usrsettings (); /* * Not using default at present. Both could be modified by the normalize() * call. However copying and assignment aren't even used at present. */ usrsettings (const usrsettings & rhs) = default; usrsettings & operator = (const usrsettings & rhs) = default; virtual void set_defaults () override; virtual void normalize () override; bool bpb_is_valid (int v) const; /* beats per bar (measure) */ int bpb_default () const; bool bw_is_valid (int v) const; /* beat width (denominator) */ int bw_default () const; bool bpm_is_valid (midibpm v) const; /* beats per minute (BPM) */ midibpm bpm_default () const; midilong scaled_bpm (midibpm bpm); /* precision 2 BPM in long */ midibpm unscaled_bpm (midilong bpm); /* precision 2 double value */ bool add_bus (const std::string & alias); bool add_instrument (const std::string & instname); void clear_buses_and_instruments () { m_midi_buses.clear(); m_instruments.clear(); } /** * \getter * Unlike the non-const version this function is public. * Cannot append the const specifier. */ const usermidibus & bus (int index) // const { return private_bus(index); } /** * Unlike the non-const version this function is public. Cannot append * the const specifier. */ const userinstrument & instrument (int index) // const { return private_instrument(index); } int bus_count () const { return int(m_midi_buses.size()); } bool set_bus_instrument (int index, int channel, int instrum); int bus_instrument (int buss, int channel) { return bus(buss).instrument(channel); } const std::string & bus_name (int buss) { return bus(buss).name(); } int instrument_count () const { return int(m_instruments.size()); } bool set_instrument_controllers ( int index, int cc, const std::string & ccname, bool isactive ); /** * \getter m_instruments[instrument].instrument (name of instrument). */ const std::string & instrument_name (int instrum) { return instrument(instrum).name(); } /** * Gets the correct instrument number from the buss and channel, and then * looks up the name of the instrument. */ const std::string & instrument_name (int buss, int channel) { int instrum = bus_instrument(buss, channel); return instrument(instrum).name(); } bool instrument_controller_active (int instrum, int cc) { return instrument(instrum).controller_active(cc); } /** * A convenience function so that the caller doesn't have to get the * instrument number from the bus_instrument() member function. It also * has a shorter name. */ bool controller_active (int buss, int channel, int cc) { int instrum = bus_instrument(buss, channel); return instrument(instrum).controller_active(cc); } const std::string & instrument_controller_name (int instrum, int cc) { return instrument(instrum).controller_name(cc); } /** * \getter m_instruments[instrument].controllers_active[controller]. * A convenience function so that the caller doesn't have to get the * instrument number from the bus_instrument() member function. It also * has a shorter name. */ const std::string & controller_name (int buss, int channel, int cc) { int instrum = bus_instrument(buss, channel); return instrument(instrum).controller_name(cc); } public: float window_scale () const { return m_window_scale; } float window_scale_x () const { return window_scale(); } float window_scale_y () const { return m_window_scale_y; } bool window_scale ( float winscale, float winscaley = 0.0, bool useoptionbit = false ); bool window_rescale (int new_width, int new_height = 0); bool parse_window_scale(const std::string & source); /** * Returns true if we're increasing the size of the main window. * In order to avoid double-precision issues, the limit is 1.01 rather * than 1.0. */ bool window_scaled_up () const { return m_window_scale >= 1.01f || m_window_scale_y >= 1.01f; } /** * Returns true if we're reducing the size of the main window. * In order to avoid double-precision issues, the limit is 0.99 rather * than 1.0. */ bool window_scaled_down () const { return m_window_scale <= 0.99f || m_window_scale_y <= 0.99f; } /** * Returns true if the window is scaled. */ bool window_is_scaled () const { return window_scaled_up() || window_scaled_down(); } int scale_font_size (int value) const; int scale_size (int value, bool shrinkmore = false) const; int scale_size_y (int value, bool shrinkmore = false) const; int mainwnd_rows () const { return m_mainwnd_rows; } int mainwnd_cols () const { return m_mainwnd_cols; } int set_size () const { return m_mainwnd_rows * m_mainwnd_cols; } int set_offset (int setno) const { return setno * set_size(); } bool swap_coordinates () const { return m_swap_coordinates; } bool is_variset () const; bool is_default_mainwnd_size () const; bool vertically_compressed () const; bool horizontally_compressed () const; bool shrunken () const; int seqs_in_set () const { return m_seqs_in_set; } int gmute_tracks () const { return m_gmute_tracks; } int max_sequence () const { return m_max_sequence; } int total_seqs () const { return m_total_seqs; /* not included in .usr file */ } /** * \getter m_seqchars_x, not user modifiable, not saved. */ int seqchars_x () const { return m_seqchars_x; } /** * \getter m_seqchars_y, not user modifiable, not saved. */ int seqchars_y () const { return m_seqchars_y; } int mainwnd_spacing () const { return scale_size(m_mainwnd_spacing); } int mainwnd_x () const; int mainwnd_y () const; int mainwnd_x_min () const; int mainwnd_y_min () const; int base_zoom () const { return m_base_zoom; } void base_zoom (int value); /* seqedit can change this one */ midipulse jitter_range (int snap); int jitter_divisor () const { return m_jitter_divisor; } void jitter_divisor (int d) { if (d > 1) m_jitter_divisor = d; } int randomization_amount () const { return m_randomization_amount; } void randomization_amount (int r) { if (r > 0 && r < 20) /* an arbitrary upper limit, large */ m_randomization_amount = r; } /** * This special value of zoom sets the zoom according to a power of two * related to the PPQN value of the song. Currently used only in the * seqedit frame. */ bool adapt_zoom () const { return midi_ppqn() != c_base_ppqn; } bool global_seq_feature () const { return m_global_seq_feature_save; } void global_seq_feature (bool flag) { m_global_seq_feature_save = flag; } void clear_global_seq_features (); int seqedit_scale () const { return m_seqedit_scale; } void seqedit_scale (int scale) { if (legal_scale(scale)) m_seqedit_scale = scale; } int seqedit_key () const { return m_seqedit_key; } void seqedit_key (int key) { if (legal_key(key)) m_seqedit_key = key; } int seqedit_bgsequence () const { return m_seqedit_bgsequence; } /** * \setter m_seqedit_bgsequence * * Note that seq::legal() allows the seq::limit() (0x800 = 2048) * value, to turn off the use of a global background sequence. */ void seqedit_bgsequence (int seqnum) { m_seqedit_bgsequence = seqnum; } bool progress_bar_thick () const { return m_progress_bar_thick; } int progress_bar_thickness () const { return m_progress_bar_thickness; } bool progress_box_elliptical () const { return m_progress_box_elliptical; } bool gridlines_thick () const { return m_gridlines_thick; } bool follow_progress () const { return m_follow_progress; } bool inverse_colors () const { return m_inverse_colors; } const std::string & time_fg_color (bool forusrfile = false) const; const std::string & time_bg_color (bool forusrfile = false) const; std::string time_colors_css () const; bool dark_theme () const { return m_dark_theme; } int window_redraw_rate () const { return m_window_redraw_rate_ms; } protected: bool test_option_bit (int b) { return bool((m_option_bits & b) == b); } void set_option_bit (int b) { m_option_bits |= b; } void clear_option_bit (int b) { m_option_bits &= ~b; } void clear_option_bits () { m_option_bits = 0; } bool mainwnd_rows (int value); bool mainwnd_cols (int value); void swap_coordinates (bool flag) { m_swap_coordinates = flag; } /* * This is a derived value, not settable by the user. We will need to fix * this at some point; it is currently a usrfile option! */ void seqchars_x (int value); void seqchars_y (int value); /* * Now an option in Edit / Preferences. */ void mainwnd_spacing (int value); /* * These values are calculated from other values in the normalize() * function: * * void seqs_in_set (int value); * void gmute_tracks (int value); * void max_sequence (int value); */ void dump_summary(); public: bool convert_to_smf_1 () const { return m_convert_to_smf_1; } void convert_to_smf_1 (bool flag) { m_convert_to_smf_1 = flag; } void reset_ppqn (); int default_ppqn () const { return m_default_ppqn; } int use_default_ppqn () const { return c_use_default_ppqn; } int base_ppqn () const; bool is_ppqn_valid (int ppqn) const; int midi_ppqn () const { return m_midi_ppqn; /* current PPQN, either default or file */ } bool use_file_ppqn () const { return m_use_file_ppqn; } int file_ppqn () const { return m_file_ppqn; } void use_file_ppqn (bool flag) { m_use_file_ppqn = flag; } void file_ppqn (int p) { m_file_ppqn = p; if (use_file_ppqn()) m_midi_ppqn = p; } int midi_beats_per_bar () const { return m_midi_beats_per_measure; } midibpm midi_bpm_minimum () const { return m_midi_bpm_minimum; } midibpm midi_beats_per_minute () const { return m_midi_beats_per_minute; } midibpm midi_bpm_maximum () const { return m_midi_bpm_maximum; } long tap_button_timeout () const; int midi_beat_width () const { return m_midi_beat_width; } bussbyte midi_buss_override () const { return m_midi_buss_override; } bool is_buss_override () const { return is_good_buss(m_midi_buss_override); } short velocity_override () const { return m_velocity_override; } short preserve_velocity () const; short note_off_velocity () const; short note_on_velocity () const; short max_note_on_velocity () const; int bpm_precision () const { return m_bpm_precision; } midibpm bpm_step_increment () const { return m_bpm_step_increment; } midibpm bpm_page_increment () const { return m_bpm_page_increment; } bool auto_add_time_sig () const { return m_auto_add_time_sig; } int min_zoom () const { return c_min_zoom; } int max_zoom () const { return c_max_zoom; } /** * No longer used. * * bool app_is_headless () const * { * return m_app_is_headless; * } */ bool option_daemonize () const { return m_user_option_daemonize; } bool save_daemonize () const { return m_user_save_daemonize; } bool option_use_logfile () const { return m_user_use_logfile; } const std::string & option_logfile () const { return m_user_option_logfile; } const std::string & user_pdf_viewer () const { return m_user_pdf_viewer; } const std::string & user_browser () const { return m_user_browser; } int min_key_height () const; int max_key_height () const; int key_height () const { return m_user_ui_key_height; } bool valid_key_height (int h) const { return h >= min_key_height() && h <= max_key_height(); } showkeys key_view () const { return m_user_ui_key_view; } std::string key_view_string () const; #if defined USE_USR_STYLE_SHEET bool style_sheet_active () const { return m_user_ui_style_active; } const std::string & style_sheet () const { return m_user_ui_style_sheet; } #endif bool resume_note_ons () const { return m_resume_note_ons; } int fingerprint_size () const { return m_fingerprint_size; } double progress_box_width () const { return m_progress_box_width; } double progress_box_height () const { return m_progress_box_height; } bool progress_box_shown () const { return m_progress_box_shown; } bool progress_box_show_cc () const { return m_progress_box_show_cc; } int progress_note_min () const { return m_progress_note_min; } int progress_note_max () const { return m_progress_note_max; } bool lock_main_window () const { return m_lock_main_window; } /* * Session manager options */ session session_manager () const { return m_session_manager; } std::string session_manager_name () const; bool want_no_session () const { return m_session_manager == session::none; } bool want_nsm_session () const { return m_session_manager == session::nsm; } bool want_jack_session () const { return m_session_manager == session::jack; } bool in_nsm_session () const { return m_in_nsm_session; } bool session_visibility () const { return m_session_visibility; } const std::string & session_url () const { return m_session_url; } /* * New-pattern options */ bool escape_pattern () const { return m_escape_pattern; } bool pattern_armed () const { return m_pattern_armed; } bool pattern_thru () const { return m_pattern_thru; } bool pattern_record () const { return m_pattern_record; } bool pattern_alter_recording () const { return ( pattern_tighten() || pattern_qrecord() || pattern_notemap() ); } alteration pattern_alteration () const { if (pattern_tighten()) return alteration::tighten; else if (pattern_qrecord()) return alteration::quantize; else if (pattern_notemap()) return alteration::notemap; else return alteration::none; } bool pattern_tighten () const { return m_pattern_tighten; } bool pattern_qrecord () const { return m_pattern_qrecord; } bool pattern_notemap () const { return m_pattern_notemap; } bool pattern_new_only () const { return m_pattern_new_only; } /* * Record style refers to what happens to already recorded notes when the * pattern wraps around. */ std::string pattern_record_style_label () const; recordstyle pattern_record_style (int rs) const { recordstyle rscast = static_cast(rs); return rscast >= recordstyle::merge && rscast < recordstyle::max ? rscast : recordstyle::merge ; } recordstyle pattern_record_style () const { return m_pattern_record_style; } void set_pattern_record_style (const std::string & style); void set_pattern_record_style (int index); void set_pattern_record_style (recordstyle style) { if (style < recordstyle::max) m_pattern_record_style = style; /* m_grid_record_style */ } recordstyle next_record_style (); recordstyle previous_record_style (); int pattern_record_code (recordstyle rs) const { return static_cast(rs); } int pattern_record_code () const { return pattern_record_code(pattern_record_style()); } bool pattern_wraparound () const { return m_pattern_wraparound; } std::string pattern_record_string () const; /* * Record-mode options. By "record mode" is meant the alterations * that can be done on-the-fly on incoming events. It used to be * an enumeration in this module, but is now defined by the alteration * enumeration in the calculations module. * * Planned is a "playback mode", as well manually-applied alterations. */ alteration record_alteration () const { return m_record_alteration; } void record_alteration (alteration rm) { if (rm < alteration::max) m_record_alteration = rm; } bool alter_recording () const { return m_record_alteration != alteration::none; } std::string record_alteration_label () const; alteration next_record_alteration (); alteration previous_record_alteration (); /* * Grid mode refers to what happens when a pattern is clicked or selected * via a hot-key. */ bool no_grid_record () const { return grid_mode() != gridmode::record; } gridmode grid_mode () const { return m_grid_mode; } gridmode grid_mode (int gm) const { return static_cast(gm); } int grid_mode_code (gridmode gm) const { return static_cast(gm); } int grid_mode_code () const { return grid_mode_code(grid_mode()); } std::string grid_mode_label (gridmode gm = gridmode::max) const; bool enable_learn_confirmation () const { return m_enable_learn_confirmation; } void enable_learn_confirmation (bool flag) { m_enable_learn_confirmation = flag; } public: // used in main application module and the usrfile class void progress_note_min_max (int vmin, int vmax); void progress_bar_thick (bool flag) { m_progress_bar_thick = flag; } void progress_bar_thickness (int t) { m_progress_bar_thickness = t; } void progress_box_elliptical (bool flag) { m_progress_box_elliptical = flag; } void follow_progress (bool flag) { m_follow_progress = flag; } void gridlines_thick (bool flag) { m_gridlines_thick = flag; } void lock_main_window (bool flag) { m_lock_main_window = flag; } /* * Not yet part of Edit / Preferences. */ void inverse_colors (bool flag) { if (! test_option_bit(option_inverse)) { m_inverse_colors = flag; set_option_bit(option_inverse); } } void time_fg_color (const std::string & fgc) { m_time_fg_color = fgc; } void time_bg_color (const std::string & bgc) { m_time_bg_color = bgc; } void dark_theme (bool flag) { m_dark_theme = flag; } void window_redraw_rate (int ms); /** * No longer used. * * void app_is_headless (bool flag) * { * m_app_is_headless = flag; * } */ void option_daemonize (bool flag, bool setup = false); void option_use_logfile (bool flag); void option_logfile (const std::string & file); /* * Since these a paths to executable, probably good to provide a full * path, for now we will not enforce that. */ void user_pdf_viewer (const std::string & file) { m_user_pdf_viewer = file; } void user_browser (const std::string & file) { m_user_browser = file; } void key_height (int h) { if (valid_key_height(h)) m_user_ui_key_height = h; } void key_view (const std::string & view); #if defined USE_USR_STYLE_SHEET void style_sheet_active (bool flag) { m_user_ui_style_active = flag; } void style_sheet (const std::string & s) { m_user_ui_style_sheet = s; } #endif void resume_note_ons (bool f) { m_resume_note_ons = f; } void session_manager (const std::string & sm); bool fingerprint_size (int sz); bool progress_box_size (double w, double h); void progress_box_shown (bool flag) { m_progress_box_shown = flag; } void progress_box_show_cc (bool flag) { m_progress_box_show_cc = flag; } void in_nsm_session (bool f) { m_in_nsm_session = f; } void session_visibility (bool f) { m_session_visibility = f; } void session_url (const std::string & value) { m_session_url = value; } void escape_pattern (bool flag) { m_escape_pattern = flag; } void pattern_armed (bool flag) { m_pattern_armed = flag; } void pattern_thru (bool flag) { m_pattern_thru = flag; } void pattern_record (bool flag) { m_pattern_record = flag; } void pattern_tighten (bool flag) { m_pattern_tighten = flag; } void pattern_qrecord (bool flag) { m_pattern_qrecord = flag; } void pattern_notemap (bool flag) { m_pattern_notemap = flag; } void pattern_new_only (bool flag) { m_pattern_new_only = flag; } void pattern_wraparound (bool flag) { m_pattern_wraparound = flag; } void grid_mode (gridmode mode) { m_grid_mode = mode; } void default_ppqn (int ppqn); void midi_ppqn (int ppqn); void midi_buss_override (bussbyte buss, bool userchange = false); void velocity_override (int vel); void bpm_precision (int precision); void bpm_step_increment (midibpm increment); void bpm_page_increment (midibpm increment); void auto_add_time_sig (bool f) { m_auto_add_time_sig = f; } protected: void midi_beats_per_bar (int beatsperbar); void midi_bpm_minimum (midibpm beatsperminute); void midi_beats_per_minute (midibpm beatsperminute); void midi_bpm_maximum (midibpm beatsperminute); void midi_beat_width (int beatwidth); private: usermidibus & private_bus (int buss); userinstrument & private_instrument (int instrum); }; // class usrsettings } // namespace seq66 #endif // SEQ66_USRSETTINGS_HPP /* * usrsettings.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/cfg/zoomer.hpp ================================================ #if ! defined SEQ66_ZOOMER_HPP #define SEQ66_ZOOMER_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ /** * \file zoomer.hpp * * This module declares/defines the base class for operations common to all * seq66 windows. * * \library seq66 application * \author Chris Ahlstrom * \date 2023-09-08 * \updates 2025-07-21 * \license GNU GPLv2 or above * */ #include "midi/midibytes.hpp" /* seq66::midipulse, etc. */ namespace seq66 { /** * We need to provide a more flexible way to draw vertical grid lines * whatever the PPQN. See the cpp file for more information. * * After doing a few analyses, as laid out in * contrib/notes/ppqn-and-grids.ods, we are not bothering with this. * It's just as issue-filled as the current method. */ #undef SEQ66_USE_NEW_STYLE_GRID_DRAWING /* not used, FYI only */ /** * The default value of the zoom indicates that one pixel represents two * ticks. However, it turns out we're going to have to support adapting the * default zoom to the PPQN, in addition to allowing some extra zoom values. * A redundant definition is used on the calculations module at present. * * The maximum value of the zoom indicates that one pixel represents 512 * ticks. The old maximum was 32, but now that we support PPQN up to 19200, * we need extra entries. */ const int c_minimum_zoom = 1; /* limit the amount of zoom */ const int c_default_seq_zoom = 2; /* default snap from app limits */ const int c_default_perf_zoom = 16; /* default snap from app limits */ const int c_maximum_zoom = 1024; /* limit the amount of zoom */ /** * This frame is the basis for editing an individual MIDI sequence. */ class zoomer { /** * Holds the current PPQN for convenience. */ int m_ppqn; /** * Provides the initial zoom, used for restoring the original zoom using * the 0 key. */ const int m_base_zoom; /** * Horizontal zoom setting. This is the ratio between pixels and MIDI * ticks, written "pixels:ticks". As ticks increases, the effect is to * zoom out, making the beats look shorter. The default zoom is 2 for * the normal PPQN of 192. * * Provides the zoom values, as given by the zoom_items() and * expanded_zoom_items() functions in the settings module. * * The value of zoom is the same as the number of ticks per pixels on * the piano roll. * * Moved here to avoid warning about m_zoom unitialized as detected by * g++ 14.2.1 on Arch Linux. */ int m_zoom; /** * X scaling. Allows the caller to adjust the overall zoom. A * constant. */ const int m_scale; /** * Zoom times the scale, to save a very common calculation, * m_zoom * m_scale. */ int m_scale_zoom; /** * Provides the current zoom index. If 0 to 9, this is the index * into the zoom_items() tokenization. If -1 to -4, this number is * negated, and 1 is subtracted, to get an index into the * expanded_zoom_items() tokenization. */ int m_zoom_index; /** * An additional kind of zoom, useful for depicting dense events such as * pitch-bend. All it does is multiply the pixel numbers by this factor. * The supported values are 1 (the same as no expansion), 2, 4, and 8. * It is accessible only via the zoom buttons and zoom keys, and applies * only to the x (horizontal) direction. If set to 0, this value is not * used. */ int m_zoom_expansion; public: zoomer (); zoomer (int ppq, int initialzoom, int scalex = 1); ~zoomer () = default; public: bool zoom_in (); bool zoom_out (); bool set_zoom (int z); bool set_zoom_by_index (int i); bool reset_zoom (int ppq = 0); bool change_zoom (bool in) { return in ? zoom_in() : zoom_out() ; /* calls the override */ } int zoom () const { return m_zoom; } int base_zoom () const { return m_base_zoom; } int scale () const { return m_scale; } int ppqn () const { return m_ppqn; } int scale_zoom () const { return m_scale_zoom; } bool expanded_zoom () const { return m_zoom_expansion > 0; } int zoom_expansion () const { return m_zoom_expansion; } midipulse pix_to_tix (int x) const; int tix_to_pix (midipulse ticks) const; int xoffset (midipulse tick) const { return tix_to_pix(tick); } bool change_ppqn (int ppq); int zoom_power_of_2 (int ppq); public: int pulses_per_pixel () const { return zoom(); } int pulses_per_substep () const { return 6 * pulses_per_pixel(); } /* * The old calculation was * * result = ppqn() * bpb / (4 * bw) * * The new calculation gives reasonable results for power-of-2 * beat-widths (as mandated by MIDI) * * We leave the bpb parameter in just in case we decide to * use it again. Voided to prevent warnings. */ int pulses_per_partial_beat (int bpb = 4, int bw = 4) const { (void) bpb; return (bw > 0) ? ppqn() / bw : ppqn() / 4 ; } int pulses_per_beat (int bw = 4) const { return (bw > 0) ? 4 * ppqn() / bw : ppqn() ; } int pulses_per_bar (int bpb = 4, int bw = 4) const { return (bw > 0) ? 4 * ppqn() * bpb / bw : ppqn() * bpb ; } private: bool initialize (); }; // class zoomer /* * Free functions. */ extern int adapted_seq_zoom (int ppq); extern int adapted_perf_zoom (int ppq); } // namespace seq66 #endif // SEQ66_ZOOMER_HPP /* * zoomer.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/automation.hpp ================================================ #if ! defined SEQ66_AUTOMATION_HPP #define SEQ66_AUTOMATION_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file automation.hpp * * This module declares/defines the namespace for some enumeration classes * used to specify control categories and actions. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-18 * \updates 2024-01-03 * \license GNU GPLv2 or above * * This module defines a number of constants relating to control of pattern * unmuting, group control, and a number of additional controls to make * Seq66 controllable without a graphical user interface. See the cpp file * for additional information. This module requires C++11 and above. */ #include namespace seq66 { namespace automation { /** * Provides the number of sub-stanzas in a midicontrol stanza in the * "rc"/"ctrl" file. The 3 sections are the valid values in the * automation::action enumeration: toggle, on, and off. */ static const int ACTCOUNT = 3; /** * Manifest constants for midicontrolfile to use as array indices. * These correspond to the MIDI Controls for UI (user-interface) actions; * see the uiactions enumeration. This enumeration cannot be a class * enumeration, because enum classes cannot be used as array indices. */ enum index { inverse, status, data_1, data_2_min, data_2_max, max }; /** * Provides the number of values in a midicontrol sub-stanza. Recall * that one sub-stanza is represented by a [ 0 0 0 0 0 ] * item in the 'ctrl' file. We have removed the "enabled" value as * redundant, and reduced the count to 5. */ static const int SUBCOUNT = int(index::max); /** * Provides enumerations for the main control sections. The pattern, * mute-group, and automation values are selected when the "rc" file is read, * based on the name of the sections in which control values were read: * * - [loop-control] * - [mute-group-control] * - [automation-control] * * In Sequencer64, keyboard control was set up in a [keyboard-control] * section, and MIDI control was set up separately in a [midi-control] * section. In Sequencer66, keyboard and MIDI controls are set up in the same * sections, the three sections noted above. */ enum class category { none, /**< Not used, except to indicate "not initialized. */ loop, /**< [loop-control], mutes/unmutes "Loops". */ mute_group, /**< [mute-group-control], specifies multiple mutings. */ automation, /**< [automation-control], GUI control automation. */ max /**< Not used, except to check for illegal settings. */ }; /** * Provides the kind of MIDI control event found, used in the new * perform::handle_midicontrol_ex() function. * * \var none * - Indicates that the control is not active (temporarily). * * \var toggle * - Normally, toggles the status of the given control. * - For the "playback" status, indicates the "pause" functionality. * - For the "playlist" and "playlist-song" status, indicates the * "select-by-value" functionality. * * \var on * - Normally, turns on the status of the given control. * - For the "playback" status, indicates the "start" functionality. * - For the "playlist" and "playlist-song" status, indicates the * "select-next" functionality. * * \var off * - Normally, turns off the status of the given control. * - For the "playback" status, indicates the "stop" functionality. * - For the "playlist" and "playlist-song" status, indicates the * "select-previous" functionality. * * \var max * - Simply a limit number. */ enum class action { none, toggle, on, off, max }; /** * Pseudo control values for associating MIDI events, for the automation of * some of the controls in seq66. Unlike the earlier version, this version is * not necessarily tied to the 32-pattern paradigm. * * Each slot value is tied to a particular performer member function. Each * slot accesses the performer member function via a lambda loaded into a * map. Faster lookup than traversing a bunch of if-statements, it is to be * hoped. * * The controls are read in from the 'ctrl' configuration files, but are no * longer written to the c_midictrl section of the "proprietary" final track * in a Seq66 MIDI file. The controls represented by slot values are part of * the automation (user-interface) section of the 'ctrl' file. * * Unlike the original controls, all of the control groups (pattern, * mute-group, and automation) all support a number of controls not * necessarily equal to 32. Also, up/down controls have been folded into one * control. We need to be able to convert between old and new "control" * numbers. * * See opcontrol::slot_name() to get the display name of each slot. * * Notes: * * -# Replace, queue, and one-shot can be combined in an operation. * -# For loop-control and mute-group control, the slot is the * pattern or group number, which redirect calls to the pattern * and mute_group slot functions. For automation-control, the * slot numbers are in one-to-one correspondence with slot * functions (also known as "operations"), * -# WARNING: If one updates this list, one MUST also update the * static opcontrol::s_slot_names vector to match! */ enum class slot { none = -1, /**< An out-of-range value, uninitialized. */ bpm_up = 0, /**< 0: BPM up; for MIDI up and down. */ bpm_dn, /**< 1: BPM down; for MIDI down and up. */ ss_up, /**< 2: Screen-set (bank) up. And down for MIDI. */ ss_dn, /**< 3: Screen-set (bank) down. */ mod_replace, /**< 4: Set status of replace control. */ mod_snapshot, /**< 5: Set status of snapshot control. */ mod_queue, /**< 6: Set status of queue control; group_on, _off */ mod_gmute, /**< 7: Set status of group-mute control. */ mod_glearn, /**< 8: Set status of group-learn control. */ play_ss, /**< 9: Sets the playing screen-set (bank). */ playback, /**< 10: Key pause, and MIDI for pause/start/stop. */ song_record, /**< 11: Sets recording of a live song performance. */ solo, /* grid? */ /**< 12: TODO, intended to solo track. */ thru, /* grid? */ /**< 13: Enables/disables the MIDI THRU control. */ bpm_page_up, /**< 14: Increments BMP by a configured page value. */ bpm_page_dn, /**< 15: Decrements BMP by a configured page value. */ ss_set, /**< 16: Key: set screen-set; MIDI: playing set. */ record_style, /**< 17: Moves between recording styles like merge. */ quan_record, /**< 18: Moves to next/previous quantize type. */ reset_sets, /**< 19: Resets all patterns/playing set. */ mod_oneshot, /**< 20: Set status of one-shot queuing. */ FF, /**< 21: Fast-forwards the clock (pulse counter.) */ rewind, /**< 22: Rewinds the clock (pulse counter). */ top, /**< 23: Set to song beginning or L marker. */ playlist, /**< 24: MIDI only, arrow keys hardwired. */ playlist_song, /**< 25: MIDI only, arrow keys hardwired. */ tap_bpm, /**< 26: Tap key for estimating BPM. */ start, /**< 27: Start playback. Compare to playback above. */ stop, /**< 28: Stop playback. Compare to playback above. */ loop_LR, /**< 29: Toggle looping between L/R markers. */ toggle_mutes, /**< 30: Song mute, unmute, and toggle? */ song_pointer, /**< 31: Reposition the song pointer. TODO. */ /* * The following add to what Seq64 supports. */ keep_queue, /**< 32: Set keep-queue (the "Q" button). */ slot_shift, /**< 33: Used for sets > 32 patterns. */ mutes_clear, /**< 34: Set all mute groups to unarmed. */ quit, /**< 35: Quit (close and exit) the application. */ pattern_edit, /**< 36: GUI action, bring up pattern for editing. */ event_edit, /**< 37: GUI action, bring up the event editor. */ song_mode, /**< 38: GUI. Toggle between Song Mode & Live Mode. */ toggle_jack, /**< 39: GUI. Toggle between JACK and ALSA support. */ menu_mode, /**< 40: GUI. Switch menu between enabled/disabled. */ follow_transport, /**< 41: GUI. Toggle between following JACK or not. */ panic, /**< 42: The Panic Button. */ visibility, /**< 43: Toggle the visibility of the main window. */ save_session, /**< 44: Save the MIDI and configuration files now. */ record_toggle, /**< 45: Enter toggle-record for next hot-key. */ grid_mutes, /**< 46: Grid mode extension :-( for reserved_46 */ reserved_47, /**< 47: Reserved for expansion. */ reserved_48, /**< 48: Reserved for expansion. */ /* * Massive expansion in automation. Record mode selection. */ record_overdub, /**< 49: Select overdub/merge recording triggering. */ record_overwrite, /**< 50: Select overdub recording triggering. */ record_expand, /**< 51: Select expand recording triggering. */ record_oneshot, /**< 52: Select oneshot recording triggering. */ /* * Massive expansion in automation. Grid mode selection. */ grid_loop, /**< 53: Normal operation of the main grid. */ grid_record, /**< 54: Use one of the record modes for slots. */ grid_copy, /**< 55: Grid slot copies the pattern. */ grid_paste, /**< 56: Grid slot pastes to the pattern. */ grid_clear, /**< 57: Grid slot clears only events. */ grid_delete, /**< 58: Grid slot deletes (removes) the pattern. */ grid_thru, /**< 59: Grid slot turns on MIDI thru. */ grid_solo, /**< 60: Grid slot turns on solo. */ grid_cut, /**< 61: Grid slot cuts the pattern. */ grid_double, /**< 62: Grid slot doubles the pattern length. */ /* * Grid quantization type selection. */ grid_quant_none, /**< 63: Grid slot remove recording quantization. */ grid_quant_full, /**< 64: Grid slot full quantization recording. */ grid_quant_tighten, /**< 65: Grid slot tighten quantization recording. */ grid_quant_random, /**< 66: Grid slot salts the magnitude randomly. */ grid_quant_jitter, /**< 67: Grid slot jitter the timing. */ grid_quant_notemap, /**< 68: Reserved for expansion (e.g. note-mapping) */ /* * A few more likely candidates. * NOT YET IMPLEMENTED. */ mod_bbt_hms, /**< 69: Toggle between time-display modes. */ mod_LR_loop, /**< 70: Toggle looping between the L and R marks. */ mod_undo, /**< 71: Undo events in current active pattern. ??? */ mod_redo, /**< 72: Redo events in current active pattern. ??? */ mod_transpose_song, /**< 73: Apply song transpose. ?????? */ mod_copy_set, /**< 74: Copy the current playing set. */ mod_paste_set, /**< 75: Paste into the current active set. */ mod_toggle_tracks, /**< 76: Toggle the armed status of the active set. */ /* * Set playing modes. * NOT YET IMPLEMENTED. */ set_mode_normal, /**< 77: A set selection replaces the playing set. */ set_mode_auto, /**< 78: Set selection starts the new set playing. */ set_mode_additive, /**< 79: Set selection adds the new set to playing. */ set_mode_all_sets, /**< 80: All sets play at the same time. */ /* * Tricky ending. */ max, /**< 81: Used only for termination/range-checking. */ /* * The following are used for selection the correct op function. Pattern * and mute groups each need only one function (with an integer * parameter), while automation uses the codes above to select the proper * op function from a rather large set of them. */ loop, /**< Useful to set and retrieve op function. */ mute_group, /**< Useful to set and retrieve op function. */ automation, /**< Useful to set and retrieve the name. */ illegal /**< A value to flag illegality. */ }; /** * Provides the status bits that used to be in the old perform class. * The values are explained in the top banner of this module. They * replace the free constants starting with "c_status_" in the old * project (Seq64). Do not confuse it with MIDI status, which is a value * specifying a MIDI event. * * These were purely internal constants used with the functions that * implement MIDI control (and also some keystroke control) for the * application. However, we now have to expose them for the Qt5 * implementation, until we can entirely reconcile/refactor the * Kepler34-based body of code. Note how they specify different bit values, * as it they could be masked together to signal multiple functions. * * This value signals the "replace" functionality. If this bit is set, then * perform::sequence_playing_toggle() unsets this status and calls * perform::off_sequences(), which calls sequence::set_playing(false) for all * active sequences. * * It works like this: * * -# The user presses the Replace key, or the MIDI control message for * c_midi_control_mod_replace is received. * -# This bit is OR'd into perform::m_control_status. This status bit * is used in perform::sequence_playing_toggle(). * - Called in perform::sequence_key() so that keystrokes in * the main window toggle patterns in the main window. * - Called in peform::toggle_other_seqs() to implement * Shift-click to toggle all other patterns but the one * clicked. * - Called in seqmenu::toggle_current_sequence(), called in * mainwnd to implement clicking on a pattern. * - Also used in MIDI control to toggle patterns 0 to 31, * offset by the screen-set. * - perform::sequence_playing_off(), similarly used in MIDI control. * - perform::sequence_playing_on(), similarly used in MIDI control. * -# When the key is released, this bit is AND'd out of * perform::m_control_status. * * Both the MIDI control and the keystroke set the sequence to be * "replaced". */ enum class ctrlstatus { /** * The default, non-functional value. */ none = 0x00, /** * This value signals the "snapshot" functionality. By default, * perform::sequence_playing_toggle() calls sequence::toggle_playing() on * the given sequence number, plus what is noted for c_status_snapshot. It * works like this: * * -# The user presses the Snapshot key. * -# This bit is OR'd into perform::m_control_status. * -# The playing state of the patterns is saved by * perform::save_playing_state(). * -# When the key is released, this bit is AND'd out of * perform::m_control_status. * -# The playing state of the patterns is restored by * perform::restore_playing_state(). */ replace = 0x01, /** * This value signals the "snapshot" functionality. By default, * perform::sequence_playing_toggle() calls sequence::toggle_playing() on * the given sequence number, plus what is noted for c_status_snapshot. * It works like this: * * -# The user presses the Snapshot key. * -# This bit is OR'd into perform::m_control_status. * -# The playing state of the patterns is saved by * perform::save_playing_state(). * -# When the key is released, this bit is AND'd out of * perform::m_control_status. * -# The playing state of the patterns is restored by * perform::restore_playing_state(). */ snapshot = 0x02, /** * This value signals the "queue" functionality. If this bit is set, * then perform::sequence_playing_toggle() calls sequence :: * toggle_queued() on the given sequence number. The regular queue key * (configurable in File / Options / Keyboard) sets this bit when * pressed, and unsets it when released. The keep-queue key sets it, but * it is not unset until the regular queue key is pressed and released. */ queue = 0x04, /** * Performs keep-queue. Currently queue and keep-queue are both keep * functions. */ keep_queue = 0x08, /** * This value signals the Kepler34 "one-shot" functionality. If this bit * is set, then perform::sequence_playing_toggle() calls * sequence::toggle_oneshot() on the given sequence number. */ oneshot = 0x10, /** * Signals that we are in mute-group learn mode. This will eventually * supplement the mutegroups "learn" flag, as we want to centralize all * mode statuses. */ learn = 0x20 }; /** * "Bit" operators for the ctrlstatus values. */ inline ctrlstatus operator | (ctrlstatus lhs, ctrlstatus rhs) { return static_cast(static_cast(lhs) | static_cast(rhs)); } inline ctrlstatus & operator |= (ctrlstatus & lhs, ctrlstatus rhs) { lhs = static_cast(static_cast(lhs) | static_cast(rhs)); return lhs; } inline ctrlstatus operator & (ctrlstatus lhs, ctrlstatus rhs) { return static_cast(static_cast(lhs) & static_cast(rhs)); } inline ctrlstatus & operator &= (ctrlstatus & lhs, ctrlstatus rhs) { lhs = static_cast(static_cast(lhs) & static_cast(rhs)); return lhs; } inline ctrlstatus operator ^ (ctrlstatus lhs, ctrlstatus rhs) { return static_cast(static_cast(lhs) & static_cast(rhs)); } inline ctrlstatus & operator ^= (ctrlstatus & lhs, ctrlstatus rhs) { lhs = static_cast(static_cast(lhs) ^ static_cast(rhs)); return lhs; } inline ctrlstatus operator ~ (ctrlstatus rhs) { return static_cast(~ static_cast(rhs)); } inline ctrlstatus add_queue (ctrlstatus cs) { return cs | ctrlstatus::queue; } /** * For complex statuses (more than one bit set), this function returns true * if there is any "on" bit in either of the two statuses. More useful if * there is only one bit, so stick to that use-case. */ inline bool bit_test_or (ctrlstatus lhs, ctrlstatus rhs) { return (static_cast(lhs) | static_cast(rhs)) != 0; } /** * For complex statuses (more than one bit set), this function returns true * if there is any overlay in "on" bits in the two statuses. More useful if * there is only one bit, so stick to that use-case. */ inline bool bit_test_and (ctrlstatus lhs, ctrlstatus rhs) { return (static_cast(lhs) & static_cast(rhs)) != 0; } /* * Free functions in the automation namespace */ extern std::string category_to_string (category c); extern category string_to_category (const std::string & s); extern std::string action_to_string (action c); extern action string_to_action (const std::string & s); extern bool actionable (action a); extern std::string ctrlstatus_to_string (ctrlstatus cs); #if defined SEQ66_USE_SLOT_STRING_CONVERSIONS extern std::string slot_to_string (slot s); extern slot string_to_slot (const std::string & s); #endif } // namespace automation /* * Free-functions for slots. */ inline automation::slot int_to_slot_cast (int s) { return static_cast(s); } inline int slot_to_int_cast (automation::slot s) { return static_cast(s); } inline int original_slot_count () { return static_cast(automation::slot::record_overdub); /* tricky */ } inline int current_slot_count () { return static_cast(automation::slot::max); } /* * Free-functions for actions. */ inline automation::action automation_action (bool flag) { return flag ? automation::action::on : automation::action::off ; } } // namespace seq66 #endif // SEQ66_AUTOMATION_HPP /* * automation.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/keycontainer.hpp ================================================ #if ! defined SEQ66_KEYCONTAINER_HPP #define SEQ66_KEYCONTAINER_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file keycontainer.hpp * * This module declares/defines the class for holding MIDI operation data for * the application. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-18 * \updates 2023-08-12 * \license GNU GPLv2 or above * * This container holds a map of keycontrol objects keyed by a key ordinal * number that can range from 0 to 255. * * It requires C++11 and above. */ #include /* std::map<> */ #include /* std::string */ #include "ctrl/keymap.hpp" /* seq66::keyboard::layout, etc. */ #include "ctrl/keycontrol.hpp" /* seq66::keycontrol */ #include "ctrl/keystroke.hpp" /* seq66::keystroke */ namespace seq66 { /** * Provides an object specifying what a keystroke, GUI action, or a MIDI * control should do. */ class keycontainer { friend class midicontrolin; public: /** * Provides the type definition for this container. The key is the * operation number (generally ranging from 0 to 31 for each * automation::category), and the value is a midioperation object. */ using keymap = std::map; /** * An additional container to hold the pattern-offset numbers and the * corresponding keystrokes to quickly look up the keystroke name based on * the offset number. */ using slotmap = std::map; /** * An additional container to hold the mute-offset numbers and the * corresponding keystrokes to quickly look up the keystroke name based on * the offset number. */ using mutemap = std::map; /** * An additional container to hold the automation numbers and the * corresponding keystrokes to quickly look up the keystroke name based on * the offset number. */ using automationmap = std::map; private: /** * Defines a small structure type for holding some default values, * using in initializing the key-container when there is no configuration * file. */ using keydefault = struct { std::string kd_name; /**< The human-readable key name. */ automation::action kd_action; /**< The action for that key. */ }; using defaults = std::vector; /** * The container itself. */ keymap m_container; /** * A name to use for showing the contents of the container. */ std::string m_container_name; /** * Reverse lookup map for pattern-offset numbers. */ slotmap m_pattern_keys; /** * Reverse lookup map for mute-offset numbers. */ mutemap m_mute_keys; /** * Reverse lookup map for automation number numbers. This map is useful * in showing the keystroke in a tool-tip as per issue #114. */ automationmap m_automation_keys; /** * Indicates if the key values were loaded from an "rc" configuration * file, as opposed to using the default values of the keys. */ bool m_loaded_from_rc; /** * Indicates if the auto-shift feature for group learning is to be used. * Defaults to true, but some keyboard layouts (e.g. AZERTY) are * problematic with this feature. */ bool m_use_auto_shift; /** * Defaults to layout::qwerty. */ keyboard::layout m_kbd_layout; /** * Prevents multiple reloading of the keys. Make sure it works with * modiifed keyboard layouts! */ bool m_defaults_loaded; public: keycontainer (); keycontainer (const std::string & name); const std::string & name () const { return m_container_name; } void clear () { m_container.clear(); m_pattern_keys.clear(); m_mute_keys.clear(); m_automation_keys.clear(); } int count () const { return int(m_container.size()); } bool add (ctrlkey ordinal, const keycontrol & kc); bool add_slot (const keycontrol & kc); bool add_mute (const keycontrol & kc); bool add_automation (const keycontrol & kc); const keycontrol & control (ctrlkey ordinal) const; std::string slot_key (int pattern_offset) const; std::string mute_key (int mute_offset) const; std::string automation_key (int ctrlcode) const; keystroke mute_keystroke (int mute_offset) const; bool loaded_from_rc () const { return m_loaded_from_rc; } void loaded_from_rc (bool flag) { m_loaded_from_rc = flag; } bool use_auto_shift () const { return m_use_auto_shift; } void use_auto_shift (bool flag) { m_use_auto_shift = flag; } keyboard::layout kbd_layout () const { return m_kbd_layout; } void kbd_layout (keyboard::layout lay) { m_kbd_layout = lay; } void set_kbd_layout (const std::string & lay); std::string kbd_layout_to_string (keyboard::layout lay); std::string kbd_layout_to_string () { return kbd_layout_to_string(m_kbd_layout); } void show () const; static const std::string & automation_default_key_name (int index); private: static const defaults & keys_automation (); void add_defaults (); /* many statics inside this one! */ const keymap & container () const { return m_container; } }; // class keycontainer } // namespace seq66 #endif // SEQ66_KEYCONTAINER_HPP /* * keycontainer.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/keycontrol.hpp ================================================ #if ! defined SEQ66_KEYCONTROL_HPP #define SEQ66_KEYCONTROL_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file keycontrol.hpp * * This module declares/defines the class for handling key control data for * the application. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-18 * \updates 2022-05-13 * \license GNU GPLv2 or above * * This class is similar in intent to the midicontrol class, but is simpler * because keystrokes don't have data parameters the way a MIDI event does. * It requires C++11 and above. */ #include "ctrl/keymap.hpp" /* seq66::qt_ordinal_keyname() */ #include "ctrl/opcontrol.hpp" /* seq66::opcontrol & automation */ namespace seq66 { /** * This class contains the control information for sequences that make up a * live set. * * Note that, although we've converted this to a full-fledged class, the * ordering of variables and the data arrays used to fill them is very * signifcant. See the midifile and optionsfile modules. */ class keycontrol : public opcontrol { friend class keycontainer; private: /** * The name used to represent any key that cannot be used. */ static const std::string scm_dead_key_name; /** * Provides the name of keystroke associated with this control. This * item is useful for displaying the assigned keystroke for debugging or * in the user interface. */ std::string m_key_name; /** * Provides the particular index for this keystroke control. This number * supplements the operation number, and applies to pattern controls and * mute-group controls, where the operation (slot) number covers a number * of controls: automation::slot::loop and automation::slot::mute_group. * * The operation (slot) number is used to choose the correct performance * function for the control. The index number is used to choose the * correct pattern or mute-group number. * * Provides the operation number, ranging from 0 to the maximum number of * keys supported, say 96 to 127. For a pattern control, this is the * pattern number. For a mute-group control, this is the group number. * For an automation control, this COULD BE the number of the performer * operation to call, of type automation::slot. */ int m_control_code; // pattern or mute-group number /** * The ordinal of this key-control. This is an index into the * keymap, and might be useful in the future to filter out certain ordinals * when processing the keys. For example, we might want to allow control * codes to be used in order to gain extra slots for automation controls to * which we will never map keystrokes, but need to provide for MIDI * control. * * Side note: Seq66 will never automate more than 254 functions. */ ctrlkey m_ordinal; public: /* * A default constructor is needed to provide a dummy object to return * when the desired one cannot be found. */ keycontrol () = default; /* * The move and copy constructors, the move and copy assignment operators, * and the destructors are all compiler generated. */ keycontrol ( const std::string & name, const std::string & keyname, automation::category opcategory, automation::action actioncode, automation::slot opnumber, int index ); keycontrol (const keycontrol &) = default; keycontrol & operator = (const keycontrol &) = default; keycontrol (keycontrol &&) = default; keycontrol & operator = (keycontrol &&) = default; virtual ~keycontrol () = default; std::string key_name () const { return m_key_name; } int control_code () const { return m_control_code; } /** * Performs a common test and returns the appropriate number, either the * control-code (for loop/pattern and mute-groups) or the slot-number * (for the automation group). */ int slot_control () const { return category_code() == automation::category::automation ? static_cast(slot_number()) : control_code() ; } public: void key_name (const std::string & kn) { m_key_name = kn; } /** * Builds a label for the key/MIDI control, which will include the loop * or group number if appropriate for the category of the control. */ std::string label () const { return build_slot_name(m_control_code); } void show (bool add_newline = true) const; ctrlkey ordinal () const { return m_ordinal; } bool is_ctrl_ordinal () const { return m_ordinal < 0x1f; } private: void ordinal (ctrlkey ck) { m_ordinal = ck; } }; // class keycontrol } // namespace seq66 #endif // SEQ66_KEYCONTROL_HPP /* * keycontrol.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/keymap.hpp ================================================ #if ! defined SEQ66_KEYMAP_HPP #define SEQ66_KEYMAP_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file keymap.hpp * * This module defines some informative functions that are actually * better off as functions. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-12 * \updates 2025-09-17 * \license GNU GPLv2 or above * */ #include /* std::string */ /** * This value is used for representing pattern, mute-group, and automation * keystrokes. It is meant to range from 0x00 to 0xff, and is also known * as the "ordinal", a unique value that can map to a control operation. */ using ctrlkey = unsigned char; /** * This value can hold the result of a call to QKeyEvent::key(). It is * common in GUI frameworks to have key-codes that require an unsigned value * to fit. */ using eventkey = unsigned; namespace seq66 { namespace keyboard { /** * Provides a list of supported keyboard layouts. * * Only the normal and azerty layouts are supported at this time. */ enum class layout { normal, /* U.S. key map */ qwerty = normal, /* Ditto */ qwertz, /* Deutsche */ azerty, /* French AZERTY key map */ max /* terminator value */ }; /** * Provides short names for these Qt::KeyboardModifier values, to make the * internal tables readable. Also used in the keystroke class. * * Note that it seems that the Alt-Gr key on a French keyboard might generate * a Ctrl-Alt in the Qt framework. * * On MacOS, KCTRL correspondes to the Command keys, and KMETA corresponds to * the Control keys. * * On Windows and Linux, the Windows key has KNONE on press, and KMETA on * release. */ using modifiers = enum kmod_t { KNONE = 0x00000000, KSHIFT = 0x02000000, KCTRL = 0x04000000, KCTRLSHIFT = KCTRL|KSHIFT, /* 0x06000000 */ KALT = 0x08000000, KCTRLALT = KCTRL|KALT, /* 0x08000000 */ KALTGR = KCTRL|KALT, /* 0x08000000 */ KMETA = 0x10000000, KEYPAD = 0x20000000, KPADSHIFT = KEYPAD|KSHIFT, KGROUP = 0x40000000 }; } // namespace keyboard /* * Free functions in the seq66 namespace. */ extern ctrlkey arrow_left (); extern ctrlkey arrow_up (); extern ctrlkey arrow_right (); extern ctrlkey arrow_down (); extern ctrlkey menu_key (); extern std::string modifier_names (unsigned kmod); extern unsigned modifier_code (const std::string & name); extern const std::string & undefined_qt_key_name (); extern bool is_undefined_qt_key (const std::string & keyname); extern int keymap_size (); extern bool is_invalid_ordinal (ctrlkey ordinal); extern unsigned ordinal_to_qt_key (ctrlkey ordinal); extern ctrlkey qt_modkey_ordinal ( eventkey qtkey, unsigned qtmodifier, eventkey virtkey = 0 ); extern std::string qt_modkey_name ( eventkey qtkey, unsigned qtmodifier, eventkey virtkey = 0 ); extern ctrlkey qt_keyname_ordinal (const std::string & name); extern std::string qt_ordinal_keyname (ctrlkey qtkey); extern void modify_keyboard_layout (keyboard::layout el); } // namespace seq66 #endif // SEQ66_KEYMAP_HPP /* * keymap.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/keystroke.hpp ================================================ #if ! defined SEQ66_KEYSTROKE_HPP #define SEQ66_KEYSTROKE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file keystroke.hpp * * This module declares/defines the base class for handling many facets * of using a GUI representation of keystrokes. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-09-21 * \updates 2025-09-17 * \license GNU GPLv2 or above * * This class is used for encapsulating keystrokes, and is used for some Qt 5 * processing. */ #include /* std::string class */ #include "ctrl/keymap.hpp" /* seq66::ctrlkey alias, etc. */ namespace seq66 { /** * Encapsulates any practical keystroke. Useful in passing more generic * events to non-GUI classes. */ class keystroke { public: /** * Provides readable values to indicate if a keystroke is a press or a * release. */ enum class action { release, press }; private: /** * Range limits for the various integer parameters. Used for * sanity-checking. */ static const ctrlkey sm_bad_value = 0x00; /* null */ static const ctrlkey sm_minimum = 0x01; /* Ctrl-A */ static const ctrlkey sm_maximum = 0xff; /** * Values from Qt 5. The commented values indicate their value in the * keymap module. */ static const eventkey sm_Qt_Backspace = 0x01000003; /* 0x83, 0xff08 */ static const eventkey sm_Qt_Delete = 0x01000007; /* 0x87, 0xffff */ static const eventkey sm_Qt_Left = 0x01000012; /* 0x92 */ static const eventkey sm_Qt_Up = 0x01000013; /* 0x93 */ static const eventkey sm_Qt_Right = 0x01000014; /* 0x94 */ static const eventkey sm_Qt_Down = 0x01000015; /* 0x95 */ /** * Determines if the key was a press or a release. See enum class * action. */ bool m_is_press; /** * The key that was pressed or released. Generally, the extended ASCII * range (0 to 0xff) is supported. However, Gtk-2.x/3.x and Qt 5.0 will * generally support the full gamut of characters, with codes that are * unsigned integers; and the modifiers might be needed for lookup. */ mutable ctrlkey m_key; /** * The optional modifiers value. Note that "keyboard::KNONE" is our word * for 0, meaning "no modifiers". */ keyboard::modifiers m_modifiers; public: keystroke () = default; keystroke ( ctrlkey key, bool press, unsigned modkey = static_cast(keyboard::KNONE) ); keystroke (const keystroke & rhs) = default; keystroke & operator = (const keystroke & rhs) = default; bool is_press () const { return m_is_press; } bool is_letter (ctrlkey ch = sm_bad_value) const; bool is_good () const { return m_key >= sm_minimum && m_key < sm_maximum; } /** * Tests the key value to see if it matches the given character exactly * (no case-insensitivity). * * \param ch * The character to be tested. * * \return * Returns true if m_key == ch. */ bool is (ctrlkey ch) const { return m_key == ch; } /** * Tests the key value to see if it matches the given character exactly * (no case-insensitivity). * * \param ch1 * The first character to be tested. * * \param ch2 * The second character to be tested. * * \return * Returns true if m_key == ch1 or ch2. */ bool is (ctrlkey ch1, ctrlkey ch2) const { return m_key == ch1 || m_key == ch2; } /* * The following functions support hard-wired usage of the arrow keys. */ bool is_left () const { return m_key == arrow_left(); } bool is_up () const { return m_key == arrow_up(); } bool is_right () const { return m_key == arrow_right(); } bool is_down () const { return m_key == arrow_down(); } bool is_menu () const { return m_key == menu_key(); } ctrlkey key () const { return m_key; } ctrlkey shifted () const; void shift_lock () { m_key = shifted(); } keyboard::modifiers modifiers () const { return m_modifiers; } void setmodifier (keyboard::modifiers km) { m_modifiers = km; } /** * \getter m_modifiers tested for Ctrl key. */ bool mod_control () const { return bool(m_modifiers & keyboard::KCTRL); } /** * \getter m_modifiers tested for Ctrl and Shift key. */ bool mod_control_shift () const { return mod_control() && (m_modifiers & keyboard::KSHIFT); } /** * \getter m_modifiers tested for Mod4/Super/Windows key. */ bool mod_super () const { return bool(m_modifiers & keyboard::KMETA); } void toupper (); /* changes m_key */ void tolower (); /* changes m_key */ ctrlkey upper () const; ctrlkey lower () const; std::string name () const; }; // class keystroke } // namespace seq66 #endif // SEQ66_KEYSTROKE_HPP /* * keystroke.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/midicontrol.hpp ================================================ #if ! defined SEQ66_MIDICONTROL_HPP #define SEQ66_MIDICONTROL_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midicontrol.hpp * * This module declares/defines the class for handling MIDI control data for * the application. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-09 * \updates 2021-11-27 * \license GNU GPLv2 or above * * This module defines a number of constants relating to control of pattern * unmuting, group control, and a number of additional controls to make Seq66 * controllable without a graphical user interface. * * It requires C++11 and above. * * Concept: * Status bits: * * See the top banner in the automation.cpp module. */ #include "ctrl/keycontrol.hpp" /* seq66::keycontrol class */ #include "midi/event.hpp" /* seq66::event class, bussbyte */ namespace seq66 { class event; /** * This class contains the MIDI control information for sequences that make * up a live set. It defines a single MIDI control. It is derived from * keycontrol so that we can store a whole control section stanza, including * the key name, in one configuration stanza. * * Note that, although we've converted this to a full-fledged class, the * ordering of variables and the data arrays used to fill them is very * significant. */ class midicontrol : public keycontrol { public: /** * Provides a key for looking up a MIDI control in the midicontainer. * When doing a lookup, the status and first data byte must match. Once * found, if the minimum and maximum byte values are not 0, then the * range is also checked. Also, the buss is now used, so that the * user can guarantee that only one device will control Seq66. It is * not part of the lookup, however. */ class key { friend class midicontrol; private: bussbyte m_buss; /**< Indicates the port of the event. */ midibyte m_status; /**< Provides the (incoming) event type. */ midibyte m_d0; /**< Provides the first byte, for searches. */ public: key () = default; key (const key &) = default; key & operator = (const key &) = default; ~key () = default; key (midibyte status, midibyte d0) : m_buss (null_buss()), m_status (status), m_d0 (d0) { // no code } key (const event & ev) : m_buss (ev.input_bus()), m_status (ev.get_status()), m_d0 (0) { ev.get_data(m_d0); } bool operator < (const key & rhs) const { return (m_status == rhs.m_status) ? (m_d0 < rhs.m_d0) : (m_status < rhs.m_status) ; } bussbyte buss () const { return m_buss; } midibyte status () const { return m_status; } midibyte d0 () const { return m_d0; } }; // nested class key private: /** * Provides the value for active. If false, this control will be * ignored. */ bool m_active; /** * Provides the value for inverse-active. */ bool m_inverse_active; /** * Provides the value for the status. Big question is, is the channel * included here? Yes. So the next question is, is it ignored? No. * A number of control devices (eg. the Launchpad */ midibyte m_status; /** * Provides the value for the first data byte of the event, d0. * Useful for searches and for incoming data. */ midibyte m_d0; /** * Provides the second data byte, d1. It is used to check that the * incoming d1 is in the range specified. Also, though not used yet, it * can further refine the operation of a MIDI control. */ midibyte m_d1; /** * Provides the minimum value for the second data byte of the event, d1, * if applicable. */ midibyte m_min_d1; /** * Provides the maximum value for the second data byte of the event, d1, * if applicable. */ midibyte m_max_d1; public: /* * A default constructor is needed to provide a dummy object to return * when the desired one cannot be found. The opcontrol::is_usable() * function will return false. */ midicontrol (); /* * The move and copy constructors, the move and copy assignment operators, * and the destructors are all compiler generated. */ midicontrol ( const std::string & keyname, automation::category opcategory, automation::action actioncode, automation::slot opnumber, int opcode ); midicontrol (const midicontrol &) = default; midicontrol & operator = (const midicontrol &) = default; midicontrol (midicontrol &&) = default; midicontrol & operator = (midicontrol &&) = default; virtual ~midicontrol () = default; bool active () const { return m_active; } bool inverse_active () const { return m_inverse_active; } int status () const { return m_status; } int d0 () const { return m_d0; } int d1 () const { return m_d1; } int min_d1 () const { return m_min_d1; } int max_d1 () const { return m_max_d1; } /* * This test does not include "inverse". */ bool blank () const { return ( ! m_active && m_status == 0 && m_d0 == 0 && m_d1 == 0 && m_min_d1 == 0 && m_max_d1 == 0 ); } bool set (int values [automation::SUBCOUNT]); /** * Handles a common check in the perform module. * * \param status * Provides the status byte, which is checked against m_status. * * \param d0 * Provides the data byte, which is checked against m_d0. */ bool match (midibyte status, midibyte d0) const { return ( m_active && (status == m_status) && (d0 == m_d0) ); } /** * Handles a common check in the perform module. */ bool in_range (midibyte d1) const { return d1 >= midibyte(m_min_d1) && d1 <= midibyte(m_max_d1); } public: key make_key () const { return key(m_status, m_d0); /* no usage of m_d1 needed here */ } bool merge_key_match (automation::category c, int opslot) const; void show (bool add_newline = true) const; }; // class midicontrol } // namespace seq66 #endif // SEQ66_MIDICONTROL_HPP /* * midicontrol.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/midicontrolbase.hpp ================================================ #ifndef SEQ66_MIDICONTROLBASE_HPP #define SEQ66_MIDICONTROLBASE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midicontrolbase.hpp * * This module declares/defines the base class for handling MIDI control * I/O of the application. * * \library seq66 application * \author C. Ahlstrom * \date 2019-11-25 * \updates 2022-08-25 * \license GNU GPLv2 or above * * Provides the base class for midicontrolout. * * Warning: * * It is NOT a base class for midicontrol or midicontrolin! */ #include "midi/midibytes.hpp" /* seq66::bussbyte data type */ namespace seq66 { /** * Provides some management support for MIDI control... on I/O. Many thanks * to igorangst! */ class midicontrolbase { friend class qseditoptions; friend class midicontrolfile; friend class performer; private: /** * A name to use for showing the contents of the container. */ std::string m_name; /** * Provides the MIDI I/O buss, that is the port number for MIDI I/O. * This value defaults to 0, and the user must be sure to avoid using * this buss value for music, or redefine the buss. This is the nominal * buss, which is read and saved, but not used for I/O; see m_true_buss * instead. */ bussbyte m_buss; /** * The true buss, which exists on the system. */ bussbyte m_true_buss; /** * Holds the original value read in from the 'ctrl' file. * It can be modified by an edit in Edit / Preferences, but * actually using it may need to be belayed (e.g. until application * exit completes. */ bussbyte m_configured_buss; /** * Indicates that this container is "empty". */ bool m_is_blank; /** * Indicates that this container is enabled or disabled. */ bool m_is_enabled; /** * Holds the original value read in from the 'ctrl' file. * It can be modified by an edit in Edit / Preferences, but * actually using it may need to be belayed (e.g. until application * exit completes. */ bool m_configure_enabled; /** * Offset provides a way to utilize a different portion of a controller * such as the Launchpad Mini. Currently just set to 0 while we work * things out. */ int m_offset; /** * Provides the number of rows, useful when the runtime number of rows * differs from that specified in the configuration file. We at least * want to avoid segfaults. */ int m_rows; /** * Provides the number of rows, useful when the runtime number of rows * differs from that specified in the configuration file. We at least * want to avoid segfaults. */ int m_columns; public: midicontrolbase (const std::string & name = ""); virtual ~midicontrolbase () = default; virtual bool initialize (int buss, int rows, int columns); /* base */ const std::string & name () const { return m_name; } bussbyte nominal_buss () const { return m_buss; } bussbyte true_buss () const { return m_true_buss; } bussbyte configured_buss () const { return m_configured_buss; } bool is_blank () const { return m_is_blank; } bool is_enabled () const { return m_is_enabled; } bool is_disabled () const { return ! is_enabled(); } bool configure_enabled () const { return m_configure_enabled; } int offset () const { return m_offset; } int rows () const { return m_rows; } int columns () const { return m_columns; } protected: void nominal_buss (bussbyte b) { m_buss = b; } void true_buss (bussbyte b) { if (is_good_buss(b)) m_true_buss = b; else is_enabled(false); } void configured_buss (bussbyte b) { m_configured_buss = b; } void is_blank (bool flag) { m_is_blank = flag; } void is_enabled (bool flag) { m_is_enabled = flag; } void configure_enabled (bool flag) { m_configure_enabled = flag; } void offset (int o) { if (o >= 0) /* more verification later */ m_offset = o; } void rows (int r) { if (r > 0) /* more verification later */ m_rows = r; } void columns (int c) { if (c > 0) /* more verification later */ m_columns = c; } }; // class midicontrolbase /* * Free functions. */ /** * Default MIDI control input buss. This value preserves the old behavior, * where the incoming MIDI events of a device on any buss would be acted on * (if specified in the MIDI control stanzas). This value is the same as * c_bussbyte_max in the midibytes.hpp module. It can be changed in the * 'ctrl' file. */ inline bussbyte default_control_in_buss () { return null_buss(); } /** * Default MIDI control output buss. It is used with igorangst's * MIDI-control-out feature at present. It can be changed in the 'ctrl' * file. */ inline bussbyte default_control_out_buss () { return null_buss(); } } // namespace seq66 #endif // SEQ66_MIDICONTROLBASE_HPP /* * midicontrolbase.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/midicontrolin.hpp ================================================ #if ! defined SEQ66_MIDICONTROLIN_HPP #define SEQ66_MIDICONTROLIN_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midicontrolin.hpp * * This module declares/defines the class for holding MIDI operation data for * the application. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-23 * \updates 2024-01-01 * \license GNU GPLv2 or above * * This container holds a map of midicontrol objects keyed by a key ordinal * number that can range from 0 to 255. * * It requires C++11 and above. */ #include /* std::map<> and multimap<> */ #include /* std::string */ #include "cfg/comments.hpp" /* seq66::comments class */ #include "ctrl/midicontrol.hpp" /* seq66::midicontrol event item */ #include "ctrl/midicontrolbase.hpp" /* seq66::midicontrolbase class */ namespace seq66 { class keycontainer; /** * Provides an object specifying what a keystroke, GUI action, or a MIDI * control should do. */ class midicontrolin final : public midicontrolbase { friend class midicontrolfile; friend class performer; public: /** * Provides the type definition for this container. The key of the * container is based on the control value itself. It is used to find * one or more instances of the MIDI control. Once found, the operation * associated with that control can be exercised. */ using mccontainer = std::multimap; private: /** * The container itself. */ mccontainer m_container; /** * Provides the text of a "[comments]" section of the MIDI control "ctrl" * file. It can, for example, note the device for which the controls * apply. */ comments m_comments_block; /** * Indicates if inactive controls are allowed to be added to the * container. When generating a "ctrl" file, all controls need to be * processed and appear in that file. */ bool m_inactive_allowed; /** * Holds the current control statuses for use by the performer. It * replaces Sequencer64's c_status_replace, c_status_snapshot, * c_status_queue, and c_status_oneshot. Functions are provided to query * and modify these values. */ automation::ctrlstatus m_control_status; /** * If true, there is at least one non-zero (i.e. functional) MIDI control * in the container. If this value if false, even if the container is * full of zeroed stanzas, the container is considered empty. */ bool m_have_controls; public: midicontrolin (const std::string & name); midicontrolin (const midicontrolin &) = default; midicontrolin & operator = (const midicontrolin &) = default; midicontrolin (midicontrolin &&) = default; midicontrolin & operator = (midicontrolin &&) = default; virtual ~midicontrolin () = default; virtual bool initialize (int buss, int rows, int columns) override; comments & comments_block () { return m_comments_block; } const comments & comments_block () const { return m_comments_block; } void clear () { m_container.clear(); } int count () const { return int(m_container.size()); } bool have_controls () const { return m_have_controls; } const mccontainer & container () const { return m_container; } bool add (const midicontrol & mc); void add_blank_controls (const keycontainer & kc); const midicontrol & control (const midicontrol::key & k) const; std::string status_string () const; bool inactive_allowed () const { return m_inactive_allowed; } void inactive_allowed (bool flag) { m_inactive_allowed = flag; } automation::ctrlstatus status () const { return m_control_status; } bool is_status () const { return m_control_status != automation::ctrlstatus::none; } bool is_set (automation::ctrlstatus status) const { return bit_test_and(status, m_control_status); } /* * Use test_and() or test_or()? We are testing for a single bit, so use * the "and" test. */ bool is_replace () const { return is_replace(m_control_status); } bool is_replace (automation::ctrlstatus status) const { return bit_test_and(status, automation::ctrlstatus::replace); } bool is_snapshot () const { return is_snapshot(m_control_status); } bool is_snapshot (automation::ctrlstatus status) const { return bit_test_and(status, automation::ctrlstatus::snapshot); } bool is_queue () const { return is_queue(m_control_status); } bool is_queue (automation::ctrlstatus status) const { return bit_test_and(status, automation::ctrlstatus::queue); } bool is_keep_queue () const { return is_keep_queue(m_control_status); } bool is_keep_queue (automation::ctrlstatus status) const { return bit_test_and(status, automation::ctrlstatus::keep_queue); } bool is_oneshot () const { return is_oneshot(m_control_status); } bool is_oneshot (automation::ctrlstatus status) const { return bit_test_and(status, automation::ctrlstatus::oneshot); } bool is_learn () const { return bit_test_and(m_control_status, automation::ctrlstatus::learn); } bool is_learn (automation::ctrlstatus status) const { return bit_test_and(status, automation::ctrlstatus::learn); } bool is_solo () const { return is_replace() && is_queue(); } bool is_solo (automation::ctrlstatus status) const { return is_replace(status) && is_queue(status); } void add_status (automation::ctrlstatus status) { m_control_status |= status; } void remove_status (automation::ctrlstatus status) { m_control_status &= ~status; } void clear_status () { m_control_status = automation::ctrlstatus::none; } void remove_queued_replace () { m_control_status &= ~(automation::ctrlstatus::queue | automation::ctrlstatus::replace); } public: void show () const; }; // class midicontrolin } // namespace seq66 #endif // SEQ66_MIDICONTROLIN_HPP /* * midicontrolin.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/midicontrolout.hpp ================================================ #ifndef SEQ66_MIDICONTROLOUT_HPP #define SEQ66_MIDICONTROLOUT_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midicontrolout.hpp * * This module declares/defines the class for handling MIDI control * output of the application. * * \library seq66 application * \author Igor Angst (major modifications by C. Ahlstrom) * \date 2018-03-28 * \updates 2025-07-12 * \license GNU GPLv2 or above * * The class contained in this file encapsulates most of the * functionality to send feedback to an external control surface in * order to reflect the state of seq66. This includes updates on * the playing and queueing status of the sequences. * */ #include /* std::vector<> */ #include "ctrl/midicontrolbase.hpp" /* seq66::midicontrolbase class */ #include "ctrl/midimacros.hpp" /* seq66::midimacros class */ #include "midi/event.hpp" /* seq66::event class */ #include "midi/mastermidibus.hpp" /* seq66::mastermidibus class */ namespace seq66 { class performer; /** * Provides some management support for MIDI control... on output. Many * thanks to igorangst! */ class midicontrolout final : public midicontrolbase { friend class midicontrolfile; friend class performer; public: /** * Provides the kind of MIDI control event that is sent out. * * \todo * Additional sequence actions to consider: record on, record off. * * \var play * Sequence is playing. * * \var mute * Sequence is muted. * * \var queue * Sequence is queued. * * \var removed * Sequence is deleted from its slot, or the slot is already empty. * * \var max * Marker for the maximum value of actions. */ enum class seqaction { armed, muted, queued, removed, max }; /** * Provides codes for various other actions. This enumeration will * replace the action enumeration. All items with have an On control and * an Off control, as well an an Inactive (dark) mode. This list * currently numbers 24 entries. We could double that by mapping all of * the 48 values of the automation::slot enumeration. However, some * (such as the Thru and Record actions) apply to specific sequences as * opposed to a single function, and would require two consecutive * controls to be clicked. Maybe later. Some are either reserved at * this time, are actions we don't yet care much about, or are less * common actions that can be done by leaning over to the computer * keyboard. */ enum class uiaction { panic, /* button 0 */ stop, /* button 1 */ pause, /* button 2 */ play, /* button 3 */ toggle_mutes, /* button 4 */ song_record, /* button 5 */ slot_shift, /* button 6 */ free, /* button 7 */ queue, /* button A */ oneshot, /* button B */ replace, /* button C */ snapshot, /* button D */ song_mode, /* button E */ learn, /* button F */ bpm_up, /* button G */ bpm_dn, /* button H */ list_up, /* extra 0 */ list_dn, /* extra 1 */ song_up, /* extra 2 */ song_dn, /* extra 3 */ set_up, /* extra 4 */ set_dn, /* extra 5 */ tap_bpm, /* extra 6 */ quit, /* extra 7 */ visibility, alt_2, alt_3, alt_4, alt_5, alt_6, alt_7, alt_8, max }; private: /** * Manifest constants for midicontrolfile to use as array indices. These * correspond to the MIDI Controls for UI (user-interface) actions; see * the uiactions enumeration. This enumeration cannot be a class * enumeration, because enum classes cannot be used as array indices. * * We dropped the enabled and channel values. We can test for an output * control to be enabled by checking for status > 0x00. And we can make * the channel part of the status. We will read the old style in the * midicontrolfile class and convert it to the new style. We change the * name of the enumeration for brevity and to uncover all usages via * compiler errors. :-D */ enum index { status, /**< A status byte, such as 0xb0 or 0x90 */ data_1, /**< The data byte, such as the note number. */ data_2, /**< More specific data, such a note velocity. */ max /**< A neverto-be used terminator/check value */ }; /** * Provides a type to hold a MIDI-control-out sequence event and its * status. There are four of these for each sequence slot, one for each * of the seqactions of arm, mute, queue, and remove. */ using actionpair = struct { bool apt_action_status; event apt_action_event; }; /** * Holds an array of actionpairs, one for each item in the actions * enumeration. These apply to pattern/sequence actions. */ using actions = std::vector; /** * Provides a type for a vector of action pairs, which can be essentially * unlimited in size. */ using actionlist = std::vector; /** * Provides a place to hold MIDI control events in response to a * user-interface change, such as starting or stopping playback. * Is also adapted to handling the toggling (on/off) of mute groups. */ using actiontriplet = struct { bool att_action_status; event att_action_event_on; event att_action_event_off; event att_action_event_del; /* inactive, show as dark */ }; /** * Matches which event in the actiontriplet to use. (Otherwise, a * boolean can be used to access only the "on" and "off" fields. */ enum actionindex { action_on, /**< The mute-group is active and selected. */ action_off, /**< The mute-group is active, but not selected. */ action_del /**< Mute-group or automation inactive. */ }; /** * Holds an array of actiontriplets, one for each item in the uiaction * enumeration. */ using uiactions = std::vector; /** * Provides a type for a vector of uiaction pairs, which can be * essentially unlimited in size. However, the number needed is * constrained to uiaction::max. */ using uiactionlist = std::vector; private: /** * Provides the MIDI master bus, provided by the performer class. * The midicontrolout class does not own this pointer, and assumes that * it is correct. */ mastermidibus * m_master_bus; /** * Provides the events to be sent out for sequence status changes. This * is a vector of vectors, by default of size 32 patterns by 4 * seqactions. */ actionlist m_seq_events; /** * Provides the events to be sent out for non-sequence actions. This * item is a vector of uiaction::max actiontriplets. */ uiactions m_ui_events; /** * Provides action events for toggling a mute-group. Handles the default * and unchanging value of 32 mutegroups. */ uiactions m_mutes_events; /** * New feature to output MIDI messages ("macros"), useful in setting up * Launchpads etc. */ midimacros m_macro_events; /** * Holds the screenset size, to use rather than calling the container. */ int m_screenset_size; public: midicontrolout (const std::string & name); midicontrolout (const midicontrolout &) = default; midicontrolout & operator = (const midicontrolout &) = default; virtual ~midicontrolout () = default; virtual bool initialize (int buss, int rows, int columns) override; static void seqaction_range (int & minimum, int & maximum) { minimum = static_cast(midicontrolout::seqaction::armed); maximum = static_cast(midicontrolout::seqaction::max); } void set_master_bus (mastermidibus * mmbus) { m_master_bus = mmbus; } int screenset_size () const { return m_screenset_size; } void send_seq_event (int seq, seqaction what, bool flush = true); void clear_sequences (bool flush = true); void clear_mutes (bool flush = true); event get_seq_event (int seq, seqaction what) const; void set_seq_event (int seq, seqaction what, int * ev); bool seq_event_is_active (int seq, seqaction what) const; bool event_is_active (uiaction what) const; std::string get_event_str (const event & ev) const; std::string get_ctrl_event_str (uiaction what, actionindex which) const; std::string get_mutes_event_str (int group, actionindex which) const; void set_event ( uiaction what, bool enabled, int * onp, int * offp, int * readyp ); void set_mutes_event ( int group, int * onp, int * offp, int * delp = nullptr ); bool mutes_event_is_active (int group) const; void send_mutes_event (int group, actionindex which); void send_event (uiaction what, actionindex which); void send_learning (bool learning); void send_automation (bool activate); int action_count () const { return int(m_ui_events.size()); } void clear_macros () { m_macro_events.clear(); } bool add_macro (const tokenization & tokens) { return m_macro_events.add(tokens); } int macro_count () const { return m_macro_events.count(); } bool macros_active () const { return m_macro_events.active(); } void macros_active (bool flag) { m_macro_events.active(flag); } bool expand_macros () { return m_macro_events.expand(); } void send_macro (const std::string & name, bool flush = true); std::string macro_lines () const { return m_macro_events.lines(); } tokenization macro_names () const { return m_macro_events.names(); } midibytes macro_bytes (const std::string & name) const { return m_macro_events.bytes(name); } const midimacro & macro (const std::string & name) const { return m_macro_events.macro(name); } std::string macro_byte_strings () const { return m_macro_events.byte_strings(); } bool make_macro_defaults () { return m_macro_events.make_defaults(); } }; // class midicontrolout /* * Free functions related to midicontrolout. */ extern midicontrolout::uiaction & operator ++ (midicontrolout::uiaction & e); extern std::string seqaction_to_string (midicontrolout::seqaction a); extern std::string action_to_string (midicontrolout::uiaction a); } // namespace seq66 #endif // SEQ66_MIDICONTROLOUT_HPP /* * midicontrolout.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/midimacro.hpp ================================================ #ifndef SEQ66_MIDIMACRO_HPP #define SEQ66_MIDIMACRO_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midimacro.hpp * * This module declares/defines the base class for handling MIDI control * I/O of the application. * * \library seq66 application * \author C. Ahlstrom * \date 2021-11-22 * \updates 2025-07-12 * \license GNU GPLv2 or above * * Provides the base class for midicontrolout. * * Warning: * * It is NOT a base class for midicontrol or midicontrolin! */ #include "midi/midibytes.hpp" /* seq66::midibytes data type */ #include "util/basic_macros.hpp" /* seq66::tokenization container */ namespace seq66 { /** * Represents a string of midibytes and provides the infrastructure for * reading them. */ class midimacro { friend class midimacros; private: /** * The name of the macro. This is also the key value for putting the * midimacro in a container. */ std::string m_name; /** * This is a list of tokens making up the macro. Although it can take up * extra space, it is useful to write the macro back to the configuration * file. It also allows putting multiple events into one macro. * * Also see the tokenize() function in the strfunctions module. */ tokenization m_tokens; /** * Provides the full list of midibytes to be sent via this macro after * expanding any macros it includes. */ midibytes m_bytes; /** * The number of events in the macro. Normally just one, unless * the vertical bar ("|") occurs in the list of tokens. */ int m_event_count; /** * Provides the midibytes for each separate event in a multiple-event * macro. Populated only if the separator bar ("|") was present. */ std::vector m_event_bytes; /** * Is the macro good? It is good if there is a name, if there's at least * one byte value or reference token, and the byte value isn't 0. * Even if invalid, the macro will be loaded and saved. */ bool m_is_valid; public: midimacro () = default; midimacro (const std::string & name, const std::string & values); midimacro (const midimacro &) = default; midimacro & operator = (const midimacro &) = default; midimacro (midimacro &&) = default; midimacro & operator = (midimacro &&) = default; ~midimacro () = default; const std::string & name () const { return m_name; } tokenization tokens () const { return m_tokens; } std::string line () const; const midibytes & bytes (int index = (-1)) const; int event_count () const { return m_event_count; } bool is_valid () const { return m_is_valid; } private: bool tokenize (const std::string & values); void name (const std::string & n) { m_name = n; } void bytes (const midibytes & b) { m_bytes = b; } void push_bytes (const midibytes & b) { m_event_bytes.push_back(b); } }; // class midimacro } // namespace seq66 #endif // SEQ66_MIDIMACRO_HPP /* * midimacro.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/midimacros.hpp ================================================ #ifndef SEQ66_MIDIMACROS_HPP #define SEQ66_MIDIMACROS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midimacros.hpp * * This module declares/defines the base class for handling MIDI control * I/O of the application. * * \library seq66 application * \author C. Ahlstrom * \date 2021-11-22 * \updates 2022-07-12 * \license GNU GPLv2 or above * * Provides the base class for midicontrolout. * * Warning: * * It is NOT a base class for midicontrol or midicontrolin! */ #include /* std::map container class */ #include "ctrl/midimacro.hpp" /* seq66::midimacro class */ namespace seq66 { /** * Represents a string of midibytes and provides the infrastructure for * reading them. */ class midimacros { public: static const std::string footer; static const std::string header; static const std::string reset; static const std::string startup; static const std::string shutdown; using container = std::map; private: /** * This is a list of tokens making up the macro. Although it can take up * extra space, it is useful to write the macro back to the configuration * file. Also see the tokenize() function in the strfunctions module. */ container m_macros; /** * We need a way to not emit startup and exit macros if the user doesn't * want that. */ bool m_active; public: midimacros (); midimacros (const midimacros &) = default; midimacros & operator = (const midimacros &) = default; midimacros (midimacros &&) = default; midimacros & operator = (midimacros &&) = default; ~midimacros () = default; void clear () { m_macros.clear(); } bool add (const tokenization & tokens); /* data from 'ctrl' file */ int count () const { return int(m_macros.size()); } bool active () const { return m_active; } void active (bool flag) { m_active = flag; } bool expand (); midibytes bytes (const std::string & name) const; const midimacro & macro (const std::string & name) const; std::string lines () const; tokenization names () const; std::string byte_strings () const; bool make_defaults (); private: void tokenize (); midibytes expand (midimacro & m); }; // class midimacros } // namespace seq66 #endif // SEQ66_MIDIMACROS_HPP /* * midimacros.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/midioperation.hpp ================================================ #if ! defined SEQ66_MIDIOPERATION_HPP #define SEQ66_MIDIOPERATION_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midioperation.hpp * * This module declares/defines the class for handling MIDI control data for * the application. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-13 * \updates 2021-12-07 * \license GNU GPLv2 or above * * This module defines a number of concepts relating to control of pattern * unmuting, group control, and a number of additional controls to make Seq66 * controllable without a graphical user interface. It is also used in * handling automation keystrokes. * * It requires C++11 and above. */ #include /* std::function<> */ #include /* std::vector<> */ #include "ctrl/opcontrol.hpp" /* seq66::opcontrol & automation */ namespace seq66 { /** * Provides an object specifying what a keystroke, GUI action, or a MIDI * control should do. */ class midioperation { public: /** * Provides the function object with a signature needed to handle any * MIDI control operation. The parameters are: * * - Action code. * - First data byte, d0. * - Second data byte, d1. [New as of 2021-11-11, 0.97.3] * - Control code (index). * - Inverse active. * * For keystrokes, the d0 value is always (-1). The boolean holds the * state of the inverse setting for MIDI control, and is always false for * key-releases; these are generally ignored. * * For a usage compatible with this type alias, see the type alias * performer::automation_function. */ using functor = std::function; private: /** * Names the operation for use in various human-readble purposes. * An example would be "BPM Page Up". */ std::string m_op_name; /** * Which section of the "rc" control file is this operation in? * Pattern, Mute_group, or Automation? */ automation::category m_op_category; /** * Provides the operation number, ranging from 0 to the maximum number of * keys supported, say 96 to 127. For a pattern control, this is the * pattern number. For a mute-group control, this is the group number. * For an automation control, this is the number of the performer * operation to call. */ automation::slot m_op_number; /** * Holds the function that the caller wants to call for this * midioperation object. */ functor m_parent_function; public: midioperation (); midioperation ( const std::string & opname, automation::category opcategory, automation::slot opnumber, functor pfunction ); midioperation (const midioperation &) = default; midioperation & operator = (const midioperation &) = default; midioperation (midioperation &&) = default; midioperation & operator = (midioperation &&) = default; ~midioperation () = default; bool is_usable () const { return m_op_category != automation::category::none; } /** * Calls the function that was registered with this operation. This call * will not alter this object. */ bool call ( automation::action a, int d0, int d1, int index, bool inverse ) const { return m_parent_function(a, d0, d1, index, inverse); } const std::string & name () const { return m_op_name; } automation::category cat_code () const { return m_op_category; } std::string cat_name () const { return opcontrol::category_name(m_op_category); } automation::slot number () const { return m_op_number; } std::string slot_name () const { return opcontrol::automation_slot_name(m_op_number); } public: void show () const; }; // class midioperation } // namespace seq66 #endif // SEQ66_MIDIOPERATION_HPP /* * midioperation.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/opcontainer.hpp ================================================ #if ! defined SEQ66_OPCONTAINER_HPP #define SEQ66_OPCONTAINER_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file opcontainer.hpp * * This module declares/defines the class for holding MIDI operation data for * the application. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-13 * \updates 2019-02-11 * \license GNU GPLv2 or above * * This container holds a map of midioperation objects keyed by an operation * number that can range from 0 to 255. This container is to be used in two * contexts: * * - Keystroke. When a keystroke is received by the user interface: * - The key-event callback will use the key-event keycode and * modifier to look up the ordinal for the key. This * standardizes the set of keys supported. * - This ordinal is then used to look up what was was configured * in one of three MIDI control sections in * the "rc" file: * - The operation category (pattern, mute-group, or * automation). * - The operation number (which pattern, mute-group, or * automation control. * - The value for this operation number is a midioperation object * that can be queried, if need be, to get the name of the * operation, the category, and even the operation number. But * in most cases, all that the caller will want to do is execute * the midioperation::call() function. * - MIDI. If an incoming MIDI event is found in the list of supported * MIDI events, which will also obtain the proper ordinal/operation * number to look up the operation, as in the three sub-steps above. * * After the midioperation is obtained, the caller will execute * midioperation::call(), passing to it the desired automation::action and * the two data values. * * It requires C++11 and above. */ #include /* std::map<> */ #include /* std::string */ #include "ctrl/midioperation.hpp" /* seq66::midioperation */ namespace seq66 { /** * Provides an object specifying what a keystroke, GUI action, or a MIDI * control should do. */ class opcontainer { public: /** * Provides the type definition for this container. The key is the * operation number (generally ranging from 0 to 31 for each * automation::category), and the value is a midioperation object. */ using opmap = std::map; private: /** * The container itself. */ opmap m_container; /** * A name to use for showing the contents of the container. */ std::string m_container_name; private: opcontainer (); public: opcontainer (const std::string & name); opcontainer (const opcontainer &) = default; opcontainer & operator = (const opcontainer &) = default; opcontainer (opcontainer &&) = default; opcontainer & operator = (opcontainer &&) = default; ~opcontainer () = default; const std::string & name () const { return m_container_name; } void clear () { m_container.clear(); } bool add (const midioperation & op); const midioperation & operation (automation::slot) const; public: void show () const; }; // class opcontainer } // namespace seq66 #endif // SEQ66_OPCONTAINER_HPP /* * opcontainer.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/ctrl/opcontrol.hpp ================================================ #if ! defined SEQ66_OPCONTROL_HPP #define SEQ66_OPCONTROL_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file opcontrol.hpp * * This module declares/defines the class for handling MIDI control data for * the application. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-12-04 * \updates 2023-09-05 * \license GNU GPLv2 or above * * This module defines a number of constants relating to control of pattern * unmuting, group control, and a number of additional controls to make Seq66 * controllable without a graphical user interface. Requires C++11 and * above. */ #include /* std::string */ #include /* std::vector<> */ #include "ctrl/automation.hpp" /* namespace seq66::automation */ namespace seq66 { /** * Provides an object that supports some enumerations to indicate what kind * of control this is. This class is a base class for the key and MIDI control * classes. */ class opcontrol { public: /** * Indicates that a category or other integer operation is not valid. */ static const int INVALID = (-1); private: /** * The name of the opcontrol object. */ std::string m_name; /** * Which section of the "rc" control file is this operation in? * Pattern, Mute_group, or Automation? */ automation::category m_category; /** * Indicates if the automation operation is a toggle, an on, or an off. */ automation::action m_action; /** * Provides the operation number. For a pattern control, this is the * slot number to obtain the loop-control midioperation object. For a * mute-group control, this is the group number to obtain the * mute-group-control midioperation object. For an automation control, * this is the number of the performer operation to call, of type * automation::slot. The values above automation::slot::max are used * for pattern and mute-group function selection. */ automation::slot m_slot_number; public: opcontrol (); opcontrol ( const std::string & opname, automation::category opcategory, automation::action opaction, automation::slot opnumber, int index = 0 ); opcontrol (const opcontrol &) = default; opcontrol & operator = (const opcontrol &) = default; opcontrol (opcontrol &&) = default; opcontrol & operator = (opcontrol &&) = default; virtual ~opcontrol () = default; static std::string category_name (automation::category c); static std::string action_name (automation::action a); static std::string automation_slot_name (automation::slot s); static automation::slot set_slot (int opcode); bool is_usable () const { return ( m_category != automation::category::none && m_action != automation::action::none && m_slot_number != automation::slot::none ); } bool is_glearn_control () const /* a special case */ { return ( m_category == automation::category::automation && m_slot_number == automation::slot::mod_glearn ); } /** * An operation is allowed if it is either not a keystroke (d0 == -1) or * is not inverse (true for keystroke release). */ static bool allowed (int d0, bool inverse) { return d0 >= 0 || ! inverse; } /** * Simplifies this check for the callers. */ static bool is_automation (automation::category cat) { return cat == automation::category::automation; } /** * Simplifies this check for the callers. */ static bool is_sequence_control (automation::category cat) { return cat == automation::category::loop || cat == automation::category::mute_group; } const std::string & name () const { return m_name; } automation::category category_code () const { return m_category; } std::string category_name () const { return category_name(m_category); } automation::action action_code () const { return m_action; } std::string action_name () const { return action_name(m_action); } automation::slot slot_number () const { return m_slot_number; } std::string automation_slot_name () const { return automation_slot_name(m_slot_number); } std::string build_slot_name (int index) const; }; // class automation /* * ---------------------------------------------------------------------- * Free functions in the seq66 namespace * ---------------------------------------------------------------------- */ extern std::string auto_name (automation::slot s); } // namespace seq66 #endif // SEQ66_OPCONTROL_HPP /* * opcontrol.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/businfo.hpp ================================================ #if ! defined SEQ66_BUSINFO_HPP #define SEQ66_BUSINFO_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file businfo.hpp * * This module declares/defines the Master MIDI Bus base class. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-12-31 * \updates 2023-06-28 * \license GNU GPLv2 or above * * The businfo module defines the businfo and busarray classes so that we can * start avoiding arrays and explicit access to them. * * The businfo class holds a pointer to its midibus object. * * The busarray class holds a number of businfo classes, and two busarrays * are maintained, one for input and one for output. */ #include /* std::shared_ptr<> */ #include /* for containing the bus objects */ #include "midi/midibus_common.hpp" /* enum class e_clock */ #include "midi/midibus.hpp" /* seq66::midibus */ namespace seq66 { class event; class midibus; /** * A new class to consolidate a number of bus-related arrays into one array. * There will be in input instances and an output instance of this object * contained by mastermidibus. Inputs will be in one container, and output in * another container. */ class businfo { friend class busarray; private: /** * Points to an existing midibus object. */ std::shared_ptr m_bus; /** * Indicates if the existing bus is active. */ bool m_active; /** * Indicates if the existing bus is initialized. */ bool m_initialized; /** * Clock initialization, if this businfo is stored in an output container. */ e_clock m_init_clock; /** * Input initialization, if the businfo is stored in an output container. */ bool m_init_input; public: businfo () = delete; businfo (midibus * bus); businfo (const businfo & rhs); ~businfo () = default; // the bus pointer is self-deleting /** * Deletes and nullifies the m_bus pointer. */ void remove () { if (bool(m_bus)) m_bus.reset(); } const midibus * bus () const { return m_bus.get(); } midibus * bus () { return m_bus.get(); } bool active () const { return m_active; } bool initialize (); bool initialized () const { return m_initialized; } e_clock init_clock () const { return m_init_clock; } bool init_input () const { return m_init_input; } public: void activate () { m_active = m_initialized = true; } void deactivate () { m_active = m_initialized = false; } void init_clock (e_clock clocktype) { m_init_clock = clocktype; if (not_nullptr(bus())) bus()->set_clock(clocktype); } void init_input (bool flag) { m_init_input = flag; /* * When clicking on the MIDI Input item, this is not needed... * it disables the detection of a change, so that init() and deinit() * do not get called. * * When starting up we need to honor the init-input flag if it is * set, and init() the bus. But we don't need to call deinit() at * startup if it is false, since init() hasn't been called yet. */ if (not_nullptr(bus())) bus()->set_io_status(flag); } private: void start () { bus()->start(); } void stop () { bus()->stop(); } void continue_from (midipulse tick) { bus()->continue_from(tick); } void init_clock (midipulse tick) { bus()->init_clock(tick); } void clock (midipulse tick) { bus()->clock(tick); } void sysex (const event * ev) { bus()->sysex(ev); } private: void print () const; }; // class businfo /** * Holds a number of businfo objects. */ class busarray { private: /** * The full set of businfo objects, only some of which will actually be * used. */ std::vector m_container; public: busarray (); ~busarray (); bool add (midibus * bus, e_clock clock); bool add (midibus * bus, bool inputing); bool initialize (); int count () const { return int(m_container.size()); } midibus * bus (bussbyte b) { return b < bussbyte(count()) ? m_container[b].bus() : nullptr ; } int client_id (bussbyte b) { return b < bussbyte(count()) ? m_container[b].bus()->client_id() : 0 ; } /** * Starts all of the busses; used for output busses only, but no check is * made at present. */ void start () { for (auto & bi : m_container) /* vector of businfo copies */ bi.start(); } /** * Stops all of the busses; used for output busses only, but no check is * made at present. */ void stop () { for (auto & bi : m_container) /* vector of businfo copies */ bi.stop(); } /** * Continues from the given tick for all of the busses; used for output * busses only. * * \param tick * Provides the tick value for all busses to continue from. */ void continue_from (midipulse tick) { for (auto & bi : m_container) /* vector of businfo copies */ bi.continue_from(tick); } /** * Initializes the clocking at the given tick for all of the busses; used * for output busses only. * * \param tick * Provides the tick value for all busses use as the clock tick. */ void init_clock (midipulse tick) { for (auto & bi : m_container) /* vector of businfo copies */ bi.init_clock(tick); } /** * Clocks at the given tick for all of the busses; used for output busses * only. * * \param tick * Provides the tick value for all busses use as the clock tick. */ void clock (midipulse tick) { for (auto & bi : m_container) /* vector of businfo copies */ bi.clock(tick); } void play (bussbyte bus, const event * e24, midibyte channel); void sysex (bussbyte bus, const event * ev); bool set_clock (bussbyte bus, e_clock clocktype); /** * Sets the clock type for all busses, usually the output buss. Note that * the settings to apply are added when the add() call is made. This is a * bit ugly. */ void set_all_clocks () { for (auto & bi : m_container) /* vector of businfo copies */ bi.bus()->set_clock(bi.init_clock()); } e_clock get_clock (bussbyte bus) const; std::string get_midi_bus_name (int bus) const; /* full display name! */ std::string get_midi_port_name (int bus) const; /* without the client */ std::string get_midi_alias (int bus) const; void print () const; void port_exit (int client, int port); bool set_input (bussbyte bus, bool inputing); /** * Set the status of all input busses. There's no implementation-specific * API function here. This function should be used only for the input * busarray, obviously. Note that the input settings used here were stored * when the add() function was called. They can be changed by the user via * the Options / MIDI Input tab. */ void set_all_inputs () { for (auto & bi : m_container) /* vector of businfo copies */ bi.bus()->set_input(bi.init_input()); } bool get_input (bussbyte bus) const; bool is_system_port (bussbyte bus) const; bool is_port_unavailable (bussbyte bus) const; bool is_port_locked (bussbyte bus) const; int poll_for_midi (); bool get_midi_event (event * inev); int replacement_port (int bus, int port); }; // class busarray /* * Free functions */ extern void swap (busarray & buses0, busarray & buses1); } // namespace seq66 #endif // SEQ66_BUSINFO_HPP /* * businfo.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/calculations.hpp ================================================ #if ! defined SEQ66_CALCULATIONS_HPP #define SEQ66_CALCULATIONS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file calculations.hpp * * This module declares/defines some common calculations needed by the * application. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-11-07 * \updates 2025-07-24 * \license GNU GPLv2 or above * * These items were moved from the globals.h module so that only the modules * that need them need to include them. Also included are some minor * "utility" functions dealing with MIDI and port-related strings. Many of * the functions are defined in this header file, as inline code. */ #include "midi/midibytes.hpp" /* midipulse alias and much more */ #include "util/basic_macros.hpp" /* seq66::tokenization container */ /** * This is supposed to be a better method than rand(), but it still * yields, seemingly, more negative numbers than positive numbers. */ #undef SEQ66_USE_UNIFORM_INT_DISTRIBUTION /** * Most of the "pulses-to-xxx" functions have been moved to the zoomer * class. The definitions in the current module have issues. */ #undef SEQ66_USE_EXTRA_PULSE_CALCULATIONS /* * Global functions in the seq66 namespace for MIDI timing calculations. */ namespace seq66 { /** * Indicates what kind of snap movement to apply in the snapped() template * function. */ enum class snapper { down, closest, up }; /** * Provides a clear enumeration of wave types supported by the wave function. * See qlfoframe. */ enum class waveform { none = 0, /**< No waveform, never used. */ sine, /**< Sine wave modulation. */ sawtooth, /**< Saw-tooth (ramp) modulation. */ reverse_sawtooth, /**< Reverse saw-tooth (decay). */ triangle, /**< No waveform, never used. */ exponential, /**< A partial exponential rise. */ reverse_exponential, /**< A partial exponential fall. */ dc, /**< DC offset adjustment only. */ max /**< Illegal value. */ }; inline int cast (waveform wv) { return static_cast(wv); } inline waveform waveform_cast (int v) { return static_cast(v); } /** * Provides the short list of options for fixing a pattern length in the * qpatternfix dialog. */ enum class lengthfix { none = 0, /**< Not adjusting pattern length directly. */ measures, /**< The user sets the desired measures. */ rescale, /**< The user wants to rescale the pattern. */ max /**< Illegal value. */ }; inline int cast (lengthfix lv) { return static_cast(lv); } inline lengthfix lengthfix_cast (int v) { return static_cast(v); } /** * Provides the short list of options for the type of alteration used in * the qpatternfix dialog. Might be useful elsewhere as well. Note the * automation::slot values shown. */ enum class alteration { none = 0, /**< grid_quant_none: No adjustment of pattern. */ tighten, /**< grid_quant_tighten: Adjust timing less halfway. */ quantize, /**< grid_quant_full: Adjust timing strictly. */ jitter, /**< grid_quant_jitter: Randomize timing slightly. */ random, /**< grid_quant_random: Randomize event magnitude. */ random_pitch, /**< ------------------ Randomize note pitches. */ notemap, /**< grid_quant_notemap: Apply configured note-mapping. */ rev_notemap, /**< Apply the note-map in the reverser direction. */ max /**< Illegal value. */ }; inline int cast (alteration lv) { return static_cast(lv); } inline alteration quantization_cast (int v) { return static_cast(v); } /** * Manifest constants for the "Applied Effects" GroupBox in qpatternfix. */ enum class fixeffect { none = 0x00, alteration = 0x01, shifted = 0x02, reversed = 0x04, reversed_abs = 0x08, /* short for "reversed in place" */ shrunk = 0x10, expanded = 0x20, time_sig = 0x40, all = 0x7F }; /** * Tests that the rhs value's bit(s) is (are) set in the lhs value. */ inline bool bit_test (fixeffect lhs, fixeffect rhs) { return (static_cast(lhs) & static_cast(rhs)) != 0; } /** * Grabs the bit(s) from rhs and OR's them into lhs, and returns the * new value of lhs. */ inline fixeffect bit_set (fixeffect lhs, fixeffect rhs) { int L = static_cast(lhs) | static_cast(rhs); lhs = static_cast(L); return lhs; } /* * Free functions in the seq66 namespace. */ extern std::string wave_type_name (waveform wv); extern int extract_timing_numbers ( const std::string & s, std::string & part_1, std::string & part_2, std::string & part_3, std::string & fraction ); extern int tokenize_string ( const std::string & source, tokenization & tokens ); extern std::string pulses_to_string (midipulse p); extern std::string pulses_to_measurestring ( midipulse p, const midi_timing & seqparms ); extern bool pulses_to_midi_measures ( midipulse p, const midi_timing & seqparms, midi_measures & measures ); extern double pulses_to_measures ( midipulse p, int P, int B, int W ); extern std::string pulses_to_time_string ( midipulse p, const midi_timing & timinginfo ); extern std::string pulses_to_time_string ( midipulse pulses, midibpm bp, int ppq, bool showus = true ); extern int pulses_to_hours (midipulse pulses, midibpm bp, int ppq); extern double trunc_measures (double measures); extern midipulse measurestring_to_pulses ( const std::string & measures, const midi_timing & seqparms ); extern midipulse midi_measures_to_pulses ( const midi_measures & measures, const midi_timing & seqparms ); extern midi_measures string_to_measures (const std::string & bbt); extern midipulse timestring_to_pulses ( const std::string & timestring, int bpm, int ppq ); extern midipulse string_to_pulses ( const std::string & s, const midi_timing & mt, bool timestring = false ); extern int randomize (int range, int seed = 0); #if defined SEQ66_USE_UNIFORM_INT_DISTRIBUTION extern int randomize_uniformly (int range, int seed = -1); #endif extern bool is_power_of_2 (int value); extern int log2_of_power_of_2 (int tsd); extern int beat_power_of_2 (int logbase2); extern int previous_power_of_2 (int value); extern int next_power_of_2 (int value); extern int power (int base, int exponent); extern midibyte beat_log2 (int value); extern midibpm tempo_us_from_bytes (const midibytes & tt); extern bool tempo_us_to_bytes (midibytes & t, midibpm tempo_us); extern midibyte tempo_to_note_value (midibpm tempo); extern midibpm note_value_to_tempo (midibyte tempo); extern midibpm fix_tempo (midibpm bp); extern int midi_data_adjust (int invalue, int reduction); extern unsigned short combine_bytes (midibyte b0, midibyte b1); extern midibpm note_value_to_tempo (midibyte note); extern midilong extract_varinum (const midibytes & data, int & index); extern midipulse rescale_tick (midipulse tick, int newppqn, int oldppqn); /** * Converts tempo (e.g. 120 beats/minute) to microseconds. * This function is the inverse of bpm_from_tempo_us(). * * \param bp * The value of beats-per-minute. If this value is 0, we'll get an * arithmetic exception. * * \return * Returns the tempo in qn/us. If the bpm value is 0, then 0 is * returned. */ inline double tempo_us_from_bpm (midibpm bp) { return bp > 0.009999999 ? (60000000.0 / bp) : 0.0 ; } /** * This function calculates the effective beats-per-minute based on the value * of a Tempo meta-event. The tempo event's numeric value is given in 3 * bytes, and is in units of microseconds-per-quarter-note (us/qn). * * \param tempous * The value of the Tempo meta-event, in units of us/qn. If this value * is 0, we'll get an arithmetic exception. * * \return * Returns the beats per minute. If the tempo value is 0, then 0 is * returned. */ inline midibpm bpm_from_tempo_us (double tempous) { return tempous >= 1.0 ? (60000000.0 / tempous) : 0.0 ; } /** * Provides a direct conversion from a midibyte array to the beats/minute * value. * * It might be worthwhile to provide an std::vector version at some point. * * \param t * The 3 tempo midibytes that were read directly from a MIDI file. */ inline midibpm bpm_from_bytes (const midibytes & t) { return bpm_from_tempo_us(tempo_us_from_bytes(t)); } /** * Calculates pulse-length from the BPM (beats-per-minute) and PPQN * (pulses-per-quarter-note) values. The formula for the pulse-length in * seconds is: * \verbatim 60 P = ------------ BPM * PPQN \endverbatim * * An alternate calculation is 60000000.0 / ppq / bp, but we can save * a division operation. * * \param bp * Provides the beats-per-minute value. No sanity check is made. If * this value is 0, we'll get an arithmetic exception. * * \param ppq * Provides the pulses-per-quarter-note value. No sanity check is * made. If this value is 0, we'll get an arithmetic exception. * * \return * Returns the pulse length in microseconds. If either parameter is * invalid, then this function will crash. :-D */ inline double pulse_length_us (midibpm bp, int ppq) { return 60000000.0 / double(bp * ppq); } /** * Converts delta time in microseconds to ticks. This function is the * inverse of ticks_to_delta_time_us(). * * Please note that terms "ticks" and "pulses" are equivalent, and refer to * the "pulses" in "pulses per quarter note". * \verbatim beats pulses 1 minute 1 sec P = 120 ------ * 192 ------ * T us * --------- * --------- minute beats 60 sec 1,000,000 us \endverbatim * * Note that this formula assumes that a beat is a quarter note. If a beat * is an eighth note, then the P value would be halved, because there would * be only 96 pulses per beat. We will implement an additional function to * account for the beat; the current function merely blesses some * calculations made in the application. * * \param us * The number of microseconds in the delta time. * * \param bp * Provides the beats-per-minute value, otherwise known as the "tempo". * * \param ppq * Provides the pulses-per-quarter-note value, otherwise known as the * "division". * * \return * Returns the tick value. */ inline double delta_time_us_to_ticks (unsigned long us, midibpm bp, int ppq) { return double(bp * ppq * (us / 60000000.0f)); } /** * Converts the time in ticks ("clocks") to delta time in microseconds. * The inverse of delta_time_us_to_ticks(). * * Please note that terms "ticks" and "pulses" are equivalent, and refer to * the "pulses" in "pulses per quarter note". * * Old: 60000000.0 * double(delta_ticks) / (double(bp) * double(ppq)); * * \param delta_ticks * The number of ticks or "clocks". * * \param bp * Provides the beats-per-minute value, otherwise known as the "tempo". * * \param ppq * Provides the pulses-per-quarter-note value, otherwise known as the * "division". * * \return * Returns the time value in microseconds. */ inline double ticks_to_delta_time_us (midipulse delta_ticks, midibpm bp, int ppq) { return double(delta_ticks) * pulse_length_us(bp, ppq); } /** * The MIDI beat clock (also known as "MIDI timing clock" or "MIDI clock") is * a clock signal that is broadcast via MIDI to ensure that several * MIDI-enabled devices or sequencers stay in synchronization. Do not * confuse it with "MIDI timecode". * * The standard MIDI beat clock ticks every 24 times every quarter note * (crotchet). See midibytes.hpp 'c_midi_clocks_per_metronome'. * * Unlike MIDI timecode, the MIDI beat clock is tempo-dependent. Clock events * are sent at a rate of 24 PPQN (pulses per quarter note). Those pulses are * used to maintain a synchronized tempo for synthesizers that have * BPM-dependent voices and also for arpeggiator synchronization. * The following value represents the standard MIDI clock rate in * beats-per-quarter-note. */ inline int midi_clock_beats_per_qn () { return c_midi_clocks_per_metronome; /* 24 */ } /** * A simple calculation to convert PPQN to MIDI clock ticks, which are * emitting 24 times per quarter note. * * \param ppq * The number of pulses per quarter note. For example, the default value * for Seq24 is 192. * * \return * The integer value of ppq / 24 [MIDI clock PPQN] is returned. */ inline int clock_ticks_from_ppqn (int ppq) { return ppq / midi_clock_beats_per_qn(); } /** * A simple calculation to convert PPQN to MIDI clock ticks. The same as * clock_ticks_from_ppqn(), but returned as a double float. * * \param ppq * The number of pulses per quarter note. * * \return * The double value of ppq / 24 [midi_clock_beats_per_qn] is returned. */ inline double double_ticks_from_ppqn (int ppq) { return ppq / double(midi_clock_beats_per_qn()); } /** * This provides a kind of fundamental value. See measures_to_ticks() and * midi_clock_beats_per_qn() */ inline double qn_per_beat (int bw = 4) { return (bw > 0) ? 4.0 / double(bw) : 1.0 ; } /** * We are moving some calculations into the zoomer class. */ #if defined SEQ66_USE_EXTRA_PULSE_CALCULATIONS /** * OBSOLETE. * * Calculates the pulses per measure. This calculation is extremely simple, * and it provides an important constraint to pulse (ticks) calculations: the * default number of pulses in a measure is always 4 times the PPQN value, * regardless of the time signature. The number pulses in a 7/8 measure is * *not* the same as in a 4/4 measure. */ inline int default_pulses_per_measure (int ppq, int bpb = 4) { return ppq * bpb; /* this is wrong anyway */ } /** * Factors in the number of beats in a measure. */ inline int pulses_per_measure (int ppq, int bpb = 4, int bw = 4) { return (bw > 0) ? 4 * ppq * bpb / bw : ppq * bpb ; } /** * Calculates the number of pulses in a quarter beat, with an adjustment * for 120 and 240 PPQN. */ inline int pulses_per_quarter_beat (int ppq, int bpb = 4, int bw = 4) { return (bw > 0) ? ppq * bpb / bw : ppq ; } #endif // defined SEQ66_USE_EXTRA_PULSE_CALCULATIONS /** * Calculates the pulses in a beat. For a 4/4 time signature, this is the * same as PPQN. * * Now, pulses/beat should be the same as qn/beat x pulse/qn. So we do not * need the number of beats, just the beatwidth. This is wrong: * * return ppq * bpb / bw; * * Used only in the metro class. Well, also in this module and in * sequence::analyze_time_signatures(). Compare to qn_per_bear() above. */ inline int pulses_per_beat (int ppq, int bw = 4) { return (bw > 0) ? 4 * ppq / bw : ppq ; } /* * Defined in the cpp file, but they are macroed out. Kept around for your * information only. * * int pulses_per_substep (midipulse ppq, int zoom) * int pulses_per_pixel (midipulse ppq, int zoom = 2) */ /** * Calculates the length of an integral number of measures, in ticks. * This function is called in seqedit::apply_length(), when the user * selects a sequence length in measures. That function calculates the * length in ticks. The number of pulses is given by the number of quarter * notes times the pulses per quarter note. The number of quarter notes is * given by the measures times the quarter notes per measure. The quarter * notes per measure is given by the beats per measure times 4 divided by * beat_width beats. So: * \verbatim p = 4 * P * M * B / W p == pulse count (ticks or pulses) M == number of measures B == beats per measure (constant) P == pulses per quarter-note (constant) W == beat width in beats per measure (constant) \endverbatim * * Testing the units to make sure they cancel out to "pulses": * \verbatim 4 qn pulses beats p pulses = --- ---- x P ------ x B ----- x M bars W beat qn bar \endverbatim * * For our "b4uacuse" MIDI file, M can be about 100 measures, B is 4, * P can be 192 (but we want to support higher values), and W is 4. * So p = 100 * 4 * 4 * 192 / 4 = 76800 ticks. (19200 = 7680000 ticks). * * Note that 4 * P is a constraint encapsulated by the inline function * default_pulses_per_measure() using the default value of beats. * Also note that 4 / W is calculable using qn_per_beat(). * * \param bpb * The B value in the equation, beats/measure or beats/bar. * * \param ppq * The P value in the equation, pulses/qn. * * \param bw * The W value in the equation, the denominator of the time signature. * If this value is 0, we'll get an arithmetic exception (crash), so we * just return 0 in this case. The quantity 4 / W is in units of * quarter-notes/beat. So a beat-width of 8 is 1/2 qn/beat, or * an eighth note. * * \param measures * The M value in the equation. It defaults to 1, in case one desires a * simple "ticks per measure" number. * * \return * Returns the L value (ticks or pulses) as calculated via the given * equation. If bw is 0, then 0 is returned. */ inline midipulse measures_to_ticks (int bpb, int ppq, int bw, int measures = 1) { return (bw > 0) ? midipulse(4 * ppq * bpb * measures / bw) : 0 ; } /** * The inverse of measures_to_ticks(). Note that callers who want to * display the measure number to a user should add 1 to it. * * Compare this function to pulses_to_measures(), which returns a double. * * \param B * The B value in the equation, beats/measure or beats/bar. * * \param P * The P value in the equation, pulses/qn. * * \param W * The W value in the equation, the denominator of the time signature, * the beat-width. If this value is 0, we'll get an arithmetic exception * (crash), so we just return 0 in this case. * * \param p * The p (pulses) value in the equation. * * \return * Returns the M value (measures or bars, re 0) as calculated via the * inverse equation. If P or B are 0, then 0 is returned. */ inline int ticks_to_measures (midipulse p, int P, int B, int W) { return (B > 0 && P > 0.0) ? int(double(p * W) / (4.0 * P * B)) : 0 ; } inline int ticks_to_beats (midipulse p, int P, int B, int W) { return (B > 0 && P > 0.0) ? ((p * W / P / 4 ) % B) : 0 ; } template INTTYPE snapped (snapper snaptype, int S, INTTYPE p) { INTTYPE result = 0; if (p > 0 && S > 0) { INTTYPE snap = INTTYPE(S); INTTYPE p0 = p - (p % snap); /* drop down to a snap */ if (snaptype == snapper::down) { return p0; } else if (snaptype == snapper::up) { return p0 + snap; } else { INTTYPE p1 = p0 + snap; /* go up by one snap */ int deltalo = int(p - p0); /* amount to lower snap */ int deltahi = int(p1 - p); /* amount to upper snap */ result = deltalo <= deltahi ? p0 : p1 ; /* use closest one */ } } return result; } /** * The absolute pitchbend range is 0 to 16383, which is 14 bits. * * Bend down Center Bend up * 0 |<----------- 8192 ----------->| 16384 * -8192 0 8191 * * 14 bits resolution (MSB, LSB). Value = 128 * MSB + LSB, where, * in Seq66, d0 is the LSB and d1 is the MSB. * * minimum : The maximum negative swing is achieved with data bytes of * 00, 00. Value = 0. * * center: The center (no effect) position is achieved with data bytes of * 00, 64 (00H, 40H). Value = 8192. * * maximum : The maximum positive swing is achieved with data bytes of * 127, 127 (7FH, 7FH). Value = 16384. * * There are 2 ways to make the calculation: * * int(d1) << 7 + int(d0) - OR - int(d1) * 128 + int(d0) * * \param d0 * Provides the LSB (least significant byte) of the pitchbend value. * * \param d1 * Provides the MSB (most significant byte) of the pitchbend value. * * \return * Returns the pitch value, ranging from 0 to 16383. * If either data value exceeds 127, 8192 is returned. */ inline int pitch_value_absolute (midibyte d0, midibyte d1) { int result = 8192; /* 8192 is the center, no bend */ if ((d0 < 128) && (d1 < 128)) /* this is a sanity check */ result = (int(d1) << 7) + int(d0); return result; } /** * Like pitch_value_absolute(), but moves the range to -8192 to +8191. * This little table summarizes the results: * \verbatim d0 d1 Value Absolute value - 127 127 +8191 16383 - 0 64 0 8192 - 0 0 -8192 0 \endverbatim */ inline int pitch_value (midibyte d0, midibyte d1) { int pabs = pitch_value_absolute(d0, d1); return pabs - 8192; } /** * Scale to range 0 to 128 (approximately), where 0 is down 2 semitones * and 127 is up 2 semitones. * * Bend down Center Bend up * 0 |<----------- 64 ----------->| 128 */ inline int pitch_value_scaled (midibyte d0, midibyte d1) { int pv = pitch_value_absolute(d0, d1); /* ranges from 0 to 16384 */ pv /= 128; /* ranges from 0 to 128 */ return pv; } /* * Free functions in the seq66 namespace. */ #if defined SEQ66_USE_EXTRA_PULSE_CALCULATIONS /* OBSOLETE */ extern int pulses_per_substep (midipulse ppq, int zoom = 2); extern int pulses_per_pixel (midipulse ppq, int zoom = 2); #endif extern double pitch_value_semitones ( midibyte d0, midibyte d1, int semitone_range = 2 ); extern void pitch_data_bytes (int pitchvalue, midibyte & d0, midibyte & d1); extern void pitch_data_bytes_scaled ( midibyte pitch, midibyte & d0, midibyte & d1 ); extern double wave_func (double angle, waveform wavetype); #if SEQ66_NEEDS_UNIT_TRUNCATION extern double unit_truncation (double omega); #endif extern double exp_normalize (double omega, bool negate = false); extern bool extract_port_names ( const std::string & fullname, std::string & clientname, std::string & portname ); extern std::string extract_bus_name (const std::string & fullname); extern std::string extract_port_name (const std::string & fullname); extern std::string extract_a2j_port_name (const std::string & alias); extern midipulse closest_snap (int S, midipulse p); extern midipulse down_snap (int S, midipulse p); extern midipulse up_snap (int S, midipulse p); extern bool fequal (double x, double y); extern bool fnotequal (double x, double y); extern bool flessthan (double x, double y); extern bool fgreaterthan (double x, double y); } // namespace seq66 #endif // SEQ66_CALCULATIONS_HPP /* * calculations.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/controllers.hpp ================================================ #if ! defined SEQ66_CONTROLLERS_HPP #define SEQ66_CONTROLLERS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file controllers.hpp * * This module declares the array of MIDI controller names. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2026-04-20 * \license GNU GPLv2 or above * * This file used to define the array itself, but now it just declares it, * since more than one module now uses this array. */ #include namespace seq66 { /** * This enumeration summarizes the MIDI Continuous Controllers (CC) that are * available. * * Notes: * * For balance and pan: 0 = hard left, 64 = center, 127 = hard right * * For all on/off switches, 0 to 63 = Off and 64 to 127 = On. * * Damper Pedal 64 versus Sostenuto CC 66: * * Sustain is a pedal on/off switch that controls sustain. Sostenuto is * an on/off switch like the Sustain controller (CC 64), but it only holds * notes that were on when the pedal was pressed. People use it to “hold” * chords” and play melodies over the held chord. * * NRPN 98 and 99: Non-Registered Parameter Number LSB and MSB. For * controllers 6, 38, 96, and 97, it selects the NRPN parameter. * * RPN 100 and 101: Registered Parameter Number LSB and MSB. For controllers * 6, 38, 96, and 97, it selects the RPN parameter. * * Local On/Off Switch 122: Turns the internal connection of a MIDI * keyboard/workstation, etc., On or Off. For a computer, one will most * likely want Local Control off to avoid notes being played twice, once * locally and twice when the note is sent back from the computer to your * keyboard. * * All Notes Off 123: Mutes all sounding notes. Release time will be * maintained, and notes held by sustain will not turn off until sustain pedal * is depressed. * * Undefined values summary: 3, 9, 14-15, 20-31, 85-90, and 102-119. * * Values 32 to 63 are for Controllers 0 to 31, the Least Significant Bit * (LSB). */ enum class cc { bank_select = 0, /**< Switches patch bank;16,384 patches/chann. */ modulation = 1, /**< Patch vibrato (pitch/loudness/brightness). */ breath_controller = 2, /**< Aftertouch, MIDI control, modulation. */ undefined_03 = 3, foot_controller = 4, /**< Aftertouch, stream of pedal values, etc. */ portamento = 5, /**< Controls rate to slide between 2 notes. */ data_entry = 6, /**< MSB sets value for NRPN/RPN parameters. */ volume = 7, /**< Controls the volume of the channel. */ balance = 8, /**< Left/right balance for stereo patches. */ undefined_09 = 9, pan = 10, /**< Left/right balance for mono patches. */ expression = 11, /**< Expression, a percentage of volume (CC7). */ effect_control_1 = 12, /**< Control a parameter of a synth effect. */ effect_control_2 = 13, /**< Control a parameter of a synth effect. */ undefined_14 = 14, undefined_15 = 15, general_purp_16 = 16, general_purp_17 = 17, general_purp_18 = 18, general_purp_19 = 19, /* * 20 – 31 Undefined. * 32 – 63 Controllers 0 to 31, Least Significant Bit (LSB). * * In the below list, Off is represented by 0x00 to 0x3F (0 to 63) * and On by 0x40 to 0x7F. */ damper_pedal = 64, /**< On/off switch that controls sustain. */ portamento_onoff = 65, /**< On/off switch that controls portamento. */ sostenuto = 66, /**< On/off switch to holds only On notes. */ soft_pedal = 67, /**< On/off switch to lower volume of notes. */ legato = 68, /**< On/off switch for legato between 2 notes. */ undefined_69 = 69, sound_control_1 = 70, /**< Control sound producing [Sound Variation]. */ sound_control_2 = 71, /**< Shapes the VCF [Resonance, timbre]. */ sound_control_3 = 72, /**< Control release time of the VCA. */ sound_control_4 = 73, /**< Control attack time of the VCA. */ sound_control_5 = 74, /**< Controls the VCF cutoff frquency. */ sound_control_6 = 75, /**< Shapes the VCF [Resonance, timbre]. */ sound_control_7 = 76, /**< Manufacturer-dependent sound alteration. */ sound_control_8 = 77, /**< Manufacturer-dependent sound alteration. */ sound_control_9 = 78, /**< Manufacturer-dependent sound alteration. */ sound_control_10 = 79, /**< Manufacturer-dependent sound alteration. */ gp_onoff_switch_1 = 80, /**< Provides a general purpose on/off switch. */ gp_onoff_switch_2 = 81, /**< Provides a general purpose on/off switch. */ gp_onoff_switch_3 = 82, /**< Provides a general purpose on/off switch. */ gp_onoff_switch_4 = 83, /**< Provides a general purpose on/off switch. */ portamento_cc = 84, /**< Controls the amount of portamento. */ /* * 85 – 90 Undefined. */ effect_1_depth = 91, /**< Usually controls reverb send amount. */ effect_2_depth = 92, /**< Usually controls tremolo amount. */ effect_3_depth = 93, /**< Usually controls chorus amount. */ effect_4_depth = 94, /**< Usually controls detune amount. */ effect_5_depth = 95, /**< Usually controls phaser amount. */ data_increment = 96, /**< Increment data for RPN and NRPN messages. */ data_decrement = 97, /**< Decrement data for RPN and NRPN messages. */ nrpn_lsb = 98, /**< CC 6, 38, 96, and 97: selects NRPN LSB. */ nrpn_msb = 99, /**< CC 6, 38, 96, and 97: selects NRPN MSB. */ rpn_lsb = 100, /**< CC 6, 38, 96, and 97: selects RPN LSB. */ rpn_msb = 101, /**< CC 6, 38, 96, and 97: selects RPN MSB. */ /* * 102 – 119 Undefined. */ reset_all = 121, /**< Reset all controllers to their default. */ local_switch = 122, /**< Switches internal connection of a device. */ all_notes_off = 123, /**< Mutes all sounding notes. See notes. */ omni_off = 124, /**< Sets to “Omni Off” mode. */ omni_on = 125, /**< Sets to “Omni On” mode. */ mono_on = 126, /**< device mode to Monophonic. */ poly_on = 127, /**< device mode to Polyphonic. */ }; // enum class cc /** * Provides the default names of MIDI controllers, which a specified in the * controllers.cpp module. This array is used * only by the qseqedit and qseqeventframe classes. * * We could make this list a configuration option. Overkill? No, it is * already configuration in the 'usr' file. So at some point we offload this * stuff to an as-shipped 'usr' file. */ extern std::string controller_name (int index, bool usehex = false); extern void set_controller_name ( int index, const std::string & newname ); } // namespace seq66 #endif // SEQ66_CONTROLLERS_HPP /* * controllers.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/drums.hpp ================================================ #if ! defined SEQ66_DRUMS_HPP #define SEQ66_DRUMS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file drums.hpp * * This module declares the array of MIDI program names. * * \library seq66 application * \author Chris Ahlstrom * \date 2026-02-16 * \updates 2026-04-17 * \license GNU GPLv2 or above * */ #include /* std::map<> template class */ #include /* std::string<> template class */ namespace seq66 { /** * Provides a small wrapper class for an alternate mapping of drum numbers * (program numbers) to drum names. */ class drums { friend std::string drum_name (int drumnumber); public: using container = std::map; private: /** * A container for the up to 128 pairs of drum numbers and names. * Initially of size zero. */ container m_drum_map; /** * Indicates if the drum map is to be used in place of the built-in * GM drum list. */ bool m_active; /** * Holds the [comments] for the drum file. */ std::string m_comments; public: drums () = default; /* an empty, inactive map */ drums (const drums &) = delete; const drums & operator = (const drums &) = delete; ~drums () = default; const container & drum_map () const { return m_drum_map; } void clear () { m_drum_map.clear(); activate(false); } bool add (int drumnumber, const std::string & drumname); std::string name (int drumnumber) const; const std::string & comments () const { return m_comments; } void comments (const std::string & c) { m_comments = c; } bool active () const { return m_active; } void activate (bool flag = true) { m_active = flag; } private: std::string name_ex (int drumnumber) const; }; // class drums /* * Acessor functions */ extern bool add_drum (int drumnumber, const std::string & drumname); extern void set_drums_comment (const std::string & c); extern const std::string & get_drums_comment (); extern std::string drum_name (int drumnumber); } // namespace seq66 #endif // SEQ66_DRUMS_HPP /* * drums.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/editable_event.hpp ================================================ #if ! defined SEQ66_EDITABLE_EVENT_HPP #define SEQ66_EDITABLE_EVENT_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file editable_event.hpp * * This module declares/defines the editable_event class for operating with * MIDI editable_events. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-11-28 * \updates 2025-02-20 * \license GNU GPLv2 or above * * This module extends the event class to support conversions between events * and human-readable (and editable) strings. */ #include "midi/calculations.hpp" /* seq66::pulses_to_string() etc. */ #include "midi/event.hpp" /* seq66::event */ #include "midi/midibytes.hpp" /* seq66::midishort data type */ namespace seq66 { class editable_events; /* forward reference to container */ /** * Provides for the management of MIDI editable events. It makes the * following members of an event modifiable using human-readable strings: * * - m_timestamp * - m_status * - m_channel * - m_data[] * * Eventually, it would be nice to be able to edit, or at least view, the * SysEx events and the Meta events. Those two will require extensions to * make events out of them (SysEx is partly supported). * * To the concepts of event, the editable_event class adds a category field * and strings to represent all of these members. */ class editable_event final : public event { public: /** * These values determine the major kind of event, which determines what * types of events are possible for this editable event object. * These tags are accompanied by category names in sm_category_names[]. * The enum values are cast to midibyte values for the purposes of using * the lookup infrastructure. */ enum class subgroup : midibyte { /** * Indicates that the lookup needs to be done on the category names, * as listed in sm_category_names[]. */ name, /* sm_category_names[] */ /** * Indicates a channel event, with a value ranging from 0x80 through * 0xEF. Some examples are note on/off, control change, and program * change. Values are looked up in sm_channel_event_names[]. */ channel_message, /* sm_channel_event_names[] */ /** * Indicates a system event, with a value ranging from 0xF0 through * 0xFF. Some examples are SysEx start/end, song position, and * stop/start/continue/reset. Values are looked up in * sm_system_event_names[]. These values are "real" only in MIDI * data coming in "over the wire". In MIDI files, they represent * Meta events. */ system_message, /* sm_system_event_names[] */ /** * Indicates a meta event, and there is a second value that is used * to look up the name of the meta event, in sm_meta_event_names[]. * Meta messages are message that are stored in a MIDI file. * Although they start with 0xFF, they are not to be confused with * the 0xFF message that can be sent "over the wire", which denotes a * Reset event. */ meta_event, /* sm_meta_event_names[] */ /** * Indicates a "proprietary", Seq66 event. Indicates to look * up the name of the event in sm_seqspec_event_names[]. Not sure if * these kinds of events will be stored separately. */ seqspec_event /* sm_seqspec_event_names[] */ }; /** * Provides a code to indicate the desired timestamp format. Three are * supported. All editable events will share the same timestamp format, * but it seems good to make this a event class member, rather than * something imposed from an outside static value. We shall see. */ using timestamp_format_t = enum { /** * This format displays the time in "measures:beats:divisions" * format, where measures and beats start at 1. Thus, "1:1:0" is * equivalent to 0 pulses or to "0:0:0.0" in normal time values. */ timestamp_measures, /** * This format displays the time in "hh:mm:second.fraction" format. * The value displayed should not depend upon the internal timing * parameters of the event. */ timestamp_time, /** * This format specifies a bare pulse format for the timestamp -- a * long integer ranging from 0 on up. Obviously, this representation * depends on the PPQN value for the sequence holding this event. */ timestamp_pulses }; /** * Provides a type that contains the pair of values needed for the * various lookup maps that are needed to manage editable events. */ using name_value_t = struct { /** * ca 2023-05-02 * Supplements the event value with an index into a combo-box or * similar list. We cannot support every possible event_value for * lookup. */ int event_index; /** * Holds a midibyte value (0x00 to 0xFF or 0x100 for end-of-list). * This field can be considered a "key" value, as it is often looked * up to find the event name. */ midishort event_value; /** * Holds the human-readable name for an event code or other numeric * value in an array of name_value_t items. */ std::string event_name; }; /** * Provides a type that contains the pair of values needed to get the * Meta event's data length. */ using meta_length_t = struct { /** * Holds a midibyte value (0x00 to 0xFF). This field has the same * meaning as the event_value of the name_value_t type. */ midishort event_value; /** * Holds the length expected for the Meta event, or 0 if it does not * apply to the Meta event. */ midishort event_length; }; private: /** * Provides a reference (pointer) to the container that holds this event. * The container's "children" need to go to their "parent" to get certain * (very limited) items of information. The event doesn't own this * pointer. */ const editable_events * m_parent; /** * Holds the linked event's timestamp (if applicable), for display in the * event table. */ midipulse m_link_time; /** * Indicates the overall category of this event, which will be * subgroup::channel_message, subgroup::system_message, * subgroup::meta_event, and subgroup::seqspec_event. The subgroup::name * value is not set here, since that category is used only for looking up * the human-readable form of the category. */ subgroup m_category; /** * Holds the name of the event category for this event. */ std::string m_name_category; /** * Indicates the format to display the time-stamp. The default is to * display in timestamp_measures format. */ timestamp_format_t m_format_timestamp; /** * Holds the string version of the MIDI pulse's time-stamp. */ std::string m_name_timestamp; /** * Holds the name of the status value for this event. It will include * the names of the channel messages and the system messages. The latter * includes SysEx and Meta messages. */ std::string m_name_status; /** * Holds the name of the meta message, if applicable. If not applicable, * this name will be empty. */ std::string m_name_meta; /** * If we eventually implement the editing of the Seq24/Seq66 * "proprietary" meta sequencer-specific events, the name of the SeqSpec * will be stored here. */ std::string m_name_seqspec; /** * Holds the channel description, if applicable. */ std::string m_name_channel; /** * Holds the data description, if applicable. */ std::string m_name_data; public: editable_event () = default; editable_event (const editable_events & parent); editable_event ( const editable_events & parent, const event & ev ); editable_event (const editable_event & rhs) = default; editable_event & operator = (const editable_event & rhs) = default; virtual ~editable_event () override { // Empty body } virtual bool set_text (const std::string & s) override; virtual std::string get_text () const override; midipulse link_time () const { return m_link_time; } void link_time (midipulse lt) { m_link_time = lt; } public: subgroup category () const { return m_category; } void category (subgroup c); const std::string & category_string () const { return m_name_category; } void category (const std::string & cs); const std::string & timestamp_string () const { return m_name_timestamp; } /** * \getter event::timestamp() * Implemented to allow a uniform naming convention that is not * slavish to the get/set crowd [this ain't Java or, chuckle, C#]. */ midipulse timestamp () const { return event::timestamp(); } void timestamp (midipulse ts); void timestamp (const std::string & ts_string); /** * Converts the current time-stamp to a string representation in units of * pulses. */ std::string time_as_pulses () { return pulses_to_string(timestamp()); } std::string time_as_measures (); std::string time_as_minutes (); void set_status_from_string ( const std::string & ts, const std::string & s, const std::string & sd0, const std::string & sd1, const std::string & ch = "", const std::string & text = "" ); void modify_channel_status_from_string ( const std::string & sd0, const std::string & sd1, const std::string & chan ); std::string format_timestamp (); std::string stock_event_string (); std::string ex_data_string () const; std::string ex_text_string () const; std::string status_string () const { return m_name_status; } std::string meta_string () const { return m_name_meta; } std::string seqspec_string () const { return m_name_seqspec; } std::string channel_string () const { return m_name_channel; } std::string data_string () const { return m_name_data; } void analyze (); static std::string category_name (int index); static std::string channel_event_name (int index); static std::string system_event_name (int index); static std::string meta_event_name (int index); static std::string seqspec_event_name (int index); static int channel_event_index (const std::string & name); private: const editable_events * parent () const { return m_parent; } static std::string value_to_name (midibyte value, subgroup cat); static midishort name_to_value (const std::string & name, subgroup cat); static midishort meta_event_length (midibyte value); }; // class editable_event /* * Free functions in the seq66 namespace. */ extern std::string time_signature_string (int n, int d, int c = 24, int b = 8); extern bool time_signature_bytes ( const std::string & text, midibytes & timesigbytes ); extern std::string sysex_string (const event::sysex & s); extern bool sysex_bytes ( const std::string & text, event::sysex & sxbytes ); } // namespace seq66 #endif // SEQ66_EDITABLE_EVENT_HPP /* * editable_event.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/editable_events.hpp ================================================ #if ! defined SEQ66_EDITABLE_EVENTS_HPP #define SEQ66_EDITABLE_EVENTS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file editable_events.hpp * * This module declares/defines a sorted container for editable_events class * for operating with an ordered collection MIDI editable_events in a * user-interface. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-12-04 * \updates 2023-07-04 * \license GNU GPLv2 or above * * This module extends the event class to support conversions between events * and human-readable (and editable) strings. */ #include /* std::multimap */ #include "midi/editable_event.hpp" /* seq66::editable_event */ namespace seq66 { class sequence; /* * ui->combo_ev_name entries from editable_event::channel_event_name(): * * - Note Off * - Note On * - Aftertouch * - Control * - Program * - Ch Pressure * - Pitch Wheel */ /** * Provides for the management of an ordered collection MIDI editable events. */ class editable_events { friend class qseventslots; /* the Qt 5 version */ private: /** * Types to use to with the multimap implementation. We should consider * reusing, somehow, the event::buffer (vector) implementation. Also too * much re-do here from eventlist! */ using Key = event::key; using Events = std::multimap; using iterator = Events::iterator; using const_iterator = Events::const_iterator; /** * Holds the editable_events. The multimap works well here. */ Events m_events; /** * Points to the current event, which is the event that has just been * inserted. (From this event we can get the current time and other * parameters.) If the container were a plain map, we could instead use * a key to access it. But we can at least use an iterator, rather than * a bare pointer. */ iterator m_current_event; /** * Provides a reference to the sequence containing the events to be * edited. Besides the events, this object also holds the beats/measure, * beat-width, and the PPQN value. The beats/minute have to be obtained * from the application's performer object, and passed to the * editable_events constructor by the caller. */ sequence & m_seq; /** * Holds the current settings for the sequence (and usually for the whole * MIDI tune as well). It holds the beats/minute, beats/measure, * beat-width, and PPQN values needed to properly convert MIDI pulse * timestamps to time and measure values. */ midi_timing m_midi_parameters; private: editable_events (); /* unimplemented */ public: editable_events (sequence & seq, midibpm bpm); editable_events (const editable_events & rhs); editable_events & operator = (const editable_events & rhs); /** * This destructor current is a rote virtual function override. */ virtual ~editable_events () { // Empty body } public: const midi_timing & timing () const { return m_midi_parameters; } editable_event & lookup_link (const editable_event & ee); midipulse string_to_pulses (const std::string & ts_string) const; bool load_events (); bool save_events (); Events & events () { return m_events; } const Events & events () const { return m_events; } iterator begin () { return m_events.begin(); } const_iterator begin () const { return m_events.cbegin(); } iterator end () { return m_events.end(); } const_iterator end () const { return m_events.cend(); } /** * Dereference access for list or map. * * \param ie * Provides the iterator to the event to which to get a reference. */ static editable_event & dref (iterator ie) { return ie->second; } /** * Dereference const access for list or map. * * \param ie * Provides the iterator to the event to which to get a reference. */ static const editable_event & cdref (const_iterator ie) { return ie->second; } /** * Returns the number of events stored in m_events. We like returning * an integer instead of size_t, and rename the function so nobody is * fooled. */ int count () const { return int(m_events.size()); } bool empty () const { return m_events.empty(); } midipulse get_length () const; bool add (const event & e); bool add (const editable_event & e); /** * Provides a wrapper for the iterator form of erase(), which is the * only one that the editable_events container uses. */ bool replace (iterator ie, const editable_event & e) { if (ie != m_events.end()) m_events.erase(ie); return add(e); } /** * Provides a wrapper for the iterator form of erase(), which is the * only one that sequence uses. */ void remove (iterator ie) { if (ie != m_events.end()) m_events.erase(ie); } void clear () { m_events.clear(); } /** * Sorts the event list; active only for the std::list implementation. */ void sort () { /* * we need nothin' for sorting a multimap. */ } /** * \getter m_current_event * The caller must make sure the iterator is not Events::end(), using * is_valid_iterator(). */ iterator current_event () const { return m_current_event; } /** * Validates the given iterator. */ bool is_valid_iterator (iterator & cit) const { return cit != m_events.end(); } int count_to_link (const editable_event & source) const; void print () const; private: const sequence & track () const { return m_seq; } sequence & track () { return m_seq; } /** * \setter m_current_event * * \param cei * Provide an iterator to the event to set as the current event. */ void current_event (iterator cei) { m_current_event = cei; } }; // class editable_events } // namespace seq66 #endif // SEQ66_EDITABLE_EVENTS_HPP /* * editable_events.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/event.hpp ================================================ #if ! defined SEQ66_EVENT_HPP #define SEQ66_EVENT_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file event.hpp * * This module declares/defines the event class for operating with * MIDI events. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2025-07-13 * \license GNU GPLv2 or above * * This module also declares/defines the various constants, status-byte * values, or data values for MIDI events. This class is also a base class, * so that we can manage "editable events". * * One thing we need to add to this event class is a way to encapsulate * Meta events. First, we use the existing event::sysex to hold * this data. * * The MIDI protocol consists of MIDI events that carry four types of * messages: * * - Voice messages. 0x80 to 0xEF; includes channel information. * - System common messages. 0xF0 (SysEx) to 0xF7 (End of SysEx) * - System realtime messages. 0xF8 to 0xFF. * - Meta messages. 0xFF is the flag, followed by type, length, and * data. */ #include "midi/midibytes.hpp" /* seq66::midibyte, vector, etc. */ #define SEQ66_STAZED_SELECT_EVENT_HANDLE /** * Defines the number of data bytes in MIDI status data. * * But consider this, events other than System Exclusive, which do not have * an arbitrary number of bytes, but a definite number. These events are: * \verbatim - Sequence No.: FF 00 02 s1 s1 - MIDI Channel: FF 20 01 cc - MIDI Port: FF 21 01 pp - Set Tempo: FF 51 03 tt tt tt - SMPTE Offset: FF 54 05 hh mm ss fr ff - Time Signature: FF 58 04 nn dd cc bb - Key Signature: FF 59 02 sf mi \endverbatim * * The arbitrarily-sized Meta events are: * \verbatim - Text: FF 01 len text - Copyright: FF 02 len text - Track Name: FF 03 len name - Instrument: FF 04 len name - Marker: FF 05 len text - Cue Point: FF 06 len text - Seq. Specific: FF 7F len data \endverbatim * * The maximum amount of constant-size data is 5 bytes. We should aim to * increase this and use it, using event::m_status as the "meta" byte and * perhaps m_channel as the "meta-event" byte. But curently, the tempo and * time signature events are stored as data in the sequence object, so * that's probably the best tact for the future. */ #define SEQ66_MIDI_DATA_BYTE_COUNT 2 namespace seq66 { /** * This highest bit of the STATUS byte is always 1. If this bit is not set, * then the MIDI byte is a DATA byte. */ const midibyte EVENT_STATUS_BIT = 0x80; /** * Channel Voice Messages. * * The following MIDI events are channel messages. The comments represent * the one or two data-bytes of the message. * * Note that Channel Mode Messages use the same code as the Control Change, * but uses reserved controller numbers ranging from 122 to 127. * * The EVENT_ANY (0x00) value may prove to be useful in allowing any event to * be dealt with. Not sure yet, but the cost is minimal. */ const midibyte EVENT_ANY = 0x00u; // our own value const midibyte EVENT_NOTE_OFF = 0x80u; // 0kkkkkkk 0vvvvvvv const midibyte EVENT_NOTE_ON = 0x90u; // 0kkkkkkk 0vvvvvvv const midibyte EVENT_AFTERTOUCH = 0xA0u; // 0kkkkkkk 0vvvvvvv const midibyte EVENT_CONTROL_CHANGE = 0xB0u; // 0ccccccc 0vvvvvvv const midibyte EVENT_PROGRAM_CHANGE = 0xC0u; // 0ppppppp const midibyte EVENT_CHANNEL_PRESSURE = 0xD0u; // 0vvvvvvv const midibyte EVENT_PITCH_WHEEL = 0xE0u; // 0lllllll 0mmmmmmm /** * Control Change Messages. This is a small subset of the roughly 40 control * changes. */ const midibyte EVENT_CTRL_VOLUME = 0x07u; const midibyte EVENT_CTRL_BALANCE = 0x08u; const midibyte EVENT_CTRL_PAN = 0x0Au; const midibyte EVENT_CTRL_EXPRESSION = 0x0Bu; /** * System Messages. * * The following MIDI events have no channel. We have included redundant * constant variables for the SysEx Start and End bytes just to make it * clear that they are part of this sequence of values, though usually * treated separately. * * Only the following constants are followed by some data bytes: * * - EVENT_MIDI_SYSEX = 0xF0 // ends with 0xF7 * - EVENT_MIDI_QUARTER_FRAME = 0xF1 // and 0x0n to 0x7n * - EVENT_MIDI_SONG_POS = 0xF2 // and 0x0 to 0x3FFF 16th note * - EVENT_MIDI_SONG_SELECT = 0xF3 // and 0x0 to 0x7F song number * - EVENT_MIDI_TUNE_REQUEST = 0xF6 // no data, tune yourself * * A MIDI System Exclusive (SYSEX) message starts with F0, followed * by the manufacturer ID (how many? bytes), a number of data bytes, and * ended by an F7. * * MIDI System Real-Time Messages: * * - https://en.wikipedia.org/wiki/MIDI_beat_clock * - http://www.midi.org/techspecs/midimessages.php */ const midibyte EVENT_MIDI_REALTIME = 0xF0u; // 0xFn when masked const midibyte EVENT_MIDI_SYSEX = 0xF0u; // Starts a SysEx message const midibyte EVENT_MIDI_QUARTER_FRAME = 0xF1u; // 1 data byte const midibyte EVENT_MIDI_SONG_POS = 0xF2u; // 2 data bytes const midibyte EVENT_MIDI_SONG_SELECT = 0xF3u; // 1 data byte const midibyte EVENT_MIDI_SONG_F4 = 0xF4u; // undefined const midibyte EVENT_MIDI_SONG_F5 = 0xF5u; // undefined const midibyte EVENT_MIDI_TUNE_REQUEST = 0xF6u; // 0 data bytes const midibyte EVENT_MIDI_SYSEX_END = 0xF7u; // redundant, see below const midibyte EVENT_MIDI_SYSEX_CONTINUE = 0xF7u; // redundant, see below const midibyte EVENT_MIDI_CLOCK = 0xF8u; // no data bytes const midibyte EVENT_MIDI_SONG_F9 = 0xF9u; // undefined const midibyte EVENT_MIDI_START = 0xFAu; // no data bytes const midibyte EVENT_MIDI_CONTINUE = 0xFBu; // no data bytes const midibyte EVENT_MIDI_STOP = 0xFCu; // no data bytes const midibyte EVENT_MIDI_SONG_FD = 0xFDu; // undefined const midibyte EVENT_MIDI_ACTIVE_SENSE = 0xFEu; // 0 data bytes, not used const midibyte EVENT_MIDI_RESET = 0xFFu; // 0 data bytes, not used /** * 0xFF is a MIDI "escape code" used in MIDI files to introduce a MIDI meta * event. Note that it has the same code (0xFF) as the Reset message, but * the Meta message is read from a MIDI file, while the Reset message is sent * to the sequencer by other MIDI participants. */ const midibyte EVENT_MIDI_META = 0xFFu; // an escape code /** * Provides values for the currently-supported Meta events, and many others: * * - Set Tempo (0x51) * - Time Signature (0x58) * * TODO: Add these to rtl66. */ const midibyte EVENT_META_SEQ_NUMBER = 0x00u; const midibyte EVENT_META_TEXT_EVENT = 0x01u; // meta text const midibyte EVENT_META_COPYRIGHT = 0x02u; // meta text const midibyte EVENT_META_TRACK_NAME = 0x03u; // meta text const midibyte EVENT_META_INSTRUMENT = 0x04u; // meta text const midibyte EVENT_META_LYRIC = 0x05u; // meta text const midibyte EVENT_META_MARKER = 0x06u; // meta text const midibyte EVENT_META_CUE_POINT = 0x07u; // meta text const midibyte EVENT_META_PROGRAM_NAME = 0x08u; // NEW: name of patch const midibyte EVENT_META_PORT_NAME = 0x09u; // NEW: name of out-port const midibyte EVENT_META_MIDI_CHANNEL = 0x20u; // skipped, obsolete const midibyte EVENT_META_MIDI_PORT = 0x21u; // skipped, obsolete const midibyte EVENT_META_END_OF_TRACK = 0x2Fu; const midibyte EVENT_META_SET_TEMPO = 0x51u; const midibyte EVENT_META_SMPTE_OFFSET = 0x54u; // skipped const midibyte EVENT_META_TIME_SIGNATURE = 0x58u; const midibyte EVENT_META_KEY_SIGNATURE = 0x59u; const midibyte EVENT_META_SEQSPEC = 0x7Fu; /** * Provides a sanity-check limit for the number of bytes in a MIDI Meta Text * message and similar messages. Might be better larger, but.... Well, now * that we're handling meta text message, we'll make this a bit larger than * 1024. This value is also used in the main Session tab to limit the * amount of text in the song info edit field. */ const size_t c_meta_text_limit = 32767; // for sanity... /** * As a "type" (overloaded on channel) value for a Meta event, 0xFF indicates * an illegal meta type. */ const midibyte EVENT_META_ILLEGAL = c_midibyte_max; /* problem code */ /** * These file masks are used to obtain (or mask off) the channel data and * status portion from an (incoming) status byte. */ const midibyte EVENT_GET_CHAN_MASK = 0x0Fu; const midibyte EVENT_GET_STATUS_MASK = 0xF0u; const midibyte EVENT_DATA_MASK = 0x7Fu; /** * Variable from the "stazed" extras. We reversed the parts of each token * for consistency with the macros defined above. */ const int EVENTS_ALL = -1; const int EVENTS_UNSELECTED = 0; /** * Provides events for management of MIDI events. * * A MIDI event consists of 3 bytes: * * -# Status byte, 1sssnnnn, where the 1sss bits specify the type of * message, and the nnnn bits denote the channel number, 0 to 15. * The status byte always starts with 1. * -# The first data byte, 0xxxxxxx, where the data byte always * start with 0, and the xxxxxxx values range from 0 to 127. * -# The second data byte, 0xxxxxxx. * * This class may have too many member functions. */ class event { friend class eventlist; friend class sequence; public: /** * Provides a type definition for a vector of midibytes. This type will * also hold the raw data of Meta events. */ using sysex = midibytes; /** * The data buffer for MIDI events. This item replaces the * eventlist::Events type definition so that we can replace event * pointers with iterators, safely. */ using buffer = std::vector; using iterator = buffer::iterator; using const_iterator = buffer::const_iterator; using reverse_iterator = buffer::reverse_iterator; using const_reverse_iterator = buffer::const_reverse_iterator; public: /** * Provides a key value for an event. Its types match the * m_timestamp and get_rank() function of the event class. * It is not needed in the eventlist class, which uses a vector as a * container, but it is needed in the editable_events class, which * uses a multimap. */ class key { private: midipulse m_timestamp; /**< The primary key-value for the key. */ int m_rank; /**< The sub-key-value for the key. */ public: key () = default; key (midipulse tstamp, int rank); key (const event & e); bool operator < (const key & rhs) const; bool operator == (const key & rhs) const; key (const key & ek) = default; key & operator = (const key & ek) = default; }; private: /** * Indicates the input buss on which this event came in. The default * value is unusable: null_buss() from the midibytes.hpp module. */ bussbyte m_input_buss; /** * Provides the MIDI timestamp in ticks, otherwise known as the "pulses" * in "pulses per quarter note" (PPQN). */ midipulse m_timestamp; /** * This is the status byte without the channel. The channel is included * when recording MIDI, but, once a sequence with a matching channel is * found, the channel nybble is cleared for storage. The channel will be * added back on the MIDI bus upon playback. The high nybble = type of * event; The low nybble = channel. Bit 7 is present in all status * bytes. * * Note that, for status values of 0xF0 (Sysex) or 0xFF (Meta), special * handling of the event can occur. We would like to eventually use * inheritance to keep the event class simple. For now, search for * "tempo" and "sysex" to tease out their implementations. Sigh. */ midibyte m_status; /** * In order to be able to handle MIDI channel-splitting of an SMF 0 file, * we need to store the channel, even if we override it when playing the * MIDI data. * * Overload: For Meta events, where is_meta() is true, this value holds * the type of Meta event. See the editable_event::sm_meta_event_names[] * array. Note that EVENT_META_ILLEGAL (0xFF) indicates an illegal Meta * event. */ midibyte m_channel; /** * The two bytes of data for the MIDI event. Remember that the * most-significant bit of a data byte is always 0. A one-byte message * uses only the 0th index. */ midibyte m_data[SEQ66_MIDI_DATA_BYTE_COUNT]; /** * The data buffer for SYSEX messages. Adapted from Stazed's Seq32 * project on GitHub. * * Note: * * This object will also hold the generally small amounts of data needed * for Meta events. Compare is_sysex() to is_meta() and is_ex_data() * [which tests for both]. In addition, detect and handle the other * Meta message that hold variable amounts of bytes. */ sysex m_sysex; /** * This event is used to link NoteOns and NoteOffs together. The NoteOn * points to the NoteOff, and the NoteOff points to the NoteOn. See, for * example, eventlist::link_notes(). * * We currently do not link tempo events; this would be necessary to * display a line from one tempo event to the next. Currently we display * a small circle for each tempo event. */ iterator m_linked; /** * Indicates that a link has been made. This item is used [via * the get_link() and link() accessors] in the sequence class. */ bool m_has_link; /** * Answers the question "is this event selected in editing." */ bool m_selected; /** * Answers the question "is this event marked in processing." This * marking is more of an internal function for purposes of reorganizing * events. */ bool m_marked; /** * Answers the question "is this event being painted." This setting is * made by sequence::add_event() or add_note() if the paint parameter is * true (it defaults to false). */ bool m_painted; public: event (); event ( midipulse tstamp, midibyte status, midibyte d0 = 0, midibyte d1 = 0 ); event (midipulse tstamp, midibpm tempo); event (midipulse tstamp, midibyte metatype, const midibytes & data); event ( midipulse tstamp, midibyte notekind, midibyte channel, int note, int velocity ); event (const event & rhs); event & operator = (const event & rhs); virtual ~event (); /* * Operator overload, the only one needed for sorting events in a list * or a map. */ bool operator < (const event & rhsevent) const; bool match (const event & target) const; void prep_for_send (midipulse tick, const event & source); void set_input_bus (bussbyte b) { if (is_good_buss(b)) m_input_buss = b; } bussbyte input_bus () const { return m_input_buss; } void set_timestamp (midipulse time) { m_timestamp = time; } midipulse timestamp () const { return m_timestamp; } midibyte channel () const { return m_channel; } /** * Checks the channel number to see if the event's channel matches it, or * if the event has no channel. Used in the SMF 0 track-splitting code. * The value of 0xFF is Seq66's channel value that indicates that the * event's m_channel value is bogus. However, it also means that the * channel, if applicable to the event, is encoded in the m_status byte * itself. This is our work around to be able to hold a multi-channel * SMF 0 track in a sequence. In a Seq66 SMF 0 track, every event has a * channel. In a Seq66 SMF 1 track, the events do not have a channel. * Instead, the channel is a global value of the sequence, and is stuffed * into each event when the event is played, but not when written to a * MIDI file. (New behavior 20201-08-10). * * \param channel * The channel to check for a match. * * \return Returns true if the given channel matches the event's channel. */ bool match_channel (int channel) const { return is_null_channel(m_channel) || midibyte(channel) == m_channel; } static midibyte mask_channel (midibyte m) { return m & EVENT_GET_CHAN_MASK; } static midibyte mask_status (midibyte m) { return m & EVENT_GET_STATUS_MASK; } /** * Static test for the status bit. The "opposite" test is is_data(). * Currently used only in midifile. * * \return * Returns true if the status bit is set. Covers 0x80 to 0xFF. */ static bool is_status (midibyte m) { return (m & EVENT_STATUS_BIT) != 0; } /** * Makes sure the status byte matches the "EVENT" message bytes exactly * by stripping the channel nybble if necessary. */ static midibyte normalized_status (midibyte status) { return is_channel_msg(status) ? mask_status(status) : status ; } #if defined SEQ66_THIS_FUNCTION_IS_USED /* * Static test for the status bit. The opposite test is is_status(). * Currently not used anywhere. * * \return * Returns true if the status bit is not set. */ static bool is_data (midibyte m) { return (m & EVENT_STATUS_BIT) == 0x00; } #endif /* * Static functions used in event and editable event. These can be useful * to any caller. */ public: static bool is_system_msg (midibyte m) { return m >= EVENT_MIDI_SYSEX; } static bool is_meta_msg (midibyte m) { return m == EVENT_MIDI_META; } static bool is_ex_data_msg (midibyte m) { return m == EVENT_MIDI_META || m == EVENT_MIDI_SYSEX; } static bool is_pitchbend_msg (midibyte m) { return mask_status(m) == EVENT_PITCH_WHEEL; } static bool is_controller_msg (midibyte m) { return mask_status(m) == EVENT_CONTROL_CHANGE; } /** * Static test for messages that involve notes and velocity: Note On, * Note Off, and Aftertouch. * * \param m * The channel status or message byte to be tested, and the channel * bits are masked off before testing. Actually, no longer * necessary, we have a faster test, since these three events have * values in an easy range to check. * * \return * Returns true if the byte represents a MIDI note message. */ static bool is_note_msg (midibyte m) { return m >= EVENT_NOTE_OFF && m < EVENT_CONTROL_CHANGE; } /** * Static test for messages that involve notes only: Note On and * Note Off, useful in note-event linking. * * \param m * The channel status or message byte to be tested. * * \return * Returns true if the byte represents a MIDI note on/off message. */ static bool is_strict_note_msg (midibyte m) { return m >= EVENT_NOTE_OFF && m < EVENT_AFTERTOUCH; } static bool is_note_on_msg (midibyte m) { return m >= EVENT_NOTE_ON && m < EVENT_AFTERTOUCH; } /** * We don't want a progress bar for patterns that just contain textual * information. Tempo event are important, though, and visible in some * pattern views. */ static bool is_playable_msg (midibyte m) { return m != EVENT_MIDI_META && m != EVENT_MIDI_SYSEX; } public: /* * Static functions used in analysizing MIDI events by external callers. */ /** * Static test for the channel message/statuse values: Note On, Note Off, * Aftertouch, Control Change, Program Change, Channel Pressure, and * Pitch Wheel. This function is also a test for a Voice Category * status. The allowed range is 0x80 to 0xEF. Currently not used * anywhere. * * \param m * The channel status or message byte to be tested, with the channel * bits masked off. * * \return * Returns true if the byte represents a MIDI channel message. */ static bool is_channel_msg (midibyte m) { return m >= EVENT_NOTE_OFF && m < EVENT_MIDI_REALTIME; } /** * Static test for channel messages that have only one data byte: Program * Change and Channel Pressure. The rest of the channel messages have * two data bytes. * * \param m * The channel status or message byte to be tested. The channel * bits are masked off before the test. * * \return * Returns true if the byte represents a MIDI channel message that * has only one data byte. */ static bool is_one_byte_msg (midibyte m) { m = mask_status(m); return m == EVENT_PROGRAM_CHANGE || m == EVENT_CHANNEL_PRESSURE; } /** * Static test for channel messages that have two data bytes: Note On, * Note Off, Control Change, Aftertouch, and Pitch Wheel. * * \param m * The channel status or message byte to be tested. The channel * bits are masked off before the test. * * \return * Returns true if the byte represents a MIDI channel message that * has two data bytes. */ static bool is_two_byte_msg (midibyte m) { return ( (m >= EVENT_NOTE_OFF && m < EVENT_PROGRAM_CHANGE) || mask_status(m) == EVENT_PITCH_WHEEL ); } /** * This static member function is used in the midifile module and in the * is_note_off_recorded() member function. * * \param status * The type of event, which might be EVENT_NOTE_ON. * * \param vel * The velocity byte to check. It should be zero for a note-on is * note-off event. */ static bool is_note_off_velocity (midibyte status, midibyte vel) { return mask_status(status) == EVENT_NOTE_ON && vel == 0; } static bool is_program_change_msg (midibyte m) { return mask_status(m) == EVENT_PROGRAM_CHANGE; } /* * This function seems iffy. Replaced by is_tempo_status() in the GUI * classes. */ static bool is_meta_status (midibyte m) { return m <= EVENT_META_SEQSPEC; } /* * This currently include Meta Track Name, which is handled differently. * handled differently. */ static bool is_meta_text_msg (midibyte m) { return m >= EVENT_META_TEXT_EVENT && m <= EVENT_META_CUE_POINT; } static bool is_tempo_status (midibyte m) { return m == EVENT_META_SET_TEMPO; } static bool is_time_signature_status (midibyte m) { return m == EVENT_META_TIME_SIGNATURE; } /** * Handles the SysEx start or continue bytes. */ static bool is_sysex_msg (midibyte m) { return m == EVENT_MIDI_SYSEX || m == EVENT_MIDI_SYSEX_CONTINUE; /* 0xF7 as SysEx Continue */ } /** * Static test for channel messages that are either not control-change * messages, or are and match the given controller value. * Replaced with a better function set. * * \param m * The channel status or message byte to be tested. * * \param cc * The desired cc value, which the datum must match, if the message * is a control-change message. * * \param datum * The current datum, to be compared to cc, if the message is a * control-change message. * * \return * Returns true if the message is not a control-change, or if it is * and the cc and datum parameters match. */ static inline bool is_desired_cc_or_not_cc ( midibyte m, midibyte cc, midibyte datum ) { m = mask_status(m); return (m != EVENT_CONTROL_CHANGE) || (datum == cc); } /** * Checks for a System Common status, which is supposed to clear any * running status. Use in midifile. */ static bool is_system_common_msg (midibyte m) { return m >= EVENT_MIDI_SYSEX && m < EVENT_MIDI_CLOCK; } /** * Checks for a Realtime Category status, which ignores running status. * Ranges from 0xF8 to 0xFF, and m <= EVENT_MIDI_RESET is always true. * Use in midifile. */ static bool is_realtime_msg (midibyte m) { return m >= EVENT_MIDI_CLOCK; } /** * Used in midi_jack. */ static bool is_sense_or_reset (midibyte m) { return m == EVENT_MIDI_ACTIVE_SENSE || m == EVENT_MIDI_RESET; } public: /** * Calculates the value of the current timestamp modulo the given * parameter. * * \param modtick * The tick value to mod the timestamp against. Usually the length * of the pattern receiving this event. */ void mod_timestamp (midipulse modtick) { if (modtick > 1) m_timestamp %= modtick; } void set_status (midibyte status); void set_channel (midibyte channel); void set_channel_status (midibyte eventcode, midibyte channel); void set_meta_status (midibyte metatype); void set_status_keep_channel (midibyte eventcode); #if defined SEQ66_THIS_FUNCTION_IS_USED void set_note_off (int note, midibyte channel); #endif bool set_midi_event ( midipulse timestamp, const midibyte * buffer, int count = 0 ); /** * Note that we have ensured that status ranges from 0x80 to 0xFF. * And recently, the status now holds the channel, redundantly. * Unless the event is a meta event, in which case the channel is the * number of the event. We can return the bare status, or status with * the channel stripped, for channel messages. */ midibyte get_status () const { return m_status; } midibyte normalized_status () const { return normalized_status(m_status); /* may strip channel nybble */ } midibyte get_status (midibyte channel) const { return mask_status(m_status) | channel; } midibyte get_meta_status () const { return is_meta_msg(m_status) ? m_channel : 0 ; } bool valid_status () const { return is_status(m_status); } /** * Checks that statuses match, clearing the channel nybble if needed. * * \param status * Provides the desired status, without any channel nybble (that is, * the channel is 0). * * \return * Returns true if the event's status (after removing the channel) * matches the status parameter. */ bool match_status (midibyte status) const { return (has_channel() ? mask_status(m_status) : m_status) == status; } /** * Clears the most-significant-bit of both parameters, and sets them into * the first and second bytes of m_data. * * \param d1 * The first byte value to set. * * \param d2 * The second byte value to set. */ void set_data (midibyte d0, midibyte d1 = 0) { m_data[0] = d0 & EVENT_DATA_MASK; m_data[1] = d1 & EVENT_DATA_MASK; } /** * Yet another overload. */ void set_data (midipulse tstamp, midibyte status, midibyte d0, midibyte d1); /** * Clears the data, useful in reusing an event to hold incoming MIDI. */ void clear_data () { m_data[0] = m_data[1] = 0; } void clear_link () { unlink(); /* no call to unmark() */ } /** * Retrieves only the first data byte from m_data[] and copies it into * the parameter. * * \param d0 [out] * The return reference for the first byte. */ void get_data (midibyte & d0) const { d0 = m_data[0]; } /** * Retrieves the two data bytes from m_data[] and copies each into its * respective parameter. * * \param d0 [out] * The return reference for the first byte. * * \param d1 [out] * The return reference for the second byte. */ void get_data (midibyte & d0, midibyte & d1) const { d0 = m_data[0]; d1 = m_data[1]; } /** * Two alternative getters for the data bytes. Useful for one-offs. */ midibyte d0 () const { return m_data[0]; } void d0 (midibyte b) { m_data[0] = b; } midibyte d1 () const { return m_data[1]; } void d1 (midibyte b) { m_data[1] = b; } /** * Increments the first data byte (m_data[0]) and clears the most * significant bit. */ void increment_d0 () { m_data[0] = (m_data[0] + 1) & EVENT_DATA_MASK; } /** * Decrements the first data byte (m_data[0]) and clears the most * significant bit. */ void decrement_d0 () { m_data[0] = (m_data[0] - 1) & EVENT_DATA_MASK; } /** * Increments the second data byte (m_data[1]) and clears the most * significant bit. */ void increment_d1 () { m_data[1] = (m_data[1] + 1) & EVENT_DATA_MASK; } /** * Decrements the second data byte (m_data[1]) and clears the most * significant bit. */ void decrement_d1 () { m_data[1] = (m_data[1] - 1) & EVENT_DATA_MASK; } virtual bool set_text (const std::string & s); virtual std::string get_text () const; /* * bool append_meta_data (midibyte metatype, const midibyte * data, int len); */ bool append_meta_data (midibyte metatype, const midibytes & data); bool append_sysex_byte (midibyte data); bool append_sysex (const midibyte * data, int len); bool append_sysex (const midibytes & data); bool set_sysex (const midibyte * data, int len); // STILL NEEDED? bool set_sysex (const midibytes & data); void set_sysex_size (int len); void reset_sysex () { m_sysex.clear(); } sysex & get_sysex () { return m_sysex; } const sysex & get_sysex () const { return m_sysex; } midibyte get_sysex (size_t i) const { return i < m_sysex.size() ? m_sysex[i] : 0 ; } int sysex_size () const { return int(m_sysex.size()); } /** * Determines if this event is a note-on event and is not already linked. */ bool on_linkable () const { return is_note_on() && ! is_linked(); } bool off_linkable () const { return is_note_off() && ! is_linked(); } /** * Determines if a Note Off event is linkable to this event (which is * assumed to be a Note On event). A test used in verify_and_link(). * * \param e * Normally this is a Note Off event. This status is required. * * \return * Returns true if the event is a Note Off, it's the same note as * this note, and the Note Off is not yet linked. */ bool off_linkable (buffer::iterator & eoff) const { return eoff->off_linkable() ? eoff->get_note() == get_note() : false ; } /** * Sets m_has_link and sets m_link to the provided event pointer. * * \param ev * Provides a pointer to the event value to set. Since we're using * an iterator, we can't use a null-pointer test that. We assume the * caller has checked that the value is not end() for the container. */ void link (iterator ev) { m_linked = ev; m_has_link = true; } iterator link () const { return m_linked; /* iterator could be invalid, though */ } bool is_linked () const { return m_has_link; } bool is_note_on_linked () const { return is_note_on() && is_linked(); } bool is_note_off_linked () const { return is_note_off() && is_linked(); } bool is_note_unlinked () const { return is_strict_note() && ! is_linked(); } void unlink () { m_has_link = false; } void paint () { m_painted = true; } void unpaint () { m_painted = false; } bool is_painted () const { return m_painted; } void mark () { m_marked = true; } void unmark () { m_marked = false; } bool is_marked () const { return m_marked; } void select () { m_selected = true; } void unselect () { m_selected = false; } bool is_selected () const { return m_selected; } /** * Sets m_status to EVENT_MIDI_CLOCK; */ void make_clock () { m_status = EVENT_MIDI_CLOCK; } midibyte data (int index) const /* index not checked, for speed */ { return m_data[index]; } /** * Assuming m_data[] holds a note, get the note number, which is in the * first data byte, m_data[0]. */ midibyte get_note () const { return m_data[0]; } /** * Sets the note number, clearing off the most-significant-bit and * assigning it to the first data byte, m_data[0]. * * \param note * Provides the note value to set. */ void set_note (midibyte note) { m_data[0] = note & EVENT_DATA_MASK; } void transpose_note (int tn); /** * Sets the note velocity, which is held in the second data byte, and * clearing off the most-significant-bit, storing it in m_data[1]. * * \param vel * Provides the velocity value to set. */ void note_velocity (int vel) { m_data[1] = midibyte(vel) & EVENT_DATA_MASK; } midibyte note_velocity () const { return is_note() ? m_data[1] : 0 ; } bool is_note_on () const { return mask_status(m_status) == EVENT_NOTE_ON; } /** * Check for the Note Off value in m_status. Currently assumes that the * channel nybble has already been stripped. * * \return * Returns true if m_status is EVENT_NOTE_OFF. */ bool is_note_off () const { return mask_status(m_status) == EVENT_NOTE_OFF; } /** * Returns true if m_status is a Note On, Note Off, or Aftertouch message. * All of these are notes, associated with a MIDI key value. Uses the * static function is_note_msg(). * * \return * The return value of is_note_msg() is returned. */ bool is_note () const { return is_note_msg(m_status); } bool is_strict_note () const { return is_strict_note_msg(m_status); } bool is_selected_note () const { return is_selected() && is_note(); } bool is_selected_note_on () const { return is_selected() && is_note_on(); } bool is_controller () const { return is_controller_msg(m_status); } bool is_pitchbend () const { return is_pitchbend_msg(m_status); } bool is_playable () const { return is_playable_msg(m_status) || is_tempo(); } bool is_selected_status (midibyte status) const { return is_selected() && mask_status(m_status) == mask_status(status); } bool is_desired (midibyte status, midibyte cc) const; bool is_data_in_handle_range (midibyte target) const; bool is_desired (midibyte status, midibyte cc, midibyte data) const; bool is_desired_ex (midibyte status, midibyte cc) const; /** * Some keyboards send Note On with velocity 0 for Note Off, so we * provide this function to test that during recording. * * \return * Returns true if the event is a Note On event with velocity of 0. */ bool is_note_off_recorded () const { return is_note_off_velocity(m_status, m_data[1]); } bool is_midi_start () const { return m_status == EVENT_MIDI_START; } bool is_midi_continue () const { return m_status == EVENT_MIDI_CONTINUE; } bool is_midi_stop () const { return m_status == EVENT_MIDI_STOP; } bool is_midi_clock () const { return m_status == EVENT_MIDI_CLOCK; } bool is_midi_song_pos () const { return m_status == EVENT_MIDI_SONG_POS; } bool has_channel () const { return is_channel_msg(m_status); } /** * Indicates if the m_status value is a one-byte message (Program Change * or Channel Pressure. Channel is stripped, because sometimes we keep * the channel. */ bool is_one_byte () const { return is_one_byte_msg(m_status); } /** * Indicates if the m_status value is a two-byte message (everything * except Program Change and Channel Pressure. Channel is stripped, * because sometimes we keep the channel. */ bool is_two_bytes () const { return is_two_byte_msg(m_status); } bool is_program_change () const { return is_program_change_msg(m_status); } /** * Indicates an event that has a line-drawable data item, such as * velocity. It is false for discrete data such as program/patch number * or Meta events. */ bool is_continuous_event () const { return ! is_program_change() && ! is_meta(); } /** * Indicates if the event is a System Exclusive event or not. * We're overloading the SysEx support to handle Meta events as well. * Perhaps we need to split this support out at some point. */ bool is_sysex () const { return is_sysex_msg(m_status); /* m_status == EVENT_MIDI_SYSEX */ } bool below_sysex () const { return m_status < EVENT_MIDI_SYSEX; } /** * Indicates if the event is a Sense event or a Reset event. * Currently ignored by Sequencer64. */ bool is_sense_reset () { return m_status == EVENT_MIDI_ACTIVE_SENSE || m_status == EVENT_MIDI_RESET; } /** * Indicates if the event is a Meta event or not. * We're overloading the SysEx support to handle Meta events as well. */ bool is_meta () const { return is_meta_msg(m_status); } /** * Note that here the channel represents the actual Meta message. */ bool is_meta_text () const { return is_meta() && is_meta_text_msg(m_channel); } /** * Indicates if we need to use extended data (SysEx or Meta). If true, * then m_channel encodes the type of meta event. */ bool is_ex_data () const { return is_ex_data_msg(m_status); } bool is_system () const { return is_system_msg(m_status); } /** * Indicates if the event is a tempo event. See sm_meta_event_names[]. */ bool is_tempo () const { return is_meta() && m_channel == EVENT_META_SET_TEMPO; /* 0x51 */ } midibpm tempo () const; bool set_tempo (midibpm tempo); bool set_tempo (const midibytes & t); /** * Indicates if the event is a Time Signature event. See * sm_meta_event_names[]. */ bool is_time_signature () const { return is_meta() && m_channel == EVENT_META_TIME_SIGNATURE; /* 0x58 */ } /** * Indicates if the event is a Key Signature event. See * sm_meta_event_names[]. */ bool is_key_signature () const { return is_meta() && m_channel == EVENT_META_KEY_SIGNATURE; /* 0x59 */ } void print (const std::string & tag = "") const; void print_note (bool showlink = true) const; std::string to_string () const; int get_rank () const; void rescale (int newppqn, int oldppqn); private: // used by friend eventlist /* * Changes timestamp. */ bool jitter (int snap, int range, midipulse seqlength); bool tighten (int snap, midipulse seqlength); bool quantize (int snap, midipulse seqlength); /* * Changes the amplitude of d0 or d1, depending on the event. */ bool randomize (int range); }; // class event /* * Global functions in the seq66 namespace. */ extern event create_tempo_event (midipulse tick, midibpm tempo); extern event create_event (midipulse tick, const midibytes & data); } // namespace seq66 #endif // SEQ66_EVENT_HPP /* * event.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/eventlist.hpp ================================================ #if ! defined SEQ66_EVENTLIST_HPP #define SEQ66_EVENTLIST_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file eventlist.hpp * * This module provides a stand-alone module for the event-list container * used by the application. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-09-19 * \updates 2025-10-22 * \license GNU GPLv2 or above * * This module extracts the event-list functionality from the sequencer * module, so that it is easier to try to replace it with some better * container later. * * List versus Map: #if defined or derivation from an interface? For our * purposes, #if defined might be simplest, and we only want to pick the * fastest one, ultimately. * * It turns out the the std::multimap implementation is a little bit faster * in release mode, and a lot faster in debug mode. Why? Probably because * the std::list implementation calls std::list::sort() a lot, and the * std::multimap implementation is a lot faster at sorting. But since the * map iterator is slower, we stick with std::list. * * But, based on this article: * * https://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html * * we will now use std::vector for the event list. */ /** * We made some fixes for sequencer64 issue #141 to disable saving the tempo * into first track. But for Seq66, we do want to be able to save the * time signature(s) of each pattern with that pattern (especially if * different from the global time signature). Note that we would support * only one time-signature per pattern, but unlimited tempo changes. * Also note that, unlike tempo, time-signature does not affect the playback * of MIDI events. It changes how they are displayed. */ #undef SEQ66_USE_FILL_TIME_SIG_AND_TEMPO /** * When recording, instead of a complete verify_and_link(), backtrack * from the latest event if it is a Note Off, and link to the previous * Note On with the same note value. * * One issue is that this will cause problems on loop_reset() when recording. * Not recommended. */ #undef SEQ66_LINK_NEWEST_NOTE_ON_RECORD /** * The jitter_events() function is currently unused, so it is macroed out. */ #undef SEQ66_USE_JITTER_EVENTS #include "midi/event.hpp" /* seq66::event, event::buffer */ namespace seq66 { /** * The eventlist class is a receptable for MIDI events. */ class eventlist { friend class editable_events; /* access to verify_and_link() */ friend class midifile; /* access to print() */ friend class sequence; /* any_selected_notes() */ public: /** * Actions. These variables represent actions that can be applied to a * selection of notes. One idea would be to add a swing-quantize action. * We will reserve the value here, for notes only; not yet used or part of * the action menu. */ enum class edit { select_all_notes = 1, select_all_events, select_inverse_notes, select_inverse_events, quantize_notes, quantize_events, randomize_events, tighten_events, tighten_notes, transpose_notes, /* basic transpose */ reserved, /* later: quantize_swing */ transpose_harmonic, /* harmonic transpose */ expand_pattern, compress_pattern, select_even_notes, select_odd_notes, swing_notes /* swing quantize */ }; /** * This enumeration is used in selecting events and note. Se the * select_note_events() and select_events() functions. */ enum class select { selecting, /**< Selection in progress. */ select_one, /**< To select a single event. */ selected, /**< The events are selected. */ would_select, /**< The events would be selected. */ deselect, /**< To deselect event under the cursor. */ toggle, /**< Toggle selection under cursor. */ remove, /**< To remove one note under the cursor. */ onset, /**< Kepler34, To select a single onset. */ is_onset /**< New, from Kepler34, onsets selected. */ }; private: /** * This list holds the current pattern/sequence events. Note that * is std::vector. */ event::buffer m_events; /** * Eventually we want to be able to move through events of a given type, * such as Meta Text events. */ bool m_match_iterating; event::iterator m_match_iterator; /** * Holds the length of the sequence holding this event-list, * in pulses (ticks). See sequence::m_length. This value is merely the * user-specified length of the track, not the actual time-stamp of the * last event. */ midipulse m_length; /** * Provides the number of ticks to shave off of the end of painted notes. * Also used when the user attempts to shrink a note to zero (or less * than zero) length. */ midipulse m_note_off_margin; /** * A sort of snap value to use when a quantized note gets shrunk to * close to zero length. Set to 16 ticks in the constructor, but * can be changed by the owning sequence. */ midipulse m_zero_len_correction; /** * A flag to indicate if an event was added or removed. We may need to * give client code a way to reload the sequence. This is currently an * issue when a seqroll and an eventedit/eventslots are active for the * same sequence. */ bool m_is_modified; /** * A new flag to indicate that a tempo event has been added. Legacy * behavior forces the tempo to be written to the track-0 sequence, * but we don't want to do that if the MIDI file (or the current event * list) contains a tempo event. */ bool m_has_tempo; /** * A new flag to indicate that a time-signature event has been added. * Legacy behavior forces the time-signature to be written to the track-0 * sequence, but we don't want to do that if the MIDI file (or the * current event list) contains a time-signature event. */ bool m_has_time_signature; /** * Another flag. */ bool m_has_key_signature; /** * Stores the setting of usr().pattern_wraparound(). It is used in * the link_new() function. */ bool m_link_wraparound; public: eventlist (); eventlist (const eventlist & rhs); /* = default; */ eventlist & operator = (const eventlist & rhs); /* = default; */ virtual ~eventlist () { // No code needed } /* * These operators are used in the scales, eventlist, editable_events, * and sequence classes. */ event::iterator begin () { return m_events.begin(); } event::const_iterator cbegin () const { return m_events.cbegin(); } event::iterator end () { return m_events.end(); } event::const_iterator cend () const { return m_events.cend(); } /** * Returns the number of events stored in m_events. We like returning * an integer instead of size_t, and rename the function so nobody is * fooled. */ int count () const { return int(m_events.size()); } int playable_count () const; bool is_playable () const; midipulse get_min_timestamp () const; midipulse get_max_timestamp () const; bool add (const event & e); bool append (const event & e); bool empty () const { return m_events.empty(); } midipulse get_length () const { return m_length; } midipulse note_off_margin () const { return m_note_off_margin; } bool is_modified () const { return m_is_modified; } bool has_tempo () const { return m_has_tempo; } bool has_time_signature () const { return m_has_time_signature; } /** * \setter m_is_modified * This function may be needed by some of the sequence editors. * But use it with great caution. */ void unmodify () { m_is_modified = false; } /** * Provides a wrapper for the iterator form of erase(), which is the * only one that sequence uses. Currently, no check on removal is * performered. Sets the modified-flag. * * \param ie * Provides the iterator to the event to be removed. * * \return * Returns an iterator to the next element, or end() if the container * is now empty. */ event::iterator remove (event::iterator ie) { event::iterator result = m_events.erase(ie); m_is_modified = true; return result; } void clear (); void sort (); bool merge (const eventlist & el, bool presort = true); /** * Dereference access for list or map. * * \param ie * Provides the iterator to the event to which to get a reference. */ static event & dref (event::iterator ie) { return *ie; } /** * Dereference const access for list or map. * * \param ie * Provides the iterator to the event to which to get a reference. */ static const event & cdref (event::const_iterator ie) { return *ie; } private: /* internal quantization functions */ bool add (event::buffer & evlist, const event & e); void merge (const event::buffer & evlist); private: /* functions for friend sequence */ /* * The following functions provide internal for-loops that do not * involved data from the caller. */ int note_count () const; bool first_notes (midipulse & ts, int & n, midipulse snap = 0) const; #if defined SEQ66_USE_FILL_TIME_SIG_AND_TEMPO void scan_meta_events (); #endif bool verify_and_link (midipulse slength = 0, bool wrap = false); bool edge_fix (midipulse snap, midipulse seqlength); bool remove_unlinked_notes (); bool quantize_events ( midibyte status, midibyte cc, int snap, int divide ); bool quantize_events (int snap, int divide = 1, bool all = false); bool quantize_notes (int snap, int divide = 1, bool all = false); midipulse adjust_timestamp (event & er, midipulse deltatick); void scale_note_off (event & noteoff, double factor); midipulse apply_time_factor ( double factor, bool savenotelength = false, bool relink = false ); bool reverse_events (bool inplace = false, bool relink = false); bool move_selected_notes (midipulse delta_tick, int delta_note); bool move_selected_events (midipulse delta_tick); bool align_left (bool relink = false); bool align_right (bool relink = false); bool randomize (midibyte status, int plus_minus, bool all = false); bool randomize_note_velocities (int range, bool all = false); bool randomize_note_pitches ( int range, scales s, keys keyofpattern, bool all = false ); #if defined SEQ66_USE_JITTER_EVENTS bool jitter_events (int snap, int jitr); #endif bool jitter_notes (int snap, int jitr, bool all = false); bool link_new (bool wrap = false); bool link_notes (event::iterator eon, event::iterator eoff); #if defined SEQ66_LINK_NEWEST_NOTE_ON_RECORD void link_new_note (); #endif bool clear_links (); #if defined SEQ66_LINK_TEMPOS bool link_tempos (); void clear_tempo_links (); #endif bool mark_selected (); bool mark_out_of_range (midipulse slength); #if defined SEQ66_MARK_ALL bool mark_all (); bool unmark_all (); #endif bool remove_event (event & e); event::iterator find_first_match (const event & e, midipulse starttick = 0); event::iterator find_next_match (const event & e); bool remove_time_signature (midipulse target); bool remove_first_match (const event & e, midipulse starttick = 0); bool remove_marked (); bool remove_trailing_events (midipulse limit); bool remove_selected (); void unpaint_all (); int count_selected_notes () const; bool any_selected_notes () const; int count_selected_events (midibyte status, midibyte cc) const; bool any_selected_events () const; bool any_selected_events (midibyte status, midibyte cc) const; void select_all (); void select_by_channel (int channel); void select_notes_by_channel (int channel); bool set_channels (int channel); void unselect_all (); int select_events ( midipulse tick_s, midipulse tick_f, midibyte status, midibyte cc, select action ); int select_event_handle ( midipulse tick_s, midipulse tick_f, midibyte astatus, midibyte cc, midibyte data ); int select_note_events ( midipulse tick_s, int note_h, midipulse tick_f, int note_l, select action ); int select_notes_by_pitch (int note_h, int note_l); bool event_in_range ( const event & e, midibyte status, midipulse tick_s, midipulse tick_f ) const; bool get_selected_events_interval ( midipulse & first, midipulse & last ) const; bool rescale (int oldppqn, int newppqn); bool stretch_selected (midipulse delta); bool grow_selected (midipulse delta, int snap); bool copy_selected (eventlist & clipbd); bool paste_selected (eventlist & clipbd, midipulse tick, int note); midipulse trim_timestamp (midipulse t) const; midipulse clip_timestamp ( midipulse ontime, midipulse offtime, int snap ) const; void print () const; std::string to_string () const; void print_notes (const std::string & tag = "in list") const; const event::buffer & events () const { return m_events; } void set_length (midipulse len) { if (len > 0) m_length = len; } void zero_len_correction (midipulse zlc) { m_zero_len_correction = zlc; } }; // class eventlist } // namespace seq66 #endif // SEQ66_EVENTLIST_HPP /* * eventlist.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/jack_assistant.hpp ================================================ #if ! defined SEQ66_JACK_ASSISTANT_HPP #define SEQ66_JACK_ASSISTANT_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file jack_assistant.hpp * * This module declares/defines the base class for handling many facets * of performering (playing) a full MIDI song using JACK. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-23 * \updates 2022-09-26 * \license GNU GPLv2 or above * * This class contains a number of functions that used to reside in the * still-large performer module. */ #include "cfg/rcsettings.hpp" /* seq66::enum class timebase */ #include "midi/midibytes.hpp" /* seq66::midipulse alias */ #if defined SEQ66_JACK_SUPPORT #include #include #if defined SEQ66_JACK_SESSION #include #endif /** * This item exists in the JACK 2 source code, but not in the installed JACK * headers on our development system. */ #if defined SEQ66_JACK_METADATA #if defined __cplusplus extern "C" { #endif extern const char * JACK_METADATA_ICON_NAME; #if defined __cplusplus } /* namespace */ #endif #endif #else #undef SEQ66_JACK_SESSION #endif namespace seq66 { class performer; /* forward reference */ /** * Provide a temporary structure for passing data and results between a * performer and jack_assistant object. The jack_assistant class already * has access to the members of performer, but it needs access to and * modification of "local" variables in performer::output_func(). This * scratchpad structure is useful even if JACK support is not enabled. */ class jack_scratchpad { public: double js_current_tick; /**< Holds current location. */ double js_total_tick; /**< Current location ignoring L/R. */ double js_clock_tick; /**< Identical to js_total_tick. */ bool js_jack_stopped; /**< Flags performer::inner_stop(). */ bool js_dumping; /**< Non-JACK playback in progress? */ bool js_init_clock; /**< We now have a good JACK lock. */ bool js_looping; /**< seqedit loop button is active. */ bool js_playback_mode; /**< Song mode (versus live mode). */ double js_ticks_converted; /**< Keeps track of ...? */ double js_ticks_delta; /**< Minor difference in tick. */ double js_ticks_converted_last; /**< Keeps track of position? */ long js_delta_tick_frac; /**< More precision for seq66 0.9.3 */ public: jack_scratchpad (); void initialize (midipulse currenttick, bool islooping, bool songmode); void set_current_tick (midipulse curtick); void add_delta_tick (midipulse deltick); void set_current_tick_ex (midipulse curtick); }; #if defined SEQ66_JACK_SUPPORT /** * Provides an internal type to make it easier to display a specific and * accurate human-readable message when a JACK operation fails. */ using jack_status_pair_t = struct { /** * Holds one of the bit-values from jack_status_t, which is defined as an * "enum JackStatus" type. */ unsigned jf_bit; /** * Holds a textual description of the corresponding status bit. */ std::string jf_meaning; }; /** * This class provides the performance mode JACK support. */ class jack_assistant { friend int jack_transport_callback (jack_nframes_t nframes, void * arg); friend void jack_transport_shutdown (void * arg); #if defined SEQ66_USE_JACK_SYNC_CALLBACK friend int jack_sync_callback ( jack_transport_state_t state, jack_position_t * pos, void * arg ); #endif friend void jack_timebase_callback ( jack_transport_state_t state, jack_nframes_t nframes, jack_position_t * pos, int new_pos, void * arg ); #if defined SEQ66_JACK_SESSION friend void jack_session_callback (jack_session_event_t * ev, void * arg); #endif public: /** * p_position_structure holds frame_rate, ticks_per_beat, and * beats/minute. */ using parameters = struct { jack_position_t position; int period_size; /* frames per cycle */ int alsa_nperiod; /* usually 2 or 3 */ }; private: /** * Pairs the JACK status bits with human-readable descriptions of each * one. */ static jack_status_pair_t sm_status_pairs []; /** * For issue #100, storage for the true JACK transport position, etc. * Store the current JACK parameters, currently for display only. * Tired of being fooled about the actual parameters. */ static parameters sm_jack_parameters; /** * Provides the performer object that needs this JACK assistant/scratchpad * class. */ performer & m_jack_parent; /** * Provides a handle into JACK, so that the application, as a JACK * client, can issue commands and retrieve status information from JACK. */ mutable jack_client_t * m_jack_client; /** * A new member to hold the actual name of the client assigned by JACK. * We might show this in the user-interface at some point. */ std::string m_jack_client_name; /** * A new member to hold the actual UUID of the client assigned by JACK. * We might show this in the user-interface at some point. */ std::string m_jack_client_uuid; /** * Holds the current frame number obtained from JACK transport, via a * call to jack_get_current_transport_frame(). */ jack_nframes_t m_frame_current; /** * Holds the last frame number we got from JACK, so that progress can be * tracked. Also used in incrementing m_jack_tick. */ jack_nframes_t m_frame_last; /** * Provides positioning information on JACK playback. This structure is * filled via a call to jack_transport_query(). It holds, among other * items, the frame rate (often 48000), the ticks/beat, and the * beats/minute. */ jack_position_t m_jack_pos; /** * Holds the JACK transport state. Common values are * JackTransportStopped, JackTransportRolling, and JackTransportLooping. */ jack_transport_state_t m_transport_state; /** * Holds the last JACK transport state. */ jack_transport_state_t m_transport_state_last; /** * The tick/pulse value derived from the current frame number, the * ticks/beat value, the beats/minute value, and the frame rate. */ double m_jack_tick; /** * Indicates if JACK Sync has been enabled successfully. */ bool m_jack_running; /** * Indicates if JACK Sync has been enabled successfully, with the * application running as JACK Master. */ timebase m_timebase; /** * Holds the current frame rate. Just in case. QJackCtl does not always * set pos.frame_rate, so we get garbage and some strange BBT * calculations displayed in qjackctl. */ jack_nframes_t m_frame_rate; /** * Ostensibly a toggle, the functions that access this member are called * "jack_mode" functions. */ bool m_toggle_jack; /** * Used in jack_process_callback() to reposition when JACK transport is * not rolling or starting. Repositions the transport marker. */ midipulse m_jack_stop_tick; /** * Indicates to follow JACK transport. */ bool m_follow_transport; /** * Holds the global PPQN value for the Seq66 session. It is used * for calculating ticks/beat (pulses/beat) and for setting the tick * position. * */ int m_ppqn; /** * Holds the song's beats/measure value for using in setting JACK * position. */ int m_beats_per_measure; /** * Holds the song's beat width value (denominator of the time signature) * for using in setting JACK position. */ int m_beat_width; /** * Holds the song's beats/minute (BPM) value for using in setting JACK * position. */ midibpm m_beats_per_minute; public: jack_assistant ( performer & parent, midibpm bpminute, int ppqn, int bpm, int beatwidth ); ~jack_assistant (); static void show_position (const jack_position_t & pos); static bool save_jack_parameters ( const jack_position_t & p, int periodsize = 0, int alsanperiod = 0 ); static const parameters & get_jack_parameters (); performer & parent () /* getter needed for external callbacks. */ { return m_jack_parent; } const performer & parent () const { return m_jack_parent; } bool is_running () const { return m_jack_running; } bool is_master () const { return m_timebase == timebase::master; } bool is_slave () const { return m_timebase == timebase::slave; } bool no_transport () const { return m_timebase == timebase::none; } int get_ppqn () const { return m_ppqn; } int beat_width () const { return m_beat_width; } void set_beat_width (int bw) { m_beat_width = bw; } int beats_per_measure () const { return m_beats_per_measure; } void set_beats_per_measure (int bpm) { m_beats_per_measure = bpm; } midibpm get_beats_per_minute () const { return m_beats_per_minute; } void set_beats_per_minute (midibpm bpminute); jack_transport_state_t transport_state () const { return m_transport_state; } /** * Returns true if the JACK transport state is not JackTransportStarting. */ bool transport_not_starting () const { return m_transport_state != JackTransportStarting; } bool transport_rolling_now () const { return ( m_transport_state_last == JackTransportStarting && m_transport_state == JackTransportRolling ); } bool transport_stopped_now () const { return ( m_transport_state_last == JackTransportRolling && m_transport_state == JackTransportStopped ); } bool init (); bool deinit (); #if defined SEQ66_JACK_SESSION void session_event (jack_session_event_t * ev); #endif bool activate (); void start (); void stop (bool rewind = false); void position (bool state, midipulse tick = 0); bool output (jack_scratchpad & pad); /** * \setter m_ppqn * For the future, changing the PPQN internally. We should consider * adding validation. But it is used by performer. * * \param ppqn * Provides the PPQN value to set. */ void set_ppqn (int ppqn) { m_ppqn = ppqn; } double get_jack_tick () const { return m_jack_tick; } const jack_position_t & jack_pos () const { return m_jack_pos; } jack_position_t & jack_pos () { return m_jack_pos; } void toggle_jack_mode () { set_jack_mode(! m_jack_running); } void set_jack_mode (bool mode) { m_toggle_jack = mode; } /** * \getter m_toggle_jack * Seems misnamed. */ bool get_jack_mode () const { return m_toggle_jack; } midipulse jack_stop_tick () const { return m_jack_stop_tick; } void jack_stop_tick (long tick) { m_jack_stop_tick = tick; } jack_nframes_t jack_frame_rate () const { return m_frame_rate; } bool get_follow_transport () const { return m_follow_transport; } void set_follow_transport (bool aset) { m_follow_transport = aset; } void toggle_follow_transport () { set_follow_transport(! m_follow_transport); } jack_client_t * client () const { return m_jack_client; } const std::string & client_name () const { return m_jack_client_name; } const std::string & client_uuid () const { return m_jack_client_uuid; } private: /** * \setter m_jack_running * * \param flag * Provides the is-running value to set. */ void set_jack_running (bool flag) { m_jack_running = flag; } /** * Convenience function for internal use. Should we change 4.0 to a * member value? What does it mean? * * The old tick_multiplier() matches what seq24 does, but it * makes the tick delta dependent on "beatwidth / 4". It is was a bug, * Dave. * * \return * Returns the multiplier to convert a JACK tick value according to * the PPQN, ticks/beat, but not the beat-type. */ double tick_multiplier () const { return double(m_ppqn) / jack_pos().ticks_per_beat; } jack_client_t * client_open (const std::string & clientname); void get_jack_client_info (); long current_jack_position () const; #if defined SEQ66_USE_JACK_SYNC_CALLBACK int sync (jack_transport_state_t state = (jack_transport_state_t)(-1)); #endif #if defined USE_TIMEBASE_MASTER void update_timebase_master (jack_transport_state_t s); #endif void set_position (midipulse currenttick); }; // class jack_assistant /** * Global functions for JACK support and JACK sessions. */ #if defined SEQ66_USE_JACK_SYNC_CALLBACK extern int jack_sync_callback ( jack_transport_state_t state, jack_position_t * pos, void * arg ); #endif extern void jack_transport_shutdown (void * arg); extern void jack_timebase_callback ( jack_transport_state_t state, jack_nframes_t nframes, jack_position_t * pos, int new_pos, void * arg ); /* * Implemented second patch for JACK Transport from freddix/seq66 GitHub * project. Added the following functions. */ extern int jack_transport_callback (jack_nframes_t nframes, void * arg); extern jack_client_t * create_jack_client ( std::string clientname, std::string uuid = "" /* deprecated */ ); extern void jack_set_position ( jack_client_t * client, jack_position_t & pos, midipulse tick ); extern std::string get_jack_client_uuid (jack_client_t * jc); #if defined SEQ66_JACK_METADATA extern bool set_jack_client_property ( jack_client_t * jc, const std::string & key, const std::string & value, const std::string & type = "text/plain" ); extern bool set_jack_port_property ( jack_client_t * jc, jack_port_t * jp, const std::string & key, const std::string & value, const std::string & type = "text/plain" ); extern bool set_jack_port_property ( jack_client_t * jc, const std::string & portname, const std::string & key, const std::string & value, const std::string & type = "text/plain" ); #endif extern void show_jack_statuses (unsigned bits); extern std::string jack_state_name (const jack_transport_state_t & state); #if defined SEQ66_JACK_SESSION extern void jack_session_callback (jack_session_event_t * ev, void * arg); #endif #endif // SEQ66_JACK_SUPPORT } // namespace seq66 #endif // SEQ66_JACK_ASSISTANT_HPP /* * jack_assistant.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/mastermidibase.hpp ================================================ #if ! defined SEQ66_MASTERMIDIBASE_HPP #define SEQ66_MASTERMIDIBASE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file mastermidibase.hpp * * This module declares/defines the Master MIDI Bus base class. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-11-23 * \updates 2025-05-20 * \license GNU GPLv2 or above * * The mastermidibase module is the base-class version of the mastermidibus * module. There's a lot of common code needed by the various * implementations of the seq66::mastermidibus class: ALSA, RtMidi, and * PortMidi. */ #include /* for channel-filtered recording */ #include "midi/businfo.hpp" /* seq66::businfo & busarray */ #include "midi/midibase.hpp" /* seq66::midibase::io & recmutex */ #include "play/clockslist.hpp" /* list of seq66::e_clock settings */ #include "play/inputslist.hpp" /* list of boolean input settings */ namespace seq66 { class event; class midibus; class sequence; /** * The class that "supervises" all of the midibus objects? */ class mastermidibase { friend class performer; friend class midi_alsa_info; protected: /** * The ALSA/JACK MIDI client ID. */ int m_client_id; /** * The maximum number of busses supported. Set to c_busscount_max for * now. */ int m_max_busses; /** * MIDI buss announcer. Only for ALSA. */ midibus * m_bus_announce; /** * Encapsulates information about the input busses. */ busarray m_inbus_array; /** * Encapsulates information about the output busses. */ busarray m_outbus_array; /** * Saves the clock settings obtained from the "rc" (options) file so that * they can be loaded into the mastermidibus once it is created. */ clockslist m_master_clocks; /** * Saves the input settings obtained from the "[midi-input] section of * the "rc" (options) file, so that they can be loaded into the * mastermidibus once it is created. However, these items will be * modified if the actual enumerated input ports do not match the ports * read from the "rc" file. */ inputslist m_master_inputs; /** * The ID of the MIDI queue. */ int m_queue; /** * Resolution in parts per quarter note. */ int m_ppqn; /** * BPM (beats per minute). We had to lengthen this name; way too easy to * confuse it with "bpm" for "beats per measure". */ midibpm m_beats_per_minute; /** * For dumping MIDI input to a sequence for recording. This value is set * to true when a sequence editor window is open and the user has * clicked the "record MIDI" or "thru MIDI" button. See the * set_sequence_input() function. */ bool m_dumping_input; /** * Used for the new "stazed" feature of filtering MIDI channels so that * a sequence gets only the channels meant for it. We want to make this * a run-time, non-legacy option. */ std::vector m_vector_sequence; /** * If true, incoming data to the sequence that has the buss it is meant * for. */ bool m_record_by_buss; /** * If true, the m_vector_sequence container is used to divert incoming * data to the sequence that has the channel it is meant for. */ bool m_record_by_channel; /** * Points to the sequence object. Set in set_sequence_input(). See that * function's description. */ sequence * m_seq; /** * The locking mutex. This object is passed to an automutex object that * lends exception-safety to the mutex locking. */ recmutex m_mutex; public: mastermidibase () = delete; mastermidibase (int ppqn, midibpm bpm); virtual ~mastermidibase (); /** * Initialize the mastermidibus using the implementation-specific API * function. A return value would be nice. * * \param ppqn * The PPQN value to which to initialize the master MIDI buss. * * \param bpm * The beats/minute value to which to initialize the master MIDI * buss. */ virtual void init (int ppqn, midibpm bpm) { m_ppqn = ppqn; m_beats_per_minute = bpm; api_init(ppqn, bpm); } int client_id () const { return m_client_id; } int get_num_out_buses () const { return m_outbus_array.count(); } int get_num_in_buses () const { return m_inbus_array.count(); } bool record_by_buss () const { return m_record_by_buss; } void record_by_buss (bool flag) { m_record_by_buss = flag; } bool record_by_channel () const { return m_record_by_channel; } void record_by_channel (bool flag) { m_record_by_channel = flag; } midibpm get_beats_per_minute () const { return m_beats_per_minute; } int get_ppqn () const { return m_ppqn; } bool is_dumping_input () const { return m_dumping_input; } /** * Used only in performer::input_func() when not filtering MIDI input by * channel. */ sequence * get_sequence () const { return m_seq; } void start (); void stop (); void port_start (int client, int port); void port_exit (int client, int port); void play (bussbyte bus, event * e24, midibyte channel); void play_and_flush (bussbyte bus, event * e24, midibyte channel); void sysex (bussbyte bus, const event * event); void continue_from (midipulse tick); void init_clock (midipulse tick); void emit_clock (midipulse tick); void print () const; void flush (); void panic (int displaybuss = c_bussbyte_max); /* kepler34 func */ bool dump_midi_input (event in); /* seq32 function */ std::string get_midi_bus_name (bussbyte bus, midibase::io iotype) const; void set_midi_alias ( bussbyte bus, midibase::io iotype, const std::string & alias ) { if (iotype == midibase::io::input) m_master_inputs.set_alias(bus, alias); else m_master_clocks.set_alias(bus, alias); } std::string get_midi_alias (bussbyte bus, midibase::io iotype) const { return iotype == midibase::io::input ? m_master_inputs.get_alias(bus, portname::brief) : m_master_clocks.get_alias(bus, portname::brief) ; } int poll_for_midi (); bool set_sequence_input (bool state, sequence * seq); bool is_more_input (); /** * Grab a MIDI event via the currently-selected MIDI API. * No locking, so we make it an inline function. * * \param ev * The event to be set based on the found input event. */ bool get_midi_event (event * in) { return api_get_midi_event(in); } e_clock get_clock (bussbyte bus) const; bool set_clock (bussbyte bus, e_clock clock_type); bool get_input (bussbyte bus) const; bool set_input (bussbyte bus, bool inputing); bool is_input_system_port (bussbyte bus) const; bool is_port_unavailable (bussbyte bus, midibase::io iotype) const; bool is_port_locked (bussbyte bus, midibase::io iotype) const; void copy_io_busses (); void set_ppqn (int ppqn); void set_beats_per_minute (midibpm bpm); protected: void set_client_id (int id) { m_client_id = id; } /** * Used in the performer class to pass the settings read from the "rc" * file to here. There is an converse function defined below. */ void set_port_statuses (const clockslist & outs, const inputslist & ins) { m_master_clocks = outs; m_master_inputs = ins; } void get_port_statuses (clockslist & outs, inputslist & ins); void get_out_port_statuses (clockslist & outs); void get_in_port_statuses (inputslist & ins); e_clock clock (bussbyte bus) { return m_master_clocks.get(bus); } bool input (bussbyte bus) { return m_master_inputs.get(bus); } virtual bool activate (); virtual void api_init (int ppqn, midibpm bpm) = 0; /** * Provides MIDI API-specific functionality for the start() function. */ virtual void api_start () { // no code for base or portmidi } /** * Provides MIDI API-specific functionality for the continue_from() * function. */ virtual void api_continue_from (midipulse /* tick */) { // no code for base or portmidi } /** * Provides MIDI API-specific functionality for the init_clock() * function. */ virtual void api_init_clock (midipulse /* tick */) { // no code for base, alsa, or portmidi } /** * Provides MIDI API-specific functionality for the stop() function. */ virtual void api_stop () { // no code for base or portmidi } /** * Provides MIDI API-specific functionality for the set_ppqn() function. */ virtual void api_set_ppqn (int /* ppqn */) { // no code for base or portmidi } /** * Provides MIDI API-specific functionality for the * set_beats_per_minute() function. */ virtual void api_set_beats_per_minute (midibpm /* bpm */) { // no code for base } /** * Provides MIDI API-specific functionality for the flush() function. */ virtual void api_flush () { // no code for base or portmidi } /** * Provides MIDI API-specific functionality for the clock() function. */ virtual void api_clock () { // no code for base, alsmidi, or portmidi } virtual void api_client_port_start (int /* client */, int /* port */) { // no code for portmidi } virtual bool api_get_midi_event (event * inev) = 0; virtual int api_poll_for_midi (); /* * So far, there is no need for these API-specific functions. * * virtual void api_sysex (const event * ev) = 0; * virtual void api_play (bussbyte bus, const event * e24, midibyte channel) = 0; * virtual void api_set_clock (bussbyte bus, e_clock clocktype) = 0; * virtual void api_get_clock (bussbyte bus) = 0; * virtual void api_set_input (bussbyte bus, bool inputting) = 0; * virtual void api_get_input (bussbyte bus) = 0; */ private: bool save_clock (bussbyte bus, e_clock clock); bool save_input (bussbyte bus, bool inputing); }; // class mastermidibase } // namespace seq66 #endif // SEQ66_MASTERMIDIBASE_HPP /* * mastermidibase.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/mastermidibus.hpp ================================================ #if ! defined SEQ66_MASTERMIDIBUS_HPP #define SEQ66_MASTERMIDIBUS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file mastermidibus.hpp * * This module declares the right version of the mastermidibus header for the * current API. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-11-28 * \updates 2019-02-09 * \license GNU GPLv2 or above * */ #include "seq66-config.h" #if defined SEQ66_RTMIDI_SUPPORT #include "mastermidibus_rm.hpp" /* seq66::mastermidibus for RtMidi */ #elif defined SEQ66_PORTMIDI_SUPPORT #include "mastermidibus_pm.hpp" /* seq66::mastermidibus, PortMidi */ #elif defined SEQ66_WINDOWS_SUPPORT #include "mastermidibus_pm.hpp" /* Windows uses PortMIDI now */ #else #include "mastermidibus_rm.hpp" /* seq66::mastermidibus default */ #endif #endif // SEQ66_MASTERMIDIBUS_HPP /* * mastermidibus.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/midi_splitter.hpp ================================================ #if ! defined SEQ66_MIDI_SPLITTER_HPP #define SEQ66_MIDI_SPLITTER_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_splitter.hpp * * This module declares/defines the base class for MIDI files. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-11-24 * \updates 2021-06-10 * \license GNU GPLv2 or above * * Seq66 can also split an SMF 0 file into multiple tracks, effectively * converting it to SMF 1. This class holds all the information needed to do * that. */ #include namespace seq66 { class performer; class sequence; /** * This class handles the parsing and writing of SMF 0 files. */ class midi_splitter { private: /** * Provides support for SMF 0, indicates how many channels were found in * the file in a single sequence. SMF 1 file parsing will only warn * about more than one channel found in a given sequence. */ int m_smf0_channels_count; /** * Provides support for SMF 0, holds a bool value that indicates the * occurrence of a given channel. We don't have to worry about multiple * MIDI busses here, we hope. We do need to fake a channel 0 for * non-channel events like time-signature, SysEx, other meta messages. */ bool m_smf0_channels[16]; /** * Provides support for SMF 0, points to the initial SMF 0 sequence, from * which the single-channel sequences will be created. */ sequence * m_smf0_main_sequence; /** * Provides support for SMF 0, holds the prospective sequence number of * the main (SMF 0) sequence. We want to be able to add that sequence * last, for easier and cleaner removal of that sequence by the user. */ int m_smf0_seq_number; public: midi_splitter (); midi_splitter (const midi_splitter &) = delete; midi_splitter & operator = (const midi_splitter &) = delete; ~midi_splitter () = default; bool log_main_sequence (sequence & seq, int seqnum); void initialize (); void increment (int channel); bool split (performer & p, int screenset, int ppqn); /** * \getter m_smf0_channels_count */ int count () const { return m_smf0_channels_count; } private: bool split_channel ( const performer & p, const sequence & main_seq, sequence * seq, int channel ); }; // class midi_splitter } // namespace seq66 #endif // SEQ66_MIDI_SPLITTER_HPP /* * midi_splitter.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/midi_vector.hpp ================================================ #if ! defined SEQ66_MIDI_VECTOR_HPP #define SEQ66_MIDI_VECTOR_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_vector.hpp * * This module declares/defines the concrete class for a container of MIDI * data. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-10-11 * \updates 2021-10-04 * \license GNU GPLv2 or above * * This implementation attempts to avoid the reversals that can occur using * the list implementation. * * However, there is still another source of reversal that is not taken care * of? There's still a note about it at line #1049 of midifile.cpp. */ #include /* std::vector<> */ #include "midi/midi_vector_base.hpp" /* seq66::midi_vector_base ABC */ namespace seq66 { /** * This class is the std::vector implementation of the midi_vector_base. */ class midi_vector : public midi_vector_base { private: /** * Provides the type of this container. */ using bytes = std::vector; /** * The container itself. */ bytes m_char_vector; public: midi_vector (sequence & seq); /** * A rote constructor needed for a base class. */ virtual ~midi_vector() { // empty body } bool song_fill_track (int track, bool standalone = true); /** * \return * Returns the size of the container, in midibytes. */ virtual unsigned size () const { return unsigned(m_char_vector.size()); } /** * For iterating through the data in the MIDI vector, we are done when * we've gotten the last element of the container. * * \return * Returns true if the position is greater than or equal to the size * of the character vector. */ virtual bool done () const { return position() >= size(); } /** * Provides a way to add a MIDI byte into the list. The original seq64 * list used an std::list and a push_front operation. * * \param b * Provides the MIDI byte to push_back() into the character vector. */ virtual void put (midibyte b) { m_char_vector.push_back(b); } /** * Provide a way to get the next byte from the container. In this * implementation, m_position_for_get is used. As a side-effect, the * position value is incremented. * * \return * Returns the next byte in the character vector. */ virtual midibyte get () const { midibyte result = m_char_vector[position()]; position_increment(); return result; } /** * Provides a way to clear the container. */ virtual void clear () { m_char_vector.clear(); } }; } // namespace seq66 #endif // SEQ66_MIDI_VECTOR_HPP /* * midi_vector.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/midi_vector_base.hpp ================================================ #if ! defined SEQ66_MIDI_VECTOR_BASE_HPP #define SEQ66_MIDI_VECTOR_BASE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_vector_base.hpp * * This module declares the abstract base class for the management of some * MIDI events, using the sequence class. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-10-10 * \updates 2025-10-16 * \license GNU GPLv2 or above * * This class is meant to hold the bytes that represent MIDI events and other * MIDI data, which can then be dumped to a MIDI file. */ #include /* std::string class */ #include "midi/midibytes.hpp" /* seq66::midibyte */ namespace seq66 { class event; class performer; class sequence; class trigger; /** * Provides tags used by the midifile class to control the reading and * writing of the extra "proprietary" information stored in a Seq24 MIDI * file. See the cpp file for more information. */ const midilong c_midibus = 0x24240001; /**< Track out-buss number. */ const midilong c_midichannel = 0x24240002; /**< Track out-channel. */ const midilong c_midiclocks = 0x24240003; /**< Track clocking. */ const midilong c_triggers = 0x24240004; /**< See c_triggers_ex. */ const midilong c_notes = 0x24240005; /**< Song data. */ const midilong c_timesig = 0x24240006; /**< Track time signature. */ const midilong c_bpmtag = 0x24240007; /**< Song beats/minute. */ const midilong c_triggers_ex = 0x24240008; /**< Trigger data w/offset. */ const midilong c_mutegroups = 0x24240009; /**< Song mute group data. */ const midilong c_gap_A = 0x2424000A; /**< Gap. A. */ const midilong c_gap_B = 0x2424000B; /**< Gap. B. */ const midilong c_gap_C = 0x2424000C; /**< Gap. C. */ const midilong c_gap_D = 0x2424000D; /**< Gap. D. */ const midilong c_gap_E = 0x2424000E; /**< Gap. E. */ const midilong c_gap_F = 0x2424000F; /**< Gap. F. */ const midilong c_midictrl = 0x24240010; /**< Song MIDI control. */ const midilong c_musickey = 0x24240011; /**< The track's key. * */ const midilong c_musicscale = 0x24240012; /**< The track's scale. * */ const midilong c_backsequence = 0x24240013; /**< Background sequence. */ const midilong c_transpose = 0x24240014; /**< Track transpose value. */ const midilong c_perf_bp_mes = 0x24240015; /**< Perfedit beats/measure. */ const midilong c_perf_bw = 0x24240016; /**< Perfedit beat-width. */ const midilong c_tempo_map = 0x24240017; /**< Reserve seq32 tempo map. */ const midilong c_midiinbus = 0x24240018; /**< Track's input bus. */ const midilong c_musicchord = 0x24240019; /**< Reserved for expansion. */ const midilong c_tempo_track = 0x2424001A; /**< Alt tempo track number. */ const midilong c_seq_color = 0x2424001B; /**< Feature from Kepler34. */ const midilong c_seq_edit_mode = 0x2424001C; /**< Unused, Kepler34. */ const midilong c_seq_loopcount = 0x2424001D; /**< N-play loop, 0=infinite. */ const midilong c_reserved_3 = 0x2424001E; /**< Reserved for expansion. */ const midilong c_reserved_4 = 0x2424001F; /**< Reserved for expansion. */ const midilong c_trig_transpose = 0x24240020; /**< Triggers with transpose. */ /** * This class is the abstract base class for a container of MIDI track * information. It is the base class for midi_list and midi_vector. */ class midi_vector_base { private: /** * Provide a hook into a sequence so that we can exchange data with a * sequence object. */ sequence & m_sequence; /** * Provides the position in the container when making a series of get() * calls on the container. */ mutable unsigned m_position_for_get; public: midi_vector_base (sequence & seq); /** * A rote constructor needed for a base class. */ virtual ~midi_vector_base () { // empty body } void fill (int tracknumber, const performer & p, bool doseqspec = true); /** * Returns the size of the container, in midibytes. Must be overridden * in the derived class. */ virtual unsigned size () const = 0; /** * Instead of checking for the size of the container when "emptying" it * [see the midifile::write() function], use this function, which is * overridden to match the type of container being used. */ virtual bool done () const { return true; } /** * Provides a way to add a MIDI byte into the container. The original * seq66 container used an std::list and a push_front operation. */ virtual void put (midibyte b) = 0; /** * Combines a number of put() calls. It puts the preamble for a MIDI Meta * event. After this function is called, the call then puts() the actual * data. Note that the data-length is assumed to fit into a midibyte (255 * maximum). */ void put_meta (midibyte metavalue, int datalen, midipulse deltatime = 0); void put_seqspec (midilong spec, int datalen); /** * Provide a way to get the next byte from the container. It also * increments m_position_for_get. */ virtual midibyte get () const = 0; /** * Provides a way to clear the container. */ virtual void clear () = 0; protected: sequence & seq () { return m_sequence; } /** * Sets the position to 0 and then returns that value. So far, it is not * used, because we create a new midi_vector for each write_track() * call. */ unsigned position_reset () const { m_position_for_get = 0; return m_position_for_get; } /** * \getter m_position_for_get * Returns the current position. */ unsigned position () const { return m_position_for_get; } /** * \getter m_position_for_get * Increments the current position. */ void position_increment () const { ++m_position_for_get; } private: void add_byte (midibyte b) { put(b); } void add_varinum (midilong v); void add_long (midilong x); void add_short (midishort x); void add_event (const event & e, midipulse deltatime); void add_ex_event (const event & e, midipulse deltatime); void fill_meta_track_end (midipulse deltatime); void fill_proprietary (); protected: void fill_seq_number (int seq); void fill_seq_name (const std::string & name); #if defined SEQ66_USE_FILL_META_TEXT void fill_meta_text (midibyte metacode, const std::string & text); #endif #if defined SEQ66_USE_FILL_TIME_SIG_AND_TEMPO void fill_time_sig_and_tempo ( const performer & p, bool has_time_sig = false, bool has_tempo = false ); void fill_time_sig (const performer & p); void fill_tempo (const performer & p); #endif midipulse song_fill_seq_event ( const trigger & trig, midipulse prev_timestamp ); void song_fill_seq_trigger ( const trigger & trig, midipulse len, midipulse prev_timestamp ); }; // class midi_vector_base } // namespace seq66 #endif // SEQ66_MIDI_VECTOR_BASE_HPP /* * midi_vector_base.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/midibase.hpp ================================================ #if ! defined SEQ66_MIDIBASE_HPP #define SEQ66_MIDIBASE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midibase.hpp * * This module declares/defines the base class for MIDI I/O under Linux. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-11-24 * \updates 2023-05-14 * \license GNU GPLv2 or above * * The midibase module is the new base class for the various implementations * of the midibus module. There is enough commonality to be worth creating a * base class for all such classes. */ #include "midi/midibus_common.hpp" /* values and e_clock enumeration */ #include "midi/midibytes.hpp" /* seq66::midibyte alias */ #include "util/automutex.hpp" /* seq66::recmutex recursive mutex */ #include "util/basic_macros.h" /* not_nullptr() macro */ #undef SEQ66_SHOW_BUS_VALUES /* ca 2024-06-04 for investigation */ namespace seq66 { class event; /** * This class implements with ALSA version of the midibase object. */ class midibase { /** * The master MIDI bus sets up the buss. */ friend class mastermidibus; public: /** * Constants for selecting input versus output ports in a more obvious * way. These items are needed for the midi_mode() setter function. * Note that midi_mode() has no functionality in the midi_api base class, * which has a number of such stub functions so that we can use the * midi_info and midi_api derived classes. Tested by the is_input_port() * functions. * * We could add "max" to this enumeration. */ enum class io { input, /**< The port is an input MIDI port. */ output, /**< The port is an output MIDI port. */ indeterminate /**< Cannot determine the type of the port. */ }; /** * Constants for selecting virtual versus normal versus built-in system * ports. Used in the rtmidi midibus constructors. Tested by the * is_virtual_port() and is_system_port() functions. * * We could add "indeterminate" and "max" to this enumeration. */ enum class port { normal, /**< Able to be automatically connected. */ manual, /**< A virtual port (virtual is a keyword, though). */ system /**< A system port (ALSA only). */ }; private: /** * This is another name for "16 * 4". */ static int m_clock_mod; /** * Provides the index of the midibase object in either the input list or * the output list. Otherwise, it is currently -1. */ const int m_bus_index; /** * The buss ID of the Seq66 application as determined by the ALSA * subsystem. It is set in the midi_alsa constructor. If there are * no other MIDI *applications* running this value will end up being * 129. * * For JACK, this is currently set to the same value as the buss ID of * Seq66. */ int m_client_id; /** * The buss ID of the midibase object represents *other* MIDI devices and * applications (besides Seq66) present at Seq66 startup. For example, * on one system the IDs are 14 (MIDI Through), 20 (LaunchPad Mini), 128 * (TiMidity), and 129 (Yoshimi). */ int m_bus_id; /** * The port ID of the midibase object. Numbering startes at 0. */ int m_port_id; /** * The type of clock to use. The special value e_clock::disabled means * we will not be using the port, so that a failure in setting up the * port is not a "fatal error". We could have added an "m_outputing" * boolean as an alternative. However, we can overload m_inputing instead. */ e_clock m_clock_type; /** * This flag indicates if an input or output bus has been selected for * action as an input device (such as a MIDI controller). It is turned * on if the user selects the port in the Options / MIDI Input tab. */ bool m_io_active; /** * Indicates if the port is unavailable. For example, when the Windows * MIDI Mapper grabs the GS wave-table synthesizer. The port is not * just disabled... it cannot be enabled. */ bool m_unavailable; /** * Provides the PPQN value in force, currently a constant. * Some APIs can control or use this value. */ int m_ppqn; /** * Provides the PPQN value in force, currently a constant. * Some APIs can control or use this value. */ midibpm m_bpm; /** * Another ID of the MIDI queue? This is an implementation-dependent * value. For ALSA, it is the ALSA queue number. For PortMidi, this is * the old "m_pm_num" value. For RtMidi, it is not currently used. */ int m_queue; /** * Holds the full display name of the bus, index, ID numbers, and item * names. Assembled by the set_name() function. */ std::string m_display_name; /** * The name of the MIDI buss. This should be something like a major device * name or the name of a subsystem such as Timidity. */ std::string m_bus_name; /** * The name of the MIDI port. This should be the name of a specific device * or port on a major device. This value, for JACK is reconstructed by * set_alt_name() so that it is essentially the "short" port name that JACK * recognizes. */ std::string m_port_name; /** * The alias of the MIDI port. This item is specific to JACK, and is * empty for other APIs. */ std::string m_port_alias; /** * The last (most recent? final?) tick. */ midipulse m_lasttick; /** * Indicates if the port is to be an input (versus output) port. * It matters when we are creating the name of the port, where we don't * want an input virtual port to have the same name as an output virtual * port... one of them will fail. */ io m_io_type; /** * Indicates if the port is a system port. Two examples are the ALSA * System Timer buss and the ALSA System Announce bus, the latter being * necessary for input subscription and notification. For most ports, * this value will be port::normal. A restricted setter is provided. * Only the rtmidi ALSA implementation sets up system ports. */ port m_port_type; /** * Locking mutex. This one is based on std:::recursive_mutex. */ recmutex m_mutex; public: midibase ( const std::string & appname, /* usually the app name */ const std::string & busname, /* subsystem name */ const std::string & portname, int index, /* 0, a display ordinal */ int bus_id, /* null_buss() */ int port_id, /* bad_id() */ int queue, /* bad_id() */ int ppqn, /* use_default_ppqn */ midibpm bpm, /* default_bpm */ io iotype, port porttype, const std::string & portalias = "" /* JACK only */ ); virtual ~midibase (); #if defined SEQ66_SHOW_BUS_VALUES void show_bus_values (); #endif static void show_clock (const std::string & context, midipulse tick); const std::string & display_name () const { return m_display_name; } const std::string & bus_name () const { return m_bus_name; } const std::string & port_name () const { return m_port_name; } const std::string & port_alias () const { return m_port_alias; } std::string connect_name () const; int bus_index () const { return m_bus_index; } int client_id () const { return m_client_id; } int bus_id () const { return m_bus_id; } int port_id () const { return m_port_id; } int ppqn () const { return m_ppqn; } midibpm bpm () const { return m_bpm; } /** * Checks if the given parameters match the current bus and port numbers. */ bool match (int bus, int port) { return (m_port_id == port) && (m_bus_id == bus); } port port_type () const { return m_port_type; } bool is_virtual_port () const { return m_port_type == port::manual; } /** * This function is needed in the rtmidi library to set the is-virtual * flag in the api_init_*_sub() functions, so that midi_alsa, midi_jack * (and any other additional APIs that end up supported by our * heavily-refactored rtmidi library), as well as the original midibus, * can know that they represent a virtual port. */ void is_virtual_port (bool flag) { if (! is_system_port()) m_port_type = flag ? port::manual : port::normal; } io io_type () const { return m_io_type; } bool is_input_port () const { return m_io_type == io::input; } bool is_output_port () const { return m_io_type == io::output; } void is_input_port (bool flag) { m_io_type = flag ? io::input : io::output ; } bool is_system_port () const { return m_port_type == port::system; } bool is_port_connectable () const; bool set_clock (e_clock clocktype); e_clock get_clock () const { return m_clock_type; } bool port_enabled () const /* replaces get_input() */ { return m_io_active; } bool port_unavailable () const { return m_unavailable; } bool clock_enabled () const { return clocking_enabled(m_clock_type); /* pos and mod enabled */ } void set_io_status (bool flag) { m_io_active = flag; } void set_port_unavailable () { m_unavailable = true; } int queue_number () const { return m_queue; } /** * Useful for setting the buss ID when using the rtmidi_info object to * create a list of busses and ports. Would be protected, but midi_alsa * needs to change this value to reflect the user-client ID actually * assigned by ALSA. (That value ranges from 128 to 191.) */ void set_bus_id (int id) { m_bus_id = id; } void set_client_id (int id) { m_client_id = id; } void set_name ( const std::string & appname, const std::string & busname, const std::string & portname ); void set_alt_name ( const std::string & appname, const std::string & busname ); /** * Set the clock mod to the given value, if legal. * * \param clockmod * If this value is not equal to 0, it is used to set the static * member m_clock_mod. */ static void set_clock_mod (int clockmod) { if (clockmod != 0) m_clock_mod = clockmod; } /** * Get the clock mod value. */ static int get_clock_mod () { return m_clock_mod; } /** * Obtains a MIDI event. * * \param inev * Points the event to be filled with the MIDI event data. * * \return * Returns true if an event was found, thus making the return parameter * useful. */ bool get_midi_event (event * inev) { return api_get_midi_event(inev); } /** * Polls for MIDI events. This is a fix for a PortMidi bug, but it is * needed for all. * * \return * Returns a value greater than 0 if MIDI events are available. * Otherwise 0 is returned, or -1 for some APIs (ALSA) when an internal * error occurs. */ int poll_for_midi () { return m_io_active ? api_poll_for_midi() : 0 ; } void play (const event * e24, midibyte channel); void sysex (const event * e24); void flush (); void start (); void stop (); void clock (midipulse tick); void continue_from (midipulse tick); void init_clock (midipulse tick); void print (); bool set_input (bool inputing); bool initialize (bool initdisabled); private: bool init_out () { return api_init_out(); } bool init_in () { return api_init_in(); } bool init_out_sub () { return api_init_out_sub(); // no portmidi implementation } bool init_in_sub () { return api_init_in_sub(); // no portmidi implementation } bool deinit_in () { return api_deinit_out(); } bool deinit_out () { return api_deinit_in(); } public: void display_name (const std::string & name) { m_display_name = name; } void bus_name (const std::string & name) { m_bus_name = name; } void port_name (const std::string & name) { m_port_name = name; } /** * Useful for setting the port ID when using the rtmidi_info object to * inspect and create a list of busses and ports. */ void set_port_id (int id) { m_port_id = id; } virtual bool is_port_locked () const { return false; /* not supported in Linux, just Windows */ } /** * Now defined in the ALSA implementation, and used by mastermidibus. * Also used in the JACK implementation. */ virtual int api_poll_for_midi () { return 0; } /** * Used in the JACK implementation. */ virtual bool api_get_midi_event (event * inev) { return not_nullptr(inev); } /** * Not defined in the PortMidi implementation. */ virtual bool api_init_in_sub () { return false; /* no code for portmidi */ } /** * Not defined in the PortMidi implementation. */ virtual bool api_init_out_sub () { return false; /* no code for portmidi */ } /** * Not defined in the PortMidi implementation. */ virtual bool api_deinit_out () { return false; } virtual bool api_deinit_in () { return false; } virtual void api_play (const event * e24, midibyte channel) = 0; /** * Handles implementation details for SysEx messages. * * The \a e24 parameter, the SysEx event pointer, is unused here. */ virtual void api_sysex (const event * /* e24 */) { // no code for portmidi } /** * Handles implementation details for the flush() function. */ virtual void api_flush () { // no code for portmidi } protected: virtual bool api_init_in () = 0; virtual bool api_init_out () = 0; virtual void api_continue_from (midipulse tick, midipulse beats) = 0; virtual void api_start () = 0; virtual void api_stop () = 0; virtual void api_clock (midipulse tick) = 0; }; // class midibase } // namespace seq66 #endif // SEQ66_MIDIBASE_HPP /* * midibase.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/midibus.hpp ================================================ #ifndef SEQ66_MIDIBUS_HPP #define SEQ66_MIDIBUS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midibus.hpp * * This module declares the right version of the midibus header for the * current API. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-11-28 * \updates 2019-03-15 * \license GNU GPLv2 or above * */ #include "seq66-config.h" /* generated by configure */ #if defined SEQ66_RTMIDI_SUPPORT #include "midibus_rm.hpp" /* seq66::midibus for RtMidi */ #elif defined SEQ66_PORTMIDI_SUPPORT #include "midibus_pm.hpp" /* seq66::midibus, PortMidi */ #elif defined SEQ66_WINDOWS_SUPPORT #include "midibus_pm.hpp" /* Windows uses PortMIDI now */ #else #include "midibus_rm.hpp" /* seq66::midibus for RtMidi */ #endif #endif // SEQ66_MIDIBUS_HPP /* * midibus.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/midibus_common.hpp ================================================ #if ! defined SEQ66_MIDIBUS_COMMON_HPP #define SEQ66_MIDIBUS_COMMON_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midibus_common.hpp * * This module declares/defines the elements that are common to the Linux * and Windows implmentations of midibus. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2023-06-25 * \license GNU GPLv2 or above * * Defines some midibus constants and the clock_e enumeration. */ namespace seq66 { /** * Manifest global constants. These constants were also defined in * midibus_portmidi.h, but we made them common to both implementations here. * * The c_midibus_output_size value is passed, in mastermidibus, to * snd_seq_set_output_buffer_size(). Not sure if the value needs to be so * large. */ const int c_midibus_output_size = 0x100000; // 1048576 /** * The c_midibus_input_size value is passed, in mastermidibus, to * snd_seq_set_input_buffer_size(). Not sure if the value needs to be so * large. */ const int c_midibus_input_size = 0x100000; // 1048576 /** * Controls the amount a SysEx data sent at one time, in the midibus module. */ const int c_midibus_sysex_chunk = 0x100; // 256 /** * A clock enumeration, as used in the File / Options / MIDI Clock dialog. * It is also (perhaps ill-advisedly) used for other statuses, including * for some input port statuses. * * \var unavailable * This value indicates that a port defined in a port-map is not * present on the system. * * \var disabled * A value to indicate to ignore/disable an output port. If a port always * fails to open, we want just to ignore it. But see the unavailable * status above. * * \var none * Corresponds to the "Off" selection in the MIDI Clock tab. With * this setting, the MIDI Clock is disabled for the buss using this * setting. Notes will still be sent that buss, of course. Some * software synthesizers might require this setting in order to make * a sound. This value also doubles as "enabled" for inputs, which * don't support the concept of clocks. * * \var pos * Corresponds to the "Pos" selection in the MIDI Clock tab. With * this setting, MIDI Clock will be sent to this buss, and, if * playback is starting beyond tick 0, then MIDI Song Position and * MIDI Continue will also be sent on this buss. * * \var mod * Corresponds to the "Mod" selection in the MIDI Clock tab. With * this setting, MIDI Clock and MIDI Start will be sent. But * clocking won't begin until the Song Position has reached the start * modulo (in 1/16th notes) that is specified. * * \var max * Illegal value for terminator. Follows our convention for enum * class maximums-but-out-of-bounds. */ enum class e_clock { unavailable = -2, disabled = -1, none = 0, pos, mod, max }; // enum class e_clock /* * Inline free functions. */ inline e_clock int_to_clock (int e) { return e < static_cast(e_clock::max) ? static_cast(e) : e_clock::disabled ; } inline int clock_to_int (e_clock e) { return static_cast(e == e_clock::max ? e_clock::disabled : e); } inline bool clocking_enabled (e_clock ce) { return ce == e_clock::pos || ce == e_clock::mod; } inline bool port_unavailable (e_clock ce) { return ce == e_clock::unavailable; } inline bool port_disabled (e_clock ce) { return ce == e_clock::disabled; } } // namespace seq66 #endif // SEQ66_MIDIBUS_COMMON_HPP /* * midibus_common.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/midibytes.hpp ================================================ #if ! defined SEQ66_MIDIBYTES_HPP #define SEQ66_MIDIBYTES_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midibytes.hpp * * This module declares a number of useful aliases (in place of the old C++ * stand-by for type definition). * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-09 * \updates 2025-07-09 * \license GNU GPLv2 or above * * These alias specifications are intended to remove the ambiguity we have * seen between signed and unsigned values. MIDI bytes and pulses, ticks, or * clocks are, by their nature, unsigned, and we should enforce that. * * One minor issue is why we didn't tack on "_t" to most of these types, to * adhere to C conventions. Well, no real reason except to save a couple * characters and save some beauty. Besides, it is easy to set up vim to * highlight these new types in a special color, making them stand out easily * while reading the code. * * Also included are some small classes for encapsulating MIDI timing * information. */ #include /* ULONG_MAX and other limits */ #include /* uint64_t and other types */ #include /* std::string, basic_string */ #include /* std::vector */ #include "seq66_features.hpp" /* seq66::seq_version_text(), etc. */ /* * Since we're using unsigned variables for counting pulses, we can't do the * occasional test for negativity, we have to use wraparound. One way is to * use this macro. However, we will probably just ignore the issue of * wraparound. With 32-bit longs, we have a maximum of 4,294,967,295. * Even at an insane PPQN of 9600, that's almost 450,000 quarter notes. * And for 64-bit code? Forgeddaboudid! * * #define IS_SEQ66_MIDIPULSE_WRAPAROUND(x) ((x) > (ULONG_MAX / 2)) */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /** * Provides a fairly common type definition for a byte value. This can be * used for a MIDI buss/port number or for a MIDI channel number. */ using midibyte = unsigned char; /** * Provides an array-like container for midibytes. */ using midibytes = std::vector; /** * There are issues with using std::vector, so we need a type that can * be returned by reference. See the mutes class. */ using midibool = unsigned char; /** * Distinguishes a buss/bus number from other MIDI bytes. */ using bussbyte = unsigned char; /** * Distinguishes a short value from the unsigned short values implicit in * short-valued MIDI numbers. */ using midishort = unsigned short; /** * Provides a 4-byte value for use in reading MIDI files. */ using miditag = uint32_t; /** * Distinguishes a long value from the unsigned long values implicit in * long-valued MIDI numbers. */ using midilong = unsigned long; /** * Distinguishes a JACK tick from a MIDI tick (pulse). The latter are ten * times as long as the JACK tick. */ using jacktick = long; /** * Distinguishes a long value from the unsigned long values implicit in MIDI * time measurements. * * However, if you make this value unsigned, then perfroll won't show any * notes in the sequence bars!!! Also, a number of manipulations of this * type currently depend upon it being a signed value. * * JACK timestamps are in units of "frames": * * typedef uint32_t jack_nframes_t; * #define JACK_MAX_FRAMES (4294967295U) // UINT32_MAX * * A long value is the same size in 32-bit code, and longer in 64-bit code, * so we can use a midipulse to hold a frame number. */ using midipulse = long; /** * JACK encodes jack_time_t as a uint64_t (8-byte) value. We will use our * own alias, of course. The unsigned long long type is guaranteed to be at * least 8 bytes long on all platforms, but could be longer. */ using microsec = uint64_t; /** * Provides the data type for BPM (beats per minute) values. This value used * to be an integer, but we need to provide more precision in order to * support better tempo matching. */ using midibpm = double; /* * Container types. The next few types are common enough to warrant aliasing in * this file. */ /** * Provides a string specialization to explicitly use unsigned characters. * * using midistring = std::basic_string; */ /** * Provides a convenient way to package a number of booleans, such as * mute-group values or a screenset's sequence statuses. */ using midibooleans = std::vector; /** * Default settings for MIDI as per the specification. */ const int c_midi_clocks_per_metronome = 24; const int c_midi_32nds_per_quarter = 8; const int c_midi_pitch_wheel_range = 2; /* +/- 2 semitones */ /** * We need a unique midipulse value that can be used to be indicate a bad, * unusable pulse value (type definition midipulse). This value should be * modified if the alias of midipulse is changed. For a signed long value, * -1 can be used. For an unsigned long value, ULONG_MAX is probably best. * To avoid issues, when testing for this value, use the inline function * is_null_midipulse(). */ const midipulse c_null_midipulse = -1; /* ULONG_MAX later? */ const midipulse c_midipulse_max = LONG_MAX; /* for sanity checks */ /** * Defines the maximum number of MIDI values, and one more than the * highest MIDI value, which is 17. */ const midibyte c_midibyte_data_max = midibyte(0x80u); const midibyte c_midibyte_value_max = 127; /** * The number of MIDI notes supported. The notes range from 0 to 127 (0x7F). */ const int c_notes_count = 128; const midibyte c_note_max = 0x7F; /** * Maximum and unusable values. Use these values to avoid sign issues. * Also see c_null_midipulse. No global buss override is in force if the * buss override number is c_bussbyte_max (0xFF). */ const midibyte c_midibyte_max = midibyte(0xFFu); const bussbyte c_bussbyte_max = bussbyte(0xFFu); const midishort c_midishort_max = midishort(0xFFFF); const midilong c_midilong_max = midilong(0xFFFFFFFF); /** * Default value for c_max_busses. Some people use a lot of ports, so we * have increased this value from 32 to 48. */ const int c_busscount_max = 48; /** * Indicates the maximum number of MIDI channels, counted internally from 0 * to 15, and by humans (sequencer user-interfaces) from 1 to 16. This value * is also used as a code to indicate that a sequence will use the events * present in the channel. */ const int c_midichannel_max = 16; const int c_midichannel_null = 0x80; /** * Indicates an integer that is not a valid ID. IDs normally start from 0, * this value is negative. */ const int c_bad_id = (-1); /* * ------------------------------------------------------------------------- * midi_measures * ------------------------------------------------------------------------- */ /** * Provides a data structure to hold the numeric equivalent of the measures * string "measures:beats:divisions" ("m:b:d"). More commonly known as * "bars:beats:ticks", or "BBT". */ class midi_measures { private: /** * The integral number of measures in the measures-based time. */ int m_measures; /** * The integral number of beats in the measures-based time. */ int m_beats; /** * The integral number of divisions/pulses in the measures-based time. * There are two possible translations of the two bytes of a division. If * the top bit of the 16 bits is 0, then the time division is in "ticks * per beat" (or “pulses per quarter note”). If the top bit is 1, then * the time division is in "frames per second". This member deals only * with the ticks/beat definition. */ int m_divisions; public: midi_measures (); midi_measures (int measures, int beats, int divisions); void clear () { m_measures = m_beats = m_divisions = 0; } int measures () const { return m_measures; } /** * \setter m_measures * * \param m * The value to which to set the number of measures. * We can add validation later. */ void measures (int m) { m_measures = m; } int beats () const { return m_beats; } /** * \setter m_beats * * \param b * The value to which to set the number of beats. * We can add validation later. */ void beats (int b) { m_beats = b; } int divisions () const { return m_divisions; } /** * \setter m_divisions * * \param d * The value to which to set the number of divisions. * We can add validation later. */ void divisions (int d) { m_divisions = d; } }; // class midi_measures /* * ------------------------------------------------------------------------- * midi_timing * ------------------------------------------------------------------------- */ /** * We anticipate the need to have a small structure holding the parameters * needed to calculate MIDI times within an arbitrary song. Although * Seq24/Seq66 currently are heavily dependent on hard-wired values, * that will be rectified eventually, so let us get ready for it. */ class midi_timing { /** * This value should match the BPM value selected when editing the song. * This value is most commonly set to 120, but is also read from the MIDI * file. This value is needed if one want to calculate durations in true * time units such as seconds, but is not needed to calculate the number * of pulses/ticks/divisions. */ midibpm m_beats_per_minute; /* T (tempo, BPM in upper-case) */ /** * This value should match the numerator value selected when editing the * sequence. This value is most commonly set to 4. */ int m_beats_per_measure; /* B (bpm in lower-case) */ /** * This value should match the denominator value selected when editing * the sequence. This value is most commonly set to 4, meaning that the * fundamental beat unit is the quarter note. * */ int m_beat_width; /* W (bw in lower-case) */ /** * This value provides the precision of the MIDI song. This value is * most commonly set to 192, but is also read from the MIDI file. We are * still working getting "non-standard" values to work. */ int m_ppqn; /* P (PPQN or ppqn) */ public: midi_timing (); midi_timing (midibpm bpminute, int bpmeasure, int beatwidth, int ppqn); midibpm beats_per_minute () const { return m_beats_per_minute; } /** * \setter m_beats_per_minute * * \param b * The value to which to set the number of beats/minute. * We can add validation later. */ void beats_per_minute (midibpm b) { m_beats_per_minute = b; } int beats_per_measure () const { return m_beats_per_measure; } /** * \setter m_beats_per_measure * * \param b * The value to which to set the number of beats/measure. * We can add validation later. */ void beats_per_measure (int b) { m_beats_per_measure = b; } int beat_width () const { return m_beat_width; } /** * \setter m_beats_per_beat_width * * \param bw * The value to which to set the number of beats in the denominator * of the time signature. We can add validation later. */ void beat_width (int bw) { m_beat_width = bw; } int ppqn () const { return m_ppqn; } /** * \setter m_ppqn * * \param p * The value to which to set the PPQN member. * We can add validation later. */ void ppqn (int p) { m_ppqn = p; } }; // class midi_timing /** * Compares a midipulse value to c_null_midipulse. By "null" in this * case, we mean "unusable", not 0. Sigh, it's always something. */ inline bool is_null_midipulse (midipulse p) { return p == c_null_midipulse; } /** * Compares a bussbyte value to the maximum value. The maximum value is well * over the c_busscount_max = 48 value, being 0xFF = 255, and thus is a * useful flag value to indicate an unusable bussbyte. */ inline bool is_null_buss (bussbyte b) { return b == c_bussbyte_max; } inline bussbyte null_buss () { return c_bussbyte_max; } inline bool is_good_buss (bussbyte b) { return b < bussbyte(c_busscount_max); } inline bool is_valid_buss (bussbyte b) { return is_good_buss(b) || is_null_buss(b); } inline bool is_good_busscount (int b) { return b > 0 && b <= c_busscount_max; } inline bool is_good_data_byte (midibyte b) { return b < c_midibyte_data_max; } inline midibyte max_midibyte () { return c_midibyte_max; /* 255 */ } inline midibyte max_midi_value () { return c_midibyte_value_max; /* 127 */ } inline midibyte clamp_midibyte_value (int b) { if (b < 0) return 0; else if (b > max_midi_value()) return max_midi_value(); else return midibyte(b); } inline midibyte abs_midibyte_value (int b) { if (b < 0) b = -b; if (b > max_midi_value()) return max_midi_value(); else return midibyte(b); } inline const midibyte * midi_bytes (const midibytes & b) { return b.data(); } /* * More free functions, not inline. */ extern std::string midi_bytes_string (const midibytes & b, int limit = 0); extern midibyte string_to_midibyte (const std::string & s, midibyte defalt = 0); /* * extern midibytes midi_bytes_from_midi_string (const midistring & b); */ extern midibooleans fix_midibooleans (const midibooleans & mbs, int newsz); /** * Compares a channel value to the maximum (and illegal) value. */ inline bool is_null_channel (midibyte c) { return c == c_midichannel_null; } inline midibyte null_channel () { return c_midichannel_null; } inline bool is_good_channel (midibyte c) { return c < c_midichannel_max; /* 0 to 15 are good */ } inline bool is_valid_channel (midibyte c) { return is_good_channel(c) || is_null_channel(c); /* null is valid */ } inline int bad_id () { return c_bad_id; } /** * This function is meant to deal with values that range from 0 to 127, but * need to be displayed over a different number of pixels. In qseqdata, the * data is normally shown as one pixel per value (up to 128 pixels). But now * we want a height half that; it's better to provide a function for that. * * \param height * The current height of the range in pixels. This will be used to scale * the value against 128. * * \param value * The value of the byte, assumed to range from 0 to 127. * * \return * Returns the actual pixel height of the value in the range from * 0 to height, versus 0 to 128. */ inline int byte_height (int height, midibyte value) { const int s_max_height = 128; return height == s_max_height ? int(value) : int(value) * height / s_max_height; } /** * The inverse of byte_height(). Parameters and result not checked, for * speed. Note that height can represent the y-difference between two * pixels. */ inline int byte_value (int height, int value) { const int s_max_height = 128; return height == s_max_height ? value : s_max_height * value / height; } /* * In the latest versions of JACK, 0xFFFE is the macro "NO_PORT". Although * krufty, we can use this value in Seq66 no matter the version of JACK, or * even what API is used. */ inline uint32_t null_system_port_id () { return 0xFFFE; } inline bool is_null_system_port_id (uint32_t portid) { return portid == null_system_port_id(); } } // namespace seq66 #endif // SEQ66_MIDIBYTES_HPP /* * midibytes.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/midifile.hpp ================================================ #if ! defined SEQ66_MIDIFILE_HPP #define SEQ66_MIDIFILE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midifile.hpp * * This module declares/defines the base class for MIDI files. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2025-10-16 * \license GNU GPLv2 or above * * The Seq24 MIDI file is a standard, Format 1 MIDI file, with some extra * "proprietary" tracks that hold information needed to set up the song in * Seq24. * * Seq66 can write out the Seq24 file with the "proprietary" tracks written * in a format more palatable for strict MIDI programs, such as midicvt (a * MIDI-to-ASCII conversion program available at the * https://github.com/ahlstromcj/midicvt.git repository. * * Seq66 can also split an SMF 0 file into multiple tracks, effectively * converting it to SMF 1. */ #include #include #include #include "cfg/rcsettings.hpp" /* enum class rsaction */ #include "midi/midibytes.hpp" /* midishort, midibyte, etc. */ #include "midi/midi_splitter.hpp" /* seq66::midi_splitter */ #include "util/automutex.hpp" /* seq66::recmutex, automutex */ namespace seq66 { class event; class midi_splitter; class midi_vector; class performer; /** * This class handles the parsing and writing of MIDI files. In addition to * the standard MIDI tracks, it also handles some "private" or "proprietary" * tracks specific to Seq24. It does not, however, handle SYSEX events. */ class midifile { public: /** * Instead of having two save options, we now have three. These values * were used in seq_gtkmm2/src/mainwnd.cpp. Should use them in * qsmainwnd. Currently unused; distinguished by function call. */ enum class save_option { normal, export_song, export_midi }; static const std::string sm_meta_text_labels[8]; private: /** * Provides locking for the sequence. Made mutable for use in * certain locked getter functions. */ mutable seq66::recmutex m_mutex; /** * Indicates if we are reading this file simply to verify it. If so, * then the song data will be removed after checking, via a call to * performer::clear_all(). */ bool m_verify_mode; /** * Holds the size of the MIDI file. This variable was added when loading * a file that caused an attempt to load data well beyond the file-size * of the midicvt test file Dixie04.mid. */ size_t m_file_size; /** * Holds the last error message, useful for trouble-shooting without * having Seq66 running in a console window. If empty, there's no * pending error. Currently most useful in the parse() function. */ std::string m_error_message; /** * Indicates if the error should be considered fatal to the loading of * the midifile. The caller can query for this value after getting the * return value from parse(). */ bool m_error_is_fatal; /** * Indicates that file reading has already been disabled (due to serious * errors), so don't complain about it anymore. Once is enough. */ bool m_disable_reported; /** * Holds the value for how to handle mistakes in running status. */ rsaction m_running_status_action; /** * Holds the position in the MIDI file. This is at least a 31-bit * value in the recent architectures running Linux and Windows, so it * will handle up to 2 Gb of data. This member is used as the offset * into the m_data vector. */ size_t m_pos; /** * The unchanging name of the MIDI file. */ const std::string m_name; /** * This vector of characters holds our MIDI data. We could also use * a string of characters, unsigned. This member is resized to the * putative size of the MIDI file, in the parse() function. Then the * whole file is read into it, as if it were an array. This member is an * input buffer. */ midibytes m_data; /* std::vector */ /** * Provides a list of characters. The class pushes each MIDI byte into * this list using the write_byte() function. Also note that the write() * function calls sequence::fill_list() to fill a temporary * std::list (!) buffer, then writes that data backwards to * this member. This member is an output buffer. */ midibytes m_char_list; /* std::list */ /** * Indicates to store the new key, scale, and background * sequence in the global, "proprietary" section of the MIDI song. */ bool m_global_bgsequence; /** * Indicates that we are rescaling the PPQN of a file as it is read in. */ bool m_use_scaled_ppqn; /** * Provides the current value of the PPQN, which used to be constant. */ int m_ppqn; /** * The value of the PPQN from the file itself. */ int m_file_ppqn; /** * Provides the ratio of the main PPQN to the file PPQN, for use with * scaling. */ double m_ppqn_ratio; /** * Holds the first beats-per-minute value found, either from a track * or from the c_bpmtag SeqSpec. If 0, there is no tempo, and * this value will be set to 120.0. */ midibpm m_main_bpm; /** * Holds the format of the MIDI file as read. */ int m_file_format; /** * Provides support for SMF 0. This object holds all of the information * needed to split a multi-channel sequence. */ midi_splitter m_smf0_splitter; public: midifile ( const std::string & name, int ppqn, bool globalbgs = true, bool playlistmode = false ); virtual ~midifile (); virtual bool parse ( performer & p, int screenset = 0, bool importing = false ); virtual bool write (performer & p, bool doseqspec = true); bool write_song (performer & p); bool write_one_pattern (performer & p, int track); const std::string & error_message () const { return m_error_message; } bool error_is_fatal () const { return m_error_is_fatal; } /** * \getter m_ppqn * Provides a way to get the actual value of PPQN used in processing * the sequences when parse() was called. The PPQN will be either * the global ppqn (legacy behavior) or the value read from the * file, depending on the ppqn parameter passed to the midifile * constructor. */ int ppqn () const { return m_ppqn; } int file_ppqn () const { return m_file_ppqn; } double ppqn_ratio () const { return m_ppqn_ratio; } bool scaled () const { return m_use_scaled_ppqn; } /** * Current position in the data stream. */ size_t pos () { return m_pos; } protected: virtual sequence * create_sequence (performer & p); virtual bool finalize_sequence ( performer & p, sequence & seq, int seqnum, int screenset ); bool verify_mode () const { return m_verify_mode; } void clear_errors () { m_error_message.clear(); m_disable_reported = false; } void ppqn (int p) { m_ppqn = p; } void file_ppqn (int p) { m_file_ppqn = p; } void ppqn_ratio (double r) { m_ppqn_ratio = r; } void scaled (bool flag) { m_use_scaled_ppqn = flag; } /** * Checks if the data stream pointer has reached the end position. * * \return * Returns true if the read pointer is at the end. */ bool at_end () const { return m_disable_reported || m_pos >= m_file_size; } bool grab_input_stream (const std::string & tag); bool parse_smf_0 (performer & p, int screenset); bool parse_smf_1 ( performer & p, int screenset, bool convert_smf0 = false ); midilong parse_seqspec_header (int file_size); bool parse_seqspec_track (performer & p, int file_size); bool prop_header_loop (performer & p, int file_size); bool parse_c_midictrl (performer & p); bool parse_c_midiclocks (performer & p); bool parse_c_notes (performer & p); bool parse_c_bpmtag (performer & p); bool parse_c_mutegroups (performer & p); bool parse_c_mutegroups_legacy ( performer & p, unsigned groupcount, unsigned groupsize ); bool parse_c_musickey (); bool parse_c_musicscale (); bool parse_c_musicchord (); bool parse_c_backsequence (); bool parse_c_perf_bp_mes (performer & p); bool parse_c_perf_bw (performer & p); bool parse_c_tempo_track (); bool write_c_mutegroups (const performer & p); bool checklen (midilong len, midibyte type); void add_trigger (sequence & seq, midishort ppqn, bool tposable); void add_old_trigger (sequence & seq); bool read_seek (size_t pos); midilong read_long (); midilong read_split_long (unsigned & highbytes, unsigned & lowbytes); midishort read_short (); midibyte read_byte (); midilong read_varinum (); bool read_byte_array (midibyte * b, size_t len); bool read_byte_array (midibytes & b, size_t len); bool read_string (std::string & b, size_t len); bool read_meta_data (sequence & s, event & e, midibyte metatype, size_t len); bool read_sysex_data ( sequence & s, event & e, size_t len, bool continuation = false ); void read_gap (size_t sz); midibyte peek (size_t ahead = 0) const { return m_data[m_pos + ahead]; } void skip (size_t sz) /* compare to read_gap() */ { m_pos += sz; /* sz can be 0 or positive */ } void back_up (size_t sz) { m_pos -= sz; /* sz can be 0 or positive */ } void write_long (midilong value); void write_split_long ( unsigned highbytes, unsigned lowbytes, bool oldstyle = false ); void write_triple (midilong value); void write_short (midishort value); /** * Writes 1 byte. The byte is written to the m_char_list member, using a * call to push_back(). * * \param c * The MIDI byte to be "written". */ void write_byte (midibyte c) { m_char_list.push_back(c); } void write_varinum (midilong); void write_track (const midi_vector & lst); void write_track_name (const std::string & trackname); void write_track_end (); std::string read_track_name (); long track_name_size (const std::string & trackname) const; void write_seq_number (midishort seqnum); int read_seq_number (); bool write_header (int numtracks, int smfformat = 1); #if defined SEQ66_USE_WRITE_START_TEMPO void write_start_tempo (midibpm start_tempo); #endif #if defined SEQ66_USE_WRITE_TIME_SIG void write_time_sig (int beatsperbar, int beatwidth); #endif void write_seqspec_header (midilong tag, long len); bool write_seqspec_track (performer & p); int varinum_size (long len) const; int prop_item_size (long len) const; bool track_error (const std::string & context, int track); bool set_error (const std::string & msg); bool append_error (const std::string & msg); bool set_error_dump (const std::string & msg); bool set_error_dump (const std::string & msg, unsigned long p); /** * Returns the size of a sequence-number event, which is always 5 * bytes, plus one byte for the delta time that precedes it. */ long seq_number_size () const { return 6; } /** * Returns the size of a track-end event, which is always 4 bytes: * 00 FF 2f 00 */ long track_end_size () const { return 4; /* ca 2024-05-20: it was 3; */ } /** * Check for special SysEx ID byte. * * \param ch * Provides the byte to be checked against 0x7D through 0x7F. * * \return * Returns true if the byte is SysEx special ID. * * THIS FUNCTION IS WRONG, BOGUS!!! * * bool is_sysex_special_id (midibyte ch) * { * return ch >= 0x7D && ch <= 0x7F; * } * */ }; // class midifile /* * Free functions related to midifile. */ extern bool read_midi_file ( performer & p, const std::string & fn, int ppqn, std::string & errmsg, bool addtorecent = true ); extern bool write_midi_file ( performer & p, const std::string & fn, std::string & errmsg ); } // namespace seq66 #endif // SEQ66_MIDIFILE_HPP /* * midifile.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/patches.hpp ================================================ #if ! defined SEQ66_PATCHES_HPP #define SEQ66_PATCHES_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file patches.hpp * * This module declares the array of MIDI program names. * * \library seq66 application * \author Chris Ahlstrom * \date 2025-02-17 * \updates 2026-02-16 * \license GNU GPLv2 or above * */ #include /* std::map<> template class */ #include /* std::string<> template class */ namespace seq66 { /** * Provides a small wrapper class for an alternate mapping of patch numbers * (program numbers) to patch names. */ class patches { friend std::string program_name (int patchnumber); public: using container = std::map; private: /** * A container for the up to 128 pairs of patch numbers and names. * Initially of size zero. */ container m_patch_map; /** * Indicates if the patch map is to be used in place of the built-in * GM patch list. */ bool m_active; /** * Holds the [comments] for the patch file. */ std::string m_comments; public: patches () = default; /* an empty, inactive map */ patches (const patches &) = delete; const patches & operator = (const patches &) = delete; ~patches () = default; const container & patch_map () const { return m_patch_map; } void clear () { m_patch_map.clear(); activate(false); } bool add (int patchnumber, const std::string & patchname); std::string name (int patchnumber) const; const std::string & comments () const { return m_comments; } void comments (const std::string & c) { m_comments = c; } bool active () const { return m_active; } void activate (bool flag = true) { m_active = flag; } private: std::string name_ex (int patchnumber) const; }; // class patches /* * Acessor functions */ extern bool add_patch (int patchnumber, const std::string & patchname); extern void set_patches_comment (const std::string & c); extern const std::string & get_patches_comment (); extern std::string program_name (int patchnumber); extern std::string program_list (); extern std::string gm_program_name (int patchnumber); } // namespace seq66 #endif // SEQ66_PATCHES_HPP /* * patches.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/midi/wrkfile.hpp ================================================ #if ! defined SEQ66_WRKFILE_HPP #define SEQ66_WRKFILE_HPP /* * WRK File component * Copyright (C) 2010-2018, Pedro Lopez-Cabanillas * * This library 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 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 General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ /** * \file wrkfile.hpp * * This module declares/defines the class for reading WRK files. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-06-04 * \updates 2025-07-09 * \license GNU GPLv2 or above * * For a quick guide to the WRK format, see, for example: * * WRK Cakewalk WRK File Parser (Input). */ #include /* std::list */ #include "midi/midifile.hpp" /* seq66::midifile base class */ namespace seq66 { class performer; class sequence; /** * Cakewalk WRK file format (input only) * * This class is used to parse Cakewalk WRK Files since 0.3.0. */ class wrkfile : public midifile { private: struct RecTempo { long time; double tempo; double seconds; }; private: /** * A nested class for holding all the data elements. */ class wrkfile_private { friend class wrkfile; public: wrkfile_private (); private: midilong m_Now; ///< Now marker time. midilong m_From; ///< From marker time. midilong m_Thru; ///< Thru marker time. midibyte m_KeySig; ///< Key signature (0=C, 1=C#, ... 11=B). midibyte m_Clock; ///< Clock Src (0=Int, 1=MIDI, 2=FSK, 3=SMPTE). midibyte m_AutoSave; ///< Auto save (0=disabled, 1..256=minutes). midibyte m_PlayDelay; ///< Play Delay. bool m_ZeroCtrls; ///< Zero continuous controllers? bool m_SendSPP; ///< Send Song Position Pointer? bool m_SendCont; ///< Send MIDI Continue? bool m_PatchSearch; ///< Patch/controller search-back? bool m_AutoStop; ///< Auto-stop? midilong m_StopTime; ///< Auto-stop time. bool m_AutoRewind; ///< Auto-rewind? midilong m_RewindTime; ///< Auto-rewind time. bool m_MetroPlay; ///< Metronome on during playback? bool m_MetroRecord; ///< Metronome on during recording? bool m_MetroAccent; ///< Metronome accents primary beats? midibyte m_CountIn; ///< Measures of count-in (0=no count-in). bool m_ThruOn; ///< MIDI Thru enabled? Only if no THRU rec. bool m_AutoRestart; ///< Auto-restart? midibyte m_CurTempoOfs; ///< Which of 3 tempo offsets is used: 0..2. midibyte m_TempoOfs1; ///< Fixed-point ratio value of offset 1. midibyte m_TempoOfs2; ///< Fixed-point ratio value of offset 2. midibyte m_TempoOfs3; ///< Fixed-point ratio value of offset 3. bool m_PunchEnabled; ///< Auto-Punch enabled? midilong m_PunchInTime; ///< Punch-in time. midilong m_PunchOutTime; ///< Punch-out time. midilong m_EndAllTime; ///< Time of latest event (incl. all tracks). int m_division; ///< TODO. midibytes m_lastChunkData; ///< Holds the latest raw data chunk. std::list m_tempos; ///< Tempo data. }; private: wrkfile_private m_wrk_data; /** * Holds a pointer to the (single) performer object in the Seq66 * session. We save it in order to avoid having to pass it around to the * numerous functions defined in the wrkfile class. See the perfp() * function. */ performer * m_performer; /** Holds the screen-set number in force for reading this WRK file. While * it is normally 0, it can be non-zero for WRK-file import. */ int m_screen_set; /** * If true, we are importing a file, most likely at a screen-set greater * than 0 (the first and main screen-set. */ bool m_importing; /** * The number of the current sequencer, re 0. It is -1 if a sequence is * not yet in progress. */ int m_seq_number; /** * The current track number as obtained from the WRK file. It is -1 if a * track is not yet in progress. */ int m_track_number; /** * Saves the track-name for the NoteArray() function. */ std::string m_track_name; /** * Saves the track channel for the EndChunk() function. */ int m_track_channel; /** * The number of tracks/sequences created so far. */ int m_track_count; /** * Holds the maximum time encountered for the current track. */ midipulse m_track_time; /** * Holds the sequence currently being filled. As in midifile, the * sequence remains in memory for the duration of the performance. */ sequence * m_current_seq; public: wrkfile ( const std::string & name, int ppqn, bool playlistmode = false ); virtual ~wrkfile () override; virtual bool parse ( performer & p, int screenset = 0, bool importing = false ) override; double get_real_time (midipulse ticks) const; private: void Set_timestamp (event & e, midipulse rawtime); /** * Returns an integer version of a midibyte, returning -1 if it was 255. */ int ibyte (midibyte b) const { return b == 255 ? (-1) : int(b) ; } performer * perfp () { return m_performer; } virtual sequence * create_sequence (performer & p) override; virtual bool finalize_sequence ( performer & p, sequence & seq, int seqnum, int screenset ) override; void next_track ( int trackno, int channel, const std::string & trackname, bool end_chunk = false ); void finalize_track (); void not_supported (const std::string & tag); midishort to_16_bit (midibyte c1, midibyte c2); midilong to_32_bit (midibyte c1, midibyte c2, midibyte c3, midibyte c4); midilong read_16_bit (); midilong read_24_bit (); midilong read_32_bit (); std::string read_string (int len); std::string read_var_string (); void read_raw_data (int size); int read_chunk (); void NoteArray (int track, int events); void TrackChunk (); void VarsChunk (); void TimebaseChunk (); void StreamChunk (); void MeterChunk (); void TempoChunk (int factor = 1); void SysexChunk (); void Sysex2Chunk (); void NewSysexChunk (); void ThruChunk (); void TrackOffset (); void TrackReps (); void TrackPatch (); void TrackBank (); void TimeFormat (); void Comments (); void VariableRecord (int max); void NewTrack (); void SoftVer (); void TrackName (); void StringTable (); void LyricsStream (); void TrackVol (); void NewTrackOffset (); void MeterKeyChunk (); void SegmentChunk (); void NewStream (); void UnknownChunk (int id); void TrackNumPlusChunk (); void EndChunk (); }; // class wrkfile } // namespace seq66 #endif // SEQ66_WRKFILE_HPP /* * wrkfile.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/os/daemonize.hpp ================================================ #if ! defined SEQ66_DAEMONIZE_HPP #define SEQ66_DAEMONIZE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file daemonize.hpp * \author Chris Ahlstrom * \date 2005-07-03 to 2007-08-21 (from xpc-suite project) * \updates 2023-10-31 * \license GNU GPLv2 or above * * Daemonization of POSIX C Wrapper (PSXC) library * Copyright (C) 2005-2025 by Chris Ahlstrom * * This module provides a function to make it easy to run an application * as a daemon. */ #include #include "seq66_platform_macros.h" /* for detecting 32-bit builds */ /* * The "flags" parameters described in Michael Kerrisk's book, "The Linux * Programming Interface", 2010. We also add a flag to avoid a second fork, * plus some other flags. */ enum d_flags_t { d_flag_none = 0x000, /**< No flags provided. */ d_flag_no_chdir = 0x001, /**< Don't chdir() to file root '/'. */ d_flag_no_close_files = 0x002, /**< Don't close all open files. */ d_flag_no_reopen_stdio = 0x004, /**< No stdin etc. sent to /dev/null. */ d_flag_no_umask = 0x008, /**< Don't call umask(0). */ d_flags_seq66cli = 0x00D, /**< No chdir, stdio, umask */ d_flag_no_fork_twice = 0x010, /**< Don't call fork() a second time. */ d_flag_no_set_currdir = 0x020, /**< Don't change current directory. */ d_flag_no_syslog = 0x040, /**< Do not open a system log file. */ d_flag_no_reserved = 0x080, /**< Reserved for expansion. */ d_flag_fake_fork_flags = 0x057, /**< For speed in debugging. */ d_flag_no_to_all = 0x0FF, /**< All of the above! */ d_flag_fake_fork = 0x100 /**< For debugging only. */ }; using daemonize_flags = enum d_flags_t; /** * Status of the daemonize() call. The following actions should be taken * based on the result. * * - failure. The parent process should exit with a return value of * EXIT_FAILURE. * - child. We're in the child process, so that normal operation of * the child application, including reading all the configuration * files, should proceed. * - parent. We're in the parent process, and the fork succeeded, * so that the parent should exit with a return value of * EXIT_SUCCESS. */ enum class daemonization { failure = (-1), /**< The call to fork() failed. */ child = 0, /**< Result of fork() in child process. */ parent = 1 /**< Result of fork() is child's PID. */ }; const int c_daemonize_max_fd = 8192; /**< Max. file-descriptors to close. */ namespace seq66 { /* * Free functions. * * These functions do a lot of the work of dealing with UNIX daemons. */ extern daemonization daemonize ( mode_t & previousmask, const std::string & appname, int flags, const std::string & cwd = ".", int mask = 0 ); extern void undaemonize (mode_t previous_umask); /* * Linux and Windows support. The pid_exists() and get_pid_by_name() * functions currently do nothing. Still thinking about them */ extern bool close_stdio (); extern bool reroute_stdio (const std::string & logfile = ""); extern bool reroute_stdio_to_dev_null (); extern bool pid_exists (const std::string & exename); extern pid_t get_pid_by_name (const std::string & exename); extern std::string get_pid (); extern std::string get_process_name (); extern std::string get_process_name (pid_t pid); extern std::string get_parent_process_name (); /* * Basic session handling from use falkTX, circa 2020-02-02. The following * function is internal. * * extern void session_handler (int sig); * * The following functions return status booleans that the caller can use to * determine what to do. */ extern bool session_setup (bool earlyexit = false); extern bool session_close (); extern bool session_save (); extern bool session_restart (); /* * Useful for the performer to flag an application exit. Be freakin' careful * with this one! :-D */ extern void signal_for_save (); extern void signal_for_exit (); extern void signal_for_restart (); extern void signal_end_restart (); } // namespace seq66 #endif // SEQ66_DAEMONIZE_HPP /* * vim: ts=4 sw=4 et ft=cpp */ ================================================ FILE: libseq66/include/os/shellexecute.hpp ================================================ #if ! defined SEQ66_SHELLEXECUTE_HPP #define SEQ66_SHELLEXECUTE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file shellexecute.hpp * \author Chris Ahlstrom * \date 2022-05-19 * \updates 2025-01-22 * \license GNU GPLv2 or above * * This module provides functions for executing commands from within * the application. */ namespace seq66 { /* * Free functions for Linux and Windows support. */ extern bool command_line (const std::string & cmdline); extern bool open_document (const std::string & documentpath); extern bool open_pdf (const std::string & pdfspec); extern bool open_url (const std::string & pdfspec); extern bool open_local_url (const std::string & pdfspec); extern bool copy_directory_recursive ( const std::string & sourcedir, const std::string & destdir ); } // namespace seq66 #endif // SEQ66_SHELLEXECUTE_HPP /* * vim: ts=4 sw=4 et ft=cpp */ ================================================ FILE: libseq66/include/os/timing.hpp ================================================ #if ! defined SEQ66_TIMING_HPP #define SEQ66_TIMING_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file timing.hpp * \author Chris Ahlstrom * \date 2005-07-03 to 2007-08-21 (from xpc-suite project) * \updates 2021-11-19 * \license GNU GPLv2 or above * * Daemonization of POSIX C Wrapper (PSXC) library * Copyright (C) 2005-2025 by Chris Ahlstrom * * This module provides functions for timing and increasing thread * priority. */ #include /* std::thread */ #include "seq66_platform_macros.h" /* for detecting 32-bit builds */ namespace seq66 { /* * Free functions for Linux and Windows support. */ extern int std_sleep_us (); extern bool microsleep (int us); extern bool millisleep (int ms); extern void thread_yield (); extern long microtime (); extern long millitime (); extern bool set_thread_priority (std::thread & t, int p = 1); extern bool set_timer_services (bool on); } // namespace seq66 #endif // SEQ66_TIMING_HPP /* * vim: ts=4 sw=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/clockslist.hpp ================================================ #if ! defined SEQ66_CLOCKSLIST_HPP #define SEQ66_CLOCKSLIST_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file clockslist.hpp * * This module declares/defines the elements that are common to the Linux * and Windows implmentations of midibus. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-12 * \updates 2023-06-24 * \license GNU GPLv2 or above * * Defines some midibus constants and the seq66::clock enumeration. In * Sequencer64, this module was called "midibus_common". Also, we use an * enum class to replace the clock_e enumeration, dropping the "e_clock" * from the enumeration names, and replacing them with "e_clock::". */ #include "play/portslist.hpp" /* seq66::listbase base class */ namespace seq66 { /** * A wrapper for a vector of clocks, as used in mastermidibus and the * performer object. */ class clockslist final : public portslist { friend bool build_output_port_map (const clockslist & lb); public: clockslist (bool isportmap = false) : portslist (isportmap) { // Nothing to do } virtual ~clockslist () = default; virtual std::string io_list_lines () const override; virtual bool add_list_line (const std::string & line) override; virtual bool add_map_line (const std::string & line) override; bool add ( int buss, bool available, e_clock clocktype, const std::string & name, const std::string & nickname = "", const std::string & alias = "" ); bool set (bussbyte bus, e_clock clocktype); e_clock get (bussbyte bus) const; }; // class clockslist /* * Free functions */ extern clockslist & output_port_map (); extern bool build_output_port_map (const clockslist & lb); extern void clear_output_port_map (); extern void activate_output_port_map (bool flag); extern bussbyte true_output_bus (const clockslist & cl, bussbyte nominalbuss); #if defined USE_IOPUT_PORT_NAME_FUNCTION extern std::string output_port_name (bussbyte b, bool addnumber = false); #endif extern bussbyte output_port_number (bussbyte b); extern std::string output_port_map_list (); } // namespace seq66 #endif // SEQ66_CLOCKSLIST_HPP /* * clockslist.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/inputslist.hpp ================================================ #if ! defined SEQ66_INPUTSLIST_HPP #define SEQ66_INPUTSLIST_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file inputslist.hpp * * This module declares/defines the elements that are common to the Linux * and Windows implmentations of midibus. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-12 * \updates 2023-06-24 * \license GNU GPLv2 or above * * Defines the list of MIDI inputs, pulled out of the old perform module. */ #include "play/portslist.hpp" /* seq66::portslist base class */ namespace seq66 { /** * A wrapper for a vector of clocks, as used in mastermidibus and the * performer object. */ class inputslist final : public portslist { friend bool build_input_port_map (const inputslist & lb); public: inputslist (bool isportmap = false) : portslist (isportmap) { // Nothing to do } virtual ~inputslist () = default; virtual std::string io_list_lines () const override; virtual bool add_list_line (const std::string & line) override; virtual bool add_map_line (const std::string & line) override; bool add ( int buss, bool available, bool enabled, const std::string & name, const std::string & nickname = "", const std::string & alias = "" ); bool set (bussbyte bus, bool inputing); bool get (bussbyte bus) const; }; // class inputslist /* * Free functions */ extern inputslist & input_port_map (); extern bool build_input_port_map (const inputslist & lb); extern void clear_input_port_map (); extern void activate_input_port_map (bool flag); extern bussbyte true_input_bus (const inputslist & cl, bussbyte nominalbuss); #if defined USE_IOPUT_PORT_NAME_FUNCTION extern std::string input_port_name (bussbyte b, bool addnumber = false); #endif extern bussbyte input_port_number (bussbyte b); extern std::string input_port_map_list (); } // namespace seq66 #endif // SEQ66_INPUTSLIST_HPP /* * inputslist.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/metro.hpp ================================================ #if ! defined SEQ66_METRO_HPP #define SEQ66_METRO_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file metro.hpp * * Provides a configurable pattern that can be used as a metronome, * plus add addition pattern class that can be used for background * recording. * * \library seq66 application * \author Chris Ahlstrom * \date 2022-08-05 * \updates 2022-08-18 * \license GNU GPLv2 or above * * The metro is a sequence with a special configuration. It can be added * to the performer's playset to be played along with the rest of the * patterns. It is not visible and it is not editable once created. * There is also a lot of stuff in seq66::sequence not needed here. * * The recorder class extends the metro class for recording in the background * automatically. */ #include "play/sequence.hpp" /* seq66::sequence */ namespace seq66 { /** * Configuration class for the metro class. It covers the members of the * metro class pluse the bus, channel, beats, and beat width. */ class metrosettings { private: /** * Provides the desired MIDI buss and channel to play the metronome. */ bussbyte m_buss; midibyte m_channel; /** * Provides the desired MIDI buss to record from when doing * background recording. No channel is forced on the pattern; * the user can apply the desired channel later. */ bussbyte m_recording_buss; /** * Provides the desired MIDI buss and channel to send the background * recording events out to be heard. */ bussbyte m_thru_buss; midibyte m_thru_channel; /** * Provides the desired time-signature of the metronome. */ int m_beats_per_bar; int m_beat_width; /** * Provides the patch/program number to use. This selects the * sound the metronome should have. It is played at the start of each * loop; added first in the event list. */ midibyte m_main_patch; /** * Optionally, the other beats can be played with a different patch. */ midibyte m_sub_patch; /** * The highlight (measure) note to play, its velocity, and its length. * The length ends up being calculated using the beat width, PPQN, and * the note-fraction members below. */ midibyte m_main_note; midibyte m_main_note_velocity; midipulse m_main_note_length; /** * The sub-measure (beat) notes to play, their velocity, and their * lengths. */ midibyte m_sub_note; midibyte m_sub_note_velocity; midipulse m_sub_note_length; /** * Provides the fraction of beat width used for the length of the main * and sub notes. */ float m_main_note_fraction; float m_sub_note_fraction; /** * Support for count-in. This involves a boolean to indicate it is active, * the number of measures to count in, and whether recording (to a hidden * record pattern is activated. * * We may need to add a recording buss number to the configuration. */ bool m_count_in_active; int m_count_in_measures; /** * Additional support for background recording. */ bool m_count_in_recording; int m_recording_measures; public: metrosettings (); midipulse calculate_length (int increment, float fraction); bool initialize (int increment); void set_defaults (); bool sanity_check () const { return m_main_note > 0 && m_sub_note > 0; } bussbyte buss () const { return m_buss; } midibyte channel () const { return m_channel; } bussbyte recording_buss () const { return m_recording_buss; } bussbyte thru_buss () const { return m_thru_buss; } midibyte thru_channel () const { return m_thru_channel; } int beats_per_bar () const { return m_beats_per_bar; } int beat_width () const { return m_beat_width; } midibyte main_patch () const { return m_main_patch; } midibyte sub_patch () const { return m_sub_patch; } midibyte main_note () const { return m_main_note; } midibyte main_note_velocity () const { return m_main_note_velocity; } float main_note_fraction () const { return m_main_note_fraction; } midipulse main_note_length () const { return m_main_note_length; } midibyte sub_note () const { return m_sub_note; } midibyte sub_note_velocity () const { return m_sub_note_velocity; } float sub_note_fraction () const { return m_sub_note_fraction; } midipulse sub_note_length () const { return m_sub_note_length; } bool count_in_active () const { return m_count_in_active; } int count_in_measures () const { return m_count_in_measures; } bool count_in_recording () const { return m_count_in_recording; } int recording_measures () const { return m_recording_measures; } bool expand_recording () const { return m_recording_measures == 0; } public: void buss (int b) { if (! is_null_buss(b)) m_buss = bussbyte(b); } void channel (int ch) { if (is_good_channel(ch)) m_channel = midibyte(ch); } void recording_buss (int b) { if (! is_null_buss(b)) m_recording_buss = bussbyte(b); } void thru_buss (int b) { if (! is_null_buss(b)) m_thru_buss = bussbyte(b); } void thru_channel (int ch) { if (is_good_channel(ch)) m_thru_channel = midibyte(ch); } void beats_per_bar (int bpb) { m_beats_per_bar = bpb; } /* * Since this is not saved, we don't care if it is not a power of * two. */ void beat_width (int bw) { m_beat_width = bw; } void main_patch (int patch) { if (is_good_data_byte(patch)) m_main_patch = midibyte(patch); } void sub_patch (int patch) { if (is_good_data_byte(patch)) m_sub_patch = midibyte(patch); } void main_note (int note) { if (is_good_data_byte(note)) m_main_note = midibyte(note); } void main_note_velocity (int vel) { if (is_good_data_byte(vel)) m_main_note_velocity = midibyte(vel); } void main_note_fraction (float fraction) { if (fraction == 0.0 || (fraction >= 0.125 && fraction <= 2.0)) m_main_note_fraction = fraction; } void sub_note (int note) { if (is_good_data_byte(note)) m_sub_note = midibyte(note); } void sub_note_velocity (int vel) { if (is_good_data_byte(vel)) m_sub_note_velocity = midibyte(vel); } void sub_note_fraction (float fraction) { if (fraction == 0.0 || (fraction >= 0.125 && fraction <= 2.0)) m_sub_note_fraction = fraction; } void count_in_active (bool flag) { m_count_in_active = flag; } void count_in_measures (int count) { m_count_in_measures = count; } void count_in_recording (bool flag) { m_count_in_recording = flag; } void recording_measures (int m) { m_recording_measures = m; } }; // class metrosettings /** * The metro class is just a sequence used for implementing a metronome * functionality. */ class metro : public sequence { friend class performer; private: metrosettings m_metro_settings; private: metro & operator = (const metro & rhs); public: metro (); metro (const metrosettings & ms); virtual ~metro (); virtual bool initialize (performer * p); virtual bool uninitialize () { return true; } metrosettings & settings () { return m_metro_settings; } protected: bool init_setup (performer * p, int measures); }; // class metro /** * An extension of metro for recording in the backbround. */ class recorder final : public metro { friend class performer; private: recorder & operator = (const recorder & rhs); public: recorder (); recorder (const metrosettings & ms); virtual ~recorder (); virtual bool initialize (performer * p) override; virtual bool uninitialize () override; }; // class recorder } // namespace seq66 #endif // SEQ66_METRO_HPP /* * recorder.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/mutegroup.hpp ================================================ #if ! defined SEQ66_MUTEGROUP_HPP #define SEQ66_MUTEGROUP_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file mutegroup.hpp * * This module declares a linear vector class solely to hold the mute status * of a number of sequences in a set, while also able to access * patterns/loops by row and column. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-12-01 * \updates 2023-12-06 * \license GNU GPLv2 or above * */ #include /* std::function, function objects */ #include #include "midi/midibytes.hpp" /* seq66::midibooleans, etc. */ #include "play/screenset.hpp" /* seq66::screenset constants */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /** * Provides a class that represents an array the same size as a screenset, * but holding armed statuses that can be saved an applied later. Note that, * unlike the array of mute-groups represented by the mutegroups class, the * size and layout of each mute-group (mutegroup class), like screen-sets, is * potentially modifiable by the configuration. */ class mutegroup { public: /** * A revealing alias for mutegroup numbers. */ using number = int; /** * Provides an alias for functions that can be called on all groups. * We might end up not using this, letting the settmapper do the work. */ using grouphandler = std::function; static const int c_default_rows = screenset::c_default_rows; static const int c_default_columns = screenset::c_default_columns; private: /** * Provides a mnemonic name for the group. By default, it is of the * format "Group 1", but can be modified directly in the mute-groups * table in the Mutes tab. */ std::string m_name; /** * Indicates the current state of the mute-group, either on or off. * Useful in toggling. */ mutable bool m_group_state; /** * The number of loops/patterns in the mute-group. Saves a calculation * of row x column. It is important to note the the size of the group is * constant throughout its lifetime (and the lifetime of the * application). * * For issue #124, we have removed the const from some member declarations * so that the default constructor etc. are not deleted. Thanks to clang * for uncovering that. */ /* const */ int m_group_size; /** * Holds a set of boolean values in a 1-D vector, but can be virtually * arranged by row and column. Note that we use midibool rather than * bool, to avoid bitset issues. */ midibooleans m_mutegroup_vector; /** * Indicates the number of virtual rows in a screen-set (bank), which is * also the same number of virtual rows as a mute-group. This value will * generally be the same as the size used in the rest of the application. * The default value is the historical value of 4 rows per set or * mute-group. */ /* const */ int m_rows; /** * Indicates the number of virtual columns in a screen-set (bank), which * is also the same number of virtual columns as a mute-group. This * value will generally be the same as the size used in the rest of the * application. The default value is the historical value of 8 columns * per set or mute-group. */ /* const */ int m_columns; /** * Experimental option to swap rows and columns. See the function * swap_coordinates(). This swap doesn't apply to the number of rows and * columns, but to whether incrementing the sequence number moves to the * next or othe next column. */ bool m_swap_coordinates; /** * Indicates the group (akin to the set or bank number) represented by * this mutegroup object. */ number m_group; /** * Indicates the screen-set offset (the number of the first loop/pattern * in the screen-set). This value is m_group_count * m_group. This * saves a calculation. */ /* const */ int m_group_offset; public: /* * Creates the vector of values, setting them all to 0 (false). */ mutegroup ( number group = 0, int rows = c_default_rows, int columns = c_default_columns ); /* * The move and copy constructors, the move and copy assignment operators, * and the destructors are all compiler generated. */ mutegroup (const mutegroup &) = default; mutegroup & operator = (const mutegroup &) = default; mutegroup (mutegroup &&) = default; mutegroup & operator = (mutegroup &&) = default; ~mutegroup () = default; /** * Checks if a the sequence number is an assigned one, i.e. not equal to * -1. */ static bool none (number group) { return group == (-1); } /** * Indicates that a mute-group number has not been assigned. */ static number unassigned () { return (-1); } void invalidate () { m_group = unassigned(); } bool valid () const { return m_group >= 0; /* should check upper range at some point */ } bool group_state () const { return m_group_state; } void group_state (bool f) { m_group_state = f; } int count () const { return int(m_mutegroup_vector.size()); } int armed_count () const; bool armed (int index) const; void armed (int index, bool flag); bool muted (int index) const { return ! armed(index); } bool set (const midibooleans & bits); const midibooleans & zeroes () const { static midibooleans s_bits(m_group_size, midibool(false)); return s_bits; } const midibooleans & get () const { return m_mutegroup_vector; } const std::string & name () const { return m_name; } void name (const std::string & n) { m_name = n; } number group () const { return m_group; } int rows () const { return m_rows; } int columns () const { return m_columns; } bool swap_coordinates () const { return m_swap_coordinates; } bool any () const; void clear (); void show () const; private: bool mute_to_grid (int group, int & row, int & column) const; /** * Calculates the group index (i.e. a pattern number) given by the rows, * columns, and the group-offset value. * * \return * Returns 0 if the row or column were illegal. But 0 is also a * legal value. */ int grid_to_mute (int row, int column); }; // class mutegroup /* * Global (free) midibyte functions. */ extern std::string write_stanza_bits ( const midibooleans & bitbucket, int grouping = mutegroup::c_default_columns, bool newstyle = true ); extern bool parse_stanza_bits ( midibooleans & target, const std::string & mutestanza ); } // namespace seq66 #endif // SEQ66_MUTEGROUP_HPP /* * mutegroup.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/mutegroups.hpp ================================================ #if ! defined SEQ66_MUTEGROUPS_HPP #define SEQ66_MUTEGROUPS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file mutegroups.hpp * * This module declares a container for a number of optional mutegroup * objects. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-12-01 * \updates 2023-10-06 * \license GNU GPLv2 or above * * This module is meant to support the main mute groups and the mute groups * from the 'mutes' file. For practical reasons, we hold the number of * mute-groups to a constant (32) with a constant layout of 4 rows by 8 * columns. This is a good upper limit for the number of mute-groups a user * might want during a performance, and 4x8 fits well with the main keys * (also used for pattern muting) on the center-left of the keyboard: * \verbatim ! @ # $ % ^ & * Q W E R T Y U I A S D F G H J K Z X C V B N M < \endverbatim */ #include /* std::map<> for mutegroup storage */ #include "cfg/basesettings.hpp" /* seq66::basesettings class */ #include "play/mutegroup.hpp" /* seq66::mutegroup stanza class */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /** * Provides a flexible container for mutegroup (mute-group) objects. */ class mutegroups final : public basesettings { public: /** * Provides settings for muting. */ enum class action { off, on, toggle, toggle_active, max }; /** * Provides mutually-exclusive codes for handling the reading/writing of * mute-groups from the 'rc' file versus the MIDI file. There's no GUI * way to set this item yet. * * saving::mutes: In this option, the mute groups are * writtin only to the 'mutes' (formerly 'rc') file. * * saving::midi: In this option, the mute groups are only written to * the 'rc' file if the MIDI file did not contain non-zero mute groups. * This option prevents the contamination of the 'mutes' mute-groups by * the MIDI file's mute-groups. We're going to make this the default * option. NEEDS FIXING! * * saving::both: This is the legacy (seq66) option, which * reads the mute-groups from the MIDI file, and saves them back to the * 'rc' file and to the MIDI file. However, for Seq66 MIDI files such as * b4uacuse-stress.midi, seq66 never reads the mute-groups in that MIDI * file! In any case, this can be considered a corruption of the 'rc' * file. */ enum class saving { none, /**< Added for version 0.99.10 to fix a bug. */ mutes, /**< Save mute groups to the 'mutes' file. */ midi, /**< Write mute groups only to the MIDI file. */ both, /**< Write the mute groups to both files. */ max /**< Keep this last... it is only a size value. */ }; /** * More codes, better than booleans. Legacy values of 'true' convert to * 'mutes'; 'false' converts to 'midi'. */ enum class loading { none, /**< Do not load any mute groups. */ mutes, /**< Load mute groups only from 'mutes' file. */ midi, /**< Load from MIDI, ignoring the 'mutes' file. */ both, /**< Read from 'mutes'; if none, then MIDI. */ max /**< Keep this last... it is only a size value. */ }; public: /** * Provides a container type for mutegroup objects, keyed by the group * number. Remember that a mutegroup object has a 2-D vector of midibools, * with a given number of rows and columns, along with the group number * and the number of patterns in the group. */ using container = std::map; private: /** * The virtual number of rows in a grid of mute-groups. This number is * constant in order to indicate that there are always 4 rows in the * whole collection of mute-groups. Why? Because we can, in all * practicality, only support 4 x 8 mute-groups with the keystrokes on a * keyboard without interfering with other automation keys. */ static const int c_rows = 4; /** * The virtual number of columns in a grid of mute-groups. Similarly, * this item reflects that we will always have 4 x 8 mute-groups. */ static const int c_columns = 8; /** * This value indicates that there is no mute-group selected. */ static const int c_null_mute_group = (-1); /** * We force a maximum number of mute-groups. We really only have enough * keys available for 32 mute-groups. */ static const int c_mute_groups_max = 32; /** * Experiment feature to swap coordinates. */ static bool s_swap_coordinates; /** * Holds a set of mutegroup objects keyed by the configured mute * group number. */ container m_container; /** * A name to use for showing the contents of the container. */ std::string m_container_name; /** * Indicates the number of rows in a group, for reading purposes. This * value defaults to 4. A "row" in the mute-group file is demarcated by * square brackets. The concept of rows and columns is simply a device * to make the mute-group file easier to read for humans by breaking one * line of data in the file into smaller sections. * * Note that each line in the mute-group file is meant to represent one * and only one mutegroup object. */ int m_rows; /** * Indicates the number of columns in a group. This value defaults to 8. * A "column" in the mute-group file is one digit or bit inside the * square brackets. There are rows x column "bits" in a mute-group. */ int m_columns; /** * If true, writes the output to a mutes file in hex format. The default * is binary (0 or 1) format, but for larger mute-groups, hex form (0x00 to * 0xff) will save a lot of space. */ bool m_group_format_hex; /** * Indicates if the control values were loaded from an 'rc' configuration * file, as opposed to being empty. The default value is false. */ bool m_loaded_from_mutes; /** * Indicates that a mute-group-related key has just been pressed, or a * similar event (MIDI or the "L" button) has occurred. */ bool m_group_event; /** * Indicates that an error occurred in group processing. The caller will * check this flag, which clears it, and act on the status. */ mutable bool m_group_error; /** * If true, indicates that a mode group is selected, and playing statuses * will be "memorized". This value starts out true. It is altered by * the MIDI control group-mute handler or when the group-off or group-on * keys are struck. */ bool m_group_mode; /** * If true, indicates that a group learn is selected, which also * "memorizes" a mode group, and notifies subscribers of a group-learn * change. */ bool m_group_learn; /** * Selects a group to mute. A "group" is essentially a "set" that is * selected for the saving and restoring of the status of all patterns in * that set. The value of -1 (SEQ66_NO_MUTE_GROUP_SELECTED) to indicate * the value should not be used. The test for a valid value is simple, * just check for group >= 0 and a limit of c_mute_groups_max (32) at * mute-group setup time. */ mutegroup::number m_group_selected; /** * If true, indicates that non-zero mute-groups were present in this MIDI * file. We need to know if valid mute-groups are present when deciding * whether or not to write them to the 'rc' file. This can be set by * getting the result of the any() function. */ bool m_group_present; /** * Indicates if non-empty mute-groups get saved to the mutes file, MIDI * file, or both. */ saving m_group_save; /** * Indicates if non-empty mute-groups get loaded from the mutes file, * MIDI file, or attempted load from both. */ loading m_group_load; /** * If true (the default is false), then, when turning off mutes via * toggling, turn off only the patterns that are part of the mute group, * leaving alone any other patterns that the user may have turned on. */ bool m_toggle_active_only; /** * If true, and there are no non-zero mutes, then they are not written to * the MIDI file. The whole "c_mutegroups" SeqSpec section is not * written. */ bool m_strip_empty; /** * Indicates the old Seq24/32/64/66 mute-group format, where all values * were stored as longs. */ bool m_legacy_mutes; public: mutegroups ( int rows = c_rows, int columns = c_columns ); mutegroups ( const std::string & name, int rows = c_rows, int columns = c_columns ); /* * The move and copy constructors, the move and copy assignment operators, * and the destructors are all compiler generated. */ mutegroups (const mutegroups &) = default; mutegroups & operator = (const mutegroups &) = default; mutegroups (mutegroups &&) = default; mutegroups & operator = (mutegroups &&) = default; ~mutegroups () = default; static int Rows () { return c_rows; } static int Columns () { return c_columns; } static int Size () { return c_rows * c_columns; } static bool Swap () { return s_swap_coordinates; } static int null_mute_group () { return c_null_mute_group; } static mutegroup::number grid_to_group (int row, int column); static bool group_to_grid(mutegroup::number g, int & row, int & column); static saving string_to_group_save (const std::string & value); const std::string & name () const { return m_container_name; } void name (const std::string & nm) { if (! nm.empty()) m_container_name = nm; } int rows () const { return m_rows; } void rows (int r) { m_rows = r; } bool group_format_hex () const { return m_group_format_hex; } int columns () const { return m_columns; } void columns (int c) { m_columns = c; } int count () const { return int(m_container.size()); } bool empty () const { return m_container.empty(); } int group_count () const { return m_rows * m_columns; } bool apply (mutegroup::number group, midibooleans & bits); bool unapply (mutegroup::number group, midibooleans & bits); bool toggle (mutegroup::number group, midibooleans & bits); bool toggle_active (mutegroup::number group, midibooleans & armedbits); bool loaded_from_mutes () const { return m_loaded_from_mutes; } void group_format_hex (bool flag) { m_group_format_hex = flag; } void loaded_from_mutes (bool flag) { m_loaded_from_mutes = flag; } saving group_save () const { return m_group_save; } bool group_save (const std::string & v); bool group_save (saving mgh); bool group_save (bool midi, bool mutes); std::string group_save_label () const; bool group_save_to_mutes () const { return ( m_group_save == saving::mutes || m_group_save == saving::both ); } bool group_save_to_midi () const { return ( m_group_save == saving::midi || m_group_save == saving::both ); } bool saveable_to_midi () const { return group_save_to_midi() && any(); } loading group_load () const { return m_group_load; } bool group_load (const std::string & v); bool group_load (loading mgh); bool group_load (bool midi, bool mutes); std::string group_load_label () const; bool load_mute_groups (bool midi, bool mutes); bool group_load_from_mutes () const { return ( m_group_load == loading::mutes || m_group_load == loading::both ); } bool group_load_from_midi () const { return ( m_group_load == loading::midi || m_group_load == loading::both ); } /* * These functions are useful for setting and retrieving individual mute * values. Our convention is that load() is an interface to a configuration * file while set() is an interface for updating existing mute values. * add_defaults() is for use by mutegroupsfile. */ bool reset_defaults (); /* used in mutegroupsfile */ bool load (mutegroup::number gmute, const midibooleans & bits); bool set (mutegroup::number gmute, const midibooleans & bits); midibooleans get (mutegroup::number gmute) const; midibooleans get_active_groups () const; bool any () const; bool any (mutegroup::number gmute) const; const mutegroup & mute_group (mutegroup::number gmute) const; mutegroup & mute_group (mutegroup::number gmute); void show ( const std::string & tag, mutegroup::number gmute = c_null_mute_group ) const; int armed_count (mutegroup::number gmute) const { return mute_group(gmute).armed_count(); } int group_names_letter_count () const; const std::string & group_name (mutegroup::number gmute) const { return mute_group(gmute).name(); } void group_name (mutegroup::number gmute, const std::string & n) { mute_group(gmute).name(n); } container & list () { return m_container; } const container & list () const { return m_container; } bool group_event () const { return m_group_event; } bool group_error () const { bool result = m_group_error; m_group_error = false; return result; } bool group_mode () const { return m_group_mode; } bool is_group_learn () const { return m_group_learn; } mutegroup::number group_selected () const { return m_group_selected; } bool group_valid () const { return group_valid(group_selected()); } bool group_valid (int g) const { return g >= 0 && g < c_mute_groups_max; } bool group_present () const { return m_group_present; } void set_group_present () { m_group_present = any(); } /** * Provides common code to keep the group value valid even in variset mode. * * \param group * The group value to be checked and rectified as necessary. * * \return * Returns the group parameter, clamped between 0 and the number of * mutes (mutegroup object). */ mutegroup::number clamp_group (mutegroup::number group) const { if (group < 0) return 0; else if (group >= count()) return count() - 1; return group; } bool check_group (mutegroup::number group) const { return (group >= 0) && (group < count()); } bool toggle_active_only () const { return m_toggle_active_only; } bool legacy_mutes () const { return m_legacy_mutes; } bool strip_empty () const { return m_strip_empty; } public: // setters that need to be public bool update (mutegroup::number gmute, const midibooleans & bits); void group_learn (bool flag); void group_event (bool flag) { m_group_event = flag; } void group_error (bool flag) { m_group_error = flag; } void legacy_mutes (bool flag) { m_legacy_mutes = flag; } void toggle_active_only (bool flag) { m_toggle_active_only = flag; } void toggle_group_mode () { m_group_mode = ! m_group_mode; } void strip_empty (bool flag) { m_strip_empty = flag; } void group_selected (mutegroup::number mg) { if (group_valid(mg) || mg == c_null_mute_group) m_group_selected = mg; } void group_mode (bool flag) { m_group_mode = flag; } private: bool clear (); void create_empty_mutes (); bool add (mutegroup::number gmute, const mutegroup & m); }; // class mutegroups } // namespace seq66 #endif // SEQ66_MUTEGROUPS_HPP /* * mutegroups.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/notemapper.hpp ================================================ #ifndef SEQ66_NOTEMAPPER_HPP #define SEQ66_NOTEMAPPER_HPP /* * This file is part of seq66. * * 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., * 675 Mass Ave, Cambridge, MA 02139, USA. * * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ /** * \file notemapper.hpp * * This module provides functions for advanced MIDI/text conversions. * * \library libseq66 * \author Chris Ahlstrom * \date 2014-04-24 * \updates 2025-01-09 * \version $Revision$ * \license GNU GPL * * The mapping process works though static functions that reference a * global notemapper object. * * This object gets its setup from an INI file. This INI file has an * unnamed section with the following format: * \verbatim gm-channel = 10 device-channel = 16 \endverbatim * * The "drum" sections are named for the GM note that is to be * remapped. * \verbatim [ Drum 35 ] gm-name = Acoustic Bass Drum gm-note = 35 dev-note = 35 \endverbatim * */ #include #include #include "cfg/basesettings.hpp" /* seq66::basesettings class */ #include "midi/midibytes.hpp" /* seq66::c_notes_count */ namespace seq66 { /** * This class provides for some basic remappings to be done to MIDI * files, using the old and new facilities of libmidifilex. * * It works by holding all sorts of standard C++ map objects that are * used to translate from one numeric value to another. * * For use in the midicvtpp application, a single global instance of * this object is created, and is used in static C-style callback * functions that can be used in the C library libmidifilex. */ class notemapper final : public basesettings { friend void show_maps ( const std::string & tag, const notemapper & container, bool full_output ); public: /** * Provides a constant to indicate an inactive or invalid integer * value. */ static const int NOT_ACTIVE = -1; private: /** * This class is meant to extend the map of values with additional data * that can be written out to summarize some information about the MIDI * remapping that was done. Instead of just the integer value to use, * this class holds the names of the items on both ends of the mapping, * plus a usage count. We also added the "GM equivalent" name to this * class as well. */ class pair { private: /** * Indicates if this is a reversed pair. This boolean is needed to * determine whether the dev-note or the gm-note is the key value. * * For issue #124, clang deletes the assignment operator, so we * get rid of the consts. */ /* const */ bool m_is_reverse; /** * The incoming note number from a non-GM compliant device. This * value is used as a "key" value in the map or the index in the * array. */ /* const */ int m_dev_value; /** * The integer value to which the incoming (key) value is to be * mapped. This is the value of the drum note on a GM-compliant * device. */ /* const */ int m_gm_value; /** * The name of the key as represented by the non-GM device. */ /* const */ std::string m_dev_name; /** * The name of the GM drum note or patch that is replacing the * device's drum note of patch. Sometimes there is no exact * replacement, so it is good to know what GM sound is replacing the * device's sound. */ /* const */ std::string m_gm_name; /** * The number of times this particular mapping was performed in the * MIDI remapping operation. */ int m_remap_count; public: pair () = delete; pair ( int devvalue, int gmvalue, const std::string & devname, const std::string & gmname, bool reverse ); pair (const pair &) = default; pair & operator = (const pair &) = default; ~pair () = default; int dev_value () const { return m_dev_value; } int gm_value () const { return m_gm_value; } const std::string & dev_name () const { return m_dev_name; } const std::string & gm_name () const { return m_gm_name; } void increment_count () { ++m_remap_count; } int count () const { return m_remap_count; } std::string to_string () const; void show () const; }; // nested class pair public: /** * For note-mapping in the pattern-fix dialog, we need to be * able to override the map-direction specified in the notemap file. * The direction is used when adding note pairs to the mapping. */ enum class direction { file, /**< Use the notemap file setting. */ forward, /**< Use the most common use case. */ reverse /**< Force the usage of reverse. */ }; private: /** * Provides the type of the map between one set of values and * another set of values. */ using map = std::map; /** * Set in the constructor. */ direction m_direction; /** * Indicates if we are in drums mode. Only true if the user specified * a valid drums (note-mapper) file that was successfully loaded. */ bool m_mode; /** * Indicates what kind of mapping is allegedly provided by the file. * This can be one of the following values: * * - "drums". The file describes mapping one pitch/channel to * another pitch/channel, used mostly for coercing old drum * machines to something akin to a General MIDI kit. * - "patches". The file describes program (patch) mappings, used * to map old devices patch change values to General MIDI. * Not yet supported. * - "multi". The file describes both "drums" and "patches" * mappings. Not yet supported. * * The name of this attribute in the INI file is "map-type". Case * is significant. */ std::string m_map_type; /** * Provides the lowest and highest notes actually read into the map and * array. */ int m_note_minimum; int m_note_maximum; /** * Provides the channel to use for General MIDI drums. This value * is usually 9, meaning MIDI channel 10. However, be careful, as * externally, this value is always on a 1-16 scale, while * internally it is reduced by 1 (a 0-15 scale) to save endless * decrements. * * The name of this attribute in the INI file is "gm-channel". Case * is significant. */ int m_gm_channel; /** * Provides the channel that is used by the native device. Older * MIDI equipment sometimes used channel 16 for percussion. * * The name of this attribute in the INI file is "dev-channel". * Case is significant. */ int m_device_channel; /** * Indicates that the mapping should occur in the reverse direction. * That is, instead of mapping the notes from the device pitches and * channel to General MIDI, the notes and channel should be mapped * from General MIDI back to the device. This option is useful for * playing back General MIDI files on old equipment. * * Note that this option is an INI option ("reverse"), as well as a * command-line option. It is specified by alternate means, such as * a command-line parameter like "--reverse". */ bool m_map_reversed; /** * Provides the mapping between pitches. If m_map_reversed is * false, then the key is the GM pitch/note, and the value is the * device pitch/note (which is the GM note needed to produce the * same sound in GM as the device would have produced). If * m_map_reversed is true, then the key is the GM pitch/note, and * the value is the device pitch/note, so that the MIDI file will be * converted from GM mapping to device mapping. */ map m_note_map; /** * Provides a quick translation "map" for use while recording. */ midibyte m_note_array[c_notes_count]; /** * Indicates if the setup is valid. */ bool m_is_valid; public: notemapper (direction dir = direction::file); notemapper (const notemapper &) = default; notemapper & operator = (const notemapper &) = default; ~notemapper () = default; int convert (int incoming) const; midibyte fast_convert (midibyte incoming) const { return m_note_array[incoming]; /* no check done, for speed */ } std::string to_string (int devnote) const; void show () const; direction get_direction () const { return m_direction; } bool mode () const { return m_mode; } void mode (bool m) { m_mode = m; } /** * Determines if the value parameter is usable, or "active". * * \param value * The integer value to be checked. * * \return * Returns true if the value is not NOT_ACTIVE. */ static bool active (int value) { return value != notemapper::NOT_ACTIVE; } /** * Determines if both value parameters are usable, or "active". * * \param v1 * The first integer value to be checked. * * \param v2 * The second integer value to be checked. * * \return * Returns true if both of the values are not NOT_ACTIVE. */ static bool active (int v1, int v2) { return ( v1 != notemapper::NOT_ACTIVE && v2 != notemapper::NOT_ACTIVE ); } bool add ( int devnote, int gmnote, const std::string & devname, const std::string & gmname ); int repitch (int channel, int input); const std::string & map_type () const { return m_map_type; } int note_minimum () const { return m_note_minimum; } int note_maximum () const { return m_note_maximum; } int gm_channel () const { return m_gm_channel + 1; } int device_channel () const { return m_device_channel + 1; } bool valid () const { return m_is_valid; } const map & list () const { return m_note_map; } bool map_reversed () const { return m_map_reversed; } public: void map_type (const std::string & mp) { m_map_type = mp; } void map_reversed (bool flag) { m_map_reversed = flag; } void gm_channel (int ch) { m_gm_channel = ch - 1; } }; // class notemapper } // namespace seq66 #endif // SEQ66_NOTEMAPPER_HPP /* * notemapper.hpp * * vim: sw=4 ts=4 wm=8 et ft=cpp */ ================================================ FILE: libseq66/include/play/performer.hpp ================================================ #if ! defined SEQ66_PERFORMER_HPP #define SEQ66_PERFORMER_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file performer.hpp * * This module declares/defines the base class for handling many facets * of performing (playing) a full MIDI song. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-13 * \updates 2025-07-27 * \license GNU GPLv2 or above * * The main player! Coordinates sets, patterns, mutes, playlists, you name * it! * * From rcsettings.hpp: * * ctrl/keycontainer.hpp * ctrl/midicontrolin.hpp * ctrl/midicontrolout.hpp * play/clockslist.hpp * play/inputslist.hpp * play/mutegroups.hpp */ #include /* std::shared_ptr<>, unique_ptr<> */ #include /* std::vector<> */ #include /* std::thread */ #include "cfg/rcsettings.hpp" /* lots of other files, see banner */ #include "ctrl/opcontainer.hpp" /* class seq66::opcontainer */ #include "midi/jack_assistant.hpp" /* optional seq66::jack_assistant */ #include "midi/mastermidibus.hpp" /* seq66::mastermidibus ALSA/JACK */ #include "play/metro.hpp" /* seq66::metro metronome pattern */ #include "play/playlist.hpp" /* seq66::playlist */ #include "play/sequence.hpp" /* seq66::sequence */ #include "play/setmapper.hpp" /* seq66::seqmanager and seqstatus */ #include "util/condition.hpp" /* seq66::condition/synchronizer */ #if defined USE_SONG_BOX_SELECT #include /* std::set, arbitary selection */ #endif namespace seq66 { /* * Offloads from the app limits header that provide a sanity check for * transposition values. Also see the transposition functions in the trigger * class. ca 2025-05-29: Was backwards, and now we restrict them to 5 * octaves. */ const int c_transpose_down_limit = -60; /* c_notes_count / 2 */ const int c_transpose_up_limit = 60; /* -c_transpose_down_limit */ /* * Forward references. */ class keystroke; class notemapper; class rcsettings; class usrsettings; /** * This class supports the performance mode. */ class performer { friend class jack_assistant; friend class midifile; friend class rcfile; friend class playlist; friend class qperfeditframe64; friend class qplaylistframe; friend class qt5nsmanager; friend class qseditoptions; friend class qsmainwnd; friend class sequence; friend class smanager; friend class wrkfile; #if defined SEQ66_JACK_SUPPORT friend int jack_sync_callback ( jack_transport_state_t state, jack_position_t * pos, void * arg ); friend int jack_transport_callback (jack_nframes_t nframes, void * arg); friend void jack_shutdown (void * arg); friend void jack_timebase_callback ( jack_transport_state_t state, jack_nframes_t nframes, jack_position_t * pos, int new_pos, void * arg ); friend long get_current_jack_position (void * arg); #endif // SEQ66_JACK_SUPPORT public: /** * Provides a setting for the fast-forward and rewind functionality. */ enum class ff_rw { rewind = -1, none = 0, forward = 1, max }; /** * A visible representation of whether to "modify" the tune. Some * changes do not require the tune to be saved before closing. The * "recreate" value is a stronger form of "yes", and additionally * requests that key elements of the notified object need to be * recreated. */ enum class change { no, /**< Do not set the modify-flag. */ yes, /**< Do set the modify-flag. */ recreate, /**< Recreate the user-interface(s). */ removed, /**< Change was a removal; more specific than yes. */ signal, /**< Could alter the UI from a different thread. */ max }; /** * A nested class to provide an implementation of the synchronizer * class. This small class is used to simplify the usage of a condition * variable to coordinate the output function and the inner-start * function. */ class synch : public synchronizer { private: performer & m_perf; public: synch (performer & p) : synchronizer (), m_perf (p) { // no code } synch () = delete; synch (const synch &) = delete; synch & operator =(const synch &) = delete; virtual bool predicate () const override { return m_perf.is_running() || m_perf.done(); } }; /** * A nested class used for notification of group-learn and other changes. * The easiest way to use this class is by inheriting from it, then * overriding the virtual functions defined within. If that leads to * multiple inheritance, well, for our use cases, that is not an issue, * as we will not copy the user-interface classes anyway. For an * example, see one of the user-interface classes, such as qsmainwnd. * * In each of the callbacks declared/defined below, the \a state * parameter indicates the state to which the object is transitioning. */ class callbacks { private: /** * Provides a reference to the main performer object. */ performer & m_performer; public: /** * Provides a type definition for a list of pointers to objects * supporting the performer::callbacks "interface". */ using clients = std::vector; /** * Provides a way to indicate via a value what callback function is * in force. */ enum class index { group_learn, /**< Group-learn turned on. */ group_learn_complete, /**< Group-learn turned off. */ mutes_change, /**< Change in the mute-state. */ set_change, /**< Change in the active screen-set. */ sequence_change, /**< New, deleted, or pasted pattern. */ automation_change, /**< A start or stop control occurred. */ ui_change, /**< Indicates a user-interface action. */ trigger_change, /**< A trigger changed pattern muting. */ resolution_change, /**< A change in PPQN or BPM. */ song_change /**< A different MIDI tune was loaded. */ }; public: callbacks (performer & p) : m_performer(p) { /* Empty body */ } static bool true_change (performer::change mod) { return ( mod == performer::change::yes || mod == performer::change::removed ); } /** * Derived classes should override these function to perform work, if * needed, and to return true if the work was done successfully. */ virtual bool on_group_learn (bool /* learning */) { return false; } virtual bool on_group_learn_complete ( const keystroke & /* k */, bool /* good */ ) { return false; } virtual bool on_mutes_change (mutegroup::number, performer::change) { return false; } virtual bool on_set_change (screenset::number, performer::change) { return false; } virtual bool on_sequence_change (seq::number, performer::change) { return false; } virtual bool on_automation_change (automation::slot) { return false; } virtual bool on_ui_change (seq::number) { return false; } virtual bool on_trigger_change (seq::number, performer::change) { return false; } virtual bool on_resolution_change ( int /* ppq */, midibpm, performer::change ) { return false; } virtual bool on_song_action (bool, playlist::action) { return false; } #if defined USE_ON_SIGNAL_ACTION virtual bool on_signal_action (bool, playlist::action) { return false; } #endif performer & cb_perf () { return m_performer; } const performer & cb_perf () const { return m_performer; } }; // class callbacks public: #if defined USE_SONG_BOX_SELECT /** * Provides a type to hold the unique shift-selected sequence numbers. * Although this can be considered a GUI function, it makes sense to * let performer manage it and encapsulate it. */ using selection = std::set; /** * Provides a function type that can be applied to each sequence number * in a selection. Generally, the caller will bind a member function to * use in operate_on_set(). The first parameter is a sequence number * (obtained from the selection). The caller can bind additional * placeholders or parameters, if desired. * * See the old seq64 perfroll module. */ using SeqOperation = std::function; #endif // defined USE_SONG_BOX_SELECT private: /** * Defines a pointer to a member automation function. These functions * match the function signature of the midioperation::functor type. */ using automation_function = bool (performer::*) ( automation::action a, int d0, int d1, int index, bool inverse ); /** * Provides a type alias useful in creating a function table to make the * loading of member op/slot functions much easier. */ using automation_pair = struct { automation::slot ap_slot; automation_function ap_function; }; static automation_pair sm_auto_func_list []; /** * Holds the first Meta Text message, if any, in the first pattern. * The string is encoded as "MIDI bytes", which means that characters * with a value greater than 127 are encoded as "\xx". See the * string_to_midi_bytes() function in the strfunctions module. */ std::string m_song_info; /** * Indicates the format of this file, either SMF 0 or SMF 1. * Note that Seq66 always converts files from SMF 0 to SMF 1, * and saves them to default to SMF 1. This setting, if set to 0, * indicates that the song has been converted to SMF 0, for export only. */ int m_smf_format; /** * Indicates that an internal setup error occurred (e.g. a device could * not be set up in PortMidi). In this case, we will eventually want to * emit an error prompt, though we keep going in order to populate the * "rc" file correctly. */ mutable bool m_error_pending; /** * Accumulates error messages for display after launch(). */ mutable std::string m_error_messages; /** * When the screenset changes, we put only the existing sequences in this * vector to try to save time in the play() function. This "play-set" * feature offloads the performer::play() work to a special short vector * of only active sequences. We're desperately trying to reduce the CPU * usage of this program when playing. Without being connected to a * synthesizer, playing the "b4uacuse" MIDI file in Live mode, with no * pattern armed, the program eats up one whole CPU on an i7. Setting * this macro cuts that roughly in half... except when a pattern is * armed. * * Note that the second playset here is accessed by the play_set() * function while count-in is in progress. */ playset m_play_set; /* normal and metronome patterns */ playset m_play_set_storage; /* only for metronome count-in */ /** * Provides an optional play-list, loosely patterned after Stazed's Seq32 * play-list. Important: This object is now owned by perform. */ std::unique_ptr m_play_list; /** * Provides an optional note-mapper or drum-mapper, read from a ".drums" * file. */ std::unique_ptr m_note_mapper; /** * Provides an optional pointer to a metronome pattern, owned and managed * by performer, but shared with the playset. */ std::shared_ptr m_metronome; /** * Provides an optional pointer to a single recorder pattern, owned and * managed by performer. Coding for this is still in progress. * Not sure we need a separate "recorder" class for this yet, as the * settings overlap with metro_settings. */ recorder * m_recorder; /** * A quick indication that count-in is requested and able to be used. */ bool m_metronome_count_in; /** * If true, playback is done in Song mode, not Live mode. This option is * saved to and restored from the "rc" configuration file. Sometimes * called "JACK start mode", it used to be a JACK setting, but now * applies to any playback. If set to 'auto', then the mode is 'song' * if the loaded tune has any pattern with song triggers. */ sequence::playback m_song_start_mode; /** * It seems that this member, if true, forces a repositioning to the left * (L) tick marker. */ bool m_reposition; /** * Provides an "acceleration" factor for the fast-forward and rewind * functionality. It starts out at 1.0, and can range up to 60.0, being * multiplied by 1.1 by the FF/RW timeout function. */ float m_excell_FF_RW; /** * Indicates whether the fast-forward or rewind key is in effect in the * perfedit window. It has values of rewind, none, or forward. This was a * free (global in a namespace) int in perfedit. */ ff_rw m_FF_RW_button_type; /** * From the liveframe/grid classes, these values make performer the boss * of pattern cut-and-paste from the grid-slot popup menu. * * The m_old_seqno member holds the current sequence number when another * sequence is clicked to change m_current_seqno. */ seq::number m_old_seqno; seq::number m_current_seqno; /** * The pattern that is being dragged to another slot. */ sequence m_moving_seq; /** * A clipboard used for channelizing, flattening, cutting, copying, * pasting, and merging sequences. */ sequence m_seq_clipboard; /** * A value not equal to -1 (it ranges from 0 to 31) indicates we're now * using the saved screen-set state to control the queue-replace * (queue-solo) status of sequence toggling. This value is set to -1 * when queue mode is exited. */ seq::number m_queued_replace_slot; /** * Indicates that a snapshot has been stored. It is cleared (currently) * when a soloed pattern is clicked again. */ seq::number m_solo_seqno; private: /* key, midi, and op container section */ /** * The list of output clocks. */ clockslist m_clocks; /** * The list of input bus statuses. */ inputslist m_inputs; /** * Indicates a clocks/inputs port_map error at startup. */ mutable bool m_port_map_error; /** * Provides a default-filled keycontrol container. */ keycontainer m_key_controls; /** * Provides a default-filled midicontrol container. */ midicontrolin m_midi_control_in; /** * Provides the class encapsulating MIDI control output. */ midicontrolout m_midi_control_out; /** * Provides a default-filled mutegroups container. It is a copy of the * data read into the global rcsettings object. */ mutegroups m_mute_groups; /** * Holds a map of midioperation functors to be used to control patterns, * mute-groups, and automation functions. */ opcontainer m_operations; /** * Pulls out the set-specific manipulations needed by the qsetmaster * user-interface class. These are moved out of setmapper for increased * clarity. The performer uses some of its functions directly, while the * setmapper can iterate over the container of sets in the set-master. */ setmaster m_set_master; /** * Manages extra sequence items formerly in separate arrays. */ setmapper m_set_mapper; /** * Holds the global MIDI transposition value. Restricted to plus or minus * 60, but the drop-down in the perf editor limits it to plus or minus * 12. */ int m_transpose; /** * Provides information for managing threads. Provides a "handle" to * the output thread. */ std::thread m_out_thread; /** * Provides a "handle" to the input thread. */ std::thread m_in_thread; /** * Indicates that the output thread has been started. */ bool m_out_thread_launched; /** * Indicates that the input thread has been started. */ bool m_in_thread_launched; /** * Indicates merely that the input and output thread functions can keep * running. Replaces m_inputing and m_outputing. */ std::atomic m_io_active; /** * Indicates that playback is running. However, this flag is conflated * with some JACK support, and we have to supplement it with another * flag, m_is_pattern_playing. */ std::atomic m_is_running; /** * Indicates that a pattern is playing. It replaces rc_settings :: * is_pattern_playing(), which is gone, since the performer is now * visible to all classes that care about it. */ bool m_is_pattern_playing; /** * Also, there are circumstance where client GUIs need to update, such as * when File / New is selected. * * Perhaps this needs to be a counter? */ mutable bool m_needs_update; /** * Indicates to belay updates during critical work. */ bool m_is_busy; /** * Indicates that status of the "loop" button in the performance editor. * If true, the performance will loop between the L and R markers in the * performance editor. */ bool m_looping; /** * Indicates to record live sequence-trigger changes into the Song data. */ bool m_song_recording; /** * Snap recorded playback changes to the sequence length or the * snap value. */ bool m_song_record_snap; /** * If record-snap is on, this supplies the selected grid-snap as * translated to ticks. Otherwise, the snap value for recording is * the length of the pattern. */ midipulse m_record_snap_length; /** * Part of a refactoring and expansion of the alterations that can be done * while recording, playing, or by a manual command. See the calculations * header for the "alteration" enumeration. It includes quantization * and jitter. */ alteration m_record_alteration; /** * Holds the current default recording style (overdub/merge, expand, * etc. */ recordstyle m_record_style; /** * Indicates to resume notes if the sequence is toggled after a Note On. * Note that this setting is settable in the user interface, and is a * usrsettings value. */ bool m_resume_note_ons; /** * Holds the current PPQN for usage in various actions. */ int m_ppqn; /** * Holds the current BPM (beats per minute) for later usage. */ midibpm m_bpm; /** * Indicates if the BPM or PPQN value has changed, for internal handling in * output_func(). */ std::atomic m_resolution_change; /** * Indicates the number of beats considered in calculating the BPM via * button tapping. This value is displayed in the button. */ int m_current_beats; /** * Holds the underrun value for possible display during very busy * playback; even more likely now that most event-drawing loops are * locked. See sequence::draw_lock() and draw_unlock(). */ long m_delta_us; /** * Indicates the first time the tap button was ... tapped. */ long m_base_time_ms; /** * Indicates the last time the tap button was tapped. If this button * wasn't tapped for awhile, we assume the user has been satisfied with * the tempo he/she tapped out. */ long m_last_time_ms; /** * Holds the beats/bar value as obtained from the MIDI file. The default * value is 4. See usrsettings. */ int m_beats_per_bar; /** * Holds the beat width value as obtained from the MIDI file. The * default value is 4. See usrsettings. */ int m_beat_width; /** * Augments the beats/bar and beat-width with the additional values * included in a Time Signature meta event. This value provides the * number of MIDI clocks between metronome clicks. The default value of * this item is 24. It can also be read from some SMF 1 files, such as * our hymne.mid example. */ int m_clocks_per_metronome; /** * Augments the beats/bar and beat-width with the additional values * included in a Time Signature meta event. Useful in export. A * duplicate of the same member in the sequence class. */ int m_32nds_per_quarter; /** * Augments the beats/bar and beat-width with the additional values * included in a Tempo meta event. Useful in export. A duplicate of the * same member in the sequence class. */ long m_us_per_quarter_note; /** * Provides our MIDI buss. We changed this item to a pointer so that we * can delay the creation of this object until after all settings have * been read. Use a smart pointer! Seems like unique_ptr<> is best * here. See the master_bus() accessors below. * * std::shared_ptr m_master_bus; */ std::unique_ptr m_master_bus; /** * Provides storage for this "rc" configuration option so that the * performer can set it in the master buss once that has been created. * If true, we can try to route events by their buss number. We have * a flag for quick checking to see if the bus/sequence vector is * usable. Note that filtering by channel and routing by buss are * incompatible with each other. */ bool m_record_by_buss; /** * Provides storage for this "rc" configuration option so that the * performer can set it in the master buss once that has been created. */ bool m_record_by_channel; /** * Provides a mapping of input busses to patterns. Treated like an array * with bussbyte indices ranging from 0 to the number of input ports * minus 1. */ std::vector m_buss_patterns; /** * Holds the "one measure's worth" of pulses (ticks), which is normally * m_ppqn * 4. We can save some multiplications, and, more importantly, * later define a more flexible definition of "one measure's worth" than * simply four quarter notes. */ midipulse m_one_measure; /** * The number of ticks to move for fast-forward and rewind. Defaults to * one-half of one measure. */ midipulse m_fast_ticks; /** * Holds the position of the left (L) marker, and it is first defined as * 0. Note that "tick" is actually "pulses". */ midipulse m_left_tick; /** * Holds the position of the right (R) marker, and it is first defined as * the end of the fourth measure. Note that "tick" is actually "pulses". */ midipulse m_right_tick; /** * Holds the starting tick for playing. By default, this value is always * reset to the value of the "left tick". We want to eventually be able * to leave it at the last playing tick, to support a "pause" * functionality. Note that "tick" is actually "pulses". */ midipulse m_start_tick; /** * MIDI Clock support. The m_tick member holds the tick to be used in * displaying the progress bars and the maintime pill. It is mutable * because sometimes we want to adjust it in a const function for pause * functionality. */ mutable midipulse m_tick; /** * Indicates the full extent of the song when in Song mode. Used for * stopping play at the end of the song. If 0, it is not used. * Only set when Song mode is on. Set when play starts, reset when play * stops. */ midipulse m_max_extent; /** * Holds a bunch of jack_assistant settings. */ jack_scratchpad m_jack_pad; /** * Let's try to save the last JACK pad structure tick for re-use with * resume after pausing. */ midipulse m_jack_tick; /** * More MIDI clock support. */ bool m_usemidiclock; /** * More MIDI clock support. Indicates if the MIDI clock is stopped or * started. */ bool m_midiclockrunning; /** * More MIDI clock support. */ int m_midiclocktick; /** * We need to adjust the clock increment for the PPQN that is in force. * Higher PPQN need a longer increment than 8 in order to get 24 clocks * per quarter note. */ int m_midiclockincrement; /** * More MIDI clock support. */ int m_midiclockpos; /** * Support for pause, which does not reset the "last tick" when playback * stops/starts. All this member is used for is keeping the last tick * from being reset. */ bool m_dont_reset_ticks; /** * It may be a good idea to eventually centralize all of the dirtiness of * a performance here. All the GUIs use a performer. */ bool m_is_modified; #if defined USE_SONG_BOX_SELECT /** * Provides a set holding all of the sequences numbers that have been * shift-selected. If we ever enable box-selection, this container will * support that as well. */ selection m_selected_seqs; #endif /** * A condition variable to protect playback. It is signalled if playback * has been started. The output thread function waits on this variable * until m_is_running and m_io_active are false. This variable is also * signalled in the performer destructor. This implementation is * new for 0.98.0, and it avoids segfaults, exit-hangs, and high CPU * usage in Windows that have occurred with other implmentations. */ synch m_condition_var; #if defined SEQ66_JACK_SUPPORT /** * A wrapper object for the JACK support of this application. It * implements most of the JACK stuff. Not used on Windows (we use * PortMidi instead). */ jack_assistant m_jack_asst; #endif /* * Not sure that we need this code; we'll think about it some more. One * issue with it is that we really can't keep good track of the modify * flag in this case, in general. * * Used for undo track modification support. * Is is worth creating an "undo" class???? */ bool m_have_undo; /** * Holds the "track" numbers or the "all tracks" values for undo * operations. See the push_trigger_undo() function. */ std::vector m_undo_vect; /** * Used for redo track modification support. */ bool m_have_redo; /** * Holds the "track" numbers or the "all tracks" values for redo * operations. See the pop_trigger_undo() function. */ std::vector m_redo_vect; /** * Can register here for events. Used in mainwnd and perform. * Now wrapped in the enregister() function, so no longer public. * * Actually, currently the main window in Qt relies on checking the learn * status in a timer. We will rethink this eventually. */ callbacks::clients m_notify; /** * If true, indicate certain events, like song-changes, occur via a * signal. In a headless run, there's no conflict with Qt's threads, but * when Qt is running, hoo boy! */ bool m_signalled_changes; /** * Set to true if automation_edit_pending() is called. It is reset by the * caller as a side-effect. The usual (but configurable) keystroke for * this function is "=". In Sequencer64 this was m_call_seq_edit. */ mutable bool m_seq_edit_pending; /** * Set to true if automation_event_pending() is called. It is reset by * the caller as a side-effect. The usual (but configurable) keystroke * for this function is "-". In Sequencer64 this was * m_call_seq_eventedit. */ mutable bool m_event_edit_pending; /** * Set to true to use the next hot-key to toggle the recording status of * the pattern selected. */ mutable bool m_record_toggle_pending; /** * Holds the loop number in the case of using the edit keys. It is * reset when the slot-shift key is struck. In Sequencer64 this was * m_call_seq_number. */ mutable seq::number m_pending_loop; /** * Incremented when automation_slot_shift() is called. It is reset by the * caller once the keystroke is handled. It is used for toggling patterns * from 32 to 63 and 64 to 95. The usual (but configurable) keystroke for * this function is "/". In Sequencer64 this was m_call_seq_shift. */ mutable int m_slot_shift; /** * Indicates if the graphical user-interface is visible. Currently * applies only to the main window. This item can be toggled by the * automation::visibility automation control or by the (Non) session * manager. The show-hide-pending flag is set indicating a change in * visibility from a keystroke or MIDI control; the GUI polling loop must * then check hidden() to see what to do, overriding any session-manager * commands. */ std::atomic m_hidden; std::atomic m_show_hide_pending; public: performer () = delete; performer (int ppq, int rows, int columns); ~performer (); performer (const performer &) = delete; performer & operator = (const performer &) = delete; void enregister (callbacks * pfcb); /* for notifications */ void unregister (callbacks * pfcb); void notify_sequence_change (seq::number seqno, change mod = change::yes); void notify_sequence_removal (seq::number seqno, change mod = change::yes); private: void notify_automation_change (automation::slot s); void notify_set_change (screenset::number setno, change mod = change::yes); void notify_mutes_change (mutegroup::number setno, change mod = change::yes); void notify_ui_change (seq::number seqno, change mod = change::yes); void notify_trigger_change (seq::number seqno, change mod = change::yes); void notify_resolution_change ( int ppq, midibpm bpm, change mod = change::yes ); void notify_song_action ( bool signalit = true, playlist::action act = playlist::action::none ); public: /** * Holds the first Meta Text message, if any, in the first pattern. */ bool set_track_info (const std::string & s, seq::number trk = 0); event get_track_info_event (seq::number trk, bool nextmatch = false); void song_info (const std::string & s, seq::number trk = 0); std::string song_info () const; std::string get_all_track_text (seq::number trk); int smf_format () const { return m_smf_format; } void smf_format (int value) { m_smf_format = value == 0 ? 0 : 1 ; } bool error_pending () const { return m_error_pending; } std::string error_messages () const { return m_error_messages; } bool modified () const; /** * \setter m_is_modified * This setter only sets the modified-flag to true. * The setter that can falsify it, unmodify(), is private. No one * but performer and its friends should falsify this flag. * For issue #90, do not use the (silly) m_needs_update flag. * If a playlist is in force, ignore changes. And some, like * mutes changes, occur just because a new file is loaded. * What a tangle! */ void modify () { if (! playlist_active()) m_is_modified = true; } /* * Added 2022-07-27 for issue #90. See usage in qsmainwnd. */ bool modification (change ctype) { return ( ctype == change::yes || ctype == change::recreate || ctype == change::removed ); } void unmodify (); /* for write_midi_file() */ bool get_settings (const rcsettings & rcs, const usrsettings & usrs); bool put_settings (rcsettings & rcs, usrsettings & usrs); bool alsa_midi_through_check (); std::string set_to_string (screenset::number setno) const { return set_master().set_to_string(setno); } std::string sets_to_string () const { return set_master().sets_to_string(); } void show_patterns () const { set_master().show(); } bool read_midi_file ( const std::string & fn, std::string & errmsg, bool addtorecent = true ); const playset & play_set () const { return m_metronome_count_in ? m_play_set_storage : m_play_set ; } playset & play_set () { return m_metronome_count_in ? m_play_set_storage : m_play_set ; } bool add_to_play_set (sequence * s); bool fill_play_set (bool clearit = true); /* * Start of playlist accessors. Playlist functionality. Note that we * ensure that a playlist object exists, even if empty. Saves a lot of * pointer checks. */ int playlist_count () const { return bool(m_play_list) ? m_play_list->list_count() : 0 ; } int song_count () const { return bool(m_play_list) ? m_play_list->song_count() : 0 ; } bool playlist_reset (int listindex = 0) { return bool(m_play_list) ? m_play_list->reset_list(listindex) : false ; } bool open_note_mapper (const std::string & notefile); bool save_note_mapper (const std::string & notefile = ""); bool open_mutegroups (const std::string & mfg); bool save_mutegroups (const std::string & mfg = ""); bool open_playlist (const std::string & pl); bool save_playlist (const std::string & pl = ""); bool import_playlist ( const std::string & sourcefile, const std::string & cfgfilepath, const std::string & midifilepath ); bool remove_playlist () { return bool(m_play_list) ? m_play_list->reset_list(true) : false ; } void playlist_show () { if (bool(m_play_list)) m_play_list->show(); } void playlist_test () { if (bool(m_play_list)) m_play_list->test(); } std::string playlist_filename () const { return bool(m_play_list) ? m_play_list->file_name() : std::string("") ; } void playlist_filename (const std::string & name); std::string playlist_midi_base () const { return bool(m_play_list) ? m_play_list->midi_base_directory() : std::string("") ; } int playlist_midi_number () const { return bool(m_play_list) ? m_play_list->list_midi_number() : 0 ; } std::string playlist_name () const { return bool(m_play_list) ? m_play_list->list_name() : std::string("") ; } bool playlist_active () const { return bool(m_play_list) && m_play_list->active(); } bool playlist_auto_arm () const { return bool(m_play_list) && m_play_list->auto_arm(); } bool playlist_auto_play () const { return bool(m_play_list) && m_play_list->auto_play(); } bool playlist_auto_advance () const { return bool(m_play_list) && m_play_list->auto_advance(); } void playlist_auto_advance (bool on) { if (bool(m_play_list)) m_play_list->auto_advance(on); } bool playlist_loaded () const { return bool(m_play_list) ? m_play_list->loaded() : false ; } void playlist_loaded (bool on) { if (bool(m_play_list)) m_play_list->loaded(on); } const std::string & playlist_error_message () const { static std::string s_null_playlist{"Null playlist"}; return bool(m_play_list) ? m_play_list->error_message() : s_null_playlist ; } std::string file_directory () const { return bool(m_play_list) ? m_play_list->file_directory() : std::string("") ; } std::string song_directory () const { return bool(m_play_list) ? m_play_list->song_directory() : std::string("") ; } bool is_own_song_directory () const { return bool(m_play_list) ? m_play_list->is_own_song_directory() : false ; } std::string song_filename () const { return bool(m_play_list) ? m_play_list->song_filename() : std::string("") ; } std::string song_filepath () const { return bool(m_play_list) ? m_play_list->song_filepath() : std::string("") ; } int song_midi_number () const { return bool(m_play_list) ? m_play_list->song_midi_number() : 0 ; } std::string playlist_song () const { return bool(m_play_list) ? m_play_list->current_song() : std::string("") ; } std::string playlist_song_basename () const; bool open_select_list_by_index (int index, bool opensong = true) { return bool(m_play_list) ? m_play_list->open_select_list(index, opensong) : false ; } bool open_select_list_by_midi (int ctrl, bool opensong = true) { return bool(m_play_list) ? m_play_list->select_list_by_midi(ctrl, opensong) : false ; } bool add_list ( int index, int midinumber, const std::string & name, const std::string & directory ) { return m_play_list->add_list(index, midinumber, name, directory); } bool modify_list ( int index, int midinumber, const std::string & name, const std::string & directory ) { return m_play_list->modify_list(index, midinumber, name, directory); } bool remove_list (int index) { return m_play_list->remove_list(index); } bool add_song ( int index, int midinumber, const std::string & name, const std::string & directory ) { return m_play_list->add_song(index, midinumber, name, directory); } bool add_song (const std::string & fullpath) { return m_play_list->add_song(fullpath); } bool modify_song ( int index, int midinumber, const std::string & name, const std::string & directory ) { return m_play_list->modify_song(index, midinumber, name, directory); } bool remove_song_by_index (int index) { return m_play_list->remove_song(index); } bool open_next_list (bool opensong = true, bool loading = false); bool open_previous_list (bool opensong = true); void handle_list_change (bool opensong); bool open_select_song_by_index (int index, bool opensong = true); bool open_select_song_by_midi (int ctrl, bool opensong = true); bool open_current_song (); bool open_next_song (bool opensong = true); bool open_previous_song (bool opensong = true); void handle_song_change (bool opensong); int next_available_song_number () const { return m_play_list->next_available_song_number(); } int next_available_list_number () const { return m_play_list->next_available_list_number(); } /* * End of playlist accessors. */ public: void repitch (event & ev) const; bool repitch_all (const std::string & nmapfile, seq::ref s); bool repitch_selected (const std::string & nmapfile, seq::ref s); bool repitch_fix (const std::string & nmapfile, seq::ref s, bool reverse); setmapper & set_mapper () { return m_set_mapper; } const setmapper & set_mapper () const { return m_set_mapper; } setmaster & set_master () { return m_set_master; } const setmaster & set_master () const { return m_set_master; } /* * This function now always returns 32. */ int screenset_count () const { return set_master().screenset_count(); } int screenset_active_count () const { return set_master().screenset_active_count(); } int highest_set () const { return set_master().highest_set(); } int screenset_max () const { return set_master().screenset_max(); } int screenset_index (screenset::number setno) const { return set_master().screenset_index(setno); } int screenset_size () const { return set_mapper().screenset_size(); } int sequences_in_sets () const { return set_mapper().sequences_in_sets(); } int ppqn () const { return m_ppqn; } void ppqn (int p) { m_ppqn = p; } /** * Only a nominal value. The mastermidibus could be considered the true * value of BPM (and PPQN). */ midibpm bpm () const { return m_bpm; /* only a nominal value */ } int rows () const { return set_mapper().rows(); } int columns () const { return set_mapper().columns(); } int mute_rows () const { return mutes().rows(); } int mute_columns () const { return mutes().columns(); } int mute_count () const { return mutes().count(); } screenset::number master_grid_to_set (int row, int column) const { return set_master().grid_to_set(row, column); } bool master_index_to_grid ( screenset::number setno, int & row, int & column ) { return set_master().index_to_grid(setno, row, column); } bool master_inside_set (int row, int column) const { return set_master().inside_set(row, column); } seq::number grid_to_seq (int row, int column) const { return set_mapper().grid_to_seq(row, column); } seq::number grid_to_seq ( screenset::number setno, int row, int column ) const { return set_mapper().grid_to_seq(setno, row, column); } bool seq_to_grid ( seq::number seqno, int & row, int & column, bool global = false ) const { return set_mapper().seq_to_grid(seqno, row, column, global); } bool index_to_grid (seq::number seqno, int & row, int & column) const { return set_mapper().index_to_grid(seqno, row, column); } int grid_to_index (int row, int column) const { return int(set_mapper().grid_to_index(row, column)); } /** * It is better to call this getter before bothering to even try to use a * sequence. In many cases at startup, or when loading a file, there are * no sequences yet, and still the code calls functions that try to access * them. */ int sequence_count () const { return set_mapper().sequence_count(); } seq::number sequence_high () const { return set_mapper().sequence_high(); } seq::number sequence_max () const { return set_mapper().sequence_max(); } int get_beats_per_bar () const { return m_beats_per_bar; } /* * Simple setter. for the one that iterates over patterns, see * set_beats_per_measure(). */ void set_beats_per_bar (int bpb) { m_beats_per_bar = bpb; #if defined SEQ66_JACK_SUPPORT m_jack_asst.set_beats_per_measure(bpb); #endif } /** * Iterates over patterns to make the setting. Used for the global beats * in the main window. */ bool set_beats_per_measure (int bpb, bool user_change = false); int get_beat_width () const { return m_beat_width; } /* * Simple setter. for the one that iterates over patterns, see * set_beat_length(). */ void set_beat_length (int bl) { m_beat_width = bl; #if defined SEQ66_JACK_SUPPORT m_jack_asst.set_beat_width(bl); #endif } /** * Iterates over patterns to make the setting. Used for the global beats * in the main window. */ bool set_beat_width (int bw, bool user_change = false); void clocks_per_metronome (int cpm) { m_clocks_per_metronome = cpm; } int clocks_per_metronome () const { return m_clocks_per_metronome; } void set_32nds_per_quarter (int tpq) { m_32nds_per_quarter = tpq; } int get_32nds_per_quarter () const { return m_32nds_per_quarter; } void us_per_quarter_note (long upqn) { m_us_per_quarter_note = upqn; } long us_per_quarter_note () const { return m_us_per_quarter_note; } mastermidibus * master_bus () { return m_master_bus.get(); } const mastermidibus * master_bus () const { return m_master_bus.get(); } std::string client_id_string () const; int client_id () const { return master_bus() ? master_bus()->client_id() : (-1) ; } void record_by_channel (bool flag) { m_record_by_channel = flag; if (master_bus()) master_bus()->record_by_channel(flag); } bool record_by_channel () const { return m_record_by_channel; } void record_by_buss (bool flag) { m_record_by_buss = flag; if (master_bus()) master_bus()->record_by_buss(flag); } bool record_by_buss () const { return m_record_by_buss; } bool sequence_inbus_setup (bool changed = false); void sequence_inbus_clear (); sequence * sequence_inbus_lookup (const event & ev); /* * Used in synchronizing starting/stopping playback and in coordination * with jack_assistant (transport). */ bool is_running () const { return m_is_running; } /* * Used in conjunction with user-interface control of playback (start, * stop, pause). */ bool is_pattern_playing () const { return m_is_pattern_playing; } void is_pattern_playing (bool flag); /* now more complex */ bool is_pattern_paused () const { return m_dont_reset_ticks; } bool done () const { return ! m_io_active; } /* * --------------------------------------------------------------------- * JACK Transport * --------------------------------------------------------------------- */ jack_scratchpad & pad () { return m_jack_pad; } #if defined SEQ66_JACK_SUPPORT bool jack_output (jack_scratchpad & pad) { return m_jack_asst.output(pad); } #else bool jack_output (jack_scratchpad & /*pad*/) { return false; } #endif /** * \getter m_jack_asst.is_running() * This function is useful for announcing the status of JACK in * user-interface items that only have access to the performer. */ bool is_jack_running () const { #if defined SEQ66_JACK_SUPPORT return m_jack_asst.is_running(); #else return false; #endif } /** * Also now includes is_jack_running(), since one cannot be JACK Master * if JACK is not running. */ bool is_jack_master () const { #if defined SEQ66_JACK_SUPPORT return m_jack_asst.is_running() && m_jack_asst.is_master(); #else return false; #endif } bool is_jack_slave () const { #if defined SEQ66_JACK_SUPPORT return m_jack_asst.is_running() && m_jack_asst.is_slave(); #else return false; #endif } bool no_jack_transport () const { #if defined SEQ66_JACK_SUPPORT return ! m_jack_asst.is_running() || m_jack_asst.no_transport(); #else return true; #endif } bool jack_transport_not_starting () const { #if defined SEQ66_JACK_SUPPORT return ! is_jack_running() || m_jack_asst.transport_not_starting(); #else return true; #endif } /** * If JACK is supported, starts the JACK transport. */ void start_jack () { #if defined SEQ66_JACK_SUPPORT m_jack_asst.start(); #endif } void stop_jack (bool rewind = false) { #if defined SEQ66_JACK_SUPPORT m_jack_asst.stop(rewind); #else (void) rewind; #endif } /** * Initializes JACK support, if defined. The launch() function and * options module (when Connect is pressed) call this. * * \return * running, false if not or if JACK support is note defined. */ bool init_jack_transport () { #if defined SEQ66_JACK_SUPPORT return m_jack_asst.init(); #else return false; #endif } /** * Tears down the JACK infrastructure. Called by launch() and the * options module (when Disconnect is pressed). This function operates * only while we are not outputing, otherwise we have a race condition * that can lead to a crash. * * \return * Returns the result of the init() call; true if JACK sync is now * no longer running or JACK is not supported. */ bool deinit_jack_transport () { #if defined SEQ66_JACK_SUPPORT return m_jack_asst.deinit(); #else return true; #endif } #if defined SEQ66_JACK_SUPPORT void position_jack (bool songmode, midipulse tick) { m_jack_asst.position(songmode, tick); } #else void position_jack (bool, midipulse) { /* no code */ } #endif bool set_jack_mode (bool connect); void toggle_jack_mode () { #if defined SEQ66_JACK_SUPPORT m_jack_asst.toggle_jack_mode(); #endif } bool get_jack_mode () const { #if defined SEQ66_JACK_SUPPORT return m_jack_asst.get_jack_mode(); #else return false; #endif } midipulse jack_stop_tick () const { #if defined SEQ66_JACK_SUPPORT return m_jack_asst.jack_stop_tick(); #else return 0; #endif } bool jack_set_beats_per_minute (midibpm bp, bool user_change = false); bool jack_set_ppqn (int p) { #if defined SEQ66_JACK_SUPPORT m_jack_asst.set_ppqn(p); return true; #else return p > 0; #endif } #if defined SEQ66_JACK_SUPPORT void jack_stop_tick (midipulse tick) { m_jack_asst.jack_stop_tick(tick); } #else void jack_stop_tick (midipulse) { /* no code needed */ } #endif midipulse get_jack_tick () const { return m_jack_tick; } void set_jack_tick (midipulse tick) { m_jack_tick = tick; /* current JACK tick/pulse value */ } #if defined SEQ66_JACK_SUPPORT void set_follow_transport (bool flag) { m_jack_asst.set_follow_transport(flag); } #else void set_follow_transport (bool) { /* no code needed */ } #endif bool get_follow_transport () const { #if defined SEQ66_JACK_SUPPORT return m_jack_asst.get_follow_transport(); #else return false; #endif } void toggle_follow_transport () { #if defined SEQ66_JACK_SUPPORT m_jack_asst.toggle_follow_transport(); #endif } /** * Convenience function for following progress in seqedit. */ bool follow_progress () const { #if defined SEQ66_JACK_SUPPORT return m_is_running && m_jack_asst.get_follow_transport(); #else return m_is_running; #endif } /* * --------------------------------------------------------------------- * Song versus Live mode * --------------------------------------------------------------------- */ bool jackless_song_mode () const { return song_mode() && ! is_jack_running(); } sequence::playback toggle_song_start_mode (); bool song_mode (sequence::playback p) const { return p == sequence::playback::song; } bool live_mode () const { return m_song_start_mode == sequence::playback::live; } bool song_mode () const { return m_song_start_mode == sequence::playback::song; } bool live_mode (sequence::playback p) const { return p == sequence::playback::live; } void song_start_mode (sequence::playback p) { m_song_start_mode = p; } /** * Note that there are a lot of existing boolean comparisons, which now * use the song_mode() function. A little confusing. */ sequence::playback song_start_mode () const { return m_song_start_mode; } void next_song_mode (); void song_mode (bool flag) { m_song_start_mode = flag ? sequence::playback::song : sequence::playback::live ; } bool toggle_song_mode () { return toggle_song_start_mode() == sequence::playback::song; } void FF_rewind (); bool FF_RW_timeout (); /* called by free-function of same name */ void jack_reposition (midipulse tick, midipulse stoptick); void set_reposition (bool postype = true) { m_reposition = postype; } ff_rw ff_rw_type () { return m_FF_RW_button_type; } void ff_rw_type (ff_rw button_type) { m_FF_RW_button_type = button_type; } /** * Sets the rewind status. * * \param press * If true, the status is set to FF_RW_REWIND, otherwise it is set to * FF_RW_NONE. */ void rewind (bool press) { ff_rw_type(press ? ff_rw::rewind : ff_rw::none); } /** * Sets the fast-forward status. * * \param press * If true, the status is set to ff_rw::forward, otherwise it is set * to ff_rw::none. */ void fast_forward (bool press) { ff_rw_type(press ? ff_rw::forward : ff_rw::none); } void reposition (midipulse tick); public: bool set_midi_bus (seq::number seqno, int buss); bool set_midi_channel (seq::number seqno, int channel); bool set_midi_in_bus (seq::number seqno, int buss); bool set_sequence_name (seq::ref s, const std::string & name); bool set_recording (seq::number seqno, toggler flag); bool set_recording (seq::ref s, toggler flag); bool set_recording (seq::ref s, alteration q, toggler flag); bool set_recording_flip (); bool set_recording_flip (seq::ref s); bool set_recording_ex (bool record); bool set_recording_buss_flip (); // could be made private bool set_recording_chan_flip (); // could be made private bool set_thru (seq::ref s, bool active, bool toggle); #if defined USE_SONG_BOX_SELECT bool selection_operation (SeqOperation func); void box_insert (seq::number dropseq, midipulse droptick); void box_delete (seq::number dropseq, midipulse droptick); void box_toggle_sequence (seq::number dropseq, midipulse droptick); void box_unselect_sequences (seq::number dropseq); void box_move_triggers (midipulse tick); void box_move_triggers (midipulse offset); bool box_selection_empty () const { return m_selected_seqs.empty(); } void box_selection_clear () { m_selected_seqs.clear(); } #endif // defined USE_SONG_BOX_SELECT bool clear_all (bool clearplaylist = false); bool clear_song (); bool launch (int ppq); bool finish (); bool activate (); bool new_sequence ( seq::number & finalseq, seq::number seqno = seq::unassigned() ); bool new_sequence ( sequence * seqptr, seq::number seqno = seq::unassigned() ); bool request_sequence (seq::number seqno = seq::unassigned()) { static seq::number s_dummy; return new_sequence(s_dummy, seqno); } bool channelize_sequence (seq::number seqno, int channel); bool clear_sequence (seq::number seqno); bool double_sequence (seq::number seqno); bool remove_sequence (seq::number seqno); bool flatten_sequence (seq::number seqno); bool export_sequence (seq::number seqno, const std::string & filename); bool copy_sequence (seq::number seqno); bool cut_sequence (seq::number seqno); bool paste_sequence (seq::number seqno); bool merge_sequence (seq::number seqno); bool move_sequence (seq::number seqno); bool finish_move (seq::number seqno); bool fix_pattern (seq::number seqno, fixparameters & params); bool remove_set (screenset::number setno); bool clear_set (screenset::number setno); bool swap_sets (seq::number set0, seq::number set1); bool can_paste () const { return m_seq_clipboard.event_count() > 0; } bool is_seq_in_edit (int seqno) const { return set_mapper().is_seq_in_edit(seqno); } /** * Shows all the triggers of all the sequences. */ void print_busses () const { if (master_bus()) master_bus()->print(); } bool auto_play_start (); bool auto_play_stop (midipulse tick); void auto_stop (bool rewind = false); void auto_pause (); void auto_play (); void play_all_sets (midipulse tick); void play (midipulse tick); void all_notes_off (); void unqueue_sequences (int hotseq) { set_mapper().unqueue(hotseq); } bool panic (); /* from kepler43 */ bool visibility (automation::action a); /* for NSM/Live use */ void set_tick (midipulse tick, bool dontreset = false); void move_tick (midipulse tick, bool dontreset = false); void set_left_tick (midipulse tick); void set_left_tick_snap (midipulse tick, midipulse snap); void set_right_tick (midipulse tick); void set_right_tick_snap (midipulse tick, midipulse snap); midipulse get_right_tick () const { return m_right_tick; } /** * Sets the global left tick and the sequence-specific last tick, for * one sequence only. */ void set_last_tick_seq (sequence & s, midipulse tick, midipulse snap); /** * For every pattern/sequence that is active, sets the "original tick" * value for the pattern. This is really the "last tick" value, so we * renamed sequence::set_orig_tick() to sequence::set_last_tick(). * * \param tick * Provides the last-tick value to be set for each sequence that is * active. */ void set_last_ticks (midipulse tick) { set_mapper().set_last_ticks(tick); } midipulse get_left_tick () const { return m_left_tick; } void set_start_tick (midipulse tick) { m_start_tick = tick; /* starting JACK tick/pulse value */ } midipulse get_start_tick () const { return m_start_tick; } /** * Convenience function for JACK support when loop in song mode. * * \return * Returns the difference between the right and left tick, cast to * double. */ double left_right_size () const { return double(m_right_tick - m_left_tick); } public: /* * Functions to move into sequence management. */ /** * Checks the pattern/sequence for activity. Uses the setmapper to get the * actually screenset (internally). * * \param seq * The pattern number. It is checked for invalidity. This can * lead to "too many" (i.e. redundant) checks, but we're trying to * centralize such checks in this function. * * \return * Returns the value of the active-flag, or false if the sequence was * invalid or null. */ bool is_seq_active (seq::number seqno) const { return set_mapper().is_seq_active(seqno); } bool is_seq_empty (seq::number seqno) const { return set_mapper().is_seq_empty(seqno); } bool is_seq_recording (seq::number seqno) const { return set_mapper().is_seq_recording(seqno); } bool is_metronome (seq::number seqno) const; seq::number first_seq () const { return set_mapper().first_seq(); } public: void apply_song_transpose () { set_mapper().apply_song_transpose(); } /** * \setter m_transpose * For sanity's sake, the values are restricted to +-60. */ void set_transpose (int t) { if (t >= c_transpose_down_limit && t <= c_transpose_up_limit) m_transpose = t; } int get_transpose () const { return m_transpose; } /** * Retrieves the BPM setting of the master MIDI buss. * This result should be the same as the value of the m_bpm member. * * \return * Returns the value of beats/minute from the master buss. */ midibpm get_beats_per_minute () const { return master_bus() ? master_bus()->get_beats_per_minute() : bpm() ; } int get_ppqn_from_master_bus () const; midibpm update_tap_bpm (); bool tap_bpm_timeout (); int current_beats () const { return m_current_beats; } long delta_us () const { return m_delta_us; } void clear_current_beats () { m_current_beats = m_base_time_ms = m_last_time_ms = 0; } bool reload_mute_groups (std::string & errmessage); bool load_mute_groups (bool bmidi, bool bmutes) { return mutes().load_mute_groups(bmidi, bmutes); } bool set_ctrl_status ( automation::action a, automation::ctrlstatus status ); bool toggle_ctrl_status (automation::ctrlstatus s); void display_ctrl_status (automation::ctrlstatus s, bool on); void unset_queued_replace (bool clearbits = true); bool sequence_playing_toggle (seq::number seqno); bool sequence_playing_change (seq::number seqno, bool on); bool replace_for_solo (seq::number seqno, bool queued = false); void set_keep_queue (bool activate); bool is_keep_queue () const { return midi_control_in().is_keep_queue(); } bool is_solo () const { return midi_control_in().is_solo(); } /* * --------------------------------------------------------------------- * Pattern/track control * --------------------------------------------------------------------- */ /** * Calls sequence_playing_change() with a value of true. * * \param seq * The sequence number of the sequence to turn on. */ void sequence_playing_on (seq::number seqno) { sequence_playing_change(seqno, true); } /** * Calls sequence_playing_change() with a value of false. * * \param seq * The sequence number of the sequence to turn off. */ void sequence_playing_off (seq::number seqno) { sequence_playing_change(seqno, false); } /** * Mutes/unmutes all tracks in the current set of active patterns/sequences. * Covers tracks from 0 to m_sequence_max. * * We have to also set the sequence's playing status, in opposition to the * mute status, in order to see the sequence status change on the * user-interface. HMMMMMM. * * \param flag * If true (the default), the song-mutes of the sequences are turned * on. Otherwise, they are turned off. */ void mute_all_tracks (bool flag = true) { set_mapper().mute_all_tracks(flag); } /** * Toggles the mutes status of all tracks in the current set of active * patterns/sequences. Covers tracks from 0 to m_sequence_max. * * Note that toggle_playing() now has two default parameters used by the * new song-recording feature, which are currently not used here. */ void toggle_all_tracks () { set_mapper().toggle(); } void set_song_mute (mutegroups::action op); void mute_screenset (int ss, bool flag = true); /** * Toggles the mutes status of all playing (currently unmuted) tracks in * the current set of active patterns/sequences on all screen-sets. * * Note that this function operates only in Live mode; it is too confusing * to use in Song mode. Do we need to call performer :: * sequence_playing_toggle() for all tracks instead, to enable recording * of those kinds of song performance changes? */ void toggle_playing_tracks () { if (! song_mode()) set_mapper().toggle_playing_tracks(); } bool any_group_unmutes () const { return mutes().any(); } bool install_sequence ( sequence * seq, seq::number & seqno, bool fileload = false ); bool install_metronome (); bool reload_metronome (); void remove_metronome (); void arm_metronome (bool on = true); bool install_recorder (); bool reload_recorder (); void remove_recorder (); bool finish_recorder (); void inner_start (); void inner_stop (bool midiclock = false); /** * If JACK is not running, call inner_start() with the given state. * * \question * Should we also call song_start_mode(songmode) here? * * \param songmode * If true, playback is to be in Song mode. Otherwise, it is to be * in Live mode. */ void start () { if (! is_jack_running()) inner_start(); } /** * If JACK is not running, call inner_stop(). */ void stop () { if (! is_jack_running()) inner_stop(); } int clamp_track (int track) const; int clamp_group (int group) const; void save_playing_state (); void restore_playing_state (); void save_queued (int repseq) { set_mapper().save_queued(repseq); } public: void start_playing (); void play_count_in (); void pause_playing (); void stop_playing (bool rewind = false); void group_learn (bool flag); void group_learn_complete (const keystroke & k, bool good = true); bool needs_update (seq::number seqno = seq::all()) const; midipulse get_tick () const { return m_tick; } void learn_toggle () { group_learn(! is_group_learn()); } void select_and_mute_group (mutegroup::number mg); int count_mutes (mutegroup::number group) { return mutes().armed_count(group); } midibooleans get_mutes (mutegroup::number gmute) const { return mutes().get(gmute); } midibooleans get_active_groups () const { return mutes().get_active_groups(); } bool set_mutes ( mutegroup::number gmute, const midibooleans & bits, bool putmutes = false ); bool learn_mutes (mutegroup::number group); bool clear_mutes (); /* can cause a modify() */ bool apply_session_mutes (); bool apply_mutes (mutegroup::number group); bool unapply_mutes (mutegroup::number group); bool toggle_mutes (mutegroup::number group); bool toggle_active_mutes (mutegroup::number group); bool toggle_active_only () const { return mutes().toggle_active_only(); } void toggle_active_only (bool flag) { mutes().toggle_active_only(flag); } midibpm decrement_beats_per_minute (); midibpm increment_beats_per_minute (); midibpm page_decrement_beats_per_minute (); midibpm page_increment_beats_per_minute (); screenset::number decrement_screenset (int amount = 1); screenset::number increment_screenset (int amount = 1); bool copy_playscreen (); bool paste_to_playscreen (); screenset::number playscreen_number () const { return set_mapper().playscreen_number(); } seq::number playscreen_offset () const { return set_mapper().playscreen_offset(); } int playscreen_active_count () const { return set_mapper().playscreen_active_count(); } /** * True if a sequence is empty and should be highlighted. This setting * is currently a build-time option, but could be made a run-time option * later. * * \param seq * Provides a reference to the desired sequence. */ bool empty (seq::cref s) const { return s.event_count() == 0; } bool highlight (seq::cref s) const { return empty(s); } /** * True if the sequence is an SMF 0 sequence. * * \param seq * Provides a reference to the desired sequence. */ bool is_smf_0 (seq::cref s) const { return s.is_smf_0(); } /** * Retrieves the actual sequence, based on the pattern / sequence / loop * / track number. This is the non-const version. Note that it is more * efficient to call this function and check the result than to call * is_active() and then call this function. * * Note that it gets the sequence / loop from the play-screen. * * \param seqno * The prospective sequence number. * * \return * Returns the sequence pointer if seq is valid. Otherwise, a * null pointer is returned. */ seq::pointer loop (seq::number seqno) { return set_mapper().loop(seqno); } /** * Retrieves the actual sequence. This is the const version. */ const seq::pointer loop (seq::number seqno) const { return set_mapper().loop(seqno); } void off_sequences (seq::number seqno = seq::unassigned()) { set_mapper().off_sequences(seqno); } std::string automation_key (automation::slot s); std::string sequence_label (seq::cref seq) const; std::string sequence_label (seq::number seqno) const; std::string sequence_title (seq::cref seq) const; std::string sequence_window_title (seq::cref seq) const; std::string main_window_title (const std::string & fn = "") const; std::string pulses_to_measure_string (midipulse tick) const; std::string pulses_to_time_string (midipulse tick) const; bool ui_set_input (bussbyte bus, bool active); bool ui_get_input ( bussbyte bus, bool & active, std::string & n, bool statusshow = false ) const; bool ui_set_clock (bussbyte bus, e_clock clocktype); bool ui_get_clock ( bussbyte bus, e_clock & e, std::string & n, bool statusshow = false ) const; bool port_maps_active () const; bool port_map_error () const { return m_port_map_error; } void clear_port_map_error () const { m_port_map_error = false; /* mutable */ } void store_io_maps_and_restart () const; bool store_io_maps (); void clear_io_maps (); void activate_io_maps (bool active); bussbyte true_input_bus (bussbyte nominalbuss) const; bussbyte true_output_bus (bussbyte nominalbuss) const; /** * Sets a single clock item, if in the currently existing range. * Mostly meant for use by the Options / MIDI Clocks tab. */ void set_clock (bussbyte bus, e_clock clocktype) { m_clocks.set(true_output_bus(bus), clocktype); } e_clock get_clock (bussbyte bus) const { return m_clocks.get(true_output_bus(bus)); } /** * Sets a single input item, if in the currently existing range. * Mostly meant for use by the Options / MIDI Input tab. */ void set_input (bussbyte bus, bool inputing) { m_inputs.set(true_input_bus(bus), inputing); } bool get_input (bussbyte bus) const { return m_inputs.get(true_input_bus(bus)); } bool is_input_system_port (bussbyte bus) const; bool new_ports_available () const; bool is_port_unavailable (bussbyte bus, midibase::io iotype) const; bool any_ports_unavailable (bool accept_zero_inputs = false) const; bool mainwnd_key_event (const keystroke & k); bool keyboard_control_press (unsigned key); bool keyboard_group_c_status_press (unsigned key); bool keyboard_group_c_status_release (unsigned key); bool keyboard_group_press (unsigned key); bool keyboard_group_release (unsigned key); bool perfroll_key_event (const keystroke & k, int drop_sequence); /* * Track-specific pass-along trigger functions. */ bool select_trigger (seq::number seqno, midipulse droptick); bool selected_trigger ( seq::number seqno, midipulse droptick, midipulse & tick0, midipulse & tick1 ); bool clear_triggers (seq::number seqno); bool print_triggers (seq::number seqno) const; bool copy_triggers (seq::number seqno); bool cut_triggers (seq::number seqno); bool delete_triggers (seq::number seqno); bool get_trigger_state (seq::number seqno, midipulse tick) const; bool add_trigger (seq::number seqno, midipulse tick, midipulse snap); bool delete_trigger (seq::number seqno, midipulse tick); bool transpose_trigger (seq::number, midipulse droptick, int transposition); bool add_or_delete_trigger (seq::number seqno, midipulse tick); bool split_trigger ( seq::number seqno, midipulse tick, trigger::splitpoint splittype ); bool grow_trigger ( seq::number seqno, midipulse tickfrom, midipulse tickto, midipulse len ); const trigger & find_trigger (seq::number seqno, midipulse tick) const; bool paste_trigger (seq::number seqno, midipulse tick = c_no_paste_trigger); bool paste_or_split_trigger (seq::number seqno, midipulse tick); #if defined USE_INTERSECT_FUNCTIONS bool intersect_triggers (seq::number seqno, midipulse tick); #endif bool offset_triggers ( triggers::grow ts, int seqlow, int seqhigh, midipulse offset ); bool move_triggers (seq::number seqno, midipulse tick, bool adjust_offset); bool move_trigger ( seq::number seqno, midipulse starttick, midipulse distance, bool direction, bool single = true ); void push_trigger_undo (seq::number seqno = seq::all()); void pop_trigger_undo (); void pop_trigger_redo (); midipulse get_max_timestamp () const { return set_mapper().max_timestamp(); } midipulse get_max_trigger () const { return set_mapper().max_trigger(); } midipulse get_max_extent () const; std::string duration (bool dur = true) const; int count_exportable () const; bool convert_to_smf_0 (bool remove_old = true); /** * Indicates that the desired sequence is active, unmuted, and has * a non-zero trigger count. * * \param seq * The index of the desired sequence. * * \return * Returns true if the sequence has the three properties noted above. */ bool is_exportable (seq::number seqno) const { return set_mapper().is_exportable(seqno); } /** * Checks the pattern/sequence for main-dirtiness. See the * sequence::is_dirty_main() function. * * \param seq * The pattern number. It is converted to a set number and an offset * into the set. It is checked for validity? * * \return * Returns the was-active-main flag value, before setting it to * false. Returns false if the pattern was invalid. */ bool is_dirty_main (seq::number seqno) const { return set_mapper().is_dirty_main(seqno); } bool is_dirty_edit (seq::number seqno) const { return set_mapper().is_dirty_edit(seqno); } bool is_dirty_perf (seq::number seqno) const { return set_mapper().is_dirty_perf(seqno); } bool is_dirty_names (seq::number seqno) const { return set_mapper().is_dirty_names(seqno); } void send_onoff_event (midicontrolout::uiaction a, bool on); void send_play_states ( midicontrolout::uiaction a, midicontrolout::actionindex ai = midicontrolout::action_del ); void send_onoff_play_states (midicontrolout::uiaction a); void send_mutes_event (int group, bool on); void send_mutes_events (int groupon, int groupoff); void send_mutes_inactive (int group); void announce_playscreen (); void announce_automation (bool activate = true); void announce_exit (bool playstatesoff = true); bool announce_sequence (seq::pointer s, seq::number sn); bool announce_pattern (seq::number sn); void announce_mutes (); void set_midi_control_out (); const midicontrolout & midi_control_out () const { return m_midi_control_out; } midicontrolout & midi_control_out () { return m_midi_control_out; } void set_needs_update (bool flag = true) { m_needs_update = flag; } void send_seq_event (int seqno, midicontrolout::seqaction what) { midi_control_out().send_seq_event(seqno, what); } void send_macro (const std::string & name) { midi_control_out().send_macro(name); } bool macros_active () const { return midi_control_out().macros_active(); } void macros_active (bool flag) { midi_control_out().macros_active(flag); } tokenization macro_names () const { return midi_control_out().macro_names(); } midibytes macro_bytes (const std::string & name) const { return midi_control_out().macro_bytes(name); } const midimacro & get_macro (const std::string & name) const { return midi_control_out().macro(name); } bool exec_slot_function ( screenset::slothandler p, bool use_set_offset = true ) { return set_mapper().exec_slot_function(p, use_set_offset); } bool exec_set_function (screenset::sethandler s) { return set_mapper().exec_set_function(s); } bool exec_set_function (screenset::sethandler s, screenset::slothandler p) { return set_mapper().exec_set_function(s, p); } screenset::number set_playing_screenset (screenset::number setno); void reset_playset (); bool toggle_other_seqs (seq::number seqno, bool isshiftkey); bool toggle_other_names (seq::number seqno, bool isshiftkey); /** * Toggles sequences. Useful in perfnames, taken from perfnames :: * on_button_press_event() so that it can be re-used in qperfnames. */ bool toggle_sequences (seq::number seqno, bool isshiftkey) { return toggle_other_names(seqno, isshiftkey); } bool are_any_armed (); /* * This is a long-standing request from user's, adapted from Kepler34. */ bool song_recording () const { return m_song_recording; } bool song_record_snap () const { return m_song_record_snap; } midipulse record_snap_length () const { return m_record_snap_length; } alteration record_alteration () const { return m_record_alteration; } recordstyle record_style () const { return m_record_style; } bool resume_note_ons () const { return m_resume_note_ons; } void resume_note_ons (bool f) { m_resume_note_ons = f; } void select_triggers_in_range ( seq::number seqlow, seq::number seqhigh, midipulse tickstart, midipulse tickfinish ) { set_mapper().select_triggers_in_range ( seqlow, seqhigh, tickstart, tickfinish ); } void unselect_all_triggers () { set_mapper().unselect_triggers(); } public: bool looping () const { return m_looping; } void looping (bool looping) { m_looping = looping; } /** * Deals with the colors used to represent specific sequences. We don't * want performer knowing the details of the palette color, just treat it * as an integer. */ int color (seq::number seqno) const { return set_mapper().color(seqno); } bool set_color (seq::number seqno, int c); bool have_undo () const { return m_have_undo; } /** * \setter m_have_undo * Note that, if the \a undo parameter is true, then we mark the * performance as modified. Once it is set, it remains set, unless * cleared by saving the file. */ void set_have_undo (bool undo) { m_have_undo = undo; if (undo) modify(); } bool have_redo () const { return m_have_redo; } void set_have_redo (bool redo) { m_have_redo = redo; } const seq::pointer get_sequence (seq::number seqno) const; seq::pointer get_sequence (seq::number seqno); bool set_current_sequence(seq::number seqno); bool have_current_seq () const { return m_current_seqno != seq::unassigned(); } public: /* GUI-support functions */ /* * Deals with the editing mode of the specific sequence. */ sequence::editmode edit_mode (seq::number seqno) const { const seq::pointer sp = loop(seqno); return sp ? sp->edit_mode() : sequence::editmode::note ; } /* * This overload deals with the editing mode of the specific sequence, * but the seqeuence ID is replaced with a reference to the sequence * itself. */ sequence::editmode edit_mode (seq::cref s) const { return s.edit_mode(); } /** * A pass-along function to set the edit-mode of the given sequence. * Was private, but a class can have too many friends. * * \param seq * Provides the sequence number. If the sequence is not active * (available), then nothing is done. * * \param ed * Provides the edit mode, which is "note" or "drum", and which * determines if the duration of events matters (note) or not (drum). */ void edit_mode (seq::number seqno, sequence::editmode ed) { seq::pointer sp = loop(seqno); if (sp) sp->edit_mode(ed); } void edit_mode (seq::ref s, sequence::editmode ed) { s.edit_mode(ed); } /** * Returns the name text for the current screen-set. */ std::string current_screenset_name () const { return set_mapper().name(); } bool is_screenset_valid (screenset::number setno) const { return set_master().is_screenset_valid(setno); } /** * Tests to see if the screen-set is active. By "active", we mean that * the screen-set has at least one active pattern. * * \param screenset * The number of the screen-set to check, re 0. * * \return * Returns true if the screen-set has an active pattern. */ bool is_screenset_active (screenset::number setno) { return set_mapper().is_screenset_active(setno); } /** * Tests to see if the screen-set is available... does it exist? * Is it usable() [i.e. not a dummy screenset]? * * \param setno * The number of the screen-set to check, re 0. * * \return * Returns true if the screen-set is found in the set container. */ bool is_screenset_available (screenset::number setno) { return set_mapper().is_screenset_available(setno); } void screenset_name (const std::string & note) { set_mapper().name(note); } void screenset_name ( screenset::number setno, const std::string & note, bool is_load_modification = false ); std::string set_name (screenset::number setno) const { return set_mapper().name(setno); } bool seq_in_playing_screen (int seq) { return set_mapper().seq_in_playscreen(seq); } void song_recording (bool on, bool atstart = false); void song_record_snap (bool f) { m_song_record_snap = f; } void toggle_record_snap () { m_song_record_snap = ! m_song_record_snap; } void record_snap_length (midipulse snap) { m_record_snap_length = snap; } mutegroup::number group_selected () const { return mutes().group_selected(); } bool midi_mute_group_present () const { return mutes().group_present(); } bool is_group_learn () const { return mutes().is_group_learn(); } int group_count () const { return mutes().group_count(); } bool group_event () const { return mutes().group_event(); } bool group_error () const { return mutes().group_error(); } /** * group_mode() starts out true, and allows mute_group_tracks() to work. * It is set and unset via the "gmute" MIDI control and the group-on/off * keys. m_mode_group_learn starts out false, and is set and unset via the * "glearn" MIDI control and the group-learn press and release actions. */ bool group_mode () const { return mutes().group_mode(); } void group_mode (bool flag) { mutes().group_mode(flag); } void toggle_group_mode () { mutes().toggle_group_mode(); } bool set_beats_per_minute (midibpm bp, bool user_change = false); bool set_ppqn (int p); bool change_ppqn (int p); bool ui_change_set_bus (int b); private: void hidden (bool flag) /* only for GUI to call */ { m_hidden = flag; /* qt5nsmanager */ m_show_hide_pending = false; /* tricky code */ } bool calculate_snap (midipulse & tick); void show_cpu (); bool playlist_activate (bool on); void playlist_auto_arm (bool on); void playlist_auto_play (bool on); void append_error_message (const std::string & msg = "") const; bool set_quantized_recording (seq::number seqno, bool active, bool toggle); bool set_tightened_recording (seq::number seqno, bool active, bool toggle); bool set_overwrite_recording (seq::number seqno, bool active, bool toggle); bool set_thru (seq::number seqno, bool active, bool toggle); bool log_current_tempo (); bool create_master_bus (); void reset_sequences (bool pause = false); bool notemap_exists () const { return bool(m_note_mapper); } void copy_triggers () { set_mapper().copy_triggers(m_left_tick, m_right_tick); } bool move_triggers (bool direction); /** * Convenience function for perfedit's collapse functionality. */ void collapse () { push_trigger_undo(); move_triggers(false); modify(); } /** * Convenience function for perfedit's copy functionality. */ void copy () { push_trigger_undo(); copy_triggers(); } /** * Convenience function for perfedit's expand functionality. */ void expand () { push_trigger_undo(); move_triggers(true); modify(); } public: /* access functions for the containers */ const keycontainer & key_controls () const { return m_key_controls; } keycontainer & key_controls () { return m_key_controls; } bool midi_control_keystroke (const keystroke & k); bool midi_control_event (const event & ev, bool recording = false); void signal_save (); void signal_quit (); /* * Looks up the slot-key (hot-key) for the given pattern number. */ std::string lookup_slot_key (int seqno) const { return m_key_controls.slot_key(seqno % screenset_size()); } std::string lookup_mute_key (int mute_number) const { return m_key_controls.mute_key(mute_number); } const midicontrolin & midi_control_in () const { return m_midi_control_in; } midicontrolin & midi_control_in () { return m_midi_control_in; } automation::ctrlstatus ctrl_status () const { return midi_control_in().status(); } bool has_ctrl_status () const { return midi_control_in().is_status(); } std::string ctrl_status_string () const { return midi_control_in().status_string(); } /* * Start of mute-groups accessors. */ int mutegroup_count () const { return mutes().count(); } std::string group_name (mutegroup::number group) const { std::string name{"None"}; if (group != mutegroup::unassigned()) name = mutes().group_name(group); return name; } bool group_name (mutegroup::number gmute, const std::string & n); bool group_format_hex () const { return mutes().group_format_hex(); } void group_format_hex (bool flag); bool group_save (bool bmidi, bool bmutes); bool group_save_to_midi () const { return mutes().group_save_to_midi(); } bool group_load_from_midi () const { return mutes().group_load_from_midi(); } bool group_load_from_mutes () const { return mutes().group_load_from_mutes(); } bool group_save_to_mutes () const { return mutes().group_save_to_mutes(); } bool strip_empty () const { return mutes().strip_empty(); } bool strip_empty (bool flag); const mutegroups & mutes () const { return m_mute_groups; } mutegroups & mutes () { return m_mute_groups; } /* * Called in qsmainwnd. Will call performer::modify() if * mutes().reset_defauilts() succeeds. */ bool clear_mute_groups (); bool reset_mute_groups () /* see clear_mutes() */ { return mutes().reset_defaults(); } private: void clear_snapshot () { set_mapper().clear_snapshot(); } void save_snapshot () { set_mapper().save_snapshot(); } void restore_snapshot () { set_mapper().restore_snapshot(); } void is_running (bool flag) { m_is_running = flag; } private: void output_func (); void input_func (); bool poll_cycle (); void launch_input_thread (); void launch_output_thread (); void midi_start (); void midi_continue (); void midi_stop (); void midi_clock (); void midi_song_pos (const event & ev); void midi_sysex (const event & ev); bool start_count_in (); bool finish_count_in (); synch & cv () { return m_condition_var; } private: void show_key_error (const keystroke & k, const std::string & tag); static void print_parameters ( const std::string & tag, automation::action a, int d0, int d1, int index, bool inverse ); static bool changed (change mod) { return mod == change::yes || mod == change::removed; } public: bool signalled_changes () const { return m_signalled_changes; } void clear_seq_edits (); void toggle_seq_edit (); void toggle_event_edit (); void toggle_record_edit (); bool seq_edit_pending () const { return m_seq_edit_pending; } bool event_edit_pending () const { return m_event_edit_pending; } bool record_toggle_pending () const { return m_event_edit_pending; } bool call_seq_edits () const { return m_seq_edit_pending || m_event_edit_pending || m_record_toggle_pending; } seq::number pending_loop () const { return m_pending_loop; } void pending_loop (seq::number n) const { m_pending_loop = n; } int slot_shift () const { return m_slot_shift; } int increment_slot_shift (); // const; void clear_slot_shift (); // const; bool hidden () const { return m_hidden; } bool show_hide_pending () const { return m_show_hide_pending; } /* * This is just a very fast check meant for use in some GUI timers. */ bool got_seqno (seq::number & s) const { bool result = seq::assigned(pending_loop()); if (result) s = pending_loop(); return result; } void next_record_style (); void previous_record_style (); void next_record_alteration (); void previous_record_alteration (); void set_record_alteration (alteration rm); bool loop_control /* [loop-control] */ ( automation::action a, int d0, int d1, int index, bool inverse ); bool mute_group_control /* [mute-group-control] */ ( automation::action a, int d0, int d1, int index, bool inverse ); bool populate_default_ops (); bool add_automation /* [automation-control] */ ( automation::slot s, automation_function f ); bool automation_no_op ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_bpm_up_dn ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_bpm_dn ( automation::action a,int d0, int d1, int index, bool inverse ); bool automation_ss_up_dn ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_ss_dn ( automation::action a,int d0, int d1, int index, bool inverse ); bool automation_replace ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_snapshot ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_queue ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_gmute ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_glearn ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_play_ss ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_playback ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_song_record ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_solo ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_thru ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_bpm_page_up_dn ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_bpm_page_dn ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_ss_set ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_record_style ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_quan_record ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_reset_sets ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_oneshot ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_FF ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_rewind ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_top ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_playlist ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_playlist_song ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_tap_bpm ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_start ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_stop ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_looping ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_toggle_mutes ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_song_pointer ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_keep_queue ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_edit_pending ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_event_pending ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_slot_shift ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_mutes_clear ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_quit ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_song_mode ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_toggle_jack ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_menu_mode ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_follow_transport ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_panic ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_visibility ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_save_session ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_record_toggle ( automation::action a, int d0, int d1, int index, bool inverse ); void set_record_style (recordstyle rs); bool automation_record_style_select ( automation::action a, int d0, int d1, int index, bool inverse ); void set_grid_mode (gridmode gm); bool automation_grid_mode ( automation::action a, int d0, int d1, int index, bool inverse ); void set_grid_quant (alteration q); bool automation_grid_quant ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_bbt_hms ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_LR_loop ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_undo ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_redo ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_copy_set ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_paste_set ( automation::action a, int d0, int d1, int index, bool inverse ); bool automation_set_mode ( automation::action a, int d0, int d1, int index, bool inverse ); }; // class performer } // namespace seq66 #endif // SEQ66_PERFORMER_HPP /* * performer.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/playlist.hpp ================================================ #if ! defined SEQ66_PLAYLIST_HPP #define SEQ66_PLAYLIST_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file playlist.hpp * * This module declares/defines the base class for a playlist file and * a playlist manager. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-08-26 * \updates 2025-06-16 * \license GNU GPLv2 or above * * \todo * Add filepath to BAD playlist message. */ #include /* std::map<> */ #include "cfg/basesettings.hpp" /* seq66::basesettings class */ namespace seq66 { class performer; /** * Provides a file for reading and writing the application' main * configuration file. The settings that are passed around are provided * or used by the performer class. */ class playlist final : public basesettings { friend class performer; friend class playlistfile; friend class qplaylistframe; public: enum class action { next_list, next_song, none, previous_song, previous_list, max }; private: /** * Holds an entry describing a song, to be used as the "second" in a map. * Also holds a copy of the key value. Do we want the user to be able to * specify a title for the tune? */ struct song_spec_t { /** * Provides an ordinal value that indicates the offset of the song in * the list. */ int ss_index; /** * Provides a copy of the key, which is the MIDI control number that * the user has applied to this song in the playlist. */ int ss_midi_number; /** * The directory where the song is located, either the default (base) * directory specified in the playlist, or the path specification that * existed in the file-name of the song. */ std::string ss_song_directory; /** * If true, then ss_song_directory was actually part of the name of * the song file, rather than being specified by * play_list_t::ls_file_directory. */ bool ss_embedded_song_directory; /** * The base file-name, of the form "base.ext". When appended to * ss_song_directory, this yields the full path to the file. */ std::string ss_filename; }; /** * A type for holding a numerically-ordered list of songs, each * represented by a song_spec_t structure. The key value is an integer * representing the MIDI control number that can call up the song. */ using song_list = std::map; /** * Holds a playlist list entry to be used as the "second" in a map. * Also holds a copy of the key value. */ struct play_list_t { /** * Provides an ordinal value that indicates the offset of the * playlist in the play-list file. */ int ls_index; /** * Provides a copy of the key, which is the MIDI control number that * the user has applied to this playlist in the play-list file. */ int ls_midi_number; /** * Provides the human name for the playlist, it's meaningful title. */ std::string ls_list_name; /** * The default directory where each song in the playlist is located. * If there is a path specification that exists in the file-name of a * given song, that overrides this directory name. */ std::string ls_file_directory; /** * A quick way to get the number of songs in this playlist. */ int ls_song_count; /** * A container holding the list of information for the songs in the * playlist. */ song_list ls_song_list; }; /** * A type for holding a numerically ordered list of playlists, each * represent by a play_list_t structure. The key value is an integer * representing the MIDI control number that can call up the play-list. */ using play_list = std::map; private: /** * Provides an empty map to use to access the end() function. */ static song_list sm_dummy; /** * Holds a pointer to the performer for this playlist. It is not owned * by this playlist, and is used only if it is not null. */ performer * m_performer; /** * The list of playlists. */ play_list m_play_lists; /** * Indicates if we are in playlist mode. Only true if the user specified * a valid playlist file that was successfully loaded, or if the playlist * was created in the user-interface */ bool m_loaded; /** * Indicates if we want to deep-verify the playlist, which means that * each MIDI file in the list is opened. This can be time-consuming. * Also called a "strong" verify. */ bool m_deep_verify; /** * Provides an iterator to the current playlist. If valid, it provides * access to the name of the playlist, its file-directory, and its list * of songs. */ play_list::iterator m_current_list; /** * Provides an iterator to the current song. It can only be valid if the * current playlist is valid, otherwise it "points" to a static empty * song structure, sm_dummy. If valid, it provides * access to the file-name for the song and its file-directory. */ song_list::iterator m_current_song; /** * If true, indicates that the current set (or all via "F8"?) be turned * on immediately, rather than depending on the musician to unmute the * various patterns. This is an option stored in the playlist file. * Also, this option is superceded if the newly-loaded tune has a * Song layout. */ bool m_auto_arm; /** * If true, the next song will start playing immediately. */ bool m_auto_play; /** * This value controls whether auto-play (in a playlist) works or not. * We don't want play-list play-back to start immediately when Seq66 * starts... we want it to engage once the user presses Play. This * value starts out false. * * - Set to true when "Play" is activated the first time. * - The next song selected will start playing if playlist::auto_play() * is true and m_engage_auto_play is true. * - Set m_engage_auto_play to false: * - When File / Open is explicitly called. * - When "Stop" is enacted manually. */ bool m_engage_auto_play; /** * We are considering how to support automatically moving to the next song * in the play-list when the current song finishes. This might also * include moving to the next playlist, as well as automatic wrap-around. * See the performer class for usage. */ bool m_auto_advance; /** * If non-empty, this provides the base directory for all MIDI files in * all playlists. Sometimes we need this, for example when importing * into a new NSM session. */ std::string m_midi_base_directory; /** * If true, write the lists/songs to standard output. This is * useful to test the CLI/daemon version of Seq66. */ bool m_show_on_stdout; public: playlist ( performer * p, const std::string & filename = "", bool show_on_stdout = false ); playlist () = delete; playlist (const playlist &) = delete; playlist & operator = (const playlist &) = delete; playlist (playlist &&) = default; playlist & operator = (playlist &&) = default; virtual ~playlist (); static int action_to_int (action a) { return static_cast(a); } static action int_to_action (int i) { action result = static_cast(i); return result < action::max ? result : action::none ; } void show () const; void test (); bool loaded () const { return m_loaded; } void loaded (bool m) { m_loaded = m; } bool validated () const; bool activate (bool flag); bool active () const; bool deep_verify () const { return m_deep_verify; } void deep_verify (bool flag) { m_deep_verify = flag; } /* * ca 2025-06-16. * Check for the playlist being active as well, to avoid turning * on all patterns when not using the playlist. */ bool auto_arm () const { return active() && m_auto_arm; } void auto_arm (bool flag) { m_auto_arm = flag; } bool auto_play () const { return m_auto_play; } void auto_play (bool flag) { m_auto_play = flag; } bool engage_auto_play () const { return m_engage_auto_play; } void engage_auto_play (bool flag) { m_engage_auto_play = flag; } bool auto_play_engaged () const { return active() && auto_play() && engage_auto_play(); } void reengage_auto_play () { if (active() && auto_play()) engage_auto_play(true); } void disengage_auto_play () { if (active() && auto_play()) engage_auto_play(false); } bool auto_advance () const { return m_auto_advance; } bool auto_advance_engaged () const { return active() && auto_play() && auto_advance(); } void auto_advance (bool flag) { m_auto_advance = flag; } void midi_base_directory (const std::string & basedir); const std::string & midi_base_directory () const { return m_midi_base_directory; } int list_midi_number () const { return m_current_list != m_play_lists.end() ? m_current_list->second.ls_midi_number : (-1) ; } int list_index () const { return m_current_list != m_play_lists.end() ? m_current_list->second.ls_index : (-1) ; } std::string list_name () const { static std::string s_dummy; return m_current_list != m_play_lists.end() ? m_current_list->second.ls_list_name : s_dummy ; } int list_count () const { return int(m_play_lists.size()); } bool empty () const { return m_play_lists.empty(); } std::string file_directory () const; std::string song_directory () const; bool is_own_song_directory () const; int song_midi_number () const; int song_index () const; /* * Normally, play_list_t holds the name of the directory holding the * songs for the currently active playlist. All songs in a playlist must * be in the same directory. This is less flexible, but also a less * confusing way to organize tunes. * * However, if empty, every song in that playlist must specify the full * or relative path to the file. To represent this empty name in the * playlist, two consecutive double quotes are used. */ std::string song_filename () const; /* base-name, optional directory */ std::string song_filepath () const; /* for current song */ int song_count () const { return m_current_list != m_play_lists.end() ? m_current_list->second.ls_song_count : (-1) ; } std::string current_song () const; public: void clear (); bool reset_list (int listindex = 0, bool clearit = false); bool copy_songs (const std::string & destination); bool add_list ( int index, int midinumber, const std::string & name, const std::string & directory ); bool modify_list ( int index, int midinumber, const std::string & name, const std::string & directory ); bool remove_list (int index); bool select_list (int index, bool selectsong = false); bool select_list_by_midi (int ctrl, bool selectsong = false); bool next_list (bool selectsong = false); bool previous_list (bool selectsong = false); bool remove_song (int index); bool select_song (int index); bool select_song_by_midi (int ctrl); bool next_song (); bool previous_song (); int next_available_song_number () const; int next_available_list_number () const; bool open_song (const std::string & filename, bool verifymode = false); bool open_select_song (int index, bool opensong = true); bool open_select_song_by_midi (int ctrl, bool opensong = true); bool open_current_song (); bool open_next_list (bool opensong = true, bool loading = false); bool open_previous_list (bool opensong = true); bool open_select_list (int index, bool opensong = true); bool open_select_list_by_midi (int ctrl, bool opensong = true); bool open_next_song (bool opensong = true); bool open_previous_song (bool opensong = true); private: virtual bool set_error_message (const std::string & added) const override; /* * We want to hide the internal structures from the caller, except for the * performer and playlistfile. */ bool check_song_list (const play_list_t & plist); bool add_list (const play_list_t & plist); void show_list (const play_list_t & pl) const; std::string song_filepath (const song_spec_t & s) const; bool add_song (song_spec_t & sspec); bool add_song (song_list & slist, song_spec_t & sspec); bool add_song (play_list_t & plist, song_spec_t & sspec); bool add_song ( int index, int midinumber, const std::string & name, const std::string & directory ); bool add_song (const std::string & fullpath); bool modify_song ( int index, int midinumber, const std::string & name, const std::string & directory ); void last_song_indices (song_list & slist, int & index, int & midinumber); void show_song (const song_spec_t & pl) const; void reorder_play_list (); void reorder_song_list (song_list & sl); bool set_file_error_message ( const std::string & fmt, const std::string & filename ); bool verify (bool strong = false); play_list & play_list_map () { return m_play_lists; } bool do_ctrl_lookup (int ctrl) { return ctrl == (-1); } bool ctrl_is_valid (int ctrl) { return (ctrl >= 0 && ctrl < 128) || do_ctrl_lookup(ctrl); } }; // class playlist } // namespace seq66 #endif // SEQ66_PLAYLIST_HPP /* * playlist.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/portslist.hpp ================================================ #if ! defined SEQ66_PORTLIST_HPP #define SEQ66_PORTLIST_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file portslist.hpp * * An abstract base class for inputslist and clockslist. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-12-11 * \updates 2025-06-10 * \license GNU GPLv2 or above * * Defines the list of MIDI inputs and outputs (clocks). We've combined them * for "convenience". :-) Oh, and for port-mapping. */ #include /* std::string */ #include /* std::map */ #include "midi/midibus_common.hpp" /* enum class e_clock, etc. */ #include "midi/midibytes.hpp" /* bussbyte and other types */ namespace seq66 { /** * Indicates whether to use the short (internal) or the long (normal) * port names in visible user-interface elements. If there is no * internal map, this option is forced to "long". */ enum class portname { brief, /**< "short": Use short names: "[0] midi_out". */ pair, /**< "pair": Pair names: "36:0 fluidsynth:midi_out". */ full, /**< "long": Long names: "[0] 36:0 fluidsynth:midi_out". */ max /**< Keep this last... a size value. */ }; /** * A wrapper for a vector of clocks and inputs values, as used in * mastermidibus and the performer object. */ class portslist { friend std::string output_port_map_list (); friend std::string input_port_map_list (); public: /** * A boolean is not quite enough for activating, deactivating, and * deactivating and clearing a port list. */ enum class status { cleared, /**< Deactivate and clear the list. */ off, /**< Deactivate the list. */ on /**< Activate the list. */ }; public: /** * Provides a port name and the input or output values. Note that the * clock setting will be off (not disabled) for all input values. This * is so that we can disable missing inputs when port-mapping. The clock * setting will be disabled for output values that are actually disabled * by the user or are missing from the actual system ports. There is * also a static function valid() in portslist to check that the io_name * is not empty. */ using io = struct { bool io_available; /**< Portmapped-bus not present. */ bool io_enabled; /**< The status setting for this buss. */ e_clock out_clock; /**< Clock/disabled setting for buss. */ std::string io_name; /**< The name of the I/O buss. */ std::string io_nick_name; /**< The short name of the I/O buss. */ std::string io_alias; /**< FYI only, and only for JACK. */ int io_client_number; /**< The system client number. */ int io_port_number; /**< The system port number. */ }; protected: /** * The container type for io information. Replaces std::vector. */ using container = std::map; /** * Saves the input or clock settings obtained from the "rc" (options) * file so that they can be loaded into the mastermidibus once it is * created. */ container m_master_io; /** * Indicates if the list is to be used. It will always be saved and read, * but not used if this flag is false. */ bool m_is_active; /** * Indicates if this list is a port-mapper list. Useful in debugging. */ bool m_is_port_map; public: portslist (bool pmflag = false); virtual ~portslist () = default; virtual std::string io_list_lines () const = 0; virtual bool add_list_line (const std::string & line) = 0; virtual bool add_map_line (const std::string & line) = 0; static bool parse_port_line ( const std::string & line, int & portnumber, int & portstatus, std::string & portname ); static bool valid (const io & item); void match_system_to_map (portslist & destination) const; void match_map_to_system (const portslist & source); void clear () { m_master_io.clear(); } void activate (status s); int available_count () const; int count () const { return int(m_master_io.size()); } bool empty () const { return m_master_io.empty(); } bool active () const { return m_is_active && ! empty(); } bool is_port_map () const { return m_is_port_map; } void active (bool flag) { m_is_active = flag; } void set_name (bussbyte bus, const std::string & name); #if defined USE_SET_NICK_NAME void set_nick_name (bussbyte bus, const std::string & name); #endif void set_alias (bussbyte bus, const std::string & name); std::string get_name (bussbyte bus) const; std::string get_pair_name (bussbyte bus) const; std::string get_nick_name ( bussbyte bus, portname style = portname::brief ) const; std::string get_alias ( bussbyte bus, portname style = portname::brief ) const; std::string get_display_name (bussbyte bus, portname style) const; bussbyte bus_from_name (const std::string & nick) const; bussbyte bus_from_nick_name (const std::string & nick) const; bussbyte bus_from_alias (const std::string & alias) const; std::string port_name_from_bus (bussbyte nominalbuss) const; void show (const std::string & tag = "") const; bool set_enabled (bussbyte bus, bool enabled); bool is_available (bussbyte bus) const; bool is_enabled (bussbyte bus) const; bool is_disabled (bussbyte bus) const { return ! is_enabled(bus); } protected: container & master_io () { return m_master_io; } const container & master_io () const { return m_master_io; } std::string to_string (const std::string & tag = "") const; std::string extract_nickname (const std::string & name) const; bool extract_port_pair ( const std::string & name, int & client, int & port ) const; std::string e_clock_to_string (e_clock e) const; std::string port_map_list (bool isclock) const; std::string io_line ( int portnumber, int status, const std::string & portname, const std::string & portalias = "" ) const; bool add ( int buss, bool available, int status, const std::string & name, const std::string & nickname = "", const std::string & alias = "" ); bool add (int buss, io & ioitem, const std::string & nickname); const io & const_io_block (const std::string & nickname) const; io & io_block (const std::string & nickname) { return const_cast(const_io_block(nickname)); } }; // class portslist } // namespace seq66 #endif // SEQ66_PORTLIST_HPP /* * portslist.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/screenset.hpp ================================================ #if ! defined SEQ66_SCREENSET_HPP #define SEQ66_SCREENSET_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file screenset.hpp * * This module declares a small manager for a set of sequences, to be used by * the performer; it also provides a collection for active sequences, called * playset. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-02-12 * \updates 2024-11-17 * \license GNU GPLv2 or above * * This module also creates a small structure for managing sequence * variables, to save on a bunch of arrays. It manages screen-sets and * mute-groups. This module also supports the saved 'armed' statuses and the * current states of the tracks or sets. */ #include /* std::function, function objects */ #include /* std::vector<> */ #include "play/seq.hpp" /* seq66::seq extension class */ /** * We now think it is better to have all 32 possible sets in place, and * change add_set() to allow insertion of existing set-numbers. */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { class playset; /** * Holds the various statuses, including the pointer, for a single sequence * (also known as a loop or pattern). This small class consolidates data * once held in separate arrays. */ class screenset { friend class performer; friend class playset; friend class setmapper; friend class setmaster; public: /** * Provides a more recognizable alias for a screen-set number, * screenset::number. */ using number = int; /** * Provides a type alias for a function that can be called on all * sequences in a set. A caller will create this function and pass it to * the exec_slot_function() function. The value for the seq::number * parameter is provided by exec_slot_function(). * * A good example of a slothandler function is created in performer :: * announce_playscreen() by binding performer :: announce_sequence () to * place-holder parameters and then calling exec_slot_function(). */ using slothandler = std::function; /** * Provides a type alias for a function that can be called on a set. A * caller will create this function and pass it to the * exec_set_function() function. There are two variations of * exec_set_function(), one which just calls the sethandler on a set, and * one that calls a sethandler, and then calls a slothandler on each slot * in the set. * * A good example of a sethandler is done in qsetmaster :: * initialize_table(), which binds qsetmaster :: set_line() to * place-holder parameters. * * A lambda function is used in performer :: change_ppqn() and other * functions that set the values used in a set. */ using sethandler = std::function; /** * Default number of rows in the main-window's grid. This value applies * to the layout of the pattern and, by default, mute-group keystrokes, * as well as the virtual layout of sets into rows and columns. */ static const int c_default_rows = 4; /** * Minimum number of rows in the main-window's grid. This will remain * the same as the default number of rows; we will not reduce the number * of sequences per set, at least at this time. */ static const int c_min_rows = 4; /** * Maximum number of rows in the main-window's grid. With the default * number of columns, this will triple the number of sequences per set * from 32 to 64. */ static const int c_max_rows = 12; /* that is, 4 * 3 */ /** * Default number of columns in the main-window's grid. */ static const int c_default_columns = 8; /** * Minimum number of columns in the main-window's grid. Currently the * same as the default number. We currently cannot support more sets * than 32, which would happen if we let rows or columns go below the * default 4 x 8 settings. */ static const int c_min_columns = 4; /** * Maximum number of columns in the main-window's grid. Currently the * same as the default number. */ static const int c_max_columns = 12; private: /** * Provides an alias for a vector of seq objects. The "key" is an * integer which is the sequence number, and is basically an array index. * The value is a seq object representing sequence. This container holds * both inactive and active slots/sequences. It is a vector because it * is cheaper to hold empty slots than it is in a map. */ using container = std::vector; private: /** * Indicates that no set number has been assigned. All valid set numbers * are greater than or equal to 0. See the unassigned() function. */ static const int sm_number_none = (-1); /** * Indicates the number of virtual rows in a screen-set (bank), which is * also the same number of virtual rows as a mute-group. This value will * generally be the same as the size used in the rest of the application. * The default value is the historical value of 4 rows per set or * mute-group. It can be mapped against a sequence number. Note that we * removed the const qualifier, as this causes issues with containers. */ int m_rows; /** * Indicates the number of virtual columns in a screen-set (bank), which * is also the same number of virtual columns as a mute-group. This * value will generally be the same as the size used in the rest of the * application. The default value is the historical value of 8 columns * per set or mute-group. It can be mapped against a sequence number. */ int m_columns; /** * Experimental option to swap rows and columns. See the function * swap_coordinates(). This swap doesn't apply to the number of rows and * columns, but to whether incrementing the sequence number moves to the * next or othe next column. */ bool m_swap_coordinates; /** * Indicates the size of a screenset, equivalent to the rows x columns * measurement. In this map implementation, it is not pre-allocated. */ int m_set_size; /** * Holds a generally sparse vector of seq objects. */ container m_container; /** * Indicates the the set (bank) number represented by this screenset * object. If set to sm_number_none, this screenset is not active. */ number m_set_number; /** * Indicates the screen-set offset (the number of the first loop/pattern * in the screen-set). This value is m_set_size * m_screenset_number. * This saves a calculation. */ seq::number m_set_offset; /** * Indicates a number one above the maximum sequence number for this * screenset. Saves a calculation. */ seq::number m_set_maximum; /** * Holds the text/name for this screenset. */ std::string m_set_name; /** * Is this screenset the current play-screen? Managed by the setmapper. */ bool m_is_playscreen; /** * Indicates the highest sequence number, plus 1, for this screenset. */ mutable seq::number m_sequence_high; public: screenset () = delete; screenset ( number setnum, int rows = c_default_rows, int columns = c_default_columns ); /** * The move and copy constructors, the move and copy assignment operators, * and the destructors are all compiler generated. */ screenset (const screenset &) = default; screenset & operator = (const screenset &) = default; screenset (screenset &&) = default; screenset & operator = (screenset &&) = default; ~screenset () = default; static number limit () { return seq::limit(); } /** * Indicates that a set number has not been assigned. */ static number unassigned () { return sm_number_none; } static bool is_unassigned (number n) { return n == sm_number_none; } bool is_unassigned () const { return m_set_number == sm_number_none; } bool dummy () const { return m_set_number == limit(); } bool usable () const { return ! is_unassigned() && ! dummy(); } int set_size () const { return m_set_size; } seq::number offset () const { return m_set_offset; } int sequence_high () const { return m_sequence_high; } int rows () const { return m_rows; } int columns () const { return m_columns; } bool swap_coordinates () const { return m_swap_coordinates; } int count () const { return int(m_container.size()); } screenset::number set_number () const { return m_set_number; } void change_set_number (screenset::number setno); const std::string & name () const { return m_set_name; } bool is_playscreen () const { return m_is_playscreen; } bool active () const; int active_count () const; seq::number first_seq () const; /** * Gets the desired sequence / loop / pattern / track pointer. * A set may be newly created, and have no sequences. * * \param seqno * Provides the index to the sequence (normally from 0 to 1023). * Is it worth validating this parameter? We want speed. * But validity is enforced by the seqinfo() function. */ seq::pointer loop (seq::number seqno) { return seqinfo(seqno).loop(); } const seq::pointer loop (seq::number seqno) const { return seqinfo(seqno).loop(); } int color (seq::number seqno) const { const seq::pointer track = seqinfo(seqno).loop(); return track ? track->color() : (-1) ; } bool active (seq::number seqno) const { return seqinfo(seqno).active(); } bool empty (seq::number seqno) const { return seqinfo(seqno).empty(); } bool recording (seq::number seqno) const { return seqinfo(seqno).recording(); } bool is_seq_in_edit (seq::number seqno) const; bool any_in_edit () const; bool is_exportable (seq::number seqno) const { return seqinfo(seqno).is_exportable(); } bool is_dirty_main (seq::number seqno) const { return seqinfo(seqno).is_dirty_main(); } bool is_dirty_edit (seq::number seqno) const { return seqinfo(seqno).is_dirty_edit(); } bool is_dirty_perf (seq::number seqno) const { return seqinfo(seqno).is_dirty_perf(); } bool is_dirty_names (seq::number seqno) const { return seqinfo(seqno).is_dirty_names(); } void activate (seq::number slotnum, seq::number seqno, bool flag = true) { seqinfo(slotnum).activate(seqno, flag); } bool armed () const; bool armed (seq::number seqno) const { const seq::pointer track = seqinfo(seqno).loop(); return track ? track->armed() : false ; } bool armed_status (seq::number seqno) const { const seq & s = seqinfo(seqno); return s.active() ? s.armed_status() : false ; } bool muted (seq::number seqno) const { return ! armed(seqno); } bool seq_in_set (seq::number seqno) const { return seqno >= m_set_offset && seqno < m_set_maximum; } seq::number grid_to_index (int row, int column) const; seq::number grid_to_seq (int row, int column) const; seq::number grid_to_seq ( screenset::number setno, int row, int column ) const; bool seq_to_grid ( seq::number seqno, int & row, int & column, bool global = false ) const; bool index_to_grid (seq::number seqno, int & row, int & column) const; bool needs_update () const; /* * exec_set_function(s, index) runs a set-handler with the two arguments. * exec_set_function(s, p) runs a set-handler, then calls * exec_slot_function(). exec_slot_function(p) runs a slot-handler for * all slots in this set. */ bool exec_set_function (sethandler s, screenset::number index) { return s(*this, index); } bool exec_set_function (sethandler s, slothandler p); bool exec_slot_function (slothandler p, bool use_set_offset = true); private: seq::number clamp (seq::number seqno) const; seq::pointer find_by_number (seq::number seqno); bool fill_play_set (playset & p, bool clearit = true); bool add_to_play_set (playset & p, seq::number seqno); seq::number play_seq (seq::number seqno); void off_sequences (seq::number seqno = seq::unassigned()); void song_recording_start (midipulse current_tick, bool snap = true); void song_recording_stop (midipulse current_tick); void clear_snapshot (); void save_snapshot (); void restore_snapshot (); void set_last_ticks (midipulse tick); bool copy_patterns (const screenset & source); int trigger_count () const; midipulse max_trigger () const; midipulse max_timestamp () const; midipulse max_extent () const; void unselect_triggers (seq::number seqno = seq::all()); void select_triggers_in_range ( seq::number seqlow, seq::number seqhigh, midipulse tick_start, midipulse tick_finish ); bool move_triggers ( midipulse lefttick, midipulse distance, bool direction, seq::number seqno = seq::all() ); void copy_triggers ( midipulse lefttick, midipulse distance, seq::number seqno = seq::all() ); void push_trigger_undo (); void pop_trigger_undo (); void pop_trigger_redo (); bool apply_bits (const midibooleans & mg); bool learn_bits (midibooleans & mg); /* * For a non-existent sequence number, should this return a dummy (inactive) * object reference, or create an empty seq? Or should it throw? */ const seq & seqinfo (seq::number seqno) const { return m_container.at(clamp(seqno)); } private: bool add (sequence *, seq::number & seqno); bool remove (seq::number seqno); #if defined SEQ66_USE_SCREENSET_RESET_SEQUENCES /* currently unused */ void reset_sequences (bool pause, sequence::playback mode); #endif bool any_modified_sequences () const; void unmodify_all_sequences (); void set_dirty (seq::number seqno = seq::all()); void toggle (seq::number seqno = seq::all()); void toggle_song_mute (seq::number seqno = seq::all()); void arm (); void mute (); void apply_armed_statuses (); bool learn_armed_statuses (); void apply_song_transpose (seq::number seqno = seq::all()); void sequence_playing_change (seq::number seqno, bool on, bool qinprogress); void save_queued (seq::number repseq); // save_current_screenset () void unqueue (seq::number hotseq); void clear (); void initialize (int rows, int columns); std::string to_string (bool showseqs = true, int limit = 0) const; void show (bool showseqs = true) const; void play (midipulse tick, sequence::playback mode, bool resumenoteons); bool color (seq::number seqno, int c); void set_seq_name (seq::number seqno, const std::string & name); bool name (const std::string & nm); const container & seq_container () const { return m_container; } container & seq_container () { return m_container; } void is_playscreen (bool flag) { m_is_playscreen = flag; } seq & seqinfo (seq::number seqno) { return m_container.at(clamp(seqno)); } void panic () { all_notes_off(); } void armed_status (seq::number seqno, bool flag); void armed (seq::number seqno, bool flag); void arm (seq::number seqno); void mute (seq::number seqno); void all_notes_off (); }; // class screenset /** * Provides a class for managing screenset seqences. */ class playset { /** * Encapsulates a raw pointer, not owned by the playset object. */ using pointer = const screenset *; /** * Holds a list of the sets included in the playset, so that they will be * included only once when filling the play list. This object does not * own the pointers. */ using sets = std::map; /** * Provides a type to support condensing the screenset into a smaller * array for use by the performer, as a bit of optimization. */ using array = std::vector; /** * Holds the list of screensets in the playset. */ sets m_screen_sets; /** * Holds the list of active sequences in the playset. */ array m_sequence_array; public: playset (); /* * The move and copy constructors, the move and copy assignment operators, * and the destructors are all compiler generated. */ playset (const playset &) = default; playset & operator = (const playset &) = default; playset (playset &&) = default; playset & operator = (playset &&) = default; ~playset () = default; void clear () { m_screen_sets.clear(); m_sequence_array.clear(); } /* * Stupid GDB says "cannot evaluate, may be inlined". */ int set_count () const { return int(m_screen_sets.size()); } int seq_count () const { return int(m_sequence_array.size()); } int count () const; const array & seq_container () const { return m_sequence_array; } array & seq_container () { return m_sequence_array; } bool set_found (screenset::number setno) const; bool fill (const screenset & sset, bool clearit = true); bool add (const screenset & sset, seq::number seqno); bool add (seq::pointer sp); void remove (seq::number seqno); std::string to_string () const; }; // class playset } // namespace seq66 #endif // SEQ66_SCREENSET_HPP /* * screenset.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/seq.hpp ================================================ #if ! defined SEQ66_SEQ_HPP #define SEQ66_SEQ_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file seq.hpp * * This module declares a small manager for a set of sequences, to be used by * the performer. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-02-12 * \updates 2024-11-17 * \license GNU GPLv2 or above * * This module also creates a small structure for managing sequence * variables, to save on a bunch of arrays. It adds the extra information * about sequences that was formerly provided by separate arrays. * * Special static test functions: * * - maximum(). Returns the maximum supported usable sequence * number (plus one), which is 1024, but could be increased to 2048. To * clarify, usable sequence numbers range from 0 to 1023 at present. * - limit(). Returns 2048 (0x0800), which indicates a legal value that * represents "no background" sequence when present in a Sequencer66 MIDI * file. * - legal(seqno). Returns true if the number is between 0 and 2048. * - valid(seqno). Returns true if the number is between 0 and 2047. * - none(seqno). Returns true if the sequence number is -1. * - disabled(seqno). Return true if the sequence number is limit(). * - null(seqno). * - all(seqno). Returns true if the sequence number is -1. To be used * only in the context of functions and can work on one sequence or all * of them. The caller should pass sequence::unassigned() as the * sequence number. * - unassigned(). Returns the value of -1 for sequence number. */ #include /* std::shared_ptr<> */ #include /* std::map<> */ #include "play/sequence.hpp" /* seq66::sequence */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /** * Holds the various additional statuses, including the pointer, for a single * sequence (also known as a loop or pattern). This small class consolidates * data once held in separate arrays. It is also generally meant to be * private, to be used only by the screenset class. However, simple * accessors, the seq::pointer alias for a shared pointer, and the seq::ref * and seq::cref reference aliases are public. Also, the destructor * definitely must be public, otherwise there can be an error concerning * static_assert::is_destructible. */ class seq { friend class performer; friend class playset; friend class screenset; friend class setmapper; public: /** * Provides a more descriptive alias for the sequences numbers (which * range from 0 to the maximum sequence number allowed for a given run * of the application. * * Note that this could be a short, but for some int references * parameters in some functions. */ using number = int; /** * Provides public access to the shared pointer for a sequence. No more * raw pointers! It cannot be a unique_ptr<> because m_seq needs to be * returned to callers. */ using pointer = std::shared_ptr; /** * References to sequence objects. */ using ref = sequence &; using cref = const sequence &; private: /** * Provides a smart pointer to a pattern/sequence/loop. */ pointer m_seq; /** * Each boolean value in this array is set to true if a sequence is * active, meaning that it will be used to hold some kind of MIDI data, * even if only Meta events. This array can have "holes" with inactive * sequences, so every sequence needs to be checked before using it. * This flag will be true only if the sequence pointer is not null and if * the sequence potentially contains some MIDI data. */ bool m_seq_active; /** * Each boolean value in this array is set to true if a sequence was * active, meaning that it was found to be active at the time we were * setting it to inactive. This value seems to be used only in * maintaining dirtiness-status; did some process modify the sequence? * Was it's mute/unmute status changed? */ mutable bool m_was_active_main; /** * Each boolean value in this array is set to true if a sequence was * active, meaning that it was found to be active at the time we were * setting it to inactive. This value seems to be used only in * maintaining dirtiness-status for editing the mute/unmute status during * pattern editing. */ mutable bool m_was_active_edit; /** * Each boolean value in this array is set to true if a sequence was * active, meaning that it was found to be active at the time we were * setting it to inactive. This value seems to be used only in * maintaining dirtiness-status for editing the mute/unmute status during * performance/song editing. */ mutable bool m_was_active_perf; /** * Each boolean value in this array is set to true if a sequence was * active, meaning that it was found to be active at the time we were * setting it to inactive. This value seems to be used only in * maintaining dirtiness-status for editing the mute/unmute status during * performance names editing. Not sure that it serves a real purpose; * perhaps created with an eye to editing the pattern name in the song * editor? */ mutable bool m_was_active_names; /** * Indicates the status of this sequence when the arming statuses of all * sequences have been saved for later restoration. Used by * save_playing_state() and restore_playing_state() for handling the * snapshot functionality. Meant to be used for all existing sequences. */ bool m_snapshot_status; /** * Indicates the status of this sequence when the arming statuses of all * sequences have been saved for later restoration. Used by * toggle_playing_tracks(). Meant to be used for all existing sequences. */ bool m_armed_status; /** * Saves the current playing state only for the current set. * This is used in the new queue-replace (queue-solo) feature. */ bool m_queued; public: /** * Most of these functions are compiler generated. */ seq (); seq (const seq &) = default; seq & operator = (const seq &) = default; seq (seq &&) = default; seq & operator = (seq &&) = default; ~seq (); /** * The maximum number of patterns supported is given by the number of * patterns supported in the panel (32) times the maximum number of sets * (32), or 1024 patterns. However, this value is now independent of the * maximum number of sets and the number of sequences in a set. Instead, * we limit them to a constant value, which seems to be well above the * number of simultaneous playing sequences the application can support. * Based on trials, the b4uacuse-stress.midi file, which has only about 4 * sets (128 patterns) pretty much loads up a CPU. Based on * seq::limit(), we can have patterns ranging from 0 to 2047. For * testing right now, we leave the old limit in place. */ static int maximum () { return sequence::maximum(); /* 1024 */ } /** * A pattern number that indicates the pattern is to be used as a * metronome. */ static number metronome () { return sequence::metronome(); /* 2047 */ } /** * The limiting sequence number, in macro form. This value indicates * that no background sequence value has been assigned yet. However, we * have issues saving a negative number in MIDI, so we will use the * "proprietary" track's bogus sequence number, which doubles the 1024 * sequences we can support. Values between 0 (inclusive) and 2048 * (exclusive) are valid. But 2048 is a legal value, used only * for disabling the selection of a background sequence. */ static number limit () { return sequence::limit(); /* 2048 */ /* 0x0800 */ } /** * Indicates that a sequence number has not been assigned. */ static number unassigned () { return sequence::unassigned(); /* (-1) */ } /** * Indicates that all patterns will be processed by a function taking a * seq::number parameter. */ static number all () { return (-2); } /** * A convenient macro function to test against limit(). Although above * the range of usable loop numbers, it is a legal value. * Compare this to the valid() function. */ static bool legal (int seqno) { return seqno >= 0 && seqno <= limit(); } /** * Checks if a the sequence number is an assigned one, i.e. not equal to * -1. Replaces the null() function. */ static bool unassigned (number seqno) { return seqno == unassigned(); } static bool assigned (number seqno) { return seqno != unassigned(); } /** * Similar to legal(), but excludes limit(). */ static bool valid (number seqno) { return seqno >= 0 && seqno < maximum(); } /** * A convenient function to test against sequence::limit(). * This function does not allow that value as a valid value to use. */ static bool disabled (number seqno) { return seqno == limit(); } const pointer loop () const { return m_seq; } pointer loop () { return m_seq; } /** * Checks if the sequence has been properly installed via the performer. * Since we can have holes in the sequence "array", where there are * inactive sequences, we check if the sequence is even active before * emitting a message about a null pointer for the sequence. We only * want to see messages that indicate actual problems. * * \return * Returns true if the m_seq_active flag is set. */ bool active () const { return m_seq_active; } bool empty () const { return m_seq_active ? m_seq->empty() : true ; } bool recording () const { return m_seq_active && m_seq->recording(); } bool armed_status () const { return m_seq_active && m_armed_status; } bool queued () const { return m_queued; } void set_was_active (); void clear_snapshot () { m_snapshot_status = false; } void save_snapshot () { m_snapshot_status = active () ? m_seq->armed() : false ; } void restore_snapshot () { if (active()) m_seq->set_armed(m_snapshot_status); } seq::number seq_number () const { return active() ? seq::number(m_seq->seq_number()) : seq::unassigned() ; } private: bool activate (sequence * s, number seqno); bool activate (number seqno, bool active = true); bool deactivate (); bool is_exportable () const; bool is_dirty_main () const; bool is_dirty_edit () const; bool is_dirty_perf () const; bool is_dirty_names () const; void sequence_playing_change (bool on, bool q_in_progress); std::string to_string (int index) const; void show (int index = 0) const; void change_seq_number (seq::number seqno) { if (active()) m_seq->seq_number(seqno); } void armed_status (bool flag) { m_armed_status = flag; } void queued (bool flag) { m_queued = flag; } }; // class seq } // namespace seq66 #endif // SEQ66_SEQ_HPP /* * seq.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/sequence.hpp ================================================ #if ! defined SEQ66_SEQUENCE_HPP #define SEQ66_SEQUENCE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file sequence.hpp * * This module declares/defines the base class for handling * patterns/sequences. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-30 * \updates 2025-10-22 * \license GNU GPLv2 or above * * The functions add_list_var() and add_long_list() have been replaced by * functions in the new midi_vector_base module. * * We've offloaded most of the trigger code to the triggers class in its own * module, and now just call its member functions to do the actual work. */ #include /* std::atomic for dirt */ #include /* std::stack */ #include /* std::string */ #include "seq66_features.hpp" /* various feature #defines */ #include "cfg/usrsettings.hpp" /* enum class record */ #include "ctrl/midimacro.hpp" /* seq66::midimacro */ #include "midi/calculations.hpp" /* seq66::lengthfix, alteration */ #include "midi/eventlist.hpp" /* seq66::eventlist */ #include "play/triggers.hpp" /* seq66::triggers, etc. */ #include "util/automutex.hpp" /* seq66::recmutex, automutex */ /** * Provides an integer value for color that matches PaletteColor::none. That * is, no color has been assigned. Track colors are represent by a plain * integer in the seq66::sequence class. */ const int c_seq_color_none = (-1); namespace seq66 { class mastermidibus; class notemapper; class performer; /** * Provides a way to save a sequence palette color in a single byte. This * value is signed since we need a value of -1 to indicate no color, and 0 to * 127 to indicate the index that "points" to a palette color. (The actual * limit is currently 31, though, which ought to be enough colors.) */ using colorbyte = char; /** * A structure for encapsulating the many input parameters of sequence :: * fix_pattern(). Also serves as an output to describe exactly what * happened with the calculations. * * Must be created using an initializer list. * * \var fp_fix_type * Indicates if the length of the pattern is to be affected, either by * setting the number of measures, or by scaling the pattern. In either * of those cases, the timestamps of all events will be adjusted * accordingly. * w \var fp_alter_type * Indicates how all events are to be altered, such as being tightened, * quantized, note-mapping, etc. * * \var fp_length * Indicates to set the pattern length to a specified value, in ticks. * * \var fp_tighten_range * Set a range for tightening (partial quantization) of the pattern's * events. * * \var fp_random_range * Set a range for randomization of events. Randomize velocity for notes. * * \var fp_pitch_range * Set a range for randomization of note-event pitches. * * \var fp_quantize_range * Set a range for full quantization) of the pattern's events. * * \var fp_jitter_range * Set a range, in MIDI ticks, for "humanizing" a pattern. * * \var fp_align_left * Indicates if the offset of the first event or, preferably first note * event, is to be adjusted to 0, shifting all events leftward by the same * ammount of time. * * \var fp_align_right * The opposite of fp_align_right. * * \var fp_reverse * Reverses the timestamps of event, while preserving the duration of the * notes. The new timestamp is the distance of the event from the end * (length) of the pattern, which we call the "reference". * * \var fp_reverse_in_place * Similar to fp_reverse, except that the last event is used as the * "reference" (instead of the pattern length). * * \var fp_save_note_length * If true, do not scale the note-off timestamps. Keep them at the same * offset against the linked note-on event. * * \var fp_use_time_signature * If true, try to alter the time signature. This occurs if the measures * string is a fraction (e.g. "3/4" or "5/4"). * * \var fp_beats_per_bar * If fp_use_time_signature is true, then this value is assumed to be the * (possibly new) beats per bar. * * \var fp_beat_width * If fp_use_time_signature is true, then this value is assumed to be the * (possibly new) beat width. * * \var [inout] fp_measures * The final length of the pattern, Ignored if the fix_type is not * lengthfix::measures, but the new bar count is returned here for * display purposes. * * \var [inout] fp_scale_factor * The factor used to change the length of the pattern, Ignored if the * fix_type is not lengthfix::rescale. Sanity checked to not too small, * not too large, and not 0. Might be changed according to process, so * that the final value can be displayed. * * \var fp_notemap_file * Provides the name of the note-map file to use to re-map notes. * * \var fp_reverse_notemap * Re-map notes in the other directions * * \var [out] fp_effect * Indicate the effect(s) of the change, using the fixeffect enumeration * in the calculations module. */ struct fixparameters { lengthfix fp_fix_type; alteration fp_alter_type; midipulse fp_length; int fp_tighten_range; int fp_quantize_range; int fp_random_range; int fp_pitch_range; int fp_jitter_range; bool fp_align_left; bool fp_align_right; bool fp_reverse; bool fp_reverse_in_place; bool fp_save_note_length; bool fp_use_time_signature; int fp_beats_per_bar; int fp_beat_width; double fp_measures; double fp_scale_factor; std::string fp_notemap_file; bool fp_reverse_notemap; fixeffect fp_effect; }; /** * A structure for encapsulating the input parameters of sequence :: * change_event_data_lfo(). * * \var lfo_dc_offset * Provides the "DC" value to be added to the waveform. Ranges from * 0 to 127. * * \var lfo_range * Provides the range of the function, that is, its lowest value and * its highest value. Ranges from 0 to 127. * * \var lfo_periods * Also known as the "range". It provides the number of periods of * the waveform to apply over the given duration (which is one * measure or the whole pattern length). Ranges from 0 to 16. * * \var lfo_phase * The starting phase of the waveform. Vaires from 0.0 to 1.0, * which corresponds to a range of 0 to 360 degrees. * * \var lfo_waveform * The waveform to be applied. See "enum class waveform" in * the calculations.hpp module. * * \var lfo_use_measure * If true (the normal case) the duration of a period of the * waveform is one measure. If false, then the duration is * the whole pattern length. * * \var lfo_multiply * Normally, the y(t) value of each LFO calculation is given by * the waveform function. If set to true, then the y(t) value * is scaled from 0 to 127 to 0.0 to 1.0, and is then multiplied * by the actual data value. This allows mutiple applications of * waveform transformations. */ struct lfoparameters { double lfo_dc_offset; double lfo_range; double lfo_periods; double lfo_phase; waveform lfo_waveform; bool lfo_use_measure; bool lfo_multiply; }; /** * The sequence class is firstly a receptable for a single track of MIDI * data read from a MIDI file or edited into a pattern. More members than * you can shake a stick at. */ class sequence { friend class performer; /* access to set_parent() */ friend class triggers; public: /** * Provides a setting for Live vs. Song mode. Much easier to grok and * expand than a boolean. */ enum class playback { live, song, automatic, max }; /** * Provides a set of methods for drawing certain items. These values are * used in the sequence, seqroll, perfroll, qloopbutton, and main window * classes. Also see the seq66::qseqdata::type enumeration. */ enum class draw { none, /**< indicates that current event is not a note */ finish, /**< Indicates that drawing is finished. */ linked, /**< Used for drawing linked notes. */ note_on, /**< For starting the drawing of a note. */ note_off, /**< For finishing the drawing of a note. */ tempo, /**< For drawing tempo meta events. */ program, /**< For drawing program change (patch) events. */ controller, /**< For all control-change events. */ pitchbend, /**< For indicating a pitch-wheel event. */ max }; /** * Provides two editing modes for a sequence. A feature adapted from * Kepler34. Not yet ready for prime time. */ enum class editmode { note, /**< Edit as a Note, the normal edit mode. */ drum /**< Edit as Drum note, using short notes. */ }; /** * A structure that holds note information, used, for example, in * sequence::get_next_note(). * * If the note is invalid (as might happen in searches), then the * note value is (-1). * * The usage of this small class has evolved to support other * events, as indicated by the draw enumeration above. */ class note_info { friend class sequence; private: midipulse ni_tick_start; midipulse ni_tick_finish; int ni_note; /* for tempo, the location to paint it */ int ni_velocity; /* for tempo, the truncated tempo value */ bool ni_selected; bool ni_non_note; /* true for all non-note events */ public: note_info () : ni_tick_start (0), ni_tick_finish (0), ni_note (0), /* we could initialize this to (-1) */ ni_velocity (0), ni_selected (false), ni_non_note (false) { // no code } midipulse start () const { return ni_tick_start; } midipulse finish () const { return ni_tick_finish; } midipulse length () const { return ni_tick_finish - ni_tick_start; } int note () const { return ni_note; } bool valid () const { return note() >= 0; } int velocity () const { return ni_velocity; } bool selected () const { return ni_selected; } bool non_note () { return ni_non_note; } void show () const; }; // nested class note_info private: /** * Provides a stack of event-lists for use with the undo and redo * facility. */ using eventstack = std::stack; public: /** * Holds partial information about a time signature. */ using timesig = struct { double sig_start_measure; /* Starting measure, precalculated. */ double sig_measures; /* Size in measures, precalculated. */ int sig_beats_per_bar; /* The beats-per-bar in the time-sig. */ int sig_beat_width; /* The size of each beat in the bar. */ int sig_ticks_per_beat; /* Simplifies later calculations. */ midipulse sig_start_tick; /* The pulse where time-sig was placed. */ midipulse sig_end_tick; /* Next time-sig start (0 == end?). */ }; /** * A list of time-signatures, which assumes that only the beats/bar and * beat width vary. */ using timesig_list = std::vector; private: /** * The number of MIDI notes in what? This value is used in the sequence * module. It looks like it is the maximum number of notes that * seq24/seq66 can have playing at one time. In other words, "only" 256 * simultaneously-playing notes can be managed. Defines the maximum * number of notes playing at one time that the application will support. * BOGUS. It was meant for counting legal notes, and only 128 are * available (see the constant c_notes_count). * * static const int c_playing_notes_max = 256; */ /** * Used as the default velocity parameter in adding notes. */ static short sm_preserve_velocity; /* * Documented at the definition point in the cpp module. */ static eventlist sm_clipboard; /* shared between sequences */ /* * For fingerprinting check with speed. */ static int sm_fingerprint_size; private: /** * For pause support, we need a way for the sequence to find out if JACK * transport is active. We can use the rcsettings flag(s), but JACK * could be disconnected. We could use a reference here, but, to avoid * modifying the midifile class as well, we use a pointer. It is set in * performer::add_sequence(). This member would also be using for passing * modification status to the parent, so that the GUI code doesn't have * to do it. */ performer * m_parent; /** * This list holds the current pattern/sequence events. It used to be * called m_list_events, but another implementation is now available, and * is the default. */ eventlist m_events; /** * Holds the list of triggers associated with the sequence, used in the * performance/song editor. */ triggers m_triggers; /** * Holds a list of time-signatures in the pattern, for use when drawing * the vertical grid-lines in the pattern-editor time, piano roll, and * event (qstriggereditor) panes. */ timesig_list m_time_signatures; /** * Provides a list of event actions to undo for the Stazed LFO and * seqdata support. */ eventlist m_events_undo_hold; /** * A stazed flag indicating that we have some undo information. */ bool m_have_undo; /** * A stazed flag indicating that we have some redo information. * Previously, unlike the perfedit, the seqedit did not provide a redo * facility. */ bool m_have_redo; /** * Provides a list of event actions to undo. */ eventstack m_events_undo; /** * Provides a list of event actions to redo. */ eventstack m_events_redo; /** * A new feature for recording, based on a "stazed" feature. If true * (not the default), then Seq66 will record only MIDI events that match * its output channel. The old behavior is preserved if this variable is * set to false. */ bool m_channel_match; /** * Contains the global MIDI output channel for this sequence. However, * if this value is null_channel() (0x80), then this sequence is a * multi-chanel track, and has no single channel, or represents a track * who's recorded channels we do not want to replace. Please note that * this is the output channel. However, if set to a valid channel, then * that channel will be forced on notes created via painting in the * seqroll. */ midibyte m_midi_channel; /* pattern's global MIDI channel */ /** * This value indicates that the global MIDI channel associated with this * pattern is not used. Instead, the actual channel of each event is * used. This is true when m_midi_channel == null_channel(). */ bool m_free_channel; /** * Contains the nominal output MIDI bus number for this sequence/pattern. * This number is saved in the sequence/pattern. If port-mapping is in * place, this number is used only to look up the true output buss. */ bussbyte m_nominal_bus; /** * Contains the actual buss number to be used in output. */ bussbyte m_true_bus; /** * Similar to the above, but for the input buss, a new feature. * Unlike the output buss, this input buss is optional. */ bussbyte m_nominal_in_bus; bussbyte m_true_in_bus; /** * Provides a flag for pattern playback song muting. */ bool m_song_mute; /** * Indicate if the sequence is transposable or not. A potential feature * from stazed's seq32 project. Now it is an actual, configurable * feature. */ bool m_transposable; /** * Provides a member to hold the polyphonic step-edit note counter. We * will never come close to the short limit of 32767. */ short m_notes_on; /** * Provides the master MIDI buss which handles the output of the sequence * to the proper buss and MIDI channel. */ mastermidibus * m_master_bus; /** * Provides a "map" for Note On events. It is used when muting, to shut * off the notes that are playing. * * unsigned short m_playing_notes[c_notes_count]; */ std::vector m_playing_notes; /** * Indicates if the sequence was playing. This value is set at the end * of the play() function. It is used to continue playing after changing * the pattern length. Turns out to be unused in both Seq24 and Seq66. * * bool m_was_playing; */ /** * True if sequence playback currently is possible for this sequence. * In other words, the sequence is armed. */ bool m_armed; /** * True if sequence recording currently is in progress for this sequence. */ bool m_recording; mutable bool m_draw_locked; /** * Eliminates a bunch of booleans. The default style is merge (overdub). */ recordstyle m_recording_style; /** * Replaces a potential bunch of booleans. The data type is defined in * the calculations module. */ alteration m_record_alteration; /** * True if recording in MIDI-through mode. */ bool m_thru; /** * True if there's a popup-menu present. See how it is used in * qloopbutton. */ bool m_has_popup; /** * True if the events are queued. */ bool m_queued; /** * A member from the Kepler34 project to indicate we are in one-shot mode * for triggering. Set to false whenever playing-state changes. Used in * sequence :: play_queue() to maybe play from the one-shot tick, then * toggle play and toggle queuing before playing normally. * * One-shot mode is entered when the MIDI control c_status_oneshot event * is received. Kepler34 reserves the period '.' to initiate this event. */ bool m_one_shot; /** * A member from the Kepler34 project, set in sequence :: * toggle_one_shot() to m_last_tick adjusted to the length of the * sequence. Compare this member to m_queued_tick. */ midipulse m_one_shot_tick; /** * Number of times to play the pattern in Live mode. A value of 0 means * to play the pattern endlessly in Live mode, like normal. The maximum * loop-count, if non-zero, is stored in a c_seq_loopcount SeqSpec as a * short integer. */ int m_loop_count_max; /** * Indicates if we have turned off from a snap operation. */ bool m_off_from_snap; /** * Used to temporarily block Song Mode events while recording new * ones. Set to false if at a trigger transition in trigger playback. * Otherwise, triggers are allow to be processed. Turned off when * song-recording stops. */ bool m_song_playback_block; /** * Used to keep on blocking Song Mode events while recording new ones. * Allows recording a live performance, by storing the sequence triggers. * Adapted from Kepler34. */ bool m_song_recording; /** * This value indicates that the following feature is active: the number * of ticks to snap recorded improvisations and manually-added triggers. */ bool m_song_recording_snap; /** * Saves the tick from when we started recording live song data. */ midipulse m_song_record_tick; /** * Indicates if the play marker has gone to the beginning of the sequence * upon looping. */ bool m_loop_reset; /** * Hold the current unit for a measure. Need to clarifiy this one. * It is calculated when needed (lazy evaluation). */ mutable midipulse m_unit_measure; /** * The "m_dirty" flags indicate that the content of the sequence has * changed due to recording, editing, performance management, or a name * change. They all start out as "true" in the sequence constructor. * * - The function sequence::set_dirty_mp() sets all but the "dirty * edit" flag to true. It is set when modifying the BPM, * beat-width, toggling cueing, changing the pattern name, * - The function sequence::set_dirty() sets all four flags to true. * * Provides the main dirtiness flag. In Seq24, it was: * * - Set in perform::is_dirty_main() to set the same status for a * given sequence. (It also set "was active main" for the * sequence.) * - Cause mainwnd to update a given sequence in the live frame. */ mutable std::atomic m_dirty_main; /** * Provides the main is-edited flag. In Seq24, it was: * * - Set in perform::is_dirty_edit() to set the same status for a * given sequence. (It also set "was active edit" for the * sequence.) * - Used in seqedit::timeout to refresh the seqroll, seqdata, and * seqevent panes. */ mutable std::atomic m_dirty_edit; /** * Provides performance dirty flagflag. * * - Set in perform::is_dirty_perf() to set the same status for a * given sequence. (It also set "was active perf" for the * sequence.) * - Used in perfroll to redraw each "dirty perf" sequence. */ mutable std::atomic m_dirty_perf; /** * Provides the names dirtiness flag. * * - Set in perform::is_dirty_names() to set the same status for a * given sequence. (It also set "was active names" for the * sequence.) * - Used in perfnames to redraw each "dirty names" sequence. */ mutable std::atomic m_dirty_names; /** * Indicates the pattern was modified. Unlike the is_dirty_xxx flags, * this one is not reset when checked. Useful when closing a file or the * application to cause a "Save?" prompt. */ mutable bool m_is_modified; /** * Indicates that the sequence is currently being edited. */ bool m_seq_in_edit; /** * Set by seqedit for the handle_action() function to use. */ midibyte m_status; midibyte m_cc; /** * Provides the name/title for the sequence. */ std::string m_name; /** * Provides the default name/title for the sequence. */ static const std::string sm_default_name; /** * These members manage where we are in the playing of this sequence, * including triggering. */ midipulse m_last_tick; /**< Provides the last tick played. */ midipulse m_queued_tick; /**< Provides the tick for queuing. */ midipulse m_trigger_offset; /**< Provides the trigger offset. */ /** * This constant provides the scaling used to calculate the time position * in ticks (pulses), based also on the PPQN value. Hardwired to * c_maxbeats at present. */ const int m_maxbeats; /** * Holds the PPQN value for this sequence, so that we don't have to rely * on a global constant value. */ unsigned short m_ppqn; /** * A new member so that the sequence number is carried along with the * sequence. This number is set in the performer::install_sequence() * function. Also see the alias seq::number, which is not short, * but int! */ short m_seq_number; /** * Implements a feature from the Kepler34 project. It is an index into a * palette. The colorbyte type is defined in the midibytes.hpp file. */ colorbyte m_seq_color; /** * A feature adapted from Kepler34. */ editmode m_seq_edit_mode; /** * Holds the length of the sequence in pulses (ticks). This value should * be a power of two when used as a bar unit. This value depends on the * settings of beats/minute, pulses/quarter-note, the beat width, and the * number of measures. */ midipulse m_length; /** * Used in handling one-shot recording while playback is in progress. * This value allows the user to wait a few loops before starting to play * the one-shot pattern. */ midipulse m_next_boundary; /** * Holds the last number of measures, purely for detecting changes that * affect the measure count. Normally, get_measures() makes a live * calculation of the current measure count. For example, changing the * beat-width to a smaller value could increase the number of measures. */ mutable int m_measures; /** * The size of snap in units of pulses (ticks). It starts out as the * value m_ppqn / 4. */ midipulse m_snap_tick; /** * The size of adding an auto-step (step-edit) note in units of pulses * (ticks). It starts out as the value m_ppqn / 4. */ midipulse m_step_edit_note_length; /** * Provides the number of beats per bar used in this sequence. Defaults * to 4. Used by the sequence editor to mark things in correct time on * the user-interface. */ unsigned short m_time_beats_per_measure; /** * Provides with width of a beat. Defaults to 4, which means the beat is * a quarter note. A value of 8 would mean it is an eighth note. Used * by the sequence editor to mark things in correct time on the * user-interface. */ unsigned short m_time_beat_width; /** * New members to use for the c_timesig SeqSpec. Rather than hold * the last time-signature that was set, this holds the first one, * or the value in a c_timesig SeqSpec. If 0, the c_timesig values * have not yet been set. */ unsigned short m_timesig_beats_per_measure; unsigned short m_timesig_beat_width; /** * Augments the beats/bar and beat-width with the additional values * included in a Time Signature meta event. This value provides the * number of MIDI clocks between metronome clicks. The default value of * this item is 24. It can also be read from some SMF 1 files, such as * our hymne.mid example. */ int m_clocks_per_metronome; /** * Augments the beats/bar and beat-width with the additional values * included in a Time Signature meta event. This value provides the * number of notated 32nd notes in a MIDI quarter note (24 MIDI clocks). * The usual (and default) value of this parameter is 8; some sequencers * allow this to be changed. */ int m_32nds_per_quarter; /** * Augments the beats/bar and beat-width with the additional values * included in a Tempo meta event. This value can be extracted from the * beats-per-minute value (mastermidibus::m_beats_per_minute), but here * we set it to 0 by default, indicating that we don't want to write it. * Otherwise, it can be read from a MIDI file, and saved here to be * restored later. */ long m_us_per_quarter_note; /** * The volume to be used when recording. It can range from 0 to 127, * or be set to the preserve-velocity (-1). */ short m_rec_vol; /** * The Note On velocity used, set to usr().note_on_velocity(). If the * recording velocity (m_rec_vol) is non-zero, this value will be set to * the desired recording velocity. A "stazed" feature. Note that * we use (-1) for flagging preserving the velocity of incoming notes. */ short m_note_on_velocity; /** * The Note Off velocity used, set to usr().note_on_velocity(), and * currently unmodifiable. A "stazed" feature. */ short m_note_off_velocity; /** * Holds a copy of the musical key for this sequence, which we now * support writing to this sequence. If the value is * c_key_of_C, then there is no musical key to be set. */ midibyte m_musical_key; /** * Holds a copy of the musical scale for this sequence, which can be * written to this sequence. If the value is the enumeration * value scales::off, then there is no musical scale to be set. * Provides the index pointing to the optional scale to be shown on the * background of the pattern. */ midibyte m_musical_scale; /** * Holds a copy of the musical chord for this sequence. */ midibyte m_musical_chord; /** * Holds a copy of the background sequence number for this sequence, * which we now support writing to this sequence. If the value is * greater than max_sequence(), then there is no background sequence to * be set. */ short m_background_sequence; /** * Provides locking for the sequence. Made mutable for use in * certain locked getter functions. */ mutable recmutex m_mutex; private: /* * We're going to replace this operator with the more specific * partial_assign() function. */ sequence & operator = (const sequence & rhs); public: sequence (int ppqn = c_use_default_ppqn); /* * What is the cost of adding virtual here, at runtime? */ virtual ~sequence (); void partial_assign (const sequence & rhs, bool domodify = true); static short maximum () { return 1024; } static short recorder () { return 2040; } static short is_recorder (int s) { return short(s) == 2040; } static short metronome () { return 2047; } static bool is_metronome (int s) { return s == 2047; } static int limit () { return 2048; /* 0x0800 */ } static bool is_normal (int s) { return s < 1024; /* see maximum() above */ } static int unassigned () { return (-1); } eventlist & events () { return m_events; } const eventlist & events () const { return m_events; } bool empty () const { return m_events.empty(); } bool any_selected_notes () const { return m_events.any_selected_notes(); } bool any_selected_events () const { return m_events.any_selected_events(); } bool any_selected_events (midibyte status, midibyte cc) const { return m_events.any_selected_events(status, cc); } bool is_exportable () const { return ! get_song_mute() && trigger_count() > 0; } const triggers::container & triggerlist () const { return m_triggers.triggerlist(); } triggers::container & triggerlist () { return m_triggers.triggerlist(); } std::string trigger_listing () const { return m_triggers.to_string(); } /** * Gets the trigger count, useful for exporting a sequence. */ int trigger_count () const { return int(m_triggers.count()); } int triggers_datasize (midilong seqspec) const { return m_triggers.datasize(seqspec); } int any_trigger_transposed () const { return m_triggers.any_transposed(); } /** * Gets the number of selected triggers. That is, selected in the * perfroll. */ int selected_trigger_count () const { return m_triggers.number_selected(); } void set_trigger_paste_tick (midipulse tick) { m_triggers.set_trigger_paste_tick(tick); } midipulse get_trigger_paste_tick () const { return m_triggers.get_trigger_paste_tick(); } bool analyze_time_signatures (); int time_signature_count () const { return int(m_time_signatures.size()); } const timesig & get_time_signature (size_t index) const; bool current_time_signature (midipulse p, int & beats, int & beatwidth) const; int measure_number (midipulse p) const; midipulse time_signature_pulses (const std::string & s) const; bool is_recorder_seq () const { return m_seq_number == recorder(); } bool is_metro_seq () const { return m_seq_number == metronome(); } /* * Indicates a normal, modifiable sequence. The sequence is not one of * our hidden workhorses for metronome and auto-recording functions. * It is normally not visible and not modifiable. */ bool is_normal_seq () const { return m_seq_number < maximum(); } int seq_number () const { return int(m_seq_number); } std::string seq_number_string () const { return std::to_string(seq_number()); } void seq_number (int seqno) { if (seqno >= 0 && seqno <= limit()) m_seq_number = short(seqno); } int color () const { return int(m_seq_color); } bool set_color (int c, bool user_change = false); void empty_coloring (); editmode edit_mode () const { return m_seq_edit_mode; } midibyte edit_mode_byte () const { return static_cast(m_seq_edit_mode); } void edit_mode (editmode mode) { m_seq_edit_mode = mode; } void edit_mode (midibyte b) { m_seq_edit_mode = b == 0 ? editmode::note : editmode::drum ; } bool loop_count_max (int m, bool user_change = false); void modify (bool notifychange = true); void unmodify () { m_is_modified = false; } int event_count () const; int note_count () const; bool first_notes (midipulse & ts, int & n) const; int playable_count () const; bool is_playable () const; bool minmax_notes (int & lowest, int & highest); bool have_undo () const { return m_have_undo; } /** * No reliable way to "unmodify" the performance here. */ void set_have_redo () { m_have_redo = m_events_redo.size() > 0; } bool have_redo () const { return m_have_redo; } void set_have_undo (); void push_undo (bool hold = false); /* adds stazed parameter */ void pop_undo (); void pop_redo (); void push_trigger_undo (); void pop_trigger_undo (); void pop_trigger_redo (); void set_name (const std::string & name = ""); int calculate_measures (bool reset = false) const; int get_measures (midipulse newlength) const; int get_measures () const; int measures () const { return m_measures; } bool event_threshold () const { return note_count() > sm_fingerprint_size; } int get_ppqn () const { return int(m_ppqn); } void set_beats_per_bar (int beatspermeasure, bool user_change = false); int get_beats_per_bar () const { return int(m_time_beats_per_measure); } void set_beat_width (int beatwidth, bool user_change = false); int get_beat_width () const { return int(m_time_beat_width); } int timesig_beats_per_measure () const { return m_timesig_beats_per_measure; } int timesig_beat_width () const { return m_timesig_beat_width; } void set_time_signature (int bpb, int bw); /** * A convenience function for calculating the number of ticks in the * given number of measures. */ midipulse measures_to_ticks (int measures = 1) const { return seq66::measures_to_ticks /* see "calculations" module */ ( int(m_time_beats_per_measure), int(m_ppqn), int(m_time_beat_width), measures ); } void clocks_per_metronome (int cpm) { m_clocks_per_metronome = cpm; // needs validation } int clocks_per_metronome () const { return m_clocks_per_metronome; } void set_32nds_per_quarter (int tpq) { m_32nds_per_quarter = tpq; // needs validation } int get_32nds_per_quarter () const { return m_32nds_per_quarter; } void us_per_quarter_note (long upqn) { m_us_per_quarter_note = upqn; // needs validation } long us_per_quarter_note () const { return m_us_per_quarter_note; } void set_rec_vol (int rec_vol); void set_song_mute (bool mute); void toggle_song_mute (); bool get_song_mute () const { return m_song_mute; } void apply_song_transpose (); void set_transposable (bool flag, bool user_change = false); bool transposable () const { return m_transposable; } std::string title () const; const std::string & name () const { return m_name; } /** * Tests the name for being changed. */ bool is_default_name () const { return m_name == sm_default_name; } bool is_new_pattern () const { return is_default_name() && event_count() == 0; } static bool valid_scale_factor (double s, bool ismeasure = false); static int trunc_measures (double m); static const std::string & default_name () { return sm_default_name; } void seq_in_edit (bool edit) { m_seq_in_edit = edit; } bool seq_in_edit () const { return m_seq_in_edit; } bool set_length ( midipulse len = 0, bool adjust_triggers = true, bool verify = true ); bool set_measures (int measures, bool user_change = false); int increment_measures (); bool apply_length ( int bpb, int ppqn, int bw, int measures = 0, bool user_change = false ); bool apply_length (int meas = 0, bool user_change = false) { return apply_length(0, 0, 0, meas, user_change); } bool extend_length (); bool double_length (); midipulse get_length () const { return m_length; } midipulse get_length_plus () const { int bpmeas = m_time_beats_per_measure; if (bpmeas == 0) bpmeas = 4; return m_length + m_unit_measure / bpmeas; } midipulse get_tick () const; midipulse get_last_tick () const; void set_last_tick (midipulse tick = c_null_midipulse); midipulse last_tick () const { return m_last_tick; } /** * Some MIDI file errors and other things can lead to an m_length of 0, * which causes arithmetic errors when m_last_tick is modded against it. * This function replaces the "m_last_tick % m_length", returning * m_last_tick if m_length is 0 or 1. */ midipulse mod_last_tick () { return (m_length > 1) ? (m_last_tick % m_length) : m_last_tick ; } /* * Documented at the definition point in the cpp module. */ bool set_armed (bool p); bool armed () const { return m_armed; } bool muted () const { return ! m_armed; } bool sequence_playing_toggle (); bool toggle_playing (); bool toggle_playing (midipulse tick, bool resumenoteons); bool toggle_queued (); void set_popup (bool flag) { m_has_popup = flag; } bool has_popup () const { return m_has_popup; } bool get_queued () const { return m_queued; } midipulse get_queued_tick () const { return m_queued_tick; } bool check_queued_tick (midipulse tick) const { return get_queued() && (get_queued_tick() <= tick); } bool set_recording_style (recordstyle rs); /* * The seq66::toggler flag enumeration is off, on, and flip! */ bool set_recording (toggler flag); bool set_recording (alteration q, toggler flag); bool set_thru (bool thru_active, bool toggle = false); bool recording () const { return m_recording; } bool alter_recording () const { return m_record_alteration != alteration::none; } alteration record_alteration () const { return m_record_alteration; } void record_alteration (alteration a) { m_record_alteration = a; } bool quantized_recording () const { return m_record_alteration == alteration::quantize; } bool quantizing () const { return quantized_recording(); } bool tightened_recording () const { return m_record_alteration == alteration::tighten; } bool tightening () const { return tightened_recording(); } bool notemapped_recording () const { return m_record_alteration == alteration::notemap; } bool notemapping () const { return notemapped_recording(); } bool expanded_recording () const { return m_recording_style == recordstyle::expand; } bool expanding () const { return recording() && expanded_recording(); } bool oneshot_recording () const { return m_recording_style == recordstyle::oneshot; } bool expand_recording () const; /* does more checking for status */ bool overwriting () const { return m_recording_style == recordstyle::overwrite; } bool thru () const { return m_thru; } midipulse snap () const { return m_snap_tick; } midipulse step_edit_note_length () const /* auto-step/step-edit */ { return m_step_edit_note_length; } void snap (int st); void step_edit_note_length (int len); void off_one_shot (); void song_recording_start (midipulse tick, bool snap = true); void song_recording_stop (midipulse tick); bool one_shot () const { return m_one_shot; } midipulse one_shot_tick () const { return m_one_shot_tick; } bool check_one_shot_tick (midipulse tick) const { return one_shot() && (one_shot_tick() <= tick); } int loop_count_max () const { return m_loop_count_max; } bool song_recording () const { return m_song_recording; } bool off_from_snap () const { return m_off_from_snap; } bool snap_it () const { return armed() && (get_queued() || off_from_snap()); } bool song_playback_block () const { return m_song_playback_block; } bool song_recording_snap () const { return m_song_recording_snap; } midipulse song_record_tick () const { return m_song_record_tick; } void resume_note_ons (midipulse tick); bool toggle_one_shot (); bool modified () const { return m_is_modified; } bool is_dirty_main () const; bool is_dirty_edit () const; bool is_dirty_perf () const; bool is_dirty_names () const; void set_dirty_mp (); void set_dirty (); std::string channel_string () const; /* "F" or "" */ bool set_channels (int channel); /* modifies event list */ midibyte seq_midi_channel () const { return m_midi_channel; /* midi_channel() below */ } midibyte midi_channel (const event & ev) const { return m_free_channel ? ev.channel() : m_midi_channel ; } midibyte midi_channel () const { return m_free_channel ? null_channel() : m_midi_channel ; } bool free_channel () const { return m_free_channel; } /** * Returns true if this sequence is an SMF 0 sequence. */ bool is_smf_0 () const { return is_null_channel(m_midi_channel); } std::string to_string () const; void play (midipulse tick, bool playback_mode, bool resume = false); void live_play (midipulse tick); void play_queue (midipulse tick, bool playbackmode, bool resume); bool push_add_note ( midipulse tick, midipulse len, int note, bool repaint = false, int velocity = sm_preserve_velocity ); bool push_add_chord ( int chord, midipulse tick, midipulse len, int note, int velocity = sm_preserve_velocity ); bool add_painted_note ( midipulse tick, midipulse len, int note, bool repaint = false, int velocity = sm_preserve_velocity ); bool add_note (midipulse len, const event & e); bool add_chord ( int chord, midipulse tick, midipulse len, int note, int velocity = sm_preserve_velocity ); bool add_tempo (midipulse tick, midibpm tempo, bool repaint = false); bool add_tempos ( midipulse tick_s, midipulse tick_f, int tempo_s, int tempo_f ); bool log_time_signature ( midipulse tick, int beats, int width, bool user_change = false ); bool update_time_signature (int bpb, int bw, bool user_change = false); bool add_timesig_event (const event & e, bool main_ts = false); bool add_timesig_event ( midipulse t, int bpb = 4, int bw = 4, bool replace = true ); bool set_main_time_signature (); bool add_c_timesig (int bpb, int bw, bool main_ts = false); bool delete_time_signature (midipulse tick); bool detect_time_signature ( midipulse & tstamp, int & numerator, int & denominator, midipulse start = 0, midipulse limit = c_null_midipulse ); bool add_event (const event & er); bool add_event ( midipulse tick, midibyte status, midibyte d0, midibyte d1, bool repaint = false ); bool add_event (midipulse tick, const midibytes & dbytes); bool add_macro (midipulse tick, const midimacro & macro); bool append_event (const event & er); void sort_events (); event find_event (const event & e, bool nextmatch = false); note_info find_note (midipulse tick, int note); bool remove_duplicate_events (midipulse tick, int note = (-1)); void notify_change (bool userchange = true); void notify_trigger (); void print_triggers () const; bool add_trigger ( midipulse tick, midipulse len, midipulse offset = 0, midibyte tpose = 0, bool adjust_offset = true ); bool split_trigger (midipulse tick, trigger::splitpoint splittype); bool grow_trigger (midipulse tick_from, midipulse tick_to, midipulse len); bool grow_trigger (midipulse tick_from, midipulse tick_to); const trigger & find_trigger (midipulse tick) const; bool delete_trigger (midipulse tick); bool clear_triggers (); bool get_trigger_state (midipulse tick) const; bool transpose_trigger (midipulse tick, int transposition); bool select_trigger (midipulse tick); triggers::container get_triggers () const; bool unselect_trigger (midipulse tick); bool unselect_triggers (); #if defined USE_INTERSECT_FUNCTIONS bool intersect_triggers (midipulse pos, midipulse & start, midipulse & end); bool intersect_triggers (midipulse pos); bool intersect_notes ( midipulse position, int position_note, midipulse & start, midipulse & ender, int & note ); bool intersect_events ( midipulse posstart, midipulse posend, midibyte status, midipulse & start ); #endif bool delete_selected_triggers (); bool cut_selected_triggers (); bool copy_selected_triggers (); bool paste_trigger (midipulse paste_tick = c_no_paste_trigger); bool move_triggers ( midipulse start_tick, midipulse distance, bool direction, bool single = true ); bool move_triggers ( midipulse tick, bool adjust_offset, triggers::grow which = triggers::grow::move ); void offset_triggers ( midipulse offset, triggers::grow editmode = triggers::grow::move ); bool selected_trigger ( midipulse droptick, midipulse & tick0, midipulse & tick1 ); midipulse selected_trigger_start (); midipulse selected_trigger_end (); midipulse get_max_timestamp () const; midipulse get_max_trigger () const; void copy_triggers (midipulse start_tick, midipulse distance); midipulse get_trigger_offset () const { return m_trigger_offset; } bussbyte seq_midi_bus () const { return m_nominal_bus; } bussbyte true_bus () const { return m_true_bus; } bussbyte seq_midi_in_bus () const { return m_nominal_in_bus; } bussbyte true_in_bus () const { return m_true_in_bus; } bool has_in_bus () const { return is_good_buss(m_true_in_bus); } bool set_master_midi_bus (const mastermidibus * mmb); bool set_midi_bus (bussbyte mb, bool user_change = false); bool set_midi_channel (midibyte ch, bool user_change = false); bool set_midi_in_bus (bussbyte mb, bool user_change = false); int select_note_events ( midipulse tick_s, int note_h, midipulse tick_f, int note_l, eventlist::select action ); int select_notes_by_pitch (int note_h, int note_l); int select_events ( midipulse tick_s, midipulse tick_f, midibyte astatus, midibyte cc, eventlist::select action ); int select_events ( midibyte astatus, midibyte cc, bool inverse = false ); int select_event_handle ( midipulse tick_s, midipulse tick_f, midibyte astatus, midibyte cc, midibyte data ); void adjust_event_handle (midibyte astatus, midibyte data); /** * New convenience function. What about Aftertouch events? I think we * need to select them as well in seqedit, so let's add that selection * here as well. * * \param inverse * If set to true (the default is false), then this causes the * selection to be inverted. */ void select_all_notes (bool inverse = false) { (void) select_events(EVENT_NOTE_ON, 0, inverse); (void) select_events(EVENT_NOTE_OFF, 0, inverse); (void) select_events(EVENT_AFTERTOUCH, 0, inverse); } int get_num_selected_notes () const; int get_num_selected_events (midibyte status, midibyte cc) const; void select_all (); void select_by_channel (int channel); void select_notes_by_channel (int channel); void unselect (); bool repitch (const notemapper & nmap, bool all = false); bool copy_selected (); bool cut_selected (bool copyevents = true); bool paste_selected (midipulse tick, int note); bool merge_events (const sequence & source); bool selected_box ( midipulse & tick_s, int & note_h, midipulse & tick_f, int & note_l ); bool onsets_selected_box ( midipulse & tick_s, int & note_h, midipulse & tick_f, int & note_l ); bool clipboard_box ( midipulse & tick_s, int & note_h, midipulse & tick_f, int & note_l ); midipulse clip_timestamp (midipulse ontime, midipulse offtime); bool move_selected_notes (midipulse deltatick, int deltanote); bool move_selected_events (midipulse deltatick); bool stream_event (event & ev); bool change_event_data_range ( midipulse tick_s, midipulse tick_f, midibyte status, midibyte cc, int d_s, int d_f, bool finalize = false ); bool change_event_data_relative ( midipulse tick_s, midipulse tick_f, midibyte status, midibyte cc, int newval, bool finalize = false ); void change_event_data_lfo ( const lfoparameters & lp, midibyte status, midibyte cc ); bool fix_pattern (fixparameters & param); /* for qpatternfix dialog */ void increment_selected (midibyte status, midibyte /*control*/); void decrement_selected (midibyte status, midibyte /*control*/); bool grow_selected (midipulse deltatick); bool stretch_selected (midipulse deltatick); bool randomize (midibyte status, int range = (-1), bool all = false); bool randomize_note_velocities (int range = (-1), bool all = false); bool randomize_note_pitches (int range = (-1), bool all = false); bool jitter_notes (int jitter = (-1), bool all = false); bool mark_selected (); void unpaint_all (); bool verify_and_link (bool wrap = false); bool edge_fix (); bool remove_unlinked_notes (); /** * Resets everything to zero. This function is used when the sequencer * stops. This function currently sets m_last_tick = 0, but we would * like to avoid that if doing a pause, rather than a stop, of playback. */ void zero_markers () { set_last_tick(0); } void play_note_on (int note); void play_note_off (int note); void off_playing_notes (); void stop (bool song_mode = false); /* playback::live vs song */ void pause (bool song_mode = false); /* playback::live vs song */ void reset_draw_trigger_marker (); bool clear_events (); void draw_lock () const; void draw_unlock () const; event::buffer::const_iterator cbegin () const { return m_events.cbegin(); } bool cend (event::buffer::const_iterator & evi) const { return evi == m_events.cend(); } bool reset_interval ( midipulse t0, midipulse t1, event::buffer::const_iterator & it0, event::buffer::const_iterator & it1 ) const; draw get_next_note ( note_info & niout, event::buffer::const_iterator & evi ) const; bool get_next_event_match ( midibyte status, midibyte cc, event::buffer::const_iterator & evi ); bool get_next_meta_match ( midibyte metamsg, event::buffer::const_iterator & evi, midipulse start = 0, midipulse range = c_null_midipulse ); bool get_next_event ( midibyte & status, midibyte & cc, event::buffer::const_iterator & evi ); bool next_trigger (trigger & trig); bool push_quantize (midibyte status, midibyte cc, int divide); bool push_quantize_notes (int divide); bool push_jitter_notes (int range = -1); bool transpose_notes (int steps, int scale, int key = 0); #if defined SEQ66_SEQ32_SHIFT_SUPPORT void shift_notes (midipulse ticks); #endif midibyte musical_key () const { return m_musical_key; } midibyte musical_scale () const { return m_musical_scale; } midibyte musical_chord () const { return m_musical_chord; } int background_sequence () const { return int(m_background_sequence); } void musical_key (int key, bool user_change = false); void musical_scale (int scale, bool user_change = false); void musical_chord (int c, bool user_change = false); bool background_sequence (int bs, bool user_change = false); void show_events () const; bool copy_events (const eventlist & newevents); midipulse unit_measure (bool reset = false) const; midipulse expand_threshold () const; midipulse expand_value (); /** * The master bus needs to know if the match feature is truly in force, * otherwise it must pass the incoming events to all recording sequences. * Compare this function to channels_match(). */ bool channel_match () const { return m_channel_match; } void loop_reset (bool reset); bool loop_reset () const { return m_loop_reset; } midipulse handle_size (midipulse start, midipulse finish); void handle_edit_action (eventlist::edit action, int var); bool check_loop_reset (); public: static void clear_clipboard () { sm_clipboard.clear(); /* shared between sequences */ } static recordstyle loop_record_style (int ri); /** * Short hand for testing a draw parameter. */ static bool is_draw_note (draw dt) { return dt == sequence::draw::linked || dt == sequence::draw::note_on || dt == sequence::draw::note_off; } /** * Necessary for drawing notes in a perf roll. Why? */ static bool is_draw_note_onoff (draw dt) { return dt == sequence::draw::note_on || dt == sequence::draw::note_off; } bool remove_selected (); bool remove_marked (); /* a forwarding function */ bool update_recording (int index); bool remove_orphaned_events (); private: bool flatten (sequence & destseq, bool maketrigger = true); midipulse flatten_trigger ( sequence & destseq, const trigger & trig, midipulse prev_timestamp ); protected: void set_parent (performer * p); void armed (bool flag) { m_armed = flag; } void free_channel (bool flag) { m_free_channel = flag; } private: midipulse apply_time_factor ( double factor, bool savenotelength = false, bool relink = false ); mastermidibus * master_bus () { return m_master_bus; } const performer * perf () const { return m_parent; } performer * perf () { return m_parent; } bool check_oneshot_recording (); bool quantize_events (midibyte status, midibyte cc, int divide = 1); bool quantize_notes (int divide = 1); bool change_ppqn (int p); void put_event_on_bus (const event & ev); void set_trigger_offset (midipulse trigger_offset); void adjust_trigger_offsets_to_length (midipulse newlen); midipulse adjust_offset (midipulse offset); draw get_note_info /* used only internally */ ( note_info & niout, event::buffer::const_iterator & evi ) const; timesig default_time_signature () const; void push_default_time_signature (); #if defined USE_SEQUENCE_REMOVE_EVENTS void remove (event::buffer::iterator i); void remove (event & e); #endif bool remove_first_match (const event & e, midipulse starttick = 0); bool remove_all (); /** * Checks to see if the event's channel matches the sequence's nominal * channel. * * \param e * The event whose channel nybble is to be checked. * * \return * Returns true if the channel-matching feature is enabled and the * channel matches, or true if the channel-matching feature is turned * off, in which case the sequence accepts events on any channel. */ bool channels_match (const event & e) const { return m_channel_match ? event::mask_channel(e.get_status()) == m_midi_channel : true ; } void draw_locked (bool flag) { m_draw_locked = flag; } void one_shot (bool f) { m_one_shot = f; } void off_from_snap (bool f) { m_off_from_snap = f; } void song_playback_block (bool f) { m_song_playback_block = f; } void song_recording (bool f) { m_song_recording = f; } void song_recording_snap (bool f) { m_song_recording_snap = f; } void song_record_tick (midipulse t) { m_song_record_tick = t; } void channel_match (bool flag) { m_channel_match = flag; } }; // class sequence } // namespace seq66 #endif // SEQ66_SEQUENCE_HPP /* * sequence.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/setmapper.hpp ================================================ #if ! defined SEQ66_SETMAPPER_HPP #define SEQ66_SETMAPPER_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file setmapper.hpp * * This module declares a small manager for a set of sequences, to be used by * the performer. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-02-12 * \updates 2023-11-17 * \license GNU GPLv2 or above * * This module also creates a small structure for managing sequence * variables, to save on a bunch of arrays. It manages screen-sets and * mute-groups. This class is meant to support the main mute groups, the * mute groups from the 'mutes' file, the saved 'armed' statuses, and the * current states of the tracks or sets. * * In this class, access is either to a given set, the playing set, or to a * sequence number that ranges from 0 up to the maximum number of sequences * allowed in a given run of the application. */ #include "play/mutegroups.hpp" /* seq66::mutegroups & mutegroup */ #include "play/setmaster.hpp" /* seq66::seqmanager and seqstatus */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /** * Provides a class for managing screensets and mutegroups. Much of the * action will occur in the selected play-screen. */ class setmapper { friend class performer; /* a very good friend to have */ private: /** * Provides a reference to an external mute group container. It can be * used to mute and unmute all of the patterns in a set at once. It can * also be modified to change the pattern when the application is in * Learn mode. */ mutegroups & m_mute_groups; /** * The number of loops/patterns in the set. Saves a calculation of row x * column. It is important to note the the size of the set is constant * throughout its lifetime (and the lifetime of the application). This * is the actual set size, not necessarily the maximum. See the * corresponding static variable. */ int m_set_size; /** * Holds a reference to the master set of sets. This is currently always * supplied by the performer object. The set-master holds the container of * all existing sets. */ setmaster & m_set_master; /** * Keeps track of created sequences, whether or not they are active. * Used by the install_sequence() function. Note that this value is not * a suitable replacement for m_sequence_max, because there can be * inactive sequences amidst the active sequences. */ int m_sequence_count; /** * A replacement for the c_max_sequence constant. However, this value is * already 32 * 32 = 1024, and is probably enough for any usage. Famous * last words? Actually, this value could go up to 2047... 2048 is used * to indicate that there is no background sequence. See seq::limit(). */ seq::number m_sequence_max; /** * Indicates the highest-number sequence. This value starts as 0, to * indicate no sequences loaded, and then contains the highest sequence * number hitherto loaded, plus 1 so that it can be used as a for-loop * limit similar to m_sequence_max. It's maximum value should be * m_sequence_max. * * Currently meant only for limited context to try to squeeze a little * extra speed out of playback. There's no easy way to lower this value * when the highest sequence is deleted, though. */ seq::number m_sequence_high; /** * Hold the number of the currently-in-edit sequence. Moving this status * from seqmenu into perform into setmapper for better centralized * management. */ seq::number m_edit_sequence; /** * Provides a place to save the screenset for later copying into another * screenset. This screenset has a set-number of -1, until it is in use, * when it has a set-number of 0, but is outside the normal set of * screensets, and is accessible only through a copy/paste mechanism. */ screenset m_set_clipboard; /** * Indicates which set is now in view and available for playback. We * guarantee this to be a valid value or a value (-1) that will be * ignored. We're not fans of throwing things. */ screenset::number m_playscreen; /** * To save set lookup during a number of operations, this pointer, owned * by no one, really, stores a painter to the playing screen-set * (play-screen) in the container. */ screenset * m_playscreen_pointer; /** * Indicates if the m_saved_armed_statuses[] values are the saved state * of the sequences, and can be restored. */ bool m_armed_saved; /** * Holds the status of the current play-screen. */ midibooleans m_tracks_mute_state; public: setmapper () = delete; /** * Creates the array of values, setting them all to 0 (false). */ setmapper ( setmaster & mc, mutegroups & mgs, int rows = screenset::c_default_rows, int columns = screenset::c_default_columns ); /* * The move and copy constructors, the move and copy assignment operators, * and the destructors are all compiler generated, or are deleted. */ setmapper (const setmapper &) = delete; setmapper & operator = (const setmapper &) = delete; setmapper (setmapper &&) = delete; setmapper & operator = (setmapper &&) = delete; ~setmapper () = default; private: /** * Given the raw sequence number, returns the calculated set number. * * \param seqno * The raw sequence number. Normally, this value can range from 0 * to 1023, or whatever the maximum is based on set size and number * of sets. All seq::number values in setmapper are assumed to be * in this range, whereas as they range from 0 to the set-size when * used by screenset functions. * * \return * Returns the calculated set number. It is clamped to a valid * value. */ screenset::number seq_set (seq::number seqno) const { return clamp(seqno / m_set_size); } /** * Checks the highest-numbered screen-set in existence, not counting * the "dummy" screen-set, and returns the number of sequences that * represents. Useful in drawing the rows in the perfedit * and avoiding vertical scrolling issues. */ int sequences_in_sets () const { return screenset_size() * (master().highest_set() + 1); } screenset::number seq_set (seq::number s, int & offset) const; /** * Gets the offset of the sequence (re 0) in its screen-set. It assumes * the pointer is good. Used only in performer::announce_sequence(). * But see grid_to_index() below. */ int seq_to_offset (seq::cref s) const { return s.seq_number() % m_set_size; } seq::number grid_to_index (int row, int column) const { return play_screen()->grid_to_index(row, column); } seq::number grid_to_seq (int row, int column) const { return play_screen()->grid_to_seq(row, column); } seq::number grid_to_seq ( screenset::number setno, int row, int column ) const { return play_screen()->grid_to_seq(setno, row, column); } bool seq_to_grid ( seq::number seqno, int & row, int & column, bool global = false ) const { return play_screen()->seq_to_grid(seqno, row, column, global); } bool index_to_grid (seq::number seqno, int & row, int & column) const { return play_screen()->index_to_grid(seqno, row, column); } int max_slot_shift () const { return m_set_size / setmaster::Size(); } int slot_shift_delta () const { return setmaster::Rows(); } void clear () { master().clear(); m_sequence_count = 0; m_sequence_high = m_edit_sequence = seq::unassigned(); } int sequence_count () const { return m_sequence_count; } int rows () const { return play_screen()->rows(); } int columns () const { return play_screen()->columns(); } bool group_event () const { return mutes().group_event(); } bool group_error () const { return mutes().group_error(); } /** * group_mode() starts out true, and allows mute_group_tracks() to work. * It is set and unset via the "gmute" MIDI control and the group-on/off * keys. m_mode_group_learn starts out false, and is set and unset via * the "glearn" MIDI control and the group-learn press and release * actions. */ bool group_mode () const { return mutes().group_mode(); } void group_mode (bool flag) { mutes().group_mode(flag); } void toggle_group_mode () { mutes().toggle_group_mode(); } bool any_in_edit () const { return master().any_in_edit(); } bool is_seq_in_edit (seq::number seqno) const; bool reset (); #if defined SEQ66_USE_SCREENSET_RESET_SEQUENCES /* currently unused */ void reset_sequences (bool pause, sequence::playback mode) { for (auto & sset : sets()) sset.second.reset_sequences(pause, mode); } #endif void play_all_sets ( midipulse tick, sequence::playback mode, bool resumenoteons ); seq::number sequence_high () const { return m_sequence_high; } seq::number sequence_max () const { return m_sequence_max; } /** * \setter m_edit_sequence * * \param seqno * Pass in seq::unassigned() (-1) to disable the edit-sequence number * unconditionally. Use unset_edit_sequence() to disable it if it * matches the current edit-sequence number. */ void set_edit_sequence (seq::number seqno) { m_edit_sequence = seqno; } /** * \setter m_edit_sequence * * Disables the edit-sequence number if it matches the parameter. * * \param seqno * The sequence number of the sequence to unset. */ void unset_edit_sequence (seq::number seqno) { if (is_edit_sequence(seqno)) set_edit_sequence(seq::unassigned()); } bool any_modified_sequences () const; void unmodify_all_sequences (); void set_dirty (seq::number seqno = seq::all()); /** * \getter m_edit_sequence * * \param seqno * Tests the parameter against m_edit_sequence. Returns true * if that member is not -1, and the parameter matches it. */ bool is_edit_sequence (seq::number seqno) const { return (m_edit_sequence != seq::unassigned()) && (seqno == m_edit_sequence); } /** * Checks if a sequence is exportable. * * \param seqno * Provides the raw sequence number, which ranges from 0 to 1023. * However, when the screenset calls is_exportable(), it get remapped * to the range 0 to m_set_size - 1. */ bool is_exportable (seq::number seqno) const { return screen(seqno).is_exportable(seqno); } bool is_dirty_main (seq::number seqno) const { return screen(seqno).is_dirty_main(seqno); } bool is_dirty_edit (seq::number seqno) const { return screen(seqno).is_dirty_edit(seqno); } bool is_dirty_perf (seq::number seqno) const { return screen(seqno).is_dirty_perf(seqno); } bool is_dirty_names (seq::number seqno) const { return screen(seqno).is_dirty_names(seqno); } int color (seq::number seqno) const { return screen(seqno).color(seqno); } bool color (seq::number seqno, int c) { return screen(seqno).color(seqno, c); } bool is_seq_active (seq::number seqno) const { return screen(seqno).active(seqno); } bool is_seq_empty (seq::number seqno) const { return screen(seqno).empty(seqno); } bool is_seq_recording (seq::number seqno) const { return screen(seqno).recording(seqno); } seq::number first_seq () const { return play_screen()->first_seq(); } /* * So far, nobody calls this function. */ void activate (int seqno, seq::number seqnum, bool flag = true) { screen(seqno).activate(seqno, seqnum, flag); } void off_sequences (seq::number seqno = seq::unassigned()); /** * Calls sequence::song_recording_start(m_current_tick) for all * sequences. Should be called only when not recording the performance * data. This is a Kepler34 feature. However, rather than operating on * all sets, it will operate on the play-screen only. Added the snap * parameter for issue #44. */ void song_recording_start (midipulse tick, bool snap = true) { play_screen()->song_recording_start(tick, snap); } void song_recording_stop (midipulse tick) { play_screen()->song_recording_stop(tick); } /** * Clears the snapshot statuses. Needed when disabling the queue mode. */ void clear_snapshot () { for (auto & sset : sets()) sset.second.clear_snapshot(); } /** * For all active patterns/sequences, this function gets the playing * status and saves it. Inactive patterns get the value set to false. * Used in unsetting the snapshot status (automation :: ctrlstatus :: * snapshot). A rework of performer::save_playing_state(). */ void save_snapshot () { for (auto & sset : sets()) sset.second.save_snapshot(); } /** * For all active patterns/sequences, this function gets the playing * status from the setmapper, and sets it for the sequence. Used in * unsetting the snapshot status (automation::ctrlstatus::snapshot). */ void restore_snapshot () { for (auto & sset : sets()) sset.second.restore_snapshot(); } /** * Perhaps we need to check ONLY the play_screen()!!! */ bool needs_update () const { for (const auto & sset : sets()) { if (sset.second.needs_update()) return true; } return false; } /* * exec_set_function(s) executes a set-handler for each set. * exec_set_function(s,p) runs a set-handler and a slot-handler for each * set. exec_set_function(p) runs the slot-handler for all patterns in * all sets. exec_slot_function(p) runs the slot-handler for the * play-screen patterns. */ bool exec_set_function (screenset::sethandler s) { return master().exec_set_function(s); } bool exec_set_function (screenset::sethandler s, screenset::slothandler p) { return master().exec_set_function(s, p); } bool exec_set_function (screenset::slothandler p) { return master().exec_set_function(p); } bool exec_slot_function ( screenset::slothandler p, bool use_set_offset = true ) { return play_screen()->exec_slot_function(p, use_set_offset); } void set_last_ticks (midipulse tick) { for (auto & sset : sets()) sset.second.set_last_ticks(tick); } void apply_song_transpose (seq::number seqno = seq::all()); int trigger_count () const; midipulse max_trigger () const; midipulse max_timestamp () const; midipulse max_extent () const; void select_triggers_in_range ( seq::number seqlow, seq::number seqhigh, midipulse tickstart, midipulse tickfinish ); void unselect_triggers (seq::number seqno = seq::all()); bool move_triggers ( midipulse lefttick, midipulse righttick, bool direction, seq::number seqno = seq::all() ); void copy_triggers ( midipulse lefttick, midipulse righttick, seq::number seqno = seq::all() ); void push_trigger_undo () { for (auto & sset : sets()) sset.second.push_trigger_undo(); } void pop_trigger_undo () { for (auto & sset : sets()) sset.second.pop_trigger_undo(); } void pop_trigger_redo () { for (auto & sset : sets()) sset.second.pop_trigger_redo(); } /** * Looks up the sequence with the given sequence number. * * Current implementation: * * Use the static function seq_set() to calculate the desired set and * offset into the set using the application-wide row and column * size. We make this work faster by calculating the set based on * the sequence number. * * Alternate implemention: * * Go through all the sets and all the sequences in each set until it * finds the exact sequence number as set by set_active(). * * \param seqno * Provides the sequence number. Historically, this value varies * from 0 to 1023, and provided the index into a number of arrays. * Although we now use containers of screensets and seq/sequence * objects, the performer and midifile classes continue to number * them as if in an array. * * \return * Returns a shared-pointer to the desired sequence. This pointer * should be checked before being used. */ const seq::pointer loop (seq::number seqno) const { return screen(seqno).loop(seqno); } /** * Provides the private, non-const version of loop(). * * \param seqno * Provides the sequence number. * * \return * Returns a pointer to the desired sequence. This pointer should be * checked before being used. */ seq::pointer loop (seq::number seqno) { return screen(seqno).loop(seqno); } /** * Converts an offset into the play-screen (ranging from 0 to * m_set_size-1) into a sequence number in the range of the play-screen. * This number can then be used for lookup via the loop() function. A * bad value (-1) is returned if the play-screen does not exist. */ seq::number play_seq (seq::number seqno) { return play_screen()->play_seq(seqno); } void save_queued (int hotseq) { play_screen()->save_queued(hotseq); } void unqueue (int hotseq) { play_screen()->unqueue(hotseq); } bool armed () const; bool armed (seq::number seqno) const { return screen(seqno).armed(seqno); } void armed (seq::number seqno, bool flag) { screen(seqno).armed(seqno, flag); } bool muted (seq::number seqno) const { return ! armed(seqno); } void arm (seq::number seqno) { armed(seqno, true); } void mute (seq::number seqno) { armed(seqno, false); } void toggle (seq::number seqno = seq::all()); void toggle_song_mute (seq::number seqno = seq::all()); void toggle_playing_tracks (); void arm () { for (auto & sset : sets()) sset.second.arm(); } void mute () { for (auto & sset : sets()) sset.second.mute(); } void mute_all_tracks (bool flag) { if (flag) mute(); else arm(); } void apply_armed_statuses () { for (auto & sset : sets()) sset.second.apply_armed_statuses(); } bool learn_armed_statuses (); void all_notes_off () { for (auto & sset : sets()) sset.second.all_notes_off(); } void panic () { for (auto & sset : sets()) sset.second.panic(); } public: void show (bool showseqs = true) const; /** * The screen() functions look for the screen-set that contains the * specified (by number) sequence. If not found, then the dummy * screen-set is returned. * * The play_screen() functions return the screen that is showing in the * main Live grid. */ const screenset & screen (seq::number seqno) const; screenset & screen (seq::number seqno); screenset * play_screen () { return m_playscreen_pointer; } const screenset * play_screen () const { return m_playscreen_pointer; } screenset::number change_playscreen (int amount) { screenset::number result = m_playscreen + amount; return set_playscreen(result); } screenset::number playscreen_number () const { return m_playscreen; } seq::number playscreen_offset () const { return play_screen()->offset(); } int playscreen_active_count () const { return play_screen()->active_count(); } bool set_playscreen (screenset::number setno); bool set_playing_screenset (screenset::number setno); bool copy_screenset (screenset::number srcset, screenset::number destset); bool save_screenset (screenset::number srcset); bool paste_screenset (screenset::number destset); /* * Encapsulates some calls used in mainwnd. */ screenset::number increment_screenset (int amount) { return change_playscreen(amount); } screenset::number decrement_screenset (int amount) { return change_playscreen(-amount); } const std::string & name () const { return play_screen()->name(); } std::string name (screenset::number setno) const; bool name (screenset::number setno, const std::string & nm); bool name (const std::string & nm) { return play_screen()->name(nm); } bool is_screenset_active (screenset::number setno) const { return master().is_screenset_active(setno); } bool is_screenset_available (screenset::number setno) const { /* * Now all 32 slots have a screenset, but inactive vs. active. * * return master().is_screenset_available(setno); */ return master().is_screenset_active(setno); } /** * A helper function for determining if: * * - the mode group is in force * - the sequence is in the range of the playing screenset * - playing screenset is the same as the current screenset * * We're not sure why the third test is necessary, so it is disabled. * * \param seqno * Provides the index of the desired sequence. * * \return * Returns true if the sequence adheres to the conditions noted above. */ bool seq_in_playscreen (seq::number seqno) { return group_mode() && play_screen()->seq_in_set(seqno); } int screenset_size () const { return m_set_size; /* play_screen()->set_size() */ } bool install_sequence (sequence * s, seq::number & seqno); bool add_sequence (sequence * s, seq::number & seqno); bool remove_sequence (seq::number seqno); bool swap_sets (seq::number set0, seq::number set1) { return master().swap_sets(set0, set1); } bool set_mutes (mutegroup::number gmute, const midibooleans & bits) { return mutes().set(gmute, bits); } bool apply_mutes (mutegroup::number gmute); bool unapply_mutes (mutegroup::number gmute); bool toggle_mutes (mutegroup::number gmute); bool toggle_active_mutes (mutegroup::number gmute); bool learn_mutes (bool learnmode, mutegroup::number gmute); void select_and_mute_group (mutegroup::number group); void mute_group_tracks (); void sequence_playing_change ( seq::number seqno, bool on, bool qinprogress = false ); void sequence_playscreen_change ( seq::number seqno, bool on, bool qinprogress = false ); private: bool fill_play_set (playset & p, bool clearit = true); bool add_to_play_set (playset & p, sequence * s); bool add_to_play_set (playset & p, screenset & s); bool add_all_sets_to_play_set (playset & p); void recount_sequences (); setmaster::container::iterator add_set (screenset::number setno) { return master().add_set(setno); } setmaster::container::iterator find_by_value (screenset::number setno) { return master().find_by_value(setno); } bool remove_set (screenset::number setno) { return master().remove_set(setno); } bool clear_set (screenset::number setno) { return master().clear_set(setno); } mutegroup::number clamp_group (mutegroup::number group) const { return mutes().clamp_group(group); } bool check_group (mutegroup::number group) const { return mutes().check_group(group); } screenset::number clamp (screenset::number offset) const { return master().clamp(offset); } screenset & dummy_screenset () { return master().dummy_screenset(); } const screenset & dummy_screenset () const { return master().dummy_screenset(); } mutegroups & mutes () { return m_mute_groups; } const mutegroups & mutes () const { return m_mute_groups; } setmaster & master () { return m_set_master; } const setmaster & master () const { return m_set_master; } setmaster::container & sets () { return master().set_container(); } const setmaster::container & sets () const { return master().set_container(); } }; // class setmapper } // namespace seq66 #endif // SEQ66_SETMAPPER_HPP /* * setmapper.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/setmaster.hpp ================================================ #if ! defined SEQ66_SETMASTER_HPP #define SEQ66_SETMASTER_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file setmaster.hpp * * This module declares a small manager for a set of sets. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-08-10 * \updates 2021-11-13 * \license GNU GPLv2 or above * * The setmaster class is meant to encapsulate the sets and their layout, * without performing any functions related to patterns. This new class was * created because we found some confusion, in the setmapper, between the * size of a set versus the size of the set of sets. * * The size of a pattern set can vary widely based on user preferences, but * the size of the set of sets managed by the setmaster is hard-wired to 4 x * 8. */ #include /* std::map<> */ #include "play/screenset.hpp" /* seq66::screenset and seq */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /** * Provides a class for managing screensets. Much of the action will occur * in the selected play-screen. */ class setmaster { friend class performer; /* a very good friend to have */ friend class setmapper; /* also a good friend/buddy */ private: using container = std::map; /** * Holds the number of rows to use when creating a new set. We could use * the value in setmapper, but we might want the user-interface to create * sets directly at some point. This value along with m_screenset_columns * provides the size of a screenset, which can vary from the default 4 x 8 * via configuration options. */ int m_screenset_rows; /** * Holds the number of columns to use when creating a new set. We could * use the value in setmapper, but we might want the user-interface to * create sets directly at some point. */ int m_screenset_columns; /** * Storage for the number of rows in the layout of the set-master. It * defaults to 4 rows and is actually considered to be a constant. * We removed the const qualifier to avoid issues with containers. */ int m_rows; /* const */ /** * Storage for the number of columns in the layout of the set-master. It * defaults to 8 columns is actually considered to be a constant. * We removed the const qualifier to avoid issues with containers. */ int m_columns; /* const */ /** * Experimental option to swap rows and columns for sets; see the similar * swappage for screensets and its patterns. */ bool m_swap_coordinates; /** * The maximum number of sets supported. The main purpose for this value * is as a sanity check for set lookup, not necessarily for limiting the * number of sets. */ int m_set_count; /** * The highest-numbered set that currently exists, whether empty or not. * Does not include the dummy set. */ int m_highest_set; /** * Holds a vector of screenset objects. This container starts out empty. */ container m_container; private: /** * The base (or default) number of rows in a set, useful in handling the * slot-shift feature and the set-master user-interface. Returned by the * static Rows() function. */ static const int c_rows = screenset::c_default_rows; /** * The canonical and default set size. Used in relation to the * keystrokes used to access sequences (and mute-groups). Returned by the * static Columns() function. */ static const int c_columns = screenset::c_default_columns; public: setmaster (int setrows = c_rows, int setcolumns = c_columns); /* * The move and copy constructors, the move and copy assignment operators, * and the destructors are all compiler generated. */ setmaster (const setmaster &) = default; setmaster & operator = (const setmaster &) = default; setmaster (setmaster &&) = default; setmaster & operator = (setmaster &&) = default; ~setmaster () = default; static int Rows () { return c_rows; } static int Columns () { return c_columns; } static int Size () { return c_rows * c_columns; } bool swap_coordinates () const { return m_swap_coordinates; } std::string set_to_string (screenset::number setno) const; std::string sets_to_string (bool showseqs = true, int limit = 0) const; void show (bool showseqs = true, int limit = 0) const; bool name (screenset::number setno, const std::string & nm) { return m_container.find(setno) != m_container.end() ? m_container.at(setno).name(nm) : false ; } bool is_screenset_active (screenset::number setno) const { return is_screenset_available(setno) ? m_container.at(setno).active() : false ; } bool is_screenset_available (screenset::number setno) const { return m_container.find(setno) != m_container.end(); } bool is_screenset_valid (screenset::number setno) const { return setno >= 0 && setno < m_set_count; } int screenset_count () const { return int(m_container.size()) - 1; /* ignore the dummy set */ } int screenset_active_count () const; int highest_set () const { return m_highest_set; } int screenset_max () const { return m_set_count; } int screenset_index (screenset::number setno) const; bool swap_sets (screenset::number set0, screenset::number set1); bool any_in_edit () const; private: screenset::number grid_to_set (int row, int column) const; bool index_to_grid (screenset::number setno, int & row, int & column); bool inside_set (int row, int column) const { return (row >= 0) && (row < m_rows) && (column >= 0) && (column < m_columns); } void clear () { m_container.clear(); /* unconditional zappage! */ } int rows () const { return m_rows; } int columns () const { return m_columns; } /* * exec_set_function(s) executes a set-handler for each set. * exec_set_function(s,p) runs a set-handler and a slot-handler for each * set. exec_set_function(p) runs the slot-handler for all patterns in * all sets. exec_slot_function() uses the play-screen, and so is in * setmapper, not here. */ bool exec_set_function (screenset::sethandler s); bool exec_set_function (screenset::sethandler s, screenset::slothandler p); bool exec_set_function (screenset::slothandler p); private: bool reset (); container::iterator add_set (screenset::number setno); container::iterator find_by_value (screenset::number setno); bool remove_set (screenset::number setno); bool clear_set (screenset::number setno); /** * Clamps a screenset number to the range of 0 to one less than * m_set_count. */ screenset::number clamp (screenset::number offset) const { if (offset < 0) return 0; else if (offset >= m_set_count) return m_set_count - 1; return offset; } screenset & screen (screenset::number setno); const screenset & screen (screenset::number setno) const; screenset & dummy_screenset () { return m_container.at(screenset::limit()); } const screenset & dummy_screenset () const { return m_container.at(screenset::limit()); } container & set_container () /* for setmapper and performer */ { return m_container; } const container & set_container () const { return m_container; } }; // class setmaster } // namespace seq66 #endif // SEQ66_SETMASTER_HPP /* * setmaster.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/songsummary.hpp ================================================ #if ! defined SEQ66_SONGSUMMARY_HPP #define SEQ66_SONGSUMMARY_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file songsummary.hpp * * This module declares/defines the base class for MIDI files. * * \library seq66 application * \author Chris Ahlstrom * \date 2021-01-22 * \updates 2021-01-24 * \license GNU GPLv2 or above * */ #include #include "play/seq.hpp" /* seq66::seq pattern class */ namespace seq66 { class performer; /** * This class handles dumping a summary of a MIDI file. */ class songsummary { private: /** * The unchanging name of the MIDI file. */ const std::string m_name; public: songsummary (const std::string & name); ~songsummary (); bool write (performer & p, bool doseqspec = true); bool write_song (performer & p); const std::string & name () const { return m_name; } protected: bool write_sequence (std::ofstream & file, seq::pointer s); bool write_header (std::ofstream & file, const performer & p); void write_mute_groups (std::ofstream & file, const performer & p); void write_prop_header ( std::ofstream & file, midilong control_tag, int value ); void write_set_names ( std::ofstream & file, const performer & p ); bool write_proprietary_track (std::ofstream & file, performer & p); void write_bpm ( std::ofstream & file, const performer & p ); void write_mutes ( std::ofstream & file, const performer & p ); void write_global_bg (std::ofstream & file); void write_beat_info ( std::ofstream & file, const performer & p ); }; // class songsummary /* * Free functions related to songsummary. */ extern bool write_song_summary (performer & p, const std::string & fn); } // namespace seq66 #endif // SEQ66_SONGSUMMARY_HPP /* * songsummary.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/play/triggers.hpp ================================================ #if ! defined SEQ66_TRIGGERS_HPP #define SEQ66_TRIGGERS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file triggers.hpp * * This module declares/defines the base class for handling * triggers used with patterns/sequences. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-10-30 * \updates 2023-12-20 * \license GNU GPLv2 or above * * By segregating trigger support into its own module, the sequence class is * a bit easier to understand. */ #include #include #include #include "midi/midibytes.hpp" /* seq66::midipulse alias, etc. */ namespace seq66 { class sequence; class triggers; /** * Indicates that there is no paste-trigger. This is a new feature from the * stazed/seq32 code. */ const int c_no_paste_trigger = (-1); /** * This class hold a single trigger for a sequence object. This class is * used in playback, and instantiations of this class are contained in the * triggers class defined later in this module. */ class trigger { friend class triggers; public: /** * Indicates how/where a trigger will be split. */ enum class splitpoint { middle, /**< Make the split in the middle of the trigger. */ snap, /**< Make the split at the nearest snap point. */ exact /**< Make the split at the exact point clicked. */ }; private: /** * Provides the starting tick for this trigger. Also known as "tick on". */ midipulse m_tick_start; /** * Provides the ending tick for this trigger. Also known as "tick off". */ midipulse m_tick_end; /** * Provides the offset for this trigger. The offset indicates where the * trigger is placed on the "perf roll". */ midipulse m_offset; /** * New feature. An additional byte indicates to transpose this trigger, * to implement the new c_trig_transpose SeqSpec tag. The values range * from 0 to 0x80. 0x00 indicates that transposition is not in effect. * 0x40 indicates that it is in effect, but has a value of 0. Values * from 0x41 to 0x80 indicate transposition from +1 to +63. Values from * 0x3F to 0x01 indicate transposition from -1 to -63. */ int m_transpose; /** * Indicates that the trigger is part of a selection. */ bool m_selected; public: trigger (); trigger ( midipulse tick, midipulse len, midipulse offset, midibyte transpose = 0 ); trigger (const trigger &) = default; trigger & operator = (const trigger &) = default; ~trigger () = default; /** * This operator compares only the m_tick_start members. * * \param rhs * The "right-hand side" of the less-than operation. * * \return * Returns true if m_tick_start is less than rhs's. */ bool operator < (const trigger & rhs) const { return m_tick_start < rhs.m_tick_start; } std::string to_string () const; bool is_valid () const { return m_tick_end > m_tick_start; } /** * \getter m_tick_end and m_tick_start. * We've seen that some of the calculations of trigger length are * wrong, being 1 tick less than the true length of the trigger in * pulses. This function calculates trigger length the correct way. */ midipulse length () const { return m_tick_end - m_tick_start + 1; } midipulse tick_start () const { return m_tick_start; } void tick_start (midipulse s) { m_tick_start = s; } void increment_tick_start (midipulse s) { m_tick_start += s; } void decrement_tick_start (midipulse s) { m_tick_start -= s; } /** * Test if the input parameters indicate we are touching a trigger * transition. If we have reached a new chunk of drawn pattern in the * Song data, and we are not recording, we unset the playback-block on * this sequence's events. * * \param s * The starting tick. * * \param e * The ending tick. */ bool at_trigger_transition (midipulse s, midipulse e) { return ( s == m_tick_start || e == m_tick_start || s == m_tick_end || e == m_tick_end ); } bool covers (midipulse tick) { return tick >= m_tick_start && tick <= m_tick_end; } midipulse tick_end () const { return m_tick_end; } void tick_end (midipulse e) { m_tick_end = e; } void increment_tick_end (midipulse s) { m_tick_end += s; } void decrement_tick_end (midipulse s) { m_tick_end -= s; } midipulse offset () const { return m_offset; } void offset (midipulse o) { m_offset = o; } void increment_offset (midipulse s) { m_offset += s; } void decrement_offset (midipulse s) { m_offset -= s; } /** * This function maps 0x00 to 0, values less than 0x40 to transposing * downward in semitones, and values greater than 0x40, but less than * 0x80, to transposing upward in semitones. Value 0x40 is not used. We * can transpose up and down by 63 semitones, or a little more than 5 * octaves. */ midibyte transpose_byte () const { return m_transpose == 0 ? 0 : midibyte(m_transpose + 0x40); } void transpose_byte (midibyte t) /* when reading a file */ { if (t > 0x00 && t < 0x80) m_transpose = t - 0x40; else m_transpose = 0; /* no transpose */ } int transpose () const { return m_transpose; } bool transposed () const { return m_transpose != 0; } static int datasize (midilong seqspec); void transpose (int t) /* to modify a trigger */ { if (t > (-64) && t < 64) /* -63 to 0 to +63 */ m_transpose = t; } bool selected () const { return m_selected; } void selected (bool s) { m_selected = s; } /** * Encapsulates a qperfroll trigger calculation. */ midipulse trigger_marker (midipulse len) { return m_tick_start - (m_tick_start % len) + (m_offset % len) - len; } private: void rescale (int oldppqn, int newppqn); }; // class trigger /** * The triggers class is a receptable the triggers that can be used with a * sequence object. */ class triggers { friend class midi_vector_base; friend class midifile; friend class sequence; public: /** * Provides an alias introduced by Stazed to make the trigger grow/move * code easier to understand. */ enum class grow { start = 0, /**< Grow the start of the trigger. */ end = 1, /**< Grow the end of the trigger. */ move = 2 /**< Move the entire trigger block. */ }; private: /** * Exposes the triggers type, currently needed for midi_vector_base only. * We might convert to using a vector instead of a list. */ using container = std::vector; /** * Provides a stack for use with the undo/redo features of the * trigger support. */ using stack = std::stack; private: /** * Holds a reference to the parent sequence object that owns this trigger * object. */ sequence & m_parent; /** * This list holds the current pattern/triggers events. */ container m_triggers; /** * Holds a count of the selected triggers, for better control over * selections. */ int m_number_selected; /** * This item holds a single copied trigger, to be pasted later. */ trigger m_clipboard; /** * Handles the undo list for a series of operations on triggers. */ stack m_undo_stack; /** * Handles the redo list for a series of operations on triggers. */ stack m_redo_stack; /** * An iterator for cycling through the triggers during drawing. */ container::iterator m_draw_iterator; /** * Set to true if there is an active trigger in the trigger clipboard. */ bool m_trigger_copied; /** * The tick point for pasting. Set to -1 if not in force. This is a new * feature from stazed's Seq32 project. */ midipulse m_paste_tick; /** * Holds the value of the PPQN from the parent sequence, for easy access. * This should not change, but we have to set it after construction, and * so we provide a setter for it, set_ppqn(), called by the sequence * constructor. */ int m_ppqn; /** * Holds the value of the length from the parent sequence, for easy access. * This might change, we're not yet sure. */ int m_length; public: triggers (sequence & parent); ~triggers () = default; triggers (const triggers & rhs) = default; triggers & operator = (const triggers & rhs); std::string to_string () const; bool change_ppqn (int p); /** * \setter m_ppqn * We have to set this value after construction for best safety. */ void set_ppqn (int ppqn) { if (ppqn > 0) m_ppqn = ppqn; } /** * \setter m_length * We have to set this value after construction for best safety. * Also, there a chance that the length of the parent might change * from time to time. Currently, only the sequence constructor and * midifile call this function. */ void set_length (int len) { if (len > 0) m_length = len; } const container & triggerlist () const { return m_triggers; } container & triggerlist () { return m_triggers; } int count () const { return int(m_triggers.size()); } int datasize (midilong seqspec) const; bool any_transposed () const; int number_selected () const { return m_number_selected; } void push_undo (); void pop_undo (); void pop_redo (); void print (const std::string & seqname) const; bool play ( midipulse & starttick, midipulse & endtick, int & transpose, bool resume = false ); void add ( midipulse tick, midipulse len, midipulse offset = 0, midibyte transpose = 0, bool adjustoffset = true ); void adjust_offsets_to_length (midipulse newlen); bool split (midipulse tick, trigger::splitpoint splittype); bool grow_trigger (midipulse tickfrom, midipulse tickto, midipulse length); const trigger & find_trigger (midipulse tick) const; const trigger & find_trigger_by_index (int index) const; bool remove (midipulse tick); bool get_state (midipulse tick) const; bool transpose (midipulse tick, int transposition); bool select (midipulse tick); bool unselect (midipulse tick); bool unselect (); bool intersect (midipulse position, midipulse & start, midipulse & end); bool intersect (midipulse position); bool remove_selected (); void copy_selected (); void paste (midipulse paste_tick = c_no_paste_trigger); bool move_selected ( midipulse tick, bool adjustoffset, triggers::grow which = triggers::grow::move ); midipulse get_selected_start (); midipulse get_selected_end (); midipulse get_maximum () const; bool move ( midipulse starttick, midipulse distance, bool direction, bool single = true ); void move_split ( midipulse starttick, midipulse distance, bool direction ); void copy (midipulse starttick, midipulse distance); /** * Clears the whole list of triggers, and zeroes the number selected. */ void clear () { m_triggers.clear(); m_number_selected = 0; } trigger next (); /** * Sets the draw-trigger iterator to the beginning of the trigger list. */ void reset_draw_trigger_marker () { m_draw_iterator = m_triggers.begin(); } void set_trigger_paste_tick (midipulse tick) { m_paste_tick = tick; } midipulse get_trigger_paste_tick () const { return m_paste_tick; } private: void sort (); bool split (trigger & t, midipulse splittick); bool rescale (int oldppqn, int newppqn); midipulse adjust_offset (midipulse offset); void offset_selected (midipulse tick, grow editmode); void select (trigger & t, bool count = true); void unselect (trigger & t, bool count = true); bool cend (container::iterator & evi) const // no can do const_iterator { return evi == m_triggers.cend(); } }; // class triggers } // namespace seq66 #endif // SEQ66_TRIGGERS_HPP /* * triggers.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/seq66_features.h ================================================ #if ! defined SEQ66_FEATURES_H #define SEQ66_FEATURES_H /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file seq66_features.h * * This module summarizes or defines all of the configure and build-time * options available for Seq66. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-24 * \updates 2026-04-17 * \license GNU GPLv2 or above * * Some options (the "USE_xxx" options) specify experimental and * unimplemented features. Some options (the "SEQ66_xxx" options) might be * experimental, or not, but are definitely supported, if defined, and may * become configure-time options. * * Some options are available (or can be disabled) by running the * "configure" script generated using the configure.ac file. These options * are things that a normal user or a seq24 aficianado might want to * disable. They are defined as desired, in the auto-generated * seq66-config.h file in the top-level "include" directory. * * The rest of the options can be modified only by editing the source code * (soon to be this file) to enable or disable features. These options are * those that we feel more strongly about. */ #include "seq66-config.h" /* automake-generated or for qmake */ #include "seq66_platform_macros.h" /* indicates the build platform */ #if defined SEQ66_PLATFORM_WINDOWS #if ! defined STDIN_FILENO #define STDIN_FILENO 0 #define STDOUT_FILENO 1 #define STDERR_FILENO 2 #endif #endif /** * If set, allow changing the key in the pattern editor to be * reflected in the name of the key, changing from "C" to whatever * key is selected. */ #define SEQ66_SHOW_SELECTED_KEY_OCTAVE /** * In the pattern editor's data pane, we can show the full GM name * of the program-change, instead of just the number. */ #define SEQ66_SHOW_GM_PROGRAM_NAME /** * In the pattern editor's data pane, we can show the full GM name * of the drum-note. It gets too cramped, though. */ #undef SEQ66_SHOW_GM_DRUM_NAME /** * Trying to make configuration copying more flexible. We want to use * additional non-standard file extensions (e.g. ".notemap" versus ".drums" * when iterating through the configuration files, Define this value to * define alternates to copy_configuration() and delete_configuration. */ #define SEQ66_KEEP_RC_FILE_LIST /** * We're testing how to properly invert color palettes. */ #undef SEQ66_PROVIDE_AUTO_COLOR_INVERSION /* experimental, investigative */ /** * If defined, a button to show or hide the main menu bar and some * of the layouts is available on the main window. */ #define SEQ66_USE_SHOW_HIDE_BUTTON /** * Make port-mapping the default. */ #define SEQ66_USE_DEFAULT_PORT_MAPPING /** * For issue #100, this macro enables using our new ring_buffer instead of * jack_ringbuffer_t. We no longer attempt to add the timestamp to the JACK * ringbuffer, even if this macro is disabled. Too many side issues, too * much code, so disabling this macro preserves the old behavior. */ #define SEQ66_USE_MIDI_MESSAGE_RINGBUFFER /** * Choose between C++ or bare pthreads. Affects only the performer class. * For condition_variables and mutexes, we are forced to stick with the * pthreads versions, because the C++ versions can cause deadlock with the * GUI in the current Seq66 processing. This option is now permanent and * needs no macro. * * #define SEQ66_USE_STD_THREADING */ /** * Adds more SYSEX processing, plus the ability to read SYSEX information * from a file. This needs a fair amount of testing and debugging. * * The former is defined in seq66_features.h (included by basic_macros.h), * but what about the latter? Sequencer64 defines the former!!! * * We cannot use this in portmidi. Code exists to support it, but it is not * called, and but where did we get it??? It's merely noted in the GitHub * portmidi repository, not coded there. */ #undef SEQ66_PORTMIDI_SYSEX_PROCESSING /** * Configure-time options. * * - SEQ66_HAVE_LIBASOUND * - SEQ66_JACK_SESSION * - SEQ66_JACK_SUPPORT * - SEQ66_NSM_SUPPORT */ /* * Edit-time (permanent) options. */ /** * Not working, removed. A very tough problem. The idea * is to go into an auto-screen-set mode via a menu entry, where the current * screen-set is queued for muting, while the next selected screen-set is * queued for unmuting. * * #undef SEQ66_USE_AUTO_SCREENSET_QUEUE */ /** * An option to make the main-window time-indicator fancier by using alpha * fade, at the expense of CPU cycles. */ #undef SEQ66_USE_METRONOME_FADE /** * If defined, then the right-click popup menu of each pattern editor grid * slot includes more nesting, to save space. */ #define SEQ66_USE_COLLAPSED_SLOT_POPUP_MENU /** * If defined, draw pitchbend as dots in the data pane. Looks kind of * raggy, so undefined. */ #undef SEQ66_DRAW_PITCHBEND_AS_DOT #endif // SEQ66_FEATURES_H /* * seq66_features.h * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: libseq66/include/seq66_features.hpp ================================================ #if ! defined SEQ66_FEATURES_HPP #define SEQ66_FEATURES_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file seq66_features.hpp * * This module summarizes or defines all of the configure and build-time * options available for Seq66. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-24 * \updates 2023-11-06 * \license GNU GPLv2 or above * * Provides some useful functions for displaying information about the * application. More flexible than macros. * * In addition, we will eventually provide a global setter and getter for * application context so that we have provide a little bit of * context-sensitive help, starting with Help / Keystrokes. * * Also see the seq66_features.h module. */ #include #include "seq66_features.h" /* the C-compatible definitions */ /* * This is the main namespace of Seq66. Do not attempt to * Doxygenate the documentation here; it breaks Doxygen. */ namespace seq66 { /** * A kind of tribool, it adds a "flip" option. Better than sending * multiple boolean parameters. */ enum class toggler { off, /**< A request to turn a state boolean to false. */ on, /**< A request to turn a state boolean to true. */ flip /**< A request to toggle a state boolean. */ }; /** * An indicator of the message level. Used in the message functions defined * in the basic_macros C++ modules, and elsewhere to specify the level. * * - none. Used only to rever back to no color in message functions. * - info. Messages that should appear only in verbose mode. * - warn. Message about problems or statuses that are minor. * - error. More serious problems. * - status. Messages that show the progesss of the application even * when not in verbose mode. * - session. Messages that need to stand out in session management. * - debug. Message that should appear only in investigate mode. */ enum class msglevel { none, /* default console color */ info, /* blue */ warn, /* yellow */ error, /* red */ status, /* green */ session, /* cyan */ debug /* debug */ }; /* * Global (free) functions. */ extern void set_pane_focus (const std::string & v); extern void set_alsa_version (const std::string & v); extern void set_jack_version (const std::string & v); extern void set_qt_version (const std::string & v); extern void set_app_build_os (const std::string & abuild_os); extern void set_app_build_issue (const std::string & abuild_issue); extern void set_app_engine (const std::string & aengine); extern void set_app_name (const std::string & aname); extern void set_app_path (const std::string & apath); extern void set_app_type (const std::string & atype); extern void set_app_cli (bool iscli); extern void set_arg_0 (const std::string & arg); extern void set_client_name (const std::string & cname); extern void set_package_name (const std::string & pname); extern const std::string & seq_pane_focus (); extern const std::string & seq_app_name (); extern const std::string & seq_app_path (); extern const std::string & seq_app_tag (); extern const std::string & seq_app_type (); extern bool seq_app_cli (); extern const std::string & seq_default_logfile_name (); extern const std::string & seq_arg_0 (); extern const std::string & seq_client_name (); extern const std::string & seq_client_short (); extern const std::string & seq_config_name (); extern const std::string & seq_config_dir_name (); extern const std::string & seq_icon_name (); extern bool is_a_tty (int fd); extern std::string seq_client_tag (msglevel el = msglevel::none); extern const std::string & seq_package_name (); extern const std::string & seq_api_version (); extern const std::string & seq_api_subdirectory (); extern const std::string & seq_version (); extern const std::string & seq_version_text (); extern std::string session_tag (const std::string & refinement = ""); extern std::string seq_build_details (); } // namespace seq66 #endif // SEQ66_FEATURES_HPP /* * seq66_features.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/seq66_platform_macros.h ================================================ #if ! defined SEQ66_PLATFORM_MACROS_H #define SEQ66_PLATFORM_MACROS_H /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file seq66_platform_macros.h * * Provides a rationale and a set of macros to make compile-time * decisions covering Windows versus Linux, GNU versus Microsoft, and * MINGW versus GNU. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2025-02-02 * \license GNU GPLv2 or above * * Copyright (C) 2013-2025 Chris Ahlstrom * * We need a uniform way to specify OS and compiler features without * littering the code with macros from disparate compilers. Put all * the compiler-specific stuff here to define "PLATFORM" macros. * * Determining useful macros: * * - GNU: cpp -dM myheaderfile * * Settings to distinguish, based on compiler-supplied macros: * * - Platform macros (set in nar-maven-plugin's aol.properties): * - Windows * - Linux * - MacOSX * - Platform macros (in the absense of Windows, Linux macros): * - SEQ66_PLATFORM_WINDOWS * - SEQ66_PLATFORM_LINUX * - SEQ66_PLATFORM_FREEBSD * - SEQ66_PLATFORM_MACOSX * - SEQ66_PLATFORM_UNIX * - SEQ66_PLATFORM_FREEBSD * - SEQ66_PLATFORM_MACOSX * - SEQ66_PLATFORM_IPHONE_OS (to do!) * - SEQ66_PLATFORM_UNIX * - Architecture size macros: * - SEQ66_PLATFORM_32_BIT * - SEQ66_PLATFORM_64_BIT * - Debugging macros: * - SEQ66_PLATFORM_DEBUG * - SEQ66_PLATFORM_RELEASE * - Compiler: * - SEQ66_PLATFORM_MSVC (alternative to _MSC_VER) * - SEQ66_PLATFORM_CYGWIN etc. * - SEQ66_PLATFORM_GLOB * - SEQ66_PLATFORM_GNU * - SEQ66_PLATFORM_XSI * - SEQ66_PLATFORM_MINGW * - SEQ66_PLATFORM_MING_OR_WINDOWS * - SEQ66_PLATFORM_MING_OR_UNIX * - SEQ66_PLATFORM_CYGWIN etc. * - SEQ66_PLATFORM_MSVC (alternative to _MSC_VER) * - SEQ66_PLATFORM_XSI * - API: * - SEQ66_PLATFORM_POSIX_API (alternative to POSIX) * - Language: * - SEQ66_PLATFORM_CPP_11 * - SEQ66_PLATFORM_CPP_14 * - SEQ66_PLATFORM_CPP_17 * - Other: * - SEQ66_PLATFORM_POSIX_ERROR * - SEQ66_PLATFORM_POSIX_SUCCESS * - SEQ66_PLATFORM_GLOB * * http://stackoverflow.com/questions/11053960/ * how-are-the-cplusplus-directive-defined-in-various-compilers * * The 199711L stands for Year=1997, Month = 11 (i.e., November of 1997) -- * the date when the committee approved the standard that the rest of the * ISO approved in early 1998. * * For the 2003 standard, there were few enough changes that the committee * (apparently) decided to leave that value unchanged. * * For the 2011 standard, it's defined as 201103L, (year=2011, month = 03), * meaning that the committee approved the standard as finalized in March * of 2011. * * For the 2014 standard, it's defined as 201402L, interpreted the same way * as above (February 2014). * * For the 2017 standard, it's defined as 201703L, interpreted the same way * as above (March 2017). * * Before the original standard was approved, quite a few compilers * normally defined it as 0 (or just an empty definition like #define * __cplusplus) to signify "not-conforming". When asked for their strictest * conformance, many defined it to 1. Ancient news! */ #undef SEQ66_PLATFORM_32_BIT #undef SEQ66_PLATFORM_64_BIT #undef SEQ66_PLATFORM_CLANG #undef SEQ66_PLATFORM_CPP_11 #undef SEQ66_PLATFORM_CPP_14 #undef SEQ66_PLATFORM_CPP_17 #undef SEQ66_PLATFORM_CYGWIN #undef SEQ66_PLATFORM_DEBUG #undef SEQ66_PLATFORM_FREEBSD #undef SEQ66_PLATFORM_GLOB #undef SEQ66_PLATFORM_GNU #undef SEQ66_PLATFORM_IPHONE_OS #undef SEQ66_PLATFORM_LINUX #undef SEQ66_PLATFORM_MACOSX #undef SEQ66_PLATFORM_MINGW #undef SEQ66_PLATFORM_MING_OR_UNIX #undef SEQ66_PLATFORM_MING_OR_WINDOWS #undef SEQ66_PLATFORM_MSVC #undef SEQ66_PLATFORM_POSIX_API #undef SEQ66_PLATFORM_POSIX_ERROR #undef SEQ66_PLATFORM_POSIX_SUCCESS #undef SEQ66_PLATFORM_RELEASE #undef SEQ66_PLATFORM_UNIX #undef SEQ66_PLATFORM_WIN32_STRICT #undef SEQ66_PLATFORM_WINDOWS #undef SEQ66_PLATFORM_WINDOWS_32 #undef SEQ66_PLATFORM_WINDOWS_64 #undef SEQ66_PLATFORM_WINDOWS_UNICODE #undef SEQ66_PLATFORM_XSI /** * Provides a "Windows" macro, in case the environment doesn't provide * it. This macro is defined if not already defined and _WIN32 or WIN32 * are encountered. */ #if defined Windows /* defined by nar-maven-plugin */ #define SEQ66_PLATFORM_WINDOWS #else #if defined _WIN32 /* defined by Microsoft compiler */ #define SEQ66_PLATFORM_WINDOWS_32 #define SEQ66_PLATFORM_WINDOWS #define Windows #define SEQ66_PLATFORM_32_BIT #else #if defined WIN32 /* defined by Mingw compiler */ #define SEQ66_PLATFORM_WINDOWS_32 #define SEQ66_PLATFORM_WINDOWS #define Windows #define SEQ66_PLATFORM_32_BIT #endif #endif #if defined _WIN64 /* defined by Microsoft compiler */ #define SEQ66_PLATFORM_WINDOWS_64 #define SEQ66_PLATFORM_WINDOWS #define Windows #define SEQ66_PLATFORM_64_BIT #else #if defined WIN64 /* defined by Mingw compiler */ #define SEQ66_PLATFORM_WINDOWS_64 #define SEQ66_PLATFORM_WINDOWS #define Windows #define SEQ66_PLATFORM_64_BIT #endif #endif #endif /** * FreeBSD macros. */ #if defined __FreeBSD__ #define SEQ66_PLATFORM_FREEBSD #define SEQ66_PLATFORM_PTHREADS #define SEQ66_PLATFORM_UNIX #endif /** * Provides a "Linux" macro, in case the environment doesn't provide it. * This macro is defined if not already defined. */ #if defined Linux /* defined by nar-maven-plugin */ #define SEQ66_PLATFORM_LINUX #else #if defined __linux__ /* defined by the GNU compiler */ #define Linux #define SEQ66_PLATFORM_LINUX #endif #endif #if defined SEQ66_PLATFORM_LINUX #if ! defined POSIX #define POSIX /* defined for legacy code purposes */ #endif #define SEQ66_PLATFORM_POSIX_API #define SEQ66_PLATFORM_PTHREADS #define SEQ66_PLATFORM_UNIX #endif /* SEQ66_PLATFORM_LINUX */ /** * Provides a "MacOSX" macro, in case the environment doesn't provide it. * This macro is defined if not already defined and __APPLE__ and * __MACH__ are encountered. */ #if defined MacOSX /* defined by the nar-maven-plugin */ #define SEQ66_PLATFORM_MACOSX #else #if defined __APPLE__ && defined __MACH__ /* defined by Apple compiler */ #define SEQ66_PLATFORM_MACOSX #define MacOSX #endif #endif #if defined SEQ66_PLATFORM_MACOSX #define SEQ66_PLATFORM_UNIX #endif /* * To do: detect the iOS platform. * * #define SEQ66_PLATFORM_IPHONE_OS */ #if defined SEQ66_PLATFORM_UNIX #define SEQ66_PLATFORM_POSIX_API #define SEQ66_PLATFORM_PTHREADS #if ! defined POSIX #define POSIX /* defined for legacy code purposes */ #endif #endif /** * Provides macros that mean 32-bit, and only 32-bit Windows. For * example, in Windows, _WIN32 is defined for both 32- and 64-bit * systems, because Microsoft didn't want to break people's 32-bit code. * So we need a specific macro. * * - SEQ66_PLATFORM_32_BIT is defined on all platforms. * - WIN32 is defined on Windows platforms. * * Prefer the former macro. The second is defined only for legacy * purposes for Windows builds, and might eventually disappear. */ #if defined SEQ66_PLATFORM_WINDWS #if defined _WIN32 && ! defined _WIN64 #if ! defined WIN32 #define WIN32 /* defined for legacy purposes */ #endif #if ! defined SEQ66_PLATFORM_32_BIT #define SEQ66_PLATFORM_32_BIT #endif #endif #endif /* SEQ66_PLATFORM_WINDOWS */ /** * Provides macros that mean 64-bit, and only 64-bit. * * - SEQ66_PLATFORM_64_BIT is defined on all platforms. * - WIN64 is defined on Windows platforms. * * Prefer the former macro. The second is defined only for legacy * purposes for Windows builds, and might eventually disappear. * */ #if defined SEQ66_PLATFORM_WINDWS #if defined _WIN64 #if ! defined WIN64 #define WIN64 #endif #if ! defined SEQ66_PLATFORM_64_BIT #define SEQ66_PLATFORM_64_BIT #endif #endif #endif /* SEQ66_PLATFORM_WINDOWS */ /** * Provides macros that mean 64-bit versus 32-bit when gcc or g++ are * used. This can occur on Linux and other systems, and with mingw on * Windows. * * - SEQ66_PLATFORM_64_BIT is defined on all platforms. * * Prefer the former macro. The second is defined only for legacy * purposes for Windows builds, and might eventually disappear. */ #if defined __GNUC__ #if defined __x86_64__ || __ppc64__ #if ! defined SEQ66_PLATFORM_64_BIT #define SEQ66_PLATFORM_64_BIT #endif #else #if ! defined SEQ66_PLATFORM_32_BIT #define SEQ66_PLATFORM_32_BIT #endif #endif #endif /** * Provides macros that indicate if Microsoft C/C++ versus GNU are being * used. THe compiler being used normally provides test macros for itself. * * - SEQ66_PLATFORM_CLANG (replaces clang) * - SEQ66_PLATFORM_MSVC (replaces _MSC_VER) * - SEQ66_PLATFORM_GNU (replaces __GNUC__) * - SEQ66_PLATFORM_MINGW (replaces __MINGW32__) * - SEQ66_PLATFORM_CYGWIN */ #if defined __clang__ #define SEQ66_PLATFORM_CLANG #endif #if defined _MSC_VER #define SEQ66_PLATFORM_MSVC #define SEQ66_PLATFORM_WINDOWS #endif #if defined __GNUC__ #define SEQ66_PLATFORM_GNU #endif #if (_POSIX_C_SOURCE >= 200112L) && ! _GNU_SOURCE #define SEQ66_PLATFORM_XSI /* * Hit this one compiling in Qt Creator on Linux. * * #error XSI defined, this is just a test */ #endif /* * SEQ66_PLATFORM_WIN32_STRICT replaces checks for WIN32 with CYGWIN not * defined. */ #if defined __CYGWIN__ #if defined __CYGWIN32__ #define SEQ66_PLATFORM_CYGWIN #define SEQ66_PLATFORM_WINDOWS_32 #elif defined __CYGWIN64__ #endif #define SEQ66_PLATFORM_CYGWIN #define SEQ66_PLATFORM_WINDOWS_64 #else #if defined SEQ66_PLATFORM_WINDOWS_32 #define SEQ66_PLATFORM_WIN32_STRICT #endif #endif #if defined __MINGW32__ #define SEQ66_PLATFORM_MINGW #define SEQ66_PLATFORM_WINDOWS #define SEQ66_PLATFORM_WINDOWS_32 #endif #if defined __MINGW64__ #define SEQ66_PLATFORM_MINGW #define SEQ66_PLATFORM_WINDOWS #define SEQ66_PLATFORM_WINDOWS_64 #endif #if defined SEQ66_PLATFORM_WINDOWS /* * Without this #define, the InitializeCriticalSectionAndSpinCount() function * is undefined. This version level means "Windows 2000 and higher". * For Windows 10, the value would be 0x0A00. */ #if ! defined _WIN32_WINNT #define _WIN32_WINNT 0x0500 #endif #if defined UNICODE || defined _UNICODE #define SEQ66_PLATFORM_WINDOWS_UNICODE #endif #endif // defined SEQ66_PLATFORM_WINDOWS /** * Provides a way to flag unused parameters at each "usage", without disabling * them globally. Use it like this: * * void foo(int UNUSED(bar)) { ... } * static void UNUSED_FUNCTION(foo)(int bar) { ... } * * The UNUSED macro won't work for arguments which contain parenthesis, * so an argument like float (*coords)[3] one cannot do, * * float UNUSED((*coords)[3]) or float (*UNUSED(coords))[3]. * * This is the only downside to the UNUSED macro; in these cases fall back to * * (void) coords; * * Another possible definition is casting the unused value to void in the * function body. */ #if defined __GNUC__ #define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) #else #define UNUSED(x) UNUSED_ ## x #endif #if defined __GNUC__ #define UNUSED_FUNCTION(x) __attribute__((__unused__)) UNUSED_ ## x #else #define UNUSED_FUNCTION(x) UNUSED_ ## x #endif #define UNUSED_VOID(x) (void) (x) /** * Provides macros to indicate the level standards support for some key * cases. We may have to play with this a bit to get it right. The main * use-case right now is in avoiding defining the nullptr macro in C++11. * * - SEQ66_PLATFORM_CPP_11 */ #if defined SEQ66_PLATFORM_MSVC #if _MSC_VER >= 1700 /* __cplusplus value doesn't work, MS! */ #define SEQ66_PLATFORM_CPP_11 #endif #else #if __cplusplus >= 201103L /* i.e. C++11 */ #define SEQ66_PLATFORM_CPP_11 #endif #if __cplusplus >= 201402L /* i.e. C++14 */ #define SEQ66_PLATFORM_CPP_14 #endif #if __cplusplus >= 201703L /* i.e. C++17 */ #define SEQ66_PLATFORM_CPP_17 #endif #endif /** * Kind of a Windows-with-MingW-matching-Visual-Studio macro. */ #if defined SEQ66_PLATFORM_MSVC || defined SEQ66_PLATFORM_MINGW #define SEQ66_PLATFORM_MING_OR_WINDOWS #endif /** * A UNIX or MingW macro. */ #if defined SEQ66_PLATFORM_UNIX || defined SEQ66_PLATFORM_MINGW #define SEQ66_PLATFORM_MING_OR_UNIX #endif /** * Provides macros that mean "debugging enabled". * * - SEQ66_PLATFORM_DEBUG or SEQ66_PLATFORM_RELEASE * - DEBUG or NDEBUG for legacy usage * * Prefer the former macro. The second is defined only for legacy * purposes for Windows builds, and might eventually disappear. */ #if ! defined SEQ66_PLATFORM_DEBUG #if defined DEBUG || defined _DEBUG || defined _DEBUG_ || \ defined __DEBUG || defined __DEBUG__ #define SEQ66_PLATFORM_DEBUG #endif #endif #if ! defined SEQ66_PLATFORM_DEBUG && ! defined SEQ66_PLATFORM_RELEASE #define SEQ66_PLATFORM_RELEASE #endif /** * Provides a check for error return codes from applications. It is a * non-error value for most POSIX-conformant functions. This macro defines * the integer value returned by many POSIX functions when they succeed -- * zero (0). * * \note * Rather than testing this value directory, the macro functions * is_posix_success() and not_posix_success() should be used. See the * descriptions of those macros for more information. */ #if ! defined SEQ66_PLATFORM_POSIX_SUCCESS #define SEQ66_PLATFORM_POSIX_SUCCESS 0 #endif /** * SEQ66_PLATFORM_POSIX_ERROR is returned from a string function when it has * processed an error. It indicates that an error is in force. Normally, * the caller then uses this indicator to set a class-based error message. * This macro defines the integer value returned by many POSIX functions when * they fail -- minus one (-1). The EXIT_FAILURE and * SEQ66_PLATFORM_POSIX_ERROR macros also have the same value. * * \note * Rather than testing this value directory, the macro functions * is_posix_error() and not_posix_error() should be used. See the * descriptions of those macros for more information. */ #if ! defined SEQ66_PLATFORM_POSIX_ERROR #define SEQ66_PLATFORM_POSIX_ERROR (-1) #endif /** * Set if the platform supports an implementation of glob(3). * Right now, on our Windows Mingw setup, it is not supported. */ #if defined SEQ66_PLATFORM_UNIX // || defined SEQ66_PLATFORM_MINGW #define SEQ66_PLATFORM_GLOB #endif /** * This macro tests the integer value against SEQ66_PLATFORM_POSIX_SUCCESS. * Other related macros are: * * - is_posix_success() * - is_posix_error() * - not_posix_success() * - not_posix_error() * - set_posix_success() * - set_posix_error() * * \note * - Some functions return values other than SEQ66_PLATFORM_POSIX_ERROR * when an error occurs. * - Some functions return values other than * SEQ66_PLATFORM_POSIX_SUCCESS when the function succeeds. * - Please refer to the online documentation for these quixotic * functions, and decide which macro one want to use for the test, if * any. * - In some case, one might want to use a clearer test. For example, * the socket functions return a result that is * SEQ66_PLATFORM_POSIX_ERROR (-1) if the function fails, but * non-zero integer values are returned if the function succeeds. * For these functions, the is_valid_socket() and not_valid_socket() * macros are much more appropriate to use. * *//*-------------------------------------------------------------------------*/ #if ! defined is_posix_success #define is_posix_success(x) ((x) == SEQ66_PLATFORM_POSIX_SUCCESS) #endif /** * This macro tests the integer value against SEQ66_PLATFORM_POSIX_ERROR (-1). */ #if ! defined is_posix_error #define is_posix_error(x) ((x) == SEQ66_PLATFORM_POSIX_ERROR) #endif /** * This macro tests the integer value against SEQ66_PLATFORM_POSIX_SUCCESS (0). */ #if ! defined not_posix_success #define not_posix_success(x) ((x) != SEQ66_PLATFORM_POSIX_SUCCESS) #endif /** * This macro tests the integer value against SEQ66_PLATFORM_POSIX_ERROR (-1). */ #if ! defined not_posix_error #define not_posix_error(x) ((x) != SEQ66_PLATFORM_POSIX_ERROR) #endif /** * This macro set the integer value to SEQ66_PLATFORM_POSIX_SUCCESS (0). The * parameter must be an lvalue, as the assignment operator is used. */ #if ! defined set_posix_success #define set_posix_success(x) ((x) = SEQ66_PLATFORM_POSIX_SUCCESS) #endif /** * This macro set the integer value to SEQ66_PLATFORM_POSIX_ERROR (-1). The * parameter must be an lvalue, as the assignment operator is used. */ #if ! defined set_posix_error #define set_posix_error(x) ((x) = SEQ66_PLATFORM_POSIX_ERROR) #endif #endif /* SEQ66_PLATFORM_MACROS_H */ /* * seq66_platform_macros.h * * vim: ts=4 sw=4 wm=4 et ft=c */ ================================================ FILE: libseq66/include/sessions/clinsmanager.hpp ================================================ #if ! defined SEQ66_CLINSMANAGER_HPP #define SEQ66_CLINSMANAGER_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file clinsmanager.hpp * * This module declares/defines the main module for the JACK/ALSA "qt5" * implementation of this application. * * \library clinsmanager application * \author Chris Ahlstrom * \date 2020-08-31 * \updates 2024-11-06 * \license GNU GPLv2 or above * * Provides a base class that can be used to manage the command-line version * of Seq66, as well as a helper for the Qt version. Hence, it is in * libseq66 rather than libsessions. If NSM support is not part of the * build, then nsmclient is type-defined to be void. (We might refactor the * concept out completely at some point.) */ #include /* std::unique_ptr, shared_ptr<> */ #include "sessions/smanager.hpp" /* seq66::smanager */ #if defined SEQ66_NSM_SUPPORT #include "nsm/nsmclient.hpp" /* seq66::nsmclient */ #endif /** * The potential list of capabilities is * * - switch: Client is capable of responding to multiple `open` * messages without restarting. * - dirty: Client knows when it has unsaved changes. * - progress: Client can send progress updates during time-consuming * operations. * - message: Client can send textual status updates. * - optional-gui: Client has an optional GUI. */ const std::string c_cli_nsm_capabilities {":message:dirty:"}; namespace seq66 { /** * Provides command-line and user-interface support for session management. If * NSM support is not built in, there is still some minor management tasks that * can be done. */ class clinsmanager : public smanager { private: #if defined SEQ66_NSM_SUPPORT /** * The optional NSM client. This item is not in the base class, * smanager, because that class is meant to allow the option of building * without NSM, but still simplifying the application's main() function. */ std::unique_ptr m_nsm_client; #endif /** * This value indicates that the nsmclient is active. It is roughly * similar in meaning to the "global" value usr().in_nsm_session(). */ bool m_nsm_active; /** * Holds a copy of the user-interface redraw rate. */ int m_poll_period_ms; public: clinsmanager (const std::string & caps = c_cli_nsm_capabilities); clinsmanager (const clinsmanager &) = delete; clinsmanager & operator = (const clinsmanager &) = delete; virtual ~clinsmanager () = default; #if defined SEQ66_NSM_SUPPORT nsmclient * nsm_client () { return m_nsm_client.get(); } #endif bool nsm_active () const { return m_nsm_active; } void nsm_active (bool flag) { m_nsm_active = flag; } virtual bool create_session ( int argc = 0, char * argv [] = nullptr ) override; virtual bool close_session (std::string & msg, bool ok = true) override; virtual bool save_session (std::string & msg, bool ok = true) override; virtual bool create_project ( int argc, char * argv [], const std::string & path ) override; virtual void show_error ( const std::string & tag, const std::string & msg ) const override; virtual bool run () override; virtual void session_manager_name (const std::string & mgrname) override; virtual void session_manager_path (const std::string & pathname) override; virtual void session_display_name (const std::string & dispname) override; virtual void session_client_id (const std::string & clid) override; private: bool read_configuration ( int argc, char * argv [], const std::string & cfgfilepath, const std::string & midifilepath ); bool detect_session (std::string & url); }; // class clinsmanager } // namespace seq66 #endif // SEQ66_CLINSMANAGER_HPP /* * clinsmanager.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/sessions/smanager.hpp ================================================ #if ! defined SEQ66_SMANAGER_HPP #define SEQ66_SMANAGER_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file smanager.hpp * * This module declares/defines the base class for handling many facets * of administering a session of seq66 usage. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-05-30 * \updates 2025-02-19 * \license GNU GPLv2 or above * * This class provides a process for starting, running, restarting, and * closing down the Seq66 application, even without session management. One * of the goals is to be able to reload the performer when the set of MIDI * devices in the system changes. */ #include /* std::shared_ptr<>, unique_ptr<> */ #include "play/performer.hpp" /* seq66::performer */ namespace seq66 { /** * This class supports manager a run of seq66. */ class smanager { public: /** * Provides a unique pointer to a performer, to enable the performer to * be recreated. */ using pointer = std::unique_ptr; private: /** * Provides a pointer to the performer to be managed. This performer can * be removed and recreated as needed (e.g. when another MIDI device * comes online.) */ pointer m_perf_pointer; /** * Holds the capabilities string (if applicable) for the application * using this session manager. */ std::string m_capabilities; /** * Holds the session manager's name, or "None". */ std::string m_session_manager_name; std::string m_session_manager_path; std::string m_session_display_name; std::string m_session_client_id; /** * Hold the name of the currently-loaded MIDI file. */ std::string m_midi_filename; /** * Indicates if the --help or --version options were provided at * start-up. */ bool m_is_help; /** * Used in seeing if the "dirty" status has changed so that the session * manager can be told about the change. */ bool m_last_dirty_status; /** * Handles the situation where we set up rerouting to a * sessions.rc-specified log file. No need to reroute twice. */ mutable bool m_rerouted; /** * Holds the current error message. Mutable because it is not part of * the true state of the session manager. */ mutable std::string m_extant_errmsg; /** * Holds the current error state. Mutable because it is not part of * the true state of the session manager. */ mutable bool m_extant_msg_active; public: smanager (const std::string & caps = ""); smanager (const smanager &) = delete; smanager & operator = (const smanager &) = delete; virtual ~smanager (); static void app_info (const std::string arg0, bool is_cli = false); bool create (int argc, char * argv []); bool main_settings (int argc, char * argv []); bool open_midi_control_file (); bool open_playlist (); bool open_note_mapper (); bool open_patch_file (); bool create_performer (); std::string open_midi_file (const std::string & fname); bool error_active () const { return m_extant_msg_active; } const std::string & error_message () const { return m_extant_errmsg; } const std::string & midi_filename () const { return m_midi_filename; } const std::string & capabilities () const { return m_capabilities; } bool last_dirty_status () const { return m_last_dirty_status; } bool is_help () const { return m_is_help; } bool internal_error_check (std::string & msg) const; void error_handling (); bool internal_error_pending () const { return bool(m_perf_pointer) ? m_perf_pointer->error_pending() : true ; } bool make_path_names ( const std::string & path, std::string & outcfgpath, std::string & outmidipath, const std::string & midisubdir = "midi" ); bool import_into_session ( const std::string & path, const std::string & sourcebase ); bool export_session_configuration ( const std::string & destpath, const std::string & destbase ); private: bool reset_configuration_items ( const std::string & sourcepath, const std::string & sourcebase, const std::string & cfgfilepath, const std::string & midifilepath ); public: virtual bool create_session (int argc = 0, char * argv [] = nullptr); virtual bool close_session (std::string & msg, bool ok = true); virtual bool save_session (std::string & msg, bool ok = true); virtual bool create_window (); virtual bool create_project ( int argc, char * argv [], const std::string & path ) = 0; virtual bool run () = 0; virtual void show_message ( const std::string & tag, const std::string & msg ) const; virtual void show_error ( const std::string & tag, const std::string & msg ) const; virtual void session_manager_name (const std::string & mgrname) { m_session_manager_name = mgrname; } virtual void session_manager_path (const std::string & pathname) { m_session_manager_path = pathname; } virtual void session_display_name (const std::string & dispname) { m_session_display_name = dispname; } virtual void session_client_id (const std::string & clid) { m_session_client_id = clid; } const std::string & manager_name () const { return m_session_manager_name; } const std::string & manager_path () const { return m_session_manager_path; } const std::string & display_name () const { return m_session_display_name; } const std::string & client_id () const { return m_session_client_id; } protected: const performer * perf () const { return m_perf_pointer.get(); } performer * perf () { return m_perf_pointer.get(); } void midi_filename (const std::string & fname) { m_midi_filename = fname; } void last_dirty_status (bool flag) { m_last_dirty_status = flag; } void is_help (bool flag) { m_is_help = flag; } bool reroute_to_log (const std::string & filepath) const; void append_error_message ( const std::string & message, const std::string & data = "" ) const; bool create_configuration ( int argc, char * argv [], const std::string & mainpath, const std::string & cfgfilepath, const std::string & midifilepath ); bool create_playlist ( const std::string & cfgfilepath, const std::string & midifilepath ); bool create_notemap (const std::string & cfgfilepath); bool read_configuration ( int argc, char * argv [], const std::string & cfgfilepath, const std::string & midifilepath ); }; // class smanager } // namespace seq66 #endif // SEQ66_SMANAGER_HPP /* * smanager.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/util/automutex.hpp ================================================ #if ! defined SEQ66_AUTOMUTEX_HPP #define SEQ66_AUTOMUTEX_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file automutex.hpp * * This module declares/defines the base class for mutexes. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2019-04-16 * \license GNU GPLv2 or above * * This module defines the following classes: * * - seq66::mutex. An alias for std::recursive_mutex. * - seq66::automutex. A way to lock a function exception-safely and * easily. * * 2019-04-21 Reverted to commit 5b125f71 to stop GUI deadlock :-( */ #include "util/recmutex.hpp" /* seq66::recmutex wrapper class */ namespace seq66 { /** * Provides a mutex that locks automatically when created, and unlocks * when destroyed. This has a couple of benefits. First, it is threadsafe * in the face of exception handling. Secondly, it can be done with just one * line of code. * * It could potentially be replaced by std::lock_guard or * std::lock_guard. However, it provides lock() and unlock() * functions for extra flexibility and danger. :-) * * How to use it? An example: * * int test_func (recmutex & mut, const sequence & s) * { * Created ----------> automutex locker(mut); * Value to return --> return s.events().count(); * Destroyed ----> } * * Quoting the standard (§3.7.3/3): "If a variable with automatic storage * duration has initialization or a destructor with side effects, it shall * not be destroyed before the end of its block, nor shall it be eliminated * as an optimization even if it appears to be unused." The end of the block, * for a function, is after the return statement. */ class automutex { private: /** * Provides the mutex reference to be used for locking. */ recmutex & m_safety_mutex; private: /* do not allow these functions to be used */ automutex () = delete; automutex (const automutex &) = delete; automutex & operator = (const automutex &) = delete; public: /** * Principal constructor gets a reference to a mutex parameter, and * then locks the mutex. * * \param my_mutex * The caller's mutex to be used for locking. */ automutex (recmutex & my_mutex) : m_safety_mutex (my_mutex) { lock(); } /** * The destructor unlocks the mutex. */ ~automutex () { unlock(); } /** * The lock() and unlock() functions are provided for additional * flexibility in usage. */ void lock () { m_safety_mutex.lock(); } void unlock () { m_safety_mutex.unlock(); } }; // class automutex } // namespace seq66 #endif // SEQ66_AUTOMUTEX_HPP /* * automutex.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/util/basic_macros.h ================================================ #if ! defined SEQ66_BASIC_MACROS_H #define SEQ66_BASIC_MACROS_H /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file basic_macros.h * * This module provides macros for generating simple messages, MIDI * parameters, and more. * * \library seq66 * \author Chris Ahlstrom and other authors; see documentation * \date 2018-11-10 * \updates 2023-05-13 * \version $Revision$ * \license GNU GPL v2 or above * * This file defines macros for both C and C++11 (or greater) code. Seq66 * requires C++11 support or above. The macros in this file cover: * * - Compiler-support macros. * - Error and information output macros. * - One or more global debugging functions that are better suited * than using a macro. * * This module replaces Seq64's easy_macros. */ #if ! defined __cplusplus #include #endif #include "seq66_features.h" /* C platform and config macros */ /** * An undefined macro purely to emphasize a C/C++ "{}" scope used for various * exception-safe locking mechanisms. */ #define SEQ66_SCOPE_LOCK /** * Language macros: * * "nullptr" provides an alternative to NULL in C compilers. * "override" is undefined (defined to nothing) in C compilers. */ #if ! defined __cplusplus #define nullptr 0 #define override #define noexcept #endif /* defined __cplusplus */ /** * Provides a way to declare functions as having either a C++ or C * interface. */ #if ! defined EXTERN_C_DEC #if defined __cplusplus #define EXTERN_C_DEC extern "C" { #define EXTERN_C_END } #else #define EXTERN_C_DEC #define EXTERN_C_END #endif #endif /** * Reserved for future usage of gettext(). */ #if ! defined GETTEXT_UNDERSCORE #define GETTEXT_UNDERSCORE #define T_(msg) (msg) #define N_(msg) (msg) #endif /** * Test for being a valid pointer. The not_NULL() macro is meant for C * code that returns NULL. */ #define not_NULL(x) ((x) != NULL) #define not_nullptr(x) ((x) != nullptr) #define not_nullptr_2(x1, x2) ((x1) != nullptr && (x2) != nullptr) /** * Test for being an invalid pointer. The is_NULL() macro is meant for C * code that returns NULL. */ #define is_NULL(x) ((x) == NULL) #define is_nullptr(x) ((x) == nullptr) #define is_nullptr_2(x1, x2) ((x1) == nullptr || (x2) == nullptr) #if ! defined __cplusplus /** * Provides a type to better represent a boolean value. Provides the "true" * and "false" values of the cbool_t type definition. */ typedef int cbool_t; #define false 0 #define true 1 #endif /** * GCC provides three magic variables which hold the name of the current * function, as a string. The first of these is `__func__', which is part * of the C99 standard: * * The identifier `__func__' is implicitly declared by the translator. It * is the name of the lexically-enclosing function. This name is the * unadorned name of the function. * * `__FUNCTION__' is another name for `__func__'. Older versions of GCC * recognize only this name. However, it is not standardized. For maximum * portability, use `__func__', but provide a fallback definition with * the preprocessor, as done below. * * `__PRETTY_FUNCTION__' is the decorated version of the function name. * It is longer, but more informative. It is also deprecated. But, for * now, we'll continue to use it * * Visual Studio defines only __FUNCTION__, so a definition is provided * below. */ #if defined SEQ66_PLATFORM_GNU #if ! defined __func__ #if __STDC_VERSION__ < 199901L #if __GNUC__ >= 2 /** * Function names. */ #define __func__ __FUNCTION__ // bald func names #else #define __func__ "" #endif // __GNUC__ #endif // __STDC_VERSION__ #endif // __func__ #else // ! SEQ66_PLATFORM_GNU #if ! defined __func__ #define __func__ __FUNCTION__ // Windows? #endif #endif // SEQ66_PLATFORM_GNU #if ! defined __cplusplus /** * Provides reporting macros (which happens to match Chris's XPC message * functions. For C++ code, these macros are defined in basic_macros.hpp * and are more convenient to use. */ #if defined SEQ66_PLATFORM_DEBUG #define errprint(x) fprintf(stderr, "%s\n", x) #define errprintf(fmt, x) fprintf(stderr, fmt, x) #define errprintfunc(x) fprintf(stderr, "%s: %s\n", __func__, x) #define warnprint(x) fprintf(stderr, "%s\n", x) #define warnprintf(fmt, x) fprintf(stderr, fmt, x) #define infoprint(x) fprintf(stderr, "%s\n", x) #define infoprintf(fmt, x) fprintf(stderr, fmt, x) #define infoprintfunc() fprintf(stdout, "%s\n", __func__) #else #define errprint(x) #define errprintf(fmt, x) #define errprintfunc(x) #define warnprint(x) #define warnprintf(fmt, x) #define infoprint(x) #define infoprintf(fmt, x) #define infoprintfunc() #endif // SEQ66_PLATFORM_DEBUG #endif // ! C++ #endif // SEQ66_BASIC_MACROS_H /* * basic_macros.h * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: libseq66/include/util/basic_macros.hpp ================================================ #if ! defined SEQ66_BASIC_MACROS_HPP #define SEQ66_BASIC_MACROS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file basic_macros.hpp * * This module provides macros for generating simple messages, MIDI * parameters, and more. * * \library seq66 * \author Chris Ahlstrom and other authors; see documentation * \date 2018-11-10 * \updates 2023-04-03 * \version $Revision$ * \license GNU GPL v2 or above * * This file defines macros for both C and C++11 (or greater) code. Seq66 * requires C++11 support or above. The macros in this file cover: * * - Compiler-support macros. * - Error and information output macros. * - One or more global debugging functions that are better suited * than using a macro. * * This module replaces Seq64's easy_macros. */ #include /* std::vector */ #include "seq66_features.hpp" /* C++ definitions, std::string */ #include "util/basic_macros.h" /* C-style definitions/features */ namespace seq66 { /* * Used in async_safe_utoa(). */ const int c_async_safe_utoa_size = 24; /* * Common data types. */ /** * Provides an easy-to-search container for strings. */ using tokenization = std::vector; /* * Global functions. The not_nullptr_assert() function is a macro in * release mode, to speed up release mode. It cannot do anything at * all, since it is used in the conditional part of if-statements. */ extern void set_verbose (bool flag); extern void set_investigate (bool flag); extern bool verbose (); extern bool investigate (); #if defined SEQ66_PLATFORM_DEBUG #if defined __cplusplus extern bool not_nullptr_assert (void * ptr, const std::string & context); #else #define not_nullptr_assert(ptr, context) (not_nullptr(ptr)) #endif // C++ #else #define not_nullptr_assert(ptr, context) (not_nullptr(ptr)) #endif #if defined __cplusplus /** * Provides reporting macros (which happens to match Chris's XPC message * functions. For C code, these macros are defined in basic_macros.h * instead, and are not as "powerful". */ #define errprint(x) (void) seq66::error_message(x) #define warnprint(x) (void) seq66::warn_message(x) #define infoprint(x) (void) seq66::info_message(x) /** * Usage: errprintf(format, cstring|value); * * Provides an error reporting macro that requires a sprintf() format * specifier as well. */ #define errprintf(fmt, x) msgprintf(seq66::msglevel::error, fmt, x) #define warnprintf(fmt, x) msgprintf(seq66::msglevel::warn, fmt, x) #define infoprintf(fmt, x) msgprintf(seq66::msglevel::info, fmt, x) #endif // C++ /** * Usage: errprintfunc(cstring); * * Provides error and informational reporting macro that includes the * function name. */ #if defined __cplusplus #define errprintfunc(x) seq66::msgprintf(seq66::msglevel::error, \ "%s: %s", __func__, x) #define infoprintfunc() seq66::msgprintf(seq66::msglevel::info, "%s", __func__) #endif extern void info_message ( const std::string & msg, const std::string & data = "" ); extern void status_message ( const std::string & msg, const std::string & data = "" ); extern void warn_message ( const std::string & msg, const std::string & data = "" ); extern bool error_message ( const std::string & msg, const std::string & data = "" ); extern void debug_message ( const std::string & msg, const std::string & data = "" ); extern void session_message ( const std::string & msg, const std::string & data = "" ); extern void file_message (const std::string & tag, const std::string & path); extern bool file_error (const std::string & tag, const std::string & filename); extern void print_client_tag (msglevel el); extern void boolprint (const std::string & tag, bool flag); extern void toggleprint (const std::string & tag, bool flag); extern void async_safe_strprint (const char * msg, bool colorit = true); extern void async_safe_errprint (const char * msg, bool colorit = true); extern void async_safe_utoa ( char * destination, unsigned number, bool spacebefore = true ); extern void msgprintf (seq66::msglevel lev, std::string fmt, ...); extern std::string msgsnprintf (std::string fmt, ...); } // namespace seq66 #endif // SEQ66_BASIC_MACROS_HPP /* * basic_macros.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/util/condition.hpp ================================================ #if ! defined SEQ66_CONDITION_HPP #define SEQ66_CONDITION_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file condition.hpp * * This module declares/defines the base class for coordinating a condition * variable and a mutex. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2024-01-16 * \license GNU GPLv2 or above * * This module defines the class seq66::condition_var, which provides a common * usage paradigm, for the performer object. Note that the mutex header * defines recursive_mutex, mutex, unique_lock, and lock_guard. * * 2019-04-21 Reverted to commit 5b125f71 to stop GUI deadlock :-( */ #include /* for seq66::synchronizer class */ #include "util/recmutex.hpp" /* seq66::recmutex wrapper class */ namespace seq66 { /** * A mutex works best in conjunction with a condition variable. The "has-a" * relationship is more logical than an "is-a" relationship. Note the * additional member function condition::wait_on_predicate(), which allow the * usage of a test function returning a boolean. */ class condition { private: /** * A nested implementation class that is sequestered inside the * condition.cpp module. */ class impl; /** * Provides a recursive mutex that can be used by the whole application, * and is. */ static recmutex sm_recursive_mutex; /** * Our recursive mutex (not the normal C++11 non-recursive mutex) used * for locking the conditional wait operation. We use our own automutex, * not the recursive mkutex that std::unique_lock<> require. Provides a * mutex lock usable by a single module or class. However, this mutex * ends up being a copy of the static sm_recursive_mutex (and, of course, * a different "object"). */ mutable recmutex m_mutex_lock; /** * A pointer to the class implementation. Needs to use the mutex created * above. */ std::unique_ptr p_imple; public: condition (); condition (condition &&) = delete; /* default; */ condition (const condition &); /* delete; */ condition & operator = (condition &&) = delete; condition & operator = (const condition &); /* delete; */ ~condition (); void lock () const { m_mutex_lock.lock(); } void unlock () const { m_mutex_lock.unlock(); } recmutex & locker () { return m_mutex_lock; } void signal (); void wait (); void wait (int ms); }; // class condition /* * -------------------------------------------------------------------------- * A C++-only implmenation * -------------------------------------------------------------------------- */ class synchronizer { private: /** * Used for locking the condition variable. */ std::mutex m_helper_mutex; /** * The new-style (for Seq66) condition variable. It replaces the pthread * implementation, at least when used in the performer class. */ std::condition_variable m_condition_var; public: synchronizer (); synchronizer (const synchronizer &) = delete; synchronizer & operator = (synchronizer &&) = delete; synchronizer & operator = (const synchronizer &) = delete; virtual ~synchronizer () = default; bool wait (); void signal (); /** * The user of this class must derive a class to properly define this * function. It should return true when some internal thread is ready to * run or when the thread has raised a flag for an exit. See * performer::predicate() for a useful override. */ virtual bool predicate () const = 0; }; // class synchronzier } // namespace seq66 #endif // SEQ66_CONDITION_HPP /* * condition.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/util/filefunctions.hpp ================================================ #if ! defined SEQ66_FILEFUNCTIONS_HPP #define SEQ66_FILEFUNCTIONS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file filefunctions.hpp * * Provides the declarations for safe replacements for some C++ * file functions. * * \author Chris Ahlstrom * \date 2015-11-20 * \updates 2025-02-01 * \version $Revision$ * * Also see the filefunctions.cpp module. The functions here use * old-school file-pointers and the new-fangled std::string. */ #include /* std::FILE * */ #include /* std::string ubiquitous class */ #include "util/basic_macros.hpp" /* seq6::tokenization vector */ #if defined SEQ66_PLATFORM_GLOB #define SEQ66_HANDLE_FILE_WILDCARDS #endif namespace seq66 { /* * Global function declarations. */ extern bool file_access (const std::string & targetfile, int mode); extern bool file_exists (const std::string & targetfile); extern bool file_readable (const std::string & targetfile); extern bool file_writable (const std::string & targetfile); extern bool file_read_writable (const std::string & targetfile); extern bool file_executable (const std::string & targetfile); extern bool file_is_directory (const std::string & targetfile); extern bool file_name_good (const std::string & filename); extern bool file_mode_good (const std::string & mode); extern size_t file_size (const std::string & filename); extern std::FILE * file_open ( const std::string & filename, const std::string & mode ); extern std::FILE * file_open_for_read (const std::string & filename); extern std::FILE * file_create_for_write (const std::string & filename); extern std::string current_date_time (); extern bool file_write_string ( const std::string & filename, const std::string & text ); extern std::string file_read_string (const std::string & oldfile); extern bool file_close ( std::FILE * filehandle, const std::string & filename = "" ); extern bool file_delete (const std::string & filespec); extern bool file_copy ( const std::string & oldfile, const std::string & newfile ); extern bool file_copy_to_path ( const std::string & sourcefile, const std::string & path ); #if defined SEQ66_USE_PRIMITIVE_WILDCARD_MATCH extern bool wildcard_match ( const std::string & target, const std::string & pattern ); #endif extern bool file_append_log ( const std::string & filename, const std::string & data ); extern bool name_has_path (const std::string & filename); extern bool name_has_root_path (const std::string & path); extern bool name_has_extension (const std::string & filename); extern bool make_directory_path (const std::string & directory_name); extern std::string make_path_relative (const std::string & path); extern bool delete_directory (const std::string & filename); extern bool set_current_directory (const std::string & path); extern std::string get_current_directory (); extern std::string get_full_path (const std::string & path); extern char path_slash (); extern char os_path_slash (); extern std::string os_normalize_path ( const std::string & path, bool terminate = false ); extern std::string unix_normalize_path (const std::string & path); extern std::string normalize_path ( const std::string & path, bool tounix = true, bool terminate = false ); extern std::string shorten_file_spec (const std::string & fpath, int leng); extern std::string clean_file (const std::string & path, bool tounix = true); extern std::string clean_path (const std::string & path, bool tounix = true); extern std::string append_file ( const std::string & path, const std::string & filename, bool to_unix = true ); extern std::string append_path ( const std::string & path, const std::string & pathname, bool to_unix = true ); extern std::string filename_concatenate ( const std::string & path, const std::string & filebase ); extern std::string filename_concatenate ( const std::string & path, const std::string & base, const std::string & ext ); extern std::string pathname_concatenate ( const std::string & path0, const std::string & path1 ); extern bool filename_split ( const std::string & fullpath, std::string & path, std::string & filebase ); extern bool filename_split_ext ( const std::string & fullpath, std::string & path, std::string & filebase, std::string & ext ); extern std::string file_path_set ( const std::string & fullpath, const std::string & newpath ); extern std::string file_base_set ( const std::string & fullpath, const std::string & newbase ); extern std::string filename_path (const std::string & fullpath); extern std::string filename_base ( const std::string & fullpath, bool noext = false ); extern bool file_extension_match ( const std::string & path, const std::string & target ); extern std::string file_extension (const std::string & path); extern std::string file_extension_set ( const std::string & path, const std::string & ext = "" ); extern std::string executable_full_path (); extern std::string user_home (const std::string & appfolder = ""); extern std::string user_config (const std::string & appfolder = ""); extern std::string user_session (const std::string & appfolder = ""); extern std::string find_file ( const tokenization & dirlist, const std::string & filename ); #if defined SEQ66_HANDLE_FILE_WILDCARDS extern bool get_wildcards ( const std::string & wildpath, tokenization & filelist, bool append = false ); #else inline bool get_wildcards ( const std::string & wildpath, tokenization & filelist, bool append = false ) { (void) wildpath; (void) filelist; (void) append; return false; } #endif extern bool file_list_copy ( const std::string & destpath, const tokenization & filelist ); #endif // SEQ66_FILEFUNCTIONS_HPP } // namespace seq66 /* * filefunctions.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/util/named_bools.hpp ================================================ #if ! defined SEQ66_NAMED_BOOLS_HPP #define SEQ66_NAMED_BOOLS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file named_bools.hpp * * This module provides a map of booleans using a string as a key value. * * \library seq66 application * \author Chris Ahlstrom * \date 2021-09-13 * \updates 2021-09-13 * \license GNU GPLv2 or above * * This seems to be much easier for small sets of booleans that using an * enumeration. */ #include /* std::map container class */ #include /* std::string class */ namespace seq66 { /** * A handy class to make it easy to look up and set a "small" number of * boolean values by name. */ class named_bools { using container = std::map; private: /** * Provides an associative container of booleans. */ container m_container; public: named_bools () : m_container () { // no code } bool add (const std::string & name, bool value) { auto p = std::make_pair(name, value); auto r = m_container.insert(p); return r.second; } bool get (const std::string & name) const { auto r = m_container.find(name); return r != m_container.end() ? r->second : false ; } void set (const std::string & name, bool value = true) { auto r = m_container.find(name); if (r != m_container.end()) r->second = value; else (void) add(name, value); } void clear () { m_container.clear(); } int count () { return int(m_container.size()); } }; // class named_bools } // namespace seq66 #endif // SEQ66_NAMED_BOOLS_HPP /* * named_bools.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/util/palette.hpp ================================================ #if ! defined SEQ66_PALETTE_HPP #define SEQ66_PALETTE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file palette.hpp * * This module declares/defines items for an abstract representation of the * color of a sequence or panel item. Colors are, of course, part of using a * GUI, but here we are not tied to a GUI. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-02-18 * \updates 2025-01-14 * \license GNU GPLv2 or above * * This module is inspired by MidiPerformance::getSequenceColor() in * Kepler34. */ #include /* std::map container class */ #include /* std::string class */ namespace seq66 { /** * A type to support the concept of sequence color. The color is a number * pointing to an RGB entry in a palette. * * This enumeration provide as stock palette of colors. For example, * Kepler34 creates a color-map in this manner: * * extern QMap colourMap; * * Of course, we would use std::map instead of QMap, and include a wrapper * class to keep QColor unexposed to non-Qt entities. Also, we define the * colors in standard X-terminal order, not in Kepler34 order. */ enum class PaletteColor { /* Seq66 */ /* Kepler34 */ none = -1, // indicates no color chosen, default color black = 0, // 0 WHITE red, // 1 RED green, // 2 GREEN yellow, // 3 BLUE blue, // 4 YELLOW magenta, // 5 PURPLE cyan, // 6 PINK white, // 7 ORANGE dk_black, // 8 place-holder dk_red, // 9 N/A dk_green, // 10 N/A dk_yellow, // 11 N/A dk_blue, // 12 N/A dk_magenta, // 13 N/A dk_cyan, // 14 N/A dk_white, // 15 N/A orange, // color_16 pink, // color_17 color_18, color_19, color_20, color_21, color_22, grey, // color_23 dk_orange, // color_24 dk_pink, // color_25 color_26, color_27, color_28, color_29, color_30, dk_grey, // color_31 max // first illegal palette value, not in color set }; /** * Provides indices into a list of colors that can be retrieved from a map of * normal colors or a map of inverse colors. Supports the --inverse option. */ enum class InvertibleColor { black, /**< Used for foreground items like grid lines. */ white, /**< Used for background items (eg. drawing canvas. */ label, /**< Used for labeling on pattern buttons/slots. */ selection, /**< Used to paint selected notes. */ drum, /**< Used for non-transposable (drum) notes. */ tempo, /**< Painting for tempo events. */ note_in, /**< Color inside the note, defaults to foreground. */ note_out, /**< Border color of note, defaults to background. */ black_key, /**< Painting for the "black keys" on the piano. */ white_key, /**< Painting for the "white keys" on the piano. */ progress, /**< Painting for the progress bar. */ backseq, /**< Painting for the background sequencce. */ grey, /**< Medium grid lines. */ dk_grey, /**< Heavy grid lines. */ lt_grey, /**< Light grid lines. */ beat, /**< For a medium-heavy beat line; was foreground. */ near, /**< Mouse is near an event in a pane. Was yellow */ backtime, /**< Used for the background of time-lines. */ backdata, /**< Used for the background of data panes. */ backevent, /**< Used for the background of the event pane. */ backkeys, /**< Used for the background of the keys pane. */ backnames, /**< Used for the background of perf names pane. */ octave, /**< Color for each octave line; was foreground. */ text, /**< Replaces "black" (foreground) for text items. */ texttime, /**< Used for the text of time-lines. */ textdata, /**< Used for the text of data panes. */ noteevent, /**< Used for the note brush of the event pane. */ textkeys, /**< Used for the text of the keys pane. */ textnames, /**< Used for the text of perf names pane. */ textslots, /**< Used for the text of the grid/pattern slots. */ scale, /**< Provides the color for drawing scale notes. */ extra, /**< Reserved for expansion. */ max /**< First illegal palette value, not in color set. */ }; /** * Macro to simplify converting between palette color enumeration values * to a simple integer. */ inline int palette_to_int (PaletteColor x) { return static_cast(x); } inline int inv_palette_to_int (InvertibleColor x) { return static_cast(x); } /** * A generic collection of whatever types of color classes (QColor, * Gdk::Color) one wants to hold, and reference by an index number. * This template class is not meant to manage color, but just to point * to them. */ template class palette { friend class gui_palette_qt5; /** * Combines the PaletteColor with a string describing the color. * This color string is not necessarily standard, but can be added to a * color-selection menu. */ using pair = struct { COLOR ppt_color; std::string ppt_color_name; }; using container = std::map; private: /** * Provides an associative container of pointers to the color-class COLOR. * A vector could be used instead of a map. * * std::map m_container; */ container m_container; public: palette (); /* initially empty, filled by add() */ bool add (PaletteColor index, const COLOR & c, const std::string & name); const COLOR & get_color (PaletteColor index) const; std::string get_color_name (PaletteColor index) const; std::string get_color_name_ex (PaletteColor index) const; bool add (InvertibleColor index, const COLOR & c, const std::string & name); const COLOR & get_color (InvertibleColor index) const; std::string get_color_name (InvertibleColor index) const; std::string get_color_name_ex (InvertibleColor index) const; /** * \param index * The color index to be tested. * * \return * Returns true if there is no color applied. */ bool no_color (PaletteColor index) const { return index == PaletteColor::none; } void clear () { m_container.clear(); } int count () { return int(m_container.size()); } private: const container & entries () const { return m_container; } container & entries () { return m_container; } }; // class palette /** * Creates the palette, and inserts a default COLOR color object as * the PaletteColor::none entry. This color has to be static so that it is * always around to be used. */ template palette::palette () : m_container () { static COLOR color; add(PaletteColor::none, color, "None"); } /** * Inserts a color-index/color pair into the palette. There is no indication * if the item was not added, which will occur only when the item is already * in the container. The type of a color pair is * std::pair. * * \param index * The index into the palette. * * \param color * The COLOR color object to add to the palette. */ template bool palette::add ( PaletteColor index, const COLOR & color, const std::string & colorname ) { int key = palette_to_int(index); size_t count = m_container.size(); pair colorspec; colorspec.ppt_color = color; colorspec.ppt_color_name = colorname; auto p = std::make_pair(key, colorspec); (void) m_container.insert(p); return m_container.size() == (count + 1); } /** * Gets a color from the palette, based on the index value. * * \param index * Indicates which color to get. This index is checked for range, and, if * out of range, the default color object, indexed by PaletteColor::none, * is returned. However, an exception will be thrown if the color does * not exist, which should be the case only via programmer error. * * \return * Returns a reference to the selected color object. */ template const COLOR & palette::get_color (PaletteColor index) const { if (index >= PaletteColor::black && index < PaletteColor::max) { int key = palette_to_int(index); return m_container.at(key).ppt_color; } else { int key = palette_to_int(PaletteColor::none); return m_container.at(key).ppt_color; } } template std::string palette::get_color_name (PaletteColor index) const { if (index >= PaletteColor::black && index < PaletteColor::max) { int key = palette_to_int(index); return m_container.at(key).ppt_color_name; } else { int key = palette_to_int(PaletteColor::none); return m_container.at(key).ppt_color_name; } } template std::string palette::get_color_name_ex (PaletteColor index) const { std::string result = std::to_string(palette_to_int(index)); result += " "; result += get_color_name(index); return result; } template bool palette::add ( InvertibleColor index, const COLOR & color, const std::string & colorname ) { int key = inv_palette_to_int(index); size_t count = m_container.size(); pair colorspec; colorspec.ppt_color = color; colorspec.ppt_color_name = colorname; auto p = std::make_pair(key, colorspec); (void) m_container.insert(p); return m_container.size() == (count + 1); } template const COLOR & palette::get_color (InvertibleColor index) const { int key = inv_palette_to_int(index); if (index >= InvertibleColor::black && index < InvertibleColor::max) { int maximum = static_cast(m_container.size()); if (key >= maximum) key = 0; } return m_container.at(key).ppt_color; } template std::string palette::get_color_name (InvertibleColor index) const { int key = inv_palette_to_int(index); if (index >= InvertibleColor::black && index < InvertibleColor::max) { int maximum = static_cast(m_container.size()); if (key >= maximum) key = 0; } return m_container.at(key).ppt_color_name; } template std::string palette::get_color_name_ex (InvertibleColor index) const { std::string result = std::to_string(inv_palette_to_int(index)); result += " "; result += get_color_name(index); return result; } } // namespace seq66 #endif // SEQ66_PALETTE_HPP /* * palette.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/util/recmutex.hpp ================================================ #if ! defined SEQ66_RECMUTEX_HPP #define SEQ66_RECMUTEX_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file recmutex.hpp * * This module declares/defines the base class for recursive mutexes. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2024-01-16 * \license GNU GPLv2 or above * * This recursive mutex is implemented in pthreads due to difficulties we had * with C++11's std::mutex. * * We also had to implement a flag to indicate if locking was in place or * not, but only for Windows at this time. Seems to work fine under Linux. * However, we're commenting it out for now and simply disabling the locking * of the condition variable's signal() call. Could be really problematic. * * https://en.cppreference.com/w/cpp/named_req/BasicLockable * * BasicLockable requirements describe the minimal characteristics of types * that provide exclusive blocking semantics for execution agents (i.e. * threads). * * -# lock(). Precondition = none. Blocks until a lock can be acquired * for the current execution agent (thread, process, task). If an * exception is thrown, no lock is acquired. * -# unlock. Precondition = The current execution agent holds a * non-shared lock. Releases the non-shared lock held by the * execution agent. Throws no exceptions. * * https://en.cppreference.com/w/cpp/named_req/Lockable * * Lockable extends the BasicLockable requirements to include attempted * locking via try_lock(). It Attempts to acquire the lock for the current * execution agent (thread, process, task) without blocking. If an exception * is thrown, no lock is obtained. */ /* * We need to expose the native mutex type so that automutex can access it. * We could go back to putting the mutex, automutex, and condition definitions * in a single module, perhaps. We also use pthread_mutex_t rather than the * C++ mutex, as noted above. */ #include "seq66_platform_macros.h" /* pick the compiler and platform */ #include namespace seq66 { /** * The mutex class provides a simple wrapper for the pthread_mutex_t type * used as a recursive mutex. * * std::experimental::propagate_const> p_imple; */ class recmutex { public: /** * Sets the native type of recursive mutex in use. We declare it here, * rather than in the impl classes, because it needs to be exposed for * the use of automutex. The pthread_mutex_t type is a union of a structure * and a byte array. The structure contains a lock integer, a count, an * owner integer, and some other values. */ using native = pthread_mutex_t; private: /** * Provides a recursive mutex that can be used by the whole * application, and is, apparently. */ static native sm_global_mutex; #if defined SEQ66_PLATFORM_FREEBSD /** * Needed for setting up a recursive mutex. */ pthread_mutexattr_t m_mutex_attributes; #endif /** * Provides a mutex lock usable by a single module or class. * However, this mutex ends up being a copy of the static * sm_global_mutex (and, of course, a different "object"). */ mutable native m_mutex_lock; public: recmutex (); recmutex (recmutex &&) = delete; /* default; */ recmutex (const recmutex &); /* delete; */ recmutex & operator = (recmutex &&) = delete; /* default; */ recmutex & operator = (const recmutex &); /* delete; */ ~recmutex (); /* default; */ void lock () const; void unlock () const; /* * bool try_lock () noexcept; */ native & native_locker () const { return m_mutex_lock; } private: #if defined USE_GLOBAL_MUTEX static void init_global_mutex (); #endif void init (); void destroy (); }; // class recmutex } // namespace seq66 #endif // SEQ66_RECMUTEX_HPP /* * recmutex.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/util/rect.hpp ================================================ #if ! defined SEQ66_RECT_HPP #define SEQ66_RECT_HPP /* * This file is part of seq66. * * seq24 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. * * seq24 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 seq24; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file rect.hpp * * This module declares/defines the base class for a Seq66 rectangle. * * \library seq66 application * \author Chris Ahlstrom * \date 2017-09-16 * \updates 2025-07-07 * \license GNU GPLv2 or above * * Our version of the rectangle provides specific functionality not necessary * found in, say, GUI rectangle classes. */ namespace seq66 { /** * Supports a simple rectangle and some common manipulations needed by the * user-interface. Will eventually replace the gui_drawingarea_gtk2::rect * structure. * * One minor issue that may crop up in the transition from Gtkmm to Qt 5 is * the exact meaning of the coordinates. To be clarified later. For now, it * uses the current Gtkmm conventions. */ class rect { private: int m_x; /**< The x coordinate of the first corner or x0. */ int m_y; /**< The y coordinate of the first corner or y0. */ int m_width; /**< The width of the rectangle. */ int m_height; /**< The height of the rectangle. */ public: rect (); rect (int x, int y, int width, int height); void get (int & x, int & y, int & width, int & height) const; void get_coordinates (int & x0, int & y0, int & x1, int & y1) const; void set (int x, int y, int width, int height); void set_coordinates (int x0, int y0, int x1, int y1); void clear () { m_x = m_y = m_width = m_height = 0; } bool is_empty () const { return m_width == 0 && m_height == 0; } static void xy_to_rect (int x0, int y0, int x1, int y1, rect & r); static void xy_to_rect_get ( int x0, int y0, int x1, int y1, int & x, int & y, int & w, int & h ); void xy_to_rect (int x0, int y0, int x1, int y1) { xy_to_rect(x0, y0, x1, y1, *this); } int x () const { return m_x; } void x (int v) { m_x = v; } int x0 () const { return m_x; } void x0 (int v) { m_x = v; } /** * Provides a setter that uses the parameter to increment the member. * The width is assumed to be unchanged by this function. */ void x_incr (int v) { m_x += v; } int y () const { return m_y; } void y (int v) { m_y = v; } int y0 () const { return m_y; } void y0 (int v) { m_y = v; } /** * Provides a setter that uses the parameter to increment the member. * The height is assumed to be unchanged by this function. */ void y_incr (int v) { m_y += v; } int width () const { return m_width; } void width (int w) { m_width = w; } int x1 () const { return m_x + m_width; } void x1 (int x) { m_width = x - m_x; } void width_incr (int w) { m_width += w; } int height () const { return m_height; } void height (int h) { m_height = h; } int y1 () const { return m_y + m_height; } void y1 (int h) { m_height = h - m_y; } void height_incr (int h) { m_height += h; } void xy_incr (int xv, int yv) { m_x += xv; m_y += yv; } private: /** * The calculated width is always positive. Follows the conventions of * the xy_to_rect_get() function. */ static int calculated_width (int x1, int x2) { return (x1 < x2) ? (x2 - x1) : (x1 - x2) ; } /** * The calculated height is always positive. Follows the conventions of * the xy_to_rect_get() function. */ static int calculated_height (int y1, int y2) { return (y1 < y2) ? (y2 - y1) : (y1 - y2) ; } }; // class rect } // namespace seq66 #endif // SEQ66_RECT_HPP /* * rect.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/util/ring_buffer.hpp ================================================ #if ! defined SEQ66_RING_BUFFER_HPP #define SEQ66_RING_BUFFER_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file ring_buffer.hpp * * This module defines our own ringbuffer that support objects, not just * characters. * * \library seq66 application * \author Chris Ahlstrom * \date 2022-09-19 * \updates 2022-10-31 * \license GNU GPLv2 or above */ #include #include #include #include "seq66_features.h" /* SEQ66_PLATFORM_DEBUG macro */ #undef SEQ66_USE_MEMORY_LOCK /* TODO: needs a lot of work ! */ #if defined SEQ66_USE_MEMORY_LOCK #include #endif namespace seq66 { template class ring_buffer { public: using value_type = TYPE; using reference = TYPE &; using const_reference = const TYPE &; using size_type = std::size_t; using container = std::vector; private: container m_buffer; /**< Container for all push/popped items. */ size_type m_buffer_size; /**< Constant power-of-two container size. */ size_type m_contents_size; /**< Number of active entries in container. */ volatile size_type m_tail; /**< Index where next item is written. */ volatile size_type m_head; /**< Index where next item is read. */ size_type m_size_mask; /**< Restricts index to < buffer size. */ bool m_locked; /**< Is memory locked? NOT YET SUPPORTED. */ size_type m_contents_max; /**< Useful in trouble-shooting. */ int m_dropped; /**< Number of items overwritten in run. */ public: explicit ring_buffer (size_type sz); ~ring_buffer (); bool mlock (); /** * Reset the read and write pointers to zero. This is not thread safe. * Neither is the clear() function. */ void reset () { m_head = m_tail = 0; } void clear () { m_dropped = m_contents_size = 0; reset(); initialize(); } int buffer_size () const { return int(m_buffer_size); } int count () const { return int(m_contents_size); } int count_max () const { return int(m_contents_max); } bool empty () const { return count() == 0; } int dropped () const { return m_dropped; } void write_advance (); void read_advance (); size_type write_space () const; size_type read_space () const; size_type read (reference dest); size_type write (const_reference src); bool push_back (const value_type & value); void pop_front () { if (m_contents_size > 0) increment_head(); } /* * Returns reference to the first element in the queue. This element will * be the first element to be removed on a call to pop(). The return * value might not be valid, either a default-constructed object or an * old and "overwritten" value. An alternative is to call the read() * function and check the return value. */ reference front () { return m_buffer[m_head]; } const_reference front () const { return m_buffer[m_head]; } /** * Currently there's no way to be sure that the back item is a * valid item. */ reference back () { return m_buffer[previous_tail()]; } const_reference back () const { return m_buffer[previous_tail()]; } private: // helper functions void initialize (); void increment_head (); void increment_tail (); size_t previous_tail () { return m_tail > 0 ? m_tail - 1 : m_buffer_size -1 ; } }; // class ring_buffer /** * Create a new ringbuffer to hold at least `sz' elements (TYPE) of data. * The actual buffer size is rounded up to the next power of two. */ template ring_buffer::ring_buffer (size_type sz) : m_buffer (), m_buffer_size (0), m_contents_size (0), m_tail (0), m_head (0), /* supports empty buffer case */ m_size_mask (0), m_locked (false), m_contents_max (0), m_dropped (0) { int power_of_two; for (power_of_two = 1; 1 << power_of_two < int(sz); ++power_of_two) ; size_type psize = size_t(1 << power_of_two); m_buffer_size = psize; m_size_mask = psize - 1; /* 0xFF... for index safety */ initialize(); } /** * Free all data associated with the ringbuffer `m_rb'. * * Note that we will have some work to do (like writing an allocator that * uses an unswappable block of memory for the vector data) if we define this * macro. */ template ring_buffer::~ring_buffer () { #if defined SEQ66_USE_MEMORY_LOCK if (m_locked) ::munlock(m_buffer, m_buffer_size); #endif } template void ring_buffer::initialize () { TYPE empty_value; m_buffer.clear(); m_buffer.reserve(m_buffer_size); for (size_t i = 0; i < m_buffer_size; ++i) (void) m_buffer.push_back(empty_value); /* prepare buffer for usage */ } template void ring_buffer::increment_head () { if (m_contents_size > 0) { ++m_head; --m_contents_size; if (m_head == m_buffer_size) m_head = 0; /* wrap around */ } } template void ring_buffer::increment_tail () { ++m_tail; ++m_contents_size; if (m_contents_size > m_contents_max) /* for checking */ m_contents_max = m_contents_size; if (m_tail == m_buffer_size) m_tail = 0; /* wrap around */ } /** * Lock the data block of `rb' using the system call 'mlock'. * Not nearly ready for prime time! */ template bool ring_buffer::mlock () { #if defined SEQ66_USE_MEMORY_LOCK if (::mlock(m_buffer, m_buffer_size) != 0) return false; m_locked = true; return true; #else return false; #endif } /** * Return the number of elements available for writing. This is the number * of elements in front of the write/tail pointer and behind the read/head * pointer. */ template std::size_t ring_buffer::write_space () const { size_type t = m_tail; /* index for writing */ size_type h = m_head; /* index for reading */ if (t > h) return ((h - t + m_buffer_size) & m_size_mask) - 1; else if (t < h) return (h - t) - 1; else return m_buffer_size - m_contents_size; } template void ring_buffer::write_advance () { increment_tail(); } /** * Since we only push one element at a time, the return code is used to * determine the number of elements currently active in the ring_buffer, * unless 0 is returned, which indicates an error (no space left). */ template std::size_t ring_buffer::write (const_reference src) { size_type result = 0; size_type write_cnt = write_space(); if (write_cnt > 0) { (void) push_back(src); result = m_contents_size; } return result; } /** * Return the number of elements (TYPE) available for reading. This is the * number of elements in front of the read pointer and behind the write * pointer. */ template std::size_t ring_buffer::read_space () const { if (count() > 0) { size_type t = m_tail; /* index for writing */ size_type h = m_head; /* index for reading */ if (t > h) return t - h; else if (t == h) return m_contents_size; else return (t - h + m_buffer_size) & m_size_mask; } else return 0; } template void ring_buffer::read_advance () { ++m_head; m_head &= m_size_mask; } /** * The copying data reader. Unlike the original "C" version, this function * does not copy `cnt' bytes from `rb'. Instead it copies one element to * the destination and returns 1 if that works. * * Unlike front(), this function and pop_front() "remove" the element. * The result return is the number of elements still stored. */ template std::size_t ring_buffer::read (reference dest) { size_t result = 0; size_type read_cnt = read_space(); if (read_cnt > 0) { dest = m_buffer[m_head]; pop_front(); result = m_contents_size; } return result; } /** * Increment the tail, then increment the head. This function supports the * special case of an empty buffer. */ template bool ring_buffer::push_back (const value_type & item) { if (m_contents_size == 0) { m_tail = m_head; m_buffer[m_tail] = item; increment_tail(); } else if (m_contents_size < m_buffer_size) { m_buffer[m_tail] = item; increment_tail(); } else /* accept item and drop front() */ { increment_head(); m_buffer[m_tail] = item; increment_tail(); ++m_dropped; /* for future use in expansion */ } return true; } /* * Free functions (for testing the ring_buffer). */ #if defined SEQ66_PLATFORM_DEBUG extern bool run_ring_test (); #endif } // namespace seq66 #endif // SEQ66_RING_BUFFER_HPP /* * ring_buffer.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/include/util/strfunctions.hpp ================================================ #if ! defined SEQ66_STRFUNCTIONS_HPP #define SEQ66_STRFUNCTIONS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file strfunctions.hpp * * Provides the declarations for safe replacements for some C++ * file functions. * * \author Chris Ahlstrom * \date 2018-11-23 * \updates 2025-02-20 * \version $Revision$ * * Also see the strfunctions.cpp module. */ #include /* std::unique_ptr<> template class */ #include /* std::string class */ #include "util/basic_macros.hpp" /* seq66::tokenization container */ /** * Since strings are not POD, Clang will error on them when passed to * a variadic function. We could use c_str() directly. Sigh. Let's * make a simple macro for that. We use a macro to avoid multiple definitions * of this function. */ #define STR(x) const_cast(x.c_str()) #define CSTR(x) x.c_str() #define V(x) x.c_str() namespace seq66 { /** * Lists of characters to trim from strings. */ const std::string SEQ66_WHITE_CHARS = " \t\r\n\v\f"; const std::string SEQ66_DIGIT_CHARS = "0123456789"; const std::string SEQ66_TRIM_CHARS = " \t\r\n\v\f"; const std::string SEQ66_TRIM_CHARS_QUOTES = " \t\r\n\v\f\"'"; const std::string SEQ66_TRIM_CHARS_PATHS = " /\\"; /** * Inline functions. Simple tests for std::string::npos. Why? I dunno, less * typing for the developer. */ inline bool not_npos (std::string::size_type p) { return p != std::string::npos; } inline bool is_npos (std::string::size_type p) { return p == std::string::npos; } /* * Global (free) string functions. */ extern bool is_empty_string (const std::string & item); extern std::string empty_string (); extern bool is_questionable_string (const std::string & item); extern const std::string & questionable_string (); extern bool is_missing_string (const std::string & item); extern bool contains (const std::string & original, const std::string & target); extern std::string strip_comments (const std::string & item); extern std::string strip_quotes (const std::string & item); extern std::string next_quoted_string ( const std::string & source, std::string::size_type pos = 0 ); extern std::string next_bracketed_string ( const std::string & source, std::string::size_type pos = 0 ); extern std::string add_quotes (const std::string & item); extern const std::string & double_quotes (); extern bool not_npos (std::string::size_type p); extern bool is_npos (std::string::size_type p); extern bool strncompare ( const std::string & a, const std::string & b, size_t n = 0 ); extern bool strcasecompare (const std::string & a, const std::string & b); extern std::string & ltrim ( std::string & str, const std::string & chars = SEQ66_TRIM_CHARS ); extern std::string & rtrim ( std::string & str, const std::string & chars = SEQ66_TRIM_CHARS ); extern std::string trim ( const std::string & str, const std::string & chars = SEQ66_TRIM_CHARS ); extern std::string string_replace ( const std::string & source, const std::string & target, const std::string & replacement, int n = -1 ); extern int hex_digit (char c); extern std::string string_to_midi_bytes (const std::string & s, size_t lim = 0); extern std::string midi_bytes_to_string (const std::string & s); extern bool string_to_bool (const std::string & s, bool defalt = false); extern bool string_to_time_signature ( const std::string & s, int & beats, int & width ); extern std::string time_signature_string (int beats, int width); extern bool string_to_int_pair ( const std::string & s, int & v1, int & v2, const std::string & delimiter ); extern double string_to_double ( const std::string & s, double defalt = 0.0, int rounding = 0 ); extern bool is_floating_string (const std::string & value); extern std::string double_to_string(double value, int precision = 0); extern float string_to_float ( const std::string & s, float defalt = 0.0, int rounding = 0 ); extern long string_to_long (const std::string & s, long defalt = 0L); extern std::string long_to_string (long value); extern unsigned long string_to_unsigned_long ( const std::string & s, unsigned long defalt = 0UL ); extern unsigned string_to_unsigned ( const std::string & s, unsigned defalt = 0U ); extern int string_to_int (const std::string & s, int defalt = 0); extern std::string int_to_string (int value); extern bool string_not_void (const std::string & s); extern bool string_is_void (const std::string & s); extern bool strings_match (const std::string & target, const std::string & x); extern bool strings_match_ex ( const std::string & target, const std::string & x ); extern std::string tolower (const std::string & source); extern std::string toupper (const std::string & source); extern std::string capitalize (const std::string & source); extern std::string bool_to_string (bool x); extern char bool_to_char (bool x); extern std::string pointer_to_string (void * ptr); extern int tokenize_stanzas ( tokenization & tokens, const std::string & source, std::string::size_type bleft = 0, const std::string & brackets = "" ); extern tokenization tokenize ( const std::string & source, const std::string & delimiters = " \t" ); extern tokenization tokenize_quoted (const std::string & source); extern std::string simplify (const std::string & source); extern std::wstring widen_string (const std::string & source); extern std::string word_wrap ( const std::string & source, size_t margin = 80, char commentchar = 0 ); extern std::string hanging_word_wrap ( const std::string & source, size_t leftmargin = 28, size_t rightmargin = 80 ); /** * This function comes, slightly modified to avoid throwing an exception, * from: * * https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf * * Extra space for '\0', but it won't be included in the result. * * It is useful for C++11. Once C++20 becomes common, the following could be * used: * \verbatim * #include * std::string result = std::format("{} {}!", "Hello", "world"); \endverbatim */ template std::string string_format (const std::string & format, Args ... args) { std::string result; size_t sz = std::snprintf(nullptr, 0, format.c_str(), args ...); if (sz > 0) { std::unique_ptr buf(new char[sz + 1]); std::snprintf(buf.get(), sz + 1, format.c_str(), args ...); result = std::string(buf.get(), buf.get() + sz); } return result; } } // namespace seq66 #endif // SEQ66_STRFUNCTIONS_HPP /* * strfunctions.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/libseq66.pro ================================================ #****************************************************************************** # libseq66.pro (qpseq66) #------------------------------------------------------------------------------ ## # \file libseq66.pro # \library qseq66 and qpseq66 application # \author Chris Ahlstrom # \date 2018-11-15 # \update 2026-05-01 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Important: # # This project file is designed only for Qt 5 (and above?). However, # on a 32-bit Linux system with an old version of Qt (5.3), the build # fails due to std::unique_ptr not being defined, even with the c++11 # flag added. # #------------------------------------------------------------------------------ message($$_PRO_FILE_PWD_) TEMPLATE = lib CONFIG += staticlib config_prl qtc_runnable c++14 TARGET = seq66 # These are needed to set up seq66_platform_macros: CONFIG(debug, debug|release) { DEFINES += DEBUG } else { DEFINES += NDEBUG } contains (CONFIG, rtmidi) { MIDILIB = rtmidi DEFINES += "SEQ66_MIDILIB=rtmidi" DEFINES += "SEQ66_RTMIDI_SUPPORT=1" } else { MIDILIB = portmidi DEFINES += "SEQ66_MIDILIB=portmidi" DEFINES += "SEQ66_PORTMIDI_SUPPORT=1" } HEADERS += include/seq66_features.h \ include/seq66_features.hpp \ include/seq66_platform_macros.h \ include/cfg/basesettings.hpp \ include/cfg/cmdlineopts.hpp \ include/cfg/comments.hpp \ include/cfg/configfile.hpp \ include/cfg/midicontrolfile.hpp \ include/cfg/mutegroupsfile.hpp \ include/cfg/notemapfile.hpp \ include/cfg/patchesfile.hpp \ include/cfg/playlistfile.hpp \ include/cfg/rcfile.hpp \ include/cfg/rcsettings.hpp \ include/cfg/recent.hpp \ include/cfg/scales.hpp \ include/cfg/sessionfile.hpp \ include/cfg/settings.hpp \ include/cfg/userinstrument.hpp \ include/cfg/usermidibus.hpp \ include/cfg/usrfile.hpp \ include/cfg/usrsettings.hpp \ include/cfg/zoomer.hpp \ include/ctrl/automation.hpp \ include/ctrl/keycontrol.hpp \ include/ctrl/keycontainer.hpp \ include/ctrl/keymap.hpp \ include/ctrl/keystroke.hpp \ include/ctrl/midicontrolin.hpp \ include/ctrl/midicontrolbase.hpp \ include/ctrl/midicontrol.hpp \ include/ctrl/midicontrolout.hpp \ include/ctrl/midimacro.hpp \ include/ctrl/midimacros.hpp \ include/ctrl/midioperation.hpp \ include/ctrl/opcontainer.hpp \ include/ctrl/opcontrol.hpp \ include/midi/businfo.hpp \ include/midi/calculations.hpp \ include/midi/controllers.hpp \ include/midi/drums.hpp \ include/midi/editable_event.hpp \ include/midi/editable_events.hpp \ include/midi/event.hpp \ include/midi/eventlist.hpp \ include/midi/jack_assistant.hpp \ include/midi/mastermidibase.hpp \ include/midi/midibase.hpp \ include/midi/midibytes.hpp \ include/midi/midifile.hpp \ include/midi/midi_splitter.hpp \ include/midi/midi_vector_base.hpp \ include/midi/midi_vector.hpp \ include/midi/patches.hpp \ include/midi/wrkfile.hpp \ include/play/clockslist.hpp \ include/play/inputslist.hpp \ include/play/metro.hpp \ include/play/mutegroup.hpp \ include/play/mutegroups.hpp \ include/play/notemapper.hpp \ include/play/performer.hpp \ include/play/playlist.hpp \ include/play/portslist.hpp \ include/play/screenset.hpp \ include/play/seq.hpp \ include/play/sequence.hpp \ include/play/setmapper.hpp \ include/play/setmaster.hpp \ include/play/songsummary.hpp \ include/play/triggers.hpp \ include/sessions/clinsmanager.hpp \ include/sessions/smanager.hpp \ include/os/daemonize.hpp \ include/os/shellexecute.hpp \ include/os/timing.hpp \ include/util/automutex.hpp \ include/util/basic_macros.h \ include/util/basic_macros.hpp \ include/util/condition.hpp \ include/util/filefunctions.hpp \ include/util/named_bools.hpp \ include/util/palette.hpp \ include/util/recmutex.hpp \ include/util/rect.hpp \ include/util/ring_buffer.hpp \ include/util/strfunctions.hpp SOURCES += src/seq66_features.cpp \ src/cfg/basesettings.cpp \ src/cfg/cmdlineopts.cpp \ src/cfg/comments.cpp \ src/cfg/configfile.cpp \ src/cfg/midicontrolfile.cpp \ src/cfg/mutegroupsfile.cpp \ src/cfg/notemapfile.cpp \ src/cfg/patchesfile.cpp \ src/cfg/playlistfile.cpp \ src/cfg/rcfile.cpp \ src/cfg/rcsettings.cpp \ src/cfg/recent.cpp \ src/cfg/scales.cpp \ src/cfg/sessionfile.cpp \ src/cfg/settings.cpp \ src/cfg/userinstrument.cpp \ src/cfg/usermidibus.cpp \ src/cfg/usrfile.cpp \ src/cfg/usrsettings.cpp \ src/cfg/zoomer.cpp \ src/ctrl/automation.cpp \ src/ctrl/keycontainer.cpp \ src/ctrl/keycontrol.cpp \ src/ctrl/keymap.cpp \ src/ctrl/keystroke.cpp \ src/ctrl/midicontrolin.cpp \ src/ctrl/midicontrolbase.cpp \ src/ctrl/midicontrol.cpp \ src/ctrl/midicontrolout.cpp \ src/ctrl/midimacro.cpp \ src/ctrl/midimacros.cpp \ src/ctrl/midioperation.cpp \ src/ctrl/opcontainer.cpp \ src/ctrl/opcontrol.cpp \ src/midi/businfo.cpp \ src/midi/calculations.cpp \ src/midi/controllers.cpp \ src/midi/drums.cpp \ src/midi/editable_event.cpp \ src/midi/editable_events.cpp \ src/midi/event.cpp \ src/midi/eventlist.cpp \ src/midi/jack_assistant.cpp \ src/midi/mastermidibase.cpp \ src/midi/midibase.cpp \ src/midi/midibytes.cpp \ src/midi/midifile.cpp \ src/midi/midi_splitter.cpp \ src/midi/midi_vector_base.cpp \ src/midi/midi_vector.cpp \ src/midi/patches.cpp \ src/midi/wrkfile.cpp \ src/play/clockslist.cpp \ src/play/inputslist.cpp \ src/play/metro.cpp \ src/play/mutegroup.cpp \ src/play/mutegroups.cpp \ src/play/notemapper.cpp \ src/play/performer.cpp \ src/play/playlist.cpp \ src/play/portslist.cpp \ src/play/screenset.cpp \ src/play/seq.cpp \ src/play/sequence.cpp \ src/play/setmapper.cpp \ src/play/setmaster.cpp \ src/play/songsummary.cpp \ src/play/triggers.cpp \ src/sessions/clinsmanager.cpp \ src/sessions/smanager.cpp \ src/os/daemonize.cpp \ src/os/shellexecute.cpp \ src/os/timing.cpp \ src/util/automutex.cpp \ src/util/basic_macros.cpp \ src/util/condition.cpp \ src/util/filefunctions.cpp \ src/util/named_bools.cpp \ src/util/palette.cpp \ src/util/recmutex.cpp \ src/util/rect.cpp \ src/util/ring_buffer.cpp \ src/util/strfunctions.cpp INCLUDEPATH = \ ../include/qt/$${MIDILIB} \ ../libsessions/include \ ../seq_$${MIDILIB}/include \ include # We want to continue to use JACK session without getting a warning about # it being deprecated. QMAKE_CXXFLAGS += -std=c++14 -Wno-deprecated -Wno-deprecated-declarations #****************************************************************************** # libseq66.pro (qpseq66) #------------------------------------------------------------------------------ # vim: ts=4 sw=4 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: libseq66/src/Makefile.am ================================================ #****************************************************************************** # Makefile.am (libseq66/src) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library libseq66 library # \author Chris Ahlstrom # \date 2018-11-11 # \update 2026-02-16 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the libseq66 C/C++ # library. # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 subdir-objects MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #****************************************************************************** # CLEANFILES #------------------------------------------------------------------------------ CLEANFILES = *.gc* MOSTLYCLEANFILES = *~ #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = #****************************************************************************** # Items from configure.ac #------------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ GIT_VERSION = @GIT_VERSION@ SEQ66_API_MAJOR = @SEQ66_API_MAJOR@ SEQ66_API_MINOR = @SEQ66_API_MINOR@ SEQ66_API_PATCH = @SEQ66_API_PATCH@ SEQ66_API_VERSION = @SEQ66_API_VERSION@ SEQ66_LT_CURRENT = @SEQ66_LT_CURRENT@ SEQ66_LT_REVISION = @SEQ66_LT_REVISION@ SEQ66_LT_AGE = @SEQ66_LT_AGE@ SEQ66_LIBTOOL_VERSION = @SEQ66_LIBTOOL_VERSION@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ prefix = @prefix@ includedir = @seq66includedir@ libdir = @seq66libdir@ datadir = @datadir@ datarootdir = @datarootdir@ seq66includedir = @seq66includedir@ seq66libdir = @seq66libdir@ #****************************************************************************** # localedir #------------------------------------------------------------------------------ # # 'localedir' is the normal system directory for installed localization # files. # #------------------------------------------------------------------------------ localedir = $(datadir)/locale DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ #***************************************************************************** # libtool #----------------------------------------------------------------------------- version = $(SEQ66_LIBTOOL_VERSION) #***************************************************************************** # git_version #----------------------------------------------------------------------------- # git_version = $(shell git describe --abbrev=7 --always --tags) #----------------------------------------------------------------------------- git_version = $(shell git describe --tags --long) git_branch =$(shell git branch | grep -e ^*) git_info = "$(git_version) $(git_branch)" #****************************************************************************** # Compiler and linker flags # # Unfortunately, we need to add the platform-specific include directories # because we include the performer module in some modules, and it includes # the platform-specific stuff. # #------------------------------------------------------------------------------ AM_CFLAGS = $(CFLAGS) -I ../include AM_CXXFLAGS = \ -I../include \ -I$(top_srcdir)/include \ -I$(top_srcdir)/libseq66/include \ -I$(top_srcdir)/libsessions/include \ -I$(top_srcdir)/seq_portmidi/include \ -I$(top_srcdir)/seq_rtmidi/include \ $(ALSA_CFLAGS) \ $(JACK_CFLAGS) \ $(CALLFLAG) \ -DSEQ66_GIT_VERSION=\"$(git_info)\" #****************************************************************************** # The library to build, a libtool-based library #------------------------------------------------------------------------------ lib_LTLIBRARIES = libseq66.la #****************************************************************************** # Source files #---------------------------------------------------------------------------- libseq66_la_SOURCES = \ seq66_features.cpp \ cfg/basesettings.cpp \ cfg/cmdlineopts.cpp \ cfg/comments.cpp \ cfg/configfile.cpp \ cfg/midicontrolfile.cpp \ cfg/mutegroupsfile.cpp \ cfg/notemapfile.cpp \ cfg/patchesfile.cpp \ cfg/playlistfile.cpp \ cfg/rcfile.cpp \ cfg/rcsettings.cpp \ cfg/recent.cpp \ cfg/scales.cpp \ cfg/sessionfile.cpp \ cfg/settings.cpp \ cfg/userinstrument.cpp \ cfg/usermidibus.cpp \ cfg/usrfile.cpp \ cfg/usrsettings.cpp \ cfg/zoomer.cpp \ ctrl/automation.cpp \ ctrl/keycontainer.cpp \ ctrl/keycontrol.cpp \ ctrl/keymap.cpp \ ctrl/keystroke.cpp \ ctrl/midicontrolin.cpp \ ctrl/midicontrolbase.cpp \ ctrl/midicontrol.cpp \ ctrl/midicontrolout.cpp \ ctrl/midimacro.cpp \ ctrl/midimacros.cpp \ ctrl/midioperation.cpp \ ctrl/opcontainer.cpp \ ctrl/opcontrol.cpp \ midi/businfo.cpp \ midi/calculations.cpp \ midi/controllers.cpp \ midi/drums.cpp \ midi/editable_event.cpp \ midi/editable_events.cpp \ midi/event.cpp \ midi/eventlist.cpp \ midi/jack_assistant.cpp \ midi/mastermidibase.cpp \ midi/midibase.cpp \ midi/midibytes.cpp \ midi/midifile.cpp \ midi/midi_splitter.cpp \ midi/midi_vector_base.cpp \ midi/midi_vector.cpp \ midi/patches.cpp \ midi/wrkfile.cpp \ play/clockslist.cpp \ play/inputslist.cpp \ play/metro.cpp \ play/mutegroup.cpp \ play/mutegroups.cpp \ play/notemapper.cpp \ play/performer.cpp \ play/playlist.cpp \ play/portslist.cpp \ play/screenset.cpp \ play/seq.cpp \ play/sequence.cpp \ play/setmapper.cpp \ play/setmaster.cpp \ play/songsummary.cpp \ play/triggers.cpp \ sessions/clinsmanager.cpp \ sessions/smanager.cpp \ os/daemonize.cpp \ os/shellexecute.cpp \ os/timing.cpp \ util/automutex.cpp \ util/basic_macros.cpp \ util/condition.cpp \ util/filefunctions.cpp \ util/named_bools.cpp \ util/palette.cpp \ util/recmutex.cpp \ util/rect.cpp \ util/ring_buffer.cpp \ util/strfunctions.cpp libseq66_la_LDFLAGS = -version-info $(version) libseq66_la_LIBADD = $(ALSA_LIBS) $(JACK_LIBS) #****************************************************************************** # uninstall-hook #------------------------------------------------------------------------------ # # We'd like to remove /usr/local/include/seq66-1.0 if it is # empty. However, we don't have a good way to do it yet. # #------------------------------------------------------------------------------ uninstall-hook: @echo "Note: you may want to remove $(libdir) manually" #****************************************************************************** # Makefile.am (libseq66/src) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: libseq66/src/cfg/basesettings.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file basesettings.cpp * * This module declares/defines some basic settings items. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-01-17 * \updates 2021-07-26 * \license GNU GPLv2 or above * */ #include "util/basic_macros.hpp" /* the C++ errprint() macro */ #include "cfg/basesettings.hpp" /* seq66::basesettings class */ /** * Indicates the "version" of the format of the "rc" ("rc", "ctrl", * "mutes", and "playlist" files. We hope to increment this number only * rarely. */ #define SEQ66_ORDINAL_VERSION 0 namespace seq66 { /** * Default constructor. */ basesettings::basesettings (const std::string & filename) : m_is_modified (false), m_ordinal_version (SEQ66_ORDINAL_VERSION), m_comments_block (), /* [comments] */ m_file_name (filename), m_error_message (), m_is_error () { // Empty body; it's no use to call normalize() here, see set_defaults(). } /** * Sets the default values. For the m_midi_buses and m_instruments members, * this function can only iterate over the current size of the vectors. But * the default size is zero! */ void basesettings::set_defaults () { m_is_modified = false; m_ordinal_version = SEQ66_ORDINAL_VERSION; m_file_name.clear(); m_error_message.clear(); m_is_error = false; normalize(); // recalculate derived values } /** * Calculate the derived values from the already-set values. Should we * normalize the BPM increment values here, in case they are irregular? * * gmute_tracks() is viable with variable set sizes only if we stick with the * 32 sets by 32 patterns, at this time. It's semantic meaning is...... * * m_max_sequence is now actually a constant (1024), so we enforce that here * now. */ void basesettings::normalize () { // \todo } /** * \return * Returns false if there is an error message in force. */ bool basesettings::set_error_message (const std::string & em) const { bool result = em.empty(); if (result) { m_error_message.clear(); m_is_error = false; } else { if (! m_error_message.empty()) m_error_message += "; "; m_error_message += em; errprint(em); } return result; } } // namespace seq66 /* * basesettings.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/cmdlineopts.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file cmdlineopts.cpp * * This module moves the command-line options processing in seq66.cpp * into the libseq66 library. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-11-20 * \updates 2026-04-17 * \license GNU GPLv2 or above * * The "rc" command-line options override setting that are first read from * the "rc" configuration file. These modified settings are always saved * when Seq66 exits, on the theory that somebody may have modified these * settings in the user-interface (there is currently no "dirty flag" for * this operation), and that command-line modifications to system-dependent * settings such as the JACK setup should be saved for convenience. * * The "user" settings are mostly not available from the command-line (--bus * being one exception). They, too, are partly system-dependent, but there * is no user-interface for changing the "user" options at this time. So the * "user" configuration file is not saved unless it does not exist in the * first place, or the "--user-save" option isprovided on the ommand line. * * We should backup the old versions of any saved configuration files at some * point. */ #include /* std::cout, etc. */ #include /* std::locale, etc. */ #include /* strlen() */ #include "cfg/cmdlineopts.hpp" /* this module's header file */ #include "cfg/rcfile.hpp" /* seq66::rcfile class */ #include "cfg/settings.hpp" /* seq66::rc() and usr() access */ #include "cfg/usrfile.hpp" /* seq66::usrfile class */ #include "util/basic_macros.hpp" /* not_nullptr() and other macros */ #include "util/filefunctions.hpp" /* file_read_writable(), etc. */ #include "util/strfunctions.hpp" /* string-to-numbers functions */ namespace seq66 { /** * Provides a return value for parse_command_line_options() that indicates a * help-related option was specified. Also included are values that * getopt_long(3) returns when an issue is encountered. */ static const int c_null_option = 99999; static const int c_missing_arg = ':'; /* only if ':' starts optstring */ static const int c_bad_option = '?'; /** * Sets up the "hardwired" version text for Seq66. This value * ultimately comes from the configure.ac script, and available in the * seq66_features module. Static. * * ca 2025-10-25 * Some builds uncover an order-of-initialization issue (we think) * that leads to a segfault. * * const std::string cmdlineopts::s_versiontext = seq_version_text(); */ const std::string cmdlineopts::s_versiontext { SEQ66_APP_NAME " " SEQ66_VERSION " " SEQ66_GIT_VERSION " " SEQ66_VERSION_DATE_SHORT "\n" }; /** * A structure for command parsing that provides the long forms of * command-line arguments, and associates them with their respective * short form. Note the terminating null structure. * * Qualifiers from getopt_long(3): * * - no_argument = 0 * - required_argument = 1 * - optional_argument = 2 * * Static. */ struct option cmdlineopts::s_long_options [] { {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {"verbose", no_argument, 0, 'v'}, #if USE_INSPECT_OPTION_FOR_SESSIONS {"inspect", required_argument, 0, 'I'}, /* duplicate of 'S' */ #endif {"investigate", no_argument, 0, 'i'}, {"home", required_argument, 0, 'H'}, #if defined SEQ66_NSM_SUPPORT {"no-nsm", no_argument, 0, 'T'}, {"nsm", no_argument, 0, 'n'}, #endif {"bus", required_argument, 0, 'b'}, {"buss", required_argument, 0, 'B'}, {"client-name", required_argument, 0, 'l'}, {"ppqn", required_argument, 0, 'q'}, {"show-midi", no_argument, 0, 's'}, {"show-keys", no_argument, 0, 'k'}, {"inverse", no_argument, 0, 'K'}, {"priority", optional_argument, 0, 'p'}, {"interaction-method", required_argument, 0, 'x'}, {"playlist", required_argument, 0, 'X'}, {"jack-start-mode", required_argument, 0, 'M'}, #if defined SEQ66_JACK_SUPPORT {"jack-transport", no_argument, 0, 'j'}, /* implies Slave */ /* * We need -S for "session" selection. * * {"jack-slave", no_argument, 0, 'S'}, */ {"no-jack-transport", no_argument, 0, 'g'}, {"jack-master", no_argument, 0, 'J'}, {"jack-master-cond", no_argument, 0, 'C'}, #if defined SEQ66_JACK_SESSION {"jack-session", required_argument, 0, 'U'}, #endif {"no-jack-midi", no_argument, 0, 'N'}, {"jack-midi", no_argument, 0, 't'}, {"jack", no_argument, 0, '1'}, /* = --jack-midi */ {"no-jack-connect", no_argument, 0, 'w'}, {"jack-connect", no_argument, 0, 'W'}, #endif {"manual-ports", no_argument, 0, 'm'}, {"auto-ports", no_argument, 0, 'a'}, {"reveal-ports", no_argument, 0, 'r'}, {"hide-ports", no_argument, 0, 'R'}, {"alsa", no_argument, 0, 'A'}, {"pass-sysex", no_argument, 0, 'P'}, {"user-save", no_argument, 0, 'u'}, {"record-by-channel", no_argument, 0, 'd'}, {"legacy-record", no_argument, 0, 'D'}, {"session", required_argument, 0, 'S'}, /* ca 2024-08-08 */ {"config", required_argument, 0, 'c'}, {"rc", required_argument, 0, 'f'}, {"usr", required_argument, 0, 'F'}, /* * Never implemented! * * {"load-recent", 0, 0, 'L'}, * {"no-load-recent", 0, 0, 'L'}, */ {"locale", required_argument, 0, 'L'}, {"User", no_argument, 0, 'Z'}, {"Native", no_argument, 0, 'z'}, /* * New app-specific options, for easier expansion. The -o/--option * processing is mostly handled outside of the get-opt setup, because it * can disable detection of a MIDI file-name argument. */ {"option", no_argument, 0, 'o'}, /* expansion! */ {0, 0, 0, 0} /* terminator */ }; /** * Provides a complete list of the short options, and is passed to * getopt_long(). The following string keeps track of the characters used so * far. An 'x' means the character is used. A ':' means it is used and * requires an argument. An 'a' indicates we could repurpose the key with * minimal impact. An asterisk indicates the option is reserved for * application-specific options. Currently we will use it for options like * "daemonize" in the seq66cli application. Common shell characters, except * for '#', are not include * \verbatim 0123456789#@AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz xx xx xx::x:xx :: x:x xxxxx::xxxx *x: :xx:xxxx:xxxx:: aa \endverbatim * * The I (inspect) options has been replaced by the S (session) option * to increase the usefulness of the sessions.rc file. * * Note that 'o' options arguments cannot be included here due to issues * involving parse_o_options(), but 'o' is *reserved* here, without the * argument indicator. * * Manual + User mode '-Z' versus Auto + Native mode '-z': * * Creates virtual ports '-m' and hides the native names for the ports * '-R' in favor of the 'usr' definition of the names of ports and * channels. The opposite (native) setting uses '-a' and '-r'. Both * modes turn on the --user-save (-u) option. * * Investigate: * * The -i/--investigate option is used on the command line), to turn on * the test-of-the-day and try to unearth difficult-to-find issues. * * Static. */ #if defined SEQ66_JACK_SUPPORT // how to handle no SEQ66_NSM_SUPPORT (n)? #define CMD_OPTS \ "01#AaB:b:Cc:DdF:f:gH:hiJjKkL:l:M:mNnoPp::q:RrS:sTtU:uVvWwX:x:Zz" #else #define CMD_OPTS \ "0#AaB:b:c:DdF:f:H:hI:iKkL:l:M:mnoPpq:RrS:sTuVvX:x:Zz#" #endif const std::string cmdlineopts::s_optstring = CMD_OPTS; /** * Indicates that there are no command-line options present. An attempt to * prevent scanning and reading files twice. Note that a MIDI file-name is * not counted as an option. Note that there are no options if optind is * equal to 1. */ bool cmdlineopts::s_no_cmd_line_options = false; bool cmdlineopts::cmd_line_options () { return ! s_no_cmd_line_options; } /** * Provides help text. */ static const std::string s_help_1a { "Options:\n" " -h, --help, ? Show this help and exit.\n" " -V, --version, # Show program version/build and exit.\n" " -v, --verbose Show more data to the console.\n" #if defined SEQ66_NSM_SUPPORT " -n, --nsm Activate debugging NSM support.\n" " -T, --no-nsm Ignore NSM in 'usr' file. (Typical).\n" #endif " -X, --playlist filename Load playlists (from \"home\" directory).\n" #if ! defined SEQ66_PORTMIDI_SUPPORT " -m, --manual-ports Create virtual ports (ALSA/JACK).\n" #endif " -a, --auto-ports Auto-Connect MIDI ports.\n" }; /** * More help text. */ static const std::string s_help_1b { " -r, --reveal-ports Don't show 'usr' definitions for port names.\n" " -R, --hide-ports Show 'usr' definitions for port names.\n" #if ! defined SEQ66_PLATFORM_WINDOWS " -A, --alsa Use ALSA, not JACK. A sticky option.\n" #endif " -b, --bus b Global override of bus number (for testing).\n" " -B, --buss b Covers the bus/buss confusion.\n" " -l, --client-name label Use label instead of 'seq66'. Overridden by a\n" " session manager.\n" " -q, --ppqn qn Specify default PPQN versus 192. The MIDI file\n" " can specify its own PPQN.\n" " -p=pri, --priority=pri High priority I/O (needs root); pri optional.\n" " -P, --pass-sysex Passes incoming SysEx messages to all outputs.\n" " Not yet fully implemented.\n" " -s, --show-midi Dump incoming MIDI events to the console.\n" }; /* * This option was never used, just settable, in Seq24. We need that letter! * * " -i, --ignore n Ignore ALSA device number." */ /** * Still more help text. */ static const std::string s_help_2 { " -k, --show-keys Prints pressed key value.\n" " -K, --inverse Inverse color scheme for seq/perf editors.\n" " -M, --jack-start-mode m ALSA or JACK play modes: live; song; auto.\n" #if defined SEQ66_JACK_SUPPORT " -j, --jack-transport Synchronize to JACK transport as Slave.\n" " -g, --no-jack-transport Turn off JACK transport.\n" " -J, --jack-master Set up as JACK Master. Also sets -j.\n" " -C, --jack-master-cond Fail if there's already a JACK Master; sets -j.\n" " -N, --no-jack-midi Use ALSA MIDI even with JACK Transport. See -A.\n" " -t, --jack, --jack-midi Use JACK MIDI, separately from JACK Transport.\n" " -W, --jack-connect Auto-connect to JACK ports. The default.\n" " -w, --no-jack-connect Don't connect to JACK ports. Good with NSM.\n" #if defined SEQ66_JACK_SESSION " -U, --jack-session uuid Set JACK session management UUID. Use 'on' to\n" " enable it and let JACK set the UUID.\n" #endif #endif " -d, --record-by-channel Divert MIDI input by channel into the patterns\n" " numbered for each channel.\n" " -D, --legacy-record Record all MIDI into the open pattern. Default.\n" }; /** * Still still more help text. */ static const std::string s_help_3 { " -0, --smf-0 Don't convert SMF 0 files to SMF 1 when read.\n" " -u, --user-save Force the save of 'usr' settings.\n" " -H, --home dir Directory for configuration. ~/.config/seq66\n" " by default. If not a full path, it's appended.\n" " -f, --rc filename An alternate 'rc' file in ~/.config/seq66 or\n" " the --home directory. '.rc' enforced.\n" " -F, --usr filename An alternate 'usr' file. Same rules as --rc.\n" " -c, --config basename Change base name of 'rc' and 'usr' files. The\n" " extension is stripped. [Default: 'qseq66'].\n" " -S, --session name Use alternate configuration from sessions.rc.\n" " -L, --locale lname Set global locale, if installed on the system.\n" " -i, --investigate Turn on various trouble-shooting code.\n" " -o, --option optoken Provides app-specific options for expansion.\n" " Options supported are:\n\n" }; /** * Still still still more more help text. */ static const std::string s_help_4a { " log=filename Redirect console output to a log file in home. If no\n" " '=filename' is provided, the name in '[user-options]'\n" " in the 'usr' file is used.\n" " sets=RxC Change set rows and columns from 4x8. R = 4 to 12;\n" " C = be 4 to 12. Call it the 'variset' mode. Affects\n" " mute groups, too.\n" }; static const std::string s_help_4b { " scale=x,y Scales size of main window. Range: 0.5 to 3.0.\n" " mutes=value Saving of mute-groups: 'mutes', 'midi', or 'both'.\n" " virtual=o,i Like --manual-ports, except the count of output and\n" " input ports are specified. Defaults are 8 & 4.\n" "\n" " seq66cli:\n\n" " daemonize Sets this application up to fork to the background.\n" " no-daemonize Or not. These options do not apply to Windows.\n" " The application writes these options to 'usr'\n" " and exits. Subsequent runs are thus affected. Tricky!\n" "\n" "Add '--user-save' to make these options permanent.\n" "\n" }; /** * Still still still more more more help text. */ static const std::string s_help_5 { "A saved MIDI file gets the current PPQN value. No JACK options shown if\n" "disabled in the build. Command-line options can be sticky; many\n" "are saved to the 'rc' files when Seq66 exits. See the Seq66 User Manual.\n" }; /** * Outputs the help text. */ void cmdlineopts::show_help () { std::cout << seq_app_name() << " v " << seq_version() << " A reboot of the seq24 live sequencer.\n" << "Usage: " << seq_app_name() << " [options] [MIDI filename]\n" << s_help_1a << s_help_1b << s_help_2 << s_help_3 << s_help_4a << s_help_4b << s_help_5 ; } /** * Gets a compound option argument. An option argument is a value flagged on * the command line by the -o/--option options. Each option has a value * associated with it, as the next argument on the command-line. A compound * option is of the form name=value, of which one example is: * * log=filename * * This function extracts both the name and the value. If the name is empty, * then the option is unsupported and should be ignored. If the value is * empty, then there may be a default value to use. * * \param compound * The putative compound option. * * \param [out] optionname * This value is filled in with the name of the option-value, if there * is an equals sign in the \a compound parameter. * * \return * Returns the value part of the compound option, or just the compound * parameter is there is no '=' sign. That is, it returns the * option-value. * * \sideeffect * The name portion is returned in the optionname parameter. */ std::string cmdlineopts::get_compound_option ( const std::string & compound, std::string & optionname ) { std::string value; auto eqpos { compound.find_first_of("=") }; if (eqpos == std::string::npos) { optionname.clear(); value = compound; } else { optionname = compound.substr(0, eqpos); /* beginning to eqpos chars */ value = compound.substr(eqpos + 1); /* rest of the string */ } return value; } /** * Checks to see if the first option is a help or version argument, just so * we can skip the "Reading configuration ..." messages. Also check for the * "?" option that people sometimes use as a guess to get help. * * \param argc * The number of command-line arguments. * * \param argv * The array of command-line argument pointers. * * \return * Returns true only if -v, -V, --version, -#, -h, --help, or "?" were * encountered. */ bool cmdlineopts::help_check (int argc, char * argv []) { bool result { false }; for ( ; argc > 1; --argc) { std::string arg { argv[argc - 1] }; if ( (arg == "-h") || (arg == "--help") || (arg == "-V") || (arg == "--version") || (arg == "-#") ) { result = true; break; } else if (arg == "?") { result = true; break; } } return result; } /** * Like help_check(), but accepts only 1 argument. Anything else * is ignored. * * \return * Returns true only if a single argument, "--kill", was found. */ bool cmdlineopts::kill_check (int argc, char * argv []) { bool result { argc == 2 }; if (result) { std::string arg { argv[1] }; result = arg == "--kill" || "kill"; } return result; } /** * Checks for the verbosity options. */ bool cmdlineopts::verbose_check (int argc, char * argv []) { bool result { false }; for ( ; argc > 1; --argc) { std::string arg { argv[argc - 1] }; if ((arg == "-v") || (arg == "--verbose")) { result = true; break; } } return result; } /** * Checks the putative command-line arguments for any of the new "options" * options. These are flagged by "-o" or "--option". These options are then * passed to the usrsettings or rcsettings modules. Multiple options can be * supplied; the whole command-line is processed. * * \param argc * The number of command-line parameters, including the name of the * application as parameter 0. * * \param argv * The array of pointers to the command-line parameters. * * \return * Returns true if any "options" option was found, and false otherwise. * If the options flags ("-o" or "--option") were found, but were not * valid options, then we break out of processing and return false. */ bool cmdlineopts::parse_o_options (int argc, char * argv []) { bool result = { false }; if (argc > 1 && not_nullptr(argv)) { int argn { 1 }; std::string arg; std::string optionname; while (argn < argc) { if (not_nullptr(argv[argn])) { arg = argv[argn]; if ((arg == "-o") || (arg == "--option")) { ++argn; if (argn < argc && not_nullptr(argv[argn])) { arg = get_compound_option(argv[argn], optionname); if (optionname.empty()) { if (arg == "daemonize") { result = true; usr().option_daemonize(true, true); // setup! } else if (arg == "no-daemonize") { result = true; usr().option_daemonize(false, true); // setup! } else if (arg == "log") { /* * Without a filename, just turn on the * log-file flag, using the name already read * from the "[user-options]" section of the * "usr" file. */ result = true; usr().option_use_logfile(true); } } else { /* * \tricky * Note that 'arg' is used in the clause * above, but 'optionname' is used here. */ if (optionname == "log") { result = true; arg = strip_quotes(arg); usr().option_logfile(arg); } else if (optionname == "sets") { result = parse_o_sets(arg); } else if (optionname == "scale") { if (arg.length() >= 1) result = usr().parse_window_scale(arg); } else if (optionname == "mutes") { result = parse_o_mutes(arg); } else if (optionname == "virtual") { result = parse_o_virtual(arg); } } if (! result) { warn_message("--option", "unsupported name"); break; } } } } else break; ++argn; } } return result; } /** * Checks the putative command-line arguments for the "log" option. Generally, * this function needs to be called near the beginning of main(). See the * rtmidi version of the main() function, for example. * * \param argc * The number of command-line parameters, including the name of the * application as parameter 0. * * \param argv * The array of pointers to the command-line parameters. * * \return * Returns true if stdio was rerouted to the "usr"-specified log-file. */ bool cmdlineopts::parse_log_option (int argc, char * argv []) { bool result { false }; std::string exename { argv[0] }; if (contains(exename, "verbose")) /* symlink to dev's program */ { #if defined SEQ66_PLATFORM_DEBUG file_message("Running debug/investigate version", argv[0]); #else file_message("Running", argv[0]); #endif rc().verbose(true); rc().investigate(true); file_message(exename, "Verbose/investigate mode enabled"); } if (parse_o_options(argc, argv)) { std::string logfile { usr().option_logfile() }; if (! logfile.empty()) result = true; } return result; } /** * Provides the command-line option support, as well as some setup support, * extracted from the main routine of Seq66. * * It also requires the caller to call rc().set_defaults() and * usr().set_defaults() at the appropriate time, which is before any parsing * of the command-line options. The caller can then use the command-line to * make any modifications to the setting that will be used here. The biggest * example is the -r/--reveal-ports option, which determines if the MIDI * buss definition strings are read from the 'usr' configuration file. * * Instead of the legacy Seq24 names, we use the new configuration * file-names, located in the ~/.config/seq66 directory. If they are not * found, we no longer fall back to the Seq24 configuration file-names. The * code also ensures the directory exists. See the rcsettings class for how * this works. * \verbatim std::string cfg_dir = seq66::rc().home_config_directory(); if (cfg_dir.empty()) return EXIT_FAILURE; \endverbatim * * We were parsing the user-file first, but we now need to parse the rc-file * first, to get the manual-ports option, so that we can avoid overriding * the port names that the ALSA system provides, if the manual-option is * false. * * \param [out] errmessage * Provides a string into which to dump any error-message that might * occur. Still not thoroughly supported in the "rc" and "user" * configuration files. * * \param argc * The number of command-line arguments. * * \param argv * The array of command-line argument pointers. * * \return * Returns true if the reading of both configuration files succeeded, or * if they did not exist. In the latter case, they will be saved as new * files upon exit. In other words, missing configuration files is not * an error. */ bool cmdlineopts::parse_options_files (std::string & errmessage) { std::string rcn { rc().config_filespec() }; bool result { parse_rc_file(rcn, errmessage) }; if (result) { rcn = rc().user_filespec(); result = parse_usr_file(rcn, errmessage); } return result; } bool cmdlineopts::parse_rc_file ( const std::string & filespec, std::string & errmessage ) { bool result { true }; /* file_readable(filespec) */ if (file_readable(filespec)) { rcfile options(filespec, rc()); file_message("Read rc", filespec); result = options.parse(); if (! result) { errmessage = options.get_error_message(); file_error("rc", errmessage); } } else { /* * We don't to force always saving permanently. * * rc().auto_rc_save(true); */ file_error("Cannot read", filespec); rc().first_run_in_progress(true); rc().create_config_names(); } return result; } /** * Get only the 'usr' file and its active flags from the 'rc' file. This * function supports testing to see if the application should be * daemonized. See cmdlineopts::parse_daemonization() and * userfile::parse_daemonization(). */ bool cmdlineopts::get_usr_file () { std::string rcn { rc().config_filespec() }; bool result { file_readable(rcn) }; if (result) { rcfile options(rcn, rc()); file_message("Read rc to get 'usr' file", rcn); result = options.get_usr_file(); if (! result) file_error("Getting 'usr' file failed", rcn); } else { /* * We don't to force always saving permanently. * * rc().auto_rc_save(true); */ file_error("Cannot read", rcn); rc().first_run_in_progress(true); rc().auto_rc_save(true); } return result; } bool cmdlineopts::parse_usr_file ( const std::string & filespec, std::string & errmessage ) { bool result { true }; if (file_readable(filespec)) { usrfile ufile(filespec, rc()); file_message("Read usr", filespec); result = ufile.parse(); if (! result) { errmessage = ufile.get_error_message(); file_error("usr", errmessage); } } else { /* * We don't to force always saving permanently. * * rc().auto_rc_save(true); */ file_error("Cannot read", filespec); rc().first_run_in_progress(true); rc().auto_usr_save(true); } return result; } /** * This function figures out if the application is to be daemonized. It needs * to do the following: * * -# Read the 'usr' file "[usr-file]" section: * - "active" flag * - "name" of the 'usr' file. * -# Make sure the 'usr' file is active and readable. * -# Parse the daemonization and logging values from the 'usr' file. */ bool cmdlineopts::parse_daemonization (bool & startdaemon, std::string & logfile) { bool result { cmdlineopts::get_usr_file() }; /* daemon values */ if (result) { std::string usrn { rc().user_filespec() }; result = file_readable(usrn); if (result) { usrfile ufile(usrn, rc()); result = ufile.parse_daemonization(startdaemon, logfile); } else { result = false; startdaemon = false; logfile = ""; } } return result; } bool cmdlineopts::parse_o_sets (const std::string & arg) { bool result { arg.length() >= 3 }; if (result) { int rows { string_to_int(arg) }; auto p { arg.find_first_of("x") }; if (p != std::string::npos) { int cols { string_to_int(arg.substr(p+1)) }; usr().mainwnd_rows(rows); usr().mainwnd_cols(cols); #if defined SEQ66_USE_AUTO_SCALING /* * This works for FHD screens (1920 x 1080). */ if (rows > 4) { float scale { float(rows) / 4.0f }; float scaley { 1.0f }; if (scale > 1.5) scale = 1.0; usr().window_scale(scale, scaley, true); } #endif } else result = false; } return result; } /** * The performer object will grab this setting and pass it to the mutegroups * object that it owns. See performer::open_mutegroup(). */ bool cmdlineopts::parse_o_mutes (const std::string & arg) { bool result { arg == "mutes" || arg == "midi" || arg == "both" }; if (result) { mutegroups::saving v { mutegroups::string_to_group_save(arg) }; if (v != mutegroups::saving::max) rc().mute_group_save(v); } return result; } bool cmdlineopts::parse_o_virtual (const std::string & arg) { int out { 0 }, in { 0 }; rc().manual_ports(true); if (! arg.empty()) { out = string_to_int(arg); auto p { arg.find_first_of(",") }; if (p != std::string::npos) { in = string_to_int(arg.substr(p + 1)); } else { p = arg.find_first_of("x"); /* if user uses "x" by habit */ if (p != std::string::npos) in = string_to_int(arg.substr(p + 1)); } } rc().manual_port_count(out); rc().manual_in_port_count(in); return true; } /** * Parses the command-line options on behalf of the application. Note that, * since we call this function twice (once before the configuration files are * parsed, and once after), we have to make sure that the global value optind * is reset to 0 before calling this function. Note that the traditional * reset value for optind is 1, but 0 is used in GNU code to trigger the * internal initialization routine of get_opt(). * * Hidden values per getopt(3): * * extern char *optarg; * extern int optind, opterr, optopt; * * At the end, optind is the index in argv of the first argv element that is * not an option. This is used in smanager::main_settings() to detect that * a MIDI file has been specified on the command-line. * * \param argc * The number of command-line arguments. * * \param argv * The array of command-line argument pointers. * * \return * Returns the value of optind if no help-related options were provided. * Returns (-1) if an error occurred. */ int cmdlineopts::parse_command_line_options (int argc, char * argv []) { int result { 0 }; int option_index { 0 }; /* getopt_long index storage */ std::string optionval; /* used only with -o options */ std::string optionname; /* ditto */ /* * opterr = 0; // suppress error messages // */ optind = 1; /* reset global for (re)scan */ for (;;) /* scan all arguments */ { int c { getopt_long ( argc, argv, s_optstring.c_str(), /* e.g. "Crb:q:Li:jM:pU:Vx:..." */ s_long_options, &option_index ) }; if (c == c_missing_arg || c == c_bad_option) { char tmp[32]; snprintf(tmp, sizeof tmp, "'%s'", argv[optind-1]); error_message("Bad option encountered", tmp); return (-1); } else if (c == (-1)) /* done */ { break; } std::string soptarg; if (not_nullptr(optarg)) soptarg = std::string(optarg); switch (c) { case '0': usr().convert_to_smf_1(false); break; #if defined SEQ66_JACK_SUPPORT case '1': /* added for consistency with --alsa */ case 't': rc().with_jack_midi(true); break; #endif case '#': std::cout << SEQ66_VERSION << std::endl; result = c_null_option; break; case 'A': rc().with_jack_transport(false); rc().with_jack_master(false); rc().with_jack_master_cond(false); rc().with_jack_midi(false); infoprint("Forcing all-ALSA mode"); break; case 'a': rc().manual_ports(false); break; case 'B': /* --buss for the oldsters */ case 'b': /* --bus for the youngsters */ usr().midi_buss_override(string_to_midibyte(soptarg)); break; #if defined SEQ66_JACK_SUPPORT case 'C': rc().with_jack_transport(true); rc().with_jack_master(false); rc().with_jack_master_cond(true); break; #endif case 'c': /* --config */ rc().set_config_files(soptarg); break; case 'D': /* --legacy-record */ rc().record_by_channel(false); break; case 'd': /* --record-by-channel */ rc().record_by_channel(true); break; case 'F': /* --usr */ rc().user_filename(soptarg); break; case 'f': /* --rc */ rc().config_filename(soptarg); break; case 'g': rc().with_jack_transport(false); rc().with_jack_master(false); rc().with_jack_master_cond(false); break; case 'H': rc().set_config_directory(soptarg); break; case 'h': show_help(); result = c_null_option; break; #if USE_INSPECT_OPTION_FOR_SESSIONS case 'I': rc().inspection_tag(soptarg); /* see smanager and sessionfile */ break; #endif case 'i': rc().investigate(true); break; #if defined SEQ66_JACK_SUPPORT case 'J': rc().with_jack_transport(true); rc().with_jack_master(true); rc().with_jack_master_cond(false); break; case 'j': rc().with_jack_transport(true); rc().with_jack_master(false); rc().with_jack_master_cond(false); break; #endif case 'K': usr().inverse_colors(true); break; case 'k': rc().print_keys(true); break; case 'L': (void) set_global_locale(soptarg); break; case 'l': set_client_name(soptarg); break; case 'M': rc().song_start_mode_by_string(std::string(soptarg)); break; case 'm': rc().manual_ports(true); break; case 'N': rc().with_jack_midi(false); break; #if defined SEQ66_NSM_SUPPORT case 'n': usr().session_manager("nsm"); /* mostly for debugging */ usr().in_nsm_session(); /* definitely debugging */ break; #endif case 'o': /* * We now handle this processing separately and first, in the * parse_o_option() function. Doing it here can mess up parsing. * We need to skip the argument in case there are other arguments * or a MIDI filename following the compound option. */ ++optind; break; case 'P': rc().pass_sysex(true); break; case 'p': rc().priority(true); if (soptarg.empty()) rc().thread_priority(0); /* c_thread_priority) */ else rc().thread_priority(string_to_int(soptarg, 0)); break; case 'q': usr().midi_ppqn(string_to_int(soptarg)); break; case 'R': rc().reveal_ports(false); break; case 'r': rc().reveal_ports(true); break; #if defined SEQ66_JACK_SUPPORT_OBSOLETE /* need S for "session" */ case 'S': /* -j and -S are the same */ rc().with_jack_transport(true); rc().with_jack_master(false); rc().with_jack_master_cond(false); break; #else case 'S': /* replaces 'I' */ rc().session_tag(soptarg); break; #endif case 's': rc().show_midi(true); break; #if defined SEQ66_NSM_SUPPORT case 'T': usr().session_manager("none"); break; #endif #if defined SEQ66_JACK_SUPPORT_NOT_MOVED_TO_ABOVE case 't': rc().with_jack_midi(true); break; #endif #if defined SEQ66_JACK_SESSION case 'U': rc().jack_session(soptarg); break; #endif case 'u': rc().auto_usr_save(true); /* usr(), not rc()! */ break; case 'V': std::cout << s_versiontext << seq_build_details(); result = c_null_option; break; case 'v': rc().verbose(true); break; #if defined SEQ66_JACK_SUPPORT case 'W': rc().jack_auto_connect(true); break; case 'w': rc().jack_auto_connect(false); break; #endif case 'X': rc().playlist_active(rc().playlist_filename_checked(soptarg)); break; /* * Undocumented and unsupported in Seq66. Kept around just in case. */ case 'x': rc().interaction_method(string_to_int(soptarg)); break; case 'Z': /* User mode */ rc().manual_ports(true); rc().reveal_ports(false); rc().auto_usr_save(true); break; case 'z': /* Native mode */ rc().manual_ports(false); rc().reveal_ports(true); rc().auto_usr_save(true); break; default: break; } } if (result != c_null_option) { std::size_t applen { strlen("seq66") }; std::string appname { argv[0] }; /* "seq66", "./seq66", etc. */ appname = appname.substr(appname.size()-applen, applen); result = optind; #if defined SEQ66_PLATFORM_DEBUG_TMI if (optind < argc) { printf ( "NON-OPTION argv ELEMENTS for %d of %d arguments: ", optind, argc ); while (optind < argc) printf("%s ", argv[optind++]); printf("\n"); } #endif } if (optind == 1) cmdlineopts::s_no_cmd_line_options = true; return result; } /** * Environment variables specific to Seq66. */ std::string cmdlineopts::env_session_tag () { static std::string s_session_variable { "SEQ66_SESSION_TAG" }; char * env { std::getenv(s_session_variable.c_str()) }; std::string result; if (not_nullptr(env)) result = std::string(env); return result; } /** * Locale-related functions. * * The MingW compiler implementation may have a bug, as this fails in our * Windows 10 virtual machine development system. */ void cmdlineopts::show_locale () { try { std::locale loc { "" }; /* get the user's preferred locale */ std::string msg { loc.name() }; status_message("Locale", msg); } catch (const std::runtime_error &) { /* * Can occur under Windows 10. */ } } bool cmdlineopts::set_global_locale (const std::string & lname) { bool result { ! lname.empty() }; if (result) { try { std::locale oldlocale { std::locale::global(std::locale{lname}) }; std::locale newlocale; /* default ctor yields global loc. */ std::string oldname { oldlocale.name() }; std::string newname { newlocale.name() }; std::string msg { oldname + " ---> " + newname }; status_message("Locale", msg); } catch (std::runtime_error & re) { std::string tag { "Locale '" + lname + "'" }; error_message(tag, re.what()); result = false; } } return result; } /** * Saves all options to the "rc" and "user" configuration files. This * function ignores any global variables. * * \note * If an error occurs, the files "erroneous.rc" and "erroneousl.usr" will * be written as a aid to trouble-shooting. However, if the normal "rc" * file specified alternate "mutes" and "ctrl" files, those will be * written to their specified names, not "erroneous" names. * * \param filebase * This value, if not empty, provides an altername base name for the * writing of the "rc" and "user" files. Normally empty, it can be * specified in order to write alternate files without overwriting the * existing ones, when a serious error occurs. It should not include the * extension; the proper one will be added. * * \return * Returns true if both files were saved successfully. Otherwise returns * false. But even if one write failed, the other might have succeeded. */ bool cmdlineopts::write_options_files (const std::string & filebase) { bool result { write_rc_file(filebase) }; if (result) result = write_usr_file(filebase); return result; } bool cmdlineopts::write_rc_file (const std::string & filebase) { bool result { true }; if (rc().auto_rc_save() || rc().first_run_in_progress()) { std::string rcn; if (filebase.empty()) { rcn = rc().config_filespec(); } else { std::string name { file_extension_set(filebase, ".rc") }; rcn = rc().config_filespec(name); } rcfile options(rcn, rc()); result = options.write(); if (! result) file_error("Write failed", rcn); } return result; } /** * These two functions are used in smanager::export_session_configuration. */ bool cmdlineopts::alt_write_rc_file (const std::string & filebase) { return write_rc_file(filebase); } bool cmdlineopts::write_usr_file (const std::string & filebase) { bool result { true }; if (rc().auto_usr_save()) { std::string usrn; if (filebase.empty()) { usrn = rc().user_filespec(); } else { std::string name { file_extension_set(filebase, ".usr") }; usrn = rc().user_filespec(name); } usrfile userstuff(usrn, rc()); result = userstuff.write(); if (! result) file_error("Write failed", usrn); } return result; } bool cmdlineopts::alt_write_usr_file (const std::string & filebase) { bool result { true }; std::string name { file_extension_set(filebase, ".usr") }; std::string usrn { rc().user_filespec(name) }; usrfile userstuff(usrn, rc()); result = userstuff.write(); if (! result) file_error("Write failed", usrn); return result; } } // namespace seq66 /* * cmdlineopts.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/comments.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file comments.cpp * * This module declares/defines just some of the global (gasp!) variables * in this application. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-26 * \updates 2021-06-07 * \license GNU GPLv2 or above * */ #include "cfg/comments.hpp" /* seq66::comments class */ namespace seq66 { /** * Default constructor. Here, we do not use the set() function. Makes * debugging what caller's are doing with set() a little easier. */ comments::comments (const std::string & comtext) : m_comments_block (comtext), m_comment_is_set (false) { if (comtext.empty()) m_comments_block = "Add your comment block here\n"; } void comments::clear () { m_comments_block.clear(); m_comment_is_set = false; } void comments::set (const std::string & block) { m_comments_block = block; m_comment_is_set = ! m_comments_block.empty(); } } // namespace seq66 /* * comments.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/configfile.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file configfile.cpp * * This module defines the base class for configuration and options * files. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-23 * \updates 2025-01-19 * \license GNU GPLv2 or above * * std::streamoff is a signed integral type (usually long long) that can * represent the maximum possible file size supported by the operating * system. It is used to represent offsets from std::streampos. -1 is used * to represent error conditions by some I/O library functions. * * Relationship with std::streampos (a specialization of std::fpos): * * - The difference between two std::streampos objects is a value of type * std::streamoff. * - An std::streamoff value may be added or subtracted from std::fpos, * yielding a different std::fpos. * - An std::streampos is implicitly convertible to std::streamoff (the * result is the offset from the beginning of the file). * - An std::streampos is constructible from an std::streamoff. * - An std::streampos contains an std::streamoff. * * istream::tellg() returns a streampos. */ #include /* std::isspace(), std::isdigit() */ #include /* std::hex, std::setw() */ #include "cfg/configfile.hpp" /* seq66:: configfile class */ #include "cfg/rcsettings.hpp" /* seq66::rcsettings class */ #include "util/filefunctions.hpp" /* seq66::filename_base() etc. */ #include "util/strfunctions.hpp" /* strncompare() for std::string */ namespace seq66 { /* * Static members. This error-messaging information is static so that the * errors from all the configuration files can be displayed at once. */ std::string configfile::sm_error_message; bool configfile::sm_is_error = false; int configfile::sm_int_missing = -9998; int configfile::sm_int_default = -9999; float configfile::sm_float_missing = -9998.0f; float configfile::sm_float_default = -9999.0f; tokenization configfile::sm_file_extensions { ".ctrl", ".drums", ".mutes", ".palette", ".playlist", ".qss", ".rc", ".usr" }; /** * Provides the string plus rcsettings constructor for a configuration file. * * \param name * The name of the configuration file. * * \param rcs * A reference to the rcsettings object to hold the settings of the * configuration file. It applies to all configuration files, including * usrfile. */ configfile::configfile ( const std::string & name, rcsettings & rcs, const std::string & fileext ) : m_rc (rcs), m_file_extension (fileext), m_name (name), m_version ("0"), m_file_version ("0"), m_line (), m_line_number (0), m_line_pos (0) { // no code needed } /** * Returns a pre-trimmed line from the configuration file. As part of this * trimming, double quotes (not single quotes) at the beginning and end are * also removed. The check is not robust at this time. * * \return * Returns a copy of line(), but trimmed of white space and, if present, * quotes surrounding the line after the space trimming. */ std::string configfile::trimline () const { std::string result = line(); result = trim(result); auto bpos = result.find_first_of("\""); if (bpos != std::string::npos) { auto epos = result.find_last_of("\""); int len; if (epos != std::string::npos) len = int(epos - bpos - 1); else len = int(result.length() - 1 - bpos); result = result.substr(bpos + 1, len); } return result; } /** * [comments] * * Header commentary is skipped during parsing. However, we provide an * optional comment block. Trimming of spaces is disabled for this operation. */ std::string configfile::parse_comments (std::ifstream & file) { std::string result; if (line_after(file, "[comments]", 0, false)) /* 1st line w/out strip */ { do { result += line(); result += "\n"; } while (next_data_line(file, false)); /* no strip here either */ } return result; } std::string configfile::parse_version (std::ifstream & file) { std::string result = get_variable(file, "[Seq66]", "version"); file_version(result); return result; } bool configfile::file_version_is_old (std::ifstream & file) { std::string file_version_string = parse_version(file); int file_version = string_to_int(file_version_string); int code_version = version_number(); return file_version < code_version; } /** * Helper function for error-handling. It assembles a message and then * passes it to error_message(). * * \param sectionname * Provides the name of the section for reporting the error. * * \param additional * Additional context information to help in finding the error. * * \return * Always returns false. */ bool configfile::make_error_message ( const std::string & sectionname, const std::string & additional ) { std::string msg = sectionname; msg += ": "; if (! additional.empty()) msg += additional; warn_message(msg); /* log to the console */ append_error_message(msg); /* append to message string */ return false; } bool configfile::version_error_message (const std::string & configtype, int vnumber) { std::string msg = "'"; msg += configtype; msg += "' file version "; msg += std::to_string(vnumber); msg += " is too old; will upgrade.\n"; return make_error_message("Version error", msg); } /** * A useful intermediate function to save a call and allow for debugging. In * addition, it also trims basic white-space from the beginning and end of * the line, to make parsing a little more robust. * * Also replaces file.get_line(m_line, sizeof m_line) with an std::string * get_line(). * * \param file * The reference to the opened input file-stream. * * \param strip * If true, then strip out any following comment in the line, as denoted * by a hash-tag character not enclosed in single- or double-quotes. * The default value is false. * * \return * Returns the value of file.good(). If the trimmed line is empty, * returns true, too; the caller can ignore the line. */ bool configfile::get_line (std::ifstream & file, bool strip) { m_line_pos = file.tellg(); (void) std::getline(file, m_line); if (strip) { m_line = trim(m_line); m_line = strip_comments(m_line); } bool result = file.good(); if (result) ++m_line_number; return result; } /** * Gets the next line of data from an input stream. If the line starts with * a number-sign, or a null, it is skipped, to try the next line. This * occurs until a section marker ("[") or an EOF is encountered. Member * m_line is a "return" value (side-effect). * * \param file * Points to an input stream. We converted this item to a reference; * pointers can be subject to problems. For example, what if someone * passes a null pointer? Nonetheless, this is a small library. Since * this function has this parameter, the caller can deal with multiple * files at the same time. * * \param strip * If true (the default), trims white space and strips out hash-tag * comments. Some sections, such as '[comments]', need this to be set to * false. * * \return * Returns true if a presumed data line was found. False is returned if * not found before an EOF or a section marker ("[") is found. This * feature assists in adding new data to the file without crapping out on * old-style configuration files. */ bool configfile::next_data_line (std::ifstream & file, bool strip) { bool result = get_line(file, strip); /* optional white zappage */ if (result) { char ch = m_line[0]; while ((ch == '#' || ch == '[' || ch == 0) && ! file.eof()) { if (m_line[0] == '[') /* we hit the next section */ { result = false; break; } if (get_line(file, strip)) /* may trim white space */ { ch = m_line[0]; } else { result = false; break; } } if (file.eof()) result = false; } return result; } /** * Acts like a combination of line_after() and next_data_line(), but requires * a specific variable-name to be found. For example, given the following * lines (blank lines are simply ignored): * \verbatim [loop-control] liveplay = true bpm = 120 name = "Funky" \endverbatim * * std::string s = next_variable(f, "[loop-control]", "name"); * will return s = "Funky". * * See the extract_value() function for information on the parsing of the * line. * * \warning * This function assumes that the next_data_line() function * * \param file * Provides the file-stream that is being read. * * \param tag * Provides the section tag (e.g. "[midi-control]") to be found before * the variable name parameter can be processed. * * \param variablename * Provides the variablename to be found in the specified tag section. * * \param position * Indicates the position to seek to, which defaults to 0 * (std::iso::beg). A non-default value is useful to speed up parsing in * cases where sections are always ordered. * * \return * If the "variablename = value" clause is found, then the the value that * is read is returned. Otherwise, an questionable-string is returned, * which is an error, while an empty string might signify a default value. * If the name is surrounded by single or double quotes, these are * trimmed. */ std::string configfile::get_variable ( std::ifstream & file, const std::string & tag, const std::string & variablename, int position ) { std::string result = questionable_string(); /* used when tag is missing */ if (! tag.empty() && ! variablename.empty()) { for ( bool done = ! line_after(file, tag, position); ! done; done = ! next_data_line(file) ) { if (! line().empty()) /* any value in section? */ { std::string value = extract_variable(line(), variablename); if (! is_questionable_string(value)) { result = value; break; } } } } return result; } /** * Parses a line of the form "name = value". Now exposed for use outside of * the get_variable() function. This function assumes that the line has been * found. * * The spaces around the '=' are optional. This function is meant to better * support an "INI" style of value specification. The variable name should * be obvious, whereas in the Seq24 implementation we had to append a comment * to make it easier for the user to comprehend the configuration file. * * Check-point 1: * * See if there is any space after the variable name, but before the "=" * sign. If so, the first space, not the "=" sign, terminates the * variable name. * * Check-point 2: * * Now get the first non-space after the "=" sign. If there is a * double-quote character ('"'), then see if there a match quote, and get * everything inside the quotes. Otherwise, grab the first token on the * value (right) side. Note that single-quotes are not treated as quote * characters. * * \param line * A line of the form "name = value". * * \param variablename * The "name" of the variable whose value is to be extracted. * * \return * If the extracted name matches the \a name parameter, then the value is * returned; it might be empty. Otherwise, a question mark is returned. */ std::string configfile::extract_variable ( const std::string & line, const std::string & variablename ) { std::string result = questionable_string(); /* a fancy name for "?" */ auto epos = line.find_first_of("="); if (epos != std::string::npos) { auto spos = line.find_first_of(" "); /* Check-point 1 */ if (spos > epos) spos = epos; std::string vname = line.substr(0, spos); if (vname == variablename) { bool havequotes = false; /* Check-point 2 */ char quotechar[2] = { 'x', 0 }; auto qpos = line.find_first_of("\"'", epos + 1); auto qpos2 = std::string::npos; if (qpos != std::string::npos) { quotechar[0] = line[qpos]; qpos2 = line.find_first_of(quotechar, qpos + 1); if (qpos2 != std::string::npos) havequotes = true; } if (havequotes) { result = line.substr(qpos + 1, qpos2 - qpos - 1); } else { spos = line.find_first_not_of(" ", epos + 1); if (spos != std::string::npos) { epos = line.find_first_of(" ", spos); result = line.substr(spos, epos-spos); } } } } return result; } /** * This function cannot detect a missing value. */ bool configfile::get_boolean ( std::ifstream & file, const std::string & tag, const std::string & variablename, int position, bool defalt ) { std::string value = get_variable(file, tag, variablename, position); return string_to_bool(value, defalt); } void configfile::write_seq66_header ( std::ofstream & file, const std::string & configtype, const std::string & ver ) { file << "\n[Seq66]\n\nconfig-type = \"" << configtype << "\"\n" "version = " << ver << "\n" ; } void configfile::write_seq66_footer (std::ofstream & file) { file << "\n# End of " << name() << "\n#\n# vim: sw=4 ts=4 wm=4 et ft=dosini\n" ; } void configfile::write_boolean ( std::ofstream & file, const std::string & name, bool status ) { file << name << " = " << bool_to_string(status) << "\n"; } /** * This function cannot detect a missing value. */ int configfile::get_integer ( std::ifstream & file, const std::string & tag, const std::string & variablename, int position ) { std::string value = get_variable(file, tag, variablename, position); int result = sm_int_missing; if (! is_missing_string(value)) /* ! value.empty() */ result = value == "default" ? sm_int_default : string_to_int(value) ; return result; } void configfile::write_integer ( std::ofstream & file, const std::string & name, int value, bool usehex ) { if (usehex) file << name << " = 0x" << std::hex << std::setw(2) << value << "\n"; else file << name << " = " << value << "\n"; } /** * This function cannot detect a missing value. */ float configfile::get_float ( std::ifstream & file, const std::string & tag, const std::string & variablename, int position ) { std::string value = get_variable(file, tag, variablename, position); float result = sm_float_missing; if (! is_missing_string(value)) /* ! value.empty() */ { result = value == "default" ? sm_float_default : float(string_to_double(value)) ; } return result; } void configfile::write_float ( std::ofstream & file, const std::string & name, float value ) { file << name << " = " << value << "\n"; } /** * Handles a number of write-string cases. We make copies of the string * value for internal use by this function. Can optionally make sure the * value is quoted. Empty strings are always quoted. */ void configfile::write_string ( std::ofstream & file, const std::string & name, std::string value, bool quote_it ) { bool add_equals = true; if (is_empty_string(name)) /* standalone, no-name string */ add_equals = false; if (is_missing_string(value)) quote_it = true; /* always quote empty strings */ if (quote_it) value = add_quotes(value); if (add_equals) file << name << " = " << value << "\n"; else file << value << "\n"; /* a no-name value */ } /** * Gets the active flag and the name of the file from the given tag section. * Very useful for all "included" configuration files. * * We now enforce that all configuration files are restricted to the HOME * directory, so we also strip the path from the file-name. */ bool configfile::get_file_status ( std::ifstream & file, const std::string & tag, std::string & filename, /* side-effect */ int position ) { bool result = get_boolean(file, tag, "active", position); filename = strip_quotes(get_variable(file, tag, "name", position)); if (is_missing_string(filename)) /* filename.empty() */ { result = false; } else { if (name_has_path(filename)) filename = filename_base(filename); } return result; } void configfile::write_file_status ( std::ofstream & file, const std::string & tag, const std::string & filename, bool status ) { std::string quoted = add_quotes(filename); file << "\n" << tag << "\n\n" << "active = " << bool_to_string(status) << "\n" << "name = " << quoted << "\n" ; } void configfile::write_comment ( std::ofstream & file, const std::string & commenttext ) { file << "\n" "# [comments] holds user documentation for this file. The first empty, hash-\n" "# commented, or tag line ends the comment.\n" "\n[comments]\n\n" << commenttext ; } /** * Looks for the next named section. Unlike line_after(), it does not * restart from the beginning of the file. Like next_data_line(), it starts * at the current line in the file. This makes it useful in parsing files, * such as a playlist, that have multiple sections with the same name. * * Note one other quirk. If we are on a line matching the tag, then we do * not search, but instead use that line. The reason is that the * next_data_line() function for the previous section will often end up * at the beginning of the next section. Especially important with * play-lists. * * \param file * Points to the input file stream. Since this function has this * parameter, the caller can deal with multiple files at the same time. * However, the caller must either close the file explicitly or open it * on the stack. * * \param tag * Provides a tag to be found. Lines are read until a match occurs * with this tag. Normally, the tag is a section marker, such as * "[user-interface]". Best to assume an exact match is needed. * * \return * Returns true if the tag was found. Otherwise, false is returned. */ bool configfile::next_section (std::ifstream & file, const std::string & tag) { bool result = false; file.clear(); if (tag == m_line) { result = true; } else { bool ok = get_line(file); /* fills in m_line as a side-effect */ while (ok) /* includes the EOF check */ { result = strncompare(m_line, tag); if (result) { break; } else { if (file.bad()) error_message("bad file stream reading config file"); else ok = get_line(file); /* trims the white space */ } } } if (result) result = next_data_line(file); return result; } /** * This function gets a specific line of text, specified as a tag. * Then it gets the next non-blank line (i.e. data line) after that. * This function is normally used to find major sections enclosed in brackets, * such as "[midi-control]". * * This function always starts from the beginning of the file. Therefore, * it can handle reading Seq66 configuration files that have had * their tagged sections arranged in a different order. This feature makes * the configuration file a little more robust against errors. * * \param file * Points to the input file stream. Since this function has this * parameter, the caller can deal with multiple files at the same time. * However, the caller must either close the file explicitly or open it * on the stack. * * \param tag * Provides a tag to be found. Lines are read until a match occurs * with this tag. Normally, the tag is a section marker, such as * "[user-interface]". Best to assume an exact match is needed. * * \param position * Indicates the position to seek to, which defaults to 0 * (std::iso::beg). A non-default value is useful to speed up parsing in * cases where sections are always ordered. * * \param strip * If true (the default), trims white space and strips out hash-tag * comments, but only in lines after the tag is found. * * \return * Returns true if the tag was found. Otherwise, false is returned. */ bool configfile::line_after ( std::ifstream & file, const std::string & tag, int position, bool strip ) { bool result = false; file.clear(); /* clear the file flags */ file.seekg(std::streampos(position), std::ios::beg); /* seek to spot */ m_line_number = 0; /* back to beginning */ bool ok = get_line(file, true); /* trims spaces/comments */ while (ok) /* includes the EOF check */ { result = strncompare(m_line, tag); if (result) { break; } else { if (file.bad()) error_message("bad file stream reading config file"); else ok = get_line(file); /* trims the white space */ } } if (result) result = next_data_line(file, strip); /* might preserve space etc */ return result; } /** * Like line_after, finds a tag, but merely marks the position preceding the * tag. The idea is to find a number of tags that might be ordered by number. * Also useful when changes are made to tag names, to detect legacy names for * section tags. * * \param file * Points to the input file stream. * * \param tag * Provides a tag to be found, which, for this function, is usually a * partial tag, such as "[Drum". Spaces are signficant! * * \return * Returns the position of the line before the tag, converted to an * integer. If not found, -1 is returned. */ int configfile::find_tag (std::ifstream & file, const std::string & tag) { int result = (-1); file.clear(); /* clear the file flags */ file.seekg(0, std::ios::beg); /* seek to the beginning */ m_line_number = 0; /* back to beginning */ bool ok = get_line(file, true); /* trims spaces/comments */ while (ok) /* includes the EOF check */ { bool match = strncompare(m_line, tag); if (match) { result = line_position(); /* int(m_line_pos) */ break; } else { if (file.bad()) error_message("bad file stream reading config file"); else ok = get_line(file); /* trims the white space */ } } return result; } /** * Extracts an integer value from a tag like the following. For this entry, * the tag to use is "[Drum". * * \verbatim * [Drum 33] * \endverbatim */ int configfile::get_tag_value (const std::string & tag) { int result = (-1); auto pos = tag.find_first_of("0123456789"); if (pos != std::string::npos) { std::string buff = tag.substr(pos); /* "35]" */ result = string_to_int(buff); } else { std::string msg = tag; msg += " tag has no integer value"; error_message(tag); } return result; } void configfile::write_date (std::ofstream & file, const std::string & tag) { file << "# Seq66 " << seq_version() << " " << tag << " configuration file\n" << "#\n# " << name() << "\n" << "# Written " << get_current_date_time() << "\n" << "#\n" ; } /** * Sets the error message, which can later be displayed to the user. * Actually, it now appends the error message, so all can be displayed in the * user-interface. We also avoid annoying duplicates. * * \param msg * Provides the error message to be set. */ void configfile::append_error_message (const std::string & msg) { if (msg.empty()) { sm_error_message.clear(); sm_is_error = false; } else { sm_is_error = true; if (msg != sm_error_message) /* avoid duplicate if possible */ { if (! sm_error_message.empty()) sm_error_message += "\n"; /* converts to
in msg box */ sm_error_message += msg; } } } bool configfile::set_up_ifstream (std::ifstream & instream) { bool result = instream.is_open(); if (result) { instream.seekg(0, std::ios::beg); /* seek to start */ std::string s = get_variable(instream, "[Seq66]", "version"); if (s.empty()) { char temp[128]; snprintf ( temp, sizeof temp, "Version not found: %s\n", name().c_str() ); result = make_error_message(file_extension(), temp); } else { /* * This test is kind of iffy, so disabled for now. * * int version = string_to_int(s); * if (version != rc_ref().ordinal_version()) * { * warnprint("'rc' file version changed!"); * } */ } } else { char temp[128]; snprintf(temp, sizeof temp, "Read open fail: %s\n", name().c_str()); result = make_error_message(file_extension(), temp); } return result; } /* * Free functions. */ #if ! defined SEQ66_KEEP_RC_FILE_LIST bool delete_configuration (const std::string & path, const std::string & basename) { bool result = ! path.empty() && ! basename.empty(); if (result) { std::string base = filename_base(basename, true); /* strip .ext */ std::string msg = "Deleting " + base + " from"; file_message(msg, path); for (const auto & ext : configfile::sm_file_extensions) { std::string fname = filename_concatenate(path, base); fname = file_extension_set(fname, ext); if (file_exists(fname)) (void) file_delete(fname); } } return result; } bool copy_configuration ( const std::string & source, /* path */ const std::string & basename, const std::string & destination /* path */ ) { bool result = ! source.empty() && ! basename.empty() && ! destination.empty(); if (result) { std::string base = filename_base(basename, true); /* strip .ext */ std::string sourcename = filename_concatenate(source, base); std::string destinationname = filename_concatenate(destination, base); std::string msg = "Copying " + source + base + " to"; file_message(msg, destination); for (const auto & ext : configfile::sm_file_extensions) { std::string srcname = file_extension_set(sourcename, ext); if (file_exists(srcname)) { std::string destname = file_extension_set(destinationname, ext); bool ok = file_copy(srcname, destname); if (! ok) { result = false; break; } } } } return result; } #endif // ! defined SEQ66_KEEP_RC_FILE_LIST std::string get_current_date_time () { return current_date_time(); } } // namespace seq66 /* * configfile.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/midicontrolfile.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midicontrolfile.cpp * * This module declares/defines the base class for managing the reading and * writing of the midi-control sections of the 'rc' file. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-13 * \updates 2025-05-03 * \license GNU GPLv2 or above * * This class handles the 'ctrl' file. */ #include /* std::cout */ #include /* std::setw() */ #include "cfg/midicontrolfile.hpp" /* seq66::midicontrolfile class */ #include "cfg/settings.hpp" /* seq66::rc() */ #include "ctrl/keymap.hpp" /* seq66::qt_keyname_ordinal() */ #include "play/setmaster.hpp" /* seq66::setmaster::Size() static */ #include "util/filefunctions.hpp" /* seq66::file_exists() */ #include "util/strfunctions.hpp" /* seq66::strip_quotes() */ namespace seq66 { /* * Provides the number of the latest official version of this file. * * Version 4: Baseline for this configuration file. * Version 5: Adds 8 more potential midi-control-out entries. * Version 6: Adds a large number of new automation controls. Version * 5 files will be renamed (for backup) and replaced. */ static const int s_ctrl_file_version = 6; /* * ------------------------------------------------------------------------- * key: midicontrolfile nested class * ------------------------------------------------------------------------- */ /** * Constructs the key value from the given midicontrol. */ midicontrolfile::key::key (const midicontrol & mc) : m_category (mc.category_code()), m_slot_control (mc.slot_control()) { // No code needed } /** * The less-than operator for this key class. */ bool midicontrolfile::key::operator < (const key & rhs) const { if (m_category == rhs.m_category) return m_slot_control < rhs.m_slot_control; else return m_category < rhs.m_category; } /* * ------------------------------------------------------------------------- * stanza: midicontrolfile nested class * ------------------------------------------------------------------------- */ /** * Extracts a control-stanza from a MIDI control object. */ midicontrolfile::stanza::stanza (const midicontrol & mc) : m_category (mc.category_code()), m_key_name (mc.key_name()), m_op_name (mc.label()), m_slot_number (mc.slot_control()), /* slot vs control no. */ m_settings () /* 2-dimensional array */ { set(mc); } bool midicontrolfile::stanza::set (const midicontrol & mc) { automation::action a = mc.action_code(); if (a > automation::action::none && a < automation::action::max) { int index = static_cast(a) - 1; /* skips "none" */ m_settings[index][0] = int(mc.inverse_active()); m_settings[index][1] = int(mc.status()); m_settings[index][2] = int(mc.d0()); /* note: d1 not needed */ m_settings[index][3] = int(mc.min_d1()); m_settings[index][4] = int(mc.max_d1()); } return true; } /* * ------------------------------------------------------------------------- * midicontrolfile * ------------------------------------------------------------------------- */ /** * Static function to create a detailed error message. */ bool midicontrolfile::keycontrol_error_message ( const keycontrol & kc, ctrlkey ordinal, int lineno ) { char temp[256]; snprintf ( temp, sizeof temp, "Error at line %d ordinal 0x%2x key '%s' control '%s' code %d\n", lineno, unsigned(ordinal), kc.key_name().c_str(), kc.name().c_str(), kc.control_code() ); return make_error_message("Key controls", temp); } /** * Principal constructor. * * \param mainfilename * Provides the name of the options file; this is usually a full path * file-specification to the "rc"/"ctrl" file using this object. * * \param rcs * Source/destination for the configuration information. */ midicontrolfile::midicontrolfile ( const std::string & filename, rcsettings & rcs ) : configfile (filename, rcs, ".ctrl"), m_temp_key_controls (), /* reading only */ m_temp_midi_ctrl_in ("ctrl"), /* reading only */ m_stanzas () { version(s_ctrl_file_version); } /** * A rote destructor. */ midicontrolfile::~midicontrolfile () { // ~configfile() called automatically } /** * Parse the ~/.config/seq66/qseq66.ctrl file-stream. * * [comments] * * [comments] Header commentary is skipped during parsing. However, we now * try to read an optional comment block. This block is part of the MIDI * container object, not part of the rcsettings object. * * [midi-control-settings] (was "midi-control-flags") * \verbatim control-buss midi-enabled button-offset button-rows button-columns \endverbatim * * [midi-control] and [midi-control-file] * * Get the number of sequence definitions provided in the following * section. Ranges from 32 on up. Then read in all of the sequence lines. * The first 32 apply to the first screen set. There can also be a comment * line "# mute in group" followed by 32 more lines. Then there are * additional comments and single lines for BPM up, BPM down, Screen Set * Up, Screen Set Down, Mod Replace, Mod Snapshot, Mod Queue, Mod Gmute, * Mod Glearn, and Screen Set Play. These are all forms of MIDI automation * useful to control the playback while not sitting near the computer. * * [loop-control] * [mute-group-control] * [automation-control] * * Provides the stanzas that define the various controls, both keys and * MIDI controls. * * Note that there are no default MIDI controls, but there are default key * controls. See the keys defined in keycontainer::add_defaults(). */ bool midicontrolfile::parse_stream (std::ifstream & file) { bool result = true; file.seekg(0, std::ios::beg); /* seek to the start */ (void) parse_version(file); std::string s = parse_comments(file); if (! s.empty()) m_temp_midi_ctrl_in.comments_block().set(s); std::string mctag = "[midi-control-settings]"; /* the new name for it */ if (bad_position(find_tag(file, mctag))) mctag = "[midi-control-flags]"; /* earlier name for it */ bool flag = get_boolean(file, mctag, "drop-empty-controls"); rc_ref().drop_empty_in_controls(flag); bussbyte buss = get_buss_number(file, false, mctag, "control-buss"); bool enabled = get_boolean(file, mctag, "midi-enabled"); int offset = 0, rows = 0, columns = 0; result = parse_control_sizes(file, mctag, offset, rows, columns); if (result) { if (enabled) enabled = rc_ref().midi_control_active(); if (m_temp_midi_ctrl_in.initialize(buss, rows, columns)) { m_temp_midi_ctrl_in.is_enabled(enabled); m_temp_midi_ctrl_in.configure_enabled(enabled); m_temp_midi_ctrl_in.offset(offset); m_temp_midi_ctrl_in.configured_buss(buss); } } else enabled = false; std::string layout = get_variable(file, mctag, "keyboard-layout"); m_temp_key_controls.clear(); m_temp_key_controls.set_kbd_layout(layout); bool good = line_after(file, "[loop-control]"); int count = 0; /* * Not important enough to cause concern. * * bool keep_empties = ! rc_ref().drop_empty_in_controls(); * if (keep_empties) * status_message("Keeping empty MIDI-In controls"); */ while (good) /* not at end of section? */ { if (! line().empty()) /* any value in section? */ good = parse_control_stanza(automation::category::loop); if (good) { good = next_data_line(file); ++count; } } if (count > 0) { infoprintf("%d loop-control lines", count); } good = line_after(file, "[mute-group-control]"); count = 0; while (good) /* not at end of section? */ { if (! line().empty()) /* any value in section? */ good = parse_control_stanza(automation::category::mute_group); if (good) { good = next_data_line(file); ++count; } } if (count > 0) { infoprintf("%d mute-group-control lines", count); } good = line_after(file, "[automation-control]"); count = 0; while (good) /* not at end of section? */ { if (! line().empty()) /* any value in section? */ good = parse_control_stanza(automation::category::automation); if (good) { good = next_data_line(file); ++count; } } if (count > 0) { infoprintf("%d automation-control lines", count); if (count < current_slot_count()) /* pre-defined */ add_default_automation_stanzas(count); } if (rc_ref().verbose()) { static bool s_not_shown = true; if (s_not_shown) { s_not_shown = false; /* show only once per run */ m_temp_key_controls.show(); } } if (m_temp_midi_ctrl_in.count() > 0) { rc_ref().midi_control_in().clear(); rc_ref().midi_control_in() = m_temp_midi_ctrl_in; rc_ref().midi_control_in().inactive_allowed(true); /* always true */ } if (m_temp_key_controls.count() > 0) { rc_ref().key_controls().clear(); rc_ref().key_controls() = m_temp_key_controls; } (void) parse_midi_control_out(file); return result; } /** * A helper function for parsing the MIDI Control I/O sections. */ bool midicontrolfile::parse_control_sizes ( std::ifstream & file, const std::string & mctag, int & newoffset, int & newrows, int & newcolumns ) { int defaultrows = usr().mainwnd_rows(); int defaultcolumns = usr().mainwnd_cols(); int rows = defaultrows; int columns = defaultcolumns; std::string s = get_variable(file, mctag, "button-offset"); newoffset = string_to_int(s, 0); /* currently constant */ s = get_variable(file, mctag, "button-rows"); rows = string_to_int(s, defaultrows); if (rows <= 0) rows = defaultrows; infoprintf("Setting control rows = %d", rows); newrows = rows; s = get_variable(file, mctag, "button-columns"); columns = string_to_int(s, defaultcolumns); if (columns <= 0) columns = defaultcolumns; infoprintf("Setting control columns = %d", columns); newcolumns = columns; bool result = ( (rows >= screenset::c_min_rows) && (rows <= screenset::c_max_rows) && (columns >= screenset::c_min_columns) && (columns <= screenset::c_max_columns) ); return result; } /** * Gets the number of sequence definitions provided in the midi-control * sections. * * \return * Returns true if the file was able to be opened for reading. * Currently, there is no indication if the parsing actually succeeded. */ bool midicontrolfile::parse () { bool result = true; std::ifstream file(name(), std::ios::in | std::ios::ate); if (! file.is_open()) { file_error("Open failed", name()); result = false; } else { result = parse_stream(file); if (! result) file_error("Read failed", name()); } return result; } /* * Removes the "enabled" flag from each control-input stanza: loops, * mute-groups, and automation. The "inverse" flag remains. * The "enabled" status will be determined by the status value not being * 0x00. We changed '%10s' to '%s' because the '10' causes sscanf() to * return a count of 2 instead of 17. Weird with a beard! */ static const char * const sg_scanf_fmt_ctrl_in_2 = "%d %s [ %d %i %i %i %i ] [ %d %i %i %i %i ] [ %d %i %i %i %i ]"; /* * Removes the "enabled" flag, replaced by testing for status not equal to * 0x00. Also removes the channel value, which becomes part of the status * value. Used for [midi-control-out]. */ static const char * const sg_scanf_fmt_ctrl_out_2 = "%d [ %i %i %i ] [ %i %i %i ] [ %i %i %i ] [ %i %i %i ]"; /* * Adds a third stanze, "del" (unconfigured) value. Used for * [automation-control-out]. */ static const char * const sg_scanf_fmt_triples = "%d [ %i %i %i ] [ %i %i %i ] [ %i %i %i ]"; /* * Removes the channel value. It goes into the status value instead. * Used for [mute-control-out]. */ static const char * const sg_scanf_fmt_mutes_triple = "%d [ %i %i %i ] [ %i %i %i ] [ %i %i %i ]"; /** * It is not an error for the "[midi-contro-out]" section to be missing. */ bool midicontrolfile::parse_midi_control_out (std::ifstream & file) { bool result; std::string mctag = "[midi-control-out-settings]"; std::string s = get_variable(file, mctag, "set-size"); int sequences = string_to_int(s, setmaster::Size()); bussbyte buss = get_buss_number(file, true, mctag, "output-buss"); bool enabled = false; s = get_variable(file, mctag, "midi-enabled"); if (s.empty()) { s = get_variable(file, mctag, "enabled"); /* the old tag name */ enabled = string_to_bool(s); } else enabled = string_to_bool(s); /* * We need to read them anyway, for saving back at exit. The enabled-flag * will determine if they are used. */ int offset = 0, rows = 0, columns = 0; result = parse_control_sizes(file, mctag, offset, rows, columns); if (result) { if (enabled) enabled = rc_ref().midi_control_active(); } else enabled = false; if (line_after(file, "[midi-control-out]")) { /* * Set up the default-constructed midicontrolout object with its buss, * setsize, and enabled values. Then read in the control-out data. * The performer sets the masterbus later on. */ midicontrolout & mco = rc_ref().midi_control_out(); if (mco.initialize(buss, rows, columns)) { mco.is_enabled(enabled); mco.configure_enabled(enabled); mco.offset(offset); mco.configured_buss(buss); } if (file_version_number() < 2) { result = version_error_message("ctrl", file_version_number()); rc_ref().auto_ctrl_save(true); } else { for (int i = 0; i < sequences; ++i) { int a[4], b[4], c[4], d[4]; int sequence = 0; (void) std::sscanf ( scanline(), sg_scanf_fmt_ctrl_out_2, &sequence, &a[0], &a[1], &a[2], &b[0], &b[1], &b[2], &c[0], &c[1], &c[2], &d[0], &d[1], &d[2] ); /* * Offset to avoid the deprecated enabled and channel values. */ mco.set_seq_event(i, midicontrolout::seqaction::armed, a); mco.set_seq_event(i, midicontrolout::seqaction::muted, b); mco.set_seq_event(i, midicontrolout::seqaction::queued, c); mco.set_seq_event(i, midicontrolout::seqaction::removed, d); if (i < (sequences - 1) && ! next_data_line(file)) { make_error_message("midi-control-out", "insufficient data"); break; } } } /* * This code (now permanent) adds two section markers and one section * for mutes, similar to the ctrl-pair options that follow this * clause. */ bool ok = true; if (line_after(file, "[mute-control-out]")) { int M = mutegroups::Size(); for (int m = 0; m < M; ++m) { ok = read_mutes_triple(file, mco, m) || (m == (M - 1)); if (! ok) break; /* currently not an error */ } } if (ok) ok = line_after(file, "[automation-control-out]"); /* Non-sequence (automation) actions */ bool newtriples = file_version_number() > 3; if (ok && newtriples) { ok = read_triples(file, mco, midicontrolout::uiaction::panic); if (ok) { read_triples(file, mco, midicontrolout::uiaction::stop); read_triples(file, mco, midicontrolout::uiaction::pause); read_triples(file, mco, midicontrolout::uiaction::play); read_triples(file, mco, midicontrolout::uiaction::toggle_mutes); read_triples(file, mco, midicontrolout::uiaction::song_record); read_triples(file, mco, midicontrolout::uiaction::slot_shift); read_triples(file, mco, midicontrolout::uiaction::free); read_triples(file, mco, midicontrolout::uiaction::queue); read_triples(file, mco, midicontrolout::uiaction::oneshot); read_triples(file, mco, midicontrolout::uiaction::replace); read_triples(file, mco, midicontrolout::uiaction::snapshot); read_triples(file, mco, midicontrolout::uiaction::song_mode); read_triples(file, mco, midicontrolout::uiaction::learn); read_triples(file, mco, midicontrolout::uiaction::bpm_up); read_triples(file, mco, midicontrolout::uiaction::bpm_dn); read_triples(file, mco, midicontrolout::uiaction::list_up); read_triples(file, mco, midicontrolout::uiaction::list_dn); read_triples(file, mco, midicontrolout::uiaction::song_up); read_triples(file, mco, midicontrolout::uiaction::song_dn); read_triples(file, mco, midicontrolout::uiaction::set_up); read_triples(file, mco, midicontrolout::uiaction::set_dn); read_triples(file, mco, midicontrolout::uiaction::tap_bpm); read_triples(file, mco, midicontrolout::uiaction::quit); read_triples(file, mco, midicontrolout::uiaction::visibility); read_triples(file, mco, midicontrolout::uiaction::alt_2); read_triples(file, mco, midicontrolout::uiaction::alt_3); read_triples(file, mco, midicontrolout::uiaction::alt_4); read_triples(file, mco, midicontrolout::uiaction::alt_5); read_triples(file, mco, midicontrolout::uiaction::alt_6); read_triples(file, mco, midicontrolout::uiaction::alt_7); read_triples(file, mco, midicontrolout::uiaction::alt_8); } if (ok) { const std::string tag{"[macro-control-out]"}; ok = line_after(file, tag); if (ok) { int count = 0; mco.clear_macros(); /* clear it for each pass */ while (ok) { tokenization t = tokenize(line(), "="); ok = mco.add_macro(t); if (ok) { ++count; ok = next_data_line(file); } } ok = count > 0; if (ok) { ok = mco.expand_macros(); (void) info_message(mco.macro_byte_strings()); } } else ok = mco.make_macro_defaults(); if (ok) ok = rc_ref().midi_control_active(); mco.macros_active(ok); } else make_error_message("midi-control-out", "read-triple error"); } else { result = version_error_message("ctrl", file_version_number()); rc_ref().auto_ctrl_save(true); } if (result) result = ok && ! is_error(); } else result = false; if (! result) rc_ref().midi_control_out().is_enabled(false); /* blank section */ return result; } /** * Reads the first digit, which is the "enabled" bit, plus a pair of stanzas * with four values in this order: channel, status, d1, and d2. * * This function assumes we have already got the line to read, and it gets * the next data line at the end. */ bool midicontrolfile::read_triples ( std::ifstream & file, midicontrolout & mco, midicontrolout::uiaction a ) { int enabled, ev_on[4], ev_off[4], ev_del[4]; if (file_version_number() < 2) { rc_ref().auto_ctrl_save(true); return version_error_message("ctrl", file_version_number()); } else { int count = std::sscanf ( scanline(), sg_scanf_fmt_triples, &enabled, &ev_on[0], &ev_on[1], &ev_on[2], &ev_off[0], &ev_off[1], &ev_off[2], &ev_del[0], &ev_del[1], &ev_del[2] ); if (count < 10) ev_del[0] = ev_del[1] = ev_del[2] = ev_del[3] = 0; if (count < 7) ev_off[0] = ev_off[1] = ev_off[2] = ev_off[3] = 0; mco.set_event(a, enabled, ev_on, ev_off, ev_del); } return next_data_line(file); } bool midicontrolfile::read_mutes_triple ( std::ifstream & file, midicontrolout & mco, int group ) { if (file_version_number() < 2) { rc_ref().auto_ctrl_save(true); return version_error_message("ctrl", file_version_number()); } else { int number, ev_on[4], ev_off[4], ev_del[4]; (void) std::sscanf ( scanline(), sg_scanf_fmt_mutes_triple, &number, &ev_on[0], &ev_on[1], &ev_on[2], &ev_off[0], &ev_off[1], &ev_off[2], &ev_del[0], &ev_del[1], &ev_del[2] ); mco.set_mutes_event(group, ev_on, ev_off, ev_del); } return next_data_line(file); } bool midicontrolfile::write_stream (std::ofstream & file) { write_date(file, "MIDI control"); file << "# Sets up MIDI I/O control. The format is like the 'rc' file. To use it, set it\n" "# active in the 'rc' [midi-control-file] section. It adds loop, mute, &\n" "# automation buttons, MIDI display, new settings, and macros.\n" ; write_seq66_header(file, "ctrl", version()); /* * [comments] */ std::string s = rc_ref().midi_control_in().comments_block().text(); write_comment(file, s); bool result = write_midi_control(file); if (result) result = write_midi_control_out(file); if (result) write_seq66_footer(file); return result; } /** * This options-writing function is just about as complex as the * options-reading function. * * \return * Returns true if the write operations all succeeded. */ bool midicontrolfile::write () { std::ofstream file(name(), std::ios::out | std::ios::trunc); bool result = file.is_open(); if (result) { result = container_to_stanzas(rc_ref().midi_control_in()); if (result) { file_message("Write ctrl", name()); result = write_stream(file); if (! result) file_error("Write fail", name()); } file.close(); } else { file_error("Write open fail", name()); } return result; } /** * Writes these sections to the given file stream: * * - [midi-control-settings] * - [loop-control] * - [mute-group-control] * - [automation-control] * * \param file * Provides the output file stream to write to. * * \return * Returns true if the write operations all succeeded. */ bool midicontrolfile::write_midi_control (std::ofstream & file) { bool result = file.is_open(); if (result) { const midicontrolin & mci = rc_ref().midi_control_in(); bussbyte bb = mci.configured_buss(); file << "\n[midi-control-settings]\n\n" "# Input settings to control Seq66. 'control-buss' ranges from 0 to the highest\n" "# system input buss. If set, that buss can send MIDI control. 255 (0xFF) means\n" "# any ENABLED MIDI input can send control. ALSA has an extra 'announce' buss,\n" "# so add 1 to the port number with ALSA. With port-mapping enabled, the port\n" "# nick-name can be provided.\n" "#\n" "# 'midi-enabled' applies to the MIDI controls; keystroke controls are always\n" "# enabled. Supported keyboard layouts are 'qwerty' (default), 'qwertz', and\n" "# 'azerty'. AZERTY turns off auto-shift for group-learn.\n\n" ; write_boolean ( file, "drop-empty-controls", rc_ref().drop_empty_in_controls() ); write_buss_info(file, false, "control-buss", bb); int defaultrows = mci.rows(); int defaultcolumns = mci.columns(); if (defaultrows == 0) defaultrows = usr().mainwnd_rows(); if (defaultcolumns == 0) defaultcolumns = usr().mainwnd_cols(); write_boolean(file, "midi-enabled", mci.configure_enabled()); write_integer(file, "button-offset", mci.offset()); write_integer(file, "button-rows", defaultrows); write_integer(file, "button-columns", defaultcolumns); write_string ( file, "keyboard-layout", rc_ref().key_controls().kbd_layout_to_string() ); file << "\n" "# A control stanza sets key and MIDI control. Keys support 'toggle', and\n" "# key-release is 'invert'. The leftmost number on each line is the loop number\n" "# (0 to 31), mutes number (same range), or an automation number. 3 groups of\n" "# of bracketed numbers follow, each providing a type of control:\n" "#\n" "# Normal: [toggle] [on] [off]\n" "# Increment/Decr: [increment] [increment] [decrement]\n" "# Playback: [pause] [start] [stop]\n" "# Playlist/Song: [by-value] [next] [previous]\n" "#\n" "# In each group, there are 5 numbers:\n" "#\n" "# [invert status d0 d1min d1max]\n" ; file << "#\n" "# A valid status (> 0x00) enables the control; 'invert' (1/0) inverts the,\n" "# the action, but not all support this. 'status' is the MIDI event to match\n" "# (channel is NOT ignored); 'd0' is the status value (eg. if 0x90, Note On,\n" "# d0 is the note number; d1min to d1max is the range of d1 values detectable.\n" "# Hex values can be used; precede with '0x'.\n" "#\n" "# ------------------------ Loop/group/automation-slot number\n" "# | -------------------- Name of key (see the key map)\n" "# | | -------------- Inverse\n" "# | | | ---------- MIDI status/event byte (eg. Note On)\n" "# | | | | ------- d0: Data 1 (eg. Note number)\n" "# | | | | | ----- d1max: Data 2 min (eg. Note velocity)\n" "# | | | | | | -- d1min: Data 2 max\n" "# | | | | | | |\n" "# v v v v v v v\n" "# 0 \"F1\" [0 0x90 0 1 127] [0 0x00 0 0 0] [0 0x00 0 0 0]\n" "# Toggle On Off\n" "#\n" "# MIDI controls often send a Note On upon a press and a Note Off on release.\n" "# To use a control as a toggle, define only the Toggle stanza. For the control\n" "# to act only while held, define the On and Off stanzas with appropriate\n" "# statuses for press-and-release.\n" "#\n" "# Warning: the 'BS' key is actually the Ctrl-H key, and NOT the Backspace key.\n" "# The Backspace key is called 'BkSpace' in the Seq66 key-map.\n" ; /* * Write out all of the 3-part stanzas, each in their own category * section. This sequence depends on the stanzas being sorted by * category. */ automation::category opcat = automation::category::none; for (const auto & stz : m_stanzas) { const midicontrolfile::stanza & stan = stz.second; automation::category currcat = stan.category_code(); if (currcat != opcat) { opcat = currcat; if (currcat == automation::category::loop) file << "\n[loop-control]\n\n"; else if (currcat == automation::category::mute_group) file << "\n[mute-group-control]\n\n"; else if (currcat == automation::category::automation) file << "\n[automation-control]\n\n"; } int spacing = 12 - int(stan.key_name().size()); file << std::setw(2) << stan.slot_number() << " \"" << stan.key_name() << "\"" << std::setw(spacing) << " " ; for (int action = 0; action < automation::ACTCOUNT; ++action) { file << "[" << std::setw(2) << stan.setting(action, 0) /* inverse */ << " 0x" << std::setw(2) << std::setfill('0') << std::hex << stan.setting(action, 1) /* status */ << std::setw(4) << std::setfill(' ') << std::dec << stan.setting(action, 2) /* d0 */ << std::setw(4) << std::dec << stan.setting(action, 3) /* min */ << std::setw(4) << std::dec << stan.setting(action, 4) /* max */ << " ] " ; } file << "# " << stan.op_name() << std::endl; } } return result; } /** * Writes a MIDI user-interface-related data stanza of the form * "1 [ 0 0x00 0 ] [ 0 0x00 0 ] [ 0 0x00 0 ]". Here, action_del is * used for the "unconfigured" (del) status. */ bool midicontrolfile::write_triples ( std::ofstream & file, const midicontrolout & mco, midicontrolout::uiaction a ) { bool active = mco.event_is_active(a); std::string act1str = mco.get_ctrl_event_str ( a, midicontrolout::action_on ); std::string act2str = mco.get_ctrl_event_str ( a, midicontrolout::action_off ); std::string act3str = mco.get_ctrl_event_str ( a, midicontrolout::action_del ); file << (active ? 1 : 0) << " " << act1str << " " << act2str << " " << act3str << " # " << action_to_string(a) << "\n" ; return file.good(); } bool midicontrolfile::write_mutes_triple ( std::ofstream & file, const midicontrolout & mco, int group ) { /* * Always active; group number written instead. * * bool active = mco.mutes_event_is_active(group); */ std::string act1str = mco.get_mutes_event_str ( group, midicontrolout::action_on ); std::string act2str = mco.get_mutes_event_str ( group, midicontrolout::action_off ); std::string act3str = mco.get_mutes_event_str ( group, midicontrolout::action_del ); file << std::setw(2) << std::dec << group << " " << act1str << " " << act2str << " " << act3str << "\n" ; return file.good(); } /** * Writes out the MIDI control data for the patterns and for the * user-interface actions. */ bool midicontrolfile::write_midi_control_out (std::ofstream & file) { /* const */ midicontrolout & mco = rc_ref().midi_control_out(); int setsize = mco.screenset_size(); bussbyte bb = mco.configured_buss(); bool result = is_valid_buss(bb); /* very light sanity check */ if (result) { if (setsize == 0) { mco.initialize ( bb, screenset::c_default_rows, screenset::c_default_columns ); setsize = mco.screenset_size(); } file << "\n[midi-control-out-settings]\n\n"; write_integer(file, "set-size", setsize); write_buss_info(file, true, "output-buss", bb); write_boolean(file, "midi-enabled", mco.configure_enabled()); write_integer(file, "button-offset", mco.offset()); write_integer(file, "button-rows", mco.rows()); write_integer(file, "button-columns", mco.columns()); file << "\n" "[midi-control-out]\n" "\n" "# This section determines how pattern statuses are to be displayed.\n" "\n" "# ---------------- Pattern or device-button number)\n" "# | ----------- MIDI status+channel (eg. Note On)\n" "# | | ------- data 1 (eg. note number)\n" "# | | | ----- data 2 (eg. velocity)\n" "# | | | |\n" "# v v v v\n" "# 31 [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0 ] [ 0x00 0 0]\n" "# Armed Muted (Un)queued Empty/Deleted\n" "#\n" "# A test of the status byte determines the enabled status, and channel is\n" "# included in the status.\n" "\n" ; /* heh heh */ if (mco.is_blank()) { for (int seq = 0; seq < setsize; ++seq) { file << std::setw(2) << seq << " [ 0x00 0 0 ]" " [ 0x00 0 0 ]" " [ 0x00 0 0 ]" " [ 0x00 0 0 ]\n" ; } } else { for (int seq = 0; seq < setsize; ++seq) { int minimum, maximum; midicontrolout::seqaction_range(minimum, maximum); file << std::setw(2) << std::dec << seq; for (int a = minimum; a < maximum; ++a) { event ev = mco.get_seq_event ( seq, midicontrolout::seqaction(a) ); midibyte d0, d1; char temp[48]; ev.get_data(d0, d1); (void) snprintf /* much easier format! */ ( temp, sizeof temp, " [ 0x%02x %3d %3d ]", unsigned(ev.get_status()), int(d0), int(d1) ); file << temp; } file << "\n"; } } file << "\n[mute-control-out]\n\n" "# The format of the mute and automation output events is similar:\n" "#\n" "# ----------------- mute-group number\n" "# | ------------- MIDI status+channel (eg. Note On)\n" "# | | --------- data 1 (eg. note number)\n" "# | | | ------- data 2 (eg. velocity)\n" "# | | | |\n" "# v v v v\n" "# 1 [0x00 0 0 ] [0x00 0 0] [0x00 0 0]\n" "# On Off Empty (dark)\n" "#\n" "# The mute-controls have an additional stanza for non-populated (\"deleted\")\n" "# mute-groups.\n" "\n" ; for (int m = 0; m < mutegroups::Size(); ++m) { if (! write_mutes_triple(file, mco, m)) break; } file << "\n[automation-control-out]\n\n" "# This format is similar to [mute-control-out], but the first number is an\n" "# active-flag, not an index number. The stanzas are are on / off / inactive,\n" "# except for 'snap', which is store / restore / inactive.\n\n" ; write_triples(file, mco, midicontrolout::uiaction::panic); write_triples(file, mco, midicontrolout::uiaction::stop); write_triples(file, mco, midicontrolout::uiaction::pause); write_triples(file, mco, midicontrolout::uiaction::play); write_triples(file, mco, midicontrolout::uiaction::toggle_mutes); write_triples(file, mco, midicontrolout::uiaction::song_record); write_triples(file, mco, midicontrolout::uiaction::slot_shift); write_triples(file, mco, midicontrolout::uiaction::free); write_triples(file, mco, midicontrolout::uiaction::queue); write_triples(file, mco, midicontrolout::uiaction::oneshot); write_triples(file, mco, midicontrolout::uiaction::replace); write_triples(file, mco, midicontrolout::uiaction::snapshot); write_triples(file, mco, midicontrolout::uiaction::song_mode); write_triples(file, mco, midicontrolout::uiaction::learn); write_triples(file, mco, midicontrolout::uiaction::bpm_up); write_triples(file, mco, midicontrolout::uiaction::bpm_dn); write_triples(file, mco, midicontrolout::uiaction::list_up); write_triples(file, mco, midicontrolout::uiaction::list_dn); write_triples(file, mco, midicontrolout::uiaction::song_up); write_triples(file, mco, midicontrolout::uiaction::song_dn); write_triples(file, mco, midicontrolout::uiaction::set_up); write_triples(file, mco, midicontrolout::uiaction::set_dn); write_triples(file, mco, midicontrolout::uiaction::tap_bpm); write_triples(file, mco, midicontrolout::uiaction::quit); write_triples(file, mco, midicontrolout::uiaction::visibility); write_triples(file, mco, midicontrolout::uiaction::alt_2); write_triples(file, mco, midicontrolout::uiaction::alt_3); write_triples(file, mco, midicontrolout::uiaction::alt_4); write_triples(file, mco, midicontrolout::uiaction::alt_5); write_triples(file, mco, midicontrolout::uiaction::alt_6); write_triples(file, mco, midicontrolout::uiaction::alt_7); write_triples(file, mco, midicontrolout::uiaction::alt_8); /* * Write any macros that exist. */ file << "\n[macro-control-out]\n\n" "# This format is 'macroname = [ hex bytes | macro-references]'. Macro references\n" "# are macro-names preceded by a '$'. Some values should always be defined, even\n" "# if empty: footer, header, reset, startup, and shutdown.\n\n" ; std::string lines = mco.macro_lines(); if (lines.empty()) { file << "footer =\n" "header =\n" "reset =\n" "startup =\n" "shutdown =\n" ; } else file << lines << std::endl; } return result; } /** * Parses the [midi-control] section. This function is used both in the * original reading of the "rc" file, and for reloading the original * midi-control data from the "rc". * * We used to throw the midi-control count value away, since it was always * 1024, but it is useful if no mute groups have been created. So, if it * reads 0 (instead of 1024), we will assume there are no midi-control * settings. We also have to be sure to go to the next data line even if the * strip-empty-mutes option is on. * * \return * Returns true if the file was able to be opened for reading, and the * desired data successfully extracted. */ bool read_midi_control_file ( const std::string & fname, rcsettings & rcs ) { midicontrolfile mcf(fname, rcs); return mcf.parse(); } /** * For automation, slot and code are the same numeric value. We've * added code so that blank stanzas can be automatically added to * older configuration files. */ bool midicontrolfile::parse_control_stanza (automation::category opcat, int index) { bool result = true; automation::slot opslot = automation::slot::none; int count = 0; bool keep_empties = ! rc_ref().drop_empty_in_controls(); std::string keyname; /* * Was deprecated, now no longer supported. */ if (file_version_number() < 2) { result = version_error_message("ctrl", file_version_number()); rc_ref().auto_ctrl_save(true); } else { int a[8] { }; int b[8] { }; int c[8] { }; bool ok; if (index == 0) { char charname[16]; count = std::sscanf ( scanline(), sg_scanf_fmt_ctrl_in_2, &index, &charname[0], &a[0], &a[1], &a[2], &a[3], &a[4], &b[0], &b[1], &b[2], &b[3], &b[4], &c[0], &c[1], &c[2], &c[3], &c[4] ); keyname = strip_quotes(std::string(charname)); ok = count == 17; } else { keyname = keycontainer::automation_default_key_name(index); ok = ! keyname.empty(); } if (ok) { opslot = automation::slot::none; if (opcat == automation::category::loop) opslot = automation::slot::loop; else if (opcat == automation::category::mute_group) opslot = automation::slot::mute_group; else if (opcat == automation::category::automation) opslot = opcontrol::set_slot(index); #if defined SEQ66_PLATFORM_DEBUG_TMI if (opcat == automation::category::automation) { printf("Key %d = '%s'\n", index, keyname.c_str()); } #endif /* * Create control objects, whether active or not. We want to * save all objects in the file, to avoid altering the user's * preferences. */ midicontrol mca ( keyname, opcat, automation::action::toggle, opslot, index ); if (mca.set(a) || keep_empties) (void) m_temp_midi_ctrl_in.add(mca); midicontrol mcb ( keyname, opcat, automation::action::on, opslot, index ); if (mcb.set(b) || keep_empties) (void) m_temp_midi_ctrl_in.add(mcb); midicontrol mcc ( keyname, opcat, automation::action::off, opslot, index ); if (mcc.set(c) || keep_empties) (void) m_temp_midi_ctrl_in.add(mcc); } else result = false; } if (result) { /* * Make reverse-lookup map for the show_ui * functions. It would be an addition to the keycontainer class. * keyname = key_controls().key_name(slotnumber); */ keycontrol kc ( "", keyname, opcat, automation::action::toggle, opslot, index ); ctrlkey ordinal = qt_keyname_ordinal(keyname); if (! is_invalid_ordinal(ordinal)) { bool ok = m_temp_key_controls.add(ordinal, kc); if (ok) { /* * We now add automation codes for issue #114. */ if (opcat == automation::category::loop) ok = m_temp_key_controls.add_slot(kc); else if (opcat == automation::category::mute_group) ok = m_temp_key_controls.add_mute(kc); else if (opcat == automation::category::automation) ok = m_temp_key_controls.add_automation(kc); } if (! ok) (void) keycontrol_error_message(kc, ordinal, line_number()); } } else { errprintf("unexpected control count %d in stanza", count); result = false; } return result; } /** * Adds to key-controls and midi-control-in controls in cases where the 'ctrl' * file pre-dates the significant increase in automation slots. */ bool midicontrolfile::add_default_automation_stanzas (int starting_index) { bool result = true; int ending_index = current_slot_count(); for (int i = starting_index; i < ending_index; ++i) { bool ok = parse_control_stanza(automation::category::automation, i); if (! ok) { result = false; break; } } return result; } /** * The counterpart is write_buss_info(). This function depends on the 'rc' * file being read first, in case port-mapping is specified. */ bussbyte midicontrolfile::get_buss_number ( std::ifstream & file, bool isoutputport, const std::string & tag, const std::string & varname ) { const int defalt = (-1); int result = defalt; std::string s = get_variable(file, tag, varname); if (! s.empty()) { result = string_to_int(s, defalt); if (result == defalt) /* could not convert as integer */ { if (isoutputport) { clockslist & opm = output_port_map(); if (opm.active()) { bussbyte b = opm.bus_from_name(s); /* 0 to FF */ result = int(b); msgprintf ( msglevel::status, "Output buss '%s' port %d", s.c_str(), result ); } else result = int(default_control_out_buss()); } else { inputslist & ipm = input_port_map(); if (ipm.active()) { bussbyte b = ipm.bus_from_name(s); /* 0 to FF */ result = int(b); msgprintf ( msglevel::status, "Input buss '%s' port %d", s.c_str(), result ); } else result = int(default_control_in_buss()); } } } return result; } void midicontrolfile::write_buss_info ( std::ofstream & file, bool isoutputport, const std::string & varname, bussbyte nb /* nominalbuss */ ) { bool active = false; std::string buss_string; if (isoutputport) { clockslist & opm = output_port_map(); active = opm.active(); if (active) buss_string = opm.port_name_from_bus(nb); } else { inputslist & ipm = input_port_map(); active = ipm.active(); if (active) buss_string = ipm.port_name_from_bus(nb); } if (active) { buss_string = add_quotes(buss_string); write_string(file, varname, buss_string); } else { write_integer(file, varname, int(nb), is_null_buss(nb)); } } /** * Note that midicontrolin is a multimap, and it can hold multiple * midicontrols for a given midicontrol::key, so that the same event can * trigger multiple operations/actions. */ bool midicontrolfile::container_to_stanzas (const midicontrolin & mc) { int current_count = mc.count(); bool result = current_count > 0; if (result) { m_stanzas.clear(); /* new ca 2021-12-03 */ for (const auto & m : mc.container()) { const midicontrol & mco = m.second; key k(mco); auto stanziter = m_stanzas.find(k); bool ok; if (stanziter != m_stanzas.end()) { /* * Here, the stanza is already in place, but we need to * update it with the right action settings. This normally * occurs when all three sub-stanzas have the same values * (which rationally happens when the MIDI control event is * not configured (all zeroes). */ stanziter->second.set(mco); ok = true; /* points to the found one */ } else { stanza s(mco); /* includes settings sect. */ auto sz = m_stanzas.size(); auto p = std::make_pair(k, s); (void) m_stanzas.insert(p); ok = m_stanzas.size() == (sz + 1); } if (! ok) { errprint("couldn't update midicontrol:"); mco.show(true); result = false; break; } } #if defined SEQ66_PLATFORM_DEBUG_TMI int origslotcount = original_slot_count(); int currslotcount = current_slot_count(); printf ( "mc.count() = %d, orig slots = %d, curr slots = %d\n", current_count, origslotcount, currslotcount ); #endif } return result; } void midicontrolfile::show_stanza (const stanza & stan) const { std::cout << "[" << stan.category_name() << "-control] " << "'" << std::setw(7) << stan.key_name() << "'" << " " << std::setw(2) << stan.slot_number() << " " ; for (int action = 0; action < automation::ACTCOUNT; ++action) { std::cout << "[" << std::setw(2) << stan.setting(action, 0) /* active */ << std::setw(2) << stan.setting(action, 1) /* inverse active */ << " 0x" << std::setw(2) << std::setfill('0') << std::hex << stan.setting(action, 2) /* status */ << std::setw(4) << std::setfill(' ') << std::dec << stan.setting(action, 3) /* d0 */ << std::setw(4) << std::dec << stan.setting(action, 4) /* min */ << std::setw(4) << std::dec << stan.setting(action, 5) /* max */ << " ] "; } std::cout << stan.op_name() << std::endl; } void midicontrolfile::show_stanzas () const { std::cout << "Number of stanzas = " << m_stanzas.size() << std::endl; if (m_stanzas.size() > 0) { for (const auto & stz : m_stanzas) show_stanza(stz.second); } } /** * A free function to write the MIDI control file. It handles these cases: * * - Recreating the file if the controls are active or if the file does * not exist. * - If controls exist: * - If less than the maximum number (automation::slot::max), then * pad the container to the max (the keycontainer always * defaults to full size, so it can be used as a reference). * - If full-sized already, just write the file. * - Otherwise, use the key-controls to populate blank MIDI controls * to the full-size of the automation slots. */ bool write_midi_control_file ( const std::string & mcfname, rcsettings & rcs ) { bool exists = file_exists(mcfname); bool active = rcs.midi_control_active(); bool recreate = active || ! exists; const midicontrolin & ctrls = rcs.midi_control_in(); bool result = ctrls.count() > 0; if (result) { if (recreate) { midicontrolfile mcf(mcfname, rcs); if (result) result = mcf.write(); } } else { if (recreate) { keycontainer & keys = rcs.key_controls(); midicontrolin & ctrls = rcs.midi_control_in(); midicontrolfile mcf(mcfname, rcs); ctrls.add_blank_controls(keys); result = mcf.write(); } } if (! result) file_error("Write ctrl failed", mcfname); return result; } } // namespace seq66 /* * midicontrolfile.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/mutegroupsfile.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file mutegroupsfile.cpp * * This module declares/defines the base class for managing the reading and * writing of the mute-group sections of the "rc" file. * * \library seq66 application * \author Seq24 team; modifications by Chris Ahlstrom * \date 2018-11-13 * \updates 2023-12-06 * \license GNU GPLv2 or above * */ #include /* std::ofstream and ifstream */ #include /* std::setw() */ #include /* std::cout */ #include "cfg/mutegroupsfile.hpp" /* seq66::mutegroupsfile class */ #include "cfg/settings.hpp" /* seq66::rc(), as rc_ref() */ #include "play/mutegroups.hpp" /* seq66::mutegroups, etc. */ #include "util/strfunctions.hpp" /* seq66::write_stanza_bits() */ namespace seq66 { /** * Provides an internal-only mutegroups object that can hold the mute-groups * defined in the file to be read/written for safety of the user's data, when * the settings specify storing the run-time mute-groups in the MIDI file. */ static mutegroups & internal_mutegroups () { static mutegroups s_internal_mutes; return s_internal_mutes; } /** * Principal constructor. * * \param filename * Provides the name of the mute-groups file; this is usually a full path * file-specification to the "mutes" file using this object. This item * can be specified in the 'rc' file. * * \param rcs * The source/destination for the configuration information. */ mutegroupsfile::mutegroupsfile ( const std::string & filename, mutegroups & mutes ) : configfile (filename, rc(), ".mutes"), m_mute_groups (mutes) { // Empty body } /** * A rote destructor. */ mutegroupsfile::~mutegroupsfile () { // ~configfile() called automatically } /** * Parse the ~/.config/seq66/seq66.rc file-stream or the * ~/.config/seq66/seq66.mutes file-stream. * * [mute-group] * * The mute-group starts with a line that indicates up to 32 mute-groups * are defined. A common value is 1024, which means there are 32 groups * times 32 keys. But this value is currently thrown away. This value * is followed by 32 lines of data, each contained 4 sets of 8 settings. * See the seq66-doc project on GitHub for a much more detailed * description of this section. */ bool mutegroupsfile::parse_stream (std::ifstream & file) { bool result = true; file.seekg(0, std::ios::beg); /* seek to start */ (void) parse_version(file); std::string s = parse_comments(file); if (! s.empty()) mutes().comments_block().set(s); std::string tag = "[mute-group-flags]"; s = get_variable(file, tag, "load-mute-groups"); bool update_needed = s.empty(); if (update_needed) { mutes().toggle_active_only(false); mutes().group_save("mutes"); mutes().group_load("none"); } else { if (s == "true") s = "mutes"; else if (s == "false") s = "midi"; if (! s.empty()) mutes().group_load(s); s = get_variable(file, tag, "save-mutes-to"); mutes().group_save(s); bool flag = get_boolean(file, tag, "strip-empty"); mutes().strip_empty(flag); #if defined USE_MUTE_GROUP_ROWS_COLUMNS /* * For issue #87, we see that the mute-group size (number of patterns * that can be set by a mute-group) must match the mainwnd_rows() and * mainwnd_columns() setting in usrsettings. These are now passed to * the performer, who then creates the mutegroups object with the * correct set size. So we disable these settings here, and mark the * 'mutes' file for saving if found. */ int value = get_integer(file, tag, "mute-group-rows"); mutes().rows(value); value = get_integer(file, tag, "mute-group-columns"); mutes().columns(value); value = get_integer(file, tag, "mute-group-selected"); #else s = get_variable(file, tag, "mute-group-rows"); if (! s.empty()) rc_ref().auto_mutes_save(true); int value = get_integer(file, tag, "mute-group-selected"); #endif mutes().group_selected(value); s = get_variable(file, tag, "groups-format"); if (! s.empty()) { bool usehex = (s == "hex"); mutes().group_format_hex(usehex); /* otherwise it is binary */ } flag = get_boolean(file, tag, "toggle-active-only"); mutes().toggle_active_only(flag); } bool load = mutes().group_load_from_mutes(); mutegroups & mutestorage = load ? mutes() : internal_mutegroups() ; bool good = line_after(file, "[mute-groups]"); if (! load) internal_mutegroups() = mutes(); (void) mutestorage.reset_defaults(); if (good) { bool ok = true; while (ok) /* not at end of section? */ { if (! line().empty()) /* any value in section? */ { ok = parse_mutes_stanza(mutestorage); if (ok) { ok = next_data_line(file); } else { file_error("Add mute stanza", line()); good = false; break; } } } } if (good) { if (mutestorage.count() <= 1) /* merely a sanity check */ good = false; } /* * Whether we loaded to internal storage or not, we have to make the * visible mutegroups reflect the actual situation. */ if (good) { mutestorage.loaded_from_mutes(load); /* loaded to non-internal? */ } else { (void) mutestorage.reset_defaults(); mutestorage.loaded_from_mutes(false); } return result; } /** * Get the number of sequence definitions provided in the [mute-group] * section. See the rcfile class for full information. * * \param p * Provides the performance object to which all of these options apply. * * \return * Returns true if the file was able to be opened for reading. * Currently, there is no indication if the parsing actually succeeded. */ bool mutegroupsfile::parse () { std::ifstream file(name(), std::ios::in | std::ios::ate); bool result = file.is_open(); if (result) { result = parse_stream(file); } else { file_error("Mutes open failed", name()); result = false; } return result; } /** * Writes the [mute-group] section to the given file stream. * * \param file * Provides the output file stream to write to. * * \return * Returns true if the write operations all succeeded. */ bool mutegroupsfile::write_stream (std::ofstream & file) { write_date(file, "mute-groups"); file << "# Used in the [mute-group-file] section of the 'rc' file, making it easier to\n" "# multiple mute groups. To use this file, specify it in [mute-group-file] file\n" "# and set 'active = true'.\n" ; write_seq66_header(file, "mutes", version()); std::string c = mutes().comments_block().text(); write_comment(file, c); file << "\n" "# load-mute-groups: Set to 'none' or 'mutes' to load from the 'mutes' file,\n" "# 'midi' to load from the song, or 'both' to try to read from 'mutes' first,\n" "# then the 'midi' file.\n" "#\n" "# save-mutes-to: 'both' writes mutes to the 'mutes' and MIDI file; 'midi'\n" "# writes only to the MIDI file; and the mutes only to the 'mutes' file.\n" "#\n" "# strip-empty: If true, all-zero mute-groups are not written to the MIDI file.\n" "#\n" #if defined USE_MUTE_GROUP_ROWS_COLUMNS "# mute-group-rows and mute-group-columns: Specifies the size of the mute-group\n" "# grid, not the number of mutes in a group (which matches the screenset size).\n" "# Keep these values at 4x8 and 32; mute-group-count is for sanity-checking.\n" "#\n" #endif "# groups-format: 'binary' means write mutes as 0/1; 'hex' means write them as\n" "# hexadecimal numbers (e.g. 0xff), useful for larger set sizes.\n" "#\n" "# mute-group-selected: If 0 to 31, and mutes are available from this file\n" "# or from the MIDI file, then this mute-group is applied at startup; useful in\n" "# restoring a session. Set to -1 to disable.\n" "#\n" "# toggle-active-only: When a group is toggled off, all patterns, even those\n" "# outside the mute-group, are muted. With this flag, only patterns in the\n" "# mute-group are muted. Patterns unmuted directly by the user remain unmuted.\n" ; bool result = write_mute_groups(file); if (result) write_seq66_footer(file); return result; } /** * This options-writing function is just about as complex as the * options-reading function. * * \return * Returns true if the write operations all succeeded. */ bool mutegroupsfile::write () { std::ofstream file(name(), std::ios::out | std::ios::trunc); bool result = file.is_open(); if (result) { file_message("Write mutes", name()); result = write_stream(file); file.close(); } else { file_error("Write open fail", name()); } return result; } /** * The default long format for writing mute groups. No longer needed. * * static const char * const sg_scanf_fmt_1 = * "%d [ %d %d %d %d %d %d %d %d ] [ %d %d %d %d %d %d %d %d ] " * " [ %d %d %d %d %d %d %d %d ] [ %d %d %d %d %d %d %d %d ]" ; */ /** * Writes the [mute-group] section to the given file stream. This can also * be called by the rcfile object to just dump the data into that file. * * \param file * Provides the output file stream to write to. * * \return * Returns true if the write operations all succeeded. */ bool mutegroupsfile::write_mute_groups (std::ofstream & file) { bool result = file.is_open(); if (result) { bool usehex = mutes().group_format_hex(); std::string gf = usehex ? "hex" : "binary" ; file << "\n[mute-group-flags]\n\n"; write_string(file, "load-mute-groups", mutes().group_load_label()); write_string(file, "save-mutes-to", mutes().group_save_label()); write_boolean(file, "strip-empty", mutes().strip_empty()); #if defined USE_MUTE_GROUP_ROWS_COLUMNS write_integer(file, "mute-group-rows", mutes().rows()); write_integer(file, "mute-group-columns", mutes().columns()); write_integer(file, "mute-group-count", mutes().count()); #endif write_integer(file, "mute-group-selected", mutes().group_selected()); write_string(file, "groups-format", gf); write_boolean(file, "toggle-active-only", mutes().toggle_active_only()); file << "\n[mute-groups]\n\n" << "# We save mute-group values in the 'mutes' file, even if all zeroes. They can\n" "# be stripped out of the MIDI file by 'strip-empty-mutes'. Hex values indicate\n" "# a bit-mask, not a single bit. A quoted group name can be placed at the end\n" "# of the line.\n\n" ; bool load = mutes().group_load_from_mutes(); const mutegroups & mutestorage = load ? mutes() : internal_mutegroups() ; if (mutestorage.empty()) { if (mutes().group_format_hex()) { for (int m = 0; m < mutegroups::Size(); ++m) { file << std::setw(2) << m << " [ 0x00 ] " << std::endl; } } else { for (int m = 0; m < mutegroups::Size(); ++m) { file << std::setw(2) << m << " " << "[ 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 ]" << std::endl ; } } } else { /* * TODO: Consider what to do if the user does not want to save * mutes to this file, but it does have mutes in it already. * Should we have a static mutegroups object that merely holds * the current mute-group data for saving later? */ for (const auto & stz : mutestorage.list()) { int gmute = stz.first; const mutegroup & m = stz.second; std::string stanza = write_stanza_bits ( m.get(), m.columns(), usehex ); if (! stanza.empty()) { std::string groupname = m.name(); file << std::setw(2) << gmute << " " << stanza; if (! groupname.empty()) file << " \"" << groupname << "\""; file << std::endl; } else { result = false; break; } } } } return result; } /** * We need the format of a mute-group stanza to be more flexible. * Should it match the set size? * * We want to support the misleading format "[ b b b... ] [ b b b...] ...", * as well as a new format "[ 0xbb ] [ 0xbb ] ...". * * This function handles the current line of data from the mutes file. */ bool mutegroupsfile::parse_mutes_stanza (mutegroups & mutes) { int group = string_to_int(line()); bool result = group >= 0 && group < c_max_groups; /* a sanity check */ if (result) { midibooleans groupmutes; result = parse_stanza_bits(groupmutes, line()); if (result) { if (groupmutes.size() != size_t(mutes.group_count())) { /* * Here, the user changed the dimensions of a set, but * we're reading an old mute-group file. So we must adjust. * Note: Also done when reading mutes-groups from the * MIDI file in midifile::parse_c_mutegroups(). */ groupmutes = fix_midibooleans(groupmutes, mutes.group_count()); rc().auto_mutes_save(true); } result = mutes.load(group, groupmutes); } if (result) { std::string quoted = next_quoted_string(line()); /* tricky */ if (! quoted.empty()) mutes.group_name(group, quoted); } } return result; } bool open_mutegroups (const std::string & source, mutegroups & mutes) { bool result = ! source.empty(); if (result) { mutegroupsfile mgf(source, mutes); result = mgf.parse(); } return result; } bool save_mutegroups (const std::string & destination, const mutegroups & mutes) { bool result = ! destination.empty(); if (result) { mutegroups & ncmutes = const_cast(mutes); mutegroupsfile mgf(destination, ncmutes); result = mgf.write(); if (! result) file_error("Mute-groups write failed", destination); } else file_error("Mute-groups file to save", "none"); return result; } } // namespace seq66 /* * mutegroupsfile.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/notemapfile.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file notemapfile.cpp * * This module declares/defines the base class for managing the reading and * writing of the note-map sections of the 'drums' file. * * \library seq66 application * \author Seq24 team; modifications by Chris Ahlstrom * \date 2019-11-05 * \updates 2025-05-17 * \license GNU GPLv2 or above * */ #include /* std::setw() */ #include /* std::cout */ #include "cfg/notemapfile.hpp" /* seq66::notemapfile class */ #include "cfg/settings.hpp" /* seq66::rcsettings & seq66::rc() */ namespace seq66 { /** * Principal constructor. * * \param mapper * Provides the notemapper reference to be acted upon. * * \param filename * Provides the name of the note-map file; this is usually a full path * file-specification to the "mutes" file using this object. * * \param rcs * The configfile currently requires an rcsetting object, but it is not * yet used here. */ notemapfile::notemapfile ( notemapper & mapper, const std::string & filename, rcsettings & rcs ) : configfile (filename, rcs, ".drums"), m_note_mapper (mapper) { // Empty body } /** * A rote destructor. */ notemapfile::~notemapfile () { // ~configfile() called automatically } /** * Parse the ~/.config/seq66/qseq66.drums file-stream or the * ~/.config/seq66/seq66.mutes file-stream. */ bool notemapfile::parse_stream (std::ifstream & file) { bool result = true; file.seekg(0, std::ios::beg); /* seek to start */ (void) parse_version(file); std::string s = parse_comments(file); if (! s.empty()) mapper().comments_block().set(s); s = get_variable(file, "[notemap-flags]", "map-type"); if (! s.empty()) mapper().map_type(s); s = get_variable(file, "[notemap-flags]", "gm-channel"); if (! s.empty()) mapper().gm_channel(string_to_int(s)); if (mapper().get_direction() == notemapper::direction::file) { bool flag = get_boolean(file, "[notemap-flags]", "reverse"); mapper().map_reversed(flag); } int note = (-1); int position = find_tag(file, "[Drum "); bool good = position > 0; if (good) { note = get_tag_value(line()); } if (note == (-1)) { errprint("No [Drum nn] tag value found"); good = false; } if (good) { for (int in_note = note; in_note < int(c_midibyte_data_max); ++in_note) { char tagtmp[24]; snprintf(tagtmp, sizeof tagtmp, "[Drum %d]", in_note); std::string tag = tagtmp; std::string gmname = get_variable(file, tag, "gm-name"); good = ! gmname.empty(); if (good) { std::string tmp = get_variable(file, tag, "gm-note"); good = ! tmp.empty(); if (good) { int gmnote = string_to_int(tmp); std::string devname = get_variable(file, tag, "dev-name"); tmp = get_variable(file, tag, "dev-note"); good = ! tmp.empty(); if (good) { int devnote = string_to_int(tmp); good = mapper().add(devnote, gmnote, devname, gmname); } } } } } mapper().mode(result); return result; } /** * Get the number of sequence definitions provided in the [mute-group] * section. See the rcfile class for full information. * * \param p * Provides the performance object to which all of these options apply. * * \return * Returns true if the file was able to be opened for reading. * Currently, there is no indication if the parsing actually succeeded. */ bool notemapfile::parse () { std::ifstream file(name(), std::ios::in | std::ios::ate); bool result = ! name().empty() && file.is_open(); if (result) { file_message("Read drums", name()); result = parse_stream(file); } else { std::string msg = "Read open fail"; file_error(msg, name()); msg += ": "; msg += name(); append_error_message(msg); result = false; } return result; } /** * Writes the [mute-group] section to the given file stream. * * \param file * Provides the output file stream to write to. * * \return * Returns true if the write operations all succeeded. */ bool notemapfile::write_stream (std::ofstream & file) { write_date(file, "note-mapper ('drums')"); file << "# This file resembles the files generated by 'midicvtpp', modified for Seq66:\n" "#\n" "# midicvtpp --csv-drum GM_DD-11_Drums.csv --output ddrums.ini\n" "#\n" "# This file can convert the percussion of non-GM devices to GM, as closely as\n" "# possible. Although it is for drums, it can be used for other note-mappings.\n" ; /* * [comments] */ write_seq66_header(file, "drums", version()); write_comment(file, mapper().comments_block().text()); file << "\n" "# Drum/note-mapping configuration for Seq66, stored in the HOME configuration\n" "# directory. To use this file, add its name to the '[note-mapper]' section of\n" "# the 'rc' file. There's no user-interface for this file. The main values are:\n" "#\n" "# map-type: drum, patch, or multi; indicates the mapping to do.\n" "# gm-channel: Indicates the channel (1-16) applied to convert notes.\n" "# reverse: true or false; map in the opposite direction if true.\n" "\n" "[notemap-flags]\n" "\n" ; write_string(file, "map-type", mapper().map_type()); write_integer(file, "gm-channel", mapper().gm_channel()); write_boolean(file, "reverse", mapper().map_reversed()); file << "\n" "# The drum section:\n" "#\n" "# [Drum 35]. Marks a GM drum-change section, one per instrument.\n" "#\n" "# gm-name GM name for the drum assigned to the input note.\n" "# gm-note Input note number, same as the section number.\n" "# dev-name The device's name for the drum.\n" "# dev-note GM MIDI note whose GM sound best matches the sound of dev-name.\n" "#\n" "# The gm-note value is converted to the dev-note value, unless reverse\n" "# mapping is activated. The actual GM drum sound might not match what the\n" "# MIDI hardware puts out.\n" "\n" ; bool result = write_map_entries(file); if (result) { file << "\n# End of " << name() << "\n#\n" << "# vim: sw=4 ts=4 wm=4 et ft=dosini\n" ; } else file_error("Write fail", name()); return result; } /** * This options-writing function is just about as complex as the * options-reading function. * * \return * Returns true if the write operations all succeeded. */ bool notemapfile::write () { std::ofstream file(name(), std::ios::out | std::ios::trunc); bool result = ! name().empty() && file.is_open(); if (result) { file_message("Write drums", name()); result = write_stream(file); file.close(); } else { file_error("Write open fail", name()); } return result; } /** * Writes the [note-mapper] section to the given file stream. This can also * be called by the rcfile object to just dump the data into that file. * * \param file * Provides the output file stream to write to. * * \return * Returns true if the write operations all succeeded. */ bool notemapfile::write_map_entries (std::ofstream & file) const { bool result = file.is_open(); if (result) { int count = 0; for (const auto & mapentry : m_note_mapper.list()) { file << "[Drum " << mapentry.second.dev_value() << "]" << "\n\n" << mapentry.second.to_string() << "\n" ; ++count; } if (count == 0) { file << "# This is a sample. See 'data/samples/GM_DD-11.drums' for a full example.\n" "\n" "[Drum 36]\n" "\n" "gm-name = \"Bass Drum 1\"\n" "gm-note = 35\n" "dev-name = \"Bass Drum Gated Reverb\"\n" "dev-note = 36\n" ; } } return result; } /** * This function reads the source notemapper file and then saves it to the new * location. * * \param [inout] nm * Provides the notemapper object. * * \param source * Provides the input file name from which the notemapper will be filled. * * \param destination * Provides the directory to which the play-list file is to be saved. * * \return * Returns true if the operation succeeded. */ bool copy_notemapper ( notemapper & nm, const std::string & source, const std::string & destination ) { bool result = ! source.empty() && ! destination.empty(); if (result) { std::string msg = source + " --> " + destination; notemapfile nmf(nm, source, rc()); file_message("Note-map save", msg); result = nmf.parse(); if (result) { nmf.name(destination); result = nmf.write(); if (! result) file_error("Write failed", destination); } else file_error("Copy failed", source); } else file_error("Note-map file", "none"); return result; } } // namespace seq66 /* * notemapfile.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/patchesfile.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file patchesfile.cpp * * This module declares/defines the base class for managing the reading and * writing of the note-map sections of the 'drums' file. * * \library seq66 application * \author Seq24 team; modifications by Chris Ahlstrom * \date 2019-11-05 * \updates 2025-02-19 * \license GNU GPLv2 or above * */ #include /* std::setw() */ #include /* std::cout */ #include "cfg/patchesfile.hpp" /* seq66::patchesfile class */ #include "cfg/settings.hpp" /* seq66::rcsettings & seq66::rc() */ namespace seq66 { /** * Principal constructor. * * \param mapper * Provides the patches reference to be acted upon. * * \param filename * Provides the name of the note-map file; this is usually a full path * file-specification to the "mutes" file using this object. * * \param rcs * The configfile currently requires an rcsetting object, but it is not * yet used here. */ patchesfile::patchesfile ( const std::string & filename, rcsettings & rcs ) : configfile (filename, rcs, ".patches") { // Empty body } /** * A rote destructor. */ patchesfile::~patchesfile () { // ~configfile() called automatically } /** * Parse the ~/.config/seq66/qseq66.drums file-stream or the * ~/.config/seq66/seq66.mutes file-stream. */ bool patchesfile::parse_stream (std::ifstream & file) { bool result = true; file.seekg(0, std::ios::beg); /* seek to start */ (void) parse_version(file); std::string patch_comments = parse_comments(file); int patch = (-1); int position = find_tag(file, "[Patch "); bool good = position > 0; set_patches_comment(patch_comments); if (good) { patch = get_tag_value(line()); } if (patch == (-1)) { errprint("No [Patch nn] tag value found"); good = false; } if (good) { for (int inpatch = 0; inpatch < int(c_midibyte_data_max); ++inpatch) { /* * In the patches file, we currently do not use gm-name (not * relevant to our simple setup) and gm-patch. We just want * the patch number (0-127) and the name of the patch as * defined by the legacy device, For now. Also, if there is * no patch for the device at a given patch number, use "N/A", * not an empty string. */ char tagtmp[24]; snprintf(tagtmp, sizeof tagtmp, "[Patch %d]", inpatch); std::string tag = tagtmp; std::string devname = get_variable(file, tag, "dev-name"); good = ! devname.empty(); if (good) { (void) add_patch(inpatch, devname); } else { error_message ( "Missing dev-name for patch number", std::to_string(inpatch) ); break; } } } return result; } /** * Get the number of sequence definitions provided in the [mute-group] * section. See the rcfile class for full information. * * \param p * Provides the performance object to which all of these options apply. * * \return * Returns true if the file was able to be opened for reading. * Currently, there is no indication if the parsing actually succeeded. */ bool patchesfile::parse () { std::ifstream file(name(), std::ios::in | std::ios::ate); bool result = ! name().empty() && file.is_open(); if (result) { file_message("Read patches", name()); result = parse_stream(file); } else { std::string msg = "Read open fail"; file_error(msg, name()); msg += ": "; msg += name(); append_error_message(msg); result = false; } return result; } /** * Writes the [mute-group] section to the given file stream. * * \param file * Provides the output file stream to write to. * * \return * Returns true if the write operations all succeeded. */ bool patchesfile::write_stream (std::ofstream & file) { write_date(file, "Patch file ('patches')"); file << "# This file resembles the files generated by 'midicvtpp', modified for Seq66:\n" "#\n" "# midicvtpp --csv-drum GM_DD-11_Drums.csv --output ddrums.ini\n" "#\n" "# This file defines legacy device-specific non-GM patch mappings. They are\n" "# currently used for display when editing Program-Change events.\n" ; /* * [comments] */ write_seq66_header(file, "patches", version()); write_comment(file, get_patches_comment()); file << "\n\n" "# Patch-mapping configuration for Seq66, stored in the HOME configuration\n" "# directory. To use this file, add its name to the '[patch-file]' section of\n" "# the 'rc' file. There's no user-interface for this file.\n" "#\n" ; file << "#\n" "# The patches section:\n" "#\n" "# [Patch 5]. Provides the ordering number for the patch sections.\n" "#\n" "# gm-name GM name for the patch assigned to the patch number.\n" "# gm-patch Patch number, same as the section number.\n" "# dev-name The device's name for the patch.\n" "# dev-patch GM MIDI patch whose GM sound best matches the dev-name.\n" "# (Not yet used).\n" "\n" ; bool result = write_map_entries(file); if (result) { file << "# End of " << name() << "\n#\n" << "# vim: sw=4 ts=4 wm=4 et ft=dosini\n" ; } else file_error("Write fail", name()); return result; } /** * This options-writing function is just about as complex as the * options-reading function. * * \return * Returns true if the write operations all succeeded. */ bool patchesfile::write () { std::ofstream file(name(), std::ios::out | std::ios::trunc); bool result = ! name().empty() && file.is_open(); if (result) { file_message("Write patches", name()); result = write_stream(file); file.close(); } else { file_error("Write open fail", name()); } return result; } /** * Writes the [note-mapper] section to the given file stream. This can also * be called by the rcfile object to just dump the data into that file. * * \param file * Provides the output file stream to write to. * * \return * Returns true if the write operations all succeeded. */ bool patchesfile::write_map_entries (std::ofstream & file) const { bool result = file.is_open(); if (result) { std::string lst = program_list(); /* constructed in patches.cpp */ if (lst.empty()) file << "No patches to write." << std::endl; else file << lst << std::endl; } return result; } bool open_patches (const std::string & source) { bool result = ! source.empty(); if (result) { patchesfile patfile(source, rc()); /* add msg? */ result = patfile.parse(); if (result) { // Anything worth doing? } else { std::string msg = "Open failed: "; msg += source; (void) error_message(msg); } } else { file_error("Patches file to open", "none"); } return result; } bool save_patches (const std::string & destination) { bool result = ! destination.empty(); if (result) { patchesfile patfile(destination, rc()); result = patfile.write(); if (! result) file_error("Write failed", destination); } else file_error("Patches file", "none"); return result; } /** * This function reads the source patches file and then saves it to the new * location. * * \param [inout] pal * Provides the patches object. * * \param source * Provides the input file name from which the patches will be filled. * * \param destination * Provides the directory to which the play-list file is to be saved. * * \return * Returns true if the operation succeeded. */ bool save_patches ( const std::string & source, const std::string & destination ) { bool result = ! source.empty(); if (result) { std::string msg = source + " --> " + destination; patchesfile patfile(source, rc()); /* * TMI: file_message("Palette save", msg); */ result = patfile.parse(); if (result) result = save_patches(destination); else file_error("Open failed", source); } else file_error("Patches file", "none"); return result; } #if 0 /** * This function reads the source patches file and then saves it to the new * location. * * \param [inout] nm * Provides the patches object. * * \param source * Provides the input file name from which the patches will be filled. * * \param destination * Provides the directory to which the play-list file is to be saved. * * \return * Returns true if the operation succeeded. */ bool copy_patches ( patches & nm, const std::string & source, const std::string & destination ) { bool result = ! source.empty() && ! destination.empty(); if (result) { std::string msg = source + " --> " + destination; patchesfile nmf(nm, source, rc()); file_message("Note-map save", msg); result = nmf.parse(); if (result) { nmf.name(destination); result = nmf.write(); if (! result) file_error("Write failed", destination); } else file_error("Copy failed", source); } else file_error("Note-map file", "none"); return result; } #endif // 0 } // namespace seq66 /* * patchesfile.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/playlistfile.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file playlistfile.cpp * * This module declares/defines the base class for managing the * ~/.seq66rc legacy configuration file or the new * ~/.config/seq66/seq66.rc ("rc") configuration file. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-09-19 * \updates 2025-05-03 * \license GNU GPLv2 or above * * Here is a skeletal representation of a Seq66 playlist file: * \verbatim [playlist] 0 # playlist number, a MIDI value (0 to 127) "Downtempo" # playlist name, for display/selection /home/user/midifiles/ # directory where the songs are stored 10 file1.mid # MIDI value and file's base-name 11 file2.midi 12 file3.midi # . . . \endverbatim * * See the file data/sample.playlist for a more up-to-date example and * explanation. */ #include "cfg/playlistfile.hpp" /* seq66::playlistfile class */ #include "cfg/settings.hpp" /* seq66::rc() */ #include "play/playlist.hpp" /* seq66::playlist class */ #include "util/filefunctions.hpp" /* functions for file-names */ #include "util/strfunctions.hpp" /* strip_quotes() */ namespace seq66 { static const int s_playlist_file_version = 1; /** * Principal constructor. * * \param p * Provides the performer object that will interface between this module * and the rest of the application. * * \param name * Provides the name of the options file; this is usually a full path * file-specification. * * \param rcs * Provides a reference to an "rc" settings object to hold current options * and modified options. * * \param show_on_stdout * If true (the default is false), then the list/song information is * written to stdout, to help with debugging. */ playlistfile::playlistfile ( const std::string & filename, playlist & pl, rcsettings & rcs, bool show_on_stdout ) : configfile (filename, rcs, ".playlist"), m_play_list (pl), m_show_on_stdout (show_on_stdout) { version(s_playlist_file_version); } /** * This destructor is a rote do-nothing destructor. */ playlistfile::~playlistfile () { // No code needed } /** * Helper function for error-handling. It assembles a message and then * passes it to append_error_message(). * * \param additional * Additional context information to help in finding the error. * * \return * Always returns false. */ bool playlistfile::set_error_message (const std::string & additional) { std::string msg = "Playlist file"; if (! additional.empty()) { msg += ": "; msg += additional; } warn_message(msg); append_error_message(msg); return false; } /** * Opens the current play-list file and optionally verifies it. * * \param verify_it * If true (the default), call verify() to make sure the playlist is * sane. The verification is weak, rather than the strong option which * is available. * * \return * Returns true if the file was parseable and verifiable. The caller * (especially the performer) may want to do a "clear all". */ bool playlistfile::open (bool verify_it) { bool result = parse(); if (result) { if (verify_it) { if (m_show_on_stdout) { msgprintf ( msglevel::status, "Verifying playlist %s", name().c_str() ); } result = play_list().verify(); /* weak verify */ } } play_list().loaded(result); return result; } /** * Parses the ~/.config/seq66/file.playlistfile file. * * The next_section() function is like line-after, but scans from the * current line in the file. Necessary here because all the sections * have the same name. After detecting the "[playlist]" section, the * following items need to be obtained: * * - Playlist number. This number is used as the key value for * the playlist. It can be any MIDI value (0 to 127), and the order * of the playlists is based on this number, and selectable via MIDI * control with this number. * - Playlist name. A human-readable string describing the * nick-name for the playlist. This is an alternate way to * look up the playlist. * - Song directory name. The directory where the songs are * stored. If this name is empty, then the song file-names * need to include the individual directories for each file. * But even if not empty, the play-list directory is not used if * the song file-name includes a path, as indicated by "/" or "\". * - Song file-name, or path to the song file-name. * * Note that the call to next_section() already gets to the next line of * data, which should be the index number of the playlist. * * We need to get the song's MIDI control number and it's directory name. * Make sure the directory name is canonical and clean. The existence of the * file should be validated later. Also determine if the song file-name * already has a directory before using the play-list's directory. * * Consider catching file exceptions! * * \return * Returns true if the file was able to be opened for reading. * Currently, there is no indication if the parsing actually succeeded. */ bool playlistfile::parse () { if (is_empty_string(name())) return false; std::ifstream file(name(), std::ios::in | std::ios::ate); bool result = file.is_open(); if (result) { file_message("Read", name()); file.seekg(0, std::ios::beg); /* seek to start */ play_list().clear(); (void) parse_version(file); std::string temp = parse_comments(file); if (! temp.empty()) play_list().comments_block().set(temp); /* * [playlist-options] */ std::string tag = "[playlist-options]"; if (file_version_number() < s_playlist_file_version) { result = version_error_message("playlist", file_version_number()); rc_ref().auto_playlist_save(true); } else { /* * The first one would be better named "auto-arm". */ bool flag = get_boolean(file, tag, "unmute-next-song"); play_list().auto_arm(flag); flag = get_boolean(file, tag, "auto-play"); play_list().auto_play(flag); flag = get_boolean(file, tag, "auto-advance"); play_list().auto_advance(flag); flag = get_boolean(file, tag, "deep-verify"); play_list().deep_verify(flag); } int listcount = 0; bool have_section = line_after(file, "[playlist]"); if (! have_section) { result = set_error_message("empty/missing [playlist] section"); } while (have_section) { int listnumber = -1; int songcount = 0; playlist::play_list_t plist; /* current playlist */ sscanf(scanline(), "number = %d", &listnumber); if (m_show_on_stdout) { msgprintf ( msglevel::status, "Processing playlist #%d", listnumber ); } if (next_data_line(file)) { std::string listline = line(); playlist::song_list slist; listline = extract_variable(line(), "name"); plist.ls_list_name = listline; if (m_show_on_stdout) { msgprintf ( msglevel::status, "Playlist name '%s'", listline.c_str() ); } if (next_data_line(file)) { listline = line(); listline = extract_variable(line(), "directory"); plist.ls_file_directory = clean_path(listline); slist.clear(); if (m_show_on_stdout) { msgprintf ( msglevel::status, "Playlist directory '%s'", listline.c_str() ); } while (next_data_line(file)) { int songnumber = -1; std::string fname; result = scan_song_file(songnumber, fname); fname = strip_quotes(fname); if (result) { playlist::song_spec_t sinfo; sinfo.ss_index = songcount; sinfo.ss_midi_number = songnumber; if (name_has_path(fname)) { std::string path; std::string filebase; filename_split(fname, path, filebase); sinfo.ss_song_directory = path; sinfo.ss_embedded_song_directory = true; sinfo.ss_filename = filebase; (void) play_list().add_song(slist, sinfo); ++songcount; } else if (! fname.empty()) { sinfo.ss_song_directory = plist.ls_file_directory; sinfo.ss_embedded_song_directory = false; sinfo.ss_filename = fname; (void) play_list().add_song(slist, sinfo); ++songcount; } } else { std::string msg = "scanning song file '"; msg += fname; msg += "' failed"; result = set_error_message(msg); break; } } /* * Need to deal with a false result? It's not really a * fatal error to have an empty song list. */ plist.ls_index = listcount; /* ordinal */ plist.ls_midi_number = listnumber; /* MIDI mapping */ plist.ls_song_count = songcount; plist.ls_song_list = slist; /* copy temp */ result = play_list().add_list(plist); } else { std::string msg = "no list directory in playlist #" + std::to_string(listnumber); result = set_error_message(msg); break; } } else { std::string msg = "no data in playlist #" + std::to_string(listnumber); result = set_error_message(msg); break; } ++listcount; have_section = next_section(file, "[playlist]"); } file.close(); /* done with playlist file */ } else if (rc().playlist_active() && ! name().empty()) { std::string msg = "Open failed: " + name(); result = set_error_message(msg); } (void) play_list().reset_list(0, ! result); /* reset, not clear, if ok */ return result; } /** * Encapsulates some groty code for the parse() function. It assumes that * next_data_line() has retrieved a file-name line for a song. * * \param [out] song_number * Holds the song number that was retrieved. Use it only if not equal * to -1 and if this function returns true. * * \param [out] song_file * Holds the song file-name that was retrieved. Use it only if not * empty and if this function returns true. * * \return * Returns true if this function succeeded. If false, an error message is * set up. */ bool playlistfile::scan_song_file (int & song_number, std::string & song_file) { bool result = false; int songnumber = -1; const char * dirname = &m_line[0]; int sscount = sscanf(scanline(), "%d", &songnumber); if (sscount == EOF || sscount == 0) { song_number = -1; /* side-effect */ song_file.clear(); /* side-effect */ result = set_error_message("song number missing"); } else { while (! std::isspace(*dirname)) { if (*dirname == 0) break; ++dirname; } while (std::isspace(*dirname)) { if (*dirname == 0) break; ++dirname; } bool gotit = std::isalnum(*dirname) || std::ispunct(*dirname); if (gotit) { song_number = songnumber; /* side-effect */ song_file = dirname; /* side-effect */ result = true; } else { song_number = -1; /* side-effect */ song_file.clear(); /* side-effect */ result = set_error_message("song file-path missing"); } } return result; } /** * Writes the play-list to the file whose name is returned by the name() * function. If the name is empty, we don't try to open it; that truncates * the file! * * Consider catching file exceptions! * * \return * Returns true if the write operations all succeeded. */ bool playlistfile::write () { std::ofstream file(name(), std::ios::out | std::ios::trunc); bool result = ! name().empty() && file.is_open(); if (result) { file_message("Write playlist", name()); } else { file_error("Write open fail", name()); return result; } write_date(file, "playlist"); file << "# This file holds multiple playlists, each in a [playlist] section. Each has\n" "# a user-specified number for sorting and MIDI control, ranging from 0 to 127.\n" "# Next comes a quoted name for this list, followed by the quoted name\n" "# of the song directory using the UNIX separator ('/').\n" "#\n" "# Next is a list of tunes, each starting with a MIDI control number and the\n" "# quoted name of the MIDI file, sorted by control number. They can be simple\n" "# 'base.midi' file-names; the playlist directory is prepended to access the\n" "# song file. If the file-name has a path, that will be used.\n" ; write_seq66_header(file, "playlist", version()); /* * [comments] */ std::string c = play_list().comments_block().text(); write_comment(file, c); /* * [playlist-options] */ file << "\n[playlist-options]\n\n"; file << "# 'unmute-next-song' causes the next selected song to have all patterns\n" "# armed for playback. (Should be called 'auto-arm'). Does not matter for\n" "# songs with triggers for Song mode. 'auto-play' causes songs to start play\n" "# automatically when loaded. 'auto-advance' implies the settings noted\n" "# above. It automatically loads the next song in the play-list when the\n" "# current song ends. 'deep-verify' causes each tune in the play-list to be\n" "# loaded to make sure each one can be loaded. Otherwise, only file existence\n" "# is checked.\n\n" ; write_boolean(file, "unmute-next-song", play_list().auto_arm()); write_boolean(file, "auto-play", play_list().auto_play()); write_boolean(file, "auto-advance", play_list().auto_advance()); write_boolean(file, "deep-verify", play_list().deep_verify()); file << "\n" "# Here are the playlist settings, default storage folder, and then a list of\n" "# each tune with its control number. The playlist number is arbitrary but\n" "# unique. 0 to 127 enforced for use with MIDI playlist controls. Similar\n" "# for the tune numbers. Each tune can include a path; it overrides the base\n" "# directory.\n" ; /* * [playlist] sections */ bool is_empty = true; for (const auto & plpair : play_list().play_list_map()) { const playlist::play_list_t & pl = plpair.second; file << "\n[playlist]\n\n"; write_integer(file, "number", pl.ls_midi_number); write_string(file, "name", pl.ls_list_name, true); write_string(file, "directory", pl.ls_file_directory, true); file << "\n"; /* * For each song, write the MIDI control number, followed only by * the song's file-name, which could include the path-name. */ const playlist::song_list & sl = pl.ls_song_list; for (const auto & sc : sl) { const playlist::song_spec_t & s = sc.second; bool haspath = s.ss_embedded_song_directory; std::string fname; if (haspath) { fname = filename_concatenate(s.ss_song_directory, s.ss_filename); } else { fname = s.ss_filename; } fname = add_quotes(fname); file << s.ss_midi_number << " " << fname << "\n"; } is_empty = false; } if (is_empty) { file << "\n[playlist]\n\n" "# This is a NON-FUNCTIONAL playlist SAMPLE. Please see one of the sample\n" "# playlist files shipped with Seq66, or fill this in the Playlist tab.\n\n" ; } write_seq66_footer(file); file.close(); return true; } /* * Free functions for handling play-lists and files. */ /** * Reads the play-list data into a playlist object. * * \param source * Provides the full path file-specification for the play-list file to be * opened. * * \param show_on_stdout * If true (the default is false), the playlist is opened to show * song selections on stdout. This is useful for trouble-shooting or for * making the CLI version of Sequencer64 easier to follow when running. * * \return * Returns true if the playlist object was able to be created. If the * file-name is not empty, this also means that it was opened, and the * play-list read. If false is returned, then the previous playlist, if * any, still exists, but is marked as inactive. */ bool open_playlist ( playlist & pl, const std::string & source, bool show_on_stdout ) { bool result = ! is_missing_string(source); /* empty, "", or "?" */ if (result) { playlistfile plf(source, pl, rc(), show_on_stdout); result = plf.open(true); /* parse and file verify */ if (result) { // Anything worth doing? } else if (rc().playlist_active()) { std::string msg = "Open failed: "; msg += source; (void) error_message(msg); } } else { file_error("Playlist file", "none"); pl.loaded(false); } return result; } /** * Writes the play-list, whether it is active or not, as long as it exists. * Saves a playlist to file. Used in clinsmanager and in performer. * * \param pl * Provides a reference to the playlist. The caller is responsible for * making sure this parameter is valid. * * \param destfile * Provides the full path file-specification for the play-list file to be * saved. If empty, the file-name with which the play-list was created * is used. * * \return * Returns true if the write operation succeeded. * */ bool save_playlist ( playlist & pl, const std::string & destfile ) { std::string destination = destfile.empty() ? pl.file_name() : destfile; bool result = ! is_missing_string(destination); /* empty, "", "?" */ if (result) { playlistfile plf(destination, pl, rc(), false); /* false --> quiet */ pl.file_name(destination); result = plf.write(); if (! result) file_error("Playlist write failed", destination); } else file_error("Playlist file to save", "none"); return result; } /** * This function reads the source playlist file and then saves it to the new * location. * * \param [inout] pl * Provides the playlist object. * * \param source * Provides the input file name from which the playlist will be filled. * * \param destination * Provides the directory to which the play-list file is to be saved. * * \return * Returns true if the operation succeeded. */ bool save_playlist ( playlist & pl, const std::string & source, const std::string & destination ) { bool result = ! source.empty() && ! destination.empty(); if (result) { playlistfile plf(source, pl, rc(), false); /* false --> quiet */ result = plf.open(false); /* parse, no verify */ if (result) { result = save_playlist(pl, destination); } else { file_error("Open failed", source); } } else { file_error("Playlist file", "none"); } return result; } /** * This function uses the playlist to copy all of the MIDI files noted in the * source playlist file. * * Note that the directory structure is somewhat preserved in the destination. * For example, here is an input directory and the resultant output directory * for the import of ca_midi.playlist: * * - /pub/Audio/MIDI/archives/Ca/Mid/FM Synth/ * - ~/.config/seq66/playlists/ca_midi/pub/Audio/MIDI/.../Mid/FM Synth/ * * Is this good or bad? * * \param [in] plp * Provides a pointer to the playlist, which should have been filled with * playlist data. * * \param source * Simply provides the input file name for information only. * * \param destination * Provides the directory to which the play-list file is to be saved. * * \return * Returns true if the operation succeeded. */ bool copy_playlist_songs ( playlist & pl, const std::string & source, const std::string & destination ) { bool result = ! source.empty() && ! destination.empty(); if (result) { std::string msg = source + " --> " + destination; file_message("Playlist copy", msg); result = pl.copy_songs(destination); if (! result) file_error("Copy failed", destination); } else { file_error("Playlist file directories", ""); } return result; } } // namespace seq66 /* * playlistfile.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/rcfile.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file rcfile.cpp * * This module declares/defines the base class for managing the * the ~/.config/seq66.rc ("rc") configuration file. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-23 * \updates 2026-04-17 * \license GNU GPLv2 or above * * The ~/.config/seq66.rc configuration file is fairly simple * in layout. See the user's manual included with Seq66. * * Note that the parse() and write() functions process sections in a * different order! The reason this does not mess things up is that the * line_after() function, by default, rescans from the beginning of the file. * As long as each section's sub-values are read and written in the same * order, there will be no problem. * * Finally, note that seq66 no longer supports the Seq24 file format; * too much has changed. */ #include /* std::setw() I/O manipulator */ #include "cfg/midicontrolfile.hpp" /* seq66::midicontrolfile class */ #include "cfg/rcfile.hpp" /* seq66::rcfile class */ #include "cfg/settings.hpp" /* seq66::rc() accessor */ #include "midi/midibus.hpp" /* seq66::midibus class */ #include "util/filefunctions.hpp" /* seq66::filename_base() etc. */ #include "util/strfunctions.hpp" /* seq66::strip_quotes() function */ #if defined SEQ66_USE_FRUITY_CODE /* will not be supported in seq66 */ /** * Provides names for the mouse-handling used by the application. The fruity * mode is currently not supported in Seq66, but maybe someone will want it * back. */ static const std::string c_interaction_method_names[3] = { "seq66", "fruity", "" }; /** * Provides descriptions for the mouse-handling used by the application. */ static const std::string c_interaction_method_descs[3] = { "Original Seq66 method", "Similar to a certain Fruity sequencer", "" }; #endif // defined SEQ66_USE_FRUITY_CODE namespace seq66 { static const int s_rc_file_version = 4; /** * Principal constructor. * * Versions: * * 0: The initial version, close to the Seq64 format. * 1: 2021-05-16. More modern JACK configuration settings. * 2: 2021-06-04. Transition to get-variable for booleans and integers, * finished on 2021-06-07. * 3: 2023-11-02. Moved style-sheets from 'usr' to 'rc'. * 4: 2025-02-18. Added [patches-file] section to the 'rc' file. * * \param rcs * The source/destination for the configuration information. * * \param name * Provides the name of the options file; this is usually a full path * file-specification. */ rcfile::rcfile (const std::string & name, rcsettings & rcs) : configfile (name, rcs, ".rc") { version(s_rc_file_version); } /** * Parse the ~/.config/seq66/qseq66.rc file. After this function is called, * the performer::get_settings() function can be used to populate the * performer with the settings it needs. * * [midi-control-file] * * The [midi-control] section is no longer supported. The MIDI control * information is in a separate file. This allows the user to switch * between different setups without having to mess with editing the "rc" * file so much. * * [midi-clock] * * The MIDI-clock section defines the clocking value for up to 16 output * busses. The first number indicates how many busses are specified. * Generally, these busses are shown to the user with names such as "[1] * seq66 1". * * [jack-transport] * * This section covers various JACK settings, one setting per line. * The following numbers are specfied: * * - jack_transport - Enable sync with JACK Transport. * - jack_master - Seq24 will attempt to serve as JACK Master. * - jack_master_cond - Seq24 will fail to be Master if there is * already a Master set. * - jack_auto_connect - Automatically connect to discovered JACK * ports. * - song_start_mode: * - 0 = Playback will be in Live mode. Use this to allow * muting and unmuting of loops. * - 1 = Playback will use the Song Editor's data. * * [midi-input] * * This section covers the MIDI input busses, and has a format similar to * "[midi-clock]". Generally, these busses are shown to the user with names * such as "[1] seq66 1", and currently there is only one input buss. The * first field is the port number, and the second number indicates whether * it is disabled (0), or enabled (1). * * [midi-clock-mod-ticks] * * This section covers.... One common value is 64. * * [manual-ports] * * Set to 1 if you want seq66 to create its own virtual ports and not * connect to other clients. * * [last-used-dir] * * This section simply holds the last path-name that was used to read or * write a MIDI file. * * [interaction-method] (obsolete) * * This section specified the kind of mouse interaction. We're keeping * the code, macro'd out, in case someone clamors for it eventually. * * - 0 = 'seq66' (original Seq24 method). * - 1 = 'fruity' (similar to a certain fruity sequencer we like). * * The second data line is set to "1" if Mod4 can be used to keep seq66 in * note-adding mode even after the right-click is released, and "0" * otherwise. * * \return * Returns true if the file was able to be opened for reading. * Currently, there is no indication if the parsing actually succeeded. */ bool rcfile::parse () { std::ifstream file(name(), std::ios::in | std::ios::ate); if (! set_up_ifstream(file)) /* verifies [Seq66]: version */ return false; /* * We no longer read the verbosity. It should be only a command line * option, otherwise it gets annoying. However, we now have a * quiet option (it's not yet a command-line option). * * bool verby = get_boolean(file, "[Seq66]", "verbose"); * rc_ref().verbose(verby); */ std::string tag = "[Seq66]"; bool verby = get_boolean(file, tag, "quiet"); rc_ref().quiet(verby); std::string s = parse_version(file); if (s.empty() || file_version_is_old(file)) rc_ref().auto_rc_save(true); s = get_variable(file, tag, "sets-mode"); rc_ref().sets_mode(s); s = get_variable(file, tag, "port-naming"); rc_ref().port_naming(s); bool initem = get_boolean(file, tag, "init-disabled-ports"); rc_ref().init_disabled_ports(initem); int priority = get_integer(file, tag, "priority", 0); if (priority > 0) { rc().priority(true); rc().thread_priority(priority); } /* * [comments] Header comments (hash-tag lead) are skipped during parsing. * However, we now try to read an optional comment block. */ s = parse_comments(file); if (! s.empty()) rc_ref().comments_block().set(s); bool ok = true; /* start hopefully! */ std::string fullpath; std::string pfname; tag = "[mute-group-file]"; bool active = get_file_status(file, tag, pfname); rc_ref().mute_group_file_active(active); rc_ref().mute_group_filename(pfname); /* base name */ fullpath = rc_ref().mute_group_filespec(); file_message("Read mutes", fullpath); /* * See get_usr_file() below. */ tag = "[usr-file]"; active = get_file_status(file, tag, pfname); rc_ref().user_file_active(active); rc_ref().user_filename(pfname); /* base name */ /* * [note-mapper] */ tag = "[note-mapper]"; active = get_file_status(file, tag, pfname); rc_ref().notemap_active(active); rc_ref().notemap_filename(pfname); /* base name */ tag = "[patches-file]"; active = get_file_status(file, tag, pfname); rc_ref().patches_active(active); rc_ref().patches_filename(pfname); /* base name */ tag = "[palette-file]"; active = get_file_status(file, tag, pfname); rc_ref().palette_active(active); rc_ref().palette_filename(pfname); /* base name */ tag = "[style-sheet-file]"; active = get_file_status(file, tag, pfname); rc_ref().style_sheet_active(active); rc_ref().style_sheet_filename(pfname); /* base name */ /* * JACK transport settings are currently accessed only via the rcsetting's * rc() accessor function. Also note that these settings must occur in the * given order in the 'rc' file: (1) Transport; (2) Master; (3) * Master-conditional. Otherwise the coordination of these settings gets * messed up. */ tag = "[jack-transport]"; s = get_variable(file, tag, "transport-type"); if (s.empty()) { int flag = 0; if (line_after(file, tag)) { std::sscanf(scanline(), "%d", &flag); /* 1 */ rc_ref().with_jack_transport(bool(flag)); next_data_line(file); std::sscanf(scanline(), "%d", &flag); /* 2 */ rc_ref().with_jack_master(bool(flag)); next_data_line(file); std::sscanf(scanline(), "%d", &flag); /* 3 */ rc_ref().with_jack_master_cond(bool(flag)); next_data_line(file); std::sscanf(scanline(), "%d", &flag); /* 4 */ rc_ref().song_start_mode(bool(flag)); if (next_data_line(file)) { std::sscanf(scanline(), "%d", &flag); /* 5 */ rc_ref().with_jack_midi(bool(flag)); } } } else { rc_ref().set_jack_transport(s); /* * If "live" or "song" are provided, then the "auto" boolean is false. */ s = get_variable(file, tag, "song-start-mode"); rc_ref().song_start_mode_by_string(s); bool flag = get_boolean(file, tag, "jack-midi"); rc_ref().with_jack_midi(flag); flag = get_boolean(file, tag, "jack-auto-connect", 0, true); rc_ref().jack_auto_connect(flag); flag = get_boolean(file, tag, "jack-use-offset", 0, true); rc_ref().jack_use_offset(flag); int buffersize = rc().jack_buffer_size(); buffersize = get_integer(file, tag, "jack-buffer-size", 0); rc().jack_buffer_size(buffersize); } tag = "[manual-ports]"; bool flag = get_boolean(file, tag, "virtual-ports"); rc_ref().manual_ports(flag); flag = get_boolean(file, tag, "auto-enable"); rc_ref().manual_auto_enable(flag); int count = get_integer(file, tag, "output-port-count"); rc_ref().manual_port_count(count); count = get_integer(file, tag, "input-port-count"); rc_ref().manual_in_port_count(count); /* * When Seq66 exits, it saves all of the inputs it has. If an input is * removed from the system (e.g. unplugging a MIDI controller), then * there will be too many entries in this section. The user might remove * one, and forget to update the buss count. So we ignore the buss * count. But we also have to read the channel-filter boolean. If an * error occurs, we abort... the user must fix the 'rc' file. */ tag = "[midi-input]"; ok = line_after(file, tag); if (ok) { int inbuses = 0; int count = std::sscanf(scanline(), "%d", &inbuses); ok = count > 0 && is_good_busscount(inbuses); } if (ok) { rc_ref().inputs().clear(); while (next_data_line(file)) { ok = rc_ref().inputs().add_list_line(line()); if (! ok) return make_error_message(tag, "in-bus data line error"); } } else { return make_error_message(tag, "section missing"); } /* * Check for an optional input port map section. */ bool portmaps_present = false; bool inportmap_active = false; bool outportmap_active = false; tag = "[midi-input-map]"; if (line_after(file, tag)) { inputslist & inputref = input_port_map(); int activeflag; int count = 0; inputref.clear(); (void) std::sscanf(scanline(), "%d", &activeflag); portmaps_present = true; inportmap_active = activeflag != 0; inputref.active(inportmap_active); while (next_data_line(file)) { if (inputref.add_map_line(line())) { ++count; } else { inputref.clear(); inputref.active(false); break; } } infoprintf("%d midi-input-map entries added", count); } /* * One thing about MIDI clock values. If a device (e.g. Korg nanoKEY2) * is present in a system when Seq66 is exited, it will be saved in the * [midi-clock] list. When unplugged, it will be read here at startup, * but won't be shown. The next exit finds it removed from this list. * Also, we want to pre-allocate the number of clock entries needed, and * then use the buss number to populate the list of clocks, in the odd * event that the user changed the bus-order of the entries. */ tag = "[midi-clock]"; ok = line_after(file, tag); if (ok) { int outbuses = 0; int count = std::sscanf(scanline(), "%d", &outbuses); ok = count > 0 && is_good_busscount(outbuses); } if (ok) { rc_ref().clocks().clear(); while (next_data_line(file)) { ok = rc_ref().clocks().add_list_line(line()); if (! ok) return make_error_message(tag, "out-bus data line error"); } } else { /* * If this is zero, we need to fake it to have 1 buss with a 0 clock, * rather than make the poor user figure out how to fix it. * * return make_error_message("midi-clock"); * * And let's use the new e_clock::disabled code instead of * e_clock::none. LATER? */ rc_ref().clocks().add(0, false, e_clock::none, "Bad clock count"); } /* * Check for an optional output port map section. */ tag = "[midi-clock-map]"; if (line_after(file, tag)) { clockslist & clocsref = output_port_map(); int activeflag; int count = 0; clocsref.clear(); (void) std::sscanf(scanline(), "%d", &activeflag); outportmap_active = activeflag != 0; clocsref.active(outportmap_active); while (next_data_line(file)) { if (clocsref.add_map_line(line())) { ++count; } else { clocsref.clear(); clocsref.active(false); break; } } infoprintf("%d midi-clock-map entries added", count); } else portmaps_present = false; /* both sections must be present */ rc().portmaps_present(portmaps_present); rc().portmaps_active(inportmap_active && outportmap_active); /* * Moved from original location above so that we have the port-mapping * in place for use here. */ tag = "[midi-control-file]"; if (file_version_number() < s_rc_file_version) { (void) version_error_message("rc", file_version_number()); rc_ref().auto_rc_save(true); } active = get_file_status(file, tag, pfname); rc_ref().midi_control_active(active); rc_ref().midi_control_filename(pfname); /* base name */ fullpath = rc_ref().midi_control_filespec(); file_message("Read ctrl", fullpath); int ticks = 64; tag = "[midi-clock-mod-ticks]"; ticks = get_integer(file, tag, "ticks"); rc_ref().set_clock_mod(ticks); /* * Note that record-by-buss supercedes record-by-channel. They cannot both * be true. */ bool recordby = get_boolean(file, tag, "record-by-buss"); rc_ref().record_by_buss(recordby); recordby = get_boolean(file, tag, "record-by-channel"); rc_ref().record_by_channel(recordby); tag = "[midi-file-tweaks]"; pfname = get_variable(file, tag, "running-status-action"); if (pfname.empty()) rc_ref().auto_rc_save(true); else rc_ref().running_status_action(pfname); int track = 0; tag = "[midi-meta-events]"; track = get_integer(file, tag, "tempo-track"); rc_ref().tempo_track_number(track); /* MIDI file can override this */ tag = "[reveal-ports]"; /* * If this flag is already raised, it was raised on the command line, * and we don't want to change it. An ugly special case. */ if (! rc_ref().reveal_ports()) { bool flag = get_boolean(file, tag, "show-system-ports"); rc_ref().reveal_ports(bool(flag)); } /* * New for issue #97, add a configurable metronome function. */ tag = "[metronome]"; if (line_after(file, tag)) { int temp = get_integer(file, tag, "output-buss"); rc().metro_settings().buss(temp); temp = get_integer(file, tag, "output-channel"); rc().metro_settings().channel(temp); temp = get_integer(file, tag, "beats-per-bar"); rc().metro_settings().beats_per_bar(temp); temp = get_integer(file, tag, "beat-width"); rc().metro_settings().beat_width(temp); temp = get_integer(file, tag, "main-patch"); rc().metro_settings().main_patch(temp); temp = get_integer(file, tag, "main-note"); rc().metro_settings().main_note(temp); temp = get_integer(file, tag, "main-note-velocity"); rc().metro_settings().main_note_velocity(temp); double v = get_float(file, tag, "main-note-length"); rc().metro_settings().main_note_fraction(v); temp = get_integer(file, tag, "sub-patch"); rc().metro_settings().sub_patch(temp); temp = get_integer(file, tag, "sub-note"); rc().metro_settings().sub_note(temp); temp = get_integer(file, tag, "sub-note-velocity"); rc().metro_settings().sub_note_velocity(temp); v = get_float(file, tag, "sub-note-length"); rc().metro_settings().sub_note_fraction(v); bool countin = get_boolean(file, tag, "count-in-active"); rc().metro_settings().count_in_active(countin); temp = get_integer(file, tag, "count-in-measures"); rc().metro_settings().count_in_measures(temp); countin = get_boolean(file, tag, "count-in-recording"); rc().metro_settings().count_in_recording(countin); temp = get_integer(file, tag, "recording-buss"); rc().metro_settings().recording_buss(temp); temp = get_integer(file, tag, "recording-measures"); rc().metro_settings().recording_measures(temp); temp = get_integer(file, tag, "thru-buss"); rc().metro_settings().thru_buss(temp); temp = get_integer(file, tag, "thru-channel"); rc().metro_settings().thru_channel(temp); } tag = "[interaction-method]"; flag = get_boolean(file, tag, "snap-split"); rc_ref().allow_snap_split(flag); flag = get_boolean(file, tag, "double-click-edit"); rc_ref().allow_click_edit(flag); tag = "[last-used-dir]"; if (line_after(file, tag)) { if (! is_missing_string(line())) { std::string ludir = strip_quotes(line()); rc_ref().last_used_dir(ludir, false); /* set w/o modify */ } } else (void) make_error_message(tag, "section missing"); rc_ref().playlist_active(false); /* * [playlist] */ tag = "[playlist]"; active = get_file_status(file, tag, pfname); rc_ref().playlist_active(active); rc_ref().playlist_filename(pfname); /* base name */ pfname = get_variable(file, tag, "base-directory"); if (! is_missing_string(pfname)) { file_message("Playlist MIDI base directory", pfname); rc_ref().midi_base_directory(pfname); } rc_ref().notemap_active(false); tag = "[auto-option-save]"; flag = get_boolean(file, tag, "auto-save-rc"); rc_ref().auto_rc_save(flag); flag = get_boolean(file, tag, "save-old-triggers"); rc_ref().save_old_triggers(flag); flag = get_boolean(file, tag, "save-old-mutes"); rc_ref().save_old_mutes(flag); /* * [recent-files] */ tag = "[recent-files]"; bool gotrf = line_after(file, tag); int recentcount; if (gotrf) { bool fullpaths = get_boolean(file, tag, "full-paths"); bool loadem = ! rc_ref().playlist_active(); if (loadem) loadem = get_boolean(file, tag, "load-most-recent"); recentcount = get_integer(file, tag, "count"); rc_ref().load_most_recent(loadem); rc_ref().full_recent_paths(fullpaths); (void) next_data_line(file); /* skip "load-most-recent" line */ } else (void) make_error_message(tag, "section missing"); if (gotrf) gotrf = line_after(file, tag); /* start over */ if (gotrf) { while (line()[0] != '"') /* find quote */ { gotrf = next_data_line(file); if (! gotrf) break; } if (gotrf && line()[0] == '"') { rc_ref().clear_recent_files(); for (int i = 0; i < recentcount; ++i) { std::string rfilename = strip_quotes(line()); if (rfilename.empty()) { break; } else { if (! rc_ref().append_recent_file(rfilename)) file_error("Cannot read recent file", rfilename); } if (! next_data_line(file)) break; } } } file.close(); /* done parsing the "rc" file */ return true; } /** * Get only the 'usr' file and its active flags from the 'rc' file. This * function supports testing to see if the application should be * daemonized. See cmdlineopts::parse_daemonization() and * userfile::parse_daemonization(). * * \return * Returns true if the file is active. */ bool rcfile::get_usr_file () { std::ifstream file(name(), std::ios::in | std::ios::ate); bool result = set_up_ifstream(file); if (result) { std::string tag = "[usr-file]"; std::string usrfilename; result = get_file_status(file, tag, usrfilename); rc_ref().user_file_active(result); rc_ref().user_filename(usrfilename); } return result; } /** * This options-writing function is just about as complex as the * options-reading function. * * \return * Returns true if the write operations all succeeded. */ bool rcfile::write () { std::ofstream file(name(), std::ios::out | std::ios::trunc); bool ok = file.is_open(); if (ok) { file_message("Write rc", name()); } else { file_error("Write open fail", name()); return false; } /* * Initial comments and MIDI control section. */ std::string noname; write_date(file, "main ('rc')"); file << "# This file holds the main configuration for Seq66. It diverges greatly from\n" "# the of the seq24rc configuration file.\n" "#\n" "# 'version' is set by Seq66; it is used to detect older configuration files,\n" "# which are upgraded to the new version when saved.\n" "#\n" "# 'quiet' suppresses start-up error messages. Useful when they are not\n" "# relevant. There's no --quiet command-line option yet. It's NOT the opposite\n" "# of 'verbose'.\n" "#\n" "# 'verbose' is temporary, same as --verbose; it's set to false at exit.\n" "#\n" "# 'sets-mode' affects set muting when moving to the next set. 'normal' leaves\n" "# the next set muted. 'auto-arm' unmutes it. 'additive' keeps the previous set\n" "# armed when moving to the next set. 'all-sets' arms all sets at once.\n" "#\n" "# 'port-naming': 'short', 'pair', or 'long'. If 'short', the device name is\n" "# shown; but if generic, the client name is added for clarity. If 'pair',\n" "# the client:port number is prepended. If 'long', the full set of name items\n" "# is shown. If port-mapping is active (now the default), this does not apply.\n" "#\n" "# 'init-disabled-ports' does not yet work. It tries live toggle of port state.\n" "#\n" "# 'priority' greater than 0 is meant to increase the priority of the I/O\n" "# threads. It needs Seq66 to run as root, or be installed as setuid 0.\n" ; write_seq66_header(file, "rc", version()); write_boolean(file, "quiet", rc_ref().quiet()); write_boolean(file, "verbose", rc_ref().verbose()); write_string(file, "sets-mode", rc_ref().sets_mode_string()); write_string(file, "port-naming", rc_ref().port_naming_string()); write_boolean(file, "init-disabled-ports", rc_ref().init_disabled_ports()); write_integer(file, "priority", rc_ref().thread_priority()); /* * [comments] */ write_comment(file, rc_ref().comments_block().text()); /* * [midi-control-file] * * Work related to issue #89: We now write the 'ctrl' file in * smanager::save_session(). * * std::string mcfname = rc_ref().midi_control_filespec(); * ok = write_midi_control_file(mcfname, rc_ref()); * * In all that follow, we no longer write the full-path. We write only * the base file-name (e.g. "qseq66.ctrl"), and enforce that all * configuration files being store in the "--home" directory. */ file << "\n" "# Provides a flag and file-name for MIDI-control I/O settings. '\"\"' means\n" "# no 'ctrl' file. If none, default keystrokes are used, with no MIDI control.\n" "# Note that all configuration files are stored in the \"home\" configuration\n" "# directory; any paths in the file-names are stripped.\n" ; write_file_status ( file, "[midi-control-file]", rc_ref().midi_control_filename(), rc_ref().midi_control_active() ); file << "\n" "# Provides a flag and file-name for mute-groups settings. '\"\"' means no\n" "# 'mutes' file. If none, there are no mute groups, unless the MIDI file\n" "# contains some.\n" ; write_file_status ( file, "[mute-group-file]", rc_ref().mute_group_filename(), rc_ref().mute_group_file_active() ); std::string usrname = rc_ref().user_filename(); file << "\n" "# Provides a flag and file-name for 'user' settings. '\"\"' means no 'usr'\n" "# file. If none, there are no special user settings. Using no 'usr' file\n" "# should be considered experimental.\n" ; write_file_status ( file, "[usr-file]", usrname, rc_ref().user_file_active() ); file << "\n" "# Provides a flag and play-list file. If no list, use '\"\"' and set active\n" "# = false. Use the extension '.playlist'. Even if not active, the play-list\n" "# file is read. 'base-directory' sets the directory holding all MIDI files\n" "# in all play-lists, useful when copying play-lists/tunes from one place to\n" "# another; it preserves sub-directories (e.g. in creating an NSM session).\n" ; std::string plname = rc_ref().playlist_filename(); std::string mbasedir = rc_ref().midi_base_directory(); /* * The play-list file-name has the "home" directory prepended as a * side-effect of smanager::open_playlist() & performer::open_playlist(). * We need to fix that, but here we could just strip the path. * * plname = filename_base(plname); */ write_file_status(file, "[playlist]", plname, rc_ref().playlist_active()); write_string(file, "base-directory", mbasedir, true); file << "\n" "# Provides a flag and file-name for note-maps. '\"\"' means no 'drums' file.\n" "# This file is used when the user invokes the note-conversion operation in\n" "# the pattern editor of a transposable pattern. Make the pattern temporarily\n" "# transposable to allow this operation.\n" ; std::string drumfile = rc_ref().notemap_filename(); bool drumactive = rc_ref().notemap_active(); write_file_status(file, "[note-mapper]", drumfile, drumactive); /* * New section for patches file. */ file << "\n" "# Provides a flag and file-name to provide a list of patches for legacy\n" "# non-GM-compliant devices.\n" ; write_file_status ( file, "[patches-file]", rc_ref().patches_filename(), rc_ref().patches_active() ); /* * New section for palette file. */ file << "\n" "# Provides a flag and file-name to allow modifying the palette using the file\n" "# specified. Use '\"\"' to indicate no 'palette' file. If none or not active,\n" "# the internal palette is used.\n" ; write_file_status ( file, "[palette-file]", rc_ref().palette_filename(), rc_ref().palette_active() ); /* * New section for style-sheet file, moved from the 'usr' file. * * Might want to enforce having the style-sheet in the "home" directory. */ file << "\n" "# If specified, a style-sheet (e.g. 'qseq66.qss') is applied at startup.\n" "# This file must be located in Seq66's \"home\" directory. Copy if needed.\n" "# Note that style-sheet specification has been removed from the 'usr' file.\n" ; write_file_status ( file, "[style-sheet-file]", rc_ref().style_sheet_filename(), rc_ref().style_sheet_active() ); /* * New section for MIDI meta events. */ file << "\n" "# Defines features of MIDI meta-event handling. Tempo events are in the first\n" "# track (pattern 0), but one can use them elsewhere. It changes where tempo\n" "# events are recorded. The default is 0, the maximum is 1023. A pattern must\n" "# exist at this number.\n" "\n[midi-meta-events]\n\n" ; write_integer(file, "tempo-track", rc_ref().tempo_track_number()); /* * Manual ports */ file << "\n" "# Set to true to create virtual ALSA/JACK I/O ports and not auto-connect to\n" "# other clients. Allows up to 48 output or input ports (defaults to 8 and 4).\n" "# If true, it disables port-mapping. Keep it false to auto-connect Seq66 to\n" "# real ALSA/JACK MIDI ports and preserve port-mapping. Set 'auto-enable' to\n" "# enable all virtual ports automatically.\n" "\n[manual-ports]\n\n" ; write_boolean(file, "virtual-ports", rc_ref().manual_ports()); write_boolean(file, "auto-enable", rc_ref().manual_auto_enable()); write_integer(file, "output-port-count", rc_ref().manual_port_count()); write_integer(file, "input-port-count", rc_ref().manual_in_port_count()); int inbuses = bussbyte(rc_ref().inputs().count()); file << "\n" "# These MIDI ports are for input and control. JACK's view: these are\n" "# 'playback' devices. The first number is the bus, the second number is the\n" "# input status, disabled (0) or enabled (1). The item in quotes is the full\n" "# input bus name. The type of port depends on the 'virtual-ports' setting.\n" "\n[midi-input]\n\n" << std::setw(2) << int(inbuses) << " # number of MIDI input (or control) buses\n\n" ; std::string listlines = rc_ref().inputs().io_list_lines(); file << listlines; const inputslist & inpsref = input_port_map(); if (! inpsref.empty()) { bool active = inpsref.active(); std::string maplines = input_port_map_list(); std::string activestring = active ? " 1" : " 0"; std::string mapstatus = "map is "; if (! active) mapstatus += "not "; mapstatus += "active"; file << "\n" "# This table is similar to the [midi-clock-map] section, but the values are\n" "# different. -2 = unavailable; 0 = not inputing; 1 = enabled for inputing.\n" "\n" "[midi-input-map]\n" "\n" << activestring << " # " << mapstatus << "\n\n" << maplines ; } /* * Bus mute/unmute data. At this point, we can use the master_bus() * accessor, even if a pointer dereference, because it was created at * application start-up, and here we are at application close-down. * * However, since we get these from the 'rc' file via the * rc_refs().clocks() container accessor, we should depend on the * performer class getting them, and then passing them to rcsettings via * the performer::put_settings() function. */ bussbyte outbuses = bussbyte(rc_ref().clocks().count()); file << "\n" "# These MIDI ports are for output, playback, and display. JACK's view: these\n" "# are 'capture' devices. The first line shows the count of output ports.\n" "# Each line shows the bus number and clock status of that bus:\n" "#\n" "# -2 = Output port is not present on the system (unavailable).\n" "# -1 = Output port is disabled.\n" "# 0 = MIDI Clock is off. Output port is enabled.\n" "# 1 = MIDI Clock on; Song Position and MIDI Continue are sent.\n" "# 2 = MIDI Clock Modulo.\n" "#\n" "# With Clock Modulo, clocking doesn't begin until song position reaches the\n" "# start-modulo value [midi-clock-mod-ticks]. Ports that are unavailable\n" "# (because another port, e.g. Windows MIDI Mapper, has exclusive access to\n" "# the device) are displayed ghosted. The type of port depends on the\n" "# 'virtual-ports' setting.\n" "\n[midi-clock]\n\n" << std::setw(2) << int(outbuses) << " # number of MIDI clocks (output/display buses)\n\n" ; listlines = rc_ref().clocks().io_list_lines(); file << listlines; const clockslist & outsref = output_port_map(); if (! outsref.empty()) { bool active = outsref.active(); std::string maplines = output_port_map_list(); std::string activestring = active ? " 1" : " 0"; std::string mapstatus = "map is "; if (! active) mapstatus += "not "; mapstatus += "active"; file << "\n" "# Patterns use bus numbers, not names. This table provides virtual bus numbers\n" "# that match real devices and can be stored in each pattern. The bus number\n" "# is looked up in this table, the port nick-name is retrieved, and the true\n" "# bus number is obtained and used. Thus, if the ports change order in the MIDI\n" "# system, the pattern will use the proper port. The short nick-names work in\n" "# ALSA or JACK (a2jmidid bridge).\n" "\n" "[midi-clock-map]\n" "\n" << activestring << " # " << mapstatus << "\n\n" << maplines ; } /* * MIDI clock modulo value, and filter by channel, new option as of * 2016-08-20. */ file << "\n" "# 'ticks' provides the Song Position (16th notes) at which clocking begins if\n" "# the bus is set to MIDI Clock Mod setting. 'record-by-buss' routes MIDI\n" "# events to the first pattern set to that input buss. 'record-by-channel',\n" "# if the buss is not set, routes events to patterns with an output channel\n" "# matching the MIDI event channel. Option adopted from the Seq32 project.\n" "\n[midi-clock-mod-ticks]\n\n" ; write_integer(file, "ticks", midibus::get_clock_mod()); write_boolean(file, "record-by-buss", rc_ref().record_by_buss()); write_boolean(file, "record-by-channel", rc_ref().record_by_channel()); /* * Running-status action */ file << "\n" "# This section defines tweaks to the reading or writing of MIDI files.\n" "# Indicates how to handle MIDI files with incorrect running status. Default\n" "# is 'recover', which tries to recover the running status when a data byte\n" "# is encountered; 'skip' ignores the rest of the bytes in the track;\n" "# 'proceed' keeps going; 'abort' just exits the parsing, which is the old\n" "# and undesirable behavior. Try each option with the 'trilogy.mid' file.\n" "\n[midi-file-tweaks]\n\n" ; write_string ( file, "running-status-action", rc_ref().running_status_action_name() ); /* * Reveal ports */ file << "\n" "# Set to true to have Seq66 ignore port names defined in the 'usr' file. Use\n" "# this option to to see the system ports as detected by ALSA/JACK.\n" "\n[reveal-ports]\n\n" ; write_boolean(file, "show-system-ports", rc_ref().reveal_ports()); /* * Metronome */ file << "\n" "# This section sets up a metronome that can be activated from the main live\n" "# grid. It consists of a 'main' note on the first beat, then 'sub' notes on\n" "# the rest of the beats. The patch/program, note value, velocity, and\n" "# fraction length relative to the beat width (can be specified. The length\n" "# ranges from about 0.125 (one-eight) to 1.0 (the same length as the beat\n" "# width) to 2.0).\n" "\n[metronome]\n\n" ; write_integer(file, "output-buss", int(rc().metro_settings().buss())); write_integer(file, "output-channel", int(rc().metro_settings().channel())); write_integer ( file, "beats-per-bar", int(rc().metro_settings().beats_per_bar()) ); write_integer ( file, "beat-width", int(rc().metro_settings().beat_width()) ); write_integer(file, "main-patch", int(rc().metro_settings().main_patch())); write_integer(file, "main-note", int(rc().metro_settings().main_note())); write_integer ( file, "main-note-velocity", int(rc().metro_settings().main_note_velocity()) ); write_float ( file, "main-note-length", rc().metro_settings().main_note_length() ); write_integer(file, "sub-patch", int(rc().metro_settings().sub_patch())); write_integer(file, "sub-note", int(rc().metro_settings().sub_note())); write_integer ( file, "sub-note-velocity", int(rc().metro_settings().sub_note_velocity()) ); write_float ( file, "sub-note-length", rc().metro_settings().sub_note_length() ); write_boolean ( file, "count-in-active", rc().metro_settings().count_in_active() ); write_integer ( file, "count-in-measures", int(rc().metro_settings().count_in_measures()) ); write_boolean ( file, "count-in-recording", rc().metro_settings().count_in_recording() ); write_integer ( file, "recording-buss", int(rc().metro_settings().recording_buss()) ); write_integer ( file, "recording-measures", int(rc().metro_settings().recording_measures()) ); write_integer(file, "thru-buss", int(rc().metro_settings().thru_buss())); write_integer ( file, "thru-channel", int(rc().metro_settings().thru_channel()) ); /* * Interaction-method */ file << "\n" "# Sets mouse usage for drawing/editing patterns. 'Fruity' mode is NOT in\n" "# Seq66. Other settings are available: 'snap-split' enables splitting\n" "# song-editor triggers at a snap position instead of in its middle. Split is\n" "# done by a middle-click or ctrl-left click. 'double-click-edit' allows double-\n" "# click on a slot to open it in a pattern editor. Set it to false if\n" "# you don't like how it works.\n" "\n[interaction-method]\n\n" ; write_boolean(file, "snap-split", rc_ref().allow_snap_split()); write_boolean(file, "double-click-edit", rc_ref().allow_click_edit()); #if defined SEQ66_USE_FRUITY_CODE /* will not be supported in seq66 */ int x = 0; while (c_interaction_method_names[x] && c_interaction_method_descs[x]) { file << "# " << x << " - '" << c_interaction_method_names[x] << "' (" << c_interaction_method_descs[x] << ")\n" ; ++x; } file << "\n" << rc_ref().interaction_method() << " # interaction_method\n\n" ; #endif // SEQ66_USE_FRUITY_CODE /* * JACK Settings */ std::string jacktransporttype = "none"; if (rc_ref().with_jack_master()) jacktransporttype = "master"; else if (rc_ref().with_jack_transport()) jacktransporttype = "slave"; else if (rc_ref().with_jack_master_cond()) jacktransporttype = "conditional"; file << "\n" "# transport-type enables synchronizing with JACK Transport. Values:\n" "# none: No JACK Transport in use.\n" "# slave: Use JACK Transport as Slave.\n" "# master: Attempt to serve as JACK Transport Master.\n" "# conditional: Serve as JACK master if no JACK master exists.\n" "#\n" "# song-start-mode playback is either Live, Song, or Auto:\n" "# live: Muting & unmuting of loops in the main window.\n" "# song: Playback uses Song (performance) editor data.\n" "# auto: If the loaded tune has song triggers, use Song mode.\n" "#\n" "# jack-midi sets/unsets JACK MIDI, separate from JACK transport.\n" "# jack-auto-connect sets connecting to JACK ports found. Default = true; use\n" "# false to have a session manager make the connections.\n" "# jack-use-offset attempts to calculate timestamp offsets to improve accuracy\n" "# at high-buffer sizes. Still a work in progress.\n" "# jack-buffer-size allows for changing the frame-count, a power of 2.\n" "\n[jack-transport]\n\n" << "transport-type = " << jacktransporttype << "\n" << "song-start-mode = " << rc_ref().song_mode_string() << "\n" ; write_boolean(file, "jack-midi", rc_ref().with_jack_midi()); write_boolean(file, "jack-auto-connect", rc_ref().jack_auto_connect()); write_boolean(file, "jack-use-offset", rc_ref().jack_use_offset()); write_integer(file, "jack-buffer-size", rc_ref().jack_buffer_size()); file << "\n" "# 'auto-save-rc' sets automatic saving of the 'rc' and other files. If set\n" "# (true if changes were made in Preferences), settings are saved.\n" "#\n" "# 'old-triggers' saves triggers in a format compatible with Seq24. Otherwise,\n" "# triggers are saved with an additional 'transpose' setting. The old-mutes\n" "# value, if true, saves mute-groups as long values (!) instead of bytes.\n" "\n[auto-option-save]\n\n" ; write_boolean(file, "auto-save-rc", rc_ref().auto_rc_save()); write_boolean(file, "save-old-triggers", rc_ref().save_old_triggers()); write_boolean(file, "save-old-mutes", rc_ref().save_old_mutes()); std::string lud = rc_ref().last_used_dir(); file << "\n" "# Specifies the last directory used for save/open of MIDI files.\n" "\n[last-used-dir]\n\n" ; write_string(file, noname, rc_ref().last_used_dir(), true); /* * Feature from Kepler34. */ int count = rc_ref().recent_file_count(); file << "\n" "# The most recently-loaded MIDI files. 'full-paths' means to show the full\n" "# file-path in the menu. The most recent file (top of list) can be loaded\n" "# via 'load-most-recent' at startup.\n" "\n[recent-files]\n\n" ; write_boolean(file, "full-paths", rc_ref().full_recent_paths()); write_boolean(file, "load-most-recent", rc_ref().load_most_recent()); write_integer(file, "count", count); file << "\n"; if (count > 0) { for (int i = 0; i < count; ++i) { std::string rfilespec = rc_ref().recent_file(i, false); write_string(file, noname, rfilespec, true); } } write_seq66_footer(file); file.close(); return true; } /* * Free functions. * * These new versions of the functions defined in the configfile module * work a bit differently. They use the actual configuration file * specifications in rcsettings::config_files() for the source names, * extract the base name, and, for copying, prepend the destination * directory. */ bool write_rc_file (const std::string & filebase) { bool result = true; std::string name = file_extension_set(filebase, ".rc"); std::string rcn = rc().config_filespec(name); rcfile options(rcn, rc()); result = options.write(); if (! result) file_error("Write failed", rcn); return result; } #if defined SEQ66_KEEP_RC_FILE_LIST bool delete_configuration (const std::string & path, const std::string & fname) { bool result = ! path.empty() && ! fname.empty(); if (result) { std::string base = filename_base(fname); std::string msg = "Deleting " + base + " from"; file_message(msg, path); for (auto & fname : rc().config_files()) { if (file_exists(fname.second)) { if (! file_delete(fname.second)) result = false; } } } return result; } /** * TODO: * * If importing from seq66tests to .config/seq66, the list of config-files * has the destination names. * * We need to construct a new list, which requires reading the source * files. * * It might be easier just to recursively copy the whole source directory. */ bool copy_configuration ( const std::string & source, /* path only */ const std::string & fname, const std::string & destination /* path only */ ) { bool result = ! source.empty() && ! fname.empty() && ! destination.empty(); if (result) { std::string base = filename_base(fname); std::string destname = filename_concatenate(destination, base); std::string msg = "Copying " + source + base + " to"; file_message(msg, destination); for (auto & srcname : rc().config_files()) { if (file_exists(srcname.second)) { bool ok = file_copy(srcname.second, destname); if (! ok) { result = false; break; } } else result = false; } } return result; } #endif // defined SEQ66_KEEP_RC_FILE_LIST } // namespace seq66 /* * rcfile.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/rcsettings.cpp ================================================ /* * This file is part of seq24/seq66. * * seq24 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. * * seq24 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 seq24; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file rcsettings.cpp * * This module declares/defines just some of the global (gasp!) variables * in this application. * * \library seq66 application * \author Seq24 team; modifications by Chris Ahlstrom * \date 2015-09-22 * \updates 2026-04-17 * \license GNU GPLv2 or above * * Note that this module also sets the legacy global variables, so that * they can be used by modules that have not yet been cleaned up. * * \warning * No more "statistics" support. */ #include /* std::find() */ #include "cfg/settings.hpp" /* seq66::rc(), seq66::usr() */ #include "play/seq.hpp" /* seq66::seq::maximum() */ #include "util/filefunctions.hpp" /* make_directory(), etc. */ #include "util/strfunctions.hpp" /* seq66::strncompare() */ #if defined SEQ66_KEEP_RC_FILE_LIST #include /* std::make_pair() */ #endif namespace seq66 { /** * Default constructor. */ rcsettings::rcsettings () : basesettings (), #if defined SEQ66_KEEP_RC_FILE_LIST m_config_files (), /* vector of file-names */ #endif m_clocks (), /* vector wrapper class */ m_inputs (), /* vector wrapper class */ m_metro_settings (), m_mute_group_save (mutegroups::saving::midi), m_keycontainer ("rc"), m_drop_empty_in_controls (false), /* the legacy value */ m_midi_control_buss (null_buss()), m_midi_control_in ("rc"), m_midi_control_out ("rc"), m_clock_mod (64), m_verbose (false), m_quiet (false), m_investigate (false), m_session_tag (), m_first_run_in_progress (false), m_save_list (), /* std::map */ m_save_old_triggers (false), m_save_old_mutes (false), m_allow_mod4_mode (false), m_allow_snap_split (false), m_allow_click_edit (true), m_show_midi (false), m_priority (false), m_thread_priority (0), /* c_thread_priority */ m_pass_sysex (false), m_with_jack_transport (false), m_with_jack_master (false), m_with_jack_master_cond (false), #if defined SEQ66_RTMIDI_SUPPORT m_with_jack_midi (true), /* tentative */ #else m_with_jack_midi (false), #endif m_with_alsa_midi (false), /* unless ALSA gets selected */ m_jack_auto_connect (true), m_jack_use_offset (true), m_jack_buffer_size (0), m_song_start_mode (sequence::playback::automatic), m_song_start_is_auto (true), m_record_by_buss (false), m_record_by_channel (false), m_manual_ports (false), m_manual_auto_enable (false), m_manual_port_count (c_output_buss_default), m_manual_in_port_count (c_input_buss_default), m_reveal_ports (false), m_init_disabled_ports (false), m_print_keys (false), m_interaction_method (interaction::seq24), m_sets_mode (setsmode::normal), m_port_naming (portname::brief), m_midi_filename (), m_midi_filepath (), m_running_status_action (rsaction::recover), m_jack_session_uuid (), m_jack_session_active (false), m_last_used_dir (), /* double_quotes() */ m_session_directory (), m_config_subdirectory_set (false), m_config_subdirectory (), m_config_filename (seq_config_name()), /* updated in body */ m_full_config_directory (), m_user_file_active (true), /* keep it true */ m_user_filename (seq_config_name()), /* updated in body */ m_midi_control_active (false), m_midi_control_filename (seq_config_name()), /* updated in body */ m_mute_group_file_active (false), m_mute_group_filename (seq_config_name()), /* updated in body */ m_playlist_active (false), m_playlist_filename (seq_config_name()), /* updated in body */ m_playlist_midi_base (), m_notemap_active (false), m_notemap_filename (seq_config_name()), /* updated in body */ m_patches_active (false), m_patches_filename (seq_config_name()), /* updated in body */ m_palette_active (false), m_palette_filename (seq_config_name()), /* updated in body */ m_style_sheet_active (false), m_style_sheet_filename (seq_config_name()), m_application_name (seq_app_name()), m_tempo_track_number (0), m_recent_files (), m_load_most_recent (true), m_full_recent_paths (false), m_portmaps_present (false), m_portmaps_active (false) { m_session_directory = user_session(seq_config_dir_name()); m_midi_control_in.inactive_allowed(true); m_config_filename += ".rc"; m_user_filename += ".usr"; m_midi_control_filename += ".ctrl"; m_mute_group_filename += ".mutes"; m_playlist_filename += ".playlist"; m_notemap_filename += ".drums"; m_patches_filename += ".patches"; m_palette_filename += ".palette"; m_style_sheet_filename += ".qss"; set_config_files(seq_config_name()); /* ca 2023-05-11 */ } /** * Sets the default values. * * Not altered: * * m_clocks.clear(); * m_inputs.clear(); * m_mute_groups.clear(); * m_keycontainer.clear(); // what is best? * m_midi_control_in.clear(); // what is best? * m_midi_control_out.clear(); // does not exist */ void rcsettings::set_defaults () { /* * basesettings * m_clocks * m_inputs * m_keycontainer * m_midi_control_in * m_midi_control_out */ m_metro_settings.set_defaults(); m_mute_group_save = mutegroups::saving::midi; m_drop_empty_in_controls = false; m_midi_control_buss = null_buss(); m_clock_mod = 64; m_verbose = false; m_quiet = false; m_investigate = false; m_session_tag.clear(); m_save_old_triggers = false; m_save_old_mutes = false; m_allow_mod4_mode = false; m_allow_snap_split = false; m_allow_click_edit = true; m_show_midi = false; m_priority = false; m_thread_priority = 0; /* c_thread_priority */ m_pass_sysex = false; m_with_jack_transport = false; m_with_jack_master = false; m_with_jack_master_cond = false; #if defined SEQ66_RTMIDI_SUPPORT m_with_jack_midi = true; #else m_with_jack_midi = false; #endif m_with_alsa_midi = false; /* unless ALSA gets selected */ m_jack_auto_connect = true; m_jack_use_offset = true; m_jack_buffer_size = 0; m_song_start_mode = sequence::playback::automatic; m_song_start_is_auto = true; m_record_by_buss = false; m_record_by_channel = false; m_manual_ports = false; m_manual_auto_enable = false; m_manual_port_count = c_output_buss_default; m_manual_in_port_count = c_input_buss_default; m_reveal_ports = false; m_init_disabled_ports = false; m_print_keys = false; m_interaction_method = interaction::seq24; m_sets_mode = setsmode::normal; m_port_naming = portname::brief; m_midi_filename.clear(); m_midi_filepath.clear(); m_running_status_action = rsaction::recover; m_jack_session_uuid.clear(); m_jack_session_active = false; m_last_used_dir.clear(); /* double_quotes() */ m_session_directory = user_session(seq_config_dir_name()); m_config_subdirectory_set = false; m_config_subdirectory.clear(); m_config_filename = seq_config_name(); m_full_config_directory.clear(); m_user_file_active = true; m_user_filename = seq_config_name(); m_midi_control_active = false; m_midi_control_filename = seq_config_name(); m_mute_group_file_active = false; m_mute_group_filename = seq_config_name(); m_playlist_active = false; m_playlist_filename = seq_config_name(); m_playlist_midi_base.clear(); m_notemap_active = false; m_notemap_filename = seq_config_name(); m_patches_active = false; m_patches_filename = seq_config_name(); m_palette_active = false; m_palette_filename = seq_config_name(); m_style_sheet_active = false; m_style_sheet_filename = seq_config_name(); m_config_filename += ".rc"; m_user_filename += ".usr"; m_midi_control_filename += ".ctrl"; m_mute_group_filename += ".mutes"; m_playlist_filename += ".playlist"; m_notemap_filename += ".drums"; m_patches_filename += ".patches"; m_palette_filename += ".palette"; m_style_sheet_filename += ".qss"; /* * const: m_application_name = seq_app_name(); */ m_tempo_track_number = 0; m_recent_files.clear(); m_load_most_recent = true; m_full_recent_paths = false; m_portmaps_present = false; m_portmaps_active = false; set_config_files(seq_config_name()); set_save_list(false); } /** * We no longer save the 'rc' every damn time. */ void rcsettings::set_save_list (bool state) { m_save_list.clear(); m_save_list.add("rc", state); /* can be edited in UI */ m_save_list.add("usr", state); /* can be edited in UI */ m_save_list.add("mutes", state); /* can be edited in UI */ m_save_list.add("playlist", state); /* can be edited in UI */ /* * The following are saved only after the first run. Thereafter, they * are managed by the user. Seq66 offers no way to edit these * files, nor the 'palette' file. The palette in place can be saved * via a special button in Edit / Preferences / Session. */ m_save_list.add("palette", state); /* can save via button */ m_save_list.add("drums", state); m_save_list.add("ctrl", state); m_save_list.add("qss", state); } /** * This function is useful when importing a session into NSM. It prevents * any saves when exiting the application. * * What about the "session" file? */ void rcsettings::disable_save_list () { m_save_list.clear(); m_save_list.add("rc", false); m_save_list.add("usr", false); m_save_list.add("mutes", false); m_save_list.add("playlist", false); m_save_list.add("palette", false); m_save_list.add("drums", false); m_save_list.add("ctrl", false); m_save_list.add("qss", false); } void rcsettings::set_save (const std::string & name, bool value) { bool status = m_save_list.get(name); if (status != value) m_save_list.set("rc", true); /* 'rc' holds all the status */ m_save_list.set(name, value); } bool rcsettings::auto_options_save () const { return ( auto_rc_save() || auto_usr_save() || is_modified() || usr().is_modified() ); } void rcsettings::verbose (bool flag) { m_verbose = flag; set_verbose(flag); /* from the basic_macros module */ } void rcsettings::investigate (bool flag) { m_investigate = flag; set_investigate(flag); /* from the basic_macros module */ } /** * After importing a playlist, call this function to make it permanent (but * will still need to reload the sesson). */ void rcsettings::set_imported_playlist ( const std::string & sourcepath, const std::string & midipath ) { playlist_active(true); playlist_filename(filename_base(sourcepath)); midi_base_directory(midipath); auto_playlist_save(true); auto_rc_save(true); } void rcsettings::auto_rc_save (bool flag) { m_save_list.set("rc", flag); if (flag) modify(); } void rcsettings::set_jack_transport (const std::string & value) { if (value == "slave") { with_jack_transport(true); } else if (value == "master") { with_jack_transport(true); with_jack_master(true); } else if (value == "conditional") { with_jack_transport(true); with_jack_master_cond(true); } else { with_jack_transport(false); with_jack_master(false); with_jack_master_cond(false); } } /** * Song-start mode. Was boolean, but now can be set to a value that * determines the mode based on the file being loaded having triggers, or * not. This should be used only by the rcfile class. */ std::string rcsettings::song_mode_string () const { std::string result; switch (m_song_start_mode) { case sequence::playback::live: result = "live"; break; case sequence::playback::song: result = "song"; break; case sequence::playback::automatic: result = "auto"; break; default: result = "unknown"; break; } if (m_song_start_is_auto) /* "auto" read from 'rc'? */ result = "auto"; return result; } void rcsettings::song_start_mode_by_string (const std::string & s) { if (s == "song" || s == "true") { m_song_start_mode = sequence::playback::song; m_song_start_is_auto = false; } else if (s == "live" || s == "false") { m_song_start_mode = sequence::playback::live; m_song_start_is_auto = false; } else { m_song_start_mode = sequence::playback::automatic; m_song_start_is_auto = true; } } /** * The record-by settings are loaded from the 'rc' file at startup. * See sequence_lookup_support() as well. */ void rcsettings::record_by_buss (bool flag) { m_record_by_buss = flag; if (flag) m_record_by_channel = false; } void rcsettings::record_by_channel (bool flag) { if (record_by_buss()) m_record_by_channel = false; else m_record_by_channel = flag; } /** * Holds the client name for the application. This is much like the * application name, but in the future will be a configuration option. * For now it is just the value returned by the seq_client_name() * function. However, note that, under session management, we replace it * with the NSM "client_id" value. */ const std::string & rcsettings::app_client_name () const { return seq_client_name(); } void rcsettings::app_client_name (const std::string & n) const { set_client_name(n); } std::string rcsettings::default_session_path () const { std::string result = user_home(); if (is_empty_string(result)) // if (result.empty()) { result += session_directory(); /* seq66 directory */ } else { if (name_has_root_path(session_directory())) result = session_directory(); /* seq66 directory */ else result = pathname_concatenate(result, session_directory()); } return result; } /** * Provides the directory for the configuration file, and also creates the * directory if necessary. * * The home directory is (in Linux) "/home/username/.config/seq66", and * the configuration file is "qseq66.rc". * * This function should also adapt to Windows conventions automatically. * No, it does not. But all we have to do is replace Window's HOMEPATH with * its LOCALAPPDATA value. * * \return * Returns the selected home configuration directory. If it does not * exist, or could not be created, then an empty string is returned. */ std::string rcsettings::home_config_directory () const { if (m_full_config_directory.empty()) { std::string home = default_session_path(); std::string result = home; if (home.empty()) { std::string temp = "Cannot find HOME!"; result = set_error_message(temp); } else { if (m_config_subdirectory_set) result = pathname_concatenate(result, m_config_subdirectory); bool ok = make_directory_path(result); if (ok) { result = normalize_path(result); m_full_config_directory = result; } else { file_error("Create fail", result); result.clear(); } } return result; } else return normalize_path(m_full_config_directory); } /** * Checks to see if the provided string matches the home configuration * directory name, up to the length of that directory name. * * \param name * Provides the name to be checked. * * \return * Returns true if the name matches the home directory path. */ bool rcsettings::has_home_config_path (const std::string & name) { return strncompare(name, home_config_directory()); } /** * Conditionally trims a file-specification of its home-directory path. This * function is meant to be used for more flexible specification of the * specified 'ctrl', 'mutes', and 'playlist' files. If there is no * directory, then the file is in the current configuration directory. The * same is true if the home directory is present, but we don't need it. If * there is another directory, whether relative or not, we need to keep it. */ std::string rcsettings::trim_home_directory (const std::string & filepath) { if (has_home_config_path(filepath)) { std::string path; std::string result; (void) filename_split(filepath, path, result); return result; } else return filepath; } /** * Moved here from cmdlineopts. * * If no "rc" file exists, then, of course, we will create them upon * exit. But we also want to force the new style of output, where the * key/MIDI controls and the mute-groups are in separate files ending * with the extensions "ctrl" and "mutes", respectively. Also, the * mute-groups container is populated with zero values. Finally, the * user may have specified an altername configuration-file name on * the command-line (e.g. "myseq66" versus the default, "qseq66", * which is the application name. * * std::string cfgname = rc().application_name(); */ void rcsettings::create_config_names (const std::string & base) { std::string cfgname = base.empty() ? config_filename() : base ; cfgname = filename_base(cfgname, true); /* strip extension */ std::string cc = file_extension_set(cfgname, ".rc"); std::string uf = file_extension_set(cfgname, ".usr"); std::string cf = file_extension_set(cfgname, ".ctrl"); std::string mf = file_extension_set(cfgname, ".mutes"); std::string pl = file_extension_set(cfgname, ".playlist"); std::string nm = file_extension_set(cfgname, ".drums"); std::string pa = file_extension_set(cfgname, ".palette"); std::string af = cfgname + "rc,ctrl,midi,mutes,drums,playlist,palette"; config_filename(cc); user_filename(uf); midi_control_filename(cf); mute_group_filename(mf); playlist_filename(pl); notemap_filename(nm); palette_filename(pa); file_message("Configuration files", af); } /** * We need a way to handle the following configuration-file paths: * * - "base.ext": Prepend the HOME directory. * - "subdirectory/base.ext": Prepend the HOME directory * - "/path/.../base.ext": Use the path as is. * - "C:/path/.../base.ext": Use the path as is. * * Compare to make_config_filespec(), used only by rcfile and this class. * That one treats the base and extension separately. */ std::string rcsettings::filespec_helper (const std::string & baseext) const { std::string result = baseext; if (! result.empty()) { bool use_as_is = false; if (name_has_path(baseext)) { if (name_has_root_path(baseext)) use_as_is = true; } if (! use_as_is) result = filename_concatenate(home_config_directory(), baseext);; result = normalize_path(result); /* change to UNIX slash */ } return result; } /** * Guarantees that a file-name is of the simple form "basename.ext". * * \param filename * The full file-specification of a file, or the basename, or a basename * without any extension. */ std::string rcsettings::filename_base_fix ( const std::string & filename, const std::string & ext ) const { std::string result = filename_base(filename, true); /* strip the .ext */ result = file_extension_set(result, ext); return result; } /** * Constructs a full path and file specification for the caller. * * \todo * Need to add support for "C:" and Windows backslash, but let's work out * the other problems first. * * \param base * The part of the filename after the path but before the extension. * If it has a path, then a full file-name is assumed. If the path is * relative (the first character is not a slash), then the home * configuration directory is still prepended. * * \param ext * The file extension, including the period (which is optional anyway). * If the \a base parameter has a period, then the extension is ignored. * * \return * Returns the full path/base.ext file specification. If the base name * contains a "/" and a ".", it is returned as is, presumably already valid. * This feature allows the user to use a full-path to the file, and * relative paths. It is not recommended to use relative paths. Maybe we * can make the relative to the configuration directory. */ std::string rcsettings::make_config_filespec ( const std::string & base, const std::string & ext ) const { std::string result = base; /* bugfix 23-07-17 */ auto pos = base.find_last_of("."); if (pos != std::string::npos) /* base.extension */ { pos = result.find_first_of("/"); if (pos != std::string::npos) /* slash in base */ { if (pos > 0) /* not at start */ result = filename_concatenate(home_config_directory(), base); } else result = filename_concatenate(home_config_directory(), base); } else { std::string extension = ext; auto pos = ext.find_first_of("."); bool add_ext_dot = ( pos == std::string::npos || /* ext has no dot */ pos > 0 /* or not at start */ ); if (add_ext_dot) { extension = "."; /* add the dot */ extension += ext; } result = home_config_directory(); result += base; result += extension; } return result; } /** * Constructs the full path and file specification for the "rc" file. * If the file-name includes a path, then that is returned as is. * See filespec_helper(). * * \return * If home_config_directory() returns a non-empty string, then the * normal "rc" configuration file-name is appended to that result, and * returned. Otherwise, an empty string is returned. */ std::string rcsettings::config_filespec () const { return filespec_helper(config_filename()); } /** * Constructs an alternate full path and file specification for the "rc" * file. This function is useful in writing to an alternate "rc" file when a * fatal error occurs. Also note that the configuration file specification * can never be empty or blank (equal to this string: ""). See the * strfunctions module's is_empty_string() function. * * \param altname * Provides the base-name of the alternate file, including the extension. * Examples are "erroneous.rc" or "mydumpfile.log". * * \return * If home_config_directory() returns a non-empty string, the alternate * normal "rc" configuration file-name is appended to that result, and * returned. Otherwise, an empty string is returned. */ std::string rcsettings::config_filespec (const std::string & altname) const { return filespec_helper(altname); } /** * Constructs the full path and file specification for the "user" file. * * \return * If home_config_directory() returns a non-empty string, then the * normal "user" configuration file-name is appended to that result, * and returned. Otherwise, an empty string is returned. */ std::string rcsettings::user_filespec () const { return filespec_helper(user_filename()); } /** * Constructs an alternate full path and file specification for the "usr" * file. This function is useful in writing to an alternate "usr" file when * a fatal error occurs. * * \param altname * Provides the base-name of the alternate file, including the extension. * Example: "erroneous.usr". * * \return * If home_config_directory() returns a non-empty string, alternate * normal "usr" configuration file-name is appended to that result, and * returned. Otherwise, an empty string is returned. */ std::string rcsettings::user_filespec (const std::string & altname) const { return filespec_helper(altname); } /** * Constructs the full path and file specification for the "playlist" file, * if necessary, if the playlist file-name exists. If the playlist file-name * already has a directory as part of its name, then it is used as is. * * \return * Returns either the filename itself, if it already has a directory in * it. Otherwise, home_config_directory() is retrieved, the * "playlist" file-name is appended to that result, and returned. * Otherwise, an empty string is returned. Don't use it! */ std::string rcsettings::playlist_filespec () const { return filespec_helper(playlist_filename()); } /** * Constructs the note-mapper filespec. */ std::string rcsettings::notemap_filespec () const { return filespec_helper(notemap_filename()); } /** * Constructs the patches configuration filespec. */ std::string rcsettings::patches_filespec () const { return filespec_helper(patches_filename()); } /** * Constructs the palette configuration filespec. */ std::string rcsettings::palette_filespec () const { return filespec_helper(palette_filename()); } /** * Constructs the style-sheet filespec. The base name is, however, part of * the 'usr' configuration. If empty, then this function returns and empty * string. */ std::string rcsettings::style_sheet_filespec () const { return filespec_helper(style_sheet_filename()); } /** * Constructs the full path and file specification for a "MIDI control" file. * * \return * Returns either the filename itself, if it already has a directory in * it. Otherwise, home_config_directory() is retrieved, the * "MIDI control" file-name is appended to that result, and returned. * Otherwise, an empty string is returned. Don't use it! */ std::string rcsettings::midi_control_filespec () const { return filespec_helper(midi_control_filename()); } /** * Constructs the full path and file specification for a "mute group" file. * * \return * Returns either the filename itself, if it already has a directory in * it. Otherwise, home_config_directory() is retrieved, the * "mute group" file-name is appended to that result, and returned. * Otherwise, an empty string is returned. Don't use it! */ std::string rcsettings::mute_group_filespec () const { return filespec_helper(mute_group_filename()); } void rcsettings::tempo_track_number (int track) { if (track < 0 || track >= seq::maximum()) track = 0; m_tempo_track_number = track; } /** * \getter m_recent_files * * Gets the desired recent MIDI file-name, if present. * * \param index * Provides the desired index into the recent-files vector. * * \param shorten * If true, remove the path-name from the file-name. True by default. * It needs to be short for the menu entry (though now using the full * path is an option), but the full path-name for the "rc" file. * * \return * Returns m_recent_files[index], perhaps shortened. An empty string is * returned if there is no such animal. */ std::string rcsettings::recent_file (int index, bool shorten) const { std::string result = m_recent_files.get(index); if (shorten && ! result.empty()) { auto slashpos = result.find_last_of("/\\"); if (slashpos != std::string::npos) result = result.substr(slashpos + 1, std::string::npos); } return result; } /** * \setter m_recent_files * * First makes sure the filename is not already present, and removes * the back entry from the list, if it is full (SEQ66_RECENT_FILES_MAX) * before adding it. Now the full pathname is added. * * ca 2025-05-31. Even if the file is already in the list, we * want it to be moved to the top, which recent::add() does for us. * * \param fname * Provides the full path to the MIDI file that is to be added to * the recent-files list. * * \return * Returns true if the file-name was able to be added. * If false, the file-name might already be in the list, so no need * to update the UI representing the list. */ bool rcsettings::add_recent_file (const std::string & filename) { std::string path = get_full_path(normalize_path(filename)); bool result = ! filename.empty(); /* ! m_recent_files.is_in_list(path) */ if (result) { result = m_recent_files.add(filename); if (result) auto_rc_save(true); /* fix on 2023-04-09 by ca */ } return result; } /** * \setter m_interaction_method * * \param value * The value to use to make the setting. * * \return * Returns true if the value was legal. */ bool rcsettings::interaction_method (interaction value) { bool result = false; switch (value) { case interaction::seq24: case interaction::fruity: m_interaction_method = value; result = true; break; default: errprint("illegal interaction-method value"); break; } return result; } /** * Prepends the exisiting m_midi_filepath value (if not empty) to the current * value and stores it as the new m_midi_filename value. This function is * meant to be used mainly under session management, but remember that the * session MIDI file-name is the same entity as the regular MIDI file-name. * * \param value * Provides the base name for the MIDI file, such as "mytune.midi". If * the ".midi" isn't provided (only the "." is searched), it will be * appended. If empty, the m_midi_filename value is cleared. */ void rcsettings::session_midi_filename (const std::string & value) { if (value.empty()) { m_midi_filename.clear(); } else { std::string base = file_extension_set(value, ".midi"); if (! m_midi_filepath.empty()) { std::string path = filename_concatenate(m_midi_filepath, base); m_midi_filename = path; } else m_midi_filename = base; } } /** * \setter m_jack_session_uuid * * This is an FYI-only data item that is controlled by JACK, and should not * be modified by the user. See https://jackaudio.org/metadata/ for more * information. * * \param uuid * The value to use to make the setting. If set to "on", it can enable * JACK session usage. "off" disables it. The session manager will * supply a long integer value (as a string). */ void rcsettings::jack_session (const std::string & uuid) { bool save_config = true; bool clear_uuid = true; if (uuid.empty()) { save_config = false; } else { if (uuid == "on") { usr().session_manager("jack"); } else if (uuid == "off") { usr().session_manager("none"); } else { usr().session_manager("jack"); m_jack_session_uuid = uuid; save_config = clear_uuid = false; } } if (save_config) auto_usr_save(true); if (clear_uuid) m_jack_session_uuid.clear(); } /** * Sets the method to use for handling running status errors. */ void rcsettings::running_status_action (const std::string & v) { if (v == "skip") m_running_status_action = rsaction::skip; else if (v == "proceed") m_running_status_action = rsaction::proceed; else if (v == "abort") m_running_status_action = rsaction::abort; else m_running_status_action = rsaction::recover; } std::string rcsettings::running_status_action_name () const { std::string result = "recover"; if (m_running_status_action == rsaction::skip) result = "skip"; else if (m_running_status_action == rsaction::proceed) result = "proceed"; else if (m_running_status_action == rsaction::abort) result = "abort"; return result; } /** * \param value * The value to use to make the setting. It needs to be a directory, not * a file. Also, we now expand a relative directory to the full path to * that directory, to avoid ambiguity should the application be run from * a different directory. * * \param userchange * If true (the default), then the 'rc' file needs to be save. * If false, we are loading the 'rc' file, so no change in status * is necessary. */ void rcsettings::last_used_dir (const std::string & value, bool userchange) { if (value.empty()) m_last_used_dir = empty_string(); /* "" from strfunctions */ else { #if defined USE_OLD_CODE std::string last = get_full_path(value); /* might end up empty */ if (last != m_last_used_dir) /* new directory? */ { m_last_used_dir = get_full_path(value); /* might end up empty */ if (userchange) auto_rc_save(true); /* need to write it */ } #else std::string last = filename_path(value); /* might end up empty */ if (last != m_last_used_dir) /* new directory? */ { m_last_used_dir = last; if (userchange) auto_rc_save(true); /* need to write it */ } #endif } } /** * \setter m_session_directory * * The default value of this member is that returned by the * user_session() function, either ".config" or "AppData/Local", with * the "seq66" or the result of seq_client_name() appended. * * \param value * The value to use to make the setting. Currently, we do not handle * relative paths. To do so seems... iffy. */ void rcsettings::session_directory (const std::string & value) { if (! value.empty()) m_session_directory = value; } /** * \setter m_config_subdirectory * * This value is one of the ways to implement the --home option. It * works if there is no root to the value parameter. It should only * work once (when m_config_subdirectory has not yet been set). * * \param value * The value to use to make the setting. This should be a relative path. * If empty (should be a rare use case), then the config subdirectory * is cleared. */ void rcsettings::config_subdirectory (const std::string & value) { if (value.empty()) { m_config_subdirectory_set = false; m_config_subdirectory.clear(); } else { if (! m_config_subdirectory_set) { m_config_subdirectory_set = true; m_config_subdirectory = value; } } } /** * New processing (2023-03-31) of the -H, --home option and of setting the * sub-directory for a session manager. * * If the name has a root path (either the user's home directory or a full * path from root), then the configuration directory becomes the value * parameter. Otherwise, we merely set the sub-directory for later use. */ void rcsettings::set_config_directory (const std::string & value) { bool rooted = name_has_root_path(value); if (rooted) { /* * Incorrect: full_config_directory(value) reads, but doesn't set * home_config_directory(). So we set it here. Note that * home_config_directory() normalizes the path it returns. */ m_full_config_directory = value;; std::string homedir = home_config_directory(); if (make_directory_path(homedir)) { /* * This setting will convert the relative session directory * to a full path. */ file_message("Config directory", homedir); session_directory(homedir); } else file_error("Could not create", homedir); } else { config_subdirectory(value); } } /** * \setter m_full_config_directory * * Provides an alternate value to be returned by the * home_config_directory() function. Please note that all configuration * locates are relative to "home". * * This causes double concatenation. But we need to call it just * once in the case where NSM has changed the default configuration * directory. * * \param value * Provides the directory name, which should be an actual full path. */ void rcsettings::full_config_directory (const::std::string & value) { if (! value.empty()) { std::string tv = value; if (m_config_subdirectory_set) /* see the banner note */ { tv = pathname_concatenate(tv, m_config_subdirectory); m_config_subdirectory_set = false; m_full_config_directory = normalize_path(tv, true, true); } std::string homedir = home_config_directory(); if (make_directory_path(homedir)) // REDUNDANT { /* * This setting will convert the relative session directory * to a full path. */ file_message("Config directory", homedir); session_directory(homedir); } else file_error("Could not create", homedir); } } /** * \setter m_config_filename and m_user_filename * * Implements the --config option to change both configuration files * ("rc" and "usr") with one option. * * What about the "ctrl", "playlist", "mutes", "notemap", and "palette" * files? They are defined in the "rc" file. * * \param value * The value to use to make the setting, if the string is not empty. * If the value has an extension, it is stripped first. * * TODO: use value = file_extension_set(value); */ void rcsettings::set_config_files (const std::string & value) { if (! value.empty()) { size_t ppos = value.rfind("."); std::string basename; if (ppos != std::string::npos) basename = value.substr(0, ppos); /* strip after first period */ else basename = value; config_filename(basename); user_filename(basename); } } /** * \setter m_config_filename ("rc"), which is the base-name of this file. * * \param value * The value to use to make the setting, if the string is not empty. * If there is no period in the string, then ".rc" is appended to the * end of the filename. */ void rcsettings::config_filename (const std::string & value) { if (value.empty()) /* an 'rc' file must always be used */ { // This should never happen } else { m_config_filename = filename_base_fix(value, ".rc"); #if defined SEQ66_KEEP_RC_FILE_LIST (void) add_config_filespec ( "rc", filespec_helper(m_config_filename) ); #endif } } /** * \setter m_playlist_filename ("playlist") * * Let the caller take care of this: m_playlist_active = true; * * \param value * The value to use to make the setting, if the string is not empty. * If there is no period in the string, then ".playlist" is appended to * the end of the filename. */ void rcsettings::playlist_filename (const std::string & value) { if (is_empty_string(value)) /* TODO: use this check elsewhere!! */ { clear_playlist(); /* clears file-name and active flag */ } else { m_playlist_filename = filename_base_fix(value, ".playlist"); #if defined SEQ66_KEEP_RC_FILE_LIST (void) add_config_filespec ( "playlist", filespec_helper(m_playlist_filename) ); #endif } } /** * Same as the playlist_filename() setter, but also checks for file existence * to help the caller decide if the playlist is active. * * \param value * The base-name of the playlist. * * \return * Returns true if the file-name was valid and the file (in the seq66 * configuration directory) exists. Otherwise returns false, but the * filename is still set if value. */ bool rcsettings::playlist_filename_checked (const std::string & value) { bool result = false; if (is_empty_string(value)) { playlist_filename(value); /* will inactivate & clear playlist */ } else { std::string fname = make_config_filespec(value, ".playlist"); result = file_exists(fname); playlist_filename(value); /* set playlist name no matter what */ } return result; } /** * Clears the play-list file-name and flags that the play-list is not active. * * \param disable * If true, the file-name is instead set to a value that indicates there * was a file-name, but the file does not exist. */ void rcsettings::clear_playlist (bool disable) { playlist_active(false); if (disable) m_playlist_filename = questionable_string(); else m_playlist_filename.clear(); } /** * \setter m_user_filename ("usr") * * \param value * The value to use to make the setting, if the string is not empty. * If there is no period in the string, then ".usr" is appended to the * end of the filename. */ void rcsettings::user_filename (const std::string & value) { if (value.empty()) /* always need a 'usr' file to read */ { user_file_active(false); /* this should never happen */ } else { m_user_filename = filename_base_fix(value, ".usr"); #if defined SEQ66_KEEP_RC_FILE_LIST (void) add_config_filespec ( "usr", filespec_helper(m_user_filename) ); #endif } } /** * If the 'ctrl' file-name is empty, the internal default keystrokes are * used. */ void rcsettings::midi_control_filename (const std::string & value) { if (value.empty()) { midi_control_active(false); } else { m_midi_control_filename = filename_base_fix(value, ".ctrl"); #if defined SEQ66_KEEP_RC_FILE_LIST (void) add_config_filespec ( "ctrl", filespec_helper(m_midi_control_filename) ); #endif } } void rcsettings::mute_group_filename (const std::string & value) { if (value.empty()) { mute_group_file_active(false); } else { m_mute_group_filename = filename_base_fix(value, ".mutes"); #if defined SEQ66_KEEP_RC_FILE_LIST (void) add_config_filespec ( "mutes", filespec_helper(m_mute_group_filename) ); #endif } } /** * We want to be able to use the .notemap extension as well. */ void rcsettings::notemap_filename (const std::string & value) { if (value.empty()) { notemap_active(false); } else { bool has_ext = name_has_extension(value); if (has_ext) m_notemap_filename = value; else m_notemap_filename = filename_base_fix(value, ".drums"); #if defined SEQ66_KEEP_RC_FILE_LIST (void) add_config_filespec ( "drums", filespec_helper(m_notemap_filename) ); #endif } } void rcsettings::patches_filename (const std::string & value) { if (value.empty()) { patches_active(false); } else { m_patches_filename = filename_base_fix(value, ".patches"); #if defined SEQ66_KEEP_RC_FILE_LIST (void) add_config_filespec ( "patches", filespec_helper(m_patches_filename) ); #endif } } void rcsettings::palette_filename (const std::string & value) { if (value.empty()) { palette_active(false); } else { m_palette_filename = filename_base_fix(value, ".palette"); #if defined SEQ66_KEEP_RC_FILE_LIST (void) add_config_filespec ( "palette", filespec_helper(m_palette_filename) ); #endif } } void rcsettings::style_sheet_filename (const std::string & value) { if (value.empty()) { style_sheet_active(false); } else { m_style_sheet_filename = filename_base_fix(value, ".qss"); #if defined SEQ66_KEEP_RC_FILE_LIST (void) add_config_filespec ( "qss", filespec_helper(m_style_sheet_filename) ); #endif } } void rcsettings::sets_mode (const std::string & v) { if (v == "normal") m_sets_mode = setsmode::normal; else if (v == "auto-arm" || (v == "autoarm")) m_sets_mode = setsmode::autoarm; else if (v == "additive") m_sets_mode = setsmode::additive; else if ((v == "all-sets") || (v == "allsets")) m_sets_mode = setsmode::allsets; else m_sets_mode = setsmode::normal; } std::string rcsettings::sets_mode_string () const { return sets_mode_string(sets_mode()); } std::string rcsettings::sets_mode_string (setsmode v) const { std::string result; switch (v) { case setsmode::normal: result = "normal"; break; case setsmode::autoarm: result = "auto-arm"; break; case setsmode::additive: result = "additive"; break; case setsmode::allsets: result = "all-sets"; break; default: result = "unknown"; break; } return result; } void rcsettings::port_naming (const std::string & v) { if (v == "long" || v == "full") m_port_naming = portname::full; else if (v == "pair") m_port_naming = portname::pair; else m_port_naming = portname::brief; } std::string rcsettings::port_naming_string () const { return port_naming_string(port_naming()); } std::string rcsettings::port_naming_string (portname v) const { std::string result; switch (v) { case portname::brief: result = "short"; break; case portname::pair: result = "pair"; break; case portname::full: result = "long"; break; default: result = "unknown"; break; } return result; } /** * In ALSA (and PortMidi), there is only one input (POLLIN) descriptor. * As far as we can tell, this allows only 1 input. For example, when * running two instances of VMPK, without recording, only one instance * yields a note. What about with two real devices? It works. * * So, don't bother trying to use two instances of VMPK for testing. */ bool rcsettings::sequence_lookup_support () const { bool result = record_by_buss(); if (result) result = with_jack_midi() || with_alsa_midi() || with_port_midi(); return result; } #if defined SEQ66_KEEP_RC_FILE_LIST bool rcsettings::add_config_filespec ( const std::string & key, const std::string & fspec ) { bool result = false; if (! fspec.empty()) { auto valueit = m_config_files.find(key); if (valueit != m_config_files.end()) m_config_files.erase(valueit); auto p = std::make_pair(key, fspec); auto fi = m_config_files.insert(p); result = fi.second; } return result; } #endif // defined SEQ66_KEEP_RC_FILE_LIST } // namespace seq66 /* * rcsettings.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/recent.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file recent.cpp * * This module declares/defines a class for keeping track of recently-used * files. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-03-29 * \updates 2021-04-22 * \license GNU GPLv2 or above * * The seq66::recent class simply keeps track of recently-used files for the * "Recent Files" menu. */ #include /* std::find() */ #include "cfg/recent.hpp" /* recent-files container */ #include "util/filefunctions.hpp" /* seq66::get_full_path() */ namespace seq66 { /** * Indicates the maximum number of recently-opened MIDI file-names we will * store. */ static const int sc_recent_files_max = 12; /** * This construction creates an empty recent-files list and sets the maximum * size of the list. */ recent::recent () : m_recent_list (), m_maximum_size (sc_recent_files_max) { // no code } /** * The conventional, but rote, principal assignent operator. It has to be * created to comment out re-assigning a constant. */ recent & recent::operator = (const recent & source) { if (this != &source) { m_recent_list = source.m_recent_list; /* * A constant, cannot be reassigned: * * m_maximum_size = source.m_maximum_size; */ } return *this; } recent::~recent () { // no code } bool recent::is_in_list (const std::string & path) { bool result = true; if (! path.empty()) { const auto & it = std::find ( m_recent_list.cbegin(), m_recent_list.cend(), path ); result = it != m_recent_list.end(); } return result; } /** * This function is meant to be used when loading the recent-files list from * a configuration file. Unlike the add() function, this one will not pop * off an existing item to allow the current item to be put into the * container. If the entry is already in the list, it is ignored. It assumes * the container had been cleared before this series of append() calls. * * \param item * Provides the file-name to append. It is converted to the full path to * the file before being added. Also, it is set to UNIX conventions, * using the forward slash as a path separator. * * \return * Returns true if the file-name was appended. */ bool recent::append (const std::string & item) { bool result = count() < maximum(); if (result) { std::string path = get_full_path(normalize_path(item)); result = ! path.empty(); if (result) result = file_readable(path); if (result) { const auto & it = std::find ( m_recent_list.cbegin(), m_recent_list.cend(), path ); if (it == m_recent_list.end()) /* not found? */ m_recent_list.push_back(path); /* append it! */ } } return result; } /** * This function is meant to be used when adding a file that the user * selected. If the file is already in the list, it is moved to the "top" * (the beginning of the list); the original entry is removed. If the list is * full, the last entry is removed, in order to make room for the new entry. * * \param item * Provides the file-name to add. It is converted to the full path to * the file before being added. Also, it is set to UNIX conventions, * using the forward slash as a path separator. * * \return * Returns true if the file-name was added. The file must be readable. */ bool recent::add (const std::string & item) { std::string path = get_full_path(normalize_path(item)); bool result = ! path.empty(); if (result) result = file_readable(path); if (result) { const auto & it = std::find ( m_recent_list.cbegin(), m_recent_list.cend(), path ); if (it != m_recent_list.end()) (void) m_recent_list.erase(it); result = count() < maximum(); if (! result) { m_recent_list.pop_back(); /* remove last entry to make room */ result = true; } if (result) m_recent_list.push_front(path); } return result; } /** * Removes the path from the recent-files list, if it was found in that list. * * \todo * The fix of 2018-12-05 should be added/tested in seq64!!! * * \param item * Provides the path to be removed. * * \return * Returns true if the item was found and removed. */ bool recent::remove (const std::string & item) { std::string path = get_full_path(normalize_path(item)); bool result = ! path.empty(); if (result) { const auto & it = std::find ( m_recent_list.cbegin(), m_recent_list.cend(), path ); if (it != m_recent_list.end()) (void) m_recent_list.erase(it); else result = false; } return result; } /** * Gets the file-path at the given position. * * \param index * Provides the index into the container. It is checked for validity. * * \return * Returns the file-path, if available. Otherwise, an empty string is * returned. The path is converted to the slash/backslash conventions * appropriate for the operating system on which this code was built. */ std::string recent::get (int index) const { std::string result; if (index >= 0 && index < count()) { result = m_recent_list[container::size_type(index)]; result = normalize_path(result); /* UNIX and no slash terminator */ } return result; } } // namespace seq66 /* * recent.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/scales.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file scales.cpp * * This module declares/defines functions related to scales. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-10-04 * \updates 2025-10-23 * \license GNU GPLv2 or above * * Here is a list of many scale interval patterns if working with * more than just diatonic scales: * \verbatim Major Scale (Ionian): R-W-W-h-W-W-W-h Natural Minor Scale (Aeolian): R-W-h-W-W-h-W-W Harmonic Minor Scale: R-W-h-W-W-h-3-h Melodic Minor Scale going up: R-W-h-W-W-W-W-h * Melodic Minor Scale going down: R-W-W-h-W-W-h-W (Mixolydian mode?) Whole Tone: R-W-W-W-W-W-W (Augmented, Bebop) Minor Blues Scale: R-3-W-h-h-3-W * Major Blues Scale: R-W-W-h-h-3-W-W (Pentatonic Blues) Major Pentatonic: R-W-W-3-W-3 Minor Pentatonic Blues (no #5): R-3-W-W-3-W Phrygian: R-h-W-W-W-h-W-W Phygian Dominant (Ahava Raba): R-h-3-h-W-h-W-W Enigmatic: R-h-3-W-W-W-h-h Diminished R-W-h-W-h-W-h-W-h Dorian Mode: R-W-h-W-W-W-h-W Mixolydian Mode: R-W-W-h-W-W-h-W (Dominant 7th) Dominant Diminished Scale: R-h-W-h-W-h-W-h-W (Diminished Blues) \endverbatim * \verbatim *: unimplemented in Seq66 R: root note h: half step (semitone) W: whole step (2 semitones) 3: 1 1/2 - a step and a half (3 semitones) \endverbatim */ #include /* std::toupper(), std::isalpha() */ #include /* for the pow() function */ #include "cfg/scales.hpp" /* seq66::scales declarations */ #include "midi/eventlist.hpp" /* seq66::eventlist */ #include "midi/midibytes.hpp" /* seq66::midibytes */ #include "util/strfunctions.hpp" /* seq66::contains() */ #if defined USE_SHOE_ALL_COUNTS #include "cfg/settings.hpp" /* seq66::rc().verbose() */ #endif namespace seq66 { /** * The minimum number of notes needed for a scale analysis. */ static const int c_analysis_minimum { 8 }; /** * Each value in the kind of scale is denoted by a true value in these * arrays. See the following sites for more information: * * - http://method-behind-the-music.com/theory/scalesandkeys/ * - https://en.wikipedia.org/wiki/Heptatonic_scale * - https://en.wikibooks.org/wiki/Music_Theory/Scales_and_Intervals * * Note that melodic minor descends in the same way as the natural minor * scale, so it descends differently than it ascends. We don't deal with * that trick, at all. In the following table, the scales all start with C, * but seq66 allows other starting notes (e.g. "keys"). * \verbatim Chromatic C C# D D# E F F# G G# A A# B Notes, chord Major C . D . E F . G . A . B Minor C . D Eb . F . G Ab . Bb . Natural Minor Harmonic Minor C . D Eb . F . G Ab . . B Melodic Minor C . D Eb . F . G . A . B Descending diff. C Whole Tone C . D . E . F# . G# . A# . C+7 chord Minor Blues C . . Eb . F Gb G . . Bb . Gb = "blue note" Major Pentatonic C . D . E . . G . A . . Minor Pentatonic C . . Eb . F . G . . Bb . See Minor Blues Phrygian C Db . Eb . F . G G# . Bb . Enigmatic C Db . . E . F# . G# . A# B Diminished C . D Eb . F Gb . Ab A . B Dorian Mode C . D Eb . F . G . A Bb . Mixolydian Mode C . D . E F . G . A Bb . Dominant Dimin'ed C Db . Eb E . F# G . A Bb Diminished Blues \endverbatim */ static const bool c_scales_policy [c_scales_max] [c_octave_size] { { /* off = chromatic */ true, true, true, true, true, true, true, true, true, true, true, true }, { /* major */ true, false, true, false, true, true, false, true, false, true, false, true }, { /* minor */ true, false, true, true, false, true, false, true, true, false, true, false }, { /* harmonic minor */ true, false, true, true, false, true, false, true, true, false, false, true }, { /* melodic minor */ true, false, true, true, false, true, false, true, false, true, false, true }, { /* whole tone */ true, false, true, false, true, false, true, false, true, false, true, false }, { /* minor blues */ true, false, false, true, false, true, true, true, false, false, true, false }, { /* maj pentatonic */ true, false, true, false, true, false, false, true, false, true, false, false }, { /* min pentatonic */ true, false, false, true, false, true, false, true, false, false, true, false }, { /* phrygian */ true, true, false, true, false, true, false, true, true, false, true, false }, { /* enigmatic */ true, true, false, false, true, false, true, false, true, false, true, true }, { /* diminished */ true, false, true, true, false, true, true, false, true, true, false, true }, { /* dorian */ true, false, true, true, false, true, false, true, false, true, true, false }, { /* mixolydian */ true, false, true, false, true, true, false, true, false, true, true, false } }; /** * Indicates if the given note (MIDI key) is part of the given scale. * * Replaced by the alternative below. * * \param s * Provides the scale to be checked. * * \param k * Provides the key to be checked, indirectly, as it also depends * of the given key (e.g. C versus C#). Let key range from 1 to * 128. Then k is given by: * * 128 - key + 12 - keyofpattern * * where keyofpattern ranges from 0 to 11. See qseqroll::draw_grid(). * * \return * Returns true if k is part of the given scale. */ bool scales_policy (scales s, int k) { return c_scales_policy[int(s)][(k - 1) % c_octave_size]; } /** * Alternative. * * \param s * Provides the scale to be checked. * * \param keyofpattern * The user-selected key for the pattern, ranging from 0 to 11. * 0 is the Major scale; the chromatic (off) scale is not used * in accessing the arrays. * * \param k * The actual MIDI key value, ranging from 0 to 127. */ bool scales_policy (scales s, keys keyofpattern, int k) { if (s == scales::chromatic) /* the same as scales::off */ { return true; } else { /* * int k2 = k + c_octave_size - 1 - key_to_int(keyofpattern); * * The minus 1 was needed because the note lines in the seqroll * were drawn one unit-height down. */ int k2 = k + c_octave_size - key_to_int(keyofpattern); k2 %= c_octave_size; return c_scales_policy[int(s)][k2]; } } /** * This function rotates a scale policy array to the "right" by one semitone. * For example, see this shift from C major to C# major, where each dot * represents a "false" boolean value: * \verbatim C Major C . D . E F . G . A . B booleans T F T F T T F T F T F T histogram sample 1 0 2 0 2 0 1 1 0 1 0 2 = 9 - 1 C# Major C C# . D# . F F# . G# . A# . booleans T T F T F T T F T F T F histogram sample 1 0 2 0 2 0 1 1 0 1 0 2 = 2 - 8 \endverbatim */ static void rotate_scale_right (bool p [c_octave_size]) { int lastindex { c_octave_size - 1 }; bool last { p[lastindex] }; /* p[11] */ for (int n = lastindex; n > 0; --n) /* p[11] to p[1] */ p[n] = p[n - 1]; p[0] = last; } static void rotate_transpose_right (int p [c_octave_size]) { int lastindex { c_octave_size - 1 }; int last { p[lastindex] }; /* p[11] */ for (int n = lastindex; n > 0; --n) /* p[11] to p[1] */ p[n] = p[n - 1]; p[0] = last; } /** * Increment values needed to transpose each scale up so that it remains in * the exact same key, for harmonic transposition. For example, if we simply * add 1 semitone to each note, it remains a minor key, but it is in a * different minor key. Using the transpositions in these arrays, the minor * key remains the same minor key. * \verbatim Major C . D . E F . G . A . B Transpose up 2 . 2 . 1 2 . 2 . 2 . 1 Result up D . E . F G . A . B . C \endverbatim * \verbatim Minor C . D D# . F . G G# . A# . Transpose up 2 . 1 2 . 2 . 1 2 . 2 . Result up D . D# F . G . G# A# . C . \endverbatim * \verbatim Harmonic minor C . D Eb . F . G Ab . . B Transpose up 2 . 1 2 . 2 . 1 3 . . 1 Result up D . Eb F . G . Ab B . . C \endverbatim * \verbatim Melodic minor C . D Eb . F . G . A . B Transpose up 2 . 1 2 . 2 . 2 . 2 . 1 Result up D . Eb F . G . A . B . C \endverbatim * \verbatim C Whole Tone C . D . E . F# . G# . A# . Transpose up 2 . 2 . 2 . 2 . 2 . 2 . Result up D . E . F# . G# . A# . C . \endverbatim * \verbatim Minor Blues C . . Eb . F Gb G . . Bb . Transpose up 3 . . 2 . 1 1 3 . . 2 . Result up Eb . . F . Gb G Bb . . C . \endverbatim * \verbatim Major Pentatonic C . D . E . . G . A . . Transpose up 2 . 2 . 3 . . 2 . 3 . . Result up D . E . G . . A . C . . \endverbatim * \verbatim Minor Pentatonic C . . Eb . F . G . . Bb . Transpose up 3 . . 2 . 2 . 3 . . 2 . Result up Eb . . F . G . Bb . . C . \endverbatim * \verbatim Phrygian C Db . Eb . F . G Ab . Bb . Transpose up 1 2 . 2 . 2 . 1 2 . 2 . Result up D Eb . F . G . A Bb . C . \endverbatim * \verbatim Enigmatic C Db . . E . F# . G# . A# B Transpose up 1 3 . . 2 . 2 . 2 . 1 1 Result up Db E . . F# . G# . A# . B C \endverbatim * \verbatim Diminished C . D Eb . F Gb . Ab A . B Transpose up 2 . 1 2 . 1 2 . 1 2 . 1 Result up D . Eb F . Gb Ab . A B . C \endverbatim * \verbatim Dorian Mode C . D Eb . F . G . A Bb . Transpose up 2 . 1 2 . 2 . 2 . 1 2 . Result up D . Eb F . G . A . Bb C . \endverbatim * \verbatim Mixolydian Mode C . D . E F . G . A Bb . Transpose up 2 . 2 . 1 2 . 2 . 1 2 . Result up D . E . F G . A . Bb . C \endverbatim * * Note that the D Dorian scale is all white keys: D E F G A B C D. * The "result up" shown above is for a transposition that preserves the * C Dorian scale. * * The Mixolydian scale starts on the 5th note of the Major scale and ends on * the fifth note. For instance, the C Major scale is C, D, E, F, G, A, B, * and C. The fifth note of C Major is G. Therefore the 5th mode of C Major * is G Mixolydian: G, A, B, C, D, E, F, and G. It is sometimes referred to * as the Dominant 7th scale. */ const int * scales_up (int scale, int key) { static const int c_scales_transpose_up [c_scales_max] [c_octave_size] { { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, /* off = chromatic */ { 2, 0, 2, 0, 1, 2, 0, 2, 0, 2, 0, 1}, /* major */ { 2, 0, 1, 2, 0, 2, 0, 1, 2, 0, 2, 0}, /* minor */ { 2, 0, 1, 2, 0, 2, 0, 1, 3, 0, 0, 1}, /* harmonic minor */ { 2, 0, 1, 2, 0, 2, 0, 2, 0, 2, 0, 1}, /* melodic minor */ { 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0}, /* C whole tone */ { 3, 0, 0, 2, 0, 1, 1, 3, 0, 0, 2, 0}, /* minor blues */ { 2, 0, 2, 0, 3, 0, 0, 2, 0, 3, 0, 0}, /* maj pentatonic */ { 3, 0, 0, 2, 0, 2, 0, 3, 0, 0, 2, 0}, /* min pentatonic */ { 1, 2, 0, 2, 0, 2, 0, 1, 2, 0, 2, 0}, /* phrygian */ { 1, 3, 0, 0, 2, 0, 2, 0, 2, 0, 1, 1}, /* enigmatic */ { 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1}, /* diminished */ { 2, 0, 1, 2, 0, 2, 0, 2, 0, 1, 2, 0}, /* dorian */ { 2, 0, 2, 0, 1, 2, 0, 2, 0, 1, 2, 0} /* mixolydian */ }; if (key > 0) { static int rotated_scale[c_octave_size]; for (int k = 0; k < c_octave_size; ++k) rotated_scale[k] = c_scales_transpose_up[scale][k]; for (int count = key; count > 0; --count) rotate_transpose_right(rotated_scale); return &rotated_scale[0]; } else return &c_scales_transpose_up[scale][0]; } /** * Making these positive makes it easier to read, but the actual array * contains negative values. * \verbatim Semitone Number 1 2 3 4 5 6 7 8 9 10 11 12 Chromatic (Off) C . D . E F . G . A . B \endverbatim * \verbatim Major C . D . E F . G . A . B Transpose down 1 . 2 . 2 1 . 2 . 2 . 2 Result down B . C . D E . F . G . A \endverbatim * \verbatim Minor C . D D# . F . G G# . A# . Transpose down 2 . 2 1 . 2 . 2 1 . 2 . Result down A# . C D . D# . F G . G# . \endverbatim * \verbatim Harmonic minor C . D Eb . F . G Ab . . B Transpose down 1 . 2 1 . 2 . 2 1 . . 3 Result down B . C D . Eb . F G . . Ab \endverbatim * \verbatim Melodic minor C . D Eb . F . G . A . B Transpose down 1 . 2 1 . 2 . 2 . 2 . 2 Result down B . C D . Eb . F . G . A \endverbatim * \verbatim C whole tone C . D . E . F# . G# . A# . Transpose down 2 . 2 . 2 . 2 . 2 . 2 . Result down A# . C . D . E . F# . G# . \endverbatim * \verbatim Minor Blues C . . Eb . F Gb G . . Bb . Transpose down 2 . . 3 . 2 1 1 . . 3 . Result down Bb . . C . Eb F Gb . . G . \endverbatim * \verbatim Major Pentatonic C . D . E . . G . A . . Transpose down 3 . 2 . 2 . . 3 . 2 . . Result down A . C . D . . E . G . . \endverbatim * \verbatim Minor Pentatonic C . . Eb . F . G . . Bb . Transpose down 2 . . 3 . 2 . 2 . . 3 . Result down Bb . . C . Eb . F . . G . \endverbatim * \verbatim Phrygian C Db . Eb . F . G Ab . Bb . Transpose down 1 1 . 1 . 1 . 1 1 . 1 . Result down B C . D . E . Fb G . A . \endverbatim * \verbatim Enigmatic C Db . . E . F# . G# . A# B Transpose down 1 1 . . 3 . 2 . 2 . 2 1 Result down B C . . Db . E . F# . G# A# \endverbatim * \verbatim Diminished C . D Eb . F Gb . Ab A . B Transpose down 1 . 2 1 . 2 1 . 2 1 . 2 Result down B . C D . Eb F . Gb Ab . A \endverbatim * \verbatim Dorian Mode C . D Eb . F . G . A Bb . Transpose down 2 . 2 1 . 2 . 2 . 2 1 . Result down Bb . C D . Eb . F . G A . \endverbatim * \verbatim Mixolydian Mode C . D . E F . G . A Bb . Transpose down 2 . 2 . 2 1 . 2 . 2 1 . Result down Bb . C . D E . F . G A . \endverbatim */ const int * scales_down (int scale, int key) { static const int c_scales_transpose_dn [c_scales_max] [c_octave_size] { { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, /* off = chromatic */ { -1, 0, -2, 0, -2, -1, 0, -2, 0, -2, 0, -2}, /* major (ionian) */ { -2, 0, -2, -1, 0, -2, 0, -2, -1, 0, -2, 0}, /* minor (aeolian) */ { -1, 0, -2, -1, 0, -2, 0, -2, -1, 0, 0, -3}, /* harmonic minor */ { -1, 0, -2, -1, 0, -2, 0, -2, 0, -2, 0, -2}, /* melodic minor */ { -2, 0, -2, 0, -2, 0, -2, 0, -2, 0, -2, 0}, /* C whole tone */ { -2, 0, 0, -3, 0, -2, -1, -1, 0, 0, -3, 0}, /* minor blues */ { -3, 0, -2, -0, 2, 0, 0, -3, 0, -2, 0, 0}, /* maj pentatonic */ { -2, 0, 0, -3, 0, -2, 0, -2, 0, 0, -3, 0}, /* min pentatonic */ { -1, -1, 0, -1, 0, -1, 0, -1, -1, 0, -1, 0}, /* phrygian */ { -1, -1, 0, 0, -3, 0, -2, 0, -2, 0, -2, -1}, /* enigmatic */ { -1, 0, -2, -1, 0, -2, -1, 0, -2, -1, 0, -2}, /* diminished */ { -2, 0, -2, -1, 0, -2, 0, -2, 0, -2, -1, 0}, /* dorian */ { -2, 0, -2, 0, -2, 1, 0, -2, 0, -2, -1, 0} /* mixolydian */ }; if (key > 0) { static int rotated_scale[c_octave_size]; for (int k = 0; k < c_octave_size; ++k) rotated_scale[k] = c_scales_transpose_dn[scale][k]; for (int count = key; count > 0; --count) rotate_transpose_right(rotated_scale); return &rotated_scale[0]; } else return &c_scales_transpose_dn[scale][0]; } static const std::string c_key_text[c_octave_size] { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; std::string musical_note_name (int n) { std::string result { "Xb" }; if (legal_note(n)) { char note[16]; int key { n % c_octave_size }; int octave { (n / c_octave_size) - 1 }; if (octave >= 0) { snprintf ( note, sizeof note, "%2s%1d", c_key_text[key].c_str(), octave ); } else snprintf(note, sizeof note, "%2s-", c_key_text[key].c_str()); result = note; } return result; } std::string musical_key_name (keys k) { return c_key_text[key_to_int(k)]; } /** * Each diatonic scale in Western music consists of 7 notes. */ std::string musical_scale_name (scales s) { static const std::string c_scales_text[c_scales_max] { "Off (Chromatic)", "Major (Ionian)", "Minor (Aeolan)", "Harmonic Minor", "Melodic Minor", /* ascending only; see Mixolydian mode */ "Whole Tone", "Minor Blues", "Pentatonic Major", "Pentatonic Minor", "Phrygian", "Enigmatic", "Diminished", "Dorian", "Mixolydian" }; return c_scales_text[scale_to_int(s)]; } /** * Provides the entries for the normal pitch interval dropdown menu in the * Pattern Editor window. We use pointers for convenience. See * qseqeditframe64.cpp for usage. * * "P" means "perfect"; "M" means "major"; "m" means "minor". * This represents "chromatic transposition". */ const char * interval_name_ptr (int interval) { static const std::string c_interval_text [c_interval_size + 1] { "P1", "m2", "M2", "m3", "M3", "P4", "TT", "P5", "m6", "M6", "m7", "M7", "P8", "m9", "M9", "0" /* "0" if error */ }; int index { std::abs(interval) }; if (index > c_interval_size) index = c_interval_size; return c_interval_text[index].c_str(); } /** * Provides the entries for the harmonic pitch interval dropdown menu in the * Pattern Editor window. We use pointers for convenience. See * qseqeditframe64.cpp for usage. * * Provides the entries for the Chord dropdown menu in the Pattern Editor * window. However, we have not seen this menu in the GUI! Ah, it only * appears if the user has selected a musical scale like Major or Minor. * It replaces the -12 to +12 transposition menu. * * This represents "diatonic transposition". We believe the numbering is * called the "Nashville numbering system." */ bool harmonic_number_valid (int n) { return std::abs(n) < c_harmonic_size; } /** * Was: "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "0" */ const char * harmonic_interval_name_ptr (int interval) { static const std::string c_interval_text [c_harmonic_size + 1] { "I", "ii", "iii", "IV", "V", "vi", "vii", "I", "0" }; int index { std::abs(interval) }; if (index > c_harmonic_size) index = c_harmonic_size; return c_interval_text[index].c_str(); } /** * Additional support data for the chord-generation feature from Stazed's * seq32 project. The chord-number is a count of the number of entries in * c_chord_table_text. Will never change, luckily. * * Additional support data for the chord-generation feature from Stazed's * seq32 project. These chords appear in the sequence-editor chord-button * dropdown menu. The longest string is 11 characters, and we add one * for the null terminator. A good case for using std::string here. :-) */ bool chord_number_valid (int n) { return (n >= 0) && (n < c_chord_number); } const char * chord_name_ptr (int n) { static const std::string c_chord_table_text [c_chord_number + 1] { "Chords off", "Major", "Majb5", "minor", "minb5", "sus2", "sus4", "aug", "augsus4", "tri", "6", "6sus4", "6add9", "m6", "m6add9", "7", "7sus4", "7#5", "7b5", "7#9", "7b9", "7#5#9", "7#5b9", "7b5b9", "7add11", "7add13", "7#11", "Maj7", "Maj7b5", "Maj7#5", "Maj7#11", "Maj7add13", "m7", "m7b5", "m7b9", "m7add11", "m7add13", "m-Maj7", "m-Maj7add11", "m-Maj7add13", "" /* terminator */ }; if (! chord_number_valid(n)) n = c_chord_number; return c_chord_table_text[n].c_str(); } /** * Additional support data for the chord-generation feature from Stazed's * seq32 project. These values indicate the note offsets needed for a * particular kind of chord. 0 means no offset, and a -1 ends the list of * note offsets for the chord. * * Strictly speaking, we could just assume 0 is always present and save * a little space-time. */ const chord_notes & chord_entry (int n) { static const std::vector s_chord_table { { 0, -1, 0, 0, 0, 0 }, /* Off */ { 0, 4, 7, -1, 0, 0 }, /* Major */ { 0, 4, 6, -1, 0, 0 }, /* Majb5 */ { 0, 3, 7, -1, 0, 0 }, /* minor */ { 0, 3, 6, -1, 0, 0 }, /* minb5 */ { 0, 2, 7, -1, 0, 0 }, /* sus2 */ { 0, 5, 7, -1, 0, 0 }, /* sus4 */ { 0, 4, 8, -1, 0, 0 }, /* aug */ { 0, 5, 8, -1, 0, 0 }, /* augsus4 */ { 0, 3, 6, 9, -1, 0 }, /* tri */ { 0, 4, 7, 9, -1, 0 }, /* 6 */ { 0, 5, 7, 9, -1, 0 }, /* 6sus4 */ { 0, 4, 7, 9, 14, -1 }, /* 6add9 */ { 0, 3, 7, 9, -1, 0 }, /* m6 */ { 0, 3, 7, 9, 14, -1 }, /* m6add9 */ { 0, 4, 7, 10, -1, 0 }, /* 7 */ { 0, 5, 7, 10, -1, 0 }, /* 7sus4 */ { 0, 4, 8, 10, -1, 0 }, /* 7#5 */ { 0, 4, 6, 10, -1, 0 }, /* 7b5 */ { 0, 4, 7, 10, 15, -1 }, /* 7#9 */ { 0, 4, 7, 10, 13, -1 }, /* 7b9 */ { 0, 4, 8, 10, 15, -1 }, /* 7#5#9 */ { 0, 4, 8, 10, 13, -1 }, /* 7#5b9 */ { 0, 4, 6, 10, 13, -1 }, /* 7b5b9 */ { 0, 4, 7, 10, 17, -1 }, /* 7add11 */ { 0, 4, 7, 10, 21, -1 }, /* 7add13 */ { 0, 4, 7, 10, 18, -1 }, /* 7#11 */ { 0, 4, 7, 11, -1, 0 }, /* Maj7 */ { 0, 4, 6, 11, -1, 0 }, /* Maj7b5 */ { 0, 4, 8, 11, -1, 0 }, /* Maj7#5 */ { 0, 4, 7, 11, 18, -1 }, /* Maj7#11 */ { 0, 4, 7, 11, 21, -1 }, /* Maj7add13 */ { 0, 3, 7, 10, -1, 0 }, /* m7 */ { 0, 3, 6, 10, -1, 0 }, /* m7b5 */ { 0, 3, 7, 10, 13, -1 }, /* m7b9 */ { 0, 3, 7, 10, 17, -1 }, /* m7add11 */ { 0, 3, 7, 10, 21, -1 }, /* m7add13 */ { 0, 3, 7, 11, -1, 0 }, /* m-Maj7 */ { 0, 3, 7, 11, 17, -1 }, /* m-Maj7add11 */ { 0, 3, 7, 11, 21, -1 } /* m-Maj7add13 */ }; if (! chord_number_valid(n)) n = 0; return s_chord_table[n]; } std::string chord_intervals (chords c) { std::string result; const chord_notes & cn = chord_entry(static_cast(c)); for (auto cnote : cn) { if (cnote == (-1)) { break; } else { result += std::to_string(cnote); result += " "; } } return result; } /** * Given a note number and a selected chord, determines if the note is part * of that chord. The parameters are not directly checked. * * We baseline ("move") a high chord note like 18 down to the range of * 0 to 11, for the purpose of drawing a note bar that is not part of * the chord. * * \param chord * The currently selected chord (e.g. in the pattern editor). * * \param key * The key selected for the pattern. * * \param note * The value of the note, ranging from 0 to 127. */ bool note_in_chord (chords c, keys k, int note) { bool result = false; const chord_notes & cn = chord_entry(static_cast(c)); int offset = key_to_int(k); int basenote = note % c_octave_size - offset; /* e.g. key D becomes C */ if (basenote < 0) basenote += c_octave_size; for (auto cnote : cn) { if (cnote == (-1)) { break; } else { if (cnote > c_octave_size) /* an adjustment needed? */ cnote -= c_octave_size; if (cnote == basenote) { result = true; break; } else if (cnote > basenote) break; } } return result; } /** * Convert a MIDI note number to frequency. The formula, based on A4 (note * 69) being the concert pitch, 440 Hz, is: * \verbatim (n-69)/12 f = 440 x 2 \endverbatim * * \return * 0.0 is returned upon an error. */ double midi_note_frequency (midibyte note) { double result { 0.0 }; if (note < c_midibyte_data_max) { double exponent { double(note) - 69.0 / 12.0 }; result = 440.0 * pow(2.0, exponent); } return result; } /** * Analyzes one note to see what key (0 to 11, keys::C to keys::B) it is, and * the octave (0 to 9) it resides in. * \verbatim 0: C-1 12: C0 24: C1 36: C2 48: C3 60: C4 (middle C) 72: C5 84: C6 96: C7 108: C8 120: C9 127: G9 \endverbatim * * \return * Returns true if the analysis was workable. */ static bool analyze_note (midibyte note, keys & outkey, int & outoctave) { bool result { note < c_midibyte_data_max }; if (result) { unsigned base_semitone { unsigned(note) % c_octave_size }; outkey = static_cast(base_semitone); outoctave = unsigned(note) / c_octave_size - 1; } return result; } #if defined USE_SHOW_ALL_COUNTS static void show_all_counts ( int histo [c_octave_size], int cmatrix [c_scales_max - 1] [c_key_of_max] [2] ) { printf(" Histogram: C C# D D# E F F# G G# A A# B\n"); printf(" "); for (int k = 0; k < c_octave_size; ++k) printf(" %2d ", histo[k]); printf("\nOn-Scale:\n"); for (int s = 0; s < c_scales_max - 1; ++s) { bool policy[c_octave_size]; int scale { s + 1 }; printf("%16s: ", musical_scale_name(scale).c_str()); for (int k = 0; k < c_octave_size; ++k) policy[k] = c_scales_policy[scale][k]; /* base (C) scale */ for (int K = 0; K < c_key_of_max; ++K) { printf(" %c%2d", '+', cmatrix[s][K][0]); rotate_scale_right(policy); } printf("\n"); } printf("Corrected:\n"); for (int s = 0; s < c_scales_max - 1; ++s) { bool policy[c_octave_size]; int scale { s + 1 }; printf("%16s: ", musical_scale_name(scale).c_str()); for (int k = 0; k < c_octave_size; ++k) policy[k] = c_scales_policy[scale][k]; /* base (C) scale */ for (int K = 0; K < c_key_of_max; ++K) { printf(" %c%2d", '%', cmatrix[s][K][0] - cmatrix[s][K][1]); rotate_scale_right(policy); } printf("\n"); } } #endif // defined USE_SHOE_ALL_COUNTS /** * The algorithm is simple. Get a histogram of the 12 semitones found in the * event list. Then see which key/scale combination contains the most of * those semitones. The key/scale combinations are in an array of dimension * c_key_of_max x (c_scales_max -1). * * We start with the major scale and C, and total up all the notes found for * the valid notes in that scale, and record the count in * count_matrix[major-1][C]. Then we move the scale up to C# and record the * count in count_matrix[major-1][C#]. * * Another algorithm to consider is the Krumhansl-Schmuckler key-finding * algorithm. * * \return * Returns the number of keys/scales found. If 0, the analysis was * unworkable and the output parameters should not be used. */ int analyze_notes ( const eventlist & evlist, std::vector & outkeys, std::vector & outscales ) { int result { 0 }; bool ok { evlist.count() > 0 }; if (ok) { int histogram[c_octave_size]; int highcount { 0 }; /* maximum count found on scale */ int notecount { 0 }; for (int k = 0; k < c_octave_size; ++k) histogram[k] = 0; for (auto e = evlist.cbegin(); e != evlist.cend(); ++e) { const event & er { eventlist::cdref(e) }; keys thekey; int theoctave; if (er.is_note_on()) { midibyte note { er.get_note() }; ++notecount; ok = analyze_note(note, thekey, theoctave); if (ok) { int k = static_cast(thekey); ++histogram[k]; } } } if (notecount < c_analysis_minimum) { ok = false; /* infoprint("Not enough notes to analyze.") */ } if (ok) { /* * The first value is for "hits" and the second is for "misses". */ int count_matrix [c_scales_max - 1] [c_key_of_max] [2]; const std::initializer_list keyslist = { keys::C, keys::Csharp, keys::D, keys::Dsharp, keys::E, keys::F, keys::Fsharp, keys::G, keys::Gsharp, keys::A, keys::Asharp, keys::B }; for (int s = 0; s < c_scales_max - 1; ++s) for (int k = 0; k < c_key_of_max; ++k) count_matrix[s][k][0] = count_matrix[s][k][1] = 0; for (int s = 0; s < c_scales_max - 1; ++s) { bool policy[c_octave_size]; for (int k = 0; k < c_octave_size; ++k) policy[k] = c_scales_policy[s+1][k]; /* base (C) scale */ for (auto ken : keyslist) { int count_in { 0 }; int count_out { 0 }; int k { static_cast(ken) }; /* * Here, we have the scale and it's (next) key. For each * policy value, add the count for each semitone in the * histogram. */ for (int bin = 0; bin < c_octave_size; ++bin) { if (policy[bin]) count_in += histogram[bin]; else count_out += histogram[bin]; } count_matrix[s][k][0] = count_in; count_matrix[s][k][1] = count_out; if (count_in > highcount) highcount = count_in; rotate_scale_right(policy); } } #if defined USE_SHOE_ALL_COUNTS if (rc().verbose()) show_all_counts(histogram, count_matrix); #endif for (int s = 0; s < c_scales_max - 1; ++s) { for (auto ken : keyslist) { int k { static_cast(ken) }; if (count_matrix[s][k][0] == highcount) { outscales.push_back(static_cast(s + 1)); outkeys.push_back(static_cast(k)); ++result; /* count it */ } } } } } return result; } /** * Returns the root note of a key signature. */ using key_sig_data = struct { int sharp_flat_count; std::string major_root_note; std::string minor_root_note; }; static key_sig_data s_key_sig_root_table [15] { { -7, "Cbmaj", "Abmin" }, { -6, "Gbmaj", "Ebmin" }, { -5, "Dbmaj", "Bbmin" }, { -4, "Abmaj", "Fmin" }, { -3, "Ebmaj", "Cmin" }, { -2, "Bbmaj", "Gmin" }, { -1, "Fmaj", "Dmin" }, { 0, "Cmaj", "Amin" }, { 1, "Gmaj", "Emin" }, { 2, "Dmaj", "Bmin" }, { 3, "Amaj", "F#min" }, { 4, "Emaj", "C#min" }, { 5, "Bmaj", "G#min" }, { 6, "F#maj", "D#min" }, { 7, "C#maj", "A#min" } }; /** * Returns the key-signature string. * * \param sfcount * Provides the sharp/flat count, rangine from -7 to 7. * * \param isminor * If true, the scale is minor, otherwise it is major. * * \return * Returns the string via a fast lookup. It is empty if * the sfcount is out of range. */ std::string key_signature_string (int sfcount, bool isminor) { std::string result; if (sfcount >= -7 && sfcount <= 7) { int index { sfcount + 7 }; const key_sig_data & ksd { s_key_sig_root_table[index] }; result = isminor ? ksd.minor_root_note : ksd.major_root_note ; } return result; } /** * \param keysigname * The human-readable name of the key signature, from the above table. * * \param keysigbytes * Returns the key-sig value, ranging from -7 to 7. This is a * brute-force lookup. Defaults to 0 if the keysigname is not found. * * \return * Returns true if the bytes can be used, because no error occurred. * If false, then the keysigbytes parameter is empty [size() == 0]. */ bool key_signature_bytes ( const std::string & keysigname, midibytes & keysigbytes ) { bool hasminor { contains(keysigname, "min") }; bool hasmajor { contains(keysigname, "maj") }; bool result { hasminor || hasmajor }; keysigbytes.clear(); if (result) { int sfcount { -99 }; if (hasminor) { for (int i = 0; i < 15; ++i) { if (keysigname == s_key_sig_root_table[i].minor_root_note) { sfcount = i - 7; break; } } } else { for (int i = 0; i < 15; ++i) { if (keysigname == s_key_sig_root_table[i].major_root_note) { sfcount = i - 7; break; } } } if (sfcount != (-99)) { keysigbytes.push_back(midibyte(sfcount)); keysigbytes.push_back(hasminor ? midibyte(1) : 0); } else result = false; } return result; } /** * Converts a note name to a note number, an octave number, and * a base number (0 to 11). Examples and results: * \verbatim * Name Note-number Octave Base * "C" to "B" 0 to 11 -1 0 to 11 * "Cx" to "Bx" 12(x+1) + base x 0 to 11 * nymbers std::stoi() \endverbatim * * \param notename * Provides either a alphabetic note name or the MIDI note number, * in string format. * * \param [out] notenumber * Holds the translated note number. * * \param [out] octavenumber * Holds the octave number calculated from the note number. * * \param [out] basenumber * Holds the octave offset (0 to 11) of the note number, 0 being C, * etc. * * \return * Returns true if the output parameters can be used. */ bool note_name_translation ( const std::string & notename, int & notenumber, int & octavenumber, int & basenumber ) { bool result { ! notename.empty() }; if (result) { int octave { -1 }, base { -1 }, nnumber { -1 }; if (std::isdigit(notename[0])) { nnumber = string_to_int(notename); octave = (nnumber / c_octave_size) - 1; base = nnumber % c_octave_size; } else { std::string basename { char(std::toupper(notename[0])) }; if (notename[1] == '#') basename += "#"; std::size_t nlen { notename.length() }; if (std::isdigit(notename[nlen - 1])) { if (notename[nlen - 2] != '#') octave = notename[nlen - 1] - '0'; /* suitable */ } int counter { 0 }; for (auto k : c_key_text) { if (basename == k) { base = counter; break; } ++counter; } result = base != (-1); } if (result) { octavenumber = octave; basenumber = base; notenumber = c_octave_size * (octave + 1) + base; } } return result; } /** * Possible value entries: * * - Single number. Sets lowest = highest = value. * - Two numbers. Sets lowest = values[0], highest = values[1] * - Note entries starting with "A" to "G" or "a" to "g". * Convert to a note pitch and process as number(s). * - A note letter followed by a digit. Convert the range to * the given octave. * * Mixing numbers and note names not supported. * * \param values * A short (1 or 2 entries) vector of strings representing the * desired pitch range. * * \param [out] lowest * An output parameter for the lowest pitch in the range (0 to 127). * * \param [out] highest * An output parameter for the highest pitch in the range (0 to 127). * * \return * Returns if a valid range can be entered into the output parameters. */ bool get_pitch_range (const tokenization & values, int & lowest, int & highest) { bool result { values.size() > 0 && values.size() < 3 }; if (result) result = ! values[0].empty(); if (result) { int note, octave, base; result = note_name_translation(values[0], note, octave, base); if (result) { lowest = note; if (values.size() == 1) { highest = note + c_octave_size - 1; } else { result = note_name_translation(values[1], note, octave, base); highest = note; } } } return result; } } // namespace seq66 /* * scales.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/sessionfile.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file sessionfile.cpp * * This module declares/defines the base class for managing the special * session.rc file. * * \library seq66 application * \author Chris Ahlstrom * \date 2021-12-29 * \updates 2023-11-05 * \license GNU GPLv2 or above * * This file is a read-only file created manually by the user in order * to create an isolated consistent setup, e.g. for testing. It provides a * single file, always named "session.rc", and locating in the Seq66 * configuration directory. The file has named sections, each of which * specifies the home configuration directory, the client-name, the base * name of the configration files to use, and the name of a log file. * * It's main parameter is rc().session_tag(), which specifies the section * of the session.rc file to use. See the --inspect or -I option. * * See "data/samples/session.rc" for a documented example. */ #include "cfg/sessionfile.hpp" /* seq66::sessionfile class */ #include "cfg/settings.hpp" /* seq66::rc() accessor */ #include "util/filefunctions.hpp" /* seq66::file_extension_set() */ namespace seq66 { static const int s_session_file_version = 0; /** * Principal constructor. * * Versions: * * 0: The initial version. * * \param rcs * The destination for the configuration information. * * \param name * Provides the name of the options file; this is a pathless * file-specification. */ sessionfile::sessionfile ( const std::string & filename, const std::string & tag, rcsettings & rcs ) : configfile (filename, rcs, ".rc"), m_tag_name (tag) { version(s_session_file_version); } /** * Builds and returns the tag name. */ std::string sessionfile::tag_name () const { std::string result; if (! m_tag_name.empty()) { result = "["; result += m_tag_name; result += "]"; } return result; } /** * Parse the ~/.config/seq66/session.rc file. * * \return * Returns true if the file was able to be opened for reading. * Currently, there is no indication if the parsing actually succeeded. */ bool sessionfile::parse () { bool result = false; std::ifstream file(name(), std::ios::in | std::ios::ate); if (set_up_ifstream(file)) /* verifies [Seq66]: version */ { std::string tag = tag_name(); std::string s = get_variable(file, tag, "home"); if (! is_missing_string(s)) { /* * If the user supplies a full path as the "home" value, we * just set that. Otherwise, append it to the default "home" * directory. */ if (name_has_path(s)) rc_ref().home_config_directory(s); else s = pathname_concatenate(rc_ref().home_config_directory(), s); file_message ( "\"Home\" directory", rc_ref().home_config_directory() ); if (make_directory_path(rc_ref().home_config_directory())) result = true; else error_message("Could not find/create that directory"); } if (result) { s = get_variable(file, tag, "config"); if (! is_missing_string(s)) rc_ref().set_config_files(s); s = get_variable(file, tag, "client-name"); if (! is_missing_string(s)) set_client_name(s); s = get_variable(file, tag, "log"); if (is_missing_string(s)) /* empty, "", or ? */ usr().option_logfile(""); else usr().option_logfile(s); } /* * [comments] is not parsed in this read-only file. Only the user * will modify the sessions file. */ } file.close(); /* done parsing the "rc" file */ return result; } } // namespace seq66 /* * sessionfile.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/settings.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file settings.cpp * * This module declares/defines just some of the global (gasp!) variables * and functions in this application. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-05-17 * \updates 2025-07-21 * \license GNU GPLv2 or above * * The first part of this file defines a couple of global structure * instances, followed by the global variables that these structures * completely replace. The second part includes some convenience functions. */ #include /* std::find() */ #include /* std::invalid_argument */ #include "seq66_features.hpp" /* seq66::seq_app_path() */ #include "cfg/settings.hpp" /* std::rc(), usr(), and much more */ #include "os/shellexecute.hpp" /* seq66::open_url(), open_pdf() */ #include "util/filefunctions.hpp" /* seq66::find_file() */ #include "util/strfunctions.hpp" /* seq66::string_to_int() */ /* * We should probably access only the version stored on the user's Seq66 * installation, if available. It is most likely to match the Seq66 version. */ static const bool s_netdocs_first = false; namespace seq66 { /** * The combolist default constructor. Currrently used in qseditoptions and * qsmainwnd. */ combolist::combolist (bool use_current) : m_list_items (), m_use_current (use_current) { if (m_use_current) m_list_items.push_back(""); } combolist::combolist (const tokenization & slist, bool use_current) : m_list_items (), m_use_current (use_current) { if (m_use_current) m_list_items.push_back(""); for (const auto & s : slist) m_list_items.push_back(s); } void combolist::current (const std::string & s) const { if (m_use_current) { combolist * ncthis = const_cast(this); ncthis->m_list_items[0] = s; } } void combolist::set (const std::string & s, int index) const { if (index < count()) { combolist * ncthis = const_cast(this); ncthis->m_list_items[index] = s; } } void combolist::current (int v) const { if (m_use_current) { combolist * ncthis = const_cast(this); ncthis->m_list_items[0] = std::to_string(v); // FIXME ! } } std::string combolist::at (int index) const { std::string result; if (index >= 0 && index < int(m_list_items.size())) result = m_list_items[index]; return result; } int combolist::ctoi (int index) const { int result = (-1); std::string s = at(index); if (! s.empty()) result = string_to_int(s); return result; } bool combolist::valid (const std::string & target) const { bool result = false; if (! target.empty()) { auto it = std::find(m_list_items.begin(), m_list_items.end(), target); result = it != m_list_items.end(); } return result; } /** * Had to update this one, after checking, to return (-1) instead of zero to * indicate an error. This change is part of issue #128, where expand-record * using auto-step tries to look up measure "9" in the seqedit measures list, * fails, returns 0, and sets the pattern length to 1, suddenly. */ int combolist::index (const std::string & target) const { int result = (-1); /* for failure, use first item */ int counter = 0; for (const auto & s : m_list_items) { if (counter == 0 && m_use_current) { ++counter; continue; } if (s == target) { result = counter; break; } ++counter; } return result; } int combolist::index (int value) const { std::string target = std::to_string(value); return index(target); } /** * These lists are useful in the user-interfaces. */ const tokenization & measure_items () { static const tokenization s_measure_list { "1", "2", "3", "4", "5", "6", "7", "8", "16", "24", "32", "64", "96", "128" }; return s_measure_list; } /** * Beats-per-bar values. */ const tokenization & beats_per_bar_items () { static const tokenization s_beats_per_bar_list { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "32" }; return s_beats_per_bar_list; } /** * MIDI cannot store any beat-width values that are not powers of 2. * So we do not show them. The user can still enter such values, but * problems will ensue. */ const tokenization & beatwidth_items () { static const tokenization s_beatwidth_list { "1", "2", "4", "8", "16", "32" }; return s_beatwidth_list; } /** * These static items are used to fill in and select the proper snap values for * the grids. These values are also used for note length. See * update_grid_snap() and update_note_length() in qseqeditframe. */ const tokenization & snap_items () { static const tokenization s_snap_list { "1", "2", "4", "8", "16", "32", "64", "128", "-", "3", "6", "12", "24", "48", "96", "192" }; return s_snap_list; } /** * Snap values for the performance/song editor when snap is enabled. * These values are converted to 1/1, 1/2, etc. and represent fractions * of a meansure. With snap disabled, the full length of the pattern is * applicable, and trigger sliding is arbitrary. */ const tokenization & perf_snap_items () { static const tokenization s_snap_list { "1", "2", "3", "4", "8", "16", "32" }; return s_snap_list; } /** * Zoom values for the pattern editor. Keep these values as consecutive * powers of 2, ranging from power-of-0 to power-of-9. The higher the * number, the more zoomed-out. This range is high to help support large * PPQNs. */ const tokenization & zoom_items () { static const tokenization s_zoom_list { "1", "2", "4", "8", "16", "32", "64", "128", "256", "512", "1024" }; return s_zoom_list; } int zoom_item (int i) { int result = 0; if (i >= 0) { const tokenization & zs = zoom_items(); if (i < int(zs.size())) result = string_to_int(zs[i]); } return result; } /** * To get around the long-standing limitation of zoom no less than 1, * we want to add additional items that can be used as factors in * drawing further expanded horizontal zoom. * * This list actually goes in the opposite direction: higher numbers * are more zoomed in, as each number is an expansion factor. */ const tokenization & expanded_zoom_items () { static const tokenization s_expanded_zoom_list { "2", "4", "8", "16" }; return s_expanded_zoom_list; } int expanded_zoom_item (int i) { int result = 0; if (i < 0) { const tokenization & expz = expanded_zoom_items(); i = -i; if (i < int(expz.size())) result = string_to_int(expz[i]); } return result; } /** * Recording-volume values for the pattern editor. Holds the entries for the * "Vel" drop-down. The first value matches usr().preserve_velocity(). It * corresponds to the "Free" recording-volume entry where the incoming * velocity is kept. */ const tokenization & rec_vol_items () { static const tokenization s_rec_vol_list { "Free", "127", "112", "96", "80", "64", "48", "32", "16" }; return s_rec_vol_list; } /** * The list of supported recording styles as used in qseditoptions. */ const tokenization & rec_style_items () { static const tokenization s_rec_style_items { "Overdub", "Overwrite", "Expand", "One-Shot", "1-Shot Reset" }; return s_rec_style_items; } /** * Returns a reference to the global rcsettings object. Why a function * instead of direct variable access? Encapsulation. We are then free to * change the way "global" settings are accessed, without changing client * code. * * \return * Returns the global object g_rcsettings. */ rcsettings & rc () { static rcsettings s_rcsettings; return s_rcsettings; } /** * Provides the replacement for all of the other settings in the "user" * configuration file, plus some of the "constants" in the globals module. * Returns a reference to the global usrsettings object, for better * encapsulation. * * \return * Returns the global object g_usrsettings. */ usrsettings & usr () { static usrsettings g_usrsettings; return g_usrsettings; } /** * Call set_defaults() on the "rc" and "usr" objects. */ void set_configuration_defaults () { rc().set_defaults(); usr().set_defaults(); } /** * Available PPQN values. The default is 192. This list is wrapped in an * accessor function. This list is useful in the user-interface. Used in * qsmainwnd and in qseditoptions. Note that generally PPQN values are * best when divisible by 24. */ const tokenization & supported_ppqns () { static tokenization s_supported_ppqn_list { "24", "32", "48", "96", "120", /* "24" added, "32" added back */ "192", "240", "384", "480", "768", /* "480" added */ "960", "1920", "2400", "3840", "7680", "9600", "19200" }; return s_supported_ppqn_list; } /** * Used for overriding the JACK server's buffer size. The first entry * translates to 0, and disables this override. */ const tokenization & jack_buffer_size_list () { static tokenization s_buffer_size_list { "0", /* do not override JACK server */ "16", /* might fail on many systems */ "32", /* might fail on some systems */ "64", /* might fail on a few systems */ "128", "256", "512", "1024", "2048", "4096", "8192" }; return s_buffer_size_list; } /** * Common code for handling PPQN settings. Putting it here means we can * reduce the reliance on the global PPQN, and have a lot more flexibility in * changing the PPQN. * * However, this function works completely only if the "user" configuration * file has already been read. In some cases we may need to retrofit the * desired PPQN value! * * \param ppqn * Provides the PPQN value to be used. The default value is * c_use_default_ppqn. * * \return * Returns the ppqn parameter, unless that parameter is one of the * special values above, or is illegal, as noted above. */ int choose_ppqn (int ppqn) { int result = ppqn; if (result == c_use_default_ppqn) result = usr().default_ppqn(); /* usr().midi_ppqn() */ else if (result == c_use_file_ppqn) result = usr().file_ppqn(); else if (! ppqn_in_range(result)) /* file, in-range PPQN */ result = usr().midi_ppqn(); /* usr().default_ppqn() */ return result; } /** * Indicates if the PPQN value is in the legal range of usable PPQN values. */ bool ppqn_in_range (int ppqn) { return usr().use_file_ppqn() || usr().is_ppqn_valid(ppqn); } /** * First try the web, then the directory list. Actually, let's do it the * other way, on the theory that the local install should be used for * documentation as well. */ bool open_user_manual () { static const std::string s_url = "https://ahlstromcj.github.io/docs/seq66/seq66-user-manual.pdf"; bool result = false; bool netdocs_done = false; if (s_netdocs_first) { result = open_url(s_url); netdocs_done = true; } if (! result) { std::string docpath = find_file ( doc_folder_list(), "seq66-user-manual.pdf" ); result = ! docpath.empty(); if (result) result = open_pdf(docpath); else if (! netdocs_done) result = open_url(s_url); } return result; } /** * If s_netdocs_first is true, we first try the web link. If that fails, * then we look through the likely locations for the tutorial on the * local host. */ bool open_tutorial () { static const std::string s_url = "https://ahlstromcj.github.io/docs/seq66/tutorial/index.html"; bool result = false; bool netdocs_done = false; if (s_netdocs_first) { result = open_url(s_url); netdocs_done = true; } if (! result) { std::string tutpath = find_file(tutorial_folder_list(), "index.html"); result = ! tutpath.empty(); if (result) result = open_url(tutpath); else if (! netdocs_done) result = open_url(s_url); } return result; } /** * This list is useful to look up the installed documentation. It starts with * the possible installation areas. For debugging, the relative directories, * either the source directory or the shadow directory, are added. */ const tokenization & doc_folder_list () { static bool s_uninitialized = true; static tokenization s_folder_list; if (s_uninitialized) { #if defined SEQ66_PLATFORM_WINDOWS static std::string s_64_dir = "C:/Program Files/Seq66/data/share/doc"; /* * Currently cannot do 32-bit builds in Windows. * * static std::string s_32_dir = * "C:/Program Files (x86)/Seq66/data/share/doc"; * s_32_dir[0] = app_path[0]; * s_folder_list.push_back(s_32_dir); */ std::string app_path = seq_app_path(); s_64_dir[0] = app_path[0]; s_folder_list.push_back(s_64_dir); #else static std::string s_usr_dir; static std::string s_usr_local_dir; s_usr_dir = "/usr/share/doc/" + seq_api_subdirectory(); s_usr_local_dir = "/usr/local/share/doc/" + seq_api_subdirectory(); s_folder_list.push_back(s_usr_dir); s_folder_list.push_back(s_usr_local_dir); #endif #if defined SEQ66_PLATFORM_DEBUG s_folder_list.push_back("data/share/doc"); /* source */ s_folder_list.push_back("../seq66/data/share/doc"); /* shadow */ #endif s_uninitialized = false; } return s_folder_list; } /** * Currently, we don't access the tutorial on the web. It's store there at * ahlstromcj.github.io. */ const tokenization & tutorial_folder_list () { static bool s_uninitialized = true; static tokenization s_folder_list; if (s_uninitialized) { #if defined SEQ66_PLATFORM_WINDOWS static std::string s_64_dir = "C:/Program Files/Seq66/data/share/doc/tutorial"; /* * Currently cannot do 32-bit builds in Windows. * * static std::string s_32_dir = * "C:/Program Files (x86)/Seq66/data/share/doc/tutorial"; * s_32_dir[0] = app_path[0]; * s_folder_list.push_back(s_32_dir); */ std::string app_path = seq_app_path(); s_64_dir[0] = app_path[0]; s_folder_list.push_back(s_64_dir); #else static std::string s_usr_dir; static std::string s_usr_local_dir; s_usr_dir = "/usr/share/doc/" + seq_api_subdirectory(); s_usr_local_dir = "/usr/local/share/doc/" + seq_api_subdirectory(); s_usr_dir += "/tutorial"; s_usr_local_dir += "/tutorial"; s_folder_list.push_back(s_usr_dir); s_folder_list.push_back(s_usr_local_dir); s_folder_list.push_back("data/share/doc/tutorial"); #if defined SEQ66_PLATFORM_DEBUG s_folder_list.push_back("../seq66/data/share/doc/tutorial"); #endif #endif s_uninitialized = false; } return s_folder_list; } /** * To be refined. */ const tokenization & share_doc_folder_list (const std::string & path_end) { static bool s_uninitialized = true; static tokenization s_folder_list; if (s_uninitialized) { #if defined SEQ66_PLATFORM_WINDOWS std::string s_64_dir = "C:/Program Files/Seq66/data/share/doc"; std::string app_path = seq_app_path(); std::string path = s_64_dir; if (! path_end.empty()) path = pathname_concatenate(s_64_dir, path_end); path[0] = app_path[0]; /* change C: if needed */ s_folder_list.push_back(path); #else std::string s_usr_dir = "/usr/share/doc/"; std::string s_usr_local_dir = "/usr/local/share/doc/"; std::string s_build_dir = "data/share/doc/"; std::string s_shadow_dir = "../seq66/data/share/doc/"; s_usr_dir += seq_api_subdirectory(); s_usr_local_dir += seq_api_subdirectory(); if (! path_end.empty()) { s_usr_dir = pathname_concatenate(s_usr_dir, path_end); s_usr_local_dir = pathname_concatenate(s_usr_local_dir, path_end); s_build_dir = pathname_concatenate(s_build_dir, path_end); s_shadow_dir = pathname_concatenate(s_shadow_dir, path_end); } s_folder_list.push_back(s_usr_dir); s_folder_list.push_back(s_usr_local_dir); s_folder_list.push_back(s_build_dir); s_folder_list.push_back(s_shadow_dir); #endif s_uninitialized = false; } return s_folder_list; } /** * This function searches only on the local drive. * * \return * Returns the contents of the file */ std::string open_share_doc_file ( const std::string & filename, const std::string & path_end ) { std::string result; std::string path = find_file(share_doc_folder_list(path_end), filename); if (! path.empty()) result = file_read_string(path); if (result.empty()) file_error("Cannot find", path); return result; } } // namespace seq66 /* * settings.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/userinstrument.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file userinstrument.cpp * * This module declares/defines just some of the global (gasp!) variables * in this application. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-24 * \updates 2018-11-24 * \license GNU GPLv2 or above * * Note that this module also sets the legacy global variables, so that * they can be used by modules that have not yet been cleaned up. */ #include "cfg/userinstrument.hpp" /* seq66::userinstrument structure */ namespace seq66 { /** * Default constructor. Fills in the defaults for the instrument definition, * sets its name, and provides some light validation. * * \param name * The name of the instrument, valid only if it is not empty. */ userinstrument::userinstrument (const std::string & name) : m_is_valid (false), m_controller_count (0), m_instrument_def () { clear(); set_name(name); } /** * Copy constructor. * * \param rhs * The sources of the data for the copy. */ userinstrument::userinstrument (const userinstrument & rhs) : m_is_valid (rhs.m_is_valid), m_controller_count (rhs.m_controller_count), m_instrument_def () { copy_definitions(rhs); } /** * Principal assignment operator. * * \param rhs * The sources of the data for the assignment. * * \return * Returns a reference to this object. */ userinstrument & userinstrument::operator = (const userinstrument & rhs) { if (this != &rhs) { m_is_valid = rhs.m_is_valid; m_controller_count = rhs.m_controller_count; copy_definitions(rhs); } return *this; } /** * Sets the default values. Also invalidates the object. */ void userinstrument::clear () { m_is_valid = false; m_controller_count = 0; m_instrument_def.instrument.clear(); for (int c = 0; c < c_midi_controller_max; ++c) { m_instrument_def.controllers_active[c] = false; m_instrument_def.controllers[c].clear(); } } /** * \setter m_instrument_def.instrument * If the name parameter is not empty, the validity flag is set to * true, otherwise it is set to false. Too tricky? * * \param instname * The name of the instrument, valid only if it is not empty. */ void userinstrument::set_name (const std::string & instname) { m_instrument_def.instrument = instname; m_is_valid = ! instname.empty(); } /** * \setter m_instrument_def.controllers[c] and .controllers_active[c] * Only sets the controller values if the object is already valid. * * \param c * The index of the desired controller. * * \param cname * The name of the controller to be set as the controller name. * * \param isactive * A flag that indicates if the desired controller is active. */ bool userinstrument::set_controller ( int c, const std::string & cname, bool isactive ) { bool result = m_is_valid && c >= 0 && c < c_midi_controller_max; if (result) { m_instrument_def.controllers[c] = cname; m_instrument_def.controllers_active[c] = isactive; if (isactive) ++m_controller_count; else { infoprint("Use this as a breakpoint"); } } return result; } /** * \getter m_instrument_def.controllers[c] * * \param c * The index of the desired controller. * * \return * The name of the desired controller has is returned. If the * index c is out of range, or the object is not valid, then a * reference to an internal, empty string is returned. */ const std::string & userinstrument::controller_name (int c) const { static const std::string s_empty; if (m_is_valid && c >= 0 && c < c_midi_controller_max) return m_instrument_def.controllers[c]; else return s_empty; } /** * \getter m_instrument_def.controllers_active[c] * * \param c * The index of the desired controller. * * \return * The status of the desired controller has is returned. If the * index c is out of range, or the object is not valid, then false is * returned. */ bool userinstrument::controller_active (int c) const { if (m_is_valid && c >= 0 && c < c_midi_controller_max) return m_instrument_def.controllers_active[c]; else return false; } /** * Copies the array members from one instance of userinstrument to this * one. Does not include the validity flag. * * \param rhs * The sources of the data for the partial copy. */ void userinstrument::copy_definitions (const userinstrument & rhs) { m_instrument_def.instrument = rhs.m_instrument_def.instrument; for (int c = 0; c < c_midi_controller_max; ++c) { m_instrument_def.controllers_active[c] = rhs.m_instrument_def.controllers_active[c]; m_instrument_def.controllers[c] = rhs.m_instrument_def.controllers[c]; } } } // namespace seq66 /* * userinstrument.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/usermidibus.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file usermidibus.cpp * * This module declares/defines just some of the global (gasp!) variables * in this application. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-24 * \updates 2021-01-21 * \license GNU GPLv2 or above * * Note that this module also sets the legacy global variables, so that * they can be used by modules that have not yet been cleaned up. */ #include "cfg/settings.hpp" /* seq66::usr() accessor */ #include "cfg/usermidibus.hpp" /* seq66::usermidibus structure */ #include "cfg/userinstrument.hpp" /* seq66::userinstrument structure */ namespace seq66 { /** * This constant indicates that a configuration file numeric value is * the default value for specifying that an instrument is a GM * instrument. Used in the "user" configuration-file processing. */ static const int c_gm_instrument_flag = (-1); /** * Default constructor. * * \param name * The name of the buss, valid only if it is not empty. */ usermidibus::usermidibus (const std::string & name) : m_is_valid (false), m_channel_count (0), m_midi_bus_def () { clear(); set_name(name); } /** * Copy constructor. * * \param rhs * The sources of the data for the copy. */ usermidibus::usermidibus (const usermidibus & rhs) : m_is_valid (rhs.m_is_valid), m_channel_count (rhs.m_channel_count), m_midi_bus_def () // a constant-size array { copy_definitions(rhs); } /** * Principal assignment operator. * * \param rhs * The sources of the data for the assignment. * * \return * Returns a reference to this object. */ usermidibus & usermidibus::operator = (const usermidibus & rhs) { if (this != &rhs) { m_is_valid = rhs.m_is_valid; m_channel_count = rhs.m_channel_count; copy_definitions(rhs); } return *this; } /** * Sets the default values. Also invalidates the object. All 16 of the * channels are set to (-1). */ void usermidibus::clear () { m_is_valid = false; m_channel_count = 0; m_midi_bus_def.alias.clear(); for (int channel = 0; channel < c_midichannel_max; ++channel) m_midi_bus_def.instrument[channel] = c_gm_instrument_flag; } /** * \getter m_midi_bus_def.instrument[channel] * * \param channel * Provides the desired buss channel number. * * \return * The instrument number of the desired buss channel is returned. If * the channel number is out of range, or the object is not valid, * then c_gm_instrument_flag (-1) is returned. */ int usermidibus::instrument (int channel) const { if (m_is_valid && channel >= 0 && channel < c_midichannel_max) return m_midi_bus_def.instrument[channel]; else return c_gm_instrument_flag; } std::string usermidibus::instrument_name (int channel) const { std::string result; if (m_is_valid && channel >= 0 && channel < c_midichannel_max) { int inumber = m_midi_bus_def.instrument[channel]; const userinstrument & uin = usr().instrument(inumber); result = uin.name(); } else result = "GM"; return result; } /** * \getter m_midi_bus_def.instrument[channel] * * Does not alter the validity flag, just checks it. * * \param channel * Provides the desired buss channel number. * * \param instrum * Provides the instrument number to set that channel to. */ bool usermidibus::set_instrument (int channel, int instrum) { bool result = m_is_valid && channel >= 0 && channel < c_midichannel_max; if (result) { m_midi_bus_def.instrument[channel] = instrum; if (instrum != c_gm_instrument_flag) ++m_channel_count; } return result; } /** * Copies the member fields from one instance of usermidibus to this * one. Does not include the validity flag. */ void usermidibus::copy_definitions (const usermidibus & rhs) { m_midi_bus_def.alias = rhs.m_midi_bus_def.alias; for (int channel = 0; channel < c_midichannel_max; ++channel) { m_midi_bus_def.instrument[channel] = rhs.m_midi_bus_def.instrument[channel]; } } } // namespace seq66 /* * usermidibus.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/usrfile.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file usrfile.cpp * * This module declares/defines the base class for managing the user's * ~/.seq66usr or ~/.config/seq66.rc * configuration file. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-23 * \updates 2025-07-17 * \license GNU GPLv2 or above * * Note that the parse function has some code that is not yet enabled. * Also note that, unlike the "rc" settings, these settings have no * user-interface. One must use a text editor to modify its settings. */ #include /* std::setw() manipulator */ #include "cfg/settings.hpp" /* seq66::usr() "global" settings */ #include "cfg/usrfile.hpp" /* seq66::usrfile */ #include "cfg/userinstrument.hpp" /* seq66::userinstrument */ #include "util/strfunctions.hpp" /* seq66::strip_quotes() */ namespace seq66 { static const int s_usr_legacy = 5; static const int s_usr_smf_1 = 8; static const int s_usr_file_version = 16; /** * Principal constructor. * * Versions: * * 0: The initial version, close to the Seq64 format. * 4: 2021-05-15. Disabled using grid-style and grid-brackets settings. * 5: 2021-06-08. Transition to get-variable for booleans/integers. * 6: 2021-07-26. Added progress-note-min and progress-note-max. * 7: 2021-09-20. Added "style-sheet-active" & "lock-main-window" flags. * 8: 2021-10-06: Added "convert-to-smf-1". * 9: 2021-10-26: Added "swap-coordinates". * 10: 2022-07-21: Added "pattern-box-shown" (issue #78). * 11: 2023-10-12: Added more new-pattern options (e.g. notemap) * 12: 2023-11-02: Moved style-sheets to the 'rc' file. * 13: 2024-02-23: Added elliptical progress-box option. * 14: 2024-02-23: Added progress-bar-thickness and gridlines-thick. * 15: 2025-04-27: Added progress-box-show-cc. * 16: 2025-06-19: Added auto-add-time-sig. * * \param name * Provides the full file path specification to the configuration file. * * \param rcs * The source/destination for the configuration information. In most * cases, the caller will pass in seq66::rc(), the "global" rcsettings * object. */ usrfile::usrfile (const std::string & name, rcsettings & rcs) : configfile (name, rcs, ".usr") { version(s_usr_file_version); /* now at 16 */ } /** * Provides a purely internal, ad hoc helper function to create numbered * section names for the usrfile class. * * \param label * The base-name of the section. * * \param value * The numeric value to append to the section name. * * \return * Returns a string of the form "[basename-1]". */ static std::string make_section_name (const std::string & label, int value) { std::string result = "["; result += label; result += "-"; result += std::to_string(value); result += "]"; return result; } /** * Provides a debug dump of basic information to help debug a surprisingly * intractable problem with all busses having the name and values of the last * buss in the configuration. Does work only if SEQ66_PLATFORM_DEBUG is * defined; see the user_settings class. */ void usrfile::dump_setting_summary () { #if defined SEQ66_PLATFORM_DEBUG_TMI usr().dump_summary(); #endif } /** * Parses a "usr" file, filling in the given performer object. This function * opens the file as a text file (line-oriented). * * \return * Returns true if the parsing succeeded. */ bool usrfile::parse () { std::ifstream file(name().c_str(), std::ios::in | std::ios::ate); if (! set_up_ifstream(file)) /* verifies [Seq66]: version */ return false; std::string temp = parse_version(file); if (temp.empty() || file_version_is_old(file)) rc().auto_usr_save(true); temp = parse_comments(file); if (! temp.empty()) usr().comments_block().set(temp); usr().clear_buses_and_instruments(); if (! rc_ref().reveal_ports()) { /* * [user-midi-bus-definitions] */ int buses = 0; if (line_after(file, "[user-midi-bus-definitions]")) sscanf(scanline(), "%d", &buses); /* * [user-midi-bus-x] */ for (int bus = 0; bus < buses; ++bus) { std::string label = make_section_name("user-midi-bus", bus); if (! line_after(file, label)) break; std::string bussname = strip_quotes(line()); if (usr().add_bus(bussname)) { (void) next_data_line(file); int instruments = 0; sscanf(scanline(), "%d", &instruments); /* no. of channels */ for (int j = 0; j < instruments; ++j) { int channel, instrument; (void) next_data_line(file); sscanf(scanline(), "%d %d", &channel, &instrument); if (! usr().set_bus_instrument(bus, channel, instrument)) break; } } else { msgprintf ( msglevel::error, "Error adding %s (line = '%s')", label.c_str(), scanline() /* see issue #124 */ ); } } } /* * [user-instrument-definitions] */ int instruments = 0; if (line_after(file, "[user-instrument-definitions]")) sscanf(scanline(), "%d", &instruments); /* * [user-instrument-x] */ for (int inst = 0; inst < instruments; ++inst) { std::string label = make_section_name("user-instrument", inst); if (! line_after(file, label)) break; std::string instname = strip_quotes(line()); if (usr().add_instrument(instname)) { int cccount = 0; (void) next_data_line(file); sscanf(scanline(), "%d", &cccount); for (int cc = 0; cc < cccount; ++cc) { if (! next_data_line(file)) break; tokenization instpair = tokenize(line(), " "); if (instpair.size() >= 1) { int c = string_to_int(instpair[0]); std::string name; for (int i = 1; i < int(instpair.size()); ++i) { if (instpair[i][0] == '#') break; if (i > 1) name += " "; name += instpair[i]; } name = strip_quotes(name); if (name.empty()) name = "---"; if (! usr().set_instrument_controllers(inst, c, name, true)) break; } } } else { /* * clang says "error: member reference base type 'const char *' * is not a structure or union." See issue #124. */ msgprintf ( msglevel::error, "Error adding %s (line = '%s')", label.c_str(), scanline() ); } } /* * [user-interface-settings] * * These are new items stored in the user file. Only variables whose * effects we can be completely sure of are read from this section, and * used, at this time. More to come. */ std::string tag = "[user-interface-settings]"; if (file_version_number() < s_usr_legacy) { (void) version_error_message("usr", file_version_number()); rc().auto_usr_save(true); } else { bool flag = get_boolean(file, tag, "swap-coordinates"); usr().swap_coordinates(flag); int rows = get_integer(file, tag, "mainwnd-rows"); (void) usr().mainwnd_rows(rows); int cols = get_integer(file, tag, "mainwnd-columns"); (void) usr().mainwnd_cols(cols); int scratch = get_integer(file, tag, "mainwnd-spacing"); usr().mainwnd_spacing(scratch); scratch = get_integer(file, tag, "default-zoom"); usr().base_zoom(scratch); flag = get_boolean(file, tag, "global-seq-feature"); usr().global_seq_feature(flag); flag = get_boolean(file, tag, "progress-bar-thick"); usr().progress_bar_thick(flag); scratch = get_integer(file, tag, "progress-bar-thickness"); if (scratch <= 0) scratch = flag ? 2 : 1 ; /* apply thickness or not? */ else if (! flag) scratch = 1; usr().progress_bar_thickness(scratch); flag = get_boolean(file, tag, "progress-box-elliptical"); usr().progress_box_elliptical(flag); flag = get_boolean(file, tag, "follow-progress"); usr().follow_progress(flag); flag = get_boolean(file, tag, "gridlines-thick"); usr().gridlines_thick(flag); flag = get_boolean(file, tag, "inverse-colors"); std::string color = get_variable(file, tag, "time-fg-color"); if (color.empty()) rc().auto_usr_save(true); /* and leave it at the default */ else usr().time_fg_color(color); color = get_variable(file, tag, "time-bg-color"); if (color.empty()) rc().auto_usr_save(true); /* and leave it at the default */ else usr().time_bg_color(color); usr().inverse_colors(flag); flag = get_boolean(file, tag, "dark-theme"); usr().dark_theme(flag); scratch = get_integer(file, tag, "window-redraw-rate"); usr().window_redraw_rate(scratch); double scale = get_float(file, tag, "window-scale"); double scaley = get_float(file, tag, "window-scale-y"); usr().window_scale(scale, scaley, true); /* x & y the same */ flag = get_boolean ( file, tag, "enable-learn-confirmation", 0, true ); usr().enable_learn_confirmation(flag); } usr().normalize(); /* recalculate */ /* * [user-midi-ppqn] */ tag = "[user-midi-ppqn]"; int ppqn = get_integer(file, tag, "default-ppqn"); bool flag = get_boolean(file, tag, "use-file-ppqn"); usr().default_ppqn(ppqn); usr().midi_ppqn(ppqn); /* can change based on file PPQN */ usr().use_file_ppqn(flag); /* * [user-randomization] */ tag = "[user-randomization]"; int randvalue = get_integer(file, tag, "jitter-divisor"); if (configfile::is_missing(randvalue)) { rc().auto_usr_save(true); } else { usr().jitter_divisor(randvalue); randvalue = get_integer(file, tag, "amplitude"); usr().randomization_amount(randvalue); } /* * [user-midi-settings] */ tag = "[user-midi-settings]"; if (file_version_number() < s_usr_smf_1) { (void) version_error_message("usr", file_version_number()); rc().auto_usr_save(true); } else { bool flag = get_boolean(file, tag, "convert-to-smf-1"); std::string c = get_variable(file, tag, "convert-to-smf-1"); bool convert = c.empty() ? true : flag ; usr().convert_to_smf_1(convert); int scratch = get_integer(file, tag, "beats-per-bar"); usr().midi_beats_per_bar(scratch); float f = get_float(file, tag, "beats-per-minute"); usr().midi_beats_per_minute(midibpm(f)); scratch = get_integer(file, tag, "beat-width"); usr().midi_beat_width(scratch); scratch = get_integer(file, tag, "buss-override"); usr().midi_buss_override(bussbyte(scratch)); scratch = get_integer(file, tag, "velocity-override"); usr().velocity_override(scratch); scratch = get_integer(file, tag, "bpm-precision"); usr().bpm_precision(scratch); f = get_float(file, tag, "bpm-step-increment"); usr().bpm_step_increment(midibpm(f)); f = get_float(file, tag, "bpm-page-increment"); usr().bpm_page_increment(midibpm(f)); f = get_float(file, tag, "bpm-minimum"); usr().midi_bpm_minimum(midibpm(f)); f = get_float(file, tag, "bpm-maximum"); usr().midi_bpm_maximum(midibpm(f)); if (file_version_is_old(file)) { usr().auto_add_time_sig(true); } else { flag = get_boolean(file, tag, "auto-add-time-sig"); usr().auto_add_time_sig(flag); } } /* * -o special options support. */ tag = "[user-options]"; flag = get_boolean(file, tag, "daemonize"); usr().option_daemonize(flag); std::string fname = get_variable(file, tag, "log"); if (fname == "none") fname.clear(); bool gotlog = ! fname.empty(); if (gotlog) fname = strip_quotes(fname); /* * In running a "session" from sessions.rc, don't use the 'usr' setting * for log-file. */ if (! rc().alt_session()) { usr().option_logfile(fname); usr().option_use_logfile(gotlog); } fname = get_variable(file, tag, "pdf-viewer"); if (fname.empty()) { /* * See above. rc().auto_usr_save(true); */ } else { fname = strip_quotes(fname); usr().user_pdf_viewer(fname); } fname = get_variable(file, tag, "browser"); if (fname.empty()) { /* * See above. rc().auto_usr_save(true); */ } else { fname = strip_quotes(fname); usr().user_browser(fname); } /* * [user-ui-tweaks]. The variables in this section are, in this order: * key-height and use-new-seqedit, which are currently not supporting the * new DOS-INI variable setting feature supported by get_variable(). * The note-resume option is now implemented as per issue #5. */ tag = "[user-ui-tweaks]"; if (line_after(file, tag)) { int scratch = 0; int count = sscanf(scanline(), "%d", &scratch); if (count == 1) { usr().key_height(scratch); (void) next_data_line(file); } else { int h = get_integer(file, tag, "key-height"); usr().key_height(h); } std::string s = get_variable(file, tag, "key-view"); usr().key_view(s); bool flag = get_boolean(file, tag, "note-resume"); usr().resume_note_ons(flag); #if defined USE_USR_STYLE_SHEET /* * Moved to the 'rc' file for consistency and ease of import/export. */ flag = get_boolean(file, tag, "style-sheet-active"); usr().style_sheet_active(flag); s = get_variable(file, tag, "style-sheet"); usr().style_sheet(strip_quotes(s)); if (s.empty()) usr().style_sheet_active(false); #else s = get_variable(file, tag, "style-sheet"); if (! is_questionable_string(s)) { rc().style_sheet_filename(strip_quotes(s)); if (s.empty()) { rc().style_sheet_active(false); } else { flag = get_boolean(file, tag, "style-sheet-active"); rc().style_sheet_active(flag); } rc().auto_usr_save(true); rc().auto_rc_save(true); } #endif int v = get_integer(file, tag, "fingerprint-size"); usr().fingerprint_size(v); double w = double(get_float(file, tag, "progress-box-width")); double h = double(get_float(file, tag, "progress-box-height")); usr().progress_box_size(w, h); flag = get_boolean(file, tag, "progress-box-shown"); usr().progress_box_shown(flag); s = get_variable(file, tag, "progress-box-show-cc"); if (! is_questionable_string(s)) { flag = get_boolean(file, tag, "progress-box-show-cc"); usr().progress_box_show_cc(flag); } v = get_integer(file, tag, "progress-note-min"); int x = get_integer(file, tag, "progress-note-max"); usr().progress_note_min_max(v, x); flag = get_boolean(file, tag, "lock-main-window"); usr().lock_main_window(flag); } tag = "[user-session]"; std::string s = get_variable(file, tag, "session"); usr().session_manager(s); s = get_variable(file, tag, "url"); usr().session_url(strip_quotes(s)); flag = get_boolean(file, tag, "visibility", 0, true); usr().session_visibility(flag); tag = "[pattern-editor]"; if (find_tag(file, tag) == (-1)) { tag = "[new-pattern-editor]"; rc().auto_usr_save(true); /* write the shorter tag at exit */ } flag = get_boolean(file, tag, "escape-pattern"); usr().escape_pattern(flag); flag = get_boolean(file, tag, "apply-to-new-only"); usr().pattern_new_only(flag); flag = get_boolean(file, tag, "armed"); usr().pattern_armed(flag); flag = get_boolean(file, tag, "thru"); usr().pattern_thru(flag); flag = get_boolean(file, tag, "record"); usr().pattern_record(flag); flag = get_boolean(file, tag, "tighten"); if (flag) usr().record_alteration(alteration::tighten); usr().pattern_tighten(flag); flag = get_boolean(file, tag, "qrecord"); if (flag) usr().record_alteration(alteration::quantize); usr().pattern_qrecord(flag); flag = get_boolean(file, tag, "notemap"); if (flag) usr().record_alteration(alteration::notemap); usr().pattern_notemap(flag); s = get_variable(file, tag, "record-style"); usr().set_pattern_record_style(s); flag = get_boolean(file, tag, "wrap-around"); usr().pattern_wraparound(flag); /* * Consider: * * Translate the pattern_x flag values to an seq66::alteration * value and pass it to usrsettings::record_alteration(). * * To do? Add "random" and "jitter" to the new-pattern values. * * Pass the usr().pattern_record_style() result to * usr().pattern_record_code(). */ /* * We have all of the data. Close the file. */ dump_setting_summary(); file.close(); /* End Of File, EOF, done! */ return true; } /** * Parses a "usr" file, but only for options important to start * the daemonization process: 'daemonization' and 'log'. Called by * cmdlineopts::parse_daemonization() if we're not in verbose() mode. * * \param [out] startdaemon * Set to true if the function succeed and [user-options] daemonize is * true. Set to false otherwize, so it can still be checked. * * \param [out] logfile * Set to the log-file specified in [user-options]. Set to empty * if parsing failed. * * \return * Returns true if the parsing succeeded. */ bool usrfile::parse_daemonization (bool & startdaemon, std::string & logfile) { std::ifstream file(name().c_str(), std::ios::in | std::ios::ate); bool result = set_up_ifstream(file); /* verifies [Seq66]: version */ if (result) { std::string tag = "[user-options]"; bool flag = get_boolean(file, tag, "daemonize"); startdaemon = flag; /* set this side-effect */ usr().option_daemonize(flag); /* set the 'usr' flag as well */ /* * In running a "session" from sessions.rc, don't use the 'usr' setting * for log-file. */ if (! rc().alt_session()) { std::string fname = get_variable(file, tag, "log"); bool gotlog = ! fname.empty(); if (gotlog) { fname = strip_quotes(fname); /* set this side-effect */ logfile = fname; /* return this side-effect */ } usr().option_logfile(fname); /* set 'usr' flag as well */ usr().option_use_logfile(gotlog); /* easy flag to use, man! */ } } else { result = false; startdaemon = false; logfile = ""; } return result; } /** * This function just returns false, as there is no "performer" information * in the user-file yet. * * \return * Returns true if the writing succeeded. */ bool usrfile::write () { std::ofstream file(name().c_str(), std::ios::out | std::ios::trunc); if (file.is_open()) { file_message("Write usr", name()); } else { file_error("Write open fail", name()); return false; } dump_setting_summary(); /* * Header commentary. Write out comments about the nature of this file. */ write_date(file, "user ('usr')"); file << "# 'usr' file. Edit it and place it in ~/.config/seq66. It allows naming each\n" "# MIDI bus/port, channel, and control code.\n" ; write_seq66_header(file, "usr", version()); write_comment(file, usr().comments_block().text()); file << "\n" "# [user-midi-bus-definitions]\n" "#\n" "# 1. Define instruments and their control-code names, as applicable.\n" "# 2. Define MIDI busses, names, and the instruments on each channel.\n" "#\n" "# Channels are counted from 0-15, not 1-16. Instruments not set here are set\n" "# to -1 and are GM (General MIDI). These labels are shown in MIDI Clocks,\n" "# Inputs, the pattern editor buss, channel, and event drop-downs. To disable\n" "# entries, set counts to 0.\n" ; /* * [user-midi-bus-definitions] */ file << "\n[user-midi-bus-definitions]\n\n" << usr().bus_count() << " # number of user-defined MIDI busses\n" ; if (usr().bus_count() > 0) file << "\n"; /* * [user-midi-bus-x] */ for (int buss = 0; buss < usr().bus_count(); ++buss) { file << "\n" << make_section_name("user-midi-bus", buss) << "\n\n"; const usermidibus & umb = usr().bus(buss); std::string bussname = add_quotes(umb.name()); if (umb.is_valid()) { file << "# Device/bus name\n\n" << bussname << "\n" << "\n" << umb.channel_count() << " # number of instrument settings\n\n" << "# Channel, instrument number, and instrument names\n\n" ; for (int channel = 0; channel < umb.channel_count(); ++channel) { std::string instname = umb.instrument_name(channel); instname = add_quotes(instname); file << std::setw(2) << channel << " " << umb.instrument(channel) << " " << instname << "\n" ; } } else file << "? This buss specification is invalid\n"; } file << "\n" "# In these MIDI instrument definitions, active (supported by the instrument)\n" "# controller numbers are paired with the (optional) name of the controller.\n" ; /* * [user-instrument-definitions] */ file << "\n[user-instrument-definitions]\n\n" << usr().instrument_count() << " # instrument list count\n" ; if (usr().instrument_count() > 0) file << "\n"; /* * [user-instrument-x] */ for (int inst = 0; inst < usr().instrument_count(); ++inst) { file << "\n" << make_section_name("user-instrument", inst) << "\n\n"; const userinstrument & uin = usr().instrument(inst); if (uin.is_valid()) { std::string fixedname = add_quotes(uin.name()); file << "# Name of instrument\n\n" << fixedname << "\n\n" << uin.controller_count() << " # number of MIDI controller number & name pairs\n" ; if (uin.controller_count() > 0) { for (int ctlr = 0; ctlr < uin.controller_max(); ++ctlr) { std::string ctrlname = uin.controller_name(ctlr); std::string fixedname = strip_quotes(ctrlname); if (fixedname == "---" || is_empty_string(fixedname)) fixedname = empty_string(); else fixedname = add_quotes(fixedname); if (uin.controller_active(ctlr)) file << ctlr << " " << fixedname << "\n"; } } } else file << "? This instrument specification is invalid\n"; } /* * [user-interface settings] * * These are new items stored in the user file. The settings are obtained * from member functions of the user_settings class. Not all members are * saved to the "user" configuration file. */ file << "\n" "# [user-interface-settings]\n" "#\n" "# Configures some user-interface items. Also see [user-ui-tweaks].\n" #if defined USE_USR_STYLE_SHEET "# For window styling, use Qt themes/style-sheets.\n" #else "# For window styling, use Qt style-sheets as specified in the 'rc' file.\n" #endif "#\n" "# 'swap-coordinates' swaps numbering so pattern numbers vary fastest by column\n" "# instead of rows. This setting applies to the live grid, mute-group buttons,\n" "# and set-buttons.\n" "#\n" "# 'mainwnd-rows' and 'mainwnd-columns' (option '-o sets=RxC') specify\n" "# rows/columns in the main grid. R ranges from 4 to 8, C from 4 to 12.\n" "# Values other than 4x8 have not been tested thoroughly.\n" "#\n" "# 'mainwnd-spacing' is for grid buttons; from 0 to 16, default = 2.\n" "#\n" "# 'default-zoom' is the initial zoom for piano rolls. From 1 to 512, default\n" "# = 2. Larger PPQNs require larger zoom to look good. Seq66 adapts the zoom to\n" "# the PPQN if set to 0. The unit of zoom is ticks/pixel.\n" "#\n" "# 'global-seq-feature' applies the key, scale, and background pattern to all\n" "# patterns versus separately to each. If all, these values are stored in the\n" "# MIDI file in the global SeqSpec versus in each track.\n" "#\n" "# 'progress-bar-thick/ness' specifies the progress bar/box. Default = 2 pixels,\n" "# 1 pixel if set to false. Also affects the slot box border and the boldness\n" "# of the slot font. 'progress-box-elliptical' creates an elliptical box.\n" "#\n" "# 'follow-progress' sets the default for following progress in the piano\n" "# rolls. Each window has a button to toggle following progess.\n" "#\n" "# 'gridlines-thick', if false (the default) specifies the normal style of\n" "# grid-lines. If true, then lines are thicker. Useful to control the lines\n" "# re the palette or theme.\n" "#\n" "# 'inverse-colors' (option -K/--inverse) specifies use of an inverse color\n" "# palette. Palettes are for Seq66 drawing areas, not for Qt widgets.\n" "# Normal/inverse palettes can be reconfigured via a 'palette' file.\n" "#\n" "# 'time-fg-color' and 'time-bg-color' override the default colors for ticks\n" "# and time displays (lime on black). 'default' keeps the defaults. 'normal'\n" "# uses the theme color.\n" "#\n" "# 'dark-theme' specifies that a dark theme is active, so that some colors\n" "# (e.g. grid-slot text) are inverted.\n" "#\n" "# 'window-redraw-rate' specifies the base window redraw rate for all windows.\n" "# From 10 to 100; default = 40 ms (25 ms for Windows).\n" "#\n" "# Window-scale (option '-o scale=m.n[xp.q]') specifies scaling the main\n" "# window at startup. Defaults to 1.0 x 1.0. If between 0.5 and 3.0, it\n" "# changes the size of the main window proportionately.\n" "#\n" "# 'enable-learn-confirmation' can be set to false to disable the prompt that\n" "# the mute-group learn action succeeded. Can be annoying.\n" "\n[user-interface-settings]\n\n" ; write_boolean(file, "swap-coordinates", usr().swap_coordinates()); write_integer(file, "mainwnd-rows", usr().mainwnd_rows()); write_integer(file, "mainwnd-columns", usr().mainwnd_cols()); write_integer(file, "mainwnd-spacing", usr().mainwnd_spacing()); write_integer(file, "default-zoom", usr().base_zoom()); write_boolean(file, "global-seq-feature", usr().global_seq_feature()); write_boolean(file, "progress-bar-thick", usr().progress_bar_thick()); write_integer ( file, "progress-bar-thickness", usr().progress_bar_thickness() ); write_boolean ( file, "progress-box-elliptical", usr().progress_box_elliptical() ); write_boolean(file, "follow-progress", usr().follow_progress()); write_boolean(file, "gridlines-thick", usr().gridlines_thick()); write_boolean(file, "inverse-colors", usr().inverse_colors()); write_string(file, "time-fg-color", usr().time_fg_color(true), true); write_string(file, "time-bg-color", usr().time_bg_color(true), true); write_boolean(file, "dark-theme", usr().dark_theme()); write_integer(file, "window-redraw-rate", usr().window_redraw_rate()); write_float(file, "window-scale", usr().window_scale()); write_float(file, "window-scale-y", usr().window_scale_y()); write_boolean ( file, "enable-learn-confirmation", usr().enable_learn_confirmation() ); /* * [user-midi-ppqn] */ file << "\n" "# Seq66 separates file PPQN from the base Seq66 PPQN, 192. 'default-ppqn'\n" "# changes the base Seq66 PPQN from 32 (not recommended) to 19200. \n" "# 'use-file-ppqn' (recommended) indicates to use the file PPQN.\n" "\n[user-midi-ppqn]\n\n" ; write_integer(file, "default-ppqn", usr().default_ppqn()); write_boolean(file, "use-file-ppqn", usr().use_file_ppqn()); /* * [user-randomization] */ file << "\n" "# This section specifies the default values to use to jitter the MIDI event\n" "# time-stamps and randomize event amplitudes (e.g. velocity for notes). The\n" "# range of jitter is 1/j times the current snap value.\n" "\n[user-randomization]\n\n" ; write_integer(file, "jitter-divisor", usr().jitter_divisor()); write_integer(file, "amplitude", usr().randomization_amount()); /* * [user-midi-settings] */ file << "\n" "# [user-midi-settings]\n" "#\n" "# Specifies MIDI-specific variables. -1 means the value isn't used.\n" "#\n" "# Item Default Range\n" "# 'convert-to-smf-1': true true/false.\n" "# 'beats-per-bar': 4 1 to 32.\n" "# 'beats-per-minute': 120.0 2.0 to 600.0.\n" "# 'beat-width': 4 1 to 32.\n" "# 'buss-override': -1 (none) -1 to 48.\n" "# 'velocity-override': -1 (Free) -1 to 127.\n" "# 'bpm-precision': 0 0 to 2.\n" "# 'bpm-step-increment': 1.0 0.01 to 25.0.\n" "# 'bpm-page-increment': 1.0 0.01 to 25.0.\n" "# 'bpm-minimum': 0.0 127.0\n" "# 'bpm-maximum': 0.0 127.0\n" "#\n" "# 'convert-to-smf-1' controls if SMF 0 files are split into SMF 1 when read.\n" "# 'buss-override' sets the output port for all patterns, for testing, etc.\n" "# This value will be saved if you save the MIDI file!!!\n" "# 'velocity-override' controls adding notes in the pattern editor; see the\n" "# 'Vol' button. -1 ('Free'), preserves incoming velocity.\n" "# 'bpm-precision' (spinner and MIDI control) is 0, 1, or 2.\n" "# 'bpm-step-increment' affects the spinner and MIDI control. For 1 decimal,\n" "# 0.1 is good. For 2, 0.01 is good, 0.05 is faster. Set 'bpm-page-increment'\n" "# larger than the step-increment; used with the Page-Up/Page-Down keys in the\n" "# spinner. BPM minimum/maximum sets the range in tempo graphing; defaults to\n" "# 0.0 to 127.0. Decrease it for a magnified view of tempo.\n" "# 'auto-add-time-sig', true by default, starts each new pattern with a time\n" "# signature event. New 0.99.21.\n" "\n[user-midi-settings]\n\n" ; write_boolean(file, "convert-to-smf-1", usr().convert_to_smf_1()); write_integer(file, "beats-per-bar", usr().midi_beats_per_bar()); write_integer(file, "beats-per-minute", usr().midi_beats_per_minute()); write_integer(file, "beat-width", usr().midi_beat_width()); int bo = int(usr().midi_buss_override()); /* writing char no good */ float increment = float(usr().bpm_step_increment()); if (is_null_buss(bussbyte(bo))) bo = (-1); write_integer(file, "buss-override", bo); write_integer(file, "velocity-override", usr().velocity_override()); write_integer(file, "bpm-precision", usr().bpm_precision()); write_float(file, "bpm-step-increment", increment); increment = float(usr().bpm_page_increment()); write_float(file, "bpm-page-increment", increment); increment = float(usr().midi_bpm_minimum()); write_float(file, "bpm-minimum", increment); increment = float(usr().midi_bpm_maximum()); write_float(file, "bpm-maximum", increment); write_boolean(file, "auto-add-time-sig", usr().auto_add_time_sig()); /* * [user-options] */ file << "\n" "# [user-options]\n" "#\n" "# These settings specify some -o or --option switch values. 'daemonize' in\n" "# seq66cli indicates that it should run as a service. 'log' specifies a log-\n" "# file redirecting output from standard output/error. If no path in the name,\n" "# the log is stored in the configuration directory. For no log-file, use\n" "# \"none\" or \"\". On the command line: '-o log=filename.log'.\n" "\n[user-options]\n\n" ; std::string fname = usr().option_logfile(); if (fname.empty()) fname = "none"; write_boolean(file, "daemonize", usr().option_daemonize()); write_string(file, "log", usr().option_logfile(), true); write_string(file, "pdf-viewer", usr().user_pdf_viewer(), true); write_string(file, "browser", usr().user_browser(), true); /* * [user-ui-tweaks] */ file << "\n" "# [user-ui-tweaks]\n" "#\n" "# key-height specifies the initial height (before vertical zoom) of pattern\n" "# editor keys. Defaults to 10 pixels, ranges from 6 to 32.\n" "#\n" "# key-view specifies the default for showing labels for each key:\n" "# 'octave-letters' (default), 'even_letters', 'all-letters',\n" "# 'even-numbers', and 'all-numbers'.\n" "#\n" "# note-resume causes notes-in-progress to resume when the pattern toggles on.\n" "#\n" #if defined USE_USR_STYLE_SHEET "# If specified, a style-sheet (e.g. 'qseq66.qss') is applied at startup.\n" "# Normally just a base-name, it can contain a file-path to provide a style\n" "# usable in many other applications.\n" #else "# Note that style-sheet specification has been moved to the 'rc' file with\n" "# all the rest of the files.\n" #endif "#\n" "# A fingerprint is a condensation of note events in a long track, to reduce\n" "# the time drawing the pattern in the buttons. Ranges from 32 (default) to\n" "# 128. 0 = don't use a fingerprint.\n" "#\n" "# progress-box-width and -height settings change the scaled size of the\n" "# progress box in the live-grid buttons. Width ranges from 0.50 to 1.0, and\n" "# the height from 0.10 to 1.0. If either is 'default', defaults (0.8 x 0.3)\n" "# are used. progress-box-shown controls if the boxes are shown at all.\n" "# progress-box-show-cc enables displaying CC and pitchbend events (as dots).\n" "#\n" "# progress-note-min and progress-note-max set the progress-box note range so\n" "# that notes aren't centered in the box, but shown at their position by pitch.\n" "#\n" "# lock-main-window prevents the accidental change of size of the main\n" "# window.\n" "\n[user-ui-tweaks]\n\n" ; write_integer(file, "key-height", usr().key_height()); write_string(file, "key-view", usr().key_view_string()); write_boolean(file, "note-resume", usr().resume_note_ons()); #if defined USE_USR_STYLE_SHEET write_boolean(file, "style-sheet-active", usr().style_sheet_active()); write_string(file, "style-sheet", usr().style_sheet(), true); #endif write_integer(file, "fingerprint-size", usr().fingerprint_size()); if (usr().progress_box_width() <= 0.0) file << "progress-box-width = default\n"; else write_float(file, "progress-box-width", usr().progress_box_width()); if (usr().progress_box_height() <= 0.0) file << "progress-box-height = default\n"; else write_float(file, "progress-box-height", usr().progress_box_height()); write_boolean(file, "progress-box-shown", usr().progress_box_shown()); write_boolean(file, "progress-box-show-cc", usr().progress_box_show_cc()); write_integer(file, "progress-note-min", usr().progress_note_min()); write_integer(file, "progress-note-max", usr().progress_note_max()); write_boolean(file, "lock-main-window", usr().lock_main_window()); /* * [user-session] */ file << "\n# [user-session]\n" "#\n" "# The session manager to use, if any. 'session' is 'none' (default), 'nsm'\n" "# (Non/New Session Manager), or 'jack'. 'url' can be set to the value set by\n" "# nsmd when run by command-line. Set 'url' if running nsmd stand-alone; use\n" "# the --osc-port number. Seq66 detects if started in NSM. The visibility flag\n" "# is used only by NSM to restore visibility.\n" "\n[user-session]\n\n" ; write_string(file, "session", usr().session_manager_name()); write_string(file, "url", usr().session_url(), true); write_boolean(file, "visibility", usr().session_visibility()); /* * [pattern-editor] (was [new-pattern-editor]) */ file << "\n# [pattern-editor]\n" "#\n" "# 'escape-pattern' allows the Esc key to close a pattern editor if not\n" "# playing or in paint mode. (Esc exits paint mode or stops playback.)\n" "#\n" "# 'apply-to-new-only' makes the next options work only when opening a new\n" "# pattern. A new pattern is 'Untitled' and has no events.\n" "#\n" "# These settings for play/record for a pattern editor save time in live\n" "# recording. Valid record-style values: 'merge' or 'overdub', 'overwrite',\n" "# 'expand', and 'one-shot'. 'wrap-around' allows recorded notes to wrap\n" "# to the pattern beginning. Currently 'notemap' and quantizing are\n" "# are mutually exclusive.\n" "\n[pattern-editor]\n\n" ; write_boolean(file, "escape-pattern", usr().escape_pattern()); write_boolean(file, "apply-to-new-only", usr().pattern_new_only()); write_boolean(file, "armed", usr().pattern_armed()); write_boolean(file, "thru", usr().pattern_thru()); write_boolean(file, "record", usr().pattern_record()); write_boolean(file, "tighten", usr().pattern_tighten()); write_boolean(file, "qrecord", usr().pattern_qrecord()); write_boolean(file, "notemap", usr().pattern_notemap()); write_string(file, "record-style", usr().pattern_record_string()); write_boolean(file, "wrap-around", usr().pattern_wraparound()); write_seq66_footer(file); file.close(); return true; } } // namespace seq66 /* * usrfile.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/usrsettings.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file usrsettings.cpp * * This module declares/defines just some of the global (gasp!) variables * in this application. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-09-23 * \updates 2025-07-19 * \license GNU GPLv2 or above * * Note that this module also sets the remaining legacy global variables, so * that they can be used by modules that have not yet been cleaned up. * * Now, we finally sat down and did some measurements of the user interface, * to try to figure out the relationships between the screen resolution and * MIDI time resolution, so that we can understand some of the magic numbers * in Seq24. * * We start with one clue, a comment in perftime (IIRC) about units being 32 * ticks per pixels. Note that "ticks" is equivalent to MIDI "pulses", and * sometimes the word "division" is used for "pulses". So let's solidy the * nomenclature and notation here: * \verbatim Symbol Units Value Description qn quarter note ----- The default unit for a MIDI beat P0 pulses/qn 192 Seq24's PPQN value, a constant P pulses/qn ----- Any other selected PPQN value R ----- ----- P / P0 Wscreen pixels 1920 Width of the screen, pixels Wperfqn pixels 6 Song editor q-note width, constant Zperf pulses/pixel 32 Song editor default zoom, constant Dperf minor/major 4 Song editor beats shown per measure ? pulses/pixel ----- GUI-MIDI resolution from selected P S ----- 16 seqroll-to-perfroll width ratio Zseqmin pulses/pixel 1 Seq editor max zoom in Zseq0 pulses/pixel 2 Seq editor default zoom Zseqmax pulses/pixel 128 (32) Seq editor max zoom out \endverbatim * * Sequence Editor (seqroll): * * Careful measuring on my laptop screen shows that the perfroll covers 80 * measures over 1920 pixels. * \verbatim 1920 pixels ----------- = 24 pixels/measure = 6 pixels/qn = Wperfqn 80 measures \endverbatim * * Song Editor (perfroll) Zoom: * * The value of S = 16 reflects that the sequence editor piano roll, * at its default zoom (2 pulses/pixel), has 16 times the width resolution * of the performer/song editor piano roll (32 pulses/pixel). This ratio * (at the default zoom) will be preserved no matter what P (PPQN) is * selected for the song. * * The sequence editor supports zooms of 1 pulse/pixel, 2 pulses/pixel (it's * default), and 4, 8, 16, and 32 pulses/pixel (the song editor's only zoom). * * Song Editor (perfedit, perfroll, pertime) Guides: * * pulses major * measureticks = P0 ------ Dperf ----- * qn minor * * perfedit: m_ppqn m_standard_bpm * * Time Signature: * * Changing the beats-per-measure of the seqroll to from the default 4 to 8 * makes the measure have 8 major divisions, each with the standard 16 minor * divisions. An added note still covers only 4 minor divisions. * * Changing the beat-width of the seqroll from the default 4 to 8 halves the * pixel-width of reach measure. */ #include "cfg/settings.hpp" /* seq66::rc(), seq66::usr() */ #include "play/screenset.hpp" /* seq66::screenset constants */ #include "play/seq.hpp" /* seq66::seq::limit() */ #include "util/filefunctions.hpp" /* seq66::name_has_root_path() */ #include "util/strfunctions.hpp" /* free functions in seq66 n'space */ /* * When ready, remove this macros. */ #undef SEQ66_USE_ADDED_ALTERATIONS /* undefined while in-progress */ namespace seq66 { static const bool s_swap_coordinates_def = false; /** * Limits offloaded from the obsolete app limits header. * Minimum, default, and maximum values for "beats-per-measure". A new * addition for the Qt 5 user-interface. This is the "numerator" in a 4/4 * time signature. It is also the value used for JACK's * jack_position_t.beats_per_bar field. For abbreviation, we will call this * value "BPB", or "beats per bar", to distinguish it from "BPM", or "beats * per minute". */ static const int c_min_beats_per_measure = 1; static const int c_def_beats_per_measure = 4; static const int c_max_beats_per_measure = 32; /** * The minimum, default, and maximum values of the beat width. A new * addition for the Qt 5 user-interface. This is the "denominator" in a 4/4 * time signature. It is also the value used for JACK's * jack_position_t.beat_type field. For abbreviation, we will call this value * "BW", or "beat width", not to be confused with "bandwidth". */ static const int c_min_beat_width = 1; static const int c_def_beat_width = 4; static const int c_max_beat_width = 32; /** * Minimum, default, and maximum values for global beats-per-minute, also known * as "BPM". Do not confuse this "bpm" with the other one, "beats per measure"; * we use "BPB" (beats-per-bar) for clarity. Also, we multiply the BPM by a * scale factor so that we can get extra precision in the value when stored as a * long integer in the MIDI file in the proprietary "bpm" section. See the * midifile class. Lastly, we provide a tap-button timeout value (which could * some day be mode configurable. */ static const midibpm c_min_beats_per_minute = 2.0; static const midibpm c_def_beats_per_minute = 120.0; static const midibpm c_max_beats_per_minute = 600.0; static const float c_beats_per_minute_scale = 1000.0; static const long c_bpm_tap_button_timeout = 5000L; /* milliseconds */ static const int c_min_bpm_precision = 0; static const int c_def_bpm_precision = 0; static const int c_max_bpm_precision = 2; static const midibpm c_min_bpm_increment = 0.01; static const midibpm c_def_bpm_increment = 1.0; static const midibpm c_max_bpm_increment = 50.0; static const midibpm c_def_bpm_page_increment = 10.0; /** * Minimum and maximum supported PPQN values. Now hidden, used in the public * function usrsettings::is_ppqn_valid(). */ static const int c_minimum_ppqn = 24; /* was 32, not a multiple of 24 */ static const int c_maximum_ppqn = 19200; /* way above the useful maximum */ /** * Velocity values. */ static const short c_def_note_off_velocity = 64; static const short c_def_note_on_velocity = 100; static const short c_max_note_on_velocity = 127; static const short c_preserve_velocity = (-1); /** * Provide limits for the option "--option scale=x.y". Based on the minimum * size of the main window specified in qsmainwnd.ui, 0.8 is the smallest one * that can go well for both width and height. */ static const double c_window_scale_min = 0.5; static const double c_window_scale_default = 1.0f; static const double c_window_scale_max = 3.0f; /** * These currently just exposed some values from the *.ui files. The size of * the main window. * * Not used: * * static const int c_minimum_window_width = 720; // shrunken = 540, 0.75 540 * static const int c_minimum_window_height = 480; // shrunken = 360, 0.66 450 */ static const int c_default_window_width = 884; // shrunken = 720, 0.82 664 static const int c_default_window_height = 602; // shrunken = 480, 0.80 450 /** * Key-height settings. Default values of the height of the piano keys in * the Qt 5 qseqkeys user-interface. */ static const int c_min_key_height = 6; static const int c_def_key_height = 10; static const int c_max_key_height = 32; /* touch-screen friendly */ /** * Minimum and maximum possible values for the global redraw rate. */ static const int c_minimum_redraw = 10; static const int c_maximum_redraw = 100; /** * Provides the redraw time when recording, in ms. Can Windows actually * draw faster? :-D */ #if defined SEQ66_PLATFORM_WINDOWS static const int c_default_redraw_ms = 25; #else static const int c_default_redraw_ms = 40; #endif /** * Constants for the main window, etc. The c_seqchars_x and c_seqchars_y * constants help define the "seqarea" size. These look like the number * of characters per line and the number of lines of characters, in a * pattern/sequence box. * * static const int c_seqchars_x = 15; * static const int c_seqchars_y = 5; * * These control sizes. We'll try changing them and see what happens. * Increasing these value spreads out the pattern grids a little bit and * makes the Patterns panel slightly bigger. Seems like it would be * useful to make these values user-configurable. * * Constants for the font class. The c_text_x and c_text_y constants * help define the "seqarea" size. It looks like these two values are * the character width (x) and height (y) in pixels. Thus, these values * would be dependent on the font chosen. But that, currently, is * hard-wired. * * static const int c_text_x = 6; // doesn't include inner padding * static const int c_text_y = 12; // does include inner padding * * The c_seqarea_x and c_seqarea_y constants are derived from the width * and heights of the default character set, and the number of characters * in width, and the number of lines, in a pattern/sequence box. * * Clang reveals they are not used. * * static const int c_seqarea_x = c_text_x * c_seqchars_x; * static const int c_seqarea_y = c_text_y * c_seqchars_y; */ /** * These control sizes. We'll try changing them and see what happens. * Increasing these value spreads out the pattern grids a little bit and * makes the Patterns panel slightly bigger. Seems like it would be * useful to make these values user-configurable. */ static const int c_mainwnd_spacing = 2; // try 4 or 6 instead of 2 /** * Provides the defaults for the progress box in the qloopbuttons. * Zero is also an acceptable value. 1.0 for both width and height are * interesting, and now valid. */ static const float c_progress_box_width_min = 0.50; static const float c_progress_box_width = 0.80; static const float c_progress_box_width_max = 1.00; static const float c_progress_box_height_min = 0.10; static const float c_progress_box_height = 0.30; static const float c_progress_box_height_max = 1.00; // 0.50; /** * Provides the default for the fingerprinting of the qloopbuttons. */ static const int c_fingerprint_none = 0; static const int c_fingerprint_size_min = 32; static const int c_fingerprint_size = 32; static const int c_fingerprint_size_max = 128; /** * Default color names for tick/time displays. */ static const std::string s_time_no_color = ""; /* keep theme color */ static const std::string s_time_fg_color = "lime"; /* CSS bright green */ static const std::string s_time_bg_color = "black"; /* CSS black :-D */ /** * Default constructor. */ usrsettings::usrsettings () : basesettings (), m_midi_buses (), /* [user-midi-bus-definitions] */ m_instruments (), /* [user-instrument-definitions] */ /* * [user-interface-settings] */ m_option_bits (option_none), m_mainwnd_rows (screenset::c_default_rows), m_mainwnd_cols (screenset::c_default_columns), m_swap_coordinates (s_swap_coordinates_def), m_window_scale (c_window_scale_default), m_window_scale_y (c_window_scale_default), m_mainwnd_spacing (0), m_base_zoom (2), /* 0 is a feature */ m_jitter_divisor (8), /* i.e. "1/8" */ m_randomization_amount (8), /* for 0 to 127 */ m_global_seq_feature_save (true), m_seqedit_scale (c_scales_off), m_seqedit_key (c_key_of_C), m_seqedit_bgsequence (seq::limit()), m_progress_bar_thick (true), m_progress_bar_thickness (2), m_progress_box_elliptical (false), m_follow_progress (true), m_gridlines_thick (false), m_inverse_colors (false), m_time_fg_color ("default"), m_time_bg_color ("default"), m_dark_theme (false), m_window_redraw_rate_ms (c_default_redraw_ms), /* * The members that follow are not yet part of the .usr file. */ m_seqchars_x (0), m_seqchars_y (0), /* * [user-midi-settings] */ m_convert_to_smf_1 (true), m_default_ppqn (c_base_ppqn), m_midi_ppqn (c_base_ppqn), m_use_file_ppqn (true), m_file_ppqn (c_base_ppqn), m_midi_beats_per_measure (c_def_beats_per_measure), m_midi_bpm_minimum (c_min_beats_per_minute), m_midi_beats_per_minute (c_def_beats_per_minute), m_midi_bpm_maximum (c_max_beats_per_minute), m_midi_beat_width (c_def_beat_width), m_midi_buss_override (null_buss()), m_velocity_override (c_preserve_velocity), m_bpm_precision (c_def_bpm_precision), m_bpm_step_increment (c_def_bpm_increment), m_bpm_page_increment (c_def_bpm_page_increment), m_auto_add_time_sig (true), /* * Calculated from other member values in the normalize() function. */ m_total_seqs (0), m_seqs_in_set (0), /* set in normalize() */ m_gmute_tracks (0), /* same as max-tracks */ m_max_sequence (seq::maximum()), m_mainwnd_x (c_default_window_width), /* 780 */ m_mainwnd_y (c_default_window_height), /* 412 */ /* m_app_is_headless (false), */ m_user_option_daemonize (false), m_user_save_daemonize (false), m_user_use_logfile (false), m_user_option_logfile (), m_user_pdf_viewer (), m_user_browser (), /* * [user-ui-tweaks] */ m_user_ui_key_height (c_def_key_height), m_user_ui_key_view (showkeys::octave_letters), m_user_ui_seqedit_in_tab (true), #if defined USE_USR_STYLE_SHEET m_user_ui_style_active (false), m_user_ui_style_sheet (""), #endif m_resume_note_ons (false), m_fingerprint_size (c_fingerprint_size), m_progress_box_width (c_progress_box_width), m_progress_box_height (c_progress_box_height), m_progress_box_shown (true), m_progress_box_show_cc (true), m_progress_note_min (0), m_progress_note_max (127), m_lock_main_window (false), m_session_manager (session::none), m_session_url (), m_in_nsm_session (false), m_session_visibility (true), m_escape_pattern (false), m_pattern_armed (false), m_pattern_thru (false), m_pattern_record (false), m_pattern_tighten (false), m_pattern_qrecord (false), m_pattern_notemap (false), m_pattern_new_only (false), m_pattern_record_style (recordstyle::merge), m_pattern_wraparound (false), m_record_alteration (alteration::none), m_grid_mode (gridmode::loop), m_enable_learn_confirmation (true) { // Empty body; it's no use to call normalize() here, see set_defaults(). } /** * Sets the default values. For the m_midi_buses and * m_instruments members, this function can only iterate over the * current size of the vectors. But the default size is zero! */ void usrsettings::set_defaults () { m_midi_buses.clear(); m_instruments.clear(); m_option_bits = option_none; m_mainwnd_rows = screenset::c_default_rows; m_mainwnd_cols = screenset::c_default_columns; m_swap_coordinates = s_swap_coordinates_def; m_window_scale = c_window_scale_default; m_window_scale_y = c_window_scale_default; m_mainwnd_spacing = c_mainwnd_spacing; m_base_zoom = 2; m_jitter_divisor = 8; m_randomization_amount = 8; m_global_seq_feature_save = true; m_seqedit_scale = c_scales_off; m_seqedit_key = c_key_of_C; m_seqedit_bgsequence = seq::limit(); m_progress_bar_thick = true; m_progress_bar_thickness = 2; m_progress_box_elliptical = false; m_follow_progress = true; m_gridlines_thick = false; m_inverse_colors = false; m_time_fg_color = "default"; m_time_bg_color = "default"; m_dark_theme = false; m_window_redraw_rate_ms = c_default_redraw_ms; m_seqchars_x = 15; m_seqchars_y = 5; m_convert_to_smf_1 = true; m_default_ppqn = m_midi_ppqn = m_file_ppqn = c_base_ppqn; // reset_ppqn() m_use_file_ppqn = true; m_midi_beats_per_measure = c_def_beats_per_measure; m_midi_bpm_minimum = c_min_beats_per_minute; m_midi_beats_per_minute = c_def_beats_per_minute; m_midi_bpm_maximum = c_max_beats_per_minute; m_midi_beat_width = c_def_beat_width; m_midi_buss_override = null_buss(); m_velocity_override = c_preserve_velocity; m_bpm_precision = c_def_bpm_precision; m_bpm_step_increment = c_def_bpm_increment; m_bpm_page_increment = c_def_bpm_page_increment; m_auto_add_time_sig = true; /* * Calculated from other member values in the normalize() function. * * m_total_seqs * m_seqs_in_set * m_gmute_tracks * m_max_sequence * * No longer used: * * m_app_is_headless */ m_mainwnd_x = c_default_window_width; m_mainwnd_y = c_default_window_height; m_user_option_daemonize = false; m_user_save_daemonize = false; m_user_use_logfile = false; m_user_option_logfile.clear(); m_user_pdf_viewer.clear(); m_user_browser.clear(); m_user_ui_key_height = c_def_key_height; m_user_ui_key_view = showkeys::octave_letters; m_user_ui_seqedit_in_tab = true; #if defined USE_USR_STYLE_SHEET m_user_ui_style_active = false; m_user_ui_style_sheet = ""; #endif m_resume_note_ons = false; m_fingerprint_size = c_fingerprint_size; m_progress_box_width = c_progress_box_width; m_progress_box_height = c_progress_box_height; m_progress_box_shown = true; m_progress_box_show_cc = true; m_progress_note_min = 0; m_progress_note_max = 127; m_lock_main_window = false; m_session_manager = session::none; m_session_url.clear(); m_in_nsm_session = false; m_session_visibility = true; m_escape_pattern = false; m_pattern_armed = false; m_pattern_thru = false; m_pattern_record = false; m_pattern_tighten = false; m_pattern_qrecord = false; m_pattern_notemap = false; m_pattern_new_only = false; m_pattern_record_style = recordstyle::merge; m_pattern_wraparound = false; m_record_alteration = alteration::none; m_grid_mode = gridmode::loop; m_enable_learn_confirmation = true; normalize(); /* recalculate derived values */ } /** * Calculate the derived values from the already-set values. * Should we normalize the BPM increment values here, in case they * are irregular? * * gmute_tracks() is viable with variable set sizes only if we stick with the * 32 sets by 32 patterns, at this time. It's semantic meaning is...... * * m_max_sequence is now actually a constant (1024), so we enforce that here * now. */ void usrsettings::normalize () { m_seqs_in_set = m_mainwnd_rows * m_mainwnd_cols; m_gmute_tracks = m_seqs_in_set * m_seqs_in_set; m_total_seqs = m_seqs_in_set * c_max_sets; /* * Let's keep rows/columns separate from scaling, and keep shrunken() * merely to detect the need to hide some buttons. * * if (shrunken()) { (void) window_scale(0.80, 0.75); } */ } void usrsettings::progress_note_min_max (int vmin, int vmax) { if (vmin >= 0 && vmin < 64) m_progress_note_min = vmin; if (vmax > 64 && vmax < 128) m_progress_note_max = vmax; } void usrsettings::set_pattern_record_style (const std::string & style) { recordstyle rs = recordstyle::merge; /* "overdub" or "merge" */ if (style == "overwrite") rs = recordstyle::overwrite; else if (style == "expand") rs = recordstyle::expand; else if (style == "one-shot") rs = recordstyle::oneshot; else if (style == "one-shot-reset") rs = recordstyle::oneshot_reset; m_pattern_record_style = rs; } void usrsettings::set_pattern_record_style (int index) { recordstyle rs = static_cast(index); m_pattern_record_style = rs; } void usrsettings::clear_global_seq_features () { seqedit_scale(c_scales_off); seqedit_key(c_key_of_C); seqedit_bgsequence(seq::limit()); } /** * Hmmmmmmm on the next two functions. */ std::string usrsettings::pattern_record_string () const { std::string result; switch (m_pattern_record_style) { case recordstyle::merge: result = "overdub"; break; case recordstyle::overwrite: result = "overwrite"; break; case recordstyle::expand: result = "expand"; break; case recordstyle::oneshot: result = "one-shot"; break; case recordstyle::oneshot_reset: result = "one-shot-reset"; break; case recordstyle::max: result = "error"; break; } return result; } /** * For recording, at present, only the following alterations are supported: * * - none * - quantize * - tighten * - notemap * * The alterations of jitter and random are left to the musician when * recording :-D. */ std::string usrsettings::record_alteration_label () const { std::string result; switch (record_alteration()) { case alteration::none: result = "None"; break; case alteration::quantize: result = "Quantize"; break; case alteration::tighten: result = "Tighten"; break; case alteration::jitter: result = "Jitter"; break; case alteration::random: result = "Random"; break; case alteration::notemap: result = "Note Map"; break; default: result = "Normal"; break; } return result; } /* * In the following two functions, we could have the caller call * * automation_quan_record(a, (-1), (-1), 0, false) * * instead, where a = automation::action::toggle/yes/no. */ alteration usrsettings::next_record_alteration () { alteration result; switch (record_alteration()) { #if defined SEQ66_USE_ADDED_ALTERATIONS case alteration::none: result = alteration::tighten; break; case alteration::tighten: result = alteration::quantize; break; case alteration::quantize: result = alteration::jitter; break; case alteration::jitter: result = alteration::random; break; case alteration::random: result = alteration::notemap break; case alteration::notemap: result = alteration::none; break; #else case alteration::none: result = alteration::tighten; break; case alteration::tighten: result = alteration::quantize; break; case alteration::quantize: result = alteration::notemap; break; case alteration::notemap: result = alteration::none; break; #endif default: result = alteration::none; break; } m_record_alteration = result; return result; } alteration usrsettings::previous_record_alteration () { alteration result; switch (record_alteration()) { #if defined SEQ66_USE_ADDED_ALTERATIONS case alteration::none: result = alteration::notemap; break; case alteration::tighten: result = alteration::none; break; case alteration::quantize: result = alteration::tighten; break; case alteration::jitter; result = alteration::quantize; break; case alteration::random; result = alteration::jitter; break; case alteration::notemap; result = alteration::random; break; #else case alteration::none: result = alteration::quantize; break; case alteration::quantize: result = alteration::tighten; break; case alteration::tighten: result = alteration::none; break; #endif default: result = alteration::none; break; } m_record_alteration = result; return result; } std::string usrsettings::pattern_record_style_label () const { std::string result; switch (pattern_record_style()) { case recordstyle::merge: result = "Overdub"; break; case recordstyle::overwrite: result = "Overwrite"; break; case recordstyle::expand: result = "Expand"; break; case recordstyle::oneshot: result = "One-shot"; break; case recordstyle::oneshot_reset: result = "One-shot Reset"; break; case recordstyle::max: result = "Error"; break; } return result; } recordstyle usrsettings::next_record_style () { recordstyle result; switch (pattern_record_style()) { case recordstyle::merge: result = recordstyle::overwrite; break; case recordstyle::overwrite: result = recordstyle::expand; break; case recordstyle::expand: result = recordstyle::oneshot; break; case recordstyle::oneshot: result = recordstyle::merge; break; default: result = recordstyle::merge; break; } m_pattern_record_style = result; /* m_grid_record_style */ return result; } recordstyle usrsettings::previous_record_style () { recordstyle result; switch (pattern_record_style()) { case recordstyle::merge: result = recordstyle::oneshot; break; case recordstyle::overwrite: result = recordstyle::merge; break; case recordstyle::expand: result = recordstyle::overwrite; break; case recordstyle::oneshot: result = recordstyle::expand; break; default: result = recordstyle::merge; break; } m_pattern_record_style = result; /* m_grid_record_style */ return result; } std::string usrsettings::grid_mode_label (gridmode gm) const { std::string result; if (gm == gridmode::max) gm = grid_mode(); switch (gm) { case gridmode::loop: result = "Loop"; break; case gridmode::mutes: result = "Mutes"; break; case gridmode::record: result = "Record"; break; case gridmode::copy: result = "Copy"; break; case gridmode::paste: result = "Paste"; break; case gridmode::clear: result = "Clear"; break; case gridmode::remove: result = "Delete"; break; case gridmode::thru: result = "Thru"; break; case gridmode::solo: result = "Solo"; break; case gridmode::cut: result = "Cut"; break; case gridmode::double_length: result = "Double"; break; default: result = "Error"; break; } return result; } std::string usrsettings::session_manager_name () const { if (want_nsm_session()) return std::string("nsm"); else if (want_jack_session()) return std::string("jack"); else return std::string("none"); } /** * Sets the desired session manager using a string value. * * \param sm * Provides a string value of "nsm" for the Non/New Session Managers, or * "jack" for JACK Session Management. All other values set the * m_session_manager code to session::none. */ void usrsettings::session_manager (const std::string & sm) { if (! test_option_bit(option_session_mgr)) { session value = session::none; if (sm == "nsm") value = session::nsm; else if (sm == "jack") value = session::jack; m_session_manager = value; set_option_bit(option_session_mgr); } } bool usrsettings::fingerprint_size (int sz) { bool result = (sz == c_fingerprint_none) || ( sz >= c_fingerprint_size_min && sz <= c_fingerprint_size_max ); if (result) m_fingerprint_size = sz; return result; } int usrsettings::scale_size (int value, bool shrinkmore) const { float s = m_window_scale; if (shrinkmore) s *= 0.8; return int(s * value + 0.5); } int usrsettings::scale_size_y (int value, bool shrinkmore) const { float s = m_window_scale_y; if (shrinkmore) s *= 0.75; return int(s * value + 0.5); } int usrsettings::mainwnd_x () const { return m_window_scale != 1.0f ? int(scale_size(m_mainwnd_x)) : m_mainwnd_x ; } int usrsettings::mainwnd_y () const { return m_window_scale_y != 1.0f ? int(scale_size_y(m_mainwnd_y)) : m_mainwnd_y ; } int usrsettings::mainwnd_x_min () const { return int(scale_size(m_mainwnd_x, true)); } int usrsettings::mainwnd_y_min () const { return int(scale_size_y(m_mainwnd_y, true)); } /** * Validated in the qloopbutton class as well. Now forces reasonable sizes. */ bool usrsettings::progress_box_size (double w, double h) { bool result = (w != m_progress_box_width) || (h != m_progress_box_height); if (result) { if (w < c_progress_box_width_min || w > c_progress_box_width_max) w = c_progress_box_width; if (h < c_progress_box_height_min || h > c_progress_box_height_max) h = c_progress_box_height; m_progress_box_width = w; m_progress_box_height = h; } return result; } /** * Adds a user buss to the container, but only does so if the name * parameter is not empty. */ bool usrsettings::add_bus (const std::string & alias) { bool result = ! alias.empty(); if (result) { size_t currentsize = m_midi_buses.size(); usermidibus temp(alias); result = temp.is_valid(); if (result) { m_midi_buses.push_back(temp); result = m_midi_buses.size() == currentsize + 1; } } return result; } /** * Adds a user instrument to the container, but only does so if the name * parameter is not empty. */ bool usrsettings::add_instrument (const std::string & name) { bool result = ! name.empty(); if (result) { size_t currentsize = m_instruments.size(); userinstrument temp(name); result = temp.is_valid(); if (result) { m_instruments.push_back(temp); result = m_instruments.size() == currentsize + 1; } } return result; } /** * \getter m_midi_buses[index] (internal function) * If the index is out of range, then an invalid object is returned. * This invalid object has an empty alias, and all the instrument * numbers are -1. */ usermidibus & usrsettings::private_bus (int index) { static usermidibus s_invalid; /* invalid by default */ if (index >= 0 && index < int(m_midi_buses.size())) return m_midi_buses[index]; else return s_invalid; } /** * \getter m_midi_buses[index].instrument[channel] * Currently this function is used, in the usrfile::parse() * function. */ bool usrsettings::set_bus_instrument (int index, int channel, int instrum) { usermidibus & mb = private_bus(index); bool result = mb.is_valid(); if (result) result = mb.set_instrument(channel, instrum); if (! result) { char temp[80]; snprintf ( temp, sizeof temp, "set_bus_instrument(%d, %d, %d) failed", index, channel, instrum ); errprint(temp); } return result; } /** * \getter m_instruments[index] * If the index is out of range, then a invalid object is returned. * This invalid object has an empty(), instrument name, false for all * controllers_active[] values, and empty controllers[] string values. */ userinstrument & usrsettings::private_instrument (int index) { static userinstrument s_invalid; if (index >= 0 && index < int(m_instruments.size())) return m_instruments[index]; else return s_invalid; } /** * \setter m_midi_instrument_defs[index].controllers, controllers_active */ bool usrsettings::set_instrument_controllers ( int index, int cc, const std::string & ccname, bool isactive ) { userinstrument & mi = private_instrument(index); bool result = mi.is_valid(); if (result) result = mi.set_controller(cc, ccname, isactive); if (! result) { char temp[80]; snprintf ( temp, sizeof temp, "set_instrument_controllers(%d, %d, %s) failed", index, cc, ccname.c_str() ); errprint(temp); } return result; } /** * \setter m_window_scale and m_window_scale_y * * For small device screens (800x480), use winscale = 0.85 and winscaley = * 0.55 approximately. * * Note that testing the option_scale bit prevent the scale from being * modified when the window is resized. We need another parameter for that. */ bool usrsettings::window_scale (float winscale, float winscaley, bool useoptionbit) { bool result = ( winscale >= c_window_scale_min && winscale <= c_window_scale_max && (! useoptionbit || ! test_option_bit(option_scale)) ); if (result) { m_window_scale = winscale; set_option_bit(option_scale); if (winscaley >= c_window_scale_min && winscaley <= c_window_scale_max) m_window_scale_y = winscaley; else m_window_scale_y = winscale; } return result; } /** * Provides a way to rescale the window settings when the user manually * changes the size of the main window. */ bool usrsettings::window_rescale (int new_width, int new_height) { float wscale = float(new_width) / float(c_default_window_width); float wscaley = float(new_height) / float(c_default_window_height); if (new_height == 0) wscaley = 0.0; return window_scale(wscale, wscaley); } bool usrsettings::parse_window_scale (const std::string & source) { bool result = false; tokenization tokens = seq66::tokenize(source, "x, "); if (tokens.size() > 0) { double value1 = string_to_double(tokens[0]); if (tokens.size() > 1) { double value2 = string_to_double(tokens[1]); result = window_scale(value1, value2, true); } else result = window_scale(value1, 0.0, true); } else { if (! source.empty()) { double value = string_to_double(source); result = window_scale(value); } } return result; } int usrsettings::scale_font_size (int value) const { int result = value; if (window_is_scaled()) { result = m_window_scale <= m_window_scale_y ? scale_size(value) : scale_size_y(value) ; } return result; } /** * \setter m_mainwnd_rows * This value is not modified unless the value parameter is * between 4 and 8, inclusive. The default value is 4. * Dependent values are recalculated after the assignment. */ bool usrsettings::mainwnd_rows (int r) { bool result = ( (r >= screenset::c_min_rows) && (r <= screenset::c_max_rows) ); /* * This test can disable setting the option bit. * * if (result) * result = r != m_mainwnd_rows; */ if (result) { result = ! test_option_bit(option_rows); if (result) { m_mainwnd_rows = r; normalize(); set_option_bit(option_rows); } } return result; } /** * \setter m_mainwnd_cols * This value is not modified unless the value parameter is * between 4 and 12, inclusive. The default value is 8. * Dependent values are recalculated after the assignment. */ bool usrsettings::mainwnd_cols (int c) { bool result = ( (c >= screenset::c_min_columns) && (c <= screenset::c_max_columns) ); /* * This test can disable setting the option bit. * * if (result) * result = c != m_mainwnd_cols; */ if (result) { result = ! test_option_bit(option_columns); if (result) { m_mainwnd_cols = c; normalize(); set_option_bit(option_columns); } } return result; } /** * \setter m_seqchars_x * This affects the size or crampiness of a pattern slot, and for now * we will hardwire it to 15. */ void usrsettings::seqchars_x (int value) { if (value == 15) { m_seqchars_x = value; /* * Unnecessary: normalize(); */ } } /** * \setter m_seqchars_y * This affects the size or crampiness of a pattern slot, and for now * we will hardwire it to 5. */ void usrsettings::seqchars_y (int value) { if (value == 5) { m_seqchars_y = value; /* * Unnecessary: normalize(); */ } } /** * \setter m_mainwnd_spacing * This value is not modified unless the value parameter is * between 0 and 16, inclusive. The default value is 2. * Dependent values are recalculated after the assignment. */ void usrsettings::mainwnd_spacing (int value) { if (value >= 0 && value <= 16) { m_mainwnd_spacing = value; /* * Unnecessary: normalize(); */ } } /** * \setter m_base_zoom * This value is not modified unless the value parameter is between 1 and * 512, inclusive. The default value is 2. Note that 0 is allowed as a * special case, which allows the default zoom to be adjusted when the * PPQN value is different from the default. */ void usrsettings::base_zoom (int value) { bool ok = value >= c_min_zoom && value <= c_max_zoom; if (ok || value == 0) /* 0 == use zoom power of 2 */ m_base_zoom = value; } /** * Calculates the actual jitter range from the given snap value. * * \param snap * The snap value in ticks (pulses). * * \return * Returns the fraction of snap as the jitter range. */ midipulse usrsettings::jitter_range (int snap) { midipulse result = 8; /* a default in case of error */ if (snap > m_jitter_divisor) result = snap / m_jitter_divisor; return result; } /** * Resets the PPQN members to the startup state. * This function needs to be called whenever File / New is performed. */ void usrsettings::reset_ppqn () { m_default_ppqn = m_midi_ppqn = m_file_ppqn = c_base_ppqn; } void usrsettings::default_ppqn (int value) { if (value >= c_minimum_ppqn && value <= c_maximum_ppqn) m_default_ppqn = value; } int usrsettings::base_ppqn () const { return c_base_ppqn; } bool usrsettings::is_ppqn_valid (int ppqn) const { return ppqn >= c_minimum_ppqn && ppqn <= c_maximum_ppqn; } /** * \setter m_midi_ppqn * This value can be set from 24 to 19200 (this upper limit will be * determined by what Seq66 can actually handle). The default value is * 192. However, if we're using file-ppqn as per the 'usr' file, then the * given value will be used even if out-of-range. */ void usrsettings::midi_ppqn (int value) { if (! test_option_bit(option_ppqn)) { if (value >= c_minimum_ppqn && value <= c_maximum_ppqn) { m_midi_ppqn = value; } else { if (value == 0) m_use_file_ppqn = true; if (m_use_file_ppqn) m_midi_ppqn = value; else m_midi_ppqn = default_ppqn(); } set_option_bit(option_ppqn); } } bool usrsettings::bpb_is_valid (int v) const { return v >= c_min_beats_per_measure && v <= c_max_beats_per_measure; } int usrsettings::bpb_default () const { return c_def_beats_per_measure; } /** * Beat width should be a power of two. We currently enforce that in * the user interface, though. */ bool usrsettings::bw_is_valid (int v) const { return v >= c_min_beat_width && v <= c_max_beat_width; } int usrsettings::bw_default () const { return c_def_beat_width; } bool usrsettings::bpm_is_valid (midibpm v) const { return v >= c_min_beats_per_minute && v <= c_max_beats_per_minute; } midibpm usrsettings::bpm_default () const { return c_def_beats_per_minute; } midilong usrsettings::scaled_bpm (midibpm bpm) { return bpm * c_beats_per_minute_scale; } midibpm usrsettings::unscaled_bpm (midilong bpm) { midibpm result = midibpm(bpm); if (result > (c_beats_per_minute_scale - 1.0f)) result /= c_beats_per_minute_scale; return result; } long usrsettings::tap_button_timeout () const { return c_bpm_tap_button_timeout; } int usrsettings::min_key_height () const { return c_min_key_height; } int usrsettings::max_key_height () const { return c_max_key_height; } std::string usrsettings::key_view_string () const { std::string result; switch (m_user_ui_key_view) { case showkeys::octave_letters: result = "octave-letters"; break; case showkeys::even_letters: result = "even-letters"; break; case showkeys::all_letters: result = "all-letters"; break; case showkeys::even_numbers: result = "even-numbers"; break; case showkeys::all_numbers: result = "all-numbers"; break; } return result; } void usrsettings::key_view (const std::string & view) { if (view == "even-letters") m_user_ui_key_view = showkeys::even_letters; else if (view == "all-letters") m_user_ui_key_view = showkeys::all_letters; else if (view == "even-numbers") m_user_ui_key_view = showkeys::even_numbers; else if (view == "all-numbers") m_user_ui_key_view = showkeys::all_numbers; else m_user_ui_key_view = showkeys::octave_letters; /* the default */ } /** * \setter m_midi_beats_per_measure * This value can be set from 1 to 20. The default value is 4. */ void usrsettings::midi_beats_per_bar (int value) { if (bpb_is_valid(value)) m_midi_beats_per_measure = value; } /** * \setter m_midi_bpm_minimum * This value can be set from 2 to 600. The default value is 2. */ void usrsettings::midi_bpm_minimum (midibpm value) { if (bpm_is_valid(value)) m_midi_bpm_minimum = value; } /** * \setter m_midi_beats_minute * This value can be set from 2 to 600. The default value is 120. */ void usrsettings::midi_beats_per_minute (midibpm value) { if (bpm_is_valid(value)) m_midi_beats_per_minute = value; } /** * \setter m_midi_bpm_maximum * This value can be set from 2 to 600. The default value is 600. */ void usrsettings::midi_bpm_maximum (midibpm value) { if (bpm_is_valid(value)) m_midi_bpm_maximum = value; } void usrsettings::midi_beat_width (int bw) { if (bw_is_valid(bw)) m_midi_beat_width = bw; } /** * This value can be set from 0 to c_busscount_max. The default value is -1 * (0xFF), which means that there is no buss override, as defined by the * inline function is_null_buss() in midibytes.hpp. It provides a way to * override the buss number for smallish MIDI files. It replaces the * buss-number read from the file. This option is turned on by the --bus * option, and is merely a convenience feature for the quick previewing of a * tune. (It's called "developer laziness".) */ void usrsettings::midi_buss_override (bussbyte buss, bool userchange) { if (is_valid_buss(buss)) /* good value or a null value */ { if (userchange || ! test_option_bit(option_buss)) { m_midi_buss_override = buss; set_option_bit(option_buss); } } } void usrsettings::velocity_override (int vel) { if (vel > c_max_note_on_velocity) vel = c_max_note_on_velocity; else if (vel <= 0) vel = c_preserve_velocity; m_velocity_override = vel; } short usrsettings::preserve_velocity () const { return c_preserve_velocity; } short usrsettings::note_off_velocity () const { return c_def_note_off_velocity; } short usrsettings::note_on_velocity () const { return c_def_note_on_velocity; } short usrsettings::max_note_on_velocity () const { return c_max_note_on_velocity; } /** * \setter m_bpm_precision */ void usrsettings::bpm_precision (int precision) { if (precision > c_max_bpm_precision) precision = c_max_bpm_precision; else if (precision < c_min_bpm_precision) precision = c_min_bpm_precision; m_bpm_precision = precision; } void usrsettings::bpm_step_increment (midibpm increment) { if (increment > c_max_bpm_increment) increment = c_max_bpm_increment; else if (increment < c_min_bpm_increment) increment = c_min_bpm_increment; m_bpm_step_increment = increment; } void usrsettings::bpm_page_increment (midibpm increment) { if (increment > c_max_bpm_increment) increment = c_max_bpm_increment; else if (increment < c_min_bpm_increment) increment = c_min_bpm_increment; m_bpm_page_increment = increment; } void usrsettings::option_logfile (const std::string & logfile) { if (is_empty_string(logfile)) { m_user_option_logfile.clear(); m_user_use_logfile = false; } else { std::string normalized = normalize_path(logfile); std::string newlogfile; if (name_has_root_path(normalized)) { newlogfile = normalized; } else { std::string home = rc().home_config_directory(); std::string fullpath = filename_concatenate(home, normalized); newlogfile = fullpath; } if (! name_has_extension(newlogfile)) newlogfile = file_extension_set(newlogfile, ".log"); m_user_option_logfile = newlogfile; m_user_use_logfile = true; } set_option_bit(option_log); } void usrsettings::window_redraw_rate (int ms) { if (ms >= c_minimum_redraw && ms <= c_maximum_redraw) m_window_redraw_rate_ms = ms; } bool usrsettings::is_variset () const { return ( (m_mainwnd_rows != screenset::c_default_rows) || (m_mainwnd_cols != screenset::c_default_columns) ); } void usrsettings::option_daemonize (bool flag, bool setup) { if (! test_option_bit(option_daemon)) { m_user_option_daemonize = flag; set_option_bit(option_daemon); if (setup) { m_user_save_daemonize = true; rc().auto_usr_save(true); } } } void usrsettings::option_use_logfile (bool flag) { if (! test_option_bit(option_log)) { m_user_use_logfile = flag; set_option_bit(option_log); } } bool usrsettings::is_default_mainwnd_size () const { return ( (m_mainwnd_rows == screenset::c_default_rows) && (m_mainwnd_cols == screenset::c_default_columns) ); } bool usrsettings::vertically_compressed () const { return m_mainwnd_rows > screenset::c_default_rows; } bool usrsettings::horizontally_compressed () const { return m_mainwnd_cols > screenset::c_default_columns; } /** * The primary use of this function is to see if some buttons should be * hidden in the main window, to allow a smaller size. */ bool usrsettings::shrunken () const { bool result = (mainwnd_rows() <= screenset::c_default_rows) && (mainwnd_cols() < screenset::c_default_columns); if (! result) result = (m_window_scale < 0.80) || (m_window_scale_y < 0.75); return result; } static bool default_color_check (const std::string & value) { return ( value == "normal" || value == "default" || value == "?" || value.empty() ); } const std::string & usrsettings::time_fg_color (bool forusrfile) const { if (forusrfile) return m_time_fg_color; else if (default_color_check(m_time_fg_color)) return s_time_fg_color; else return m_time_fg_color; } const std::string & usrsettings::time_bg_color (bool forusrfile) const { if (forusrfile) return m_time_bg_color; else if (m_time_bg_color == "normal") return s_time_no_color; else if (default_color_check(m_time_bg_color)) return s_time_bg_color; else return m_time_bg_color; } std::string usrsettings::time_colors_css () const { std::string result; if (! time_fg_color().empty() && ! time_bg_color().empty()) { char tempcss[64]; (void) snprintf ( tempcss, sizeof tempcss, "color:%s; background-color:%s", time_fg_color().c_str(), time_bg_color().c_str() ); result = std::string(tempcss); } return result; } /** * Provides a debug dump of basic information to help debug a * surprisingly intractable problem with all busses having the name and * values of the last buss in the configuration. */ void usrsettings::dump_summary () { int buscount = bus_count(); printf("[user-midi-bus-definitions] %d busses\n", buscount); for (int b = 0; b < buscount; ++b) { const usermidibus & umb = bus(b); printf(" [user-midi-bus-%d] '%s'\n", b, umb.name().c_str()); } int instcount = instrument_count(); printf("[user-instrument-definitions] %d instruments\n", instcount); for (int i = 0; i < instcount; ++i) { const userinstrument & umi = instrument(i); printf(" [user-instrument-%d] '%s'\n", i, umi.name().c_str()); } printf("\n"); printf ( " mainwnd_rows() = %d\n" " mainwnd_cols() = %d\n" " seqs_in_set() = %d\n" " gmute_tracks() = %d\n" " max_sequence() = %d\n" , mainwnd_rows(), mainwnd_cols(), seqs_in_set(), gmute_tracks(), max_sequence() ); printf ( " seqchars_x(), _y() = %d, %d\n" " mainwnd_spacing() = %d\n" , seqchars_x(), seqchars_y(), mainwnd_spacing() ); printf("\n"); printf ( " midi_ppqn() = %d\n" " midi_beats_per_bar() = %d\n" " midi_beats_per_minute() = %g\n" " midi_beat_width() = %d\n" " midi_buss_override() = %d\n" , midi_ppqn(), midi_beats_per_bar(), midi_beats_per_minute(), midi_beat_width(), int(midi_buss_override()) ); } } // namespace seq66 /* * usrsettings.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/cfg/zoomer.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file zoomer.cpp * * This module declares/defines zoom management. * * \library seq66 application * \author Chris Ahlstrom * \date 2023-09-08 * \updates 2025-07-21 * \license GNU GPLv2 or above * * Refactoring: * * Originally, Seq66 started with PPQN and then derived the proper * ticks-to-pixels conversion. This has issues with PPQNs of 120 * and 240 (versus 192). Let's start with the zoom (ticks per pixel) * and work toward PPQN. The following suggested zoom member functions * are similar to the like-named functions in the calculations module, * but we start from the pixel and move up, ignoring PPQN. * * - pulses_per_pixel(). This is basically the zoom value, which * starts at 2. * - pulses_per_substep(). The sub-step vertical lines are 6 pixels. * We need to stick with that, no matter what the zoom. This function * can call pulses_per_pixel() and multiply it by 6. * - pulses_per_quarter_beat(). Assuming it is good to use a 4th of * a beat (but what about beat-widths of 8, 16, ...? 4/x?) * pulses_per_partial_beat()? Default = factor of 4. * - pulses_per_beat(). Default = factor of 4. * - pulses_per_measure(). Based on beats. * * * Diagram: * * measure * sub-step * quarter-beat * beat (beats) beat measure * ||...:...:...:...|...:...:. . . . ..:...|...:...:...:...|| * * SEQ66_USE_NEW_STYLE_GRID_DRAWING: * * In this method, we replace the 6-pixel sub-step spacing with * spacing based on the time-signature, PPQN, and a sub-step/beat * count. * * Instead of counting by ticks and ticks/step, we count by integers, * each value representing a sub-step. See the discussion in * contrib/notes/ppqn-and-grids.ods. * * in units of ticks (pulses). Let B = beats/bar and W = beat width, * and F be the divisor to get a sub-step. * * We belayed this, as it brings its own issue at other PPQNs. */ #include "cfg/settings.hpp" /* seq66::zoom_items() */ #include "cfg/zoomer.hpp" /* seq66::zoomer class */ namespace seq66 { /** * Default constructor. */ zoomer::zoomer () : m_ppqn (192), m_base_zoom (2), m_zoom (2), m_scale (1), m_scale_zoom (2), m_zoom_index (0), m_zoom_expansion (1) { initialize(); } /** * Principal constructor. */ zoomer::zoomer (int ppq, int initialzoom, int scalex) : m_ppqn (ppq), m_base_zoom (initialzoom), m_zoom (initialzoom), m_scale (scalex > 4 ? scalex / 4 : 1), m_scale_zoom (m_scale * zoom()), /* see change_ppqn() */ m_zoom_index (0), m_zoom_expansion (1) { initialize(); } bool zoomer::initialize () { int index = log2_of_power_of_2(m_base_zoom); bool result = index >= 0; if (result) { m_zoom_index = index; m_zoom_expansion = 0; m_zoom = m_base_zoom; } else { m_zoom_index = 1; m_zoom_expansion = 0; m_zoom = zoom_item(1); } m_scale_zoom = zoom() * m_scale; return result; } /** * Make the view cover less horizontal length. The lowest zoom possible * is 1. But, if the user still wants to zoom in some more, we fake it * by using "zoom expansion". This factor increases the pixel spread by * a factor of 1, 2, 4, or 8. * * If the new index is valid, then the zoom index, expansion factor, and * zoom itself are modified. */ bool zoomer::zoom_in () { int index = m_zoom_index - 1; return set_zoom_by_index(index); } bool zoomer::zoom_out () { int index = m_zoom_index + 1; return set_zoom_by_index(index); } /** * This handles only the normal zooms, no zoom expansion support. * It rejects zooms that are not powers of 2. */ bool zoomer::set_zoom (int z) { int index = log2_of_power_of_2(z); bool result = index >= 0; if (result) set_zoom_by_index(index); return result; } bool zoomer::set_zoom_by_index (int i) { bool result = false; if (i >= 0) { int z = zoom_item(i); if (z > 0) { m_zoom_index = i; m_zoom_expansion = 0; m_zoom = z; m_scale_zoom = zoom() * m_scale; result = true; } } else { m_zoom_expansion = expanded_zoom_item(i); if (expanded_zoom()) { m_zoom_index = i; m_zoom = 1; result = true; } } return result; } bool zoomer::reset_zoom (int ppq) { if (ppq != 0) m_ppqn = ppq; return initialize(); } /* * Takes screen coordinates, give us notes/keys (to be generalized to * other vertical user-interface quantities) and ticks (always the * horizontal user-interface quantity). Compare this function to * qbase::pix_to_tix(). */ midipulse zoomer::pix_to_tix (int x) const { midipulse result = x * pulses_per_pixel(); if (m_ppqn == 32) /* EXPERIMENTAL, special case */ result = x * 0.7937; /* 100 / 126 ~= 24 /32 */ if (expanded_zoom()) result /= m_zoom_expansion; return result; } int zoomer::tix_to_pix (midipulse ticks) const { int result = ticks / pulses_per_pixel(); if (expanded_zoom()) result *= m_zoom_expansion; return result; } /** * Handles changes to the PPQN value in one place. Useful mainly at startup. */ bool zoomer::change_ppqn (int p) { m_scale_zoom = zoom() * m_scale; m_ppqn = p; return true; } /** * Calculates a suitable starting zoom value for the given PPQN value. The * default starting zoom is 2, but this value is suitable only for PPQN of * 192 and below. Also, zoom currently works consistently only if it is a * power of 2. For starters, we scale the zoom to the selected ppqn, and * then shift it each way to get a suitable power of two. * * \param ppqn * The ppqn of interest. * * \return * Returns the power of 2 appropriate for the given PPQN value. */ int zoomer::zoom_power_of_2 (int ppq) { int result = adapted_seq_zoom(ppq); if (ppq < usr().base_ppqn()) m_zoom_expansion = 3; return result; } /* * Free function. */ /** * Calculates a suitable starting zoom value for the given PPQN value. The * default starting zoom is 2, but this value is suitable only for PPQN of * 192 and below. Also, zoom currently works consistently only if it is a * power of 2. For starters, we scale the zoom to the selected ppqn, and * then shift it each way to get a suitable power of two. * * \param ppqn * The ppqn of interest. * * \return * Returns the power of 2 appropriate for the given PPQN value. */ int adapted_seq_zoom (int ppq) { int result = c_default_seq_zoom; if (ppq > usr().base_ppqn()) { int zoom = result * ppq / usr().base_ppqn(); result = next_power_of_2(zoom); if (result > c_maximum_zoom) result = c_maximum_zoom; else if (result == 0) result = c_minimum_zoom; } else if (ppq < usr().base_ppqn()) result = c_minimum_zoom; return result; } int adapted_perf_zoom (int ppq) { int result = c_default_perf_zoom; if (ppq > usr().base_ppqn()) { int zoom = result * ppq / usr().base_ppqn(); result = next_power_of_2(zoom); if (result > c_maximum_zoom) result = c_maximum_zoom; else if (result == 0) result = c_minimum_zoom; } else if (ppq < usr().base_ppqn()) result = c_minimum_zoom; return result; } } // namespace seq66 /* * zoomer.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/automation.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file automation.cpp * * This module declares/defines just some of the global (gasp!) variables * and functions for the extended MIDI control feature. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-18 * \updates 2024-01-03 * \license GNU GPLv2 or above * * Currently, there is no code in this file. * * Concept: * * In the original (Seq24/Seq64) perform class, there were 32 pattern * controls, 32 mute-group controls, and 32 automation controls, all * in the same [midi-control] section. Each MIDI contol line was placed into * one of 3 arrays of MIDI controls, for toggle, on, and off settings. * In perform :: handle_midicontrol_event(), each array was checked for a * match to the incoming MIDI control, and a perform function was executed. * * In the new version, we want to look up an incoming MIDI event and * determine in which control-section it belongs, and which kind of event it * is (toggle/on/off). If it is a pattern control, it will call * performer::sequence_playing_toggle() with a pattern number. If it is a * mute-group control, it will call performer::select_and_mute_group(). * If it is an automation control, it will call some other performer member * function. All of these functions will accept an action parameter * (toggle/on/off), a pattern or mute-group number, or some other value if * applicable. * * Status bits (enum class ctrlstatus): * * These were purely internal constants used with the functions that * implement MIDI control (and also some keystroke control) for the * application, and they were defined in the perform header file in Seq64. * However, we now have to expose them for the Qt5 implementation, until we * can entirely reconcile/refactor the Kepler34-based body of code. Note how * they specify different bit values, as it they could be masked together to * signal multiple functions. We're going to explain them here so that class * declaration doesn't become difficult to read. * * "replace": * * If this bit is set, then perform::sequence_playing_toggle() unsets * this status and calls perform::off_sequences(), which calls * sequence::set_playing(false) for all active sequences. * * It works like this: * * -# The user presses the Replace key, or the MIDI control message for * c_midicontrol_mod_replace is received. * -# This bit is OR'd into perform::m_control_status. This status bit * is used in perform::sequence_playing_toggle(). * - Called in perform::sequence_key() so that keystrokes in * the main window toggle patterns in the main window. * - Called in peform::toggle_other_seqs() to implement * Shift-click to toggle all other patterns but the one * clicked. * - Called in seqmenu::toggle_current_sequence(), called in * mainwnd to implement clicking on a pattern. * - Also used in MIDI control to toggle patterns 0 to 31, * offset by the screen-set. * - perform::sequence_playing_off(), similarly used in MIDI control. * - perform::sequence_playing_on(), similarly used in MIDI control. * -# When the key is released, this bit is AND'd out of * perform::m_control_status. * * Both the MIDI control and the keystroke set the sequence to be * "replaced". * * "snapshot": * * By default, perform::sequence_playing_toggle() calls sequence :: * toggle_playing() on the given sequence number, plus what is noted for * c_status_snapshot. It works like this: * * -# The user presses the Snapshot key. * -# This bit is OR'd into perform::m_control_status. * -# The playing state of the patterns is saved by * perform::save_playing_state(). * -# When the key is released, this bit is AND'd out of * perform::m_control_status. * -# The playing state of the patterns is restored by * perform::restore_playing_state(). * * "queue": * * If this bit is set, then perform::sequence_playing_toggle() calls * sequence::toggle_queued() on the given sequence number. The regular * queue key (configurable in File / Options / Keyboard) sets this bit * when pressed, and unsets it when released. The keep-queue key sets * it, but it is not unset until the regular queue key is pressed and * released. * * "one-shot": * * This value signals the Kepler34 "one-shot" functionality. If this bit * is set, then perform::sequence_playing_toggle() calls * sequence::toggle_oneshot() on the given sequence number. * */ #include "ctrl/automation.hpp" /* seq66::automation base class */ namespace seq66 { namespace automation { std::string category_to_string (category c) { switch (c) { case category::none: return std::string("none"); case category::loop: return std::string("loop"); case category::mute_group: return std::string("mutegroup"); case category::automation: return std::string("automation"); default: return std::string("unknown"); } } category string_to_category (const std::string & s) { if (s == "none") return category::none; else if (s == "loop") return category::loop; else if (s == "mutegroup") return category::mute_group; else if (s == "automation") return category::automation; else return category::none; } std::string action_to_string (action c) { switch (c) { case action::none: return std::string("none"); case action::toggle: return std::string("toggle"); case action::on: return std::string("on"); case action::off: return std::string("off"); default: return std::string("unknown"); } } action string_to_action (const std::string & s) { if (s == "none") return action::none; else if (s == "toggle") return action::toggle; else if (s == "on") return action::on; else if (s == "off") return action::off; else return action::none; } /** * Some actions should work whether the user defined it as "on" or * a "toggle". For example, seee performer::automation_grid_mode(). * This function is especially important because the keystrokes configured * in the 'ctrl' file are always treated like toggles. */ bool actionable (action a) { return (a == action::on || a == action::toggle); } /** * Lists the bits set in a control-status value. */ std::string ctrlstatus_to_string (ctrlstatus cs) { std::string result; if (bit_test_and(cs, ctrlstatus::replace)) result += "replace "; if (bit_test_and(cs, ctrlstatus::snapshot)) result += "snapshot "; if (bit_test_and(cs, ctrlstatus::queue)) result += "queue "; if (bit_test_and(cs, ctrlstatus::keep_queue)) result += "keep queue "; if (bit_test_and(cs, ctrlstatus::oneshot)) result += "oneshot "; if (bit_test_and(cs, ctrlstatus::learn)) result += "learn "; if (result.empty()) result = "none"; return result; } /* * This code is currently unused. We currently don't need to lookup by a * slots string name, and use performer::print_parameters() on a * hardwired name. */ #if defined SEQ66_USE_SLOT_STRING_CONVERSIONS using slot_pair = struct { slot slotcode; std::string slotname; }; /** * Compare this list to the similar list in libseq66/src/ctrl/opcontrol.cpp. * They differ in letter case and (slightly) in numbering. * * This list is meant for (eventually) lookups of names rather than numbers * in configuration files. */ static slot_pair s_slotnamelist [] = { /* * { slot::none, "none" }, */ { slot::bpm_up, "bpm_up" }, { slot::bpm_dn, "bpm_dn" }, { slot::ss_up, "ss_up" }, { slot::ss_dn, "ss_dn" }, { slot::mod_replace, "mod_replace" }, { slot::mod_snapshot, "mod_snapshot" }, { slot::mod_queue, "mod_queue" }, { slot::mod_gmute, "mod_gmute" }, { slot::mod_glearn, "mod_glearn" }, { slot::play_ss, "play_ss" }, { slot::playback, "playback" }, { slot::song_record, "song_record" }, { slot::solo, "solo" }, { slot::thru, "thru" }, { slot::bpm_page_up, "bpm_page_up" }, { slot::bpm_page_dn, "bpm_page_dn" }, { slot::ss_set, "ss_set" }, { slot::record_style, "record_style" }, { slot::quan_record, "quan_record" }, { slot::reset_sets, "reset_sets" }, { slot::mod_oneshot, "mod_oneshot" }, { slot::FF, "FF" }, { slot::rewind, "rewind" }, { slot::top, "top" }, { slot::playlist, "playlist" }, { slot::playlist_song, "playlist_song" }, { slot::tap_bpm, "tap_bpm" }, { slot::start, "start" }, { slot::stop, "stop" }, { slot::reserved_29, "reserved_29" }, { slot::toggle_mutes, "toggle_mutes" }, { slot::song_pointer, "song_pointer" }, /* * The following add to what Seq64 supports. */ { slot::keep_queue, "keep_queue" }, { slot::slot_shift, "slot_shift" }, { slot::mutes_clear, "mutes_clear" }, { slot::quit, "quit" }, { slot::pattern_edit, "pattern_edit" }, { slot::event_edit, "event_edit" }, { slot::song_mode, "song_mode" }, { slot::toggle_jack, "toggle_jack" }, { slot::menu_mode, "menu_mode" }, { slot::follow_transport, "follow_transport" }, { slot::panic, "panic" }, { slot::visibility, "visibility" }, { slot::save_session, "save_session" }, { slot::record_toggle, "record_toggle" }, { slot::grid_mutes, "grid_mutes" }, { slot::reserved_47, "reserved_47" }, { slot::reserved_48, "reserved_48" }, /* * Proposed massive expansion in automation. Grid mode selection. */ { slot::record_overdub, "record_overdub" }, { slot::record_overwrite, "record_overwrite" }, { slot::record_expand, "record_expand" }, { slot::record_oneshot, "record_oneshot" }, { slot::grid_loop, "grid_loop" }, { slot::grid_record, "grid_record" }, { slot::grid_copy, "grid_copy" }, { slot::grid_paste, "grid_paste" }, { slot::grid_clear, "grid_clear" }, { slot::grid_delete, "grid_delete" }, { slot::grid_thru, "grid_thru" }, { slot::grid_solo, "grid_solo" }, { slot::grid_velocity, "grid_velocity" }, { slot::grid_double, "grid_double" }, /* * Grid quantization type selection. */ { slot::grid_quant_none, "grid_quant_none" }, { slot::grid_quant_full, "grid_quant_full" }, { slot::grid_quant_tighten, "grid_quant_tighten" }, { slot::grid_quant_random, "grid_quant_random" }, { slot::grid_quant_jitter, "grid_quant_jitter" }, { slot::grid_quant_notemap, "grid_quant_notemap", }, /* * A few more likely candidates. */ { slot::mod_bbt_hms, "mod_bbt_hms" }, { slot::mod_LR_loop, "mod_LR_loop" }, { slot::mod_undo, "mod_undo" }, { slot::mod_redo, "mod_redo" }, { slot::mod_transpose_song, "mod_transpose_song" }, { slot::mod_copy_set, "mod_copy_set" }, { slot::mod_paste_set, "mod_paste_set" }, { slot::mod_toggle_tracks, "mod_toggle_tracks" }, /* * Set playing modes. */ { slot::set_mode_normal, "set_mode_normal" }, { slot::set_mode_auto, "set_mode_auto" }, { slot::set_mode_additive, "set_mode_additive" }, { slot::set_mode_all_sets, "set_mode_all_sets" }, /* * Tricky ending. */ { slot::max, "maximum" }, { slot::loop, "loop" }, { slot::mute_group, "mute_group" }, { slot::automation, "automation" }, { slot::illegal, "illegal" } }; /* * This function is not used. And the slotname field is basically the same as * the std::string returned by opcontrol::slot_name(slot s). */ std::string slot_to_string (slot s) { std::string result; if (s >= slot::bpm_up && s < slot::illegal) { int index = static_cast(s); result = s_slotnamelist[index].slotname; } return result; } slot string_to_slot (const std::string & s) { slot result = slot::illegal; slot_pair * sptr = &s_slotnamelist[0]; for (int i = 0; ; ++i) { if (sptr->slotname == s || sptr->slotcode == slot::illegal) { result = sptr->slotcode; break; } } return result; } #endif // defined SEQ66_USE_SLOT_STRING_CONVERSIONS } // namespace automation } // namespace seq66 /* * automation.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/keycontainer.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file keycontainer.cpp * * This module declares/defines a container for key-ordinals and MIDI * operation information. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-18 * \updates 2024-01-02 * \license GNU GPLv2 or above * */ #include /* std::setw manipulator */ #include /* std::cerr */ #include "ctrl/keycontainer.hpp" /* seq66::keycontainer class */ #include "util/strfunctions.hpp" /* seq66::strcasecompare() */ namespace seq66 { /** * This default constructor creates a "zero" object. Every member is * either false or some other form of zero. */ keycontainer::keycontainer () : m_container (), m_container_name ("Default keys"), m_pattern_keys (), m_mute_keys (), m_automation_keys (), m_loaded_from_rc (false), m_use_auto_shift (true), m_kbd_layout (keyboard::layout::qwerty), m_defaults_loaded (false) { add_defaults(); } /** * This constructor assigns the basic values of control name, number, and * action code. The rest of the members can be set via the set() function. * * ca 2022-08-08: Found that the last four members were not in the * initializer list! Thank you valgrind! */ keycontainer::keycontainer (const std::string & name) : m_container (), m_container_name (name), m_pattern_keys (), m_mute_keys (), m_automation_keys (), m_loaded_from_rc (false), m_use_auto_shift (true), m_kbd_layout (keyboard::layout::qwerty), m_defaults_loaded (false) { add_defaults(); } /** * Adds the keystroke ordinal and it corresponding key control to the key * container. * * \param ordinal * Provides the keystroke value (see the keymap.cpp module). This is an * internal value ranging from 0x00 to 0xfe that can be tied to an * operation/control. For the ASCII character set, this value is the * same as the key-code returned by the Qt function nativeVirtualKey(). * For other characters, we have to look up the key-code to find the * proper ordinal. This value is also stored in the keycontrol for future * use. * * \param op * Provides the key-control operation to be triggered by this keystroke. * * \return * Returns true if the container size increased by 1. */ bool keycontainer::add (ctrlkey ordinal, const keycontrol & op) { bool result = false; keycontrol & nc = const_cast(op); nc.ordinal(ordinal); /* now we can set key's ordinal */ auto sz = m_container.size(); auto p = std::make_pair(ordinal, op); /* pair */ (void) m_container.insert(p); result = m_container.size() == (sz + 1); if (result) { /* no code needed */ } else { std::string tag = is_invalid_ordinal(ordinal) ? "Invalid" : "Duplicate" ; std::cerr << tag << " key (#" << ordinal << " = '" << qt_ordinal_keyname(ordinal) << "')" << " for '" << op.name() << "' Type " << op.category_name() << std::endl ; } return result; } /** * Add to a map of key-names (normally a single character) keyed by slot * numbers. These are useful in updating the live windows. Only the * pattern-control mappings are added to this container. * * \param op * Provides the key-control operation to be triggered by this keystroke. * This item provides the pattern offset for the slot, and the name of * the keystroke that toggles the slot. * * \return * Returns true if the container size increased by 1. */ bool keycontainer::add_slot (const keycontrol & op) { bool result = false; int keyslot = op.control_code(); /* pattern offset */ std::string keyname = op.key_name(); auto p = std::make_pair(keyslot, keyname); auto r = m_pattern_keys.insert(p); result = r.second; if (result) { /* no code needed */ } else { std::cerr << "Duplicate pattern slot #" << std::setw(3) << keyslot << " : '" << keyname << "'" << std::endl ; } return result; } /** * Add to a map of key-names (normally a single character) keyed by mute * numbers. These are useful in updating the mute-master window. Only the * mute-group-control mappings are added to this container. * * \param op * Provides the key-control operation to be triggered by this keystroke. * This item provides the mute offset for the mute group, and the name of * the keystroke that activates the mute group. * * \return * Returns true if the container size increased by 1. */ bool keycontainer::add_mute (const keycontrol & op) { bool result = false; int keyslot = op.control_code(); /* pattern offset */ std::string keyname = op.key_name(); auto p = std::make_pair(keyslot, keyname); auto r = m_mute_keys.insert(p); result = r.second; if (result) { /* no code needed */ } else { std::cerr << "Duplicate mute slot #" << std::setw(3) << keyslot << " : '" << keyname << "'" << std::endl ; } return result; } /** * Function added for issue #114. */ bool keycontainer::add_automation (const keycontrol & op) { bool result = false; int keyslot = op.control_code(); /* pattern offset */ std::string keyname = op.key_name(); auto p = std::make_pair(keyslot, keyname); auto r = m_automation_keys.insert(p); result = r.second; if (result) { /* no code needed */ } else { std::cerr << "Duplicate automation slot #" << std::setw(3) << keyslot << " : '" << keyname << "'" << std::endl ; } return result; } /** * Looks up the key-control object matching the given keystroke ordinal, as * returned by qt_modkey_ordinal() or qt_keyname_ordinal(). */ const keycontrol & keycontainer::control (ctrlkey ordinal) const { static keycontrol sm_keycontrol_dummy; const auto & cki = m_container.find(ordinal); return (cki != m_container.end()) ? cki->second : sm_keycontrol_dummy; } /** * For issue #47, we set up the key-map to use the hex-code for the name of * the key. See the discussion in keymap.cpp for the function qt_keys(). Our * detection of this case is that the name begins with "0x", which is * sufficient based on the contents of the keymap. * * However, we've also added some more names to the list in the keymap * module, so these numeric names won't occur all that often. */ std::string keycontainer::slot_key (int pattern_offset) const { std::string result; auto p = m_pattern_keys.find(pattern_offset); if (p != m_pattern_keys.end()) { result = p->second; if (result[0] == '0' && result[1] == 'x') { char ch = char(std::stoi(result, nullptr, 0)); result = ch; } } else result = "?"; return result; } /** * Similar to slot_key(). */ std::string keycontainer::mute_key (int mute_offset) const { std::string result; auto p = m_mute_keys.find(mute_offset); if (p != m_mute_keys.end()) { result = p->second; if (result[0] == '0' && result[1] == 'x') { char ch = char(std::stoi(result, nullptr, 0)); result = ch; } } else result = "?"; return result; } /** * Function added for issue #114. * Similar to slot_key(). */ std::string keycontainer::automation_key (int ctrlcode) const { std::string result; auto p = m_automation_keys.find(ctrlcode); if (p != m_automation_keys.end()) { result = p->second; if (result[0] == '0' && result[1] == 'x') { char ch = char(std::stoi(result, nullptr, 0)); result = ch; } } else result = "?"; return result; } /** * This function was added in order to be able to get the keystroke from * the mute-group in order to try to display that the mute-group learn worked * via MIDI control. */ keystroke keycontainer::mute_keystroke (int mute_offset) const { static keystroke s_empty; std::string keyname = mute_key(mute_offset); if (keyname != "?") { bool press = true; /* doesn't matter which */ ctrlkey ordinal = qt_keyname_ordinal(keyname); return keystroke(ordinal, press); } else return s_empty; } /** * If verbose is set, shows the contents of the keycontainer. */ void keycontainer::show () const { using namespace std; int index = 0; std::string tag = "Key container size: "; tag += std::to_string(m_container.size()); info_message(tag); tag = "Index Key Name Category Action Slot/Code"; info_message(tag); tag.clear(); for (const auto & kp : m_container) { unsigned key = kp.first; if (key > 0xff) key = 0xff; info_message(tag); std::cout << "[" << std::setw(3) << std::right << index << "] " << "(0x" << std::hex << std::setw(2) << std::right << key << ") " ; kp.second.show(); ++index; } } void keycontainer::set_kbd_layout (const std::string & lay) { if (strcasecompare(lay, "normal")) m_kbd_layout = keyboard::layout::qwerty; else if (strcasecompare(lay, "qwerty")) m_kbd_layout = keyboard::layout::qwerty; else if (strcasecompare(lay, "qwertz")) m_kbd_layout = keyboard::layout::qwertz; else if (strcasecompare(lay, "azerty")) m_kbd_layout = keyboard::layout::azerty; else m_kbd_layout = keyboard::layout::qwerty; modify_keyboard_layout(m_kbd_layout); if (m_kbd_layout == keyboard::layout::azerty) use_auto_shift(false); } std::string keycontainer::kbd_layout_to_string (keyboard::layout lay) { std::string result; if (lay == keyboard::layout::qwerty) result = "qwerty"; else if (lay == keyboard::layout::qwertz) result = "qwertz"; else if (lay == keyboard::layout::azerty) result = "azerty"; return result; } /** * Static function to look up the default name of a key based on * its index (actually a ctrlkey value). */ const std::string & keycontainer::automation_default_key_name (int index) { static std::string s_dummy; const defaults & keylist = keys_automation(); if (index >= 0 && index < int(keylist.size())) return keylist[index].kd_name; else return s_dummy; } /** * Indicates the default keystroke and action status of a particular * automation keystroke operation. Matches the automation::slot enum * class. * * Default keystrokes still available (around 24 of them): * * " ( ) + 9 : > ? L O ) _ p { } DEL Del Tab BkTab BS * End KP_End KP_Del KP_PageUp, KP_PageFn KP_. KP_/ * KP_Left, _Right, _Up, _Down ? Return ? Enter ? */ const keycontainer::defaults & keycontainer::keys_automation () { static defaults s_keys_automation = { { "'", automation::action::on }, // 0 bpm_up { ";", automation::action::on }, // 1 bpm_dn { "]", automation::action::on }, // 2 ss_up { "[", automation::action::on }, // 3 ss_dn { "KP_Home", automation::action::toggle }, // 4 mod_replace { "Ins", automation::action::toggle }, // 5 mod_snapshot { "o", automation::action::toggle }, // 6 mod_queue { "`", automation::action::on }, // 7 mod_gmute { "l", /*el*/ automation::action::on }, // 8 mod_glearn { "Home", automation::action::on }, // 9 play_ss { ".", automation::action::toggle }, // 10 playback (pause) { "P", automation::action::on }, // 11 song_record { "BkSpace", automation::action::on }, // 12 solo { "LF", automation::action::on }, // 13 thru { "PageUp", automation::action::on }, // 14 bpm_page_up { "PageDn", automation::action::on }, // 15 bpm_page_dn { "KP_.", automation::action::on }, // 16 ss_set { "KP_*", automation::action::on }, // 17 record_style { "KP_-", automation::action::on }, // 18 quan_record { "KP_+", automation::action::on }, // 19 reset_sets { "|", automation::action::on }, // 20 mod_oneshot { "F6", automation::action::on }, // 21 FF { "F5", automation::action::on }, // 22 rewind { "F1", automation::action::on }, // 23 top (beginning) { "F2", automation::action::on }, // 24 playlist (next) { "F3", automation::action::on }, // 25 playlist_song (next) { "F9", automation::action::on }, // 26 tap_bpm { "Space", automation::action::on }, // 27 start [not " "!] { "Esc", automation::action::on }, // 28 stop { "KP_Ins", automation::action::on }, // 29 reserved_29 { "F8", automation::action::on }, // 30 toggle_mutes { "F7", automation::action::on }, // 31 song_pointer { "\\", automation::action::toggle }, // 32 keep_queue { "/", automation::action::off }, // 33 slot_shift { "0", automation::action::on }, // 34 mutes_clear { "Quit", automation::action::off }, // 35 quit { "=", automation::action::on }, // 36 pattern_edit { "-", automation::action::on }, // 37 event_edit { "F10", automation::action::on }, // 38 song_mode { "F11", automation::action::on }, // 39 toggle_jack { "F12", automation::action::on }, // 40 menu_mode { "F4", automation::action::on }, // 41 follow_transport { "~", automation::action::on }, // 42 panic { "0xf9", automation::action::toggle }, // 43 visibility { "0xfa", automation::action::off }, // 44 save_session { "+", automation::action::on }, // 45 record_toggle { "_", automation::action::off }, // 46 grid_mutes { "0xfd", automation::action::off }, // 47 reserved_47 { "0xfe", automation::action::off }, // 48 reserved_48 /* * Proposed massive expansion in automation. Grid mode selection. */ { "Sh_F1", automation::action::on }, // 49 record_overdub { "Sh_F2", automation::action::on }, // 50 record_overwrite { "Sh_F3", automation::action::on }, // 51 record_expand { "Sh_F4", automation::action::on }, // 52 record_oneshot { "Sh_F5", automation::action::on }, // 53 grid_loop { "Sh_F6", automation::action::on }, // 54 grid_record { "Sh_F7", automation::action::on }, // 55 grid_copy { "Sh_F8", automation::action::on }, // 56 grid_paste { "Sh_F9", automation::action::on }, // 57 grid_clear { "Sh_F10", automation::action::on }, // 58 grid_delete { "Sh_F11", automation::action::on }, // 59 grid_thru { "Sh_F12", automation::action::on }, // 60 grid_solo { "0xe0", automation::action::on }, // 61 grid_cut { "0xe1", automation::action::on }, // 62 grid_double /* * Grid quantization type selection. */ { "0xe2", automation::action::on }, // 63 grid_quant_none { "0xe3", automation::action::on }, // 64 grid_quant_full { "0xe4", automation::action::on }, // 65 grid_quant_tighten { "0xe5", automation::action::on }, // 66 grid_quant_random { "0xe6", automation::action::on }, // 67 grid_quant_jitter { "0xe7", automation::action::off }, // 68 grid_quant_notemap /* * A few more likely candidates. */ { "0xe8", automation::action::off }, // 69 mod_bbt_hms { "0xe9", automation::action::off }, // 70 mod_LR_loop { "0xea", automation::action::off }, // 71 mod_undo { "0xeb", automation::action::off }, // 72 mod_redo { "0xec", automation::action::off }, // 73 mod_transpose_song { "0xed", automation::action::off }, // 74 mod_copy_set { "0xee", automation::action::off }, // 75 mod_paste_set { "0xef", automation::action::off }, // 76 mod_toggle_tracks /* * Set playing modes. */ { "0x8c", automation::action::off }, // 77 set_mode_normal { "0x8d", automation::action::off }, // 78 set_mode_auto { "0x8e", automation::action::off }, // 79 set_mode_additive { "0x8f", automation::action::off }, // 80 set_mode_all_sets /* * Tricky ending. */ { "0xff", automation::action::off }, // -- maximum }; return s_keys_automation; } /** * We had to put the static vectors inside this function because they were * not initialized in time for their usage. Odd. */ void keycontainer::add_defaults () { static tokenization s_keys_pattern = { "1", /* 0 */ "q", /* 1 */ "a", /* 2 */ "z", /* 3 */ "2", /* 4 */ "w", /* 5 */ "s", /* 6 */ "x", /* 7 */ "3", /* 8 */ "e", /* 9 */ "d", /* 10 */ "c", /* 11 */ "4", /* 12 */ "r", /* 13 */ "f", /* 14 */ "v", /* 15 */ "5", /* 16 */ "t", /* 17 */ "g", /* 18 */ "b", /* 19 */ "6", /* 20 */ "y", /* 21 */ "h", /* 22 */ "n", /* 23 */ "7", /* 24 */ "u", /* 25 */ "j", /* 26 */ "m", /* 27 */ "8", /* 28 */ "i", /* 29 */ "k", /* 30 */ ",", /* 31 */ }; static tokenization s_keys_mute_group = { "!", /* 0 */ "Q", /* 1 */ "A", /* 2 */ "Z", /* 3 */ "@", /* 4 */ "W", /* 5 */ "S", /* 6 */ "X", /* 7 */ "#", /* 8 */ "E", /* 9 */ "D", /* 10 */ "C", /* 11 */ "$", /* 12 */ "R", /* 13 */ "F", /* 14 */ "V", /* 15 */ "%", /* 16 */ "T", /* 17 */ "G", /* 18 */ "B", /* 19 */ "^", /* 20 */ "Y", /* 21 */ "H", /* 22 */ "N", /* 23 */ "&", /* 24 */ "U", /* 25 */ "J", /* 26 */ "M", /* 27 */ "*", /* 28 */ "I", /* 29 */ "K", /* 30 */ "<", /* 31 */ }; if (m_defaults_loaded) return; clear(); /* * Pattern-control keys. */ std::string tagprefix{"Loop "}; /* shorter than "Pattern" */ for (int seq = 0; seq < int(s_keys_pattern.size()); ++seq) { std::string nametag = tagprefix + std::to_string(seq); keycontrol kc ( nametag, s_keys_pattern[seq], /* provides the key name */ automation::category::loop, automation::action::toggle, automation::slot::loop, seq ); ctrlkey ordinal = qt_keyname_ordinal(s_keys_pattern[seq]); if (! add(ordinal, kc)) break; if (! add_slot(kc)) /* provides slot number */ break; } /* * Mute-group-control keys. */ tagprefix = "Mute "; for (int group = 0; group < int(s_keys_mute_group.size()); ++group) { std::string nametag = tagprefix + std::to_string(group); keycontrol kc ( nametag, s_keys_mute_group[group], /* provides the key name */ automation::category::mute_group, automation::action::toggle, automation::slot::mute_group, group ); ctrlkey ordinal = qt_keyname_ordinal(s_keys_mute_group[group]); if (! add(ordinal, kc)) break; if (! add_mute(kc)) /* provides mute number */ break; } /* * Automation-control keys. Any way to grab the real name from Qt? * Doesn't matter, we need our own names. */ tagprefix = "Auto "; automation::category c = automation::category::automation; int ausmax = int(keys_automation().size()) - 1; /* stop before 0xff */ for (int auslot = 0; auslot < ausmax; ++auslot) { automation::slot s = int_to_slot_cast(auslot); automation::action a = keys_automation()[auslot].kd_action; std::string nametag = opcontrol::automation_slot_name(s); std::string keyname = keys_automation()[auslot].kd_name; ctrlkey ordinal = qt_keyname_ordinal(keyname); if (is_invalid_ordinal(ordinal)) { #if defined SEQ66_PLATFORM_DEBUG continue; /* not sure we want to see a message here */ #endif } else { keycontrol kc(nametag, keyname, c, a, s, auslot); if (! add(ordinal, kc)) break; if (! add_automation(kc)) /* provides mute number */ break; } } m_defaults_loaded = true; m_loaded_from_rc = false; } } // namespace seq66 /* * keycontainer.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/keycontrol.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file keycontrol.cpp * * This module declares/defines just some of the global (gasp!) variables * and functions for the extended MIDI control feature. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-09 * \updates 2021-12-31 * \license GNU GPLv2 or above * */ #include /* std::setw() manipulator */ #include /* std::cout (using namespace std) */ #include "ctrl/keycontrol.hpp" /* seq66::keycontrol class */ namespace seq66 { /** * The name for keys that are unused. They should not be added to a * keycontainer, but they are needed in the base class of midicontrol. */ const std::string keycontrol::scm_dead_key_name = "Blank"; /** * This constructor assigns the basic values of control name, number, and * action code. The rest of the members can be set via the set() function. * * \param opname * Provides the name of the control, such as "BPM Up/Down". It is not * used as part of the control information, but it can be used in * messages, or as a label in the user-interface. * * \param keyname * Provides the name of the keystroke that triggers this control. * This name is one of the values in the keymap.cpp/hpp module's * static qt_keycodes array, sq_qt_keys[]. This name is not part of the * control information, but it does allow for conversion to an ordinal * value using the keymap function qt_keyname_ordinal(). * * \param opcategory * Indicates if this keystroke is meant for pattern control, mute-group * (mutes) control, or general automation control. Like the \a opname * and the \a keyname parameters, this one is used for informational * purposes. * * \param actioncode * One of the values of keycontrol::action::none, * keycontrol::action::toggle, keycontrol::action::on, or * keycontrol::action::off. The "none" action generally won't be used. * Instead, we just don't create the keycontrol. * * \param opslot * Provides the slot number of the control, which determines which slot * function (see the automation::slot enumeration) is called. * * \param index * Provides an index for use by the keystroke control. For pattern * control, this number is the pattern offset into the active screen-set * (e.g. 0 to 31). For mute-group control, this number is the mute-group * to toggle (e.g. 0 to 31). For automation control, the specific slot * function determines if and how this parameter is used. */ keycontrol::keycontrol ( const std::string & opname, const std::string & keyname, automation::category opcategory, automation::action actioncode, automation::slot opslot, int index ) : opcontrol (opname, opcategory, actioncode, opslot, index), m_key_name (keyname), m_control_code (index), m_ordinal (qt_keyname_ordinal(keyname)) { if (is_invalid_ordinal(m_ordinal)) { m_key_name = scm_dead_key_name; } } /** * Man, this would be a lot easier with printf()! */ void keycontrol::show (bool add_newline) const { using namespace std; cout << setw(7) << left << key_name() << " " << setw(4) << left << category_name() << " " << setw(6) << left << action_name() << " " << setw(2) << dec << right << int(slot_number()) << "/" << setw(2) << dec << right << int(control_code()) << " '" << name() << "'" ; if (add_newline) cout << endl; } } // namespace seq66 /* * keycontrol.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/keymap.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file keymap.cpp * * This module defines some informative functions that are actually * better off as functions. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-12 * \updates 2025-09-17 * \license GNU GPLv2 or above */ #include /* std::map and std::multimap */ #include "ctrl/keymap.hpp" /* keymap function declarations */ #include "util/strfunctions.hpp" /* contains() */ namespace seq66 { std::string modifier_names (unsigned kmod) { std::string result; if (kmod == keyboard::KNONE) { result = "None "; } else { if (kmod & keyboard::KSHIFT) result += "Shift "; if (kmod & keyboard::KCTRL) result += "Ctrl "; if (kmod & keyboard::KALT) result += "Alt "; if (kmod & keyboard::KMETA) result += "Meta "; if (kmod & keyboard::KEYPAD) result += "Keypad "; if (kmod & keyboard::KGROUP) result += "Group "; } return result; } unsigned modifier_code (const std::string & name) { unsigned result = keyboard::KNONE; if (contains(name, "Shift")) result |= keyboard::KSHIFT; if (contains(name, "Ctrl")) result |= keyboard::KCTRL; if (contains(name, "Alt")) result |= keyboard::KALT; if (contains(name, "Alt-Gr")) result |= keyboard::KCTRLALT; if (contains(name, "Keypad")) result |= keyboard::KEYPAD; if (contains(name, "Meta")) result |= keyboard::KMETA; if (contains(name, "Group")) result |= keyboard::KGROUP; return result; } ctrlkey arrow_left () { return 0x92; } ctrlkey arrow_up () { return 0x93; } ctrlkey arrow_right () { return 0x94; } ctrlkey arrow_down () { return 0x95; } ctrlkey menu_key () { return 0xae; } /** * Indicates the start of section where expansion/foreign characters can be * placed. Covers the ordinals from 0xe0 to 0xfe (31 characters). * Not yet used, as revealed by clang-12. * * static const int s_expansion_start = 0xe0; */ /** * Provides a data type for key name/value pairs. */ struct qt_keycodes { /** * Provides an 8-bit ordinal number representing our universal code for * that character. Up to almost 255 characters can be supported. The * incoming keystroke is analyzed and converted to this number. Many of * the values are ASCII values. * * These ordinals are key-values in keycontainer::keymap for fast lookup * of the keys the application actually loaded from the 'ctrl' file at * startup. */ ctrlkey qtk_ordinal; /** * Provides the incoming key value presented to the application by Qt, as * obtained by the QKeyEvent :: key() function. It is important to note * that this is NOT the ASCII value of the character. */ eventkey qtk_keyevent; /** * Provides the keycode for the key, as obtained by the QKeyEvent :: * nativeVirtualKey() function. However, see this link: * * https://forum.qt.io/topic/95527/ * dealing-with-keyboard-layouts-for-input-on-multiple-platforms * * "Each platform has a different set of virtual key codes. For * Windows, the list can be found here. For mac, it's in Events.h. As * a simple comparison to demonstrate that they are different, the * virtual key code for the letter key O on Windows is 0x4F, while on * mac the same key's code is 0x1F. Not to mention, I couldn't even * find any relevant info for Linux. * * This makes it impossible to set good default settings that would * work on all platforms, forcing me to create separate defaults for * each platform (unlike key())." * * But key() is layout-dependent! */ eventkey qtk_virtkey; /** * Provides our name for the keystroke. These values are deliberately * shortened to save space in the "rc" file. */ std::string qtk_keyname; /** * Provides the incoming modifier bit(s) presented to the application by * Qt, obtained by the QKeyEvent::modifiers() function. This is the * integer version of the enum class qt_kbd_modifier defined above. */ /* unsigned */ keyboard::modifiers qtk_modifier; }; /** * Maps key names to the key integers. These come mostly from * /usr/include/x86_64-linux-gnu/qt5/QtCore/qnamespace.h, with the "Key_" * prefix removed, or the name radically shortened. * * Does not include keypad keys directly, one must include a test for the * KeypadModifier (0x20) bit in the KeyboardModifier enumeration. Similarly, * the ShiftModifier (0x02), ControlModifier (0x04), AltModifier (0x08), and * MetaModifier (0x10) bits need to be checked. They are accessed via * QKeyEvent::modifiers(). * * - Code: qtk_ordinal [legal range is 0x01 to 0xfe] * - Qt Key Event: qtk_keyevent [QKeyEvent::key(), unsigned] * - Qt Virtual Key: qtk_virtkey [QKeyEvent::nativeVirtualKey(), unsigned] * - Key Name: qtk_keyname * - Modifier: qtk_modifiers [QKeyEvent::modifiers(), unsigned] * * By using the modifier, we can distinguish between various version of the * same key, such as the normal PageDn and the keypad's PageDn. * * A number of these keys (e.g. the keypad keys) are defined in Seq64's * gdk_basic_keys.h file, which we do not use here. * * Also note that we restrict the keystroke names to 9 characters or less, to * keep the MIDI control section readable. * * Also note that, currently, "Quit" is a fake key, a placeholder for * the new MIDI control for quitting the application (especially when running * headless. * * For alternate keyboards such as AZERTY, we have many characters in the * Extened ASCII (code >= 0x80) set, besides the usual items (e.g. keypad * characters) that Qt provides. And, because the exact character can, as * far as we know, depend on the code page, we cannot use the extended * character itself as the name of the character. Instead, we use the hex * code (e.g. "0xe9") as the name of the character. This affects the display * of pattern and mute group keys as obtained by keycontainer::slot_key() and * mute_key(). * * This array can be modified at run-time. See setup_qt_azerty_fr_keys(), * for example. * * Finally, we started encountering uninitialized static variables. A g++ * bug, or bug fix? So now we enforce initialization by wrapping static * values in accessor functions. */ #if defined SEQ66_PLATFORM_WINDOWS #include "winkeys.hpp" /* provisional Windows key mapping */ #else static qt_keycodes & qt_keys (int i) { using namespace keyboard; static qt_keycodes s_qt_keys [] = /* modifiable inside this module */ { /* * Code Qt Qt Key * Ordinal Key Event Virt Key Name Modifier * * Ctrl-key section. It is best to avoid using Control keys to control * loops, mutes, and automation. Too much chance of interfering with * the normal user interface. */ { 0x00, 0x40, 0x60, "NUL", KCTRL }, // ^@: Null { 0x01, 0x41, 0x61, "SOH", KCTRL }, // ^A: Start of Heading { 0x02, 0x42, 0x62, "STX", KCTRL }, // ^B: Start of Text { 0x03, 0x43, 0x63, "ETX", KCTRL }, // ^C: End of Text { 0x04, 0x44, 0x64, "EOT", KCTRL }, // ^D: End of Transmision { 0x05, 0x45, 0x65, "ENQ", KCTRL }, // ^E: Enquiry { 0x06, 0x46, 0x66, "ACK", KCTRL }, // ^F: Acknowledge { 0x07, 0x47, 0x67, "BEL", KCTRL }, // ^G: Bell/beep { 0x08, 0x48, 0x68, "BS", KCTRL }, // ^H: Backspace { 0x09, 0x49, 0x69, "HT", KCTRL }, // ^I: Horizontal Tab { 0x0a, 0x4a, 0x6a, "LF", KCTRL }, // ^J: Line Feed { 0x0b, 0x4b, 0x6b, "VT", KCTRL }, // ^K: Vertical Tab { 0x0c, 0x4c, 0x6c, "FF", KCTRL }, // ^L: Form Feed { 0x0d, 0x4d, 0x6d, "CR", KCTRL }, // ^M: Carriage Return { 0x0e, 0x4e, 0x6e, "SO", KCTRL }, // ^N: Shift Out { 0x0f, 0x4f, 0x6f, "SI", KCTRL }, // ^O: Shift In { 0x10, 0x50, 0x70, "DLE", KCTRL }, // ^P: Data Link Escape { 0x11, 0x51, 0x71, "DC1", KCTRL }, // ^Q: Device Control 1 { 0x12, 0x52, 0x72, "DC2", KCTRL }, // ^R: Device Control 2 { 0x13, 0x53, 0x73, "DC3", KCTRL }, // ^S: Device Control 3 { 0x14, 0x54, 0x74, "DC4", KCTRL }, // ^T: Device Control 4 { 0x15, 0x55, 0x75, "NAK", KCTRL }, // ^U: Negative ACK { 0x16, 0x56, 0x76, "SYN", KCTRL }, // ^V: Synchronous Idle { 0x17, 0x57, 0x77, "ETB", KCTRL }, // ^W: End of Trans Block { 0x18, 0x58, 0x78, "CAN", KCTRL }, // ^X: Cancel { 0x19, 0x59, 0x79, "EM", KCTRL }, // ^Y: End of Medium { 0x1a, 0x5a, 0x7a, "SUB", KCTRL }, // ^Z: Substitute { 0x1b, 0x5b, 0x7b, "ESC", KCTRL }, // ^[: Escape { 0x1c, 0x5c, 0x7c, "FS", KCTRL }, // ^\: File Separator { 0x1d, 0x5d, 0x7d, "GS", KCTRL }, // ^]: Group Separator { 0x1e, 0x5e, 0x7e, "RS", KCTRLSHIFT }, // ^^: Record Separator { 0x1f, 0x5f, 0x7f, "US", KCTRLSHIFT }, // ^_???: Unit Separator /* * Ordinal Key Event Virt Key Name Modifier */ { 0x20, 0x20, 0x20, "Space", KNONE }, // Space (" " not good) { 0x21, 0x21, 0x21, "!", KSHIFT }, // Exclam { 0x22, 0x22, 0x22, "\"", KSHIFT }, // QuoteDbl { 0x23, 0x23, 0x23, "#", KSHIFT }, // NumberSign { 0x24, 0x24, 0x24, "$", KSHIFT }, // Dollar { 0x25, 0x25, 0x25, "%", KSHIFT }, // Percent { 0x26, 0x26, 0x26, "&", KSHIFT }, // Ampersand { 0x27, 0x27, 0x27, "'", KSHIFT }, // Apostrophe { 0x28, 0x28, 0x28, "(", KSHIFT }, // ParenLeft { 0x29, 0x29, 0x29, ")", KSHIFT }, // ParenRight { 0x2a, 0x2a, 0x2a, "*", KSHIFT }, // Asterisk { 0x2b, 0x2b, 0x2b, "+", KSHIFT }, // Plus { 0x2c, 0x2c, 0x2c, ",", KNONE }, // Comma { 0x2d, 0x2d, 0x2d, "-", KNONE }, // Minus { 0x2e, 0x2e, 0x2e, ".", KNONE }, // Period { 0x2f, 0x2f, 0x2f, "/", KNONE }, // Slash { 0x30, 0x30, 0x30, "0", KNONE }, { 0x31, 0x31, 0x31, "1", KNONE }, { 0x32, 0x32, 0x32, "2", KNONE }, { 0x33, 0x33, 0x33, "3", KNONE }, { 0x34, 0x34, 0x34, "4", KNONE }, { 0x35, 0x35, 0x35, "5", KNONE }, { 0x36, 0x36, 0x36, "6", KNONE }, { 0x37, 0x37, 0x37, "7", KNONE }, { 0x38, 0x38, 0x38, "8", KNONE }, { 0x39, 0x39, 0x39, "9", KNONE }, { 0x3a, 0x3a, 0x3a, ":", KSHIFT }, { 0x3b, 0x3b, 0x3b, ";", KNONE }, { 0x3c, 0x3c, 0x3c, "<", KSHIFT }, { 0x3d, 0x3d, 0x3d, "=", KNONE }, { 0x3e, 0x3e, 0x3e, ">", KSHIFT }, { 0x3f, 0x3f, 0x3f, "?", KSHIFT }, { 0x40, 0x40, 0x40, "@", KSHIFT }, { 0x41, 0x41, 0x41, "A", KSHIFT }, // Shift key modifier { 0x42, 0x42, 0x42, "B", KSHIFT }, { 0x43, 0x43, 0x43, "C", KSHIFT }, { 0x44, 0x44, 0x44, "D", KSHIFT }, { 0x45, 0x45, 0x45, "E", KSHIFT }, { 0x46, 0x46, 0x46, "F", KSHIFT }, { 0x47, 0x47, 0x47, "G", KSHIFT }, { 0x48, 0x48, 0x48, "H", KSHIFT }, { 0x49, 0x49, 0x49, "I", KSHIFT }, { 0x4a, 0x4a, 0x4a, "J", KSHIFT }, { 0x4b, 0x4b, 0x4b, "K", KSHIFT }, { 0x4c, 0x4c, 0x4c, "L", KSHIFT }, { 0x4d, 0x4d, 0x4d, "M", KSHIFT }, { 0x4e, 0x4e, 0x4e, "N", KSHIFT }, { 0x4f, 0x4f, 0x4f, "O", KSHIFT }, { 0x50, 0x50, 0x50, "P", KSHIFT }, { 0x51, 0x51, 0x51, "Q", KSHIFT }, { 0x52, 0x52, 0x52, "R", KSHIFT }, { 0x53, 0x53, 0x53, "S", KSHIFT }, { 0x54, 0x54, 0x54, "T", KSHIFT }, { 0x55, 0x55, 0x55, "U", KSHIFT }, { 0x56, 0x56, 0x56, "V", KSHIFT }, { 0x57, 0x57, 0x57, "W", KSHIFT }, { 0x58, 0x58, 0x58, "X", KSHIFT }, { 0x59, 0x59, 0x59, "Y", KSHIFT }, { 0x5a, 0x5a, 0x5a, "Z", KSHIFT }, { 0x5b, 0x5b, 0x5b, "[", KNONE }, // BracketLeft { 0x5c, 0x5c, 0x5c, "\\", KNONE }, // Backslash { 0x5d, 0x5d, 0x5d, "]", KNONE }, // BracketRight { 0x5e, 0x5e, 0x5e, "^", KSHIFT }, // AsciiCircumflex { 0x5f, 0x5f, 0x5f, "_", KSHIFT }, // Underscore { 0x60, 0x60, 0x60, "`", KNONE }, // QuoteLeft, Backtick { 0x61, 0x41, 0x61, "a", KNONE }, { 0x62, 0x42, 0x62, "b", KNONE }, { 0x63, 0x43, 0x63, "c", KNONE }, { 0x64, 0x44, 0x64, "d", KNONE }, { 0x65, 0x45, 0x65, "e", KNONE }, { 0x66, 0x46, 0x66, "f", KNONE }, { 0x67, 0x47, 0x67, "g", KNONE }, { 0x68, 0x48, 0x68, "h", KNONE }, { 0x69, 0x49, 0x69, "i", KNONE }, { 0x6a, 0x4a, 0x6a, "j", KNONE }, { 0x6b, 0x4b, 0x6b, "k", KNONE }, { 0x6c, 0x4c, 0x6c, "l", KNONE }, { 0x6d, 0x4d, 0x6d, "m", KNONE }, { 0x6e, 0x4e, 0x6e, "n", KNONE }, { 0x6f, 0x4f, 0x6f, "o", KNONE }, { 0x70, 0x50, 0x50, "p", KNONE }, { 0x71, 0x51, 0x71, "q", KNONE }, { 0x72, 0x52, 0x72, "r", KNONE }, { 0x73, 0x53, 0x73, "s", KNONE }, { 0x74, 0x54, 0x74, "t", KNONE }, { 0x75, 0x55, 0x75, "u", KNONE }, { 0x76, 0x56, 0x76, "v", KNONE }, { 0x77, 0x57, 0x77, "w", KNONE }, { 0x78, 0x58, 0x78, "x", KNONE }, { 0x79, 0x59, 0x79, "y", KNONE }, { 0x7a, 0x5a, 0x7a, "z", KNONE }, { 0x7b, 0x7b, 0x7b, "{", KSHIFT }, // BraceLeft { 0x7c, 0x7c, 0x7c, "|", KSHIFT }, // Bar { 0x7d, 0x7d, 0x7d, "}", KSHIFT }, // BraceRight { 0x7e, 0x7e, 0x7e, "~", KSHIFT }, // AsciiTilde { 0x7f, 0x7f, 0x7f, "DEL", KNONE }, /* * Block moved from above to here. * * Ordinal Key Event Virt Key Name Modifier */ { 0x80, 0x01000000, 0xff1b, "Esc", KNONE }, { 0x81, 0x01000001, 0xff09, "Tab", KNONE }, // avoid, moves focus { 0x82, 0x01000002, 0xff09, "BkTab", KSHIFT }, // avoid, moves focus { 0x83, 0x01000003, 0xff08, "BkSpace", KNONE }, // differs from Ctrl-H ! { 0x84, 0x01000004, 0xff0d, "Return", KNONE }, { 0x85, 0x01000005, 0xff8d, "Enter", KEYPAD }, // Keypad-Enter { 0x86, 0x01000006, 0xff63, "Ins", KNONE }, { 0x87, 0x01000007, 0xffff, "Del", KNONE }, { 0x88, 0x88, 0x88, "0x88", KNONE }, // was "Pause", duplicate { 0x89, 0x89, 0x89, "0x89", KNONE }, // was "Print", duplicate { 0x8a, 0x0100000a, 0x8a, "SysReq", KNONE }, { 0x8b, 0x0100000b, 0x8b, "Clear", KNONE }, { 0x8c, 0x0100000c, 0x8c, "0x8c", KNONE }, { 0x8d, 0x0100000d, 0x8d, "0x8d", KNONE }, { 0x8e, 0x0100000e, 0x8e, "0x8e", KNONE }, { 0x8f, 0x0100000f, 0x8f, "0x8f", KNONE }, { 0x90, 0x01000010, 0xff50, "Home", KNONE }, { 0x91, 0x01000011, 0xff57, "End", KNONE }, { 0x92, 0x01000012, 0xff51, "Left", KNONE }, { 0x93, 0x01000013, 0xff52, "Up", KNONE }, { 0x94, 0x01000014, 0xff53, "Right", KNONE }, { 0x95, 0x01000015, 0xff54, "Down", KNONE }, { 0x96, 0x01000016, 0xff55, "PageUp", KNONE }, { 0x97, 0x01000017, 0xff56, "PageDn", KNONE }, /* * See starting around 0xd7 for the Right versions of these keys. * * Ordinal Key Event Virt Key Name Modifier */ { 0x98, 0x01000020, 0xffe1, "Shift_L", KSHIFT }, // Left-Shift { 0x99, 0x01000021, 0xffe3, "Ctrl_L", KCTRL }, // Left-Ctrl { 0x9a, 0x01000022, 0x9a, "Meta", KMETA }, { 0x9b, 0x01000023, 0xffe9, "Alt_L", KALT }, // Left-Alt { 0x9c, 0x01000024, 0xffe5, "CapsLk", KNONE }, // Shift-Lock too??? { 0x9d, 0x01000025, 0xff7f, "NumLk", KNONE }, { 0x9e, 0x01000026, 0xff14, "ScrlLk", KNONE }, // Good? { 0x9f, 0x01000027, 0x9f, "0x9f", KNONE }, { 0xa0, 0x01000030, 0xffbe, "F1", KNONE }, { 0xa1, 0x01000031, 0xffbf, "F2", KNONE }, { 0xa2, 0x01000032, 0xffc0, "F3", KNONE }, { 0xa3, 0x01000033, 0xffc1, "F4", KNONE }, { 0xa4, 0x01000034, 0xffc2, "F5", KNONE }, { 0xa5, 0x01000035, 0xffc3, "F6", KNONE }, { 0xa6, 0x01000036, 0xffc4, "F7", KNONE }, { 0xa7, 0x01000037, 0xffc5, "F8", KNONE }, { 0xa8, 0x01000038, 0xffc6, "F9", KNONE }, { 0xa9, 0x01000039, 0xffc7, "F10", KNONE }, { 0xaa, 0x0100003a, 0xffc8, "F11", KNONE }, { 0xab, 0x0100003b, 0xffc9, "F12", KNONE }, { 0xac, 0x01000053, 0xffeb, "Super_L", KNONE }, // Left-Windows { 0xad, 0x01000054, 0xffec, "Super_R", KNONE }, // Right-Windows { 0xae, 0x01000055, 0xff67, "Menu", KNONE }, // Win-Menu key { 0xaf, 0x01000056, 0xaf, "Hyper_L", KNONE }, { 0xb0, 0x01000057, 0xb0, "Hyper_R", KNONE }, { 0xb1, 0x01000058, 0xb1, "Help", KNONE }, { 0xb2, 0x01000059, 0xb2, "Dir_L", KNONE }, { 0xb3, 0x01000060, 0xb3, "Dir_R", KNONE }, // Direction_R { 0xb4, 0x01000030, 0xffbe, "Sh_F1", KSHIFT }, { 0xb5, 0x01000031, 0xffbf, "Sh_F2", KSHIFT }, { 0xb6, 0x01000032, 0xffc0, "Sh_F3", KSHIFT }, { 0xb7, 0x01000033, 0xffc1, "Sh_F4", KSHIFT }, { 0xb8, 0x01000034, 0xffc2, "Sh_F5", KSHIFT }, { 0xb9, 0x01000035, 0xffc3, "Sh_F6", KSHIFT }, { 0xba, 0x01000036, 0xffc4, "Sh_F7", KSHIFT }, { 0xbb, 0x01000037, 0xffc5, "Sh_F8", KSHIFT }, { 0xbc, 0x01000038, 0xffc6, "Sh_F9", KSHIFT }, { 0xbd, 0x01000039, 0xffc7, "Sh_F10", KSHIFT }, { 0xbe, 0x0100003a, 0xffc8, "Sh_F11", KSHIFT }, { 0xbf, 0x0100003b, 0xffc9, "Sh_F12", KSHIFT }, /* * Keys missing: KP_0 to KP_9, accessible with NumLock on. * { 0x30, 0x30, 0xffb0, "KP_0", KEYPAD }, { 0x31, 0x31, 0xffb1, "KP_0", KEYPAD }, . . . . . . . . . { 0x39, 0x39, 0xffb9, "KP_0", KEYPAD }, * */ { 0xc0, 0x01000006, 0xff9e, "KP_Ins", KEYPAD }, { 0xc1, 0x01000007, 0xff9f, "KP_Del", KEYPAD }, { 0xc2, 0x01000008, 0xffe1, "Pause", KSHIFT }, { 0xc3, 0x01000009, 0xff61, "Print", KSHIFT }, { 0xc4, 0x01000010, 0xff95, "KP_Home", KEYPAD }, { 0xc5, 0x01000011, 0xff9c, "KP_End", KEYPAD }, { 0xc6, 0x01000012, 0xff96, "KP_Left", KEYPAD }, { 0xc7, 0x01000013, 0xff97, "KP_Up", KEYPAD }, { 0xc8, 0x01000014, 0xff98, "KP_Right", KEYPAD }, { 0xc9, 0x01000015, 0xff99, "KP_Down", KEYPAD }, { 0xca, 0x01000016, 0xff9a, "KP_PageUp", KEYPAD }, { 0xcb, 0x01000017, 0xff9b, "KP_PageDn", KEYPAD }, { 0xcc, 0x01000099, 0xff9d, "KP_Begin", KNONE }, // KP_Begin { 0xcd, 0x01000099, 0xcd, "0xcd", KNONE }, { 0xce, 0x01000099, 0xce, "0xce", KNONE }, { 0xcf, 0x01000099, 0xcf, "0xcf", KNONE }, { 0xd0, 0x2a, 0xffaa, "KP_*", KEYPAD }, // Asterisk, KP_Multiply { 0xd1, 0x2b, 0xffab, "KP_+", KEYPAD }, // Plus, KP_Add { 0xd2, 0x2c, 0xffac, "KP_,", KEYPAD }, // Comma, KP_Separator { 0xd3, 0x2d, 0xffad, "KP_-", KEYPAD }, // Minus, KP_Subtract { 0xd4, 0x2e, 0xffae, "KP_.", KPADSHIFT }, // Period, KP_Decimal { 0xd5, 0x2f, 0xffaf, "KP_/", KEYPAD }, // Slash, KP_Divide /* * Remainders. Provides the Right version and key-release versions * of some keys. Keys not yet covered: * * Alt and Alt_R releases. */ { 0xd6, 0x01000099, 0xd6, "0xd6", KNONE }, // available { 0xd7, 0x01000020, 0xffe2, "Shift_R", KSHIFT }, // Right-Shift { 0xd8, 0x01000021, 0xffe4, "Ctrl_R", KCTRL }, // Right-Ctrl { 0xd9, 0x2e, 0xffae, "KP_.", KEYPAD }, // KP_Decimal release { 0xda, 0x01000023, 0xffea, "Alt_R", KGROUP }, // Right-Alt { 0xdb, 0x01000020, 0xffe1, "Shift_Lr", KNONE }, // L-Shift release { 0xdc, 0x01000020, 0xffe2, "Shift_Rr", KNONE }, // R-Shift release { 0xdd, 0x01000021, 0xffe3, "Ctrl_Lr", KNONE }, // L-Ctrl release { 0xde, 0x01000021, 0xffe4, "Ctrl_Rr", KNONE }, // R-Ctrl release { 0xdf, 0x01000099, 0xdf, "Quit", KNONE }, // fake key, MIDI control only /* * This section is currently useful to fill in for future expansion or * for extended ASCII characters. See setup_qt_azerty_fr_keys(). */ { 0xe0, 0x01000099, 0xe0, "0xe0", KNONE }, { 0xe1, 0x01000099, 0xe1, "0xe1", KNONE }, { 0xe2, 0x01000099, 0xe2, "0xe2", KNONE }, { 0xe3, 0x01000099, 0xe3, "0xe3", KNONE }, { 0xe4, 0x01000099, 0xe4, "0xe4", KNONE }, { 0xe5, 0x01000099, 0xe5, "0xe5", KNONE }, { 0xe6, 0x01000099, 0xe6, "0xe6", KNONE }, { 0xe7, 0x01000099, 0xe7, "0xe7", KNONE }, { 0xe8, 0x01000099, 0xe8, "0xe8", KNONE }, { 0xe9, 0x01000099, 0xe9, "0xe9", KNONE }, { 0xea, 0x01000099, 0xea, "0xea", KNONE }, { 0xeb, 0x01000099, 0xeb, "0xeb", KNONE }, { 0xec, 0x01000099, 0xec, "0xec", KNONE }, { 0xed, 0x01000099, 0xed, "0xed", KNONE }, { 0xee, 0x01000099, 0xee, "0xee", KNONE }, { 0xef, 0x01000099, 0xef, "0xef", KNONE }, { 0xf0, 0x01000099, 0xf0, "0xf0", KNONE }, { 0xf1, 0x01000099, 0xf1, "0xf1", KNONE }, { 0xf2, 0x01000099, 0xf2, "0xf2", KNONE }, { 0xf3, 0x01000099, 0xf3, "0xf3", KNONE }, { 0xf4, 0x01000099, 0xf4, "0xf4", KNONE }, { 0xf5, 0x01000099, 0xf5, "0xf5", KNONE }, { 0xf6, 0x01000099, 0xf6, "0xf6", KNONE }, { 0xf7, 0x01000099, 0xf7, "0xf7", KNONE }, { 0xf8, 0x01000099, 0xf8, "0xf8", KNONE }, { 0xf9, 0x01000099, 0xf9, "0xf9", KNONE }, { 0xfa, 0x01000099, 0xfa, "0xfa", KNONE }, { 0xfb, 0x01000099, 0xfb, "0xfb", KNONE }, { 0xfc, 0x01000099, 0xfc, "0xfc", KNONE }, { 0xfd, 0x01000099, 0xfd, "0xfd", KNONE }, { 0xfe, 0x01000099, 0xfe, "0xfe", KNONE }, { 0xff, 0xffffffff, 0xff, "Null_ff", KNONE } // end-of-list }; if (i < 0 || i > 0xff) i = 0; return s_qt_keys[i]; } /** * Extended keys, running on system locale with French (fr) AZERTY keymap. * The first number is the value returned by QKeyEvent::key(). The second * column is the name returned by QKeyEvent::text(). The scan code is * returned by QKeyEvent::nativeScanCode(), but is not used in this table. * The keycode is returned by QKeyEvent::nativeVirtualKey(), and is used to * look up the s_qt_keys[] slot that will be replaced by the information * below. * * Note that these code are what we found on Debian Linux after running * "setxkbmap fr". All of the standard ASCII symbols are unchanged with this * mapping, even though their locations on the French keyboard are different, * and numeric-key shifting is reversed compared to the US key-map. * * Also note that we cannot place the characters themselves in a 'ctrl' file. * When read, these characters have the value 0xffffffc2 or 0xffffffc3. So * we have to make up names to use in the 'ctrl' file. * * Sylvain's findings on a real AZERTY keyboard: * \verbatim Key #0xa3 '£' scan = 0x23; keycode = 0xa3 Key #0xa4 '¤' scan = 0x23; keycode = 0xa4 Key #0xa7 '§' scan = 0x3d; keycode = 0xa7 Key #0xb0 '°' scan = 0x14; keycode = 0xb0 Key #0xb2 '²' scan = 0x31; keycode = 0xb2 Key #0xc0 'à' scan = 0x13; keycode = 0xe0 Key #0xc7 'ç' scan = 0x12; keycode = 0xe7 Key #0xc8 'è' scan = 0x10; keycode = 0xe8 Key #0xc9 'é' scan = 0x0b; keycode = 0xe9 Key #0xd9 'ù' scan = 0x30; keycode = 0xf9 Key #0x039c 'µ' scan = 0x33; keycode = 0xb5 Key #0x20ac '€' scan = 0x1a; keycode = 0x20ac Key #0x1001252 '^' scan = 0x22; keycode = 0xfe52 Key #0x1001257 '¨' scan = 0x22; keycode = 0xfe57 \endverbatim * * Just call this function once. */ static void setup_qt_azerty_fr_keys () { using namespace keyboard; static const qt_keycodes s_fr_keys [] = { /* * Code Qt Qt Key * Ordinal Evkey Virtkey Name Modifier */ { 0x21, 0x21, 0x21, "!", KNONE }, // Exclam { 0x22, 0x22, 0x22, "\"", KNONE }, // QuoteDbl { 0x23, 0x23, 0x23, "#", KALTGR }, // NumberSign { 0x26, 0x26, 0x26, "&", KNONE }, // Ampersand { 0x27, 0x27, 0x27, "'", KNONE }, // Apostrophe { 0x28, 0x28, 0x28, "(", KNONE }, // ParenLeft { 0x29, 0x29, 0x29, ")", KNONE }, // ParenRight { 0x2a, 0x2a, 0x2a, "*", KNONE }, // Asterisk { 0x2e, 0x2e, 0x2e, ".", KSHIFT }, // Period { 0x2f, 0x2f, 0x2f, "/", KSHIFT }, // Slash { 0x3a, 0x3a, 0x3a, ":", KNONE }, // Colon { 0x3c, 0x3c, 0x3c, "<", KNONE }, { 0x40, 0x40, 0x40, "@", KALTGR }, // AtSign { 0x5b, 0x5b, 0x5b, "[", KALTGR }, // BracketLeft { 0x5c, 0x5c, 0x5c, "\\", KALTGR }, // Backslash { 0x5d, 0x5d, 0x5d, "]", KALTGR }, // BracketRight { 0x5e, 0x5e, 0x5e, "^", KALTGR }, // AsciiCircumflex { 0x5f, 0x5f, 0x5f, "_", KNONE }, // Underscore { 0x60, 0x60, 0x60, "`", KALTGR }, // QuoteLeft, Backtick { 0x7b, 0x7b, 0x7b, "{", KALTGR }, // BraceLeft { 0x7c, 0x7c, 0x7c, "|", KALTGR }, // Bar { 0x7d, 0x7d, 0x7d, "}", KALTGR }, // BraceRight { 0x7e, 0x7e, 0x7e, "~", KALTGR }, // Tilde (dead key) { 0xe0, 0xa3, 0xa3, "L_pound", KNONE }, // £ <--F4 { 0xe1, 0xa4, 0xa4, "Currency", KALTGR }, // ¤ <--F5 { 0xe2, 0xa7, 0xa7, "Silcrow", KSHIFT }, // § <--F8 { 0xe3, 0xb0, 0xb0, "Degrees", KSHIFT }, // ° <--Hyper_R { 0xe4, 0x01000022, 0xffec, "Super_2", KMETA }, // ² <--Dir_L press { 0xe5, 0xc0, 0xe0, "a_grave", KNONE }, // à <--KP_Ins { 0xe6, 0xc7, 0xe7, "c_cedilla", KNONE }, // ç <--KP_Up { 0xe7, 0xc8, 0xe8, "e_grave", KNONE }, // è <--KP_Right { 0xe8, 0xc9, 0xe9, "e_acute", KNONE }, // é <--KP_Down { 0xe9, 0xd9, 0xf9, "u_grave", KNONE }, // ù <--Super/Mod4/Win { 0xea, 0x039c, 0xb5, "Mu", KSHIFT }, // µ <--(new) { 0xeb, 0x20ac, 0xb6, "Euro", KALTGR }, // € <--(new) { 0xec, 0x1001252, 0xfe52, "Circflex", KNONE }, // ^ <--Caret { 0xed, 0x1001257, 0xfe57, "Umlaut", KSHIFT }, // ¨ <--Diaeresis { 0xee, 0x01000022, 0xffec, "Super_2r", KNONE }, // ² <--Dir_L release { 0x00, 0xffffffff, 0xff, "??", KNONE } // terminator }; for (int i = 0; s_fr_keys[i].qtk_keyevent != 0xffffffff; ++i) { int index = s_fr_keys[i].qtk_ordinal; // not qtk_keyevent #if defined SEQ66_PLATFORM_DEBUG_TMI printf ( "Key #%03d '%s': Ord %02x; code %02x-->", i, qt_keys(index).qtk_keyname.c_str(), qt_keys(index).qtk_ordinal, qt_keys(index).qtk_keyevent ); #endif qt_keys(index) = s_fr_keys[i]; #if defined SEQ66_PLATFORM_DEBUG_TMI printf ( "Key #%03d '%s': Ord %02x; code %02x\n", i, qt_keys(index).qtk_keyname.c_str(), qt_keys(index).qtk_ordinal, qt_keys(index).qtk_keyevent ); #endif } } #endif // defined SEQ66_PLATFORM_WINDOWS /** * Returns the key-name for undefined keys, those not yet defined in the * key-map. Also used when out-of-range ordinals are encountered. */ const std::string & undefined_qt_key_name () { static const std::string s_undefined_key_name = "Null_ff"; return s_undefined_key_name; } /** * Checks if the given key-name matches the undefined key name. */ bool is_undefined_qt_key (const std::string & keyname) { return keyname == undefined_qt_key_name(); } /** * Indicates that a keystroke could not be found in the keymap. * It also is the last ordinal present in the keymap. */ ctrlkey invalid_ordinal () { return 0xff; } /** * Indicates that a keystroke could not be found in the keymap. */ bool is_invalid_ordinal (ctrlkey ordinal) { return ordinal == invalid_ordinal(); } /** * Provides a data type to convert from incoming Qt 5 key() values * into the corresponding internal key value. It is important to note that * more than one key can have the same value. Some are examples are the * keypad keys and their normal keys, or letters that can be typed on their * own, but also be paired with the Control or Shift modifiers. Thus we use * a multi-map to hold the data, to allow multiple entries with the same * event-key value. * * Each event-key value is paired with a structure that contains the ordinal, * virtual key, and modifier that completely define that key, plus the name * of the key. * * So, this map lets us look up an event-key, and, if there are more than * one, search a little further to find the one we want. */ using qt_keycode_map = std::multimap; /** * Provide a built-in key map. We could make all this stuff part of a class, * but there doesn't seem to be any benefit to that. */ static qt_keycode_map & keycode_map () { static qt_keycode_map s_keycode_map; return s_keycode_map; } /** * Another map to use to look up keystroke names from the 'ctrl' file. See * the function qt_keyname_ordinal() in this module. This lets us look up an * ordinal value (ctrlkey), ranging from 0x00 to 0xff-1, given the name of * the key. */ using qt_keyname_map = std::map; /** * Provide a built-in name map. */ static qt_keyname_map & keyname_map () { static qt_keyname_map s_keyname_map; return s_keyname_map; } /** * Returns the keymap's size. */ int keymap_size () { return static_cast(keycode_map().size()); } /** * Indicates if the key maps are set up. Also allows for re-initialization in * order to alter the key maps for alternate keyboard maps. */ static bool initialize_key_maps (bool reinit) { static bool s_are_maps_initialized = false; if (reinit) s_are_maps_initialized = false; if (! s_are_maps_initialized) { keycode_map().clear(); /* multimap of ctrlkey and key-code structs */ keyname_map().clear(); /* map of key-names and ctrlkey ordinals */ for (int k = 0x01; k < 0xff; ++k) { qt_keycodes temp = qt_keys(k); /* * auto --> std::pair */ auto pk = std::make_pair(temp.qtk_keyevent, temp); (void) keycode_map().insert(pk); /* * auto --> std::pair */ auto pn = std::make_pair(temp.qtk_keyname, temp.qtk_ordinal); (void) keyname_map().insert(pn); } s_are_maps_initialized = keymap_size() >= 0xfe; if (! s_are_maps_initialized) error_message("Key map unable to be initialized"); #if defined SEQ66_PLATFORM_DEBUG_TMI /* * std::multimap */ printf("====================== Key Code Map ====================\n"); int index = 0; for (const auto & kp : keycode_map()) { printf ( "Key[%3d] = ev key %8u { 0x%2x <-- code 0x%08x, " "'%9s', mod 0x%2x }\n", index, kp.first, kp.second.qtk_ordinal, kp.second.qtk_keyevent, kp.second.qtk_keyname.c_str(), kp.second.qtk_modifier ); ++index; } printf("keymap size = %d\n", keymap_size()); /* * = std::map */ printf("====================== Key Name Map ====================\n"); index = 0; for (const auto & kp : keyname_map()) { printf ( "Name[%3d] = %10s #%3u (0x%2x)\n", index, kp.first.c_str(), kp.second, kp.second ); ++index; } printf("keyname size = %d\n", int(keyname_map().size())); #endif } /* if (! s_are_maps_initialized) */ return s_are_maps_initialized; } /** * The inverse of qt_key_to_ordinal(). Slower, due to a brute force search. * * \param ordinal * The ordinal key-code to look up. The ordinals the application support * ranges from 0 to 255. * * \return * If the Gdk maps to a special character, this function returns the * corresponding Qt 5 key-code (0x1000000 and above) or, if a normal * character, the Qt 5 text-code (in the ASCII) range. */ unsigned ordinal_to_qt_key (ctrlkey ordinal) { unsigned result = 0; if (initialize_key_maps(false)) { for (const auto & qk : keycode_map()) { if (qk.second.qtk_ordinal == ordinal) { result = qk.second.qtk_keyevent; break; } } } return result; } /** * This function searches for a given keystroke, including the specified * modifier. This function lets us return different ordinals for variations * on the same key. For example, the normal PageUp key can be distinguished * from the PageUP key on the key-pad. * * This function does not use the "text()" result passed in by Qt; it has to * search for all key-codes. * * \param qtkey * The Qt 5 key-code, as provided to the Qt keyPressEvent() callback via * QKeyEvent::key(). This value does not distinguish between lower-case * and upper-case characters. However, the qtmodifier parameter can do * that, and more. * * \param qtmodifier * The KeyboardModifier code returned from the QKeyEvent::modifiers() * function. It can alter the value returned, for example distinguishing * normal keys from keypad keys. * * \param virtkey * This is the result of the QKeyEvent::nativeVirtualKey() function. * For ASCII keys it is the ASCII code. For extended keys it is values such * as 0xffnn or even other values. This parameter is used only if non-zero. * For now, the default value is 0. * * \return * If the combination is found in the key-map, then it's ordinal is * returned. If not found, then 0xff [invalid_ordinal()] is returned. * This would indicate an error of some kind. */ ctrlkey qt_modkey_ordinal (eventkey qtkey, unsigned qtmodifier, eventkey virtkey) { ctrlkey result = invalid_ordinal(); if (initialize_key_maps(false)) { auto cqi = keycode_map().find(qtkey); /* copy modified later! */ if (cqi != keycode_map().end()) { /* * std::multimap::size_type */ auto c = keycode_map().count(qtkey); bool found = c == 1; if (c > 1) { auto p = keycode_map().equal_range(qtkey); for (cqi = p.first; cqi != p.second; ++cqi) { found = cqi->second.qtk_modifier == qtmodifier; if (found && virtkey > 0) found = cqi->second.qtk_virtkey == virtkey; if (found) break; } } if (found) result = cqi->second.qtk_ordinal; } } #if defined SEQ66_PLATFORM_DEBUG_TMI std::string name = qt_ordinal_keyname(result); char temp[132]; (void) snprintf ( temp, sizeof temp, "qt_modkey_ordinal(0x%x, 0x%x, 0x%x) --> 0x%x '%s'", qtkey, qtmodifier, virtkey, result, name.c_str() ); printf("%s\n", temp); #endif return result; } /** * Gets the name of the key/text combination, either from the text() value or * via lookup in the key-code map. * * std::multimap::size_type * * \param qtkey * The Qt 5 key-code, as provided to the Qt keyPressEvent() callback via * QKeyEvent::key(). This value does not distinguish between lower-case * and upper-case characters. * * \param qtmodifier * The KeyboardModifier code returned from the QKeyEvent::modifiers() * function. It can alter the value returned, for example distinguishing * normal keys from keypad keys. * * \param virtkey * This is the result of the QKeyEvent::nativeVirtualKey() function. * For ASCII keys it is the ASCII code. For extended keys it is values such * as 0xffnn or even other values. This parameter is used only if non-zero. * For now, the default value is 0. * * \return * Returns the name of the key. */ std::string qt_modkey_name (eventkey qtkey, unsigned qtmodifier, eventkey virtkey) { ctrlkey ordinal = qt_modkey_ordinal(qtkey, qtmodifier, virtkey); return qt_ordinal_keyname(ordinal); } /** * Gets the ordinal from the given key name. It is the opposite of the * qt_ordinal_keyname() function. * * \param name * Provides the "official" name of the key. * * \return * If the key is found, its ordinal value (unsigned) is returned. * Otherwise, invalid_ordinal() is returned. Check for this value with * the is_invalid_ordinal() function. */ ctrlkey qt_keyname_ordinal (const std::string & name) { ctrlkey result = invalid_ordinal(); if (initialize_key_maps(false)) { const auto & cki = keyname_map().find(name); if (cki != keyname_map().end()) result = cki->second; } return result; } /** * Gets the key name from the given ordinal. It uses a brute force lookup, * but it is fast because all 255 ordinals have an entry in the qt_keycodes * array s_qt_keys[]. * * This function is useful mainly in error reporting. It is the opposite of * the qt_keyname_ordinal() function. */ std::string qt_ordinal_keyname (ctrlkey ordinal) { return is_invalid_ordinal(ordinal) ? "Missing_Key" : qt_keys(ordinal).qtk_keyname ; } /** * Tweaks the setup for some keyboard layouts that give direct access (i.e. * without using a "dead key") to extended ASCII keys. */ void modify_keyboard_layout (keyboard::layout el) { if (el == keyboard::layout::azerty) { setup_qt_azerty_fr_keys(); /* this call must come first */ (void) initialize_key_maps(true); /* reinitialize the key maps */ } } } // namespace seq66 /* * keymap.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/keystroke.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file keystroke.cpp * * This module declares/defines the base class for handling many facets * of using a GUI representation of keystrokes. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-09-30 * \updates 2021-05-05 * \license GNU GPLv2 or above * * This class makes access to keystroke features simpler. */ #include /* std::islower(), std::toupper() */ #include "ctrl/keymap.hpp" /* seq66::qt_modkey_name() */ #include "ctrl/keystroke.hpp" /* seq66::keystroke class */ namespace seq66 { /** * The principal constructor. * * \param key * The keystroke number of the key that was pressed or released. * * \param press * If true, the keystroke action was a press, otherwise it was a release. * * \param modkey * The modifier key combination that was pressed, if any, in the form of * a bit-mask, as defined in the keymap module; it follows Qt * conventions. If no modifier, this value is keyboard::KNONE = 0. */ keystroke::keystroke (ctrlkey key, bool press, unsigned modkey) : m_is_press (press), m_key (key), m_modifiers (static_cast(modkey)) { // Empty body } /** * \getter m_key to test letters, handles ASCII only. * * \param ch * An optional character to test as an ASCII letter. * * \return * If a character is not provided, true is returned if it is an upper * or lower-case letter. Otherwise, true is returned if the m_key * value matches the character case-insensitively. * \tricky */ bool keystroke::is_letter (ctrlkey ch) const { if (ch == sm_bad_value) return bool(std::isalpha(m_key)); else return std::tolower(m_key) == std::tolower(ch); } /** * Holds a pair of characters. These don't yet apply to A-Z, for speed. */ struct charpair_t { ctrlkey m_character; /**< The input character. */ ctrlkey m_shift; /**< The shift of input character. */ }; /** * The array of shifted mappings of the non-alphabetic characters. */ static struct charpair_t s_character_mapping [] = { { '0', ')' }, // no mapping { '1', '!' }, { '2', '@' }, // " { '3', '#' }, { '4', '$' }, { '5', '%' }, { '6', '&' }, { '7', '^' }, // Super-L { '8', '*' }, // ( { '9', '(' }, // no mapping { '-', '_' }, { '=', '+' }, { '[', '{' }, { ']', '}' }, { ';', ':' }, { '\'', '"' }, { ',', '<' }, { '.', '>' }, { '/', '?' }, { '\\', '|' }, { 0, 0 }, }; /** * If a lower-case letter, a number, or another character on the "main" part * of the keyboard, shift the m_key value to upper-case or the character * shifted on a standard American keyboard. Currently also assumes the ASCII * character set. * * There's an oddity here: the shift of '2' is the '@' character, but seq66 * seems to have treated it like the '"' character. Some others were treated * the same: * \verbatim Key: 1 2 3 4 5 6 7 8 9 0 Shift: ! @ # $ % ^ & * ( ) Seq24: ! " # $ % & ' ( ) space \endverbatim * * This function is meant to avoid using the Caps-Lock when picking a * group-learn character in the group-learn mode. */ ctrlkey keystroke::shifted () const { ctrlkey result = m_key; if (std::islower(m_key)) { result = ctrlkey(std::toupper(m_key)); } else { charpair_t * cp_ptr = &s_character_mapping[0]; while (cp_ptr->m_character != 0) { if (cp_ptr->m_character == m_key) { result = cp_ptr->m_shift; break; } ++cp_ptr; } } return result; } /** * If the character is lower-case, it is converted (internally) to * upper-case. Also see the shift_lock() function. */ void keystroke::toupper () { if (std::islower(m_key)) m_key = ctrlkey(std::toupper(m_key)); } /** * Returns the upper-case version of the character, if it is lower-case. */ ctrlkey keystroke::upper () const { return std::islower(m_key) ? ctrlkey(std::toupper(m_key)) : m_key ; } /** * If the character is upper-case, it is converted (internally) to * lower-case. */ void keystroke::tolower () { if (std::isupper(m_key)) m_key = ctrlkey(std::tolower(m_key)); } /** * Returns the lower-case version of the character, if it is upper-case. */ ctrlkey keystroke::lower () const { return std::isupper(m_key) ? ctrlkey(std::tolower(m_key)) : m_key ; } /** * Assembles and returns the human-readable name of the key. */ std::string keystroke::name () const { return qt_ordinal_keyname(m_key); } } // namespace seq66 /* * keystroke.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/midicontrol.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midicontrol.cpp * * This module declares/defines a class for extended and flexible MIDI * control, unified with keyboard control. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-09 * \updates 2021-11-27 * \license GNU GPLv2 or above * * The idea behind the MIDI control automation setup is that an incoming * event can be looked up in a midicontrolin object, based on its event * status byte, the first data byte, and a range for values for the second * data byte. * * The control also contains an automation slot number with indicates which * of roughly 50 functions is to be called. Two of these functions handle * pattern toggling and mute-groups, and use a third value to determine which * sequences in a set or mutes in a group are to be operated on. * * A MIDI control object can appear more than once in the container, to * affect, for example, multiple patterns. */ #include /* std::setw() manipulator */ #include /* std::cout (using namespace std) */ #include "ctrl/midicontrol.hpp" /* seq66::midicontrol class */ namespace seq66 { /** * This default constructor creates a "zero" object. Every member is * either false or some other form of zero. This object is useful as a * return value for container lookups that do not succeed. */ midicontrol::midicontrol () : keycontrol (), m_active (false), m_inverse_active (false), m_status (0), m_d0 (0), m_d1 (0), m_min_d1 (0), m_max_d1 (0) { // Empty body } /** * This constructor assigns the basic values of control name, number, and * action code. The rest of the members can be set via the set() function. * * \param keyname * Provides the name of the key associated with the control, such as * "KP_Home". * * \param opcategory * Indicates if this keystroke is meant for pattern control, mute-group * (mutes) control, or general automation control. It's value is determine * by the section-name in the "ctrl" file. * * \param actioncode * One of the values of automation::action::none, * automation::action::toggle, automation::action::on, or * automation::action::off. The "none" action generally won't be used. * Instead, we just don't create the midicontrol. This value is * implicit in the positioning of a sub-stanza in the stanza line * consisting of three sub-stanzas. * * \param opslot * Provides the slot number of the control, which determines which slot * function (see the automation::slot enumeration) is called. This * specifies the pattern/loop function, mute-group function, or one of the * roughly 48 automation functions. * * \param index * This value is useful in when calling the pattern and mute-group slot * functions. It contains the pattern number, mute-group number, or, * redundantly re the opslot parameter, the automation-slot number, that * the control is supposed to operate. This number is the first number on * each MIDI-control stanza line in the "ctrl" files. */ midicontrol::midicontrol ( const std::string & keyname, automation::category opcategory, automation::action actioncode, automation::slot opslot, int index ) : keycontrol ( "MIDI", keyname, opcategory, actioncode, opslot, index ), m_active (false), m_inverse_active (false), m_status (0), m_d0 (0), m_d1 (0), m_min_d1 (0), m_max_d1 (0) { // Empty body } /** * Not so sure if this really saves trouble for the caller. It fits in * with the big-ass sscanf() call in midicontrolfile. * * \param values * Provides the 6 values, in an integer array, to set into the * members in this order: m_control_code, m_action, m_active, * m_inverse_active, m_status, m_d0, m_min_d1, and m_max_d1. * * \return * Returns true if the control is active. That is, it has a non-zero * status. This is a convenience return value. */ bool midicontrol::set (int values [automation::SUBCOUNT]) { m_inverse_active = bool(values[automation::index::inverse]); m_status = values[automation::index::status]; m_d0 = values[automation::index::data_1]; m_min_d1 = values[automation::index::data_2_min]; m_max_d1 = values[automation::index::data_2_max]; m_active = m_status > 0x00; return m_active; } /** * Checks to see if this control matches the given category and slot. * For the pattern category, the slot should be the pattern number. For the * mute_group category, the slot should be the group number. For both, this * is the control_code() value. For the automation category, the number to * check is the slot_number(). */ bool midicontrol::merge_key_match (automation::category c, int opslot) const { if (c == category_code()) { if (opcontrol::is_automation(c)) { automation::slot s = static_cast(opslot); return s == slot_number(); } else /* assumes a valid category here */ { return opslot == control_code(); } } else return false; } void midicontrol::show (bool add_newline) const { using namespace std; cout << "Key: "; keycontrol::show(false); cout << " [ " << active() << " " << inverse_active() << " " << "0x" << setw(2) << setfill('0') << hex <I/O of the application. * * \library seq66 application * \author C. Ahlstrom * \date 2019-11-25 * \updates 2022-08-25 * \license GNU GPLv2 or above * * The class contained in this file encapsulates most of the functionality to * send feedback to an external control surface in order to reflect the state * of seq66. This includes updates on the playing and queueing status of the * sequences. */ #include "ctrl/midicontrolbase.hpp" /* seq66::midicontrolbase class */ namespace seq66 { midicontrolbase::midicontrolbase (const std::string & name) : m_name (name), m_buss (null_buss()), /* 0xFF */ m_true_buss (null_buss()), m_configured_buss (null_buss()), m_is_blank (true), m_is_enabled (false), m_configure_enabled (false), m_offset (0), m_rows (0), m_columns (0) { // No code needed } bool midicontrolbase::initialize (int buss, int rows, int columns) { bussbyte b = bussbyte(buss); m_buss = m_true_buss = b; m_rows = rows; m_columns = columns; return is_valid_buss(b) && rows > 0 && columns > 0; } } // namespace seq66 /* * midicontrolbase.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/midicontrolin.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midicontrolin.cpp * * This module declares/defines a container for key-ordinals and MIDI * operation information. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-23 * \updates 2023-11-18 * \license GNU GPLv2 or above * * MIDI control container: * * Key: * * The MIDI container is sorted by a key := { event status, d0 }. * * Value: * * The MIDI control value stored in the container consists of: * * opcontrol: name, category, action, slot * keycontrol: key-name, control-code (pattern or group number) * midicontrol: active, inverse, status, d0, d1, min d1, max d1 * * Run-time lookup: * * For run-time lookup, a key is created from the incoming status and d0, * and used to find the (possibly multiple) midicontrols for that event. * If found, then the d1 value is checked for the proper range. If * satisfied, then the corresponding midicontrol operation is invoked. * * In the new-style "rc"/"ctrl" file, the configured keystroke is also * added to the midicontrol object, as well as to the keycontainer. * * Key-control setup from legacy "rc" file: * * After setting up the MIDI control container as above (via the defaults * or the "ctrl" file), the keystrokes are backfilled into the existing * midicontrol container. * * In this reading, all MIDI control stanzas are added to the container, * even if not active, so that all can be written out to the new file * format. * * The order of the keystroke values in the MIDI container depends on * whether the keystroke matches a non-zero MIDI event or not. * * Key/MIDI control setup written to new "ctrl" file: * * Writing of the MIDI control setting takes place every time Seq66 * exits. */ #include /* std::setw() manipulator */ #include /* std::cerr */ #include "ctrl/keycontainer.hpp" /* seq66::keycontainer class */ #include "ctrl/midicontrolin.hpp" /* seq66::midicontrolin class */ namespace seq66 { /** * This constructor assigns the basic values of control name, number, and * action code. The rest of the members can be set via the set() function. */ midicontrolin::midicontrolin (const std::string & name) : midicontrolbase (name), m_container (), m_comments_block (), m_control_status (automation::ctrlstatus::none), m_have_controls (false) { // no code } bool midicontrolin::initialize (int buss, int rows, int columns) { bool result = midicontrolbase::initialize(buss, rows, columns); is_enabled(result); // master bus??? return result; } /** * Adds the midicontrol object to the container, keyed by the midicontrol :: * key object, which represents the basic event data the control must match. * * \param mc * The midicontrol value to add. The key is constructed from the data in * this object. * * \return * Returns true if the control was added. That is, the size of the * container increased by 1. It is an std::multimap, so there shouldn't * be any failures. However, the caller is responsible for inserting * only what controls are needed. */ bool midicontrolin::add (const midicontrol & mc) { bool result = false; auto sz = m_container.size(); auto k = mc.make_key(); auto p = std::make_pair(k, mc); /* std::pair */ (void) m_container.insert(p); result = m_container.size() == (sz + 1); if (result) { if (! mc.blank()) m_have_controls = true; } else { std::cerr << "Duplicate or invalid opslot for '" << mc.name() << "' Category " << mc.category_name() << " Slot " << mc.automation_slot_name() << std::endl ; } return result; } /** * This function is needed for running the application for the first time, * when there is no "rc" or "ctrl" file. We want to be able to write out the * full set of stanzas, with the keystrokes, even if the MIDI controls are * all zero. Controls are written only for defined keystrokes in the * keycontainer. * * \param kc * Provides the key setup, so that the keystroke can be added to the * output. */ void midicontrolin::add_blank_controls (const keycontainer & kc) { for (const auto & kpair : kc.container()) { const keycontrol & k = kpair.second; midicontrol blank ( k.key_name(), k.category_code(), k.action_code(), k.slot_number(), k.control_code() ); (void) add(blank); } } /** * Looks up the MIDI-control object matching the given key value. Remember * that the key is match on the MIDI event status and the d0 value, and * range-checked on the d1 value. And now, the source bus of the event is * now part of the key, not for operator <, but for checking the source of * the event. The source should match this container's true buss, if it * isn't the "null" buss (0xFF). */ const midicontrol & midicontrolin::control (const midicontrol::key & k) const { static midicontrol sm_midicontrol_dummy; bool ok = have_controls(); if (ok) { const auto & cki = m_container.find(k); ok = cki != m_container.end(); if (ok) ok = is_null_buss(nominal_buss()) || k.buss() == true_buss(); return ok ? cki->second : sm_midicontrol_dummy; } else return sm_midicontrol_dummy; } /** * The possible status are contained in automation::ctrlstatus, and consist * of none, replace, snapshot, queue, keep_queue, oneshot, and learn. There * is a combo-status solo == queue + replace. * */ std::string midicontrolin::status_string () const { std::string result; if (is_solo()) result = "Solo"; /* solo is a queued replace */ else if (is_keep_queue()) /* check this before is_queue() */ result = "Keep Q"; else if (is_queue()) result = "Queue"; else if (is_replace()) result = "Replace"; else if (is_snapshot()) result = "Snapshot"; else if (is_oneshot()) result = "Oneshot"; else if (is_learn()) result = "Learn"; return result; } void midicontrolin::show () const { using namespace std; int index = 0; cout << "MIDI-In controls (size " << m_container.size() << "): " << endl << "Index; MIDI key; Keystroke (name, action, slot, code); stanza" << endl ; for (const auto & mcpair : m_container) { const midicontrol::key & k = mcpair.first; const midicontrol & mc = mcpair.second; int status = k.status(); int d0 = k.d0(); /* key::d1() removed */ cout << "[" << setw(3) << hex << right << index << "] " << "0x" << setw(2) << setfill('0') << hex << status << setfill(' ') << " " << setw(2) << hex << d0 << " " ; mc.show(); /* key & midi control */ ++index; } } } // namespace seq66 /* * midicontrolin.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/midicontrolout.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midicontrolout.cpp * * This module declares/defines the class for handling MIDI control * output of the application. * * \library seq66 application * \author Igor Angst (with refactoring by C. Ahlstrom) * \date 2018-03-28 * \updates 2025-07-12 * \license GNU GPLv2 or above * * The class contained in this file encapsulates most of the functionality to * send feedback to an external control surface in order to reflect the state * of seq66. This includes updates on the playing and queueing status of the * sequences. * * Automation buttons (uiactions): This is a TO DO, to follow these rules. * * panic: Always red. * stop: Always red. Making it green when pressed is * confusing. * pause: Red normally, amber when paused. * play: Red normally, green when playing. * toggle_mutes: Always green (for "enabled")? * song_record: Green when on, red when off. * slot_shift: Green when on, red when done. * free: Unused, always red. * queue: Always green? Can we do momentary coloring via * thread? * oneshot: Ditto? * replace: Need to investigate. * snapshot: Green while held, red while released. * song: * Red (or yellow?) in live mode, green in song mode. * learn: * Red normally, green in learn mode. * bpm_up: Always green when configured. * bpm_dn: Ditto. * list_up: Ditto. * list_dn: Ditto. * song_up: Ditto. * song_dn: Ditto. * set_up: Ditto. * set_dn: Ditto. * tap_bpm: Green while tapping. * quit: Keep it red or amber (warning!) * visibility: Hmmmmm. * alt_2 to alt_7 Unused at present. * */ #include /* std::setw() manipulator */ #include /* std::ostringstream class */ #include "ctrl/opcontrol.hpp" /* seq66::automation & opcontrol */ #include "ctrl/midicontrolout.hpp" /* seq66::midicontrolout class */ #include "play/mutegroups.hpp" /* seq66::mutegroups::Size() */ namespace seq66 { /** * This constructor assigns the basic values of control name, number, and * action code. The rest of the members can be set via the set() function. */ midicontrolout::midicontrolout (const std::string & name) : midicontrolbase (name), m_master_bus (nullptr), m_seq_events (), m_ui_events (), m_mutes_events (), m_macro_events (), m_screenset_size (0) { // no code } /** * Reinitializes an empty set of MIDI-control-out values. It first clears * any existing values from the vectors. * * Next, it loads an action-pair with "empty" values. It the creates an * array of these pairs. * * Finally, it pushes the desired number of action-pair arrays into an * actionlist, which is, for example, a vector of 32 elements, each * containing 4 pairs of event + status. A vector of vector of pairs. * * \param buss * The buss number, which can range from 0 to 31, and defaults to * default_control_out_buss (15). * * \param rows * The number of rows in the set, normally 4. * * \param columns * The number of columns in the set, normally 8. */ bool midicontrolout::initialize (int buss, int rows, int columns) { bool result = midicontrolbase::initialize(buss, rows, columns); m_seq_events.clear(); m_ui_events.clear(); m_mutes_events.clear(); if (result) { int count = rows * columns; event dummy_event; actions actionstemp; dummy_event.set_channel_status(0, 0); /* set status and channel */ actionpair apt; apt.apt_action_status = false; apt.apt_action_event = dummy_event; for (int a = 0; a < static_cast(seqaction::max); ++a) actionstemp.push_back(apt); /* blank action-pair vector */ for (int c = 0; c < count; ++c) m_seq_events.push_back(actionstemp); actiontriplet att; att.att_action_status = false; att.att_action_event_on = dummy_event; att.att_action_event_off = dummy_event; att.att_action_event_del = dummy_event; for (int a = 0; a < static_cast(uiaction::max); ++a) m_ui_events.push_back(att); for (int m = 0; m < mutegroups::Size(); ++m) m_mutes_events.push_back(att); m_screenset_size = count; is_enabled(true); /* master bus??? */ } else { m_screenset_size = 0; is_enabled(false); } return result; } /** * This implementation of the prefix "++" operator is needed to use * midicontrolout::uiaction in a for-loop. */ midicontrolout::uiaction & operator ++ (midicontrolout::uiaction & e) { if (e == midicontrolout::uiaction::max) { throw std::out_of_range("operator++(midicontrolout::uiaction)"); } e = midicontrolout::uiaction ( static_cast::type>(e) + 1 ); return e; } /** * This function helps avoid long function calls like: * * std::string name = * opcontrol::automation_slot_name(automation::slot::bpm_up); * * C/C++ talen-pasting cannot work here. */ std::string auto_name (automation::slot s) { return opcontrol::automation_slot_name(s); } /** * A "to_string" function for the seqaction enumeration. */ std::string seqaction_to_string (midicontrolout::seqaction a) { switch (a) { case midicontrolout::seqaction::armed: return "Armed"; case midicontrolout::seqaction::muted: return "Muted"; case midicontrolout::seqaction::queued: return "Queued"; case midicontrolout::seqaction::removed: return "Empty"; default: return "Unknown"; } } /** * A "to_string" function for the action enumeration. */ std::string action_to_string (midicontrolout::uiaction a) { switch (a) { case midicontrolout::uiaction::panic: return auto_name(automation::slot::panic); // "Panic" case midicontrolout::uiaction::stop: return auto_name(automation::slot::stop); // "Stop" case midicontrolout::uiaction::pause: return "Pause"; case midicontrolout::uiaction::play: return auto_name(automation::slot::playback); // "Play" case midicontrolout::uiaction::toggle_mutes: return auto_name(automation::slot::toggle_mutes); // "Toggle-mutes case midicontrolout::uiaction::song_record: return auto_name(automation::slot::song_record); // "Song-record" case midicontrolout::uiaction::slot_shift: return auto_name(automation::slot::slot_shift); // "Slot-shift" case midicontrolout::uiaction::free: return "Free"; case midicontrolout::uiaction::queue: return auto_name(automation::slot::mod_queue); // "Queue" case midicontrolout::uiaction::oneshot: return auto_name(automation::slot::mod_oneshot); // "One-shot" case midicontrolout::uiaction::replace: return auto_name(automation::slot::mod_replace); // "Replace" case midicontrolout::uiaction::snapshot: return auto_name(automation::slot::mod_snapshot); // "Snapshot" case midicontrolout::uiaction::song_mode: return auto_name(automation::slot::song_mode); // "Song-mode" case midicontrolout::uiaction::learn: return auto_name(automation::slot::mod_glearn); // "Learn" case midicontrolout::uiaction::bpm_up: return auto_name(automation::slot::bpm_up); // "BPM-Up" case midicontrolout::uiaction::bpm_dn: return auto_name(automation::slot::bpm_dn); // "BPM-Dn" case midicontrolout::uiaction::list_up: return auto_name(automation::slot::playlist) + " Up"; case midicontrolout::uiaction::list_dn: return auto_name(automation::slot::playlist) + " Dn"; case midicontrolout::uiaction::song_up: return auto_name(automation::slot::playlist_song) + " Up"; case midicontrolout::uiaction::song_dn: return auto_name(automation::slot::playlist_song) + " Dn"; case midicontrolout::uiaction::set_up: return auto_name(automation::slot::ss_up); // "Set-Up" case midicontrolout::uiaction::set_dn: return auto_name(automation::slot::ss_dn); // "Set-Dn" case midicontrolout::uiaction::tap_bpm: return auto_name(automation::slot::tap_bpm); // "Tap-BPM" case midicontrolout::uiaction::quit: return auto_name(automation::slot::quit); // "Quit" case midicontrolout::uiaction::visibility: return auto_name(automation::slot::visibility); // "Visibility" case midicontrolout::uiaction::alt_2: return "Alt_2"; case midicontrolout::uiaction::alt_3: return "Alt_3"; case midicontrolout::uiaction::alt_4: return "Alt_4"; case midicontrolout::uiaction::alt_5: return "Alt_5"; case midicontrolout::uiaction::alt_6: return "Alt_6"; case midicontrolout::uiaction::alt_7: return "Alt_7"; case midicontrolout::uiaction::alt_8: return "Alt_8"; default: return "Unknown"; } } /** * Send out notification about playing status of a sequence. * * \todo * Need to handle screen sets. Since sequences themselves are ignorant * about the current screen set, maybe we can centralise this knowledge * inside this class, so before sending a sequence event, we check here * if the sequence is in the active screen set, otherwise we drop the * event. This requires that in the performer class, we do a "repaint" * each time the screen set is changed. For now, the size of the * screenset is fixed to 32 in this function. * * Also, maybe consider adding an option to the config file, making this * behavior optional: So either absolute sequence actions (let the receiver do * the math...), or sending events relative (modulo) the current screen set. * * \param index * The index into the m_seq_events[][] array. * * \param seq * * \param what * The status action of the sequence. This indicates if the sequence is * playing, muted, queued, or deleted (removed, empty). * * \param flush * Flush MIDI buffer after sending (default true). */ void midicontrolout::send_seq_event (int index, seqaction what, bool flush) { bool ok = is_enabled() && index < int(m_seq_events.size()); if (ok) ok = what < seqaction::max; if (ok) { int w = static_cast(what); if (m_seq_events[index][w].apt_action_status) { event ev = m_seq_events[index][w].apt_action_event; if (not_nullptr(m_master_bus) && ev.valid_status()) { #if defined SEQ66_PLATFORM_DEBUG_TMI std::string act = seqaction_to_string(what); std::string evstring = ev.to_string(); printf ( "send_seq_event(%s): %s\n", act.c_str(), evstring.c_str() ); #endif bussbyte tb = true_buss(); if (flush) m_master_bus->play_and_flush(tb, &ev, ev.channel()); else m_master_bus->play(tb, &ev, ev.channel()); } } } } /** * Clears all visible sequences by sending "delete" messages for all * sequences ranging from 0 to 31. */ void midicontrolout::clear_sequences (bool flush) { if (is_enabled()) { for (int seq = 0; seq < screenset_size(); ++seq) send_seq_event(seq, midicontrolout::seqaction::removed, false); if (flush && not_nullptr(m_master_bus)) m_master_bus->flush(); } } void midicontrolout::clear_mutes (bool flush) { if (is_enabled()) { for (int g = 0; g < mutegroups::Size(); ++g) send_mutes_event(g, action_del); if (flush && not_nullptr(m_master_bus)) m_master_bus->flush(); } } /** * Getter for sequence action events. * * \param seq * The index of the sequence. * * \param what * The action to be notified. * * \return * The MIDI event to be sent. */ event midicontrolout::get_seq_event (int seq, seqaction what) const { static event s_dummy_event; int w = static_cast(what); return seq >= 0 && seq < screenset_size() ? m_seq_events[seq][w].apt_action_event : s_dummy_event; } /** * Register a MIDI event for a given sequence action. * * \param seq * The index of the sequence. * * \param what * The action to be notified. * * \param ev * Raw int array representing The MIDI event to be sent. It has been * reduced to the three values need to specify a status event with channel * and two data values. */ void midicontrolout::set_seq_event (int seq, seqaction what, int * eva) { bool ok = what < seqaction::max && seq < int(m_seq_events.size()); if (ok) { int w = static_cast(what); bool enabled = eva[index::status] > 0x00; event ev; ev.set_status_keep_channel(eva[index::status]); ev.set_data(eva[index::data_1], eva[index::data_2]); m_seq_events[seq][w].apt_action_event = ev; m_seq_events[seq][w].apt_action_status = enabled; is_blank(false); } } /** * Checks if a sequence status event is active. * * \param seq * The index of the sequence. * * \param what * The action to be notified. * * \return * Returns true if the respective event is active. */ bool midicontrolout::seq_event_is_active (int seq, seqaction what) const { int w = static_cast(what); return (seq >= 0 && seq < screenset_size()) ? m_seq_events[seq][w].apt_action_status : false ; } /** * Note the att_action_event_del is now used with uiaction events. * * Issue #55: While researching this issue, we found the portmidi * implementation of Seq66 was trying to send events of status 0x00. */ void midicontrolout::send_event (uiaction what, actionindex which) { if (is_enabled() && not_nullptr(m_master_bus)) { event ev; int w = static_cast(what); if (event_is_active(what)) { if (which == action_on) ev = m_ui_events[w].att_action_event_on; else if (which == action_off) ev = m_ui_events[w].att_action_event_off; else if (which == action_del) ev = m_ui_events[w].att_action_event_del; } else ev = m_ui_events[w].att_action_event_del; if (ev.valid_status()) m_master_bus->play_and_flush(true_buss(), &ev, ev.channel()); } } void midicontrolout::send_learning (bool learning) { actionindex which = learning ? action_on : action_off ; send_event(uiaction::learn, which); } void midicontrolout::send_automation (bool activate) { actionindex ai = activate ? action_off : action_del ; for (uiaction uia = uiaction::panic; uia < uiaction::max; ++uia) send_event(uia, ai); } void midicontrolout::send_macro (const std::string & name, bool flush) { bool enabled = is_enabled() && not_nullptr(m_master_bus); if (enabled) enabled = m_macro_events.active(); if (enabled) { midibytes byts = m_macro_events.bytes(name); if (! byts.empty()) { int len = int(byts.size()); bussbyte tb = true_buss(); if (event::is_ex_data_msg(byts[0])) { event ev; const midibyte * b = midi_bytes(byts); (void) ev.set_sysex(b, len); m_master_bus->sysex(tb, &ev); /* flushes */ } else { midibyte d1 = len == 3 ? byts[2] : 0 ; event ev(0, byts[0], byts[1], d1); if (flush) m_master_bus->play_and_flush(tb, &ev, ev.channel()); else m_master_bus->play(tb, &ev, ev.channel()); } } } } std::string midicontrolout::get_event_str (const event & ev) const { int s = int(ev.get_status()); midibyte d0, d1; ev.get_data(d0, d1); std::ostringstream str; str << "[ 0x" << std::hex << std::setw(2) << std::setfill('0') << s << " " << std::dec << std::setw(3) << std::setfill(' ') << int(d0) << " " << std::dec << std::setw(3) << std::setfill(' ') << int(d1) << " ]" ; return str.str(); } std::string midicontrolout::get_ctrl_event_str (uiaction what, actionindex which) const { std::string result; if (! m_ui_events.empty()) { int w = static_cast(what); event ev; if (which == action_on) ev = m_ui_events[w].att_action_event_on; else if (which == action_off) ev = m_ui_events[w].att_action_event_off; else if (which == action_del) ev = m_ui_events[w].att_action_event_del; result = get_event_str(ev); } return result; } std::string midicontrolout::get_mutes_event_str (int group, actionindex which) const { std::string result; event ev; if (m_mutes_events.size() > 0) { if (which == action_on) ev = m_mutes_events[group].att_action_event_on; else if (which == action_off) ev = m_mutes_events[group].att_action_event_off; else if (which == action_del) ev = m_mutes_events[group].att_action_event_del; } result = get_event_str(ev); return result; } /** * 3 elements in each integer array: status, d1, d2. If either status (on vs * off) is 0x00, we disable it, to avoid sending junk. */ void midicontrolout::set_event ( uiaction what, bool enabled, int * onp, int * offp, int * delp ) { if (what < uiaction::max && ! m_ui_events.empty()) { int w = static_cast(what); event on; on.set_status_keep_channel(onp[index::status]); on.set_data(onp[index::data_1], onp[index::data_2]); m_ui_events[w].att_action_event_on = on; event off; off.set_status_keep_channel(offp[index::status]); off.set_data(onp[index::data_1], offp[index::data_2]); m_ui_events[w].att_action_event_off = off; event del; del.set_status_keep_channel(delp[index::status]); del.set_data(onp[index::data_1], delp[index::data_2]); m_ui_events[w].att_action_event_del = del; /* tricky */ if (enabled) { /* * Currently the new and third section, "inactive", is optional. */ if (onp[index::status] == 0x00 || offp[index::status] == 0x00) enabled = false; } m_ui_events[w].att_action_status = enabled; if (enabled) is_blank(false); } } /** * Checks if an event is active. * * \param what * The action to be notified. * * \return * Returns true if the respective event is active. */ bool midicontrolout::event_is_active (uiaction what) const { int w = static_cast(what); return (what < uiaction::max && ! m_ui_events.empty()) ? m_ui_events[w].att_action_status : false ; } void midicontrolout::set_mutes_event ( int group, int * onp, int * offp, int * delp ) { if (group >= 0 && group < mutegroups::Size()) { event on; bool enabled = onp[index::status] > 0x00; on.set_status_keep_channel(onp[index::status]); on.set_data(onp[index::data_1], onp[index::data_2]); m_mutes_events[group].att_action_event_on = on; event off; off.set_status_keep_channel(offp[index::status]); off.set_data(offp[index::data_1], offp[index::data_2]); m_mutes_events[group].att_action_event_off = off; event del; del.set_status_keep_channel(delp[index::status]); del.set_data(delp[index::data_1], delp[index::data_2]); m_mutes_events[group].att_action_event_del = del; m_mutes_events[group].att_action_status = enabled; if (enabled) is_blank(false); } } bool midicontrolout::mutes_event_is_active (int group) const { return group >= 0 && group < mutegroups::Size() ? m_mutes_events[group].att_action_status : false ; } void midicontrolout::send_mutes_event (int group, actionindex which) { bool ok = is_enabled() && mutes_event_is_active(group); if (ok) { event ev; if (which == action_on) ev = m_mutes_events[group].att_action_event_on; else if (which == action_off) ev = m_mutes_events[group].att_action_event_off; else if (which == action_del) ev = m_mutes_events[group].att_action_event_del; if (ev.valid_status() && not_nullptr(m_master_bus)) m_master_bus->play_and_flush(true_buss(), &ev, ev.channel()); } } } // namespace seq66 /* * midicontrolout.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/midimacro.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midimacro.cpp * * This module declares/defines the base class for handling MIDI control * I/O of the application. * * \library seq66 application * \author C. Ahlstrom * \date 2021-11-21 * \updates 2025-07-12 * \license GNU GPLv2 or above * * The specification for the midimacro is of the following format: * \verbatim macroname = { valuespec } \endverbatim * * where valuespec is either a byte value in hex format (e.g. 0xF3) or * the name of another macro in the format "$othermacro". Here are some * examples: * \verbatim header = 0xF0 0x00 0x20 0x29 0x02 0x0E # Launchpad Pro MK3 function = $header \endverbatim * */ #include "ctrl/midimacro.hpp" /* seq66::midimacro class */ #include "util/strfunctions.hpp" /* seq66::tokenize() */ namespace seq66 { midimacro::midimacro (const std::string & name, const std::string & values) : m_name (name), m_tokens (), m_bytes (), m_event_count (0), m_event_bytes (), m_is_valid (false) { m_is_valid = tokenize(values); /* the member function below */ } const midibytes & midimacro::bytes (int index) const { static midibytes s_dummy { 0 }; if (event_count() == 1 || index == (-1)) { return m_bytes; } else { if (index >= 0 && index < m_event_count) return m_event_bytes[index]; else return s_dummy; } } /** * We have added the ability to provide multiple "|"-separated events in * one macro. */ bool midimacro::tokenize (const std::string & values) { bool result; m_tokens = seq66::tokenize(values); /* from strfunctions module */ result = m_tokens.size() > 0; if (result) { m_event_count = 1; if (m_tokens.size() >= 3) { for (const auto & t : m_tokens) { if (t == "|") ++m_event_count; } } } return result; } std::string midimacro::line () const { std::string result = name(); result += " ="; for (const auto & t : tokens()) { result += " "; result += t; } return result; } } // namespace seq66 /* * midimacro.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/midimacros.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midimacros.cpp * * This module declares/defines the base class for handling MIDI control * I/O of the application. * * \library seq66 application * \author C. Ahlstrom * \date 2021-11-21 * \updates 2025-07-12 * \license GNU GPLv2 or above * * The specification for the midimacros is of the following format: * \verbatim macroname = { valuespec } \endverbatim * * where valuespec is either a byte value in hex format (e.g. 0xF3) or * the name of another macro in the format "$othermacro". Here are some * examples: * \verbatim header = 0xF0 0x00 0x20 0x29 0x02 0x0E # Launchpad Pro MK3 function = $header \endverbatim * */ #include "ctrl/midimacros.hpp" /* seq66::midimacros class */ #include "util/strfunctions.hpp" /* seq66::tokenize() */ namespace seq66 { /** * Constant static macro names. */ const std::string midimacros::footer = "footer"; const std::string midimacros::header = "header"; const std::string midimacros::reset = "reset"; const std::string midimacros::startup = "startup"; const std::string midimacros::shutdown = "shutdown"; /** * Default constructor. */ midimacros::midimacros () : m_macros () { // No code needed } /** * This function handles parsing a line from the 'ctrl' file. The line should * have the form "macroname = " which is then converted in that * file to a two-element vector of tokens: tokens[0] = name, tokens[1] = * , which is a space-separated list of midibyte-strings in * hex format. */ bool midimacros::add (const tokenization & tokens) { bool result = tokens.size() >= 1; /* the name, then the data */ if (result) { std::string key = tokens[0]; std::string data; if (tokens.size() > 1) /* any data? */ data = tokens[1]; midimacro m(key, data); /* further tokenizes */ auto p = std::make_pair(key, m); auto r = m_macros.insert(p); /* r: pair */ result = r.second; } return result; } /** * Converts all the loaded macros into midibytes, expanding macro * references where needed. References are tokens showing the name of * another macro, e.g. "$header". */ bool midimacros::expand () { bool result = count() > 0; if (result) { for (auto & m : m_macros) { midimacro & mac = m.second; midibytes b = expand(mac); result = ! b.empty(); if (result) mac.bytes(b); else break; } } return result; } /** * Recursively expands macro variables (e.g. "$footer") */ midibytes midimacros::expand (midimacro & m) { midibytes result; /* holds all of the bytes found */ midibytes temp; /* holds bytes of 1 event if ! N/A */ bool found_events = false; for (const auto & token : m.tokens()) { if (token[0] == '$') { std::string name = token.substr(1); const auto cit = m_macros.find(name); if (cit != m_macros.end()) { /* * Was a midistring: result += expand(cit->second); */ midibytes xpanded = expand(cit->second); result.insert(result.end(), xpanded.begin(), xpanded.end()); } else { result.clear(); break; } } else if (token[0] == '|') { found_events = true; m.push_bytes(temp); temp.clear(); } else { /* * result += b; */ midibyte b = string_to_midibyte(token); result.push_back(b); temp.push_back(b); } } if (found_events) m.push_bytes(temp); /* push the bytes of last event */ return result; } midibytes midimacros::bytes (const std::string & name) const { midibytes result; const auto cit = m_macros.find(name); if (cit != m_macros.end()) { const midimacro & m = cit->second; if (m.is_valid()) result = m.bytes(); } return result; } const midimacro & midimacros::macro (const std::string & name) const { static midimacro s_dummy; /* is_valid() will return false */ const auto cit = m_macros.find(name); return cit != m_macros.end() ? cit->second : s_dummy ; } std::string midimacros::lines () const { std::string result; for (const auto & m : m_macros) { result += m.second.line(); result += "\n"; } return result; } tokenization midimacros::names () const { tokenization result; for (const auto & m : m_macros) /* const auto & [key, value] */ result.push_back(m.second.name()); return result; } std::string midimacros::byte_strings () const { std::string result; for (const auto & m : m_macros) { const midimacro & mac = m.second; result += mac.name(); result += ": "; result += midi_bytes_string(mac.bytes()); result += "\n"; } return result; } /** * This function creates some defaults to ensure that there is a valid * macro-control section in the 'ctrl' file. These are not useable, but will * be checked for at (for example) startup and shutdown. */ bool midimacros::make_defaults () { static const std::string s_defaults [] = { "footer = 0xF7 # End-of-SysEx byte", "header = 0xF0 0x00 0x00 # device SysEx header, 0xF0 required", "reset = $header 0x00 $footer # fill in with device's reset command", "startup = $header 0x00 $footer # sent at start, if not empty", "shutdown = $header 0x00 $footer # sent at exit, if not empty", "" /* list terminator */ }; bool result = count() == 0; if (result) { for (int i = 0; ! s_defaults[i].empty(); ++i) { tokenization t = seq66::tokenize(s_defaults[i], "="); if (! add(t)) break; } } return result; } } // namespace seq66 /* * midimacros.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/midioperation.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midioperation.cpp * * This module declares/defines just some of the global (gasp!) variables * and functions for the extended MIDI control feature. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-13 * \updates 2019-03-20 * \license GNU GPLv2 or above * */ #include /* std::cout for show() */ #include "ctrl/midioperation.hpp" /* seq66::midioperation class */ namespace seq66 { /** * This default constructor creates a "zero" object. Every member is * either false or some other form of zero. */ midioperation::midioperation () : m_op_name (), m_op_category (automation::category::none), m_op_number (automation::slot::none), m_parent_function () { // Empty body } /** * This constructor assigns the basic values of control name, number, and * action code. The rest of the members can be set via the set() function. */ midioperation::midioperation ( const std::string & opname, automation::category opcategory, automation::slot opnumber, functor pfunction ) : m_op_name (opname), m_op_category (opcategory), m_op_number (opnumber), m_parent_function (pfunction) { // Empty body } void midioperation::show () const { std::cout << "Op " << name() << " Cat " << cat_name() << " Slot " << slot_name() << std::endl ; } } // namespace seq66 /* * midioperation.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/opcontainer.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file opcontainer.cpp * * This module declares/defines a container for operation numbers and * midioperation objects. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-18 * \updates 2023-12-07 * \license GNU GPLv2 or above * */ #include /* std::setw() manipulator */ #include /* std::cout (using namespace std) */ #include "ctrl/opcontainer.hpp" /* seq66::opcontainer class */ namespace seq66 { /** * This default constructor creates a "zero" object. Every member is * either false or some other form of zero. */ opcontainer::opcontainer () : m_container (), m_container_name () { // Empty body } /** * This constructor assigns the basic values of control name, number, and * action code. The rest of the members can be set via the set() function. */ opcontainer::opcontainer (const std::string & name) : m_container (), m_container_name (name) { // Empty body } /** * Adds a midioperation to the container, if it is not the automation slot. * * \param op * The operation's object, which provides the op-number, the automation * slot number to use. The "max" value will never be encountered, and * the "automation" value is ignored. The "reserved" numbers should use * a no-op midioperation. * * What about just checking "none"? */ bool opcontainer::add (const midioperation & op) { bool result = false; automation::slot opnumber = op.number(); if ( opnumber != automation::slot::max && // || re issue #124 opnumber != automation::slot::automation ) { opmap::size_type sz = m_container.size(); /* * auto --> std::pair; */ auto p = std::make_pair(opnumber, op); (void) m_container.insert(p); result = m_container.size() == (sz + 1); } return result; } const midioperation & opcontainer::operation (automation::slot s) const { static midioperation sm_midioperation_dummy; const auto & coi = m_container.find(s); return (coi != m_container.end()) ? coi->second : sm_midioperation_dummy; } void opcontainer::show () const { int index = 0; std::cout << "Op container size: " << m_container.size() << std::endl; for (const auto & oc : m_container) { std::cout << "[" << std::setw(2) << std::right << index << "] " << opcontrol::automation_slot_name(oc.first) << ": " ; oc.second.show(); ++index; } } } // namespace seq66 /* * opcontainer.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/opcontrol.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file opcontrol.cpp * * This module declares/defines the opcontrol base class for the keycontrol * class and the extended MIDI control feature. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-12-04 * \updates 2023-09-30 * \license GNU GPLv2 or above * */ #include "ctrl/opcontrol.hpp" /* seq66::opcontrol base class */ #include "util/basic_macros.hpp" /* seq66::tokenization alias */ namespace seq66 { using namespace automation; /** * This default constructor creates a "zero" object. Every member is either * false or some other form of zero. */ opcontrol::opcontrol () : m_name (), m_category (category::none), m_action (action::none), m_slot_number (slot::none) { // Empty body } /** * This constructor assigns the basic values of control name, number, and * action code. The rest of the members can be set via the set() function. */ opcontrol::opcontrol ( const std::string & opname, category opcategory, action opaction, slot opslot, int index /* for pattern, mutes only */ ) : m_name (opname), m_category (opcategory), m_action (opaction), m_slot_number (opslot) { if (m_name.empty()) m_name = build_slot_name(index); } /** * Constructs the slot name based on the category and the operation number of * this opcontrol object. Note that this function is not static. Also note * that it works only for categories other than "none" or "max", which are * just range-checkers. */ std::string opcontrol::build_slot_name (int index) const { std::string result; if (m_category == category::loop) { result = automation_slot_name(slot::loop); result += " "; result += std::to_string(static_cast(index)); } else if (m_category == category::mute_group) { result = automation_slot_name(slot::mute_group); result += " "; result += std::to_string(static_cast(index)); } else if (m_category == category::automation) { result = automation_slot_name(m_slot_number); } return result; } /** * Access to the category names in a static function. A static function to * return the name of an operation category. We keep these very short for * readability in debugging dumps. */ std::string opcontrol::category_name (category c) { std::string result; switch (c) { case category::none: result = "None"; break; case category::loop: result = "Loop"; break; case category::mute_group: result = "Mute"; break; case category::automation: result = "Auto"; break; case category::max: result = "Max"; break; } return result; } /** * Access to the action names in a static function. A static function to * return the name of an action code. */ std::string opcontrol::action_name (action a) { std::string result; switch (a) { case action::none: result = "None"; break; case action::toggle: result = "Toggle"; break; case action::on: result = "On"; break; case action::off: result = "Off"; break; case action::max: result = "Max"; break; } return result; } /** * Access to the slot names in a static function. A static function to * return the base name of an operation slot. * * Compare this list to the similar list in libseq66/src/ctrl/automation.cpp. * and its header file. * They differ in letter case and (slightly) * in numbering. * * This list is meant mainly for decorating the configuration file and for * human-readable reporting. * * Note the asterisks; they indicate the slots having MIDI output display * in the midicontrolout::uiaction enumeration. */ std::string opcontrol::automation_slot_name (slot s) { static tokenization s_slot_names = { "BPM Up", // 0 bpm_up * "BPM Dn", // 1 bpm_dn * "Set Up", // 2 ss_up * "Set Dn", // 3 ss_dn * "Replace", // 4 mod_replace * "Snapshot", // 5 mod_snapshot * "Queue", // 6 mod_queue * "Group Mute", // 7 mod_gmute "Group Learn", // 8 mod_glearn * "Playing Set", // 9 play_ss "Playback", // 10 playback (pause) * * * "Song Record", // 11 song_record * "Solo", // 12 solo "Thru", // 13 thru "BPM Page Up", // 14 bpm_page_up "BPM Page Dn", // 15 bpm_page_dn "Set Set", // 16 ss_set "Record Style", // 17 record_style (increment/decrement) "Quan Record", // 18 quan_record "Reset Sets", // 19 reset_sets "One-shot", // 20 mod_oneshot * "FF", // 21 FF (movement faster than playback) "Rewind", // 22 rewind (movement faster than playback) "Top", // 23 top (song beginning or L marker) "Play List", // 24 playlist * * "Play Song", // 25 playlist_song * * "Tap BPM", // 26 tap_bpm * "Start", // 27 start "Stop", // 28 stop ? "Loop L/R", // 29 loop_LR "Toggle Mutes", // 30 toggle_mutes * "Song Pos", // 31 song_pointer /* * The following add to what Seq64 supports. */ "Keep Queue", // 32 keep_queue "Slot Shift", // 33 slot_shift * "Mutes Clear", // 34 mutes_clear "Quit", // 35 quit * "Loop Edit", // 36 pattern_edit "Event Edit", // 37 event_edit "Song Mode", // 38 song_mode * "Toggle JACK", // 39 toggle_jack "Menu Mode", // 40 menu_mode "Follow JACK", // 41 follow_transport "Panic", // 42 panic * "Visibility", // 43 visibility * "Save Session", // 44 save_session "Record Toggle", // 45 record_toggle "Grid Mutes", // 46 grid_mutes Grid expansion :-( "Reserved 47", // 47 reserved_47 "Reserved 48", // 48 reserved_48 /* * Record mode selection. Changed labels on 2023-03-18. */ "Overdub", // 49: record_overdub "Overwrite", // 50: record_overwrite "Expand", // 51: record_expand "Oneshot", // 52: record_oneshot /* * Grid mode selection. Changed labels on 2023-03-18. */ "Grid Loop", // 53: grid_loop "Grid Record", // 54: grid_record "Grid Copy", // 55: grid_copy "Grid Paste", // 56: grid_paste "Grid Clear", // 57: grid_clear "Grid Delete", // 58: grid_delete "Grid Thru", // 59: grid_thru "Grid Solo", // 60: grid_solo "Grid Cut", // 61: grid_velocity "Grid Double", // 62: grid_double /* * Grid quantization type selection. */ "Q None", // 63: grid_quant_none "Q Full", // 64: grid_quant_full "Q Tighten", // 65: grid_quant_tighten "Randomize", // 66: grid_quant_random "Jitter", // 67: grid_quant_jitter "Note-map", // 68: grid_quant_notemap /* * A few more likely candidates. */ "BBT/HMS", // 69: mod_bbt_hms "LR Loop", // 70: mod_LR_loop "Undo", // 71: mod_undo "Redo", // 72: mod_redo "Transpose Song", // 73: mod_transpose_song ??? "Copy Set", // 74: mod_copy_set "Paste Set", // 75: mod_paste_set "Toggle Tracks", // 76: mod_toggle_tracks /* * Set-playing modes. */ "Sets Normal", // 77: set_mode_normal "Sets Auto", // 78: set_mode_auto "Sets Additive", // 79: set_mode_additive "All Sets", // 80: set_mode_all_sets /* * Tricky ending. */ "Max", // 81: Maximum -- used only for limit-checking /* * The following selectthe correct op function. */ "Loop", // Indicates the pattern key group "Mute", // Indicates the mute key group "Auto" // Indicates automation key group }; std::string result; if (s >= slot::bpm_up && s <= slot::automation) { int index = static_cast(s); result = s_slot_names[index]; } return result; } /** * A static function to set a slot value from an integer, safely. */ automation::slot opcontrol::set_slot (int op) { static int s_minimum = static_cast(automation::slot::bpm_up); static int s_maximum = static_cast(automation::slot::max); return (op >= s_minimum && op < s_maximum) ? static_cast(op) : automation::slot::none ; } } // namespace seq66 /* * opcontrol.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/ctrl/winkeys.hpp ================================================ #if ! defined SEQ66_WINKEYS_HPP #define SEQ66_WINKEYS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file winkeys.hpp * * This module defines some key-handling functionality specific to Windows. * * \library seq66 application * \author Chris Ahlstrom * \date 2023-07-18 * \updates 2023-07-19 * \license GNU GPLv2 or above * * Include this file in keymap.cpp if building for Windows. The main * difference is in the values of the native virtual keys. See the * contrib/notes/win-virtual-keys.text file for source information. Note * that many of the Windows virtual keys represent functions we don't support * here, usch as mouse button and Asion IME modes. * * #include // VK_SHIFT and other key constants */ /** * Maps key names to the key integers. The Windows version. See the * original version in keymap.cpp for more details. */ static qt_keycodes & qt_keys (int i) { using namespace keyboard; static qt_keycodes s_qt_keys [] = { /* * Code Qt Qt Key * Ordinal Key Event Virt Key Name Modifier * * Ctrl-key section. It is best to avoid using Control keys to control * loops, mutes, and automation. Too much chance of interfering with * the normal user interface. */ { 0x00, 0x40, 0x00, "NUL", KCTRL }, // ^@: Null { 0x01, 0x41, 0x01, "SOH", KCTRL }, // ^A: Start of Heading { 0x02, 0x42, 0x02, "STX", KCTRL }, // ^B: Start of Text { 0x03, 0x43, 0x03, "ETX", KCTRL }, // ^C: End of Text { 0x04, 0x44, 0x04, "EOT", KCTRL }, // ^D: End of Transmision { 0x05, 0x45, 0x05, "ENQ", KCTRL }, // ^E: Enquiry { 0x06, 0x46, 0x06, "ACK", KCTRL }, // ^F: Acknowledge { 0x07, 0x47, 0x07, "BEL", KCTRL }, // ^G: Bell/beep { 0x08, 0x48, 0x08, "BS", KCTRL }, // ^H: Backspace, VK_BACK { 0x09, 0x49, 0x09, "HT", KCTRL }, // ^I: Tab, VK_TAB { 0x0a, 0x4a, 0x0a, "LF", KCTRL }, // ^J: Line Feed { 0x0b, 0x4b, 0x0b, "VT", KCTRL }, // ^K: Vertical Tab { 0x0c, 0x4c, 0x0c, "FF", KCTRL }, // ^L: Form Feed { 0x0d, 0x4d, 0x0d, "CR", KCTRL }, // ^M: CR, VK_RETURN { 0x0e, 0x4e, 0x0e, "SO", KCTRL }, // ^N: Shift Out { 0x0f, 0x4f, 0x0f, "SI", KCTRL }, // ^O: Shift In { 0x10, 0x50, 0x10, "DLE", KCTRL }, // ^P: Data Link Escape { 0x11, 0x51, 0x11, "DC1", KCTRL }, // ^Q: Device Control 1 { 0x12, 0x52, 0x12, "DC2", KCTRL }, // ^R: Device Control 2 { 0x13, 0x53, 0x13, "DC3", KCTRL }, // ^S: Device Control 3 { 0x14, 0x54, 0x14, "DC4", KCTRL }, // ^T: Device Control 4 { 0x15, 0x55, 0x15, "NAK", KCTRL }, // ^U: Negative ACK { 0x16, 0x56, 0x16, "SYN", KCTRL }, // ^V: Synchronous Idle { 0x17, 0x57, 0x17, "ETB", KCTRL }, // ^W: End of Trans Block { 0x18, 0x58, 0x18, "CAN", KCTRL }, // ^X: Cancel { 0x19, 0x59, 0x19, "EM", KCTRL }, // ^Y: End of Medium { 0x1a, 0x5a, 0x1a, "SUB", KCTRL }, // ^Z: Substitute { 0x1b, 0x5b, 0x1b, "ESC", KCTRL }, // ^[: Escape, VK_ESCAPE { 0x1c, 0x5c, 0x1c, "FS", KCTRL }, // ^\: File Separator { 0x1d, 0x5d, 0x1d, "GS", KCTRL }, // ^]: Group Separator { 0x1e, 0x5e, 0x1e, "RS", KCTRLSHIFT }, // ^^: Record Separator { 0x1f, 0x5f, 0x1f, "US", KCTRLSHIFT }, // ^_???: Unit Separator /* * Ordinal Key Event Virt Key Name Modifier */ { 0x20, 0x20, 0x20, "Space", KNONE }, // Space, VK_SPACE { 0x21, 0x21, 0x31, "!", KSHIFT }, // Exclam, Shift-1 { 0x22, 0x22, 0xde, "\"", KSHIFT }, // QuoteDbl, VK_OEM_7 { 0x23, 0x23, 0x33, "#", KSHIFT }, // NumberSign, Shift-3 { 0x24, 0x24, 0x34, "$", KSHIFT }, // Dollar, Shift-4 { 0x25, 0x25, 0x35, "%", KSHIFT }, // Percent, Shift-5 { 0x26, 0x26, 0x37, "&", KSHIFT }, // Ampersand, Shift-7 { 0x27, 0x27, 0xde, "'", KSHIFT }, // Apostrophe, VK_OEM_7 { 0x28, 0x28, 0x39, "(", KSHIFT }, // ParenLeft, Shift-9 { 0x29, 0x29, 0x30, ")", KSHIFT }, // ParenRight, Shift-0 { 0x2a, 0x2a, 0x38, "*", KSHIFT }, // Asterisk, Shift-8 { 0x2b, 0x2b, 0xbb, "+", KSHIFT }, // Plus, VK_OEM_PLUS { 0x2c, 0x2c, 0xbc, ",", KNONE }, // Comma, VK_OEM_COMMA { 0x2d, 0x2d, 0xbd, "-", KNONE }, // Minus, VK_OEM_MINUS { 0x2e, 0x2e, 0xbe, ".", KNONE }, // Period, VK_OEM_PERIOD { 0x2f, 0x2f, 0xbf, "/", KNONE }, // Slash, VK_OEM_2 { 0x30, 0x30, 0x30, "0", KNONE }, // ASCII { 0x31, 0x31, 0x31, "1", KNONE }, // ASCII { 0x32, 0x32, 0x32, "2", KNONE }, // ASCII { 0x33, 0x33, 0x33, "3", KNONE }, // ASCII { 0x34, 0x34, 0x34, "4", KNONE }, // ASCII { 0x35, 0x35, 0x35, "5", KNONE }, // ASCII { 0x36, 0x36, 0x36, "6", KNONE }, // ASCII { 0x37, 0x37, 0x37, "7", KNONE }, // ASCII { 0x38, 0x38, 0x38, "8", KNONE }, // ASCII { 0x39, 0x39, 0x39, "9", KNONE }, // ASCII { 0x3a, 0x3a, 0xba, ":", KSHIFT }, // VK_OEM_1 { 0x3b, 0x3b, 0xba, ";", KNONE }, // VK_OEM_1 { 0x3c, 0x3c, 0xbc, "<", KSHIFT }, // Shift-VK_OEM_COMMA { 0x3d, 0x3d, 0xbb, "=", KNONE }, // Unshift-VK_OEM_PLUS { 0x3e, 0x3e, 0xbe, ">", KSHIFT }, // Shift-VK_OEM_PERIOD { 0x3f, 0x3f, 0xbf, "?", KSHIFT }, // VK_OEM_2 { 0x40, 0x40, 0x32, "@", KSHIFT }, // Shift-2 { 0x41, 0x41, 0x41, "A", KSHIFT }, // Shift key modifier, ASCII { 0x42, 0x42, 0x42, "B", KSHIFT }, // ASCII { 0x43, 0x43, 0x43, "C", KSHIFT }, // ASCII { 0x44, 0x44, 0x44, "D", KSHIFT }, // ASCII { 0x45, 0x45, 0x45, "E", KSHIFT }, // ASCII { 0x46, 0x46, 0x46, "F", KSHIFT }, // ASCII { 0x47, 0x47, 0x47, "G", KSHIFT }, // ASCII { 0x48, 0x48, 0x48, "H", KSHIFT }, // ASCII { 0x49, 0x49, 0x49, "I", KSHIFT }, // ASCII { 0x4a, 0x4a, 0x4a, "J", KSHIFT }, // ASCII { 0x4b, 0x4b, 0x4b, "K", KSHIFT }, // ASCII { 0x4c, 0x4c, 0x4c, "L", KSHIFT }, // ASCII { 0x4d, 0x4d, 0x4d, "M", KSHIFT }, // ASCII { 0x4e, 0x4e, 0x4e, "N", KSHIFT }, // ASCII { 0x4f, 0x4f, 0x4f, "O", KSHIFT }, // ASCII { 0x50, 0x50, 0x50, "P", KSHIFT }, // ASCII { 0x51, 0x51, 0x51, "Q", KSHIFT }, // ASCII { 0x52, 0x52, 0x52, "R", KSHIFT }, // ASCII { 0x53, 0x53, 0x53, "S", KSHIFT }, // ASCII { 0x54, 0x54, 0x54, "T", KSHIFT }, // ASCII { 0x55, 0x55, 0x55, "U", KSHIFT }, // ASCII { 0x56, 0x56, 0x56, "V", KSHIFT }, // ASCII { 0x57, 0x57, 0x57, "W", KSHIFT }, // ASCII { 0x58, 0x58, 0x58, "X", KSHIFT }, // ASCII { 0x59, 0x59, 0x59, "Y", KSHIFT }, // ASCII { 0x5a, 0x5a, 0x5a, "Z", KSHIFT }, // ASCII { 0x5b, 0x5b, 0xdb, "[", KNONE }, // BracketLeft, VK_OEM_4 { 0x5c, 0x5c, 0xdc, "\\", KNONE }, // Backslash, VK_OEM_5 { 0x5d, 0x5d, 0xdd, "]", KNONE }, // BracketRight, VK_OEM_6 { 0x5e, 0x5e, 0x36, "^", KSHIFT }, // AsciiCircumflex, Shift-6 { 0x5f, 0x5f, 0xbd, "_", KSHIFT }, // Underscore, Shift-VK_OEM_MINUS { 0x60, 0x60, 0xc0, "`", KNONE }, // QuoteLeft, Backtick, VK_OEM_3 { 0x61, 0x41, 0x41, "a", KNONE }, // ASCII { 0x62, 0x42, 0x42, "b", KNONE }, // ASCII { 0x63, 0x43, 0x43, "c", KNONE }, // ASCII { 0x64, 0x44, 0x44, "d", KNONE }, // ASCII { 0x65, 0x45, 0x45, "e", KNONE }, // ASCII { 0x66, 0x46, 0x46, "f", KNONE }, // ASCII { 0x67, 0x47, 0x47, "g", KNONE }, // ASCII { 0x68, 0x48, 0x48, "h", KNONE }, // ASCII { 0x69, 0x49, 0x49, "i", KNONE }, // ASCII { 0x6a, 0x4a, 0x4a, "j", KNONE }, // ASCII { 0x6b, 0x4b, 0x4b, "k", KNONE }, // ASCII { 0x6c, 0x4c, 0x4c, "l", KNONE }, // ASCII { 0x6d, 0x4d, 0x4d, "m", KNONE }, // ASCII { 0x6e, 0x4e, 0x4e, "n", KNONE }, // ASCII { 0x6f, 0x4f, 0x4f, "o", KNONE }, // ASCII { 0x70, 0x50, 0x50, "p", KNONE }, // ASCII { 0x71, 0x51, 0x51, "q", KNONE }, // ASCII { 0x72, 0x52, 0x52, "r", KNONE }, // ASCII { 0x73, 0x53, 0x53, "s", KNONE }, // ASCII { 0x74, 0x54, 0x54, "t", KNONE }, // ASCII { 0x75, 0x55, 0x55, "u", KNONE }, // ASCII { 0x76, 0x56, 0x56, "v", KNONE }, // ASCII { 0x77, 0x57, 0x57, "w", KNONE }, // ASCII { 0x78, 0x58, 0x58, "x", KNONE }, // ASCII { 0x79, 0x59, 0x59, "y", KNONE }, // ASCII { 0x7a, 0x5a, 0x5a, "z", KNONE }, // ASCII { 0x7b, 0x7b, 0xdb, "{", KSHIFT }, // BraceLeft, VK_OEM_4 { 0x7c, 0x7c, 0xdc, "|", KSHIFT }, // Bar, VK_OEM_5 { 0x7d, 0x7d, 0xdd, "}", KSHIFT }, // BraceRight, VK_OEM_6 { 0x7e, 0x7e, 0xc0, "~", KSHIFT }, // AsciiTilde, Shift-VK_OEM_3 { 0x7f, 0x7f, 0x7f, "DEL", KNONE }, /* * Block moved from above to here. * * Ordinal Key Event Virt Key Name Modifier */ { 0x80, 0x01000000, 0x1b, "Esc", KNONE }, // VK_ESCAPE again { 0x81, 0x01000001, 0x09, "Tab", KNONE }, // avoid, moves focus { 0x82, 0x01000002, 0x09, "BkTab", KSHIFT }, // avoid, moves focus { 0x83, 0x01000003, 0x08, "BkSpace", KNONE }, // differs from Ctrl-H ! VK_BACK again { 0x84, 0x01000004, 0x0d, "Return", KNONE }, // VK_RETURN again { 0x85, 0x01000005, 0xff8d, "Enter", KEYPAD }, // Keypad-Enter { 0x86, 0x01000006, 0x2d, "Ins", KNONE }, // VK_INSERT { 0x87, 0x01000007, 0x2e, "Del", KNONE }, // VK_DELETE { 0x88, 0x88, 0x13, "0x88", KNONE }, // was "Pause", duplicate, VK_PAUSE { 0x89, 0x89, 0x89, "0x89", KNONE }, // was "Print", duplicate { 0x8a, 0x0100000a, 0x8a, "SysReq", KNONE }, { 0x8b, 0x0100000b, 0x8b, "Clear", KNONE }, { 0x8c, 0x0100000c, 0x8c, "0x8c", KNONE }, { 0x8d, 0x0100000d, 0x8d, "0x8d", KNONE }, { 0x8e, 0x0100000e, 0x8e, "0x8e", KNONE }, { 0x8f, 0x0100000f, 0x8f, "0x8f", KNONE }, { 0x90, 0x01000010, 0x24, "Home", KNONE }, // VK_HOME { 0x91, 0x01000011, 0x23, "End", KNONE }, // VK_END { 0x92, 0x01000012, 0x25, "Left", KNONE }, // VK_LEFT { 0x93, 0x01000013, 0x26, "Up", KNONE }, // VK_UP { 0x94, 0x01000014, 0x27, "Right", KNONE }, // VK_RIGHT { 0x95, 0x01000015, 0x28, "Down", KNONE }, // VK_DOWN { 0x96, 0x01000016, 0x21, "PageUp", KNONE }, // VK_PRIOR { 0x97, 0x01000017, 0x22, "PageDn", KNONE }, // VK_NEXT /* * See starting around 0xd7 for the Right versions of these keys. * * Ordinal Key Event Virt Key Name Modifier * { 0x98, 0x01000020, 0x10, "Shift_L", KSHIFT }, // Left-Shift, VK_SHIFT { 0x99, 0x01000021, 0x11, "Ctrl_L", KCTRL }, // Left-Ctrl, VK_CONTROL . . . { 0x9b, 0x01000023, 0x12, "Alt_L", KALT }, // Left-Alt, VK_MENU * */ { 0x98, 0x01000020, 0xa0, "Shift_L", KSHIFT }, // Left-Shift, VK_LSHIFT { 0x99, 0x01000021, 0xa3, "Ctrl_L", KCTRL }, // Left-Ctrl, VK_LCONTROL { 0x9a, 0x01000022, 0x9a, "Meta", KMETA }, { 0x9b, 0x01000023, 0xa4, "Alt_L", KALT }, // Left-Alt, VK_LMENU { 0x9c, 0x01000024, 0x14, "CapsLk", KNONE }, // Shift-Lock, VK_CAPITAL { 0x9d, 0x01000025, 0x90, "NumLk", KNONE }, // VK_NUMLOCK { 0x9e, 0x01000026, 0x90, "ScrlLk", KNONE }, // VK_SCROLL { 0x9f, 0x01000027, 0x9f, "0x9f", KNONE }, { 0xa0, 0x01000030, 0x70, "F1", KNONE }, // VK_F1 { 0xa1, 0x01000031, 0x71, "F2", KNONE }, // VK_F2 { 0xa2, 0x01000032, 0x72, "F3", KNONE }, // VK_F3 { 0xa3, 0x01000033, 0x73, "F4", KNONE }, // VK_F4 { 0xa4, 0x01000034, 0x74, "F5", KNONE }, // VK_F5 { 0xa5, 0x01000035, 0x75, "F6", KNONE }, // VK_F6 { 0xa6, 0x01000036, 0x76, "F7", KNONE }, // VK_F7 { 0xa7, 0x01000037, 0x77, "F8", KNONE }, // VK_F8 { 0xa8, 0x01000038, 0x78, "F9", KNONE }, // VK_F9 { 0xa9, 0x01000039, 0x79, "F10", KNONE }, // VK_F10 { 0xaa, 0x0100003a, 0x7a, "F11", KNONE }, // VK_F11 { 0xab, 0x0100003b, 0x7b, "F12", KNONE }, // VK_F12 { 0xac, 0x01000053, 0x5b, "Super_L", KNONE }, // Left-Windows, VK_LWIN { 0xad, 0x01000054, 0x5c, "Super_R", KNONE }, // Right-Windows, VK_RWIN { 0xae, 0x01000055, 0x5d, "Menu", KNONE }, // Win-Menu key, VK_APPS ? { 0xaf, 0x01000056, 0xaf, "Hyper_L", KNONE }, // ??? { 0xb0, 0x01000057, 0xb0, "Hyper_R", KNONE }, // ??? { 0xb1, 0x01000058, 0x2f, "Help", KNONE }, // VK_HELP { 0xb2, 0x01000059, 0xb2, "Dir_L", KNONE }, { 0xb3, 0x01000060, 0xb3, "Dir_R", KNONE }, // Direction_R { 0xb4, 0x01000030, 0x7c, "Sh_F1", KSHIFT }, // VK_F13 { 0xb5, 0x01000031, 0x7d, "Sh_F2", KSHIFT }, // VK_F14 { 0xb6, 0x01000032, 0x7e, "Sh_F3", KSHIFT }, // VK_F15 { 0xb7, 0x01000033, 0x7f, "Sh_F4", KSHIFT }, // VK_F16 { 0xb8, 0x01000034, 0x80, "Sh_F5", KSHIFT }, // VK_F17 { 0xb9, 0x01000035, 0x81, "Sh_F6", KSHIFT }, // VK_F18 { 0xba, 0x01000036, 0x82, "Sh_F7", KSHIFT }, // VK_F19 { 0xbb, 0x01000037, 0x83, "Sh_F8", KSHIFT }, // VK_F20 { 0xbc, 0x01000038, 0x84, "Sh_F9", KSHIFT }, // VK_F21 { 0xbd, 0x01000039, 0x85, "Sh_F10", KSHIFT }, // VK_F22 { 0xbe, 0x0100003a, 0x86, "Sh_F11", KSHIFT }, // VK_F23 { 0xbf, 0x0100003b, 0x87, "Sh_F12", KSHIFT }, // VK_F24 /* * Keys missing: KP_0 to KP_9, accessible with NumLock on. * { 0x30, 0x30, 0xffb0, "KP_0", KEYPAD }, // VK_NUMPAD0 { 0x31, 0x31, 0xffb1, "KP_1", KEYPAD }, // VK_NUMPAD1 . . . . . . . . . { 0x39, 0x39, 0xffb9, "KP_9", KEYPAD }, // VK_NUMPAD9 * */ { 0xc0, 0x01000006, 0xff9e, "KP_Ins", KEYPAD }, { 0xc1, 0x01000007, 0xff9f, "KP_Del", KEYPAD }, { 0xc2, 0x01000008, 0x13, "Pause", KSHIFT }, // VK_PAUSE again { 0xc3, 0x01000009, 0x2a, "Print", KSHIFT }, // VK_PRINT { 0xc4, 0x01000010, 0xff95, "KP_Home", KEYPAD }, { 0xc5, 0x01000011, 0xff9c, "KP_End", KEYPAD }, { 0xc6, 0x01000012, 0xff96, "KP_Left", KEYPAD }, { 0xc7, 0x01000013, 0xff97, "KP_Up", KEYPAD }, { 0xc8, 0x01000014, 0xff98, "KP_Right", KEYPAD }, { 0xc9, 0x01000015, 0xff99, "KP_Down", KEYPAD }, { 0xca, 0x01000016, 0xff9a, "KP_PageUp", KEYPAD }, { 0xcb, 0x01000017, 0xff9b, "KP_PageDn", KEYPAD }, { 0xcc, 0x01000099, 0xff9d, "KP_Begin", KNONE }, // KP_Begin { 0xcd, 0x01000099, 0xcd, "0xcd", KNONE }, { 0xce, 0x01000099, 0xce, "0xce", KNONE }, { 0xcf, 0x01000099, 0xcf, "0xcf", KNONE }, { 0xd0, 0x2a, 0x6a, "KP_*", KEYPAD }, // Asterisk, VK_MULTIPLY { 0xd1, 0x2b, 0x6b, "KP_+", KEYPAD }, // Plus, VK_ADD { 0xd2, 0x2c, 0x6c, "KP_,", KEYPAD }, // Comma, VK_SEPARATOr { 0xd3, 0x2d, 0x6d, "KP_-", KEYPAD }, // Minus, VK_SUBTRACT { 0xd4, 0x2e, 0x6e, "KP_.", KPADSHIFT }, // Period, VK_DECIMAL { 0xd5, 0x2f, 0x6f, "KP_/", KEYPAD }, // Slash, VK_DIVIDE /* * Remainders. Provides the Right version and key-release versions * of some keys. Keys not yet covered: * * Alt and Alt_R releases. */ { 0xd6, 0x01000099, 0xd6, "0xd6", KNONE }, // available { 0xd7, 0x01000020, 0xa1, "Shift_R", KSHIFT }, // Right-Shift, VK_RSHIFT { 0xd8, 0x01000021, 0xa3, "Ctrl_R", KCTRL }, // Right-Ctrl, VK_RCONTROL { 0xd9, 0x2e, 0xffae, "KP_.", KEYPAD }, // KP_Decimal release { 0xda, 0x01000023, 0xa4, "Alt_R", KGROUP }, // Right-Alt, VK_RMENU { 0xdb, 0x01000020, 0xffe1, "Shift_Lr", KNONE }, // L-Shift release { 0xdc, 0x01000020, 0xffe2, "Shift_Rr", KNONE }, // R-Shift release { 0xdd, 0x01000021, 0xffe3, "Ctrl_Lr", KNONE }, // L-Ctrl release { 0xde, 0x01000021, 0xffe4, "Ctrl_Rr", KNONE }, // R-Ctrl release { 0xdf, 0x01000099, 0xdf, "Quit", KNONE }, // fake key, MIDI control only /* * This section is currently useful to fill in for future expansion or * for extended ASCII characters. See setup_qt_azerty_fr_keys(). */ { 0xe0, 0x01000099, 0xe0, "0xe0", KNONE }, { 0xe1, 0x01000099, 0xe1, "0xe1", KNONE }, { 0xe2, 0x01000099, 0xe2, "0xe2", KNONE }, { 0xe3, 0x01000099, 0xe3, "0xe3", KNONE }, { 0xe4, 0x01000099, 0xe4, "0xe4", KNONE }, { 0xe5, 0x01000099, 0xe5, "0xe5", KNONE }, { 0xe6, 0x01000099, 0xe6, "0xe6", KNONE }, { 0xe7, 0x01000099, 0xe7, "0xe7", KNONE }, { 0xe8, 0x01000099, 0xe8, "0xe8", KNONE }, { 0xe9, 0x01000099, 0xe9, "0xe9", KNONE }, { 0xea, 0x01000099, 0xea, "0xea", KNONE }, { 0xeb, 0x01000099, 0xeb, "0xeb", KNONE }, { 0xec, 0x01000099, 0xec, "0xec", KNONE }, { 0xed, 0x01000099, 0xed, "0xed", KNONE }, { 0xee, 0x01000099, 0xee, "0xee", KNONE }, { 0xef, 0x01000099, 0xef, "0xef", KNONE }, { 0xf0, 0x01000099, 0xf0, "0xf0", KNONE }, { 0xf1, 0x01000099, 0xf1, "0xf1", KNONE }, { 0xf2, 0x01000099, 0xf2, "0xf2", KNONE }, { 0xf3, 0x01000099, 0xf3, "0xf3", KNONE }, { 0xf4, 0x01000099, 0xf4, "0xf4", KNONE }, { 0xf5, 0x01000099, 0xf5, "0xf5", KNONE }, { 0xf6, 0x01000099, 0xf6, "0xf6", KNONE }, { 0xf7, 0x01000099, 0xf7, "0xf7", KNONE }, { 0xf8, 0x01000099, 0xf8, "0xf8", KNONE }, { 0xf9, 0x01000099, 0xf9, "0xf9", KNONE }, { 0xfa, 0x01000099, 0xfa, "0xfa", KNONE }, { 0xfb, 0x01000099, 0xfb, "0xfb", KNONE }, { 0xfc, 0x01000099, 0xfc, "0xfc", KNONE }, { 0xfd, 0x01000099, 0xfd, "0xfd", KNONE }, { 0xfe, 0x01000099, 0xfe, "0xfe", KNONE }, { 0xff, 0xffffffff, 0xff, "Null_ff", KNONE } // end-of-list }; if (i < 0 || i > 0xff) i = 0; return s_qt_keys[i]; } /** * Extended keys, running on system locale with French (fr) AZERTY keymap, * for Windows. * * Just call this function once. */ static void setup_qt_azerty_fr_keys () { using namespace keyboard; static const qt_keycodes s_fr_keys [] = { /* * Code Qt Qt Key * Ordinal Evkey Virtkey Name Modifier */ { 0x21, 0x21, 0x31, "!", KNONE }, // Exclam { 0x22, 0x22, 0xde, "\"", KNONE }, // QuoteDbl { 0x23, 0x23, 0x33, "#", KALTGR }, // NumberSign { 0x26, 0x26, 0x37, "&", KNONE }, // Ampersand { 0x27, 0x27, 0xde, "'", KNONE }, // Apostrophe { 0x28, 0x28, 0x39, "(", KNONE }, // ParenLeft { 0x29, 0x29, 0x30, ")", KNONE }, // ParenRight { 0x2a, 0x2a, 0x38, "*", KNONE }, // Asterisk { 0x2e, 0x2e, 0xbe, ".", KSHIFT }, // Period { 0x2f, 0x2f, 0xbf, "/", KSHIFT }, // Slash { 0x3a, 0x3a, 0xba, ":", KNONE }, // Colon { 0x3c, 0x3c, 0xbc, "<", KNONE }, { 0x40, 0x40, 0x32, "@", KALTGR }, // AtSign { 0x5b, 0x5b, 0xdb, "[", KALTGR }, // BracketLeft { 0x5c, 0x5c, 0xdc, "\\", KALTGR }, // Backslash { 0x5d, 0x5d, 0xdd, "]", KALTGR }, // BracketRight { 0x5e, 0x5e, 0x36, "^", KALTGR }, // AsciiCircumflex { 0x5f, 0x5f, 0xbd, "_", KNONE }, // Underscore { 0x60, 0x60, 0xc0, "`", KALTGR }, // QuoteLeft, Backtick { 0x7b, 0x7b, 0xdb, "{", KALTGR }, // BraceLeft { 0x7c, 0x7c, 0xdc, "|", KALTGR }, // Bar { 0x7d, 0x7d, 0xdd, "}", KALTGR }, // BraceRight { 0x7e, 0x7e, 0xc0, "~", KALTGR }, // Tilde (dead key) { 0xe0, 0xa3, 0x73, "L_pound", KNONE }, // £ <--F4 { 0xe1, 0xa4, 0x74, "Currency", KALTGR }, // ¤ <--F5 { 0xe2, 0xa7, 0x77, "Silcrow", KSHIFT }, // § <--F8 { 0xe3, 0xb0, 0xb0, "Degrees", KSHIFT }, // ° <--Hyper_R ??? { 0xe4, 0x01000022, 0xb2, "Super_2", KMETA }, // ² <--Dir_L press ??? { 0xe5, 0xc0, 0x2d, "a_grave", KNONE }, // à <--KP_Ins ??? { 0xe6, 0xc7, 0x26, "c_cedilla", KNONE }, // ç <--KP_Up { 0xe7, 0xc8, 0x27, "e_grave", KNONE }, // è <--KP_Right { 0xe8, 0xc9, 0x28, "e_acute", KNONE }, // é <--KP_Down { 0xe9, 0xd9, 0x5d, "u_grave", KNONE }, // ù <--Super/Mod4/Win { 0xea, 0x039c, 0xb5, "Mu", KSHIFT }, // µ <--(new) { 0xeb, 0x20ac, 0xb6, "Euro", KALTGR }, // € <--(new) { 0xec, 0x1001252, 0x36, "Circflex", KNONE }, // ^ <--Caret { 0xed, 0x1001257, 0xde, "Umlaut", KSHIFT }, // ¨ <--Diaeresis { 0xee, 0x01000022, 0xb2, "Super_2r", KNONE }, // ² <--Dir_L release { 0x00, 0xffffffff, 0xff, "??", KNONE } // terminator }; for (int i = 0; s_fr_keys[i].qtk_keyevent != 0xffffffff; ++i) { int index = s_fr_keys[i].qtk_ordinal; // not qtk_keyevent #if defined SEQ66_PLATFORM_DEBUG_TMI printf ( "Key #%03d '%s': Ord %02x; code %02x-->", i, qt_keys(index).qtk_keyname.c_str(), qt_keys(index).qtk_ordinal, qt_keys(index).qtk_keyevent ); #endif qt_keys(index) = s_fr_keys[i]; #if defined SEQ66_PLATFORM_DEBUG_TMI printf ( "Key #%03d '%s': Ord %02x; code %02x\n", i, qt_keys(index).qtk_keyname.c_str(), qt_keys(index).qtk_ordinal, qt_keys(index).qtk_keyevent ); #endif } } #endif // SEQ66_WINKEYS_HPP /* * winkeys.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/businfo.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file businfo.cpp * * This module declares/defines the base class for handling MIDI I/O via * the ALSA system. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-12-31 * \updates 2023-09-20 * \license GNU GPLv2 or above * * This file provides a base-class implementation for various master MIDI * buss support classes. There is a lot of common code between these MIDI * buss classes. * * Bus Info: * * -# For each MIDI I/O port, a new midibus object and pointer is created * in one of the following seq_rtmidi functions: * - midi_alsa_info::api_port_start() * - mastermidibus::api_init() * Both function pass in a mastermidibus object as well. * -# In busarray::add (), this pointer is passed to the constructor of * businfo. There, it is managed by an std::shared<> pointer. The * local pointer in step 1 goes out of scope. * -# This copy of businfo is init'ed and then pushed onto the busarray's * businfo container. */ #include "cfg/settings.hpp" /* seq66::rc() and seq66::usr() */ #include "midi/businfo.hpp" /* seq66::businfo class */ #include "midi/event.hpp" /* seq66::event class */ namespace seq66 { /* * ------------------------------------------------------------------------- * class businfo * ------------------------------------------------------------------------- */ /** * Principal constructor. * * is_input_port(): * * Indicates if the midibus represents an input port (true) versus an * output port (false). * * is_virtual_port(): * * Indicates if the midibus represents a virtual port (true) versus a * normal port (false). * * \param bus * Provides a pointer to the MIDI buss object to be represented by this * object. */ businfo::businfo (midibus * bus) : m_bus (), /* this is an std::shared_ptr<> */ m_active (false), m_initialized (false), m_init_clock (e_clock::none), /* could end up disabled as well */ m_init_input (false) { m_bus.reset(bus); /* also see initialize() */ } /** * Copy constructor. * * \param rhs * The source object to be copied. */ businfo::businfo (const businfo & rhs) : m_bus (rhs.m_bus), m_active (rhs.m_active), m_initialized (rhs.m_initialized), m_init_clock (rhs.m_init_clock), m_init_input (rhs.m_init_input) { // no other code needed } /** * This function is called when the businfo object is added to the busarray. * It relies on the performer::launch() function to actually activate() all of * the ports that have been flagged as "activated" here. * * is_input_port(): * * Indicates if the midibus represents an input port (true) versus an * output port (false). The way the mastermidibus currently works, it * creates the API MIDI input objects there, so it does not need to be * done here. This falls under the heading of "tricky code". * * is_virtual_port(): * * Indicates if the midibus represents a manual/virtual port (true) * versus a normal port (false). * * The rules for port initialization follow those of seq66 for MIDI busses: * * - Manual (virtual) input and output ports always get their init * functions called. They are unconditionally marked as "active" * and "initialized". * - Normal output ports are marked as "active" and "initialized" if * init_out() succeeds. * - Normal input ports don't have init_in() called, but are marked * as "active" and "initialized" anyway. The settings from the "rc" * file determine which inputs will operate. * * We still have a potential conflict between "active", "initialized", and * "disabled". * * "Active" is used for: enabling play(), set_clock(), get_clock(), * get_midi_bus_name(), set_input(), port_enabled(), is_system_port(), * replacement_port(). * * "Initialized" is used for: . . . * * "Disabled" is currently used to making an OS-disabled, non-openable port * a non-fatal error. * * Unavailable ports: * * In Windows, a port can be present on the system, but be unavailable * because the MIDI Mapper has taken control of it. Can a port be * unavailable for a similar reason (e.g. grabbed by another application) * in Linux? * * \return * Returns true if the buss is value, and it could be initialized (as an * output port or a virtual output port. If bus has been "disabled" * (e_clock::disabled), skip this port and return true. */ bool businfo::initialize () { bool result = not_nullptr(bus()); /* a bit too tricky now */ if (result) { result = bus()->initialize(rc().init_disabled_ports()); if (result) { activate(); /* "initialized" & "active" */ } else { #if defined SEQ66_PLATFORM_WINDOWS_DISABLED /* don't allow it1!! */ result = true; /* allow it for Windoze */ #endif bus()->set_port_unavailable(); /* currently permanent */ } } else { errprint("businfo(): null midibus pointer provided"); } return result; } /** * Print some information about the MIDI bus. */ void businfo::print () const { std::string flags; if (bus()->is_virtual_port()) flags += " virtual"; else if (bus()->is_system_port()) flags += " system"; else flags += " normal"; if (bus()->is_input_port()) flags += " input"; else flags += " output"; if (active()) flags += " active"; else flags += bus()->port_unavailable() ? "unavailable" : " inactive" ; if (initialized()) flags += " initialized"; else flags += " uninitialized"; if (bus()->is_input_port()) { flags += " "; flags += init_input() ? "inputting" : "not inputting" ; } else { flags += " clock "; if (init_clock() == e_clock::none) flags += "Off"; else if (init_clock() == e_clock::pos) flags += "Pos"; else if (init_clock() == e_clock::mod) flags += "Mod"; else if (init_clock() == e_clock::disabled) flags += "Disabled"; else flags += "illegal!"; } printf ( " %s:%s %s\n", bus()->bus_name().c_str(), bus()->port_name().c_str(), flags.c_str() ); } /* * ------------------------------------------------------------------------- * class busarray * ------------------------------------------------------------------------- */ /** * A new class to hold a vector of MIDI busses and flags for more controlled * access than using arrays of booleans and pointers. */ busarray::busarray () : m_container() { // Empty body } /** * Removes components from the container. * * \question * However, now that we swap containers, we cannot call this functionality, * because it deletes the bus's midibus pointer and nullifies it. * But we do call it, and it seems to work. */ busarray::~busarray () { for (auto & bi : m_container) /* vector of businfo copies */ bi.remove(); /* deletes the businfo's midibus */ } /** * Creates and adds a new midibus object to the list. Then the clock value * is set. This function is meant for output ports. * * We need to belay the initialization until later, when we know the * configured clock settings for the output ports. So initialization has * been removed from the constructor and moved to the initialize() function. * * \param bus * The midibus to be hooked into the array of busses. * * \param clock * The clocking value for the bus. * * \return * Returns true if the bus was added successfully, though, really, it * cannot fail. */ bool busarray::add (midibus * bus, e_clock clock) { bool result = not_nullptr(bus); if (result) { size_t count = m_container.size(); businfo b(bus); b.init_clock(clock); m_container.push_back(b); /* creates a copy */ result = m_container.size() == (count + 1); } return result; } /** * Adds a new midibus object to the list. Then the inputing value * is set. This function is meant for input ports. * * We need to belay the initialization until later, when we know the * configured inputing settings for the input ports. So initialization has * been removed from the constructor and moved to the initialize() function. * However, now we know the configured status and can apply it right away. * * \param bus * The midibus to be hooked into the array of busses. * * \param inputing * The input flag value for the bus. If true, this value indicates that * the user has selected this bus to be the input MIDI bus. * * \return * Returns true if the bus was added successfully, though, really, it * cannot fail. */ bool busarray::add (midibus * bus, bool inputing) { bool result = not_nullptr(bus); if (result) { size_t count = m_container.size(); businfo b(bus); b.init_input(inputing); /* sets the flag, important */ m_container.push_back(b); /* now we can push a copy */ result = m_container.size() == (count + 1); } return result; } /** * Initializes all busses. Not sure we need this function. * * \return * Returns true if all busses initialized successfully. It currently keeps * going even after a failure, though. */ bool busarray::initialize () { bool result = true; for (auto & bi : m_container) /* vector of businfo copies */ { if (! bi.initialize()) result = false; } return result; } /** * Plays an event, if the bus is proper. * * \param bus * The MIDI buss on which to play the event. The buss number must be * valid (in range) and the bus must be active. * * \param e24 * A pointer to the event to be played. Currently we don't bother to check * it! * * \param channel * The MIDI channel on which to play the event. Seq66 controls * the actual channel of playback, no matter what the channel specified * in the event. */ void busarray::play (bussbyte bus, const event * e24, midibyte channel) { if (bus < count() && m_container[bus].active()) m_container[bus].bus()->play(e24, channel); } /** * Handles SysEx events; used for output busses. * * \param ev * Provides the SysEx event to handle. */ void busarray::sysex (bussbyte bus, const event * e24) { if (bus < count() && m_container[bus].active()) m_container[bus].bus()->sysex(e24); } /** * Sets the clock type for the given bus, usually the output buss. * This code is a bit more restrictive than the original code in * mastermidibus::set_clock(). * * Getting the current clock setting is essentially equivalent to: * * m_container[bus].bus()->get_clock(); * * ca 2023-05-18 * * The check for a change in status is commented out because it * can disable setting values for the same item as stored in the portmap. * Need to investigate this at some point. * * \param bus * The MIDI bus for which the clock is to be set. * * \param clocktype * Provides the type of clocking for the buss. * * \return * Returns true if the change was made. It is made only if needed. */ bool busarray::set_clock (bussbyte bus, e_clock clocktype) { e_clock current = get_clock(bus); bool result = bus < count(); if (result) { businfo & bi = m_container[bus]; result = bi.active() || current == e_clock::disabled; if (result) bi.init_clock(clocktype); /* also handles set_clock() */ } return result; } /** * Gets the clock type for the given bus, usually the output buss. * * \param bus * The MIDI bus for which the clock is to be set. * * \return * Returns the clock value set for the desired buss. If the buss is * invalid, e_clock::none is returned. If the buss is not active, we * still get the existing clock value. The theory here is that we don't * want to junk the current clock value; it could alter what was read * from the "rc" file. */ e_clock busarray::get_clock (bussbyte bus) const { if (bus < count()) { const businfo & bi = m_container[bus]; return bi.bus()->get_clock(); } else return e_clock::none; } /** * Get the MIDI output buss name (i.e. the full display name) for the given * (legal) buss number. * * This function adds the retrieval of client and port numbers that are not * needed in the portmidi implementation, but seem generally useful to * support in all implementations. It's main use is to display the * full portname in one of two forms: * * - "[0] 0:0 clientname:portname" * - "[0] 0:0 portname" * * The second version is chosen if "clientname" is already included in the * port name, as many MIDI clients do that. However, the name gets * modified to reflect the remote system port to which it will connect. * * \param bus * Provides the output buss number. Checked before usage. * Actually should now be an index number * * \return * Returns the buss name as a standard C++ string, truncated to 80-1 * characters. Also contains an indication that the buss is disconnected * or unconnected. If the buss number is illegal, this string is empty. */ std::string busarray::get_midi_bus_name (int bus) const { std::string result; if (bus < count()) { const businfo & bi = m_container[bus]; const midibus * buss = bi.bus(); e_clock current = buss->get_clock(); if (bi.active() || current == e_clock::disabled) { std::string busname = buss->bus_name(); std::string portname = buss->port_name(); std::size_t len = busname.size(); int test = busname.compare(0, len, portname, 0, len); if (test == 0) { char tmp[80]; snprintf ( tmp, sizeof tmp, "[%d] %d:%d %s", bus, buss->bus_id(), buss->port_id(), portname.c_str() ); result = tmp; } else result = buss->display_name(); } else { /* * The display name gets saved, and so must be used unaltered * here. */ result = buss->display_name(); } } return result; } /** * The function gets the port name. We're trying to keep our own client name * (normally "seq66") out of the 'rc' input and clock sections. */ std::string busarray::get_midi_port_name (int bus) const { std::string result; if (bus < count()) { const businfo & bi = m_container[bus]; const midibus * buss = bi.bus(); result = buss->port_name(); } return result; } /** * This function gets only the alias name of the port, if it exists. * Some (later) versions of JACK support getting the alias in the manner * of "a2jmidid --export-hw", which can be used to use the device's model name * rather that some generic name. */ std::string busarray::get_midi_alias (int bus) const { std::string result; if (bus < count()) { const businfo & bi = m_container[bus]; const midibus * buss = bi.bus(); result = buss->port_alias(); } return result; } /** * Print some information about the available MIDI input or output busses. */ void busarray::print () const { printf("Available busses:\n"); for (const auto & bi : m_container) /* vector of businfo copies */ bi.print(); } /** * Turn off the given port for the given client. Both the busses for the given * client are stopped: that is, set to inactive. * * This function is called by api_get_midi_event() when the ALSA event * SND_SEQ_EVENT_PORT_EXIT is received. Since port_exit() has no direct * API-specific code in it, we do not need to create a virtual * api_port_exit() function to implement the port-exit event. * * \param client * The client to be matched and acted on. This value is actually an ALSA * concept. * * \param port * The port to be acted on. Both parameter must be matched before the * buss is made inactive. This value is actually an ALSA concept. */ void busarray::port_exit (int client, int port) { for (auto & bi : m_container) /* vector of businfo copies */ { if (bi.bus()->match(client, port)) bi.deactivate(); } } /** * Set the status of the given input buss, if a legal buss number. There's * currently no implementation-specific API function called directly here. * What happens is that midibase::set_input() uses the \a inputing parameter * to decide whether to call init_in() or deinit_in(), and these functions * ultimately lead to an API specific called. * * Note that the call to midibase::set_input() will set its m_inputing flag, * and then call init_in() or deinit_in() if that flag changed. This change * is important, so we have to call midibase::set_input() first. Then the * call to businfo::init_input() will set that flag again (plus another * flag). A bit confusing in sequence and in function naming. * * This function should be used only for the input busarray, obviously. * * ca 2023-05-18 * * The check for a change in status is commented out because it * can disable setting values for the same item as stored in the portmap. * Need to investigate this at some point. * * \threadsafe * * \param bus * Provides the buss number. * * \param inputing * True if the input bus will be inputting MIDI data. * * \return * Returns true if the buss number is valid and was active, and so could * be set. */ bool busarray::set_input (bussbyte bus, bool inputing) { bool current = get_input(bus); /* see below */ bool result = bus < count(); if (result) { businfo & bi = m_container[bus]; /* * The init_input() call here first sets the m_init_input flag in * businfor. Then it sets the I/O status flag of the midibus. It * does this only if the businfo is active (i.e. initialized) and the * status has changed. */ result = bi.active() || ! current; if (result) bi.init_input(inputing); } return result; } /** * Get the input for the given (legal) buss number. * * There's currently no implementation-specific API function here. * * \param bus * Provides the buss number. * * \return * If the buss is a system buss, always returns true. Otherwise, if the * buss is inactive, returns false. Otherwise, the buss's port_enabled() * status is returned. */ bool busarray::get_input (bussbyte bus) const { bool result = false; if (bus < count()) { const businfo & bi = m_container[bus]; if (bi.active()) result = bi.bus()->is_system_port() ? true : bi.bus()->port_enabled(); } return result; } /** * Get the system-port status for the given (legal) buss number. * * \param bus * Provides the buss number. * * \return * Returns the selected buss's is-system-port status. If the buss number * is out of range, then false is returned. */ bool busarray::is_system_port (bussbyte bus) const { bool result = false; if (bus < count()) { const businfo & bi = m_container[bus]; if (bi.active()) result = bi.bus()->is_system_port(); } return result; } bool busarray::is_port_unavailable (bussbyte bus) const { bool result = true; if (bus < count()) { const businfo & bi = m_container[bus]; result = bi.bus()->port_unavailable(); } return result; } /** * Modified to account for the fact that if a port has not been * added to the array, it cannot be locked. This check is made by * comparing the bus to the count(). * * \param bus * The buss/port number. */ bool busarray::is_port_locked (bussbyte bus) const { bool result = false; if (bus < count()) { const businfo & bi = m_container[bus]; result = bi.bus()->is_port_locked(); } return result; } /** * Initiate a poll() on the existing poll descriptors. This is a primitive * poll, which exits when some data is obtained. It also applies only to the * input busses. * * One issue is that we have no way of knowing here which MIDI input device * has MIDI input events waiting. Should we randomize the order of polling * in order to avoid starving some input devices? * * \return * Returns the number of MIDI events detected on one of the busses. Note * that this is no longer a boolean value. */ int busarray::poll_for_midi () { int result = 0; for (auto & bi : m_container) /* vector of businfo copies */ { result = bi.bus()->poll_for_midi(); /* works if I/O active */ if (result > 0) break; } return result; } /** * Gets the first MIDI event in finds on an input bus. * * Note that this function risks starving the second input device if more * than one is enabled in Seq66. We will figure that one out later. * * \param inev * A pointer to the event to be modified by incoming data, if any. * * \return * Returns true if an event's data was copied into the event pointer. */ bool busarray::get_midi_event (event * inev) { for (auto & bi : m_container) /* vector of businfo copies */ { if (bi.bus()->get_midi_event(inev)) { bussbyte b = bussbyte(bi.bus()->bus_index()); inev->set_input_bus(b); #if defined SEQ66_PLATFORM_DEBUG_TMI printf("[seq66] input event on bus %d\n", int(b)); #endif return true; } } return false; } /** * Provides a function to use in api_port_start(), to determine if the port * is to be a "replacement" port. This function is meant only for the output * buss (so far). * * Still need to determine exactly what this function needs to do. * * \param bus * The buss to be affected. * * \param port * The prot to be affected. * * \return * Returns -1 if no matching port is found, otherwise it returns the * replacement-port number. */ int busarray::replacement_port (int bus, int port) { int result = -1; int counter = 0; for (auto bi = m_container.begin(); bi != m_container.end(); ++bi) { if (bi->bus()->match(bus, port) && ! bi->active()) { result = counter; if (bool(bi->bus())) { (void) m_container.erase(bi); /* deletes m_bus as well */ errprintf("port_start(): bus out %d not null\n", result); } break; } ++counter; } return result; } /** * This free function swaps the contents of two busarray objects. * * \param buses0 * Provides the first buss in the swap. * * \param buses1 * Provides the second buss in the swap. */ void swap (busarray & buses0, busarray & buses1) { busarray temp = buses0; buses0 = buses1; buses1 = temp; } } // namespace seq66 /* * businfo.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/calculations.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file calculations.cpp * * This module declares/defines some utility functions and calculations * needed by this application. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-11-07 * \updates 2025-07-24 * \license GNU GPLv2 or above * * This code was moved from the globals module so that other modules * can include it only if they need it. * * To convert the ticks for each MIDI note into a millisecond value to * display the notes visually along a timeline, one needs to use the division * and the tempo to determine the value of an individual tick. That * conversion looks like: * \verbatim 1 min 60 sec 1 beat Z clocks ------- * ------ * -------- * -------- = seconds X beats 1 min Y clocks 1 \endverbatim * * X is the tempo (beats per minute, or BPM), Y is the division (pulses per * quarter note, PPQN), and Z is the number of clocks from the incoming * event. All of the units cancel out, yielding a value in seconds. The * condensed version of that conversion is therefore: * \verbatim (60 * Z) / (X * Y) = seconds seconds = 60 * clocks / (bpm * ppqn) \endverbatim * * The value given here in seconds is the number of seconds since the * previous MIDI event, not from the sequence start. One needs to keep a * running total of this value to construct a coherent sequence. Especially * important if the MIDI file contains tempo changes. Leaving the clocks (Z) * out of the equation yields the periodicity of the clock. * * The inverse calculation is: * \verbatim clocks = seconds * bpm * ppqn / 60 \endverbatim * * \todo * There are additional user-interface and MIDI scaling variables in the * perfroll module that we need to move here. */ #include /* std::isspace(), std::isdigit() */ #include /* std::floor(), std::log() */ #include /* std::atoi(), std::strtol() */ #include /* std::memset() */ #include /* std::strftime() */ #include "cfg/settings.hpp" /* seq66::usr() */ #include "midi/calculations.hpp" /* MIDI-related calculations */ #include "util/strfunctions.hpp" /* seq66::contains(), etc. */ #if defined SEQ66_USE_UNIFORM_INT_DISTRIBUTION #include /* std::uniform_int_distribution */ #endif namespace seq66 { /** * This value represents the fundamental and default beats-per-bar. */ static const int c_qn_beats = 4; /** * Convenience function. We don't want to use seq66::string_to_int() * because that uses a leading "0" or "0x" to determine the base of the * conversions. */ static int strtoi (const std::string & v) { return v.empty() ? 0 : std::atoi(v.c_str()); } /** * Extracts up to 4 numbers from a colon-delimited string, or 1 from a * non-delimited string. Actually colon or period are used. * * - measures : beats : divisions * - "8" represents solely the number of pulses. That is, if the * user enters a single number, it is treated as the number of * pulses. * - "8:1" represents a measure and a beat. * - "213:4:920" represents a measure, a beat, and pulses. * - hours : minutes : seconds . fraction. We really don't support * this concept at present. Beware! * - "2:04:12.14" * - "0:1:2" * * \warning * This is not the most efficient implementation you'll ever see. * At some point we will tighten it up. * * \param s * Provides the input time string, in measures or time format, * to be processed. * * \param [out] part_1 * The destination reference for the first part of the time. * In some contexts, this number alone is a pulse (ticks) value; * in other contexts, it is a measures value. * * \param [out] part_2 * The destination reference for the second part of the time. * * \param [out] part_3 * The destination reference for the third part of the time. * * \param [out] fraction * The destination reference for the fractional part of the time. * * \return * Returns the number of parts provided, ranging from 0 to 4. * If 0, there is an error. If 1, it is assumed to be an single number, * such as 768. */ int extract_timing_numbers ( const std::string & s, std::string & part_1, std::string & part_2, std::string & part_3, std::string & fraction ) { tokenization tokens; int count = tokenize_string(s, tokens); /* a function in this module */ part_1.clear(); part_2.clear(); part_3.clear(); fraction.clear(); if (count > 0) part_1 = tokens[0]; if (count > 1) part_2 = tokens[1]; if (count > 2) part_3 = tokens[2]; if (count > 3) fraction = tokens[3]; return count; } /** * Tokenizes a string using the colon, space, or period as delimiters. They * are treated equally, and the caller must determine what to do with the * parts. Here are the steps: * * -# Skip any delimiters found at the beginning. The position will * either exist, or there will be nothing to parse. * -# Get to the next delimiter. This will exist, or not. Get all * non-delimiters until the next delimiter or the end of the string. * -# Repeat until no more delimiters exist. * * \param source * The string to be parsed and tokenized. * * \param tokens * Provides a vector into which to push the tokens. * * \return * Returns the number of tokens pushed (i.e. the final size of the tokens * vector). */ int tokenize_string ( const std::string & source, tokenization & tokens ) { static std::string s_delims = ":. "; int result = 0; tokens.clear(); auto pos = source.find_first_not_of(s_delims); if (pos != std::string::npos) { for (;;) { auto depos = source.find_first_of(s_delims, pos); if (depos != std::string::npos) { tokens.push_back(source.substr(pos, depos - pos)); pos = source.find_first_not_of(s_delims, depos +1); if (pos == std::string::npos) break; } else { tokens.push_back(source.substr(pos)); break; } } result = int(tokens.size()); } return result; } /** * Converts MIDI pulses (also known as ticks, clocks, or divisions) into a * string. * * \param p * The MIDI pulse/tick value to be converted. * * \return * Returns the string as an unsigned ASCII integer number. */ std::string pulses_to_string (midipulse p) { char tmp[32]; snprintf(tmp, sizeof tmp, "%lu", (unsigned long)(p)); return std::string(tmp); } /** * Converts a MIDI pulse/ticks/clock value into a string that represents * "measures:beats:ticks" ("measures:beats:division"). * * \param p * The number of MIDI pulses (clocks, divisions, ticks, you name it) to * be converted. If the value is c_null_midipulse, it is converted * to 0, because callers don't generally worry about such niceties, and * the least we can do is convert illegal measure-strings (like * "000:0:000") to a legal value. * * \param seqparms * This small structure provides the beats/measure, beat-width, and PPQN * that hold for the sequence involved in this calculation. These values * are needed in the calculations. * * \return * Returns the string, in measures notation, for the absolute pulses that * mark this duration. */ std::string pulses_to_measurestring (midipulse p, const midi_timing & seqparms) { midi_measures measures; /* measures, beats, divisions */ char tmp[32]; int width = 3; if (is_null_midipulse(p)) { p = 0; /* punt the runt! */ } else if (seqparms.ppqn() >= 1000) { if (seqparms.ppqn() < 10000) width = 4; else if (seqparms.ppqn() < 100000) width = 5; } pulses_to_midi_measures(p, seqparms, measures); /* fill measures struct */ snprintf ( tmp, sizeof tmp, "%03d:%d:%0*d", /* "%03d:%d:%03d" */ measures.measures(), measures.beats(), width, measures.divisions() ); return std::string(tmp); } /** * Converts a MIDI pulse/ticks/clock value into a string that represents * "measures:beats:ticks" ("measures:beats:division"). * \verbatim m = p * W / (4 * P * B) \endverbatim * * \param p * Provides the MIDI pulses (as in "pulses per quarter note") that are to * be converted to MIDI measures format. * * \param seqparms * This small structure provides the beats/measure (B), beat-width (W), * and PPQN (P) that hold for the sequence involved in this calculation. * The beats/minute (T for tempo) value is not needed. * * \param [out] measures * Provides the current MIDI song time structure to hold the results, * which are the measures, beats, and divisions values for the time of * interest. Note that the measures and beats are corrected to be re 1, * not 0. * * \return * Returns true if the calculations were able to be made. The P, B, and * W values all need to be greater than 0. */ bool pulses_to_midi_measures ( midipulse p, const midi_timing & seqparms, midi_measures & measures ) { int W = seqparms.beat_width(); int P = seqparms.ppqn(); int B = seqparms.beats_per_measure(); bool result = (W > 0) && (P > 0) && (B > 0); if (result) { double qnotes = double(c_qn_beats) * B / W; /* Q notes per measure */ double measlength = P * qnotes; /* pulses/std measure */ int beatticks = measlength / B; /* pulses/beat */ int m = int(p / measlength) + 1; /* measure no. of pulse */ int metro = 1 + ((p * W / P / c_qn_beats ) % B); measures.measures(m); /* number of measures */ measures.beats(metro); /* beats within measure */ measures.divisions(int(p % beatticks)); /* leftover pulses */ } return result; } /** * Function used in sequence::analyze_time_signatures() to precalculate * the size of each time-signature (sequence::timesig) segment. * * Compare this function to ticks_to_measures(), which returns an * integer. Also, in measures_to_ticks() the formula is: * \verbatim p = 4 * P * M * B / W \endverbatim * * Solving for M: * \verbatim M = p * W / (4 * P * B) \endverbatim * * \param p * Provides either the time in ticks (pulses), or the duration of a * timesig segment. * * \param P * The PPQN in force for this calculation. Must be greater than 0. * * \param B * The beats/measure in force for this calculation. Must be greater * than 0. * * \param W * The beat width in force for this calculation. Must be greater * than 0. * * \return * If the parameters are valid, returns the measure count or size * as a floating-point value. The caller is responsible for any * rounding. If parameters are invalid, 0.0 is returned. */ double pulses_to_measures (midipulse p, int P, int B, int W) { double result = 0.0; /* indicates an error */ if (B > 0 && P > 0) { double divisor = double(c_qn_beats) * P * B; return double(p) * W / divisor; } return result; } /** * Converts a MIDI pulse/ticks/clock value into a string that represents * "hours:minutes:seconds.fraction". See the other pulses_to_time_string() * overload. * * \param p * Provides the number of ticks, pulses, or divisions in the MIDI * event time. * * \param timinginfo * Provides the tempo of the song, in beats/minute, and the * pulse-per-quarter-note of the song. * * \return * Returns the return-value of the other pulses_to_time_string() function. */ std::string pulses_to_time_string (midipulse p, const midi_timing & timinginfo) { return pulses_to_time_string ( p, timinginfo.beats_per_minute(), timinginfo.ppqn() ); } /** * Converts a MIDI pulse/ticks/clock value into a string that represents * "hours:minutes:seconds.fraction". If the fraction part is 0, then it is * not shown. Examples: * * - "0:0:0" * - "0:0:0.102333" * - "12:3:1" * - "12:3:1.000001" * * \param p * Provides the number of ticks, pulses, or divisions in the MIDI * event time. * * \param bpm * Provides the tempo of the song, in beats/minute. * * \param ppqn * Provides the pulses-per-quarter-note of the song. * * \param showus * If true (the default), shows the microseconds as well. Hours are now * shown only if greater than zero. * * \return * Returns the time-string representation of the pulse (ticks) value. */ std::string pulses_to_time_string (midipulse p, midibpm bpm, int ppqn, bool showus) { unsigned long microseconds = ticks_to_delta_time_us(p, bpm, ppqn); int seconds = int(microseconds / 1000000UL); int minutes = seconds / 60; int hours = seconds / (60 * 60); int hoursecs = hours * 60 * 60; int minutesecs = minutes * 60; minutes -= hours * 60; seconds -= hoursecs + minutesecs; char tmp[48]; if (showus) { microseconds -= (hoursecs + minutesecs + seconds) * 1000000UL; microseconds /= 10000L; if (hours > 0) { snprintf ( tmp, sizeof tmp, "%d:%02d:%02d.%02lu", hours, minutes, seconds, microseconds ); } else { snprintf ( tmp, sizeof tmp, "%02d:%02d.%02lu", minutes, seconds, microseconds ); } } else { /* * Why the spaces? It is inconsistent. But see the * timestring_to_pulses() function first. */ if (hours > 0) { snprintf ( tmp, sizeof tmp, "%d:%02d:%02d ", hours, minutes, seconds ); } else snprintf(tmp, sizeof tmp, "%02d:%02d ", minutes, seconds); } return std::string(tmp); } /** * A handy function for checking for long songs (an hour or more). */ int pulses_to_hours (midipulse p, midibpm bpm, int ppqn) { unsigned long microseconds = ticks_to_delta_time_us(p, bpm, ppqn); int seconds = int(microseconds / 1000000UL); return seconds / (60 * 60); } /** * Recalculates the number of measures, making sure that values less than 1.0 * become 1, and that, otherwise, the measure count is either very close * (0.01) to the lower integer number, or moved up the next integer measure * value. A static function. */ double trunc_measures (double measures) { static const double s_slop = 0.01; /* allows for a little slop */ double result; if (measures <= (1.0 + s_slop)) { result = 1.0; } else { double truncated = std::trunc(measures); if ((measures - truncated) <= s_slop) result = double(int(truncated)); else result = double(int(truncated)) + 1.0; } return result; } /** * Converts a string that represents "measures:beats:division" (also known * as "B:B:T") to a MIDI pulse/ticks/clock value. Note that, here, "division" * is simply a number of pulses less than a beat. * * If the third value (the MIDI pulses or ticks value) is set to the dollar * sign ("$"), then the pulses are set to PPQN-1, as a handy shortcut to * indicate the end of the beat. * * \warning * If only one number is provided, it is treated in this function like * a measures value, not a pulses value. * * \param measures * Provides the current MIDI song time in "measures:beats:divisions" * format, where divisions are the MIDI pulses in * "pulses-per-quarter-note". * * \param seqparms * This small structure provides the beats/measure, beat-width, and PPQN * that hold for the sequence involved in this calculation. * * \return * Returns the absolute pulses that mark this duration. If the input * string is empty, then 0 is returned. */ midipulse measurestring_to_pulses ( const std::string & measures, const midi_timing & seqparms ) { midipulse result = 0; if (! measures.empty()) { std::string m, b, d, dummy; int valuecount = extract_timing_numbers(measures, m, b, d, dummy); if (valuecount >= 1) { midi_measures meas_values; /* 0 in ctor */ meas_values.measures(strtoi(m)); if (valuecount > 1) { meas_values.beats(strtoi(b)); if (valuecount > 2) { if (d == "$") meas_values.divisions(seqparms.ppqn() - 1); else meas_values.divisions(strtoi(d)); } } result = midi_measures_to_pulses(meas_values, seqparms); } } return result; } /** * Converts a string that represents "measures:beats:division" to a MIDI * pulse/ticks/clock value. * * p = 4 * P * m * B / W * p == pulse count (ticks or pulses) * m == number of measures * B == beats per measure (constant) * P == pulses per quarter-note (constant) * W == beat width in beats per measure (constant) [NOT CORRECT] * * Note that the 0-pulse MIDI measure is "1:1:0", which means "at the * beginning of the first beat of the first measure, no pulses'. It is not * "0:0:0" as one might expect. * * If we get a 0 for measures or for beats, we * treat them as if they were 1. It is too easy for the user to mess up. * * We should consider clamping the beats to the beat-width value as well. * * Example: Current time-signature = 3/16. Then q_per_beat = 4/16 = 0.25. * For 1 measure and 3 beats, the pulses are p = 1 * 3 * 0.25 * PPQN. If PPQN * is 192, the pulses per beat are 0.25 * PPQN = 48. * * \param measures * Provides the current MIDI song time structure holding the * measures, beats, and divisions values for the time of interest. * Note that it does not employ beat-width. It is a time position. * * \param seqparms * This small structure provides the beats/minute, beats/measure, * beat-width, and PPQN that hold for the sequence in this calculation. * * \return * Returns the absolute pulses that mark this duration. If the * pulse-value cannot be calculated, then c_null_midipulse is * returned. */ midipulse midi_measures_to_pulses ( const midi_measures & measures, /* B:B:T time value */ const midi_timing & seqparms /* ppqn and beat-width */ ) { midipulse result = c_null_midipulse; int m = measures.measures() - 1; /* true measure count */ int b = measures.beats() - 1; /* true beats count */ if (m >= 0 && b >= 0) { double ppq = double(seqparms.ppqn()); double beats_per_bar = double(seqparms.beats_per_measure()); double beat_width = double(seqparms.beat_width()); double ticks_per_beat = pulses_per_beat(ppq, beat_width); double ticks_per_meas = m * ticks_per_beat * beats_per_bar; double ticks = m * ticks_per_meas; ticks += b * ticks_per_beat; result = midipulse(ticks); result += measures.divisions(); } else result = 0; return result; } /** * A new function to create a midi_measures structure from a string assumed * to have a formate of "B:B:T". Any fractional part is ignored as being * less than a pulse. */ midi_measures string_to_measures (const std::string & bbt) { std::string m; std::string b; std::string t; std::string fraction; int count = extract_timing_numbers(bbt, m, b, t, fraction); if (count > 0) { int measures = strtoi(m); int beats = strtoi(b); int ticks = strtoi(t); if (measures == 0) measures = 1; if (beats == 0) beats = 1; return midi_measures(measures, beats, ticks); } else { static midi_measures s_dummy; return s_dummy; } } /** * Converts a string that represents "hours:minutes:seconds.fraction" into a * MIDI pulse/ticks/clock value. * * \param timestring * The time value to be converted, which must be of the form * "hh:mm:ss" or "hh:mm:ss.fraction". That is, all four parts must * be found. * * \param bpm * The beats-per-minute tempo (e.g. 120) of the current MIDI song. * * \param ppqn * The parts-per-quarter note precision (e.g. 192) of the current MIDI * song. * * \return * Returns 0 if an error occurred or if the number actually translated to * 0. */ midipulse timestring_to_pulses (const std::string & timestring, midibpm bpm, int ppqn) { midipulse result = 0; if (! timestring.empty()) { std::string sh, sm, ss, us; if (extract_timing_numbers(timestring, sh, sm, ss, us) >= 4) { /** * This conversion assumes that the fractional parts of the * seconds is padded with zeroes on the left or right to 6 digits. */ int hours = strtoi(sh); int minutes = strtoi(sm); int seconds = strtoi(ss); double secfraction = string_to_double(us, 0, 3); /* atof(us) */ long sec = ((hours * 60) + minutes) * 60 + seconds; long microseconds = 1000000 * sec + long(1000000.0 * secfraction); double pulses = delta_time_us_to_ticks(microseconds, bpm, ppqn); result = midipulse(pulses); } } return result; } /** * Converts a time string to pulses. First, the type of string is deduced by * the characters in the string. If the string contains two colons and a * decimal point, it is assumed to be a time-string ("hh:mm:ss.frac"); in * addition ss will have to be less than 60. ??? Actually, now we only care * if four numbers are provided. * * If the string just contains two colons, then it is assumed to be a * measure-string ("measures:beats:divisions"). * * If it has none of the above, it is assumed to be pulses. Testing is not * rigorous. * * measurestring_to_pulses(): Converts "B:B:T" values to pulses. * timestring_to_pulses(): Converts "H:M:S.f" values to pulses. * * \param s * Provides the string to convert to pulses. * * \param mt * Provides the structure needed to provide BPM and other values needed * for some of the conversions done by this function. * * \param timestring * If true, interpret the string as an "H:M:S" string. * * \return * Returns the string as converted to MIDI pulses (or divisions, clocks, * ticks, whatever you call it). */ midipulse string_to_pulses ( const std::string & s, const midi_timing & mt, bool timestring ) { midipulse result = 0; tokenization tokens; int count = tokenize_string(s, tokens); /* function in this module */ if (count == 1) /* no colons in it */ { result = midipulse(string_to_long(s)); } else if (count > 1) { if (timestring) result = timestring_to_pulses(s, mt.beats_per_minute(), mt.ppqn()); else result = measurestring_to_pulses(s, mt); } return result; } /** * Creates a number with a negative-to-0-to-positive range. * * The first call is seeded with the current time, then a pseudo-random * number is returned. This is a simplistic linear congruence generator. It * returns a random number between -range and + range. [Could consider using * random(3) which has a much longer periodicity.] * * Another options is rand() % (2 * range + 1) + (-range + 1), but it uses * only the low-order bits of the number. * * C++11 has a default random engine template, but it is a pain! * * return rand() / (RAND_MAX / (2 * range + 1) + 1) - range; * * \param range * The amount of "randomness" desired. * * \return * Returns a number from -range to +range, uniformly distributed. */ int randomize (int range, int seed) { static bool s_uninitialized = true; if (s_uninitialized) { s_uninitialized = false; if (seed == 0) seed = time(NULL); srand(unsigned(seed)); } if (range != 0) { if (range < 0) range = -range; long result = (2 * range * long(rand()) / RAND_MAX) - range; return int(result); } else return 0; } #if defined SEQ66_USE_UNIFORM_INT_DISTRIBUTION class randomizer { private: static const int s_upper_limit = std::numeric_limits::max(); std::random_device m_rd; /* seed source for random number engine */ std::mt19937 m_mtwister; /* mersenne_twister_engine, maybe seeded */ std::uniform_int_distribution m_distribution; public: randomizer (int seed = -1) : m_rd (), /* random device (opt.) */ m_mtwister (m_rd()), /* internal generator */ m_distribution (0, s_upper_limit) /* uniform int range */ { if (seed != (-1)) m_mtwister.seed(seed); } int generate () { return m_distribution(m_mtwister); } int generate (int range) { int rnd = generate(); long result = 2 * range * long(rnd) / long(s_upper_limit); return int(result) - range; } }; // class randomizer int randomize_uniformly (int range, int seed) { static bool s_uninitialized = true; static randomizer * s_randomizer_pointer = nullptr; if (s_uninitialized) { static randomizer s_randomizer(seed); /* create it secretly */ s_randomizer_pointer = &s_randomizer; /* point to it */ s_uninitialized = false; } if (range != 0) { if (range < 0) range = -range; return s_randomizer_pointer->generate(range); } else return 0; } #endif /** * Returns true if a number is a power of 2. MIDI's beatwidth values * provide the power of 2 needed for a valid beat width value. * First b in the below expression is for the case when b is 0. * * Taken from: * * https://www.geeksforgeeks.org/c-program-to-find-whether-a-no-is-power-of-two/ */ bool is_power_of_2 (int b) { if (b <= 0) return false; else return (b & (b - 1)) == 0; } /** * Calculates the log-base-2 value of a number that is already a power of 2. * Useful in converting a time signature's denominator to a Time Signature * meta event's "dd" value. * * \param tsd * The time signature denominator or other integer, which must be a power * of 2: 2, 4, 8, 16, 32, .... * * \return * Returns the power of 2 that achieves the \a tsd parameter value. * Returns -1 if the value is not a power of 2. */ int log2_of_power_of_2 (int tsd) { if (is_power_of_2(tsd)) { int result = 0; while (tsd > 1) { ++result; tsd >>= 1; } return result; } else return (-1); } #if defined SEQ66_USE_EXTRA_PULSE_CALCULATIONS /** * OBSOLETE. See the zoomer class instead. * * This function provides the size of the smallest horizontal grid unit in * units of pulses (ticks). We need this to be able to increment grid * drawing by more than one (time-wasting!) without skipping any lines. * * The smallest grid unit in the seqroll is a "sub-step". The next largest * unit is a "note-step", which is inside a note. Each note contains PPQN * ticks. The pulses-per-sub-step value represent the smallest horizontal * unit in a Seq66 grid. It is the number of pixels in the smallest * increment between vertical lines in the grid. For a zoom of 2, this * number gets doubled. * * If the value that results is odd, then the horizontal line match * calculation can fail, resulting in missing lines and missing measure * numbers. In this case, we increment the resul to make it even. * * Current status at PPQN = 192, Base pixels (pixels_per_substep) = 6: * \verbatim Zoom Note-steps Substeps Substeps/Note (#SS) 1 4 8 32 2 4 4 16 4 4 2 8 8 4 0 4 16 2 0 2 32 0 0 1 64 1/2 0 1/2 128 0 0 1/4 \endverbatim * * Pulses-per-substep is given by PPSS = PPQN / #SS, where #SS = 32 / Zoom. * But 32 is the base PPQN (192) divided by the base pixels (6). In the end, * * PPSS = (PPQN * Zoom * Base Pixels) / Base PPQN * * In units: * * pulses pulses pulses pixel qn * --------- = -------- * -------- * --------- * -------- * substep qn pixel substep pulses * * Currently the Base values are hardwired (see usrsettings). The base * pixels value is pixels_per_substep = 6, and the base PPQN is * usr().base_ppqn() = 192. The numerator of this equation is well within * the limit of a 32-bit integer. * * \param ppqn * Provides the actual PPQN used by the currently-loaded tune. * * \param zoom * Provides the current zoom value. Defaults to 2, but can be * another value. The zoom value is the number of pulses per pixel. * Thus, zooming in (making the horizontal grid segments wider) * yields fewer pulses per pixel. We can zoom out further than * we can zoom in, which is just one step from the default zoom. * * \return * The result of the above equation is returned. */ int pulses_per_substep (midipulse ppq, int zoom) { const int pixels_per_substep = 6; int result = int(ppq) * zoom * pixels_per_substep / usr().base_ppqn(); if ((result % 2) != 0) ++result; return result; } /** * Similar to pulses_per_substep(), but for a single pixel. Actually, what * this function does is scale the PPQN against usr().base_ppqn() (192). * * \param ppqn * Provides the actual PPQN used by the currently-loaded tune. * * \param zoom * Provides the current zoom value. Defaults to 1, which can be used * to simply get the ratio between the actual PPQN, but only when PPQN >= * usr().base_ppqn(). * * \return * The result of the above equation is returned. */ int pulses_per_pixel (midipulse ppq, int zoom) { midipulse result = (ppq * zoom) / usr().base_ppqn(); if (result == 0) result = 1; return result; } #endif // defined SEQ66_USE_EXTRA_PULSE_CALCULATIONS /** * Internal function for simple calculation of a power of 2 without a lot of * math. Use for calculating the denominator of a time signature. * * \param logbase2 * Provides the power to which 2 is to be raised. This integer is * probably only rarely greater than 5 (which represents a denominator of * 32). * * \return * Returns 2 raised to the logbase2 power. */ int beat_power_of_2 (int logbase2) { int result; if (logbase2 == 0) { result = 1; } else { result = 2; for (int c = 1; c < logbase2; ++c) result <<= 1; } return result; } /** * Calculates the previous power of 2 before a given number. Not meant to be * rigorous, just enough for MIDI usage. No numbers where a multiplication * by 2 would overflow an int. * * * \param value * Provides the value to be rounded up to the next power of 2. * * \return * Returns the previous power of 2 below the given value. If already a * power of 2, it is returned as is. If the value is 0 (or less than * 0), 1 is returned. */ int previous_power_of_2 (int value) { int result = 1; if (value > 1) { result = value >> 1; result <<= 1; } return result; } /** * Calculates the next power of 2 after a given number. Not meant to be * rigorous, just enough for MIDI usage. No numbers where a multiplication * by 2 would overflow an int. * * * \param value * Provides the value to be rounded up to the next power of 2. * * \return * Returns the next power of 2 above the given value. If already a * power of 2, it is returned as is. If the value is 0 (or less than * 0), 1 is returned. */ int next_power_of_2 (int value) { int result = 1; if (value > 0) { while (result <= value) { if (result < value) result <<= 1; else break; } } return result; } /** * Calculates positive integer powers. * * \param base * The number to be raised to the power. * * \param exponent * The power to be applied. Only 0 or above are accepted. However, * there is currently no check for integer overflow of the result. This * function is meant for reasonably small powers. * * \return * Returns the power. If the exponent is illegal, the 0 is returned. */ int power (int base, int exponent) { int result = 0; if (exponent > 1) { result = base; for (int p = exponent; p > 1; --p) result *= base; } else if (exponent == 1) result = base; else if (exponent == 0) result = 1; return result; } /** * Calculates the base-2 log of a number. This number is truncated to an * integer byte value, as it is used in calculating values to be written to a * MIDI file. * * \param value * The integer value for which the log2(value) is needed. * * \return * Returns log2(value) for values of 1 or greater. Otherwise returns 0. */ midibyte beat_log2 (int value) { return value > 0 ? midibyte(std::log(double(value)) / std::log(2.0)) : 0 ; } /** * Calculates the tempo in microseconds from the bytes read from a Tempo * event in the MIDI file. * * Is it correct to simply cast the bytes to a double value? * * \param tt * Provides the 3-byte array of values making up the raw tempo data. * * \return * Returns the result of converting the bytes to a double value. */ midibpm tempo_us_from_bytes (const midibytes & tt) { if (tt.size() == 3) { midibpm result = midibpm(tt[0]); result = (result * 256) + midibpm(tt[1]); result = (result * 256) + midibpm(tt[2]); return result; } else return 0.0; } /** * Provide a way to convert a tempo value (microseconds per quarter note) * into the three bytes needed as value in a Tempo meta event. Recall the * format of a Tempo event: * * 0 FF 51 03 t2 t1 t0 (tempo as number of microseconds per quarter note) * * This code is the inverse of the lines of code around line 768 in * midifile.cpp, which is basically * ((t2 * 256) + t1) * 256 + t0 . * * As a test case, note that the default tempo is 120 beats/minute, which is * equivalent to tttttt=500000 (0x07A120). The output of this function will * be t[] = { 0x07, 0xa1, 0x20 } [the indices go 0, 1, 2]. * * \param t * Provides a small array of 3 elements to hold each tempo byte. * * \param tempo_us * Provides the temp value in microseconds per quarter note. This is * always an integer, not a double, so do not get confused here. */ bool tempo_us_to_bytes (midibytes & t, midibpm tempo_us) { bool result = tempo_us > 0.0; t.clear(); if (result) { int temp = int(tempo_us + 0.5); t.push_back(midibyte((temp & 0xFF0000) >> 16)); t.push_back(midibyte((temp & 0x00FF00) >> 8)); t.push_back(midibyte(temp & 0x0000FF)); } else { t.push_back(0); t.push_back(0); t.push_back(0); } return result; } /** * Converts a tempo value to a MIDI note value for the purpose of displaying * a tempo value in the mainwnd, seqdata section (hopefully!), and the * perfroll. It implements the following linear equation, with clamping just * in case. * \verbatim N1 - N0 N = N0 + (B - B0) --------- where (N1 - N0) is always 127 B1 - B0 \endverbatim * \verbatim 127 N = (B - B0) --------- B1 - B0 \endverbatim * * where N0 = 0 (MIDI note 0 is the minimum), N1 = 127 (the maximum MIDI * note), B0 is the value of usr().midi_bpm_minimum(), * B1 is the value of usr().midi_bpm_maximum(), B is the input beats/minute, * and N is the resulting note value. As a precaution due to rounding error, * we clamp the values between 0 and 127. * * \param tempovalue * The tempo in beats/minute. * * \return * Returns the tempo value scaled to the range 0 to 127, based on the * configured BPM minimum and maximum values. */ midibyte tempo_to_note_value (midibpm tempovalue) { double slope = double(max_midi_value()); double minimum = usr().midi_bpm_minimum(); slope /= usr().midi_bpm_maximum() - minimum; int note = int(slope * (tempovalue - minimum) + 0.5); return clamp_midibyte_value(note); } /** * From the above, we can derive: * \verbatim (B1 - B0) N B = ------------ + B0 127 \endverbatim * */ midibpm note_value_to_tempo (midibyte notevalue) { double denominator = double(max_midi_value()); double minimum = usr().midi_bpm_minimum(); midibpm result = notevalue * (usr().midi_bpm_maximum() - minimum); result /= denominator; result += minimum; return result; } /** * Fixes the tempo value, truncating it to the number of digits of tempo * precision (0, 1, or 2) specified by "bpm_precision" in the "usr" file. * * \param bpm * The uncorrected BPM value. * * \return * Returns the BPM truncated to the desired precision. */ midibpm fix_tempo (midibpm bpm) { int precision = usr().bpm_precision(); /* 0/1/2 digits past decimal */ if (precision > 0) { bpm *= 10.0; if (precision == 2) bpm *= 10.0; } bpm = trunc(bpm); if (precision > 0) { bpm /= 10.0; if (precision == 2) bpm /= 10.0; } return bpm; } /** * In order to display discrete data, such as Program/Patch values, * properly in the data pane, we need to account for the constraints * on viewability that reduce the range from 0-127 to something less, * such as 7-120, for painting. * * \param invalue * Provides the input value, ranging from 0 to 127. * * \param reduction * Provides the amount to reduce the range on both ends. * This value might be a radius in pixels, for example. */ int midi_data_adjust (int invalue, int reduction) { const int m_min = 0; const int m_max = max_midi_value(); /* 127 */ const int a_min = m_min + reduction; const int a_max = m_max - reduction; double slope = double(a_max - a_min) / double(m_max - m_min); return int(slope * int(invalue) + a_min); } /** * Combines bytes into an unsigned-short value. * * http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec/wheel.htm * * Two data bytes follow the status. The two bytes should be combined * together to form a 14-bit value. The first data byte's bits 0 to 6 are * bits 0 to 6 of the 14-bit value. The second data byte's bits 0 to 6 are * really bits 7 to 13 of the 14-bit value. In other words, assuming that a * C program has the first byte in the variable First and the second data * byte in the variable Second, here's how to combine them into a 14-bit * value (actually 16-bit since most computer CPUs deal with 16-bit, not * 14-bit, integers). * * I think Kepler34 got the bytes backward. * * \param b0 * The first byte to be combined. * * \param b1 * The second byte to be combined. * * \return * Returns the bytes basically OR'd together. */ unsigned short combine_bytes (midibyte b0, midibyte b1) { unsigned short short_14bit = (unsigned short)(b1); short_14bit <<= 7; short_14bit |= (unsigned short)(b0); return short_14bit * 48; } /** * Extracts a MIDI Variable-Length Value (VLV) from a byte array. * See read_varinum(). * * \return * Returns the accumulated values as a single number. */ midilong extract_varinum (const midibytes & data, int & index) { midilong result = 0; midibyte c = 0; for ( ; index < int(data.size()); ++index) { c = data[index]; if ((c & 0x80) != 0x00) /* bit 7 is set */ { result <<= 7; /* shift result 7 bits */ result += c & 0x7F; /* add bits 0-6 */ } else { ++index; break; } } result <<= 7; /* bit was clear */ result += c & 0x7F; return result; } /** * Formalizes the rescaling of ticks base on changing the PPQN. For speed * the parameters are all assumed to be valid. The PPQN values supported * explicity range from 24 to 19200. The maximum tick value for 32-bit code * is 2147483647. At the highest PPQN that's almost 28000 measures. 64-bit * code maxes at over 9E18. * * \param tick * The tick value to be rescaled. * * \param newppqn * The new PPQN. * * \param oldppqn * The original PPQN. Defaults to 192. * * \return * Returns the new tick value. */ midipulse rescale_tick (midipulse tick, int newppqn, int oldppqn) { return midipulse(double(tick) * newppqn / oldppqn + 0.5); } /** * Calculates a wave function for use as an LFO (low-frequency oscillator) * for modifying data values in a sequence. We extracted this function from * mattias's lfownd module, as it is more generally useful. The angle * parameter is provided by the lfownd object. It is calculated by * \verbatim speed * tick angle = -------------- + phase length \endverbatim * * The speed (number of periods in the transformation) ranges from 0 to 16 in * the user interface; the ratio of tick/seqlength ranges from 0 to 1; the * phase ranges from 0 to 1, equivalent to 0 to 360 degrees. * * \param omega * Provides the radial "angle" to be applied. Assuming that the "speed" * (number of periods) is 1, then, for a one-measure pattern or * a longer pattern with "Use Measures" unchecked in qlfoframe, this value * ranges from 0.0 to 1.0. Increasing the "speed" or the number of * measures increases the angle range proportionately. * * \param wavetype * Provides the wave value to select the type of wave data-point * to be generated. * * \return * Returns the result of the calculation, which will range from -1.0 to * 0.0 to 1.0, depending on the wave employed. */ double wave_func (double omega, waveform wavetype) { double result = 0.0; double tmp; switch (wavetype) { case waveform::sine: result = sin(omega); break; case waveform::sawtooth: result = fmod(omega, 2.0 * M_PI) / (2.0 * M_PI); /* 0.0 to 1.0 */ break; case waveform::reverse_sawtooth: result = 1.0 - fmod(omega, 2.0 * M_PI) / (2.0 * M_PI); break; case waveform::triangle: result = fmod(omega, 2.0 * M_PI) / (2.0 * M_PI); /* 0.0 to 1.0 */ result *= 2.0; /* 0.0 to 2.0 */ result -= 1.0; /* -1.0 to 1.0 */ break; case waveform::exponential: tmp = fmod(omega, 2.0 * M_PI) / (2.0 * M_PI); /* 0.0 to 1.0 */ result = exp_normalize(tmp); break; case waveform::reverse_exponential: tmp = fmod(omega, 2.0 * M_PI) / (2.0 * M_PI); /* 0.0 to 1.0 */ result = exp_normalize(tmp, true); break; case waveform::dc: result = 1.0; break; default: break; } return result; } #if SEQ66_NEEDS_UNIT_TRUNCATION /** * Converts a double value to range from 0.0 to 1.0. That is, it returns the * fractional portion. For example, 4.145 would become 0.145. */ double unit_truncation (double angle) { double result = angle; if (result > 1.0) { double truncated = trunc(result); result -= truncated; } return result; } #endif /** * This function maps midibyte values from 0.0 to 1.0 to the range of * approximately e^(-2.436) to e^(+2.436), which is 0.884 to 11.314. * These values convert nicely to a range of 11.314 / 0.0884 = 128. * * So we take the angle A: * * -# Convert A via truncation so it ranges from 0.0 to 1.0. Call it T. * -# Re-map A to range from Emin = -2.436 to Emax = 2.436. Call it A'. * -# Take e to the power A'. * -# Rescale the result to the range of 0.0 to 1.0, for use in the LFO * generator. * * The re-mapping equations is y = mx + b, where the y-intercept is easily * seen to be b = Emin, and the slope is m = Emax - Emin = range. * * For a reverse exponential, we simply negate the exponent. */ double exp_normalize (double angle, bool negate) { static const double s_range = log(double(max_midi_value())); /* 4.852 */ static const double s_exp_max = s_range / 2.0; /* +2.42 */ static const double s_exp_min = -s_exp_max; /* -2.42 */ static const double s_scaler = exp(s_exp_min); double Aprime = s_range * angle + s_exp_min; if (negate) Aprime = -Aprime; double result = exp(Aprime); result *= s_scaler; return result; } /** * Converts a wave type value to a string. These names are short because I * cannot figure out how to get the window pad out to show the longer names. * * \param wavetype * The wave-type value to be displayed. * * \return * Returns a short description of the wave type. */ std::string wave_type_name (waveform wavetype) { std::string result = "None"; switch (wavetype) { case waveform::sine: result = "Sine"; break; case waveform::sawtooth: result = "Ramp Up Saw"; break; case waveform::reverse_sawtooth: result = "Decay Saw"; break; case waveform::triangle: result = "Triangle"; break; case waveform::exponential: result = "Exponential Rise"; break; case waveform::reverse_exponential: result = "Exponential Fall"; break; case waveform::dc: result = "DC Offset"; break; default: break; } return result; } /** * Extracts the two names from the ALSA/JACK client/port name format: * * [0] 128:0 client name:port name * * When a2jmidid is running: * * a2j:Midi Through [14] (playback): Midi Through Port-0 * * with "a2j" as the client name and the rest, including the second colon, as * the port name. For that kind of name, use extract_a2j_port_name(). * * \param fullname * The full port specification to be split. * * \param [out] clientname * The destination for the client name portion, "clientname". * * \param [out] portname * The destination for the port name portion, "portname". * * \return * Returns true if all items are non-empty after the process. */ bool extract_port_names ( const std::string & fullname, std::string & clientname, std::string & portname ) { bool result = ! fullname.empty(); clientname.clear(); portname.clear(); if (result) { std::string cname; std::string pname; std::size_t colonpos = fullname.find_first_of(":"); /* not last! */ if (colonpos != std::string::npos) { /* * The client name consists of all characters up the the first * colon. Period. The port name consists of all characters * after that colon. Period. */ cname = fullname.substr(0, colonpos); pname = fullname.substr(colonpos+1); result = ! cname.empty() && ! pname.empty(); } else pname = fullname; clientname = cname; portname = pname; } return result; } /** * Extracts the buss name from "bus:port". Sometimes we don't need both * parts at once. * * However, when a2jmidid is active. the port name will have a colon in it. * * \param fullname * The "bus:port" name. * * \return * Returns the "bus" portion of the string. If there is no colon, then * it is assumed there is no buss name, so an empty string is returned. */ std::string extract_bus_name (const std::string & fullname) { std::size_t colonpos = fullname.find_first_of(":"); /* not last! */ return (colonpos != std::string::npos) ? fullname.substr(0, colonpos) : std::string(""); } /** * Extracts the port name from "bus:port". Sometimes we don't need both * parts at once. * * However, when a2jmidid is active. the port name will have a colon in it. * * \param fullname * The "bus:port" name. * * \return * Returns the "port" portion of the string. If there is no colon, then * it is assumed that the name is a port name, and so \a fullname is * returned. */ std::string extract_port_name (const std::string & fullname) { std::size_t colonpos = fullname.find_first_of(":"); /* not last! */ return (colonpos != std::string::npos) ? fullname.substr(colonpos + 1) : fullname ; } /** * Sets the name to be displayed for showing to the user, and hopefully, * later, for look-up. * * NOT YET USED. * * For JACK ports created by a2jmidid (a2j_control), we want to shorten the * name radically, and also set the bus ID, which is contained in square * brackets. * * - ALSA: "[0] 14:0 Midi Through Port-0" * - JACK: "[0] 0:0 seq66:system midi_playback_1" * - A2J: "[0] 0:0 seq66:a2j Midi Through [14] (playback): Midi Through Port-0" * * Skip past the two colons to get to the main part of the name. Extract it, * and prepend "a2j". * * \param alias * The system-supplied or a2jmidid name for the port. One example: * "a2j:Midi Through [14] (playback): Midi Through Port-0". Obviously, * this function depends on the formatting of a2jmidid name assignments * not changing. * * \sideeffect * The bus ID is also modified, if present in the string (see "[14]" * above). * * \return * Returns the bare port-name is "a2j" appears in the alias. Otherwise, and * empty string is returned. */ std::string extract_a2j_port_name (const std::string & alias) { std::string result; if (contains(alias, "a2j")) { auto lpos = alias.find_first_of(":"); if (lpos != std::string::npos) { lpos = alias.find_first_of(":", lpos + 1); if (lpos != std::string::npos) { result = alias.substr(lpos + 2); result = "A2J " + result; } } } return result; } /** * Calculates the closest snap value. * * \param S * Provides the snap value to be applied. The snap specifies the * interval between time steps. For example, a snap of 1/16th * for a PPQN of 192 and a time-signature of 4/4 is equal to * 192 * 4 / 16 = 48 ticks. It is Ignored if it is not greater than * 0. * * \param p * Provide the value to be snapped to the closet snap value. * * \return * Returns the snapped value. If either parameter is not greater than * 0, then return 0. */ midipulse closest_snap (int S, midipulse p) { midipulse result = 0; if (p > 0 && S > 0) { midipulse snap = midipulse(S); midipulse p0 = p - (p % snap); /* drop down to a snap */ midipulse p1 = p0 + snap; /* go up by one snap */ int deltalo = int(p - p0); /* amount to lower snap */ int deltahi = int(p1 - p); /* amount to upper snap */ result = deltalo <= deltahi ? p0 : p1 ; /* use the one closest */ } return result; } midipulse down_snap (int S, midipulse p) { midipulse result = 0; if (p > 0 && S > 0) { midipulse snap = midipulse(S); result = midipulse(p - (p % snap)); /* drop down to a snap */ if (result < 0) /* should never happen, tho */ result = 0; } return result; } midipulse up_snap (int S, midipulse p) { midipulse result = 0; if (p > 0 && S > 0) { midipulse snap = midipulse(S); midipulse Sn0 = p - (p % snap); result = Sn0 + snap; } return result; } /** * Comparison of floating-point values. We can tolerate a fairly large * epsilon value in our MIDI code. * * See https://realtimecollisiondetection.net/blog/?p=89 for some gory * details. * * if (Abs(x – y) <= EPSILON * Max(1.0f, Abs(x), Abs(y)) */ static double s_epsilon = 0.0001; static double one_max (double a, double b) { double result = 1.0f; if (std::fabs(a) > result) result = std::fabs(a); if (std::fabs(b) > result) result = std::fabs(b); return result; } /** * Calculates x == y to within the epsilon, and returns true if that is so. */ bool fequal (double x, double y) { return std::fabs(x - y) <= s_epsilon * one_max(std::fabs(x), std::fabs(y)); } /** * Calculates x != y to within the epsilon, and returns true if that is so. */ bool fnotequal (double x, double y) { return std::fabs(x - y) > s_epsilon * one_max(std::fabs(x), std::fabs(y)); } /** * Calculates x < y and returns true if that is so. */ bool flessthan (double x, double y) { return x < (y - s_epsilon * one_max(std::fabs(x), std::fabs(y))); } /** * Calculates x > y and returns true if that is so. */ bool fgreaterthan (double x, double y) { return x > (y + s_epsilon * one_max(std::fabs(x), std::fabs(y))); } /** * Pitch bend calculations. The MIDI Pitch Bend range is depicted here: * * bend down center bend up * 0 |<----------- | 8192 | ----------->| 16384 * -8192 0 8191 * * - 14 bits resolution (MSB, LSB). Value = 128 * MSB + LSB * - Minimum: The maximum negative swing is byte values of * 00, 00. Value = 0. * - Center: The center (no effect) position is byte value of * 00, 64 (0x00, 0x40). Value = 8192. * - Maximum: The maximum positive swing is byte values of * 127, 127 (0x7F, 0x7F). Value = 16384. * * Pitch Bend Sensitivity is defined as Registered Parameter Number 00 00. * The MSB represents the sensitivity in semitones and the LSB * represents the sensitivity in cents. A cent is 0.01 semitones. * * MIDI pitch bend data is encoded using big-endian byte order: the MSB * is transmitted or stored first, followed by the LSB. For example, a * pitch bend of 8192 (no pitch bend) is represented as two bytes: * 0x40 (MSB) and 0x00 (LSB). In Seq66, the LSB is D0, and the MSB * is D1. * * \param d0 * The LSB of the pitch-bend value. * * \param d1 * The MSB of the pitch-bend value. * * \param semitone-range * The number of semitones up or down when the highest or lowest * values in the range are used. It defaults to 2 semitones up or * down. */ double pitch_value_semitones (midibyte d0, midibyte d1, int semitone_range) { double factor = double(semitone_range) / 8192.0; int value = pitch_value(d0, d1); /* from -8192 to +8192, approx. */ return factor * double(value); } /** * Given a base pitch value (i.e. independent of the current semitone range), * provides the two bytes needed to encode the pitch-wheel event in a MIDI * file. The maximum value is 16383 = 0x3FFF, or, in bits * * 3 F F F * 0011 1111 1111 1111 * * But we have to get 7 bits for each: * * 7F * 00111111-1-1111111 * 3 F 8 0 * * \param pitch * Provides the pitch value, which must range from 0 to 16384. * Otherwise, 8192 is assumed. * * \param [out] d0 * The LSB of the pitch-bend value. It is obtained with a mask of * 0x7F. * * \param [out] d1 * The MSB of the pitch-bend value. It is obtained by shifting * the pitch value 7 bits. */ void pitch_data_bytes (int pitch, midibyte & d0, midibyte & d1) { if (pitch >= 0 && pitch < 16384) { d1 = midibyte(pitch >> 7); /* shift pitch to get the MSB */ d0 = midibyte(pitch & 0x7F); /* mask the LSB to get d0 */ } else { d1 = 64; d0 = 0; } } void pitch_data_bytes_scaled (midibyte pitch, midibyte & d0, midibyte & d1) { int truevalue = int(pitch) >> 7; pitch_data_bytes(truevalue, d0, d1); } } // namespace seq66 /* * calculations.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/controllers.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file controllers.hpp * * This module defines the array of MIDI controller names. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-12-06 * \updates 2026-04-20 * \license GNU GPLv2 or above * * This definition used to reside in the controllers.hpp file, but now more * than one module uses it, so we have to define it here to avoid a linker * error about multiple definitions of this item. */ #include "midi/controllers.hpp" /* seq66::controller_name(), etc. */ #include "midi/midibytes.hpp" /* seq66::midibyte type */ namespace seq66 { using namepair = struct { int number; std::string name; }; /** * Provides the default names of MIDI controllers. This array is used * only by the functions below. */ static namepair s_controller_names [c_midibyte_data_max] = { { 0, "Bank Select" }, // 0x00 { 1, "Modulation Wheel" }, { 2, "Breath controller" }, { 3, "---" }, { 4, "Foot Pedal" }, { 5, "Portamento Time" }, { 6, "Data Entry Slider" }, { 7, "Volume" }, { 8, "Balance" }, { 9, "---" }, { 10, "Pan position" }, { 11, "Expression " }, { 12, "Effect Control 1 " }, { 13, "Effect Control 2 " }, { 14, "---" }, { 15, "---" }, { 16, "General Purpose Slider 1" }, // 0x10 { 17, "General Purpose Slider 2" }, { 18, "General Purpose Slider 3" }, { 19, "General Purpose Slider 4" }, { 20, "---" }, { 21, "---" }, { 22, "---" }, { 23, "---" }, { 24, "---" }, { 25, "---" }, { 26, "---" }, { 27, "---" }, { 28, "---" }, { 29, "---" }, { 30, "---" }, { 31, "---" }, { 32, "Bank Select (fine)" }, // 0x20 { 33, "Modulation Wheel (fine)" }, { 34, "Breath controller (fine)" }, { 35, "---" }, { 36, "Foot Pedal (fine)" }, { 37, "Portamento Time (fine)" }, { 38, "Data Entry (fine)" }, { 39, "Volume (fine)" }, { 40, "Balance (fine)" }, { 41, "---" }, { 42, "Pan position (fine)" }, { 43, "Expression (fine)" }, { 44, "Effect Control 1 (fine)" }, { 45, "Effect Control 2 (fine)" }, { 46, "---" }, { 47, "---" }, { 48, "---" }, // 0x30 { 49, "---" }, { 50, "---" }, { 51, "---" }, { 52, "---" }, { 53, "---" }, { 54, "---" }, { 55, "---" }, { 56, "---" }, { 57, "---" }, { 58, "---" }, { 59, "---" }, { 60, "---" }, { 61, "---" }, { 62, "---" }, { 63, "---" }, { 64, "Hold Pedal (on/off)" }, // 0x40 { 65, "Portamento (on/off)" }, { 66, "Sustenuto Pedal (on/off)" }, { 67, "Soft Pedal (on/off)" }, { 68, "Legato Pedal (on/off)" }, { 69, "Hold 2 Pedal (on/off)" }, { 70, "Sound Variation" }, { 71, "Sound Timbre" }, { 72, "Sound Release Time" }, { 73, "Sound Attack Time" }, { 74, "Sound Brightness" }, { 75, "Sound Control 6" }, { 76, "Sound Control 7" }, { 77, "Sound Control 8" }, { 78, "Sound Control 9" }, { 79, "Sound Control 10" }, { 80, "General Purpose Button 1 (on/off)" }, // 0x50 { 81, "General Purpose Button 2 (on/off)" }, { 82, "General Purpose Button 3 (on/off)" }, { 83, "General Purpose Button 4 (on/off)" }, { 84, "---" }, { 85, "---" }, { 86, "---" }, { 87, "---" }, { 88, "---" }, { 89, "---" }, { 90, "---" }, { 91, "Effects Level" }, { 92, "Tremulo Level" }, { 93, "Chorus Level" }, { 94, "Celeste Level" }, { 95, "Phaser Level" }, { 96, "Data Button Increment" }, // 0x60 { 97, "Data Button Decrement" }, { 98, "Non-registered Parameter (fine)" }, { 99, "Non-registered Parameter (coarse)" }, { 100, "Registered Parameter (fine)" }, { 101, "Registered Parameter (coarse)" }, { 102, "---" }, { 103, "---" }, { 104, "---" }, { 105, "---" }, { 106, "---" }, { 107, "---" }, { 108, "---" }, { 109, "---" }, { 110, "---" }, { 111, "---" }, { 112, "---" }, // 0x70 { 113, "---" }, { 114, "---" }, { 115, "---" }, { 116, "---" }, { 117, "---" }, { 118, "---" }, { 119, "---" }, { 120, "All Sound Off" }, { 121, "All Controllers Off" }, { 122, "Local Keyboard On/Off" }, { 123, "All Notes Off" }, { 124, "Omni Mode Off" }, { 125, "Omni Mode On" }, { 126, "Mono On" }, { 127, "Poly On" } // 0x7F }; std::string controller_name (int index, bool usehex) { std::string result; if (index >= 0 && index < c_midibyte_data_max) { std::string name = s_controller_names[index].name; if (usehex) { char tmp[32]; (void) snprintf(tmp, sizeof tmp, "0x%02x", index); result = tmp; } else result = std::to_string(index); result += " "; result += name; } return result; } void set_controller_name (int index, const std::string & newname) { if (index >= 0 && index < c_midibyte_data_max) s_controller_names[index].name = newname; } #if defined THIS_CODE_IS_READY using rpnpair = struct { short number; std::string name; }; const int c_rpn_value_count = 8; static rpnpair s_rpn_names [c_rpn_value_count] = { { 0x0000, "Pitch Bend Range" }, { 0x0001, "Fine Tuning" }, { 0x0002, "Coarse Tuning" }, { 0x0003, "Tuning Program Change" }, { 0x0004, "Tuning Bank Select" }, { 0x0005, "Modulation Depth Range" }, { 0x0006, "Channel Range" }, { 0x3FFF, "RPN Null" } }; std::string rpn_name (int index) { std::string result; if (index == 0x3FFFF) index = c_rpn_value_count - 1; if (index >= 0 && index < c_rpn_value_count) { std::string name = s_rpn_names[index].name; result = std::to_string(index); result += " "; result += name; } return result; } /** * Converts a 14-but RPN number to the MSB and LSB bytes. * * Here is the process: * * - In binary, this is a 16-bit number. 0011111110000000. * - Ignore the two leading zeroes, i.e. it's a 14-bit number. * - Get the MSB. * - Get the next 7 bits. * - Prepend a 0. * - Get the LSB. * - Get the last 7 bits. * - Prepend a 0. * * MMMMMMMLLLLLLL * * \param rpnn * The 14-bit RPN number. It must be greater than zero and less * than 16364 (0x4000). * * \param [out] out * Holds the two bytes, with out[0] being the LSB, and out[1] being * the MSB. * * \return * Returns true if the output bytes can be used. */ bool rpn_number_to_bytes (short rpnn, midibyte & out [2]) { bool result = rpnn >= 0 && rpnn < 16384; if (result) { unsigned short rpnn_lsb = rpnn & 0x3F; unsigned short rpnn_msb = rpnn & 0x3F80; /* rpnn - rpnn_lsb ? */ out[0] = midibyte(rpnn_lsb); out[1] = midibyte(rpnn_msb); } return result; } short bytes_to_rpn_number (const midibyte & in [2]) { short result = short(in[1]); /* the MSB 7 bits */ result <<= 7; result += short(in[0]); return result; } } #endif } // namespace seq66 /* * controllers.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/drums.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file drums.hpp * * This module defines the array of MIDI drum names. * * \library seq66 application * \author Chris Ahlstrom * \date 2026-02-16 * \updates 2026-04-17 * \license GNU GPLv2 or above * * This module is code extracted from the controllers module for better * modularity. */ #include "midi/drums.hpp" /* seq66::drum_name(), etc. */ #include "midi/midibytes.hpp" /* seq66::c_midibyte_data_max(), etc. */ namespace seq66 { /** * Note that the constructor and destructor are defaulted in the header * file. */ bool drums::add (int drumnumber, const std::string & drumname) { bool result { drumnumber < int(c_midibyte_data_max) }; if (result) { auto p { std::make_pair(drumnumber, drumname) }; auto r { m_drum_map.insert(p) }; result = r.second; } return result; } std::string drums::name (int drumnumber) const { auto it { m_drum_map.find(drumnumber) }; return it == m_drum_map.end() ? "N/A" : it->second ; } std::string drums::name_ex (int drumnumber) const { std::string result { std::to_string(drumnumber) }; auto it { m_drum_map.find(drumnumber) }; result += " "; result += it == m_drum_map.end() ? "N/A" : it->second; return result; } /* * A single global instance of the drums class */ static drums & non_gm_drums () { static drums s_non_gm_drums; return s_non_gm_drums; } /** * Works for GM drums, but other sets loaded might go beyond * this range. We let end() determine if the drum number is * not listed in the drum-set. */ #if defined USE_DRUM_IS_VALID_FUNCTION /* * static const drumpair * c_gm_drum_names [c_midibyte_data_max] = */ static std::size_t s_gm_drum_min { 35 }; static std::size_t s_gm_drum_max { 81 }; static bool drum_is_valid (int drumnumber) { return ( std::size_t(drumnumber) >= s_gm_drum_min && std::size_t(drumnumber) <= s_gm_drum_max ); } #endif /** * Provides the default names of General MIDI drums. Note that the * numbering starts from 0 internally. We could add support for this kind * of list in usrsettings, or the note-mapper, or in a new 'drum' * configuration file that holds this mapping. */ static drums::container s_gm_drum_names { { 35, "Acoustic Bass Drum" }, { 36, "Bass Drum 1" }, { 37, "Side Stick" }, { 38, "Acoustic Snare" }, { 39, "Hand Clap" }, { 40, "Electric Snare" }, { 41, "Low Floor Tom" }, { 42, "Closed Hi Hat" }, { 43, "High Floor Tom" }, { 44, "Pedal Hi-Hat" }, { 45, "Low Tom" }, { 46, "Open Hi-Hat" }, { 47, "Low-Mid Tom" }, { 48, "Hi Mid Tom" }, { 49, "Crash Cymbal 1" }, { 50, "High Tom" }, { 51, "Ride Cymbal 1" }, { 52, "Chinese Cymbal" }, { 53, "Ride Bell" }, { 54, "Tambourine" }, { 55, "Splash Cymbal" }, { 56, "Cowbell" }, { 57, "Crash Cymbal 2" }, { 58, "Vibraslap" }, { 59, "Ride Cymbal 2" }, { 60, "Hi Bongo" }, { 61, "Low Bongo" }, { 62, "Mute Hi Conga" }, { 63, "Open Hi Conga" }, { 64, "Low Conga" }, { 65, "High Timbale" }, { 66, "Low Timbale" }, { 67, "High Agogo" }, { 68, "Low Agogo" }, { 69, "Cabasa" }, { 70, "Maracas" }, { 71, "Short Whistle" }, { 72, "Long Whistle" }, { 73, "Short Guiro" }, { 74, "Long Guiro" }, { 75, "Claves" }, { 76, "Hi Wood Block" }, { 77, "Low Wood Block" }, { 78, "Mute Cuica" }, { 79, "Open Cuica" }, { 80, "Mute Triangle" }, { 81, "Open Triangle" } }; /** * Adds a drum number/name pair to the non-GM map supported by the * drums class. The drumnumber is not validated. */ bool add_drum (int drumnumber, const std::string & drumname) { bool result { non_gm_drums().add(drumnumber, drumname) }; if (result) non_gm_drums().activate(); return result; } /** * Adds a comment to the non-GM drum set for the 'drums' file. */ void set_drums_comment (const std::string & c) { if (non_gm_drums().active()) non_gm_drums().comments(c); } const std::string & get_drums_comment () { static const std::string s_gm_comment { "Provides the internal set of GM drums." }; return non_gm_drums().active() ? non_gm_drums().comments() : s_gm_comment ; } /** * Returns the drum number plus the drum name for the hard-wired GM * drum list. This function is used in displays and drop-down lists. * * If the drum number isn't found, the empty string is returned? No. * If the patch number isn't found, the name "N/A" is used. */ std::string gm_drum_name (int drumnumber) { std::string result; auto it { s_gm_drum_names.find(drumnumber) }; result = std::to_string(drumnumber); result += " "; if (it == s_gm_drum_names.end()) result += "N/A"; /* result.clear() */ else result += it->second; return result; } /** * This function returns the drum name and number from the user's * loaded non-GM drum list, if active. Otherwise, it returns the * drum name and number from the internal GM list. */ std::string drum_name (int drumnumber) { std::string result; result = non_gm_drums().active() ? non_gm_drums().name_ex(drumnumber) : gm_drum_name(drumnumber) ; return result; } /** * This function creates a long string of all the drums in the 'drums' * file format. * * Note that it depends on having the full 128-count of drums. */ std::string drum_list () { std::string result; const drums::container & active_drum_set { non_gm_drums().active() ? non_gm_drums().drum_map() : s_gm_drum_names }; int drumnumber { 0 }; for (const auto & p : active_drum_set) { auto it { s_gm_drum_names.find(drumnumber) }; std::string gmname { it == s_gm_drum_names.end() ? "N/A" : it->second }; std::string numb { std::to_string(drumnumber ) }; result += "[Drum "; result += numb; result += "]\n\ngm-name = \""; result += gmname; result += "\"\n" "gm-drum = "; result += numb; result += "\ndev-name = \""; result += p.second; result += "\"\n\n"; /* * We don't need to support a dev drum that matches the sound of a * GM drum. Let the user figure it out, if even needed. */ ++drumnumber; } return result; } } // namespace seq66 /* * drums.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/editable_event.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file editable_event.cpp * * This module declares/defines the base class for MIDI editable_events. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2025-08-20 * \license GNU GPLv2 or above * * A MIDI editable event is encapsulated by the seq66::editable_event * object. */ #include /* atoi(3) and atof(3) for 32-bit */ #include /* std::invalid_argument */ #include "cfg/scales.hpp" /* seq66::key_signature_bytes() etc */ #include "midi/editable_event.hpp" /* seq66::editable_event */ #include "midi/editable_events.hpp" /* seq66::editable_events multimap */ #include "util/strfunctions.hpp" /* seq66::strings_match(), etc. */ namespace seq66 { /** * Provides an integer value that is larger than any MIDI value, to be * used to terminate a array of items keyed by a midibyte value. */ midishort s_end_of_table = 0x100; /* one more than 0xFF */ /** * We moved all of these static arrays out of editable_events because * they are shown by the GDB print command, and they make the GDB wrapper we * use, cgdb, crash trying to output them all. * * Initializes the array of event/name pairs for the MIDI events categories. * Terminated by an empty string, the latter being the preferred test, for * consistency with the other arrays and because 0 is often a legitimate code * value. */ static const editable_event::name_value_t s_category_names [] = { { 0, midishort(editable_event::subgroup::channel_message), "Channel Message" }, { 1, midishort(editable_event::subgroup::system_message), "System Message" }, { 2, midishort(editable_event::subgroup::meta_event), "Meta Event" }, { 3, midishort(editable_event::subgroup::seqspec_event), "SeqSpec Event" }, { -1, s_end_of_table, "" } }; /** * Initializes the array of event/name pairs for the channel MIDI events. * Terminated by an empty string. */ static const editable_event::name_value_t s_channel_event_names [] = { { 0, midishort(EVENT_NOTE_OFF), "Note Off" }, // 0x80 { 1, midishort(EVENT_NOTE_ON), "Note On" }, // 0x90 { 2, midishort(EVENT_AFTERTOUCH), "Aftertouch" }, // 0xA0 { 3, midishort(EVENT_CONTROL_CHANGE), "Control Change" }, // 0xB0 { 4, midishort(EVENT_PROGRAM_CHANGE), "Program Change" }, // 0xC0 { 5, midishort(EVENT_CHANNEL_PRESSURE), "Ch Pressure" }, // 0xD0 { 6, midishort(EVENT_PITCH_WHEEL), "Pitch Wheel" }, // 0xE0 { -1, s_end_of_table, "" } // end }; /** * Initializes the array of event/name pairs for the system MIDI events. * Terminated by an empty string. */ static const editable_event::name_value_t s_system_event_names [] = { { 0, midishort(EVENT_MIDI_SYSEX), "SysEx Start" }, // 0xF0 { 1, midishort(EVENT_MIDI_QUARTER_FRAME), "Quarter Frame" }, // . { 2, midishort(EVENT_MIDI_SONG_POS), "Song Position" }, // . { 3, midishort(EVENT_MIDI_SONG_SELECT), "Song Select" }, // . { -1, midishort(EVENT_MIDI_SONG_F4), "F4" }, { -1, midishort(EVENT_MIDI_SONG_F5), "F5" }, { 4, midishort(EVENT_MIDI_TUNE_REQUEST), "Tune Request" }, { 5, midishort(EVENT_MIDI_SYSEX_END), "SysEx End" }, { 6, midishort(EVENT_MIDI_CLOCK), "Timing Clock" }, { -1, midishort(EVENT_MIDI_SONG_F9), "F9" }, { 7, midishort(EVENT_MIDI_START), "Start" }, { 8, midishort(EVENT_MIDI_CONTINUE), "Continue" }, { 9, midishort(EVENT_MIDI_STOP), "Stop" }, // . { -1, midishort(EVENT_MIDI_SONG_FD), "FD" }, // . { 10, midishort(EVENT_MIDI_ACTIVE_SENSE), "Active sensing" }, // . { 11, midishort(EVENT_MIDI_RESET), "Reset" }, // 0xFF { -1, s_end_of_table, "" } // end }; /** * Initializes the array of event/name pairs for all of the Meta events. * Terminated only by the empty string. Events with an index of -1 are not * supported. Only s_end_of_table is used to detect the end of the table. * Previous to version 0.99.5, this array wasn't used, so we are free * to mess with it and hide non-editable events. Events that are non-editable * include events handled by non-MIDI manipulations: * * - Seq Number * - MIDI Channel * - MIDI Port * - Track End * - SeqSpec (maybe) * * However, we need to be able to look up their names for display in case * someone's tune contains them. */ static const editable_event::name_value_t s_meta_event_names [] = { { -1, 0x00, "Seq Number" }, // FF 00 02 ss ss (16-bit) { 0, 0x01, "Text" }, // FF 01 len text { 1, 0x02, "Copyright" }, // FF 02 len text { 2, 0x03, "Track Name" }, // FF 03 len text (disabled) { 3, 0x04, "Instrument Name" }, // FF 04 len text { 4, 0x05, "Lyric" }, // FF 05 len text { 5, 0x06, "Marker" }, // FF 06 len text { 6, 0x07, "Cue Point" }, // FF 07 len text { 7, 0x08, "Program Name" }, // FF 08 len text { 8, 0x09, "Device Name" }, // FF 09 len text { -1, 0x0A, "Event 0A" }, { -1, 0x0B, "Event 0B" }, { -1, 0x0C, "Event 0C" }, { -1, 0x0D, "Event 0D" }, { -1, 0x0E, "Event 0E" }, { -1, 0x0F, "Event 0F" }, { -1, 0x20, "MIDI Channel" }, // FF 20 01 cc (obsolete) { -1, 0x21, "MIDI Port" }, // FF 21 01 pp (obsolete) { -1, 0x2F, "Track End" }, // FF 2F 00 (mandatory event) { 9, 0x51, "Tempo" }, // FF 51 03 tt tt tt (set tempo) { 10, 0x54, "SMPTE Offset" }, // FF 54 05 hh mm ss fr ff { 11, 0x58, "Time Sig" }, // FF 58 04 nn dd cc bb { 12, 0x59, "Key Sig" }, // FF 59 02 sf mi { 13, 0x7F, "Seq Spec" }, // FF 7F len id data (seq66 prop) { -1, 0xFF, "Illegal meta event" }, // indicator of problem { -1, s_end_of_table, "" } // terminator }; /** * Initializes the array of event/length pairs for all of the Meta events. * Terminated only by the empty string. */ static const editable_event::meta_length_t sm_meta_lengths [] = { { 0x00, 2 }, // "Seq Number" /* * Since meta_event_length() returns 0 by default, we can save some lookup * time. * * { 0x01, 0 }, // "Text Event" * { 0x02, 0 }, // "Copyright" * { 0x03, 0 }, // "Track Name" * { 0x04, 0 }, // "Instrument Name" * { 0x05, 0 }, // "Lyric" * { 0x06, 0 }, // "Marker" * { 0x07, 0 }, // "Cue Point" * { 0x08, 0 }, // "Program Name" * { 0x09, 0 }, // "Device Name" */ /* * The following events are normally not documented, so let's save some * more lookup time. * * { 0x0A, 0 }, // "Text Event 0A" * { 0x0B, 0 }, // "Text Event 0B" * { 0x0C, 0 }, // "Text Event 0C" * { 0x0D, 0 }, // "Text Event 0D" * { 0x0E, 0 }, // "Text Event 0E" * { 0x0F, 0 }, // "Text Event 0F" */ { 0x20, 1 }, // "MIDI Channel" { 0x21, 1 }, // "MIDI Port" { 0x2F, 0 }, // "Track End" { 0x51, 3 }, // "Tempo" { 0x54, 5 }, // "SMPTE Offset" { 0x58, 4 }, // "Time Sig" { 0x59, 2 }, // "Key Sig" { 0x7F, 0 }, // "Seq Spec" { 0xFF, 0 }, // "Illegal meta event" { s_end_of_table, 0 } // terminator }; /** * Initializes the array of event/name pairs for all of the * seq66-specific events. Terminated only by the empty string. * Note that the numbers reflect the masking off of the high-order bits * of 0x242400nn to retrieve 0xnn. * * Also see the list of midilong value in midi_vector_base.hpp. */ static const editable_event::name_value_t s_seqspec_event_names [] = { { 0, 0x01, "Buss number" }, { 1, 0x02, "Channel number" }, { 2, 0x03, "Clocking" }, { 3, 0x04, "Old trigger" }, // original Seq24-style trigger { 4, 0x05, "Song notes" }, { 5, 0x06, "Time signature" }, { 6, 0x07, "Beats per minute" }, { 7, 0x08, "Trigger ex" }, // newer Seq24 trigger { 8, 0x09, "Mute groups" }, { -1, 0x0A, "Gap A" }, { -1, 0x0B, "Gap B" }, { -1, 0x0C, "Gap C" }, { -1, 0x0D, "Gap D" }, { -1, 0x0E, "Gap E" }, { -1, 0x0F, "Gap F" }, { 9, 0x10, "Song MIDI control" }, { 10, 0x11, "Music key" }, { 11, 0x12, "Music scale" }, { 12, 0x13, "Background pattern" }, { 13, 0x14, "Track transpose" }, // Seq32 { 14, 0x15, "Perfedit beats/measure" }, // Seq32 { 15, 0x16, "Perfedit beat width" }, // Seq32 { 16, 0x17, "Tempo map" }, // Seq32 { -1, 0x18, "Reserved 1" }, { -1, 0x19, "Reserved 2" }, { 17, 0x1A, "Tempo track" }, { 18, 0x1B, "Pattern color" }, { 19, 0x1C, "Patter edit mode" }, { 20, 0x1D, "Pattern loop count" }, { -1, 0x1E, "Reserved 3" }, { -1, 0x1F, "Reserved 4" }, { 21, 0x20, "Transposable trigger" }, // Seq66/Sequencer64 trigger { -1, s_end_of_table, "" } // terminator }; /** * Contains pointers (references cannot be stored in an array) to the * desired array for a given category. This code could be considered a bit * rococo. */ static const editable_event::name_value_t * const s_category_arrays [] = { s_category_names, s_channel_event_names, s_system_event_names, s_meta_event_names, s_seqspec_event_names }; /** * A static member function used to fill the event category combo-box. */ std::string editable_event::category_name (int index) { std::string result; int counter = 0; while (s_category_names[counter].event_value != s_end_of_table) { if (counter == index) { result = s_category_names[counter].event_name; break; } ++counter; } return result; } /** * A static member function used to fill a channel-event status combo-box. */ std::string editable_event::channel_event_name (int index) { std::string result; int counter = 0; while (s_channel_event_names[counter].event_value != s_end_of_table) { if (counter == index) { result = s_channel_event_names[counter].event_name; break; } ++counter; } return result; } /** * The inverse of channel_event_name() */ int editable_event::channel_event_index (const std::string & name) { int result = (-1); int counter = 0; while (s_channel_event_names[counter].event_value != s_end_of_table) { if (s_channel_event_names[counter].event_name == name) { result = s_channel_event_names[counter].event_index; break; } ++counter; } return result; } /** * A static member function used to fill a system-event combo-box. */ std::string editable_event::system_event_name (int index) { std::string result; int counter = 0; while (s_system_event_names[counter].event_value != s_end_of_table) { int tindex = s_system_event_names[counter].event_index; if (tindex >= 0) { if (tindex == index) { result = s_system_event_names[counter].event_name; break; } } ++counter; } return result; } /** * A static member function used to fill a meta-event combo-box. */ std::string editable_event::meta_event_name (int index) { std::string result; int counter = 0; while (s_meta_event_names[counter].event_value != s_end_of_table) { int tindex = s_meta_event_names[counter].event_index; if (tindex >= 0) { if (tindex == index) { result = s_meta_event_names[counter].event_name; break; } } ++counter; } return result; } /** * A static member function used to fill a seqspec-event combo-box. */ std::string editable_event::seqspec_event_name (int index) { std::string result; int counter = 0; while (s_seqspec_event_names[counter].event_value != s_end_of_table) { int tindex = s_seqspec_event_names[counter].event_index; if (tindex >= 0) { if (tindex == index) { result = s_seqspec_event_names[counter].event_name; break; } } ++counter; } return result; } /** * Provides a static lookup function that returns the name, if any, * associated with a midibyte value. * * \param value * The MIDI byte value to look up. * * \param cat * The category of the MIDI byte. Each category calls a different name * array into play. * * \return * Returns the name associated with the value. If there is no such name, * then an empty string is returned. */ std::string editable_event::value_to_name ( midibyte value, editable_event::subgroup cat ) { std::string result; const name_value_t * const table = s_category_arrays[int(cat)]; if (cat == subgroup::channel_message) value = event::mask_status(value); midibyte counter = 0; while (table[counter].event_value != s_end_of_table) { if (value == table[counter].event_value) { result = table[counter].event_name; break; } ++counter; } return result; } /** * Provides a static lookup function that returns the value, if any, * associated with a name string. The string_match_ex() function, which can * skip spacing, numbers, and various non-letter characters, and which can * match abbreviations, case-insensitively, is used to make the string * comparisons. * * \param name * The string value to look up. * * \param cat * The category of the MIDI byte. Each category calls a different name * array into play. * * \return * Returns the value associated with the name. If there is no such value, * then s_end_of_table is returned. */ midishort editable_event::name_to_value ( const std::string & name, editable_event::subgroup cat ) { midishort result = s_end_of_table; if (! name.empty()) { const name_value_t * const table = s_category_arrays[int(cat)]; midibyte counter = 0; while (table[counter].event_value != s_end_of_table) { if (strings_match_ex(table[counter].event_name, name)) { result = table[counter].event_value; break; } ++counter; } } return result; } /** * Provides a static lookup function that takes a meta-event number and * returns the expected length of the data for that event. * * \param value * The MIDI byte value to look up. * * \return * Returns the length associated with the meta event. If the expected * length is actually 0, or is variable, then 0 is returned. */ midishort editable_event::meta_event_length (midibyte value) { midishort result = 0; midibyte counter = 0; while (sm_meta_lengths[counter].event_value != s_end_of_table) { if (value == sm_meta_lengths[counter].event_value) { result = sm_meta_lengths[counter].event_length; break; } ++counter; } return result; } /** * Principal constructor. * * The default constructor is hidden and unimplemented. We will get the * default controller name from the controllers module. We should also be * able to look up the selected buss's entries for a sequence, and load up * the CC/name pairs on the fly. * * \param parent * Provides the overall editable-events object that manages the whole set * of editable-event. */ editable_event::editable_event (const editable_events & parent) : event (), m_parent (&parent), m_link_time (c_null_midipulse), m_category (subgroup::name), m_name_category (), m_format_timestamp (timestamp_measures), m_name_timestamp (), m_name_status (), m_name_meta (), m_name_seqspec (), m_name_channel (), m_name_data () { // No code needed } /** * Event constructor. This function basically adds all of the extra * editable_event stuff to a standard event, so that the resulting * editable_event is container-ready. */ editable_event::editable_event ( const editable_events & parent, const event & ev ) : event (ev), m_parent (&parent), m_link_time (c_null_midipulse), m_category (subgroup::name), m_name_category (), m_format_timestamp (timestamp_measures), m_name_timestamp (), m_name_status (), m_name_meta (), m_name_seqspec (), m_name_channel (), m_name_data () { if (is_linked()) m_link_time = ev.link()->timestamp(); } /** * \setter m_category by value * Also keeps the m_name_category member in synchrony. Note that a bad * value is translated to the enum value subgroup::name. * * \param c * Provides the category value to set. */ void editable_event::category (editable_event::subgroup c) { if (c >= subgroup::channel_message && c <= subgroup::seqspec_event) m_category = c; else m_category = subgroup::name; std::string name = value_to_name(static_cast(c), subgroup::name); if (! name.empty()) m_name_category = name; } /** * \setter m_category by name * Also keeps the m_name_category member in synchrony, but looks up the * name, rather than using the name parameter, to avoid storing * abbreviations. Note that a bad value is translated to the value of * subgroup::name. * * \param name * Provides the category name for the category value to set. */ void editable_event::category (const std::string & name) { midishort catcode = name_to_value(name, subgroup::name); if (catcode < s_end_of_table) m_category = static_cast(catcode); else m_category = subgroup::name; m_name_category = value_to_name ( static_cast(m_category), subgroup::name ); } /** * \setter event::set_timestamp() * Implemented to allow a uniform naming convention that is not * slavish to the get/set crowd [this ain't Java]. Plus, we also * have to set the string version at the same time. * * The format of the string representation is of the format selected by the * m_format_timestamp member and is set by the format_timestamp() function. * * \param ts * Provides the timestamp in units of MIDI pulses. */ void editable_event::timestamp (midipulse ts) { event::set_timestamp(ts); (void) format_timestamp(); } /** * \setter event::set_timestamp() [string version] * * The format of the string representation is of the format selected by the * m_format_timestamp member and is set by the format_timestamp() function. * * \param ts_string * Provides the timestamp in units of MIDI pulses. */ void editable_event::timestamp (const std::string & ts_string) { if (not_nullptr(parent())) { midipulse ts = parent()->string_to_pulses(ts_string); event::set_timestamp(ts); (void) format_timestamp(); } } /** * Formats the current timestamp member as a string. The format of the * string representation is of the format selected by the m_format_timestamp * member. */ std::string editable_event::format_timestamp () { if (m_format_timestamp == timestamp_measures) m_name_timestamp = time_as_measures(); else if (m_format_timestamp == timestamp_time) m_name_timestamp = time_as_minutes(); else if (m_format_timestamp == timestamp_pulses) m_name_timestamp = time_as_pulses(); else m_name_timestamp = "unsupported category in editable event"; return m_name_timestamp; } /** * Converts the current time-stamp to a string representation in units of * measures, beats, and divisions. Cannot be inlined because of a circular * dependency between the editable_event and editable_events classes. */ std::string editable_event::time_as_measures () { if (not_nullptr(parent())) { return pulses_to_measurestring(timestamp(), parent()->timing()); } else { static std::string s_dummy; return s_dummy; } } /** * Converts the current time-stamp to a string representation in units of * hours, minutes, seconds, and fraction. Cannot be inlined because of a * circular dependency between the editable_event and editable_events * classes. */ std::string editable_event::time_as_minutes () { if (not_nullptr(parent())) { return pulses_to_time_string(timestamp(), parent()->timing()); } else { static std::string s_dummy; return s_dummy; } } /** * Converts a string into an event status, along with timestamp and data * bytes. Currently, this function handles only the following two messages: * * - subgroup::channel_message. * Handle Meta or SysEx events, setting that status to 0xFF and the * meta-type (in the m_channel member) to the meta event type-value, * then filling in get_sysex() based on the field values in the sd0 * parameter. * - subgroup::system_message. * - subgroup::meta_event. * * The Tempo data 0 field consists of one double BPM value. We convert it to * a tempo-in-microseconds value, then populate a 3-byte array with it. Then * we need to create an event from it. * * The Time Signature data 0 field consists of a string like "4/4". The data * 1 field has two values for metronome support. First, parse the "nn/dd" * string; the slash (solidus) is required. Then get the cc and bb metronome * values, if present. Otherwise, hardwired them to values of 0x18 and 0x08. * * After all of the numbering member items have been set, they are converted * and assigned to the string versions via a call to the analyze() function. * * \param ts * Provides the time-stamp string of the event. * * \param s * Provides the name of the event, such as "Program Change". * * \param sd0 * Provides the string defining the first data byte of the event. For * Meta events, this might have multiple values, though we support only * Set Tempo and Time Signature at present. * * \param sd1 * Provides the string defining the second data byte of the event, if * applicable to the event. Some meta event may provide multiple values * in this string. * * \param chan * Provides the string name for the channel. If empty (the default * value), the channel is not changed. The name of the channel is re * "1", not 0. */ void editable_event::set_status_from_string ( const std::string & ts, const std::string & s, const std::string & sd0, const std::string & sd1, const std::string & chan, const std::string & text ) { midishort value = name_to_value(s, subgroup::channel_message); timestamp(ts); if (value != s_end_of_table) /* channel message */ { midibyte status = midibyte(value); midibyte c = string_to_midibyte(chan, 1) - 1; /* default == 0 */ midibyte d0 = string_to_midibyte(sd0); midibyte d1 = string_to_midibyte(sd1); set_channel_status(status, c); if (is_one_byte_msg(status)) set_data(d0); else if (is_two_byte_msg(status)) set_data(d0, d1); } else { value = name_to_value(s, subgroup::meta_event); if (value != s_end_of_table) /* meta message */ { set_meta_status(value); if (value == EVENT_META_SET_TEMPO) /* 0x51 */ { double bp = string_to_double(text); if (bp <= 0.0f) bp = string_to_double(sd0); if (bp > 0.0f) (void) set_tempo(bp); } else if (value == EVENT_META_TIME_SIGNATURE) /* 0x58 */ { midibytes t; bool ok = time_signature_bytes(text, t); if (ok) (void) set_sysex(t); } else if (value == EVENT_META_KEY_SIGNATURE) /* 0x59 */ { midibytes k; bool ok = key_signature_bytes(text, k); if (ok) (void) set_sysex(k); } else if ( value >= EVENT_META_TEXT_EVENT && value <= EVENT_META_CUE_POINT ) { (void) set_text(text); } else if (value == EVENT_MIDI_SYSEX) { sysex s; bool ok = sysex_bytes(text, s); if (ok) (void) set_sysex(s); } else { /* * These have to be system events. TODO */ } } } analyze(); /* create the strings */ } /** * This function can modify the data bytes and the channel of a channel * event. For example, it can change the note number, note velocity, and * note channel. Not modified are the name and type of the event, and its * timestamp. This function is useful in modifying the linked note event of * a Note On/Off event. */ void editable_event::modify_channel_status_from_string ( const std::string & sd0, const std::string & sd1, const std::string & chan ) { midibyte status = mask_status(get_status()); midibyte c = midibyte(string_to_int(chan) - 1); set_channel_status(status, c); /* pass in status and channel */ if (is_one_byte_msg(status) || is_pitchbend_msg(status)) { /* * Do not change the Program Change or Channel Pressure data. * Do not change the Coarse or Fine Pitchbend. */ } else if (is_two_byte_msg(status)) { midibyte d0 = string_to_midibyte(sd0); midibyte d1 = string_to_midibyte(sd1); if (is_note_msg(status)) /* Note On/Off and Aftertouch */ { d1 = note_velocity(); /* keep velocity or pressure */ } else if (is_controller_msg(status)) /* keep CC# and CC value */ { get_data(d0, d1); } set_data(d0, d1); } analyze(); /* (re)create the strings */ } /** * Converts the event into a string desribing the full event. We get the * time-stamp as a string, make sure the event is fully analyzed so that all * items and strings are set correctly. * * \return * Returns a human-readable string describing this event. This string is * displayed in an event list, such as in the eventedit module. */ std::string editable_event::stock_event_string () { char temp[64]; std::string ts = format_timestamp(); analyze(); if (is_ex_data()) { if (is_tempo() || is_time_signature()) { snprintf ( temp, sizeof temp, "%9s %-11s %-10s", ts.c_str(), m_name_status.c_str(), m_name_data.c_str() ); } else { snprintf ( temp, sizeof temp, "%9s %-11s %-12s", ts.c_str(), m_name_status.c_str(), m_name_data.c_str() ); } } else { snprintf ( temp, sizeof temp, "%9s %-11s %-10s %-20s", ts.c_str(), m_name_status.c_str(), m_name_channel.c_str(), m_name_data.c_str() ); } return std::string(temp); } /** * Analyzes an editable-event to make all the settings it needs. Used in the * constructors. Some of the setters indirectly set the appropriate string * representation, as well. * * Category: * * This function can figure out if the status byte implies a channel * message or a system message, and set the category string as well. * However, at this time, detection of Meta events (0xFF) or * Proprietary/SeqSpec events (0xFF with 0x2424) doesn't work due to lack * of context here (and due to the fact that currently such events are * not yet stored in a Seq66 sequence/track, and the * least-significant-byte gets masked off anyway.) * * Status: * * We distinguish between channel and system messages, and then one- and * two-byte messages, but don't yet distinguish the data values fully. * * Sysex and Meta events: * * We are starting to support events with statuses ranging from 0xF0 to * 0xFF, with a concentration on Set Tempo and Time Signature events. * We want them to be full-fledged Seq66 events. * * The 0xFF byte represents a Meta event, not a Reset event, when we're * dealing with data from a MIDI file, as we are here. And we need to get * the next byte after the status byte. * * We want Set Tempo events to appear as "Tempo 120.0" and Time Signature * events to appear as "Time Sig 4/4". */ void editable_event::analyze () { midibyte status = get_status(); (void) format_timestamp(); if (is_channel_msg(status)) { char tmp[32]; int ch = int(channel()) + 1; int di0, di1; midibyte d0, d1; get_data(d0, d1); di0 = int(d0); di1 = int(d1); category(subgroup::channel_message); status = event::mask_status(status); /* * Get channel message name (e.g. "Program change"); */ m_name_status = value_to_name(status, subgroup::channel_message); snprintf(tmp, sizeof tmp, "%d", ch); /* no "Ch", too much */ m_name_channel = std::string(tmp); if (is_one_byte_msg(status)) { snprintf(tmp, sizeof tmp, "Data %d", di0); } else { if (is_note_msg(status)) snprintf(tmp, sizeof tmp, "Key %d Vel %d", di0, di1); else snprintf(tmp, sizeof tmp, "Data %d, %d", di0, di1); } m_name_data = std::string(tmp); } else if (is_system_msg(status)) { if (is_meta_msg(status)) { char tmp[32]; int ch = int(channel()); snprintf(tmp, sizeof tmp, "0x%02x", ch); midibyte metatype = get_meta_status(); /* stored in channel! */ category(subgroup::meta_event); m_name_status = value_to_name(metatype, subgroup::meta_event); m_name_channel = std::string(tmp); m_name_data = ex_data_string(); } else { /* * Get system message name (e.g. "SysEx start"); */ category(subgroup::system_message); m_name_status = value_to_name(status, subgroup::system_message); m_name_channel.clear(); m_name_data.clear(); } } else { /* * Would try to detect SysEx versus Meta message versus SeqSpec here. * Then set either m_name_meta and/or m_name_seqspec. * ALso see eventslots::set_current_event(). */ } } /** * Assuming the event is a Meta event or a SysEx, this function returns a * short string representation of the event data, usable in the eventeditor * class or elsewhere. Most SysEx events will only show the first few bytes; * we could make a SysEx viewer/editor for handling long events. * * \return * Returns the data string. If empty, the data is bad in some way, or * the event is not a Meta event. */ std::string editable_event::ex_data_string () const { std::string result; char tmp[32]; if (is_tempo()) { snprintf(tmp, sizeof tmp, "%6.2f", tempo()); result = tmp; } else if (is_time_signature()) { if (sysex_size() > 0) { int nn = get_sysex(0); int dd = beat_power_of_2(get_sysex(1)); /* 2^sysex[1] */ int cc = get_sysex(2); int bb = get_sysex(3); snprintf(tmp, sizeof tmp, "%d/%d 0x%X 0x%X", nn, dd, cc, bb); result += tmp; } } else if (is_meta_text()) { std::string data; int limit = sysex_size(); /* the length of the text */ if (limit > 25) limit = 25; /* we have space limits */ result += "'"; for (int i = 0; i < limit; ++i) { result += get_sysex(i); } if (sysex_size() > limit) result += "...'"; else result += "'"; } else { std::string data; int limit = sysex_size(); /* number of SysEx bytes */ if (limit > 9) limit = 9; /* we have space limits */ for (int i = 0; i < limit; ++i) { snprintf(tmp, sizeof tmp, "%02X ", get_sysex(i)); result += tmp; } if (sysex_size() > limit) result += "..."; } return result; } /** * Assuming the event is a Meta text event, this function returns a * short string representation of the event data in ASCII text. * Only a few characters are shown, at present. * * \return * Returns the data string. If empty, the data is bad in some way, or * the event is not a Meta event. */ std::string editable_event::ex_text_string () const { std::string result; int limit = sysex_size(); if (limit > 24) limit = 24; /* we have space limits */ for (int i = 0; i < limit; ++i) { char ch = char(get_sysex(i)); result += ch; } if (sysex_size() > limit) result += "..."; return result; } /** * Gets the values in the m_sysex vector and converts them to a strictly * human readable string in plaintext extended (values 0 to 255) ASCII. * If the event is a text event, then this function converts the midibytes * to a string. * * If it is another meta event, the data is converted to a human-readable * string using decimal numbers. * * If it is a sysex event, the data is converted to a series of 0xnn hex * values. * * For channel events, an empty string is returned, since they don't * populate m_sysex. * * \return * Returns the text if valid, otherwise returns an empty string. * * True? * * Note that the text is in "midi-bytes" format, where characters greater * than 127 are encoded as a hex value, "\xx". */ std::string editable_event::get_text () const { std::string result; if (is_meta_text()) /* FF 01-07 len text */ { size_t dsize = get_sysex().size(); for (size_t i = 0; i < dsize; ++i) /* TODO SEE "True?" ABOVE */ { char c = char(get_sysex(i)); result.push_back(c); /* plain-text */ } } else if (is_tempo()) /* FF 51 03 tt tt tt */ { midibpm bp = tempo(); char tmp[16]; (void) snprintf(tmp, sizeof tmp, "%.2f", bp); /* "120.12" */ result = std::string(tmp); } else if (is_key_signature()) /* FF 59 02 -7-+7 0-1 */ { int sharpsflats = int(get_sysex(0)); /* -7 to +7 */ bool isminor = get_sysex(1) != 0; result = key_signature_string(sharpsflats, isminor); if (result.empty()) result = "Key signature format error!"; } else if (is_time_signature()) /* FF 58 04 nn dd cc bb */ { int n = int(get_sysex(0)); int d = beat_power_of_2(int(get_sysex(1))); /* 2^sysex[1] */ int c = int(get_sysex(2)); int b = int(get_sysex(3)); result = time_signature_string(n, d, c, b); } else if (is_sysex()) /* F0 id msg-bytes ... F7 */ { result = sysex_string(get_sysex()); } return result; } bool editable_event::set_text (const std::string & s) { bool result = ! s.empty(); if (result) { get_sysex().clear(); if (is_meta_text()) { std::string converted = string_to_midi_bytes(s); for (const auto c : converted) get_sysex().push_back(c); } else result = event::set_text(s); /* no "\xx" conversion is done */ } return result; } /** * An external function to construct a human-readable string from * time-signature values. * * Compare these functions with the key-signature functions in the scales * module. * * \param n * Provides the numerator of the time signature, the beats per measure. * * \param d * Provides the denominator of the time signature, the beat width. * If not a power of 2, then this function will still work, but the text * doesn't represent a proper MIDI time signature. * * \param c * Provides the number of MIDI clock ticks that occur for every tick * of the metronome. Defaults to 24. * * \param b * Provides the number of 32nd notes per beats. Defaults to 8. */ std::string time_signature_string (int n, int d, int c, int b) { char tmp[32]; (void) snprintf ( tmp, sizeof tmp, "%d/%d %d %d", n, d, c, b /* "3/4 24 8" */ ); return std::string(tmp); } /** * An external function to convert text to the midibytes for a time signature. * * \param text * Provides a string of the form "nn/dd cc bb", such as * "3/4 24 8". Note the dd must be a power of 2, that 24 is * a common MIDI clock ticks/metronome value, and we have here * 8 32nd notes per beat (quarter note). * * \param [out] timesigbytes * Provides a vector that will be 4 bytes in length, to hold * the converted values that can be written to a MIDI file. * * \return * Returns true if no errors were found. If false, the timesigbytes * vector is empty. */ bool time_signature_bytes ( const std::string & text, midibytes & timesigbytes ) { bool result = false; auto pos = text.find_first_of("/"); timesigbytes.clear(); if (pos != std::string::npos) { int nn = string_to_int(text); ++pos; std::string partial = text.substr(pos); /* drop the "nn/" */ result = ! partial.empty(); if (result) { int dd = string_to_int(partial); /* user value, 4, 8 ... */ int cc = 0x18; /* 24 is a common value */ int bb = 0x08; /* 8/32nds per beat */ result = is_power_of_2(dd); if (result) { dd = beat_log2(dd); /* convert to 2-power */ pos = text.find_first_of(" ", pos); /* bypass the "dd" */ if (pos != std::string::npos) { pos = text.find_first_of("0123456789x", pos); if (pos != std::string::npos) { cc = int(strtol(&text[pos], NULL, 0)); pos = text.find_first_of(" ", pos); if (pos != std::string::npos) { pos = text.find_first_of("0123456789x", pos); if (pos != std::string::npos) bb = int(strtol(&text[pos], NULL, 0)); } } } timesigbytes.push_back(midibyte(nn)); timesigbytes.push_back(midibyte(dd)); timesigbytes.push_back(midibyte(cc)); timesigbytes.push_back(midibyte(bb)); } else { /* anything we can leverage here? */ } } } return result; } /** * Converts the SysEx midibytes to a string of hex values of the form * "0xnn 0xnn ... 0xnn". */ std::string sysex_string (const event::sysex & s) { std::string result; if (s.size() > 0) { char tmp[8]; for (auto b : s) { (void) snprintf(tmp, sizeof tmp, "0x%02x ", unsigned(b)); result += tmp; } } return result; } bool sysex_bytes ( const std::string & text, event::sysex & sxbytes ) { tokenization tokens = tokenize(text); bool result = ! tokens.empty(); sxbytes.clear(); if (result) { try { for (auto t : tokens) { midibyte b = midibyte(std::stoi(t, nullptr, 0)); sxbytes.push_back(b); } } catch (std::invalid_argument const &) { result = false; sxbytes.clear(); } } return result; } } // namespace seq66 /* * editable_event.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/editable_events.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file editable_events.cpp * * This module declares/defines the base class for an ordered container of * MIDI editable_events. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-12-04 * \updates 2023-07-04 * \license GNU GPLv2 or above * * A MIDI editable event is encapsulated by the seq66::editable_events * object. */ #include "midi/editable_events.hpp" /* seq66::editable_events */ #include "play/sequence.hpp" /* seq66::sequence */ namespace seq66 { /* * We will get the default controller name from the controllers module. * We should also be able to look up the selected buss's entries for a * sequence, and load up the CC/name pairs on the fly. */ /** * This constructor hooks into the sequence object. * * \param seq * Provides a reference to the sequence object, which provides the events * and some of the MIDI timing parameters. * * \param bpm * Provides the beats/minute value, which the caller figures out how to * get and provides in this parameter. */ editable_events::editable_events (sequence & s, midibpm bp) : m_events (), m_current_event (m_events.end()), m_seq (s), m_midi_parameters ( bp, s.get_beats_per_bar(), s.get_beat_width(), s.get_ppqn() ) { // Empty body } /** * This copy constructor initializes most of the class members. * Note that we need to reconstitute the event links here, as well. * * \note * Like eventlist::verify_and_link(), this class counts on the caller * (in this case, the user-interface instead of the sequence), to call it. * * \param rhs * Provides the editable_events object to be copied. */ editable_events::editable_events (const editable_events & rhs) : m_events (rhs.m_events), m_current_event (rhs.m_current_event), m_seq (rhs.m_seq), m_midi_parameters (rhs.m_midi_parameters) { // no code } /** * This principal assignment operator sets most of the class members. * Note that we need to reconstitute the event links here, as well. * * \param rhs * Provides the editable_events object to be assigned. * * \return * Returns a reference to "this" object, to support the serial assignment * of editable_eventss. */ editable_events & editable_events::operator = (const editable_events & rhs) { if (this != &rhs) { m_events = rhs.m_events; m_current_event = rhs.m_current_event; m_midi_parameters = rhs.m_midi_parameters; m_seq.partial_assign(rhs.m_seq); } return *this; } /** * Provides the length of the events in MIDI pulses. This function gets the * iterator for the last element and returns its length value. * * \return * Returns the timestamp of the latest event in the container. */ midipulse editable_events::get_length () const { midipulse result = 0; if (count() > 0) { auto lci = m_events.rbegin(); /* get last element */ result = lci->second.timestamp(); /* get length value */ } return result; } /** * Adds an event, converted to an editable_event, to the internal event list. * * \param e * Provides the regular event to be added to the list of editable events. * * \return * Returns true if the insertion succeeded, as evidenced by an increment * in container size. */ bool editable_events::add (const event & e) { editable_event ed(*this, e); /* make the event "editable" */ return add(ed); } /** * Adds an editable event to the internal event list. For the std::multimap * implementation, this is an option if we want to make sure the insertion * succeeded: * \verbatim * std::pair result = m_events.insert(p); * return result.second; \endverbatim * * \param e * Provides the regular event to be added to the list of editable events. * * \return * Returns true if the insertion succeeded, as evidenced by an increment * in container size. * * \sideeffect * Sets m_current_event, which can be used right-away in a * single-threaded context to get an iterator to the event via the * current_event() accessor. */ bool editable_events::add (const editable_event & e) { size_t count = m_events.size(); /* save initial size */ event::key key(e); /* create the key value */ auto p = std::make_pair(key, e); /* EventsPair */ auto ei = m_events.insert(p); /* std::multimap operation */ bool result = m_events.size() == (count + 1); if (result) current_event(ei); return result; } /** * Accesses the sequence's event-list, iterating through it from beginning to * end, wrapping each event in the list in an editable event and inserting it * into the editable-event container. * * Note that the new events will not have valid links (actually, no links). * These links are used for associating Note Off events with their respective * Note On events. To be consistent, we must take the time to reconstitute * these links, using editable_events::verify_and_link(). * * \return * Returns true if the size of the final editable_event container matches * the size of the original events container. */ bool editable_events::load_events () { bool result; int original_count = track().events().count(); for (const auto & ei : track().events()) { if (! add(ei)) break; } result = count() == original_count; return result; } /** * Erases the sequence's event container and recreates it using the edited * container of editable events. * * Note that the old events are replaced only if the container of editable * events is not empty. There are safer ways for the user to erase all the * events. * * \todo * Consider what to do about the sequence::m_is_modified flag. * * \return * Returns true if the size of the final event container matches * the size of the original editable_events container. */ bool editable_events::save_events () { bool result = count() > 0; if (result) { track().events().clear(); for (const auto & ei : events()) { if (! track().add_event(ei.second)) /* sorts the events */ break; } result = track().events().count () == count(); if (result) { /* * ca 2021-0-02 Reload in case of note changes. */ (void) track().events().verify_and_link(); /* hmmm, 0, false */ clear(); result = load_events(); } } return result; } /** * Calculates the MIDI pulses (divisions) from a string using one of the * free functions of the calculations module. */ midipulse editable_events::string_to_pulses (const std::string & ts_string) const { return track().time_signature_pulses(ts_string); } /** * Gets the index (integer position in the map) of the linked event, if any. */ int editable_events::count_to_link (const editable_event & ee) const { if (ee.is_linked()) { event::key k(ee); int index = 0; for (const auto & i : events()) { const editable_event & e = i.second; if (e.is_linked()) { event::key k2(*e.link()); if (k2 == k) return index; } ++index; } } return (-1); } /** * One can use the event::valid_status() to make sure the item was found. * Using the event::key is not fool-proof. */ editable_event & editable_events::lookup_link (const editable_event & ee) { static editable_event s_dummy_event; if (ee.is_linked()) { event::key k(ee); for (auto & i : events()) { editable_event & e = i.second; if (e.is_linked()) { event::key k2(*e.link()); if (k2 == k) return e; } } } return s_dummy_event; } /** * Prints a list of the currently-held events. Useful for debugging. */ void editable_events::print () const { printf("editable_events[%d]:\n", count()); for (const auto & i : events()) i.second.print(); } } // namespace seq66 /* * editable_events.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/event.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file event.cpp * * This module declares/defines the base class for MIDI events. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2025-07-13 * \license GNU GPLv2 or above * * A MIDI event (i.e. "track event") is encapsulated by the seq66::event * object. * * - Varinum delta time stamp. * - Event byte: * - MIDI event. * - Channel event (0x80 to 0xE0, channel in low nybble). * - Data byte 1. * - Data byte 2 (for all but patch and channel pressure). * - Non-channel event (0xF0 to 0xFF). * - SysEx (0xF0), discussed below, includes data bytes. * - Song Position (0xF2) includes data bytes. * - Song Select (0xF3) includes data bytes. * - The rest of the non-channel events don't include data * byte. * - Meta event (0xFF). * - Meta type byte. * - Varinum length. * - Data bytes. * - SysEx event. * - Start byte (0xF0) or continuation/escape byte (0xF7). * - Varinum length??? * - Data bytes (not yet fully supported in event class). * - End byte (0xF7). * * Running status is used, where status bytes of MIDI channel messages can be * omitted if the preceding event is a MIDI channel message with the same * status. Running status continues across delta-times. * * In Seq24/Seq66, none of the non-channel events are stored in an * event object. There is some provisional support for storing SysEx, but * none of the support functions are yet called. In mastermidibus, there is * support for recording SysEx data, but it is macro'ed out. In rcsettings, * there is an option for SysEx. The midibus and performer objects also deal * with Sysex. But the midifile module does not read it -- it skips SysEx. * Apparently, it does serve as a Thru for incoming SysEx, though. * See this message thread: * * http://sourceforge.net/p/seq66/mailman/message/1049609/ * * In Seq24/Seq66, the Meta events are handled directly, and they * set up sequence parameters. * * This module also defines the event::key object. */ #include /* std::memcpy() */ #include "midi/event.hpp" /* seq66::event class */ #include "midi/calculations.hpp" /* seq66::rescale_tick() */ #include "util/basic_macros.hpp" /* helpful debugging/build macros */ /* * As noted elsewhere, the song judyblue.mid does not play all notes. * This happens when the Note Off for a preceding note follows * a Note On for the same note number, and these two events have the * same time-stamp. * * Enabling this macro gives Note Offs priority over Note Ons with the * same time-stamp, and seems to fix the problem. * * However, this breaks, it seems, the recording of notes of the same * note number, so that some notes end up way too long. That's a worse * effect. */ namespace seq66 { /* * -------------------------------------------------------------- * event * -------------------------------------------------------------- */ /** * This constructor simply initializes all of the class members to default * values. */ event::event () : m_input_buss (null_buss()), /* 0xFF */ m_timestamp (0), m_status (EVENT_NOTE_OFF), /* note-off, channel 0 */ m_channel (null_channel()), /* 0x80 */ m_data (), /* a two-element array */ m_sysex (), /* an std::vector */ m_linked (), /* uninit'd iterator #124 */ m_has_link (false), m_selected (false), m_marked (false), m_painted (false) { } /** * This constructor initializes some of the class members to default * values, and provides the most oft-changed values a parameters. * * \param tstamp * Provides the timestamp of this event. * * \param status * Provides the status value. The channel nybble is cleared, since the * channel is generally provided by the settings of the sequence. * However, this value should include the channel if applicable! * * \param d0 * Provides the first data byte. There is no default value. * * \param d1 * Provides the second data byte. There is no default value. */ event::event (midipulse tstamp, midibyte status, midibyte d0, midibyte d1) : m_input_buss (null_buss()), /* 0xFF */ m_timestamp (tstamp), m_status (status), /* keep the channel 2021-08-09 */ m_channel (mask_channel(status)), m_data (), /* two-element array, midibytes */ m_sysex (), /* an std::vector of midibytes */ m_linked (), /* removed nullptr issue #124 */ m_has_link (false), m_selected (false), m_marked (false), m_painted (false) { set_data(d0, d1); /* fills the m_data array */ } /** * Creates a tempo event. */ event::event (midipulse tstamp, midibpm tempo) : m_input_buss (null_buss()), m_timestamp (tstamp), m_status (EVENT_MIDI_META), m_channel (EVENT_META_SET_TEMPO), m_data (), /* two-element array, midibytes */ m_sysex (), /* an std::vector of midibytes */ m_linked (), /* removed nullptr issue #124 */ m_has_link (false), m_selected (false), m_marked (false), m_painted (false) { set_tempo(tempo); /* fills the m_sysex vector */ } /** * Creates other Meta events. */ event::event (midipulse tstamp, midibyte metatype, const midibytes & data) : m_input_buss (null_buss()), m_timestamp (tstamp), m_status (EVENT_MIDI_META), m_channel (metatype), m_data (), /* two-element array, midibytes */ m_sysex (), /* an std::vector of midibytes */ m_linked (), /* removed nullptr issue #124 */ m_has_link (false), m_selected (false), m_marked (false), m_painted (false) { (void) append_meta_data(metatype, data); } /** * Creates a note event. */ event::event ( midipulse tstamp, midibyte notekind, /* must not include channel (ch == 0) */ midibyte channel, int note, int velocity ) : m_input_buss (null_buss()), m_timestamp (tstamp), m_status (notekind), m_channel (channel), m_data (), /* two-element array, midibytes */ m_sysex (), /* an std::vector of midibytes */ m_linked (), /* removed nullptr issue #124 */ m_has_link (false), m_selected (false), m_marked (false), m_painted (false) { m_data[0] = midibyte(note); m_data[1] = midibyte(velocity); if (is_null_channel(channel)) { m_channel = 0; } else { midibyte chan = mask_channel(channel); m_status = mask_status(notekind) | chan; m_channel = chan; } } /** * This copy constructor initializes most of the class members. This * function is currently geared only toward support of the SMF 0 * channel-splitting feature. Many of the members are not set to useful * values when the MIDI file is read, so we don't handle them for now. * * \warning * Note that now events are also copied when creating the editable_events * container, so this function is even more important. The event links, * for linking Note Off events to their respective Note On events, are * dropped. Generally, they will need to be reconstituted by calling the * eventlist::verify_and_link() function. * * \warning * This function does not yet copy the SysEx data. The inclusion * of SysEx events was not complete in Seq24, and it is still not * complete in Seq66. Nor does it currently bother with the * links, as noted above. * * \param rhs * Provides the event object to be copied. */ event::event (const event & rhs) : m_input_buss (rhs.m_input_buss), m_timestamp (rhs.m_timestamp), m_status (rhs.m_status), m_channel (rhs.m_channel), m_data (), /* a two-element array */ m_sysex (rhs.m_sysex), /* copies a vector of data */ m_linked (rhs.m_linked), /* for vector implemenation */ m_has_link (rhs.m_has_link), /* m_linked has 2 linkers! */ m_selected (rhs.m_selected), m_marked (rhs.m_marked), m_painted (rhs.m_painted) { m_data[0] = rhs.m_data[0]; m_data[1] = rhs.m_data[1]; } /** * This principal assignment operator sets most of the class members. This * function is currently geared only toward support of the SMF 0 * channel-splitting feature. Many of the member are not set to useful value * when the MIDI file is read, so we don't handle them for now. * * \warning * This function now copies the SysEx data, but the inclusion of SysEx * events was not complete in Seq24, and it is still not complete in * Seq66. Nor does it currently bother with the link the event * might have, except in the std::vector implementation. * * \param rhs * Provides the event object to be assigned. * * \return * Returns a reference to "this" object, to support the serial assignment * of events. */ event & event::operator = (const event & rhs) { if (this != &rhs) { m_input_buss = rhs.m_input_buss; m_timestamp = rhs.m_timestamp; m_status = rhs.m_status; m_channel = rhs.m_channel; m_data[0] = rhs.m_data[0]; m_data[1] = rhs.m_data[1]; m_sysex = rhs.m_sysex; m_linked = rhs.m_linked; /* vector implemenation */ m_has_link = rhs.m_has_link; /* two linkers! */ m_selected = rhs.m_selected; /* false instead? */ m_marked = rhs.m_marked; /* false instead? */ m_painted = rhs.m_painted; /* false instead? */ } return *this; } /** * This destructor explicitly deletes m_sysex and sets it to null. * The reset_sysex() function does what we need. But now that m_sysex is a * vector, no action is needed. */ event::~event () { // Automatic destruction of members is enough } /** * Copies a subset of data to the calling event. Used in the * sequence::put_event_on_bus() function to add a timestamp to outgoing * events, a lapse in earlier versions of the SeqXX series. We don't * need the following settings in an event merely to be played. * * m_channel = source.m_channel; * m_linked = source.m_linked; * m_has_link = source.m_has_link; * m_selected = source.m_selected; * m_marked = source.m_marked; * m_painted = source.m_painted; * * It is assumed that "this" event has been default constructed. * * \param tick * Provides the current tick (pulse) time of playback. This always * increases, and never loops back. * * \param source * The event to be sent. We need just some items from this * event. */ void event::prep_for_send (midipulse tick, const event & source) { m_input_buss = source.m_input_buss; m_timestamp = tick; m_status = source.m_status; m_data[0] = source.m_data[0]; m_data[1] = source.m_data[1]; m_sysex = source.m_sysex; } /** * If the current timestamp equal the event's timestamp, then this * function returns true if the current rank is less than the event's * rank. * * Otherwise, it returns true if the current timestamp is less than * the event's timestamp. * * \warning * The less-than operator is supposed to support a "strict weak * ordering", and is supposed to leave equivalent values in the same * order they were before the sort. However, every time we load and * save our sample MIDI file, events get reversed. Here are * program-changes that get reversed: * \verbatim Save N: 0070: 6E 00 C4 48 00 C4 0C 00 C4 57 00 C4 19 00 C4 26 Save N+1: 0070: 6E 00 C4 26 00 C4 19 00 C4 57 00 C4 0C 00 C4 48 \endverbatim * * The 0070 is the offset within the versions of the b4uacuse-seq64.midi * file. * * Because of this issue, and the very slow speed of loading a MIDI file * when built for debugging, we explored using an std::multimap instead * of an std::list. This worked better than a list, for loading MIDI * events, but may cause the upper limit of the number of playing * sequences to drop a little, due to the overhead of incrementing * multimap iterators versus list iterators). Now we use only the * std::vector implementation, slightly faster than using std::list. * * \param rhs * The object to be compared against. * * \return * Returns true if the time-stamp and "rank" are less than those of the * comparison object. */ bool event::operator < (const event & rhs) const { if (m_timestamp == rhs.m_timestamp) return get_rank() < rhs.get_rank(); else return m_timestamp < rhs.m_timestamp; } /** * Indicates that the events are "identical". This function is not a * replacement for operator < (). It is meant to be used in a brute force * search for one particular event in the sorted event list. * * SysEx (or text) data are not checked, just the status and channel values.. */ bool event::match (const event & target) const { bool result = false; bool ignore_ts = is_null_midipulse(target.timestamp()); if (ignore_ts || timestamp() == target.timestamp()) { result = ( get_status() == target.get_status() && channel() == target.channel() ); if (result && ! is_meta()) /* additional matching */ { result = ( m_data[0] == target.m_data[0] && m_data[1] == target.m_data[1] ); } } return result; } /** * Returns true if the event's status is *not* a control-change, but does * match the given status OR if the event's status is a control-change that * matches the given status, and has a control value matching the given * control-change value. * * \param status * The status to be checked. * * \param cc * The controller value to be matched, for control-change events. */ bool event::is_desired (midibyte status, midibyte cc) const { bool result; if (is_tempo_status(status)) { result = is_tempo(); } else { midibyte s = mask_status(status); midibyte ms = mask_status(m_status); /* ca 2023-05-22 */ result = s == ms; if (result && (s == EVENT_CONTROL_CHANGE)) result = m_data[0] == cc; } return result; } /** * This is a bit tricky. The range of pixels in the data pane is 0 to 127 * or 0 to 64 (in shrunken mode). For note events, the ranges is also * 0 to 127. The diameter of tempo grab handle is currently s_handled_d = 8, * which can represent a wide range in tempos. */ bool event::is_data_in_handle_range (midibyte target) const { bool result = false; if (is_tempo()) { static const midibpm s_delta = note_value_to_tempo(4); midibpm t = tempo(); midibpm tdesired = note_value_to_tempo(target); result = t >= (tdesired - s_delta) && t <= (tdesired + s_delta); } else if (is_program_change()) { result = true; } else { static const midibyte s_delta = 4; /* seq32 provision */ static const midibyte max = c_midibyte_value_max - s_delta; midibyte datum = is_one_byte() ? d0() : d1() ; result = target >= s_delta && target <= max; if (result) { result = datum >= (target - s_delta) && datum <= (target + s_delta); } } return result; } bool event::is_desired (midibyte status, midibyte cc, midibyte data) const { bool result; if (is_tempo_status(status)) { result = is_tempo(); } else { result = mask_status(status) == mask_status(m_status); if (result && (event::is_controller_msg(status))) { result = m_data[0] == cc; if (result) result = is_data_in_handle_range(data); /* check d0/d1() */ } } return result; } /** * We should also match tempo events here. But we have to treat them * differently from the matched status events. */ bool event::is_desired_ex (midibyte status, midibyte cc) const { bool result; /* is_desired_cc_or_not_cc */ bool match = match_status(status); if (status == EVENT_CONTROL_CHANGE) { result = match && m_data[0] == cc; /* correct status & correct CC */ } else { if (is_tempo()) result = true; /* Set tempo always editable */ else result = match; /* correct status and not CC */ } return result; } /** * Sometimes we need to alter the event completely. */ void event::set_data ( midipulse tstamp, midibyte status, midibyte d0, midibyte d1 ) { set_timestamp(tstamp); set_status(status); set_data(d0, d1); } /** * Transpose the note, if possible. * * \param tn * The amount (positive or negative) to transpose a note. If the result * is out of range, the transposition is not performered. */ void event::transpose_note (int tn) { int note = int(m_data[0]) + tn; if (note >= 0 && note < c_midibyte_data_max) m_data[0] = midibyte(note); } void event::set_channel (midibyte channel) { if (is_null_channel(channel)) { m_channel = channel; } else { int chan = mask_channel(channel); /* clears status nybble */ m_channel = chan; if (has_channel()) /* a channel message */ m_status = mask_status(m_status) | chan; } } /** * Sets the m_status member to the value of status. If \a status is a * channel event, then the channel portion of the status is cleared using * a bitwise AND against EVENT_GET_STATUS_MASK. This version is basically * the Seq24 version with the additional setting of the Seq66-specific * m_channel member. * * Found in yet another fork of seq24: // ORL fait de la merde * He also provided a very similar routine: set_status_midibus(). * * Stazed: * * The record parameter, if true, does not clear channel portion * on record for channel specific recording. The channel portion is * cleared in sequence::stream_event() by calling set_status() (record * = false) after the matching channel is determined. Otherwise, we use * a bitwise AND to clear the channel portion of the status. All events * will be stored without the channel nybble. This is necessary since * the channel is appended by midibus::play() based on the track. * * Instead of adding a "record" parameter to set_status(), we provide a more * specific function, set_status_keep_channel(), for use in the mastermidibus * class. This usage also has the side-effect of allowing the usage of * channel in the MIDI-control feature. * * \param status * The status byte, perhaps read from a MIDI file or edited in the * sequencer's event editor. Sometimes, this byte will have the channel * nybble masked off. If that is the case, the eventcode/channel * overload of this function is more appropriate. Only values with the * highest bit set are allowed, as per the MIDI specification. */ void event::set_status (midibyte status) { if (status >= EVENT_MIDI_SYSEX) /* 0xF0 and above */ { m_status = status; m_channel = null_channel(); /* channel "not applicable" */ } else if (status >= EVENT_NOTE_OFF) /* 0x80 to 0xEF */ { m_status = mask_status(status); m_channel = mask_channel(status); } } /** * This overload is useful when synthesizing events, such as converting a * Note On event with a velocity of zero to a Note Off event. See its usage * in midifile and qseventslots, for example. * * \param status * The status byte, perhaps read from a MIDI file. If the event is a * channel event, it will have its channel updated via the \a channel * parameter as well. * * \param channel * The channel byte. Combined with the event-code, this makes a valid * MIDI "status" byte. This byte is masked to guarantee the high nybble * is clear. */ void event::set_channel_status (midibyte status, midibyte channel) { m_status = status; set_channel(channel); } /** * This function is used in recording to preserve the input channel * information for deciding what to do with an incoming MIDI event. * It replaces stazed's set_status() that had an optional "record" * parameter. This call allows channel to be detected and used in MIDI * control events. It "keeps" the channel in the status byte. * * \param eventcode * The status byte, generally read from the MIDI buss. If it is not a * channel message, the channel is not modified. */ void event::set_status_keep_channel (midibyte eventcode) { m_status = eventcode; if (is_channel_msg(eventcode)) m_channel = mask_channel(eventcode); } #if defined SEQ66_THIS_FUNCTION_IS_USED void event::set_note_off (int note, midibyte channel) { midibyte chan = mask_channel(channel); m_status = EVENT_NOTE_OFF | chan; set_data(midibyte(note), 0); } #endif /** * In relation to issue #206. * * Combines a bunch of common operations in getting events. Code used in: * * - midi_in_jack::api_get_midi_event(event *) * - midi_alsa_info::api_get_midi_event(event *) * * Can we use it in these contexts? * * - wrkfile::NoteArray(int, int) * - midifile::parse_smf_1(...) [very unlikely] * * Some keyboards send Note On with velocity 0 for Note Off, so we take care * of that situation here by creating a Note Off event, with the channel * nybble preserved. Note that we call event::set_status_keep_channel() * instead of using stazed's set_status function with the "record" parameter. * Also, we have to mask in the actual channel number. * * Another note: we assume SysEx data is set as one packet currently. * That is, 0xF0 data ... 0xF7. However, note that some MIDI devices send * it in a series of small packets with a time-delay in between: * * - 0xF0 data ... MORE TO COME * * MIDI Time Code: * * MIDI Time Code (MTC) is a sub-protocol within MIDI, and is used to keep 2 * devices that control some sort of timed performance (ie, maybe a * sequencer and a video deck) in sync. MTC messages are an alternative to * using MIDI Clocks and Song Position Pointer messages. MTC is essentially * SMPTE mutated for transmission over MIDI. SMPTE timing is referenced from * an absolute "time of day". On the other hand, MIDI Clocks and Song * Position Pointer are based upon musical beats from the start of a song, * played at a specific Tempo. For many (non-musical) cues, it's easier for * humans to reference time in some absolute way rather than based upon * musical beats at a certain tempo. * * There are several MIDI messages which make up the MTC protocol. All * but one are specially defined SysEx messages. It is the quarter frame. * * Quarter Frame (see http://midi.teragonaudio.com/tech/mtc.htm): * * It has a status of 0xF1, and one subsequent data byte. It takes 8 * Quarter Frame messages to convey the current SMPTE time. For cueing * the slave to a particular start point, Quarter Frame messages are not * used. Instead, an MTC Full Frame message should be sent. The Full * Frame is a SysEx message that encodes the entire SMPTE time in * one message. * * Song Position Pointer: * * Song Position (SP) is the number of MIDI beats (1 beat = 6 MIDI * clocks) that have elapsed from the start of the song and is used * to begin playback of a sequence from a position other than * the beginning of the song. See the MIDI spec for more information. * * Song Select: * * Specifies which song or sequence is to play upon receipt of a * Start message in sequencers and drum machines capable of holding * multiple songs or sequences. * * Encapsulates some common code. This function assumes we have retrieved * the status and data bytes. * * \param timestamp * The desired or actual time-stamp for the event. * * \param buffer * Provides a pointer to the data buffer. This can also be a pointer * to the data obtained via std::vector::data(). We use a pointer * because PortMidi is C code, not C++ code. * * \param count * The number of bytes in the message. If set to 0 (the default), then * the event is a basic MIDI event, not a realtime event, and we * analyze the status byte. * * \return * Returns true if the event data could be set. If false, the event * is not a Channel Message, and must be parsed in event::create_event(). * Meta events are never sent by devices, but they might be inserted * by the Seq66 macro feature. */ bool event::set_midi_event ( midipulse tstamp, const midibyte * buffer, int count ) { midibyte eventstatus = buffer[0]; bool result = eventstatus < EVENT_MIDI_SYSEX; /* < 0xf0 */ set_timestamp(tstamp); /* set_sysex_size(count) wrong */ if (result) { if (count == 0) /* analyze event to get count */ { if (is_two_byte_msg(eventstatus)) count = 3; else if (is_one_byte_msg(eventstatus)) count = 2; else count = 1; /* should never happen */ } if (count == 3) { set_status_keep_channel(eventstatus); set_data(buffer[1], buffer[2]); if (is_note_off_recorded()) { midibyte channel = mask_channel(eventstatus); midibyte notestatus = EVENT_NOTE_OFF | channel; set_status_keep_channel(notestatus); } } else if (count == 2) { set_status_keep_channel(eventstatus); set_data(buffer[1]); } else if (count == 1) { set_status(eventstatus); clear_data(); } } else { result = true; reset_sysex(); /* set up for sysex if needed */ switch (eventstatus) { case EVENT_MIDI_SYSEX: result = append_sysex(buffer, count); if (! result) errprint("append_sysex() failed"); break; case EVENT_MIDI_META: /* 0xFF: done in create_event() */ break; /* * System Common Messages. Included in the default case (i.e. no * data bytes) are: * * EVENT_MIDI_TUNE_REQUEST = 0xF6u * * System Realtime Messages are all in the default case: * * EVENT_MIDI_CLOCK = 0xF8u * EVENT_MIDI_START = 0xFAu * EVENT_MIDI_CONTINUE = 0xFBu * EVENT_MIDI_STOP = 0xFCu * EVENT_MIDI_ACTIVE_SENSE = 0xFEu * EVENT_MIDI_RESET = 0xFFu */ case EVENT_MIDI_QUARTER_FRAME: /* 0xF1u: 1 data byte */ result = append_sysex_byte(buffer[1]); break; case EVENT_MIDI_SONG_POS: /* 0xF2u: 2 data bytes */ result = append_sysex(&buffer[1], 2); break; case EVENT_MIDI_SONG_SELECT: /* 0xF3u: 1 data byte, unused */ result = append_sysex_byte(buffer[1]); break; case EVENT_MIDI_SYSEX_END: /* 0xF7u: SysEx End or Continue */ warnprint("Unexpected SysEx End"); break; default: set_status(eventstatus); clear_data(); break; } } return result; } /** * Sets a Meta event. Meta events have a status byte of EVENT_MIDI_META == * 0xff and a channel value that reflects the type of Meta event (e.g. 0x51 * for a "Set Tempo" event. * * Note that the data bytes (if any) for this event will still need to be * added to the event via (for example) the append_sysex() or set_sysex() * function. * * \param metatype * Indicates the type of meta event. */ void event::set_meta_status (midibyte metatype) { m_status = EVENT_MIDI_META; m_channel = metatype; } /** * This base class version simply returns the bytes as characters in a * string. * * \return * Returns the text if valid, otherwise returns an empty string. * * Note that the text is in "midi-bytes" format, where characters greater * than 127 are encodes as a hex value, "\xx". */ std::string event::get_text () const { std::string result; size_t dsize = m_sysex.size(); for (size_t i = 0; i < dsize; ++i) { char c = char(m_sysex[i]); result.push_back(c); } return result; } /** * This base-class version unconditionally loads bytes into the * m_sysex vector. */ bool event::set_text (const std::string & s) { bool result = ! s.empty(); if (result) { m_sysex.clear(); for (const auto c : s) m_sysex.push_back(c); } return result; } /** * Appends Meta-event data to a new buffer. Similar to append_sysex(), but * useful for holding the data for a Meta event. Please note that Meta * events and SysEx events shared the same "extended" data buffer that * originated to support SysEx. * * Also see set_meta_status(), which, like this function, sets the * event::m_channel_member to indicate the type of Meta event, but, unlike * this function, leaves the data alone. Also note that the set_status() * call in midifile flags the event as a Meta event. The handling of Meta * events is not yet uniform between all the modules. * * \warning * Currently does not clear the "sysex" buffer first. * * \param metatype * Provides the type of the Meta event, which is stored in the m_channel * member. * * \param data * Provides the Meta event's data. If not provided, nothing is done, * and false is returned. * * \param dsize * Provides the size of the data. If not provided, nothing is done. * * \return * Returns false if an error occurred, and the caller needs to stop * trying to process the data. bool event::append_meta_data (midibyte metatype, const midibyte * data, int dsize) { bool result = not_nullptr(data) && (dsize > 0); if (result) { set_meta_status(metatype); for (int i = 0; i < dsize; ++i) m_sysex.push_back(data[i]); } else { errprint("event::append_meta_data(null data)"); } return result; } */ /** * This overload appends Meta-event data from a vector to a new buffer. * * \param metatype * Provides the type of the Meta event, which is stored in the m_channel * member. * * \param data * Provides the Meta event's data as a vector. * * \return * Returns false if an error occurred, and the caller needs to stop * trying to process the data. */ bool event::append_meta_data (midibyte metatype, const midibytes & data) { int dsize = int(data.size()); bool result = dsize > 0; if (result) { set_meta_status(metatype); /* * for (int i = 0; i < dsize; ++i) * m_sysex.push_back(data[i]); */ for (auto d : data) m_sysex.push_back(d); } else { errprint("event::append_meta_data(no data)"); } return result; } /** * An overload for logging SYSEX data byte-by-byte. If the byte is F7 * and no bytes have been pushed, then this is an EVENT_MIDI_SYSEX_CONTINUE * and starts the SysEx, rather than ending it. * * \param data * A single MIDI byte of data, assumed to be part of a SYSEX message * event. * * \return * Returns true if the event is not a SysEx-end event. The EOX ($F7) * status byte can be replaced with any other status byte except for * a Real-Time message, but this probably never is done. */ bool event::append_sysex_byte (midibyte data) { bool firstbyte = m_sysex.empty(); m_sysex.push_back(data); return firstbyte || data != EVENT_MIDI_SYSEX_END; } /** * Appends SYSEX data to a new buffer. We now use a vector instead of an * array, so there is no need for reallocation and copying of the current * SYSEX data. The data represented by data and dsize is appended to that * data buffer. * * \param data * Provides the additional SysEx/Meta data. If not provided, nothing is * done, and false is returned. * * \param dsize * Provides the size of the additional SYSEX data. If not provided, * nothing is done. * * \return * Returns true if there was data to add. The End-of-SysEx byte is * included. */ bool event::append_sysex (const midibyte * data, int dsize) { bool result = not_nullptr(data) && (dsize > 0); if (result) { for (int i = 0; i < dsize; ++i) m_sysex.push_back(data[i]); } else { errprint("event::append_sysex(): null parameters"); } return result; } bool event::append_sysex (const midibytes & data) { bool result = ! data.empty(); if (result) { for (auto b : data) m_sysex.push_back(b); } else { errprint("event::append_sysex(): no data"); } return result; } /** * Resets and adds ex data. * * \param data * Provides the SysEx/Meta data. If not provided, nothing is done, * and false is returned. * * \param len * The number of bytes to set. * * \return * Returns true if the function succeeded. */ bool event::set_sysex (const midibyte * data, int len) { reset_sysex(); return append_sysex(data, len); } bool event::set_sysex (const midibytes & data) { reset_sysex(); return append_sysex(data); } void event::set_sysex_size (int len) { if (len == 0) m_sysex.clear(); else if (len > 0) m_sysex.resize(len); } /** * Prints out the timestamp, data size, the current status byte, channel * (which is the type value for Meta events), any SysEx or * Meta data if present, or the two data bytes for the status byte. * * There's really no percentage in converting this code to use std::cout, we * feel. We might want to make it contingent on the --verbose option at some * point. */ void event::print (const std::string & tag) const { std::string buffer = to_string(); if (tag.empty()) printf("%s", buffer.c_str()); else printf("%s: %s", tag.c_str(), buffer.c_str()); } void event::print_note (bool showlink) const { if (is_note()) { bool shownote = is_note_on() || (is_note_off() && ! showlink); if (shownote) { std::string type = is_note_on() ? "On " : "Off" ; char channel[8]; if (m_channel == null_channel()) { channel[0] = '-'; channel[1] = 0; } else { snprintf(channel, sizeof channel, "%1x", int(m_channel)); } printf ( "%06ld Note %s:%s %3d Vel %02X", long(m_timestamp), type.c_str(), channel, int(m_data[0]), int(m_data[1]) ); if (is_linked() && showlink) { const_iterator mylink = link(); printf(" --> "); mylink->print_note(false); } else printf("\n"); } } } /** * Prints out the timestamp, data size, the current status byte, channel * (which is the type value for Meta events), any SysEx or * Meta data if present, or the two data bytes for the status byte. * * There's really no percentage in converting this code to use std::cout, we * feel. We might want to make it contingent on the --verbose option at some * point. */ std::string event::to_string () const { char tmp[64]; (void) snprintf(tmp, sizeof tmp, "[%06ld] (", long(m_timestamp)); const char * label = is_meta() ? "type" : "channel" ; std::string result = tmp; result += is_linked() ? "L" : "U"; result += is_marked() ? "M" : " "; result += is_selected() ? "S" : " "; result += is_painted() ? "P" : " "; result += ") "; (void) snprintf ( tmp, sizeof tmp, "event 0x%02X %s 0x%02X d0=%d d1=%d\n", unsigned(m_status), label, unsigned(m_channel), int(m_data[0]), int(m_data[1]) ); result += tmp; if (is_sysex() || is_meta()) { bool use_linefeeds = sysex_size() > 8; (void) snprintf(tmp, sizeof tmp, "SysEx/Meta[%d]: ", sysex_size()); result += tmp; for (int i = 0; i < sysex_size(); ++i) { if (use_linefeeds && (i % 16) == 0) result += "\n "; (void) snprintf(tmp, sizeof tmp, "%02X ", m_sysex[i]); result += tmp; } result += "\n"; } return result; } void event::rescale (int newppqn, int oldppqn) { set_timestamp(rescale_tick(timestamp(), newppqn, oldppqn)); } /** * This function is used in sorting MIDI status events (e.g. note on/off, * aftertouch, control change, etc.) The sort order is not determined by the * actual status values. * * The ranking, from high to low, is note off, note on, aftertouch, channel * pressure, and pitch wheel, control change, and program changes. The lower * the ranking, the more upfront an item comes in the sort order, given the * same time-stamp. * * We also now consider SysEx and Meta event. Meta comes first, SysEx comes * last. * * ca 2025-07-02, 07-03 * * Considering that bank select control values (coarse and fine) would be * followed by a program change. Can they occur simultaneously? * Just in case, we should give control change the higher priority. * Actually, no, because this screws up the order of bank change then * program change. So we make them equivalent, *and* we also change * to using std::stable_sort() [slower] in eventlist. * * Note: * We could add the channel number as part of the ranking. Sound? No. * * \return * Returns the rank of the current m_status byte. */ int event::get_rank () const { int result; if (is_sysex()) { result = 0x3000; } else if (is_meta()) { result = 0x0030; } else { int eventcode = mask_status(m_status); /* strip off channel nybble */ switch (eventcode) { case EVENT_NOTE_OFF: result = 0x2000 + get_note(); break; case EVENT_NOTE_ON: result = 0x1000 + get_note(); break; case EVENT_AFTERTOUCH: case EVENT_CHANNEL_PRESSURE: case EVENT_PITCH_WHEEL: result = 0x0500; break; case EVENT_PROGRAM_CHANGE: case EVENT_CONTROL_CHANGE: result = 0x0100; /* was 0x200 */ break; default: result = 0; break; } /* * This makes no sense. What were we thinking? * * if (result != 0) * result += mask_channel(m_status) << 8; */ } return result; } /** * Calculates the tempo from the stored event bytes, if the event is a Tempo * meta-event and has valid data. Remember that we are overloading the SysEx * support to hold Meta-event data. Also note that we're treating the vector * like an array, which is supposed to work. * * \return * Returns the result of calculating the tempo from the three data bytes. * If an error occurs, 0.0 is returned. */ midibpm event::tempo () const { midibpm result = 0.0; if (is_tempo() && sysex_size() == 3) result = bpm_from_bytes(m_sysex); return result; } /** * The inverse of tempo(). First, we convert beats/minute to a tempo * microseconds value. Then we convert the microseconds to three tempo * bytes. */ bool event::set_tempo (midibpm tempo) { double us = tempo_us_from_bpm(tempo); midibytes t; tempo_us_to_bytes(t, midibpm(us)); return set_sysex(t); } bool event::set_tempo (const midibytes & t) { double tt = tempo_us_from_bytes(t); bool result = tt > 0.0; if (result) set_sysex(t); return result; } /* * Helper functions for alteration of events. */ /** * Modifies the velocity (for notes) or some other amplitude. However, * the caller will like not want to change the amplitude of a Note On with * velocity 0. Let the caller beware! * * \param range * The range of the changes up and down. Not used if 0 or less. * * \return * Returns true if the amplitude was actually jittered. */ bool event::randomize (int range) { bool result = range > 0; if (result) { bool twobytes = is_two_bytes(); int datum = int(twobytes ? m_data[1] : m_data[0]); #if defined SEQ66_USE_UNIFORM_INT_DISTRIBUTION int delta = seq66::randomize_uniformly(range); #else int delta = seq66::randomize(range); #endif result = delta != 0; if (result) { midibyte d = clamp_midibyte_value(datum + delta); if (twobytes) m_data[1] = d; else m_data[0] = d; } } return result; } /** * Modifies the timestamp of the event by plus or minus the range value. * * \param snap * Used for keeping the time-stamp change within the snap. * Does this even make sense? * * \param range * The range of the changes up and down. Not used if 0 or less. * * \return * Returns true if the timestamp was actually jittered. */ bool event::jitter (int snap, int range, midipulse seqlength) { bool result = range > 0; if (result) { #if defined SEQ66_USE_UNIFORM_INT_DISTRIBUTION midipulse delta = midipulse(seq66::randomize_uniformly(range)); #else midipulse delta = midipulse(seq66::randomize(range)); #endif result = delta != 0; if (result) { if (delta < -snap) delta = -snap + 1; else if (delta > snap) delta = snap - 1; midipulse tstamp = timestamp() + delta; if (tstamp >= seqlength) tstamp = seqlength - 1; else if (tstamp < 0) tstamp = 0; set_timestamp(tstamp); } } return result; } /** * Division by 2 "tightens" toward the nearest snap time. * * \param snap * The time boundary length to snap to. A common value is the number of * ticks (pulses) in a 16th note. * * \param seqlength * The length in ticks of the pattern that holds this event. * * \return * Returns true if the time-stamp was actually altered. */ bool event::tighten (int snap, midipulse seqlength) { bool result = snap > 0; if (result) { midipulse t = timestamp(); midipulse tremainder = t % snap; midipulse tdelta; if (tremainder < snap / 2) tdelta = -(tremainder / 2); else tdelta = (snap - tremainder) / 2; result = tdelta != 0; if (result) { t += tdelta; if (t >= seqlength) t = seqlength - 1; else if (t < 0) t = 0; set_timestamp(t); } } return result; } /** * Quantizes the time-stamp toward the nearest snap time. * * \param snap * The time boundary length to snap to. A common value is the number of * ticks (pulses) in a 16th note. * * \param seqlength * The length in ticks of the pattern that holds this event. * * \return * Returns true if the time-stamp was actually altered. */ bool event::quantize (int snap, midipulse seqlength) { bool result = snap > 0; if (result) { midipulse t = timestamp(); midipulse tremainder = t % snap; midipulse tdelta; if (tremainder < snap / 2) tdelta = -tremainder; else tdelta = (snap - tremainder); result = tdelta != 0; if (result) { t += tdelta; if (t >= seqlength) t = seqlength - 1; else if (t < 0) t = 0; set_timestamp(t); } } return result; } /* * -------------------------------------------------------------- * event::key * -------------------------------------------------------------- */ /** * Principal event::key constructor. * * \param tstamp * The time-stamp is the primary part of the key. It is the most * important key item. * * \param rank * Rank is an arbitrary number used to order events that have the * same time-stamp. See the event::get_rank() function for more * information. */ event::key::key (midipulse tstamp, int rank) : m_timestamp (tstamp), m_rank (rank) { // Empty body } /** * Event-based constructor. This constructor makes it even easier to * create an key. Note that the call to event::get_rank() makes a * simple calculation based on the status of the event. * * \param rhs * Provides the event key to be copied. */ event::key::key (const event & rhs) : m_timestamp (rhs.timestamp()), m_rank (rhs.get_rank()) { // Empty body } /** * Provides the minimal operator needed to sort events using an key. * * \param rhs * Provides the event key to be compared against. * * \return * Returns true if the rank and timestamp of the current object are less * than those of rhs. */ bool event::key::operator < (const key & rhs) const { if (m_timestamp == rhs.m_timestamp) return (m_rank < rhs.m_rank); else return (m_timestamp < rhs.m_timestamp); } /** * Necessary for comparing keys directly (rather than in sorting). */ bool event::key::operator == (const key & rhs) const { return (m_timestamp == rhs.m_timestamp) && m_rank == rhs.m_rank; } /* * -------------------------------------------------------------- * Free functions * -------------------------------------------------------------- */ /** * Creates and returns a tempo event. */ event create_tempo_event (midipulse tick, midibpm tempo) { event e; e.set_meta_status(EVENT_META_SET_TEMPO); e.set_timestamp(tick); e.set_tempo(tempo); return e; } /** * We want to create a single event from raw bytes (not from a file), for * the purpose of inserting macros into a pattern. * * The first parameter is the time-stamp. * * For three byte events: * * EVENT_NOTE_OFF, EVENT_NOTE_ON, EVENT_AFTERTOUCH, EVENT_CONTROL_CHANGE, * EVENT_PITCH_WHEEL; * * event (midipulse tstamp, midibyte status, midibyte d0, midibyte d1) : * * status = byte[0], * d0 = byte[1] * d1 = byte[2] * * For two-byte events: * * EVENT_PROGRAM_CHANGE, EVENT_CHANNEL_PRESSURE: * * event (midipulse tstamp, midibyte status, midibyte d0, midibyte d1) : * * status = byte[0], * d0 = byte[1] * d1 = 0 or default * * Realtime MIDI Meta: * * status = byte[0], * meta-type = byte[1] * length = varinum bytes [2 to 4] * data = length bytes * * Realtime SysEx: * * status = byte[0], * length = varinum bytes [1 to 3] * data = length bytes * * Seq66-specific SeqSpecs are not handled. * * https://www.songstuff.com/recording/article/midi-message-format/ */ event create_event (midipulse tstamp, const midibytes & dbytes) { event result; bool is_set = result.set_midi_event(tstamp, dbytes.data(), dbytes.size()); if (! is_set) { midibyte eventstatus = dbytes[0]; if (eventstatus == EVENT_MIDI_META) { midibyte metatype = dbytes[1]; int index = 2; midilong len = extract_varinum(dbytes, index); result.set_meta_status(metatype); (void) result.set_sysex(dbytes.data() + index, len); } else { warnprint("unhandled MIDI event"); } } return result; } } // namespace seq66 /* * event.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/eventlist.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file eventlist.cpp * * This module declares/defines a class for handling MIDI events in a list * container. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-09-19 * \updates 2025-10-22 * \license GNU GPLv2 or above * * This container now can indicate if certain Meta events (time-signaure or * tempo) have been added to the container. */ #include /* std::stable_sort() */ #include "cfg/settings.hpp" /* seq66::usr() */ #include "midi/eventlist.hpp" /* seq66::eventlist */ namespace seq66 { /** * Principal constructor. */ eventlist::eventlist () : m_events (), m_match_iterating (false), m_match_iterator (m_events.end()), m_length (0), m_note_off_margin (3), m_zero_len_correction (16), m_is_modified (false), m_has_tempo (false), m_has_time_signature (false), m_has_key_signature (false), m_link_wraparound (usr().pattern_wraparound()) { // No code needed } /** * We have to now define this copy constructor because the atomic copy * constructor is deleted, making the compiler-generated copy constructor * ill-formed. */ eventlist::eventlist (const eventlist & rhs) : m_events (rhs.m_events), m_match_iterating (false), m_match_iterator (m_events.end()), m_length (rhs.m_length), m_note_off_margin (rhs.m_note_off_margin), m_zero_len_correction (rhs.m_zero_len_correction), m_is_modified (rhs.m_is_modified), m_has_tempo (rhs.m_has_tempo), m_has_time_signature (rhs.m_has_time_signature), m_has_key_signature (rhs.m_has_key_signature), m_link_wraparound (rhs.m_link_wraparound) { // no code } eventlist & eventlist::operator = (const eventlist & rhs) { if (this != &rhs) { m_events = rhs.m_events; m_match_iterating = rhs.m_match_iterating; /* ok? */ m_match_iterator = rhs.m_match_iterator; /* ok? */ m_length = rhs.m_length; m_note_off_margin = rhs.m_note_off_margin; m_zero_len_correction = rhs.m_zero_len_correction; m_is_modified = rhs.m_is_modified; m_has_tempo = rhs.m_has_tempo; m_has_time_signature = rhs.m_has_time_signature; m_has_key_signature = rhs.m_has_key_signature; m_link_wraparound = rhs.m_link_wraparound; } return *this; } /** * Provides the minimum and maximux timestamps of the events, in MIDI pulses. * These functions get the iterator for the first or last element and returns * its value. Obviously the events must already be sorted. * * \return * Returns the timestamp of the first or last event in the container. */ midipulse eventlist::get_min_timestamp () const { midipulse result = 0; if (count() > 0) { auto lci = m_events.begin(); /* get 1st element */ result = lci->timestamp(); /* get length value */ } return result; } midipulse eventlist::get_max_timestamp () const { midipulse result = 0; if (count() > 0) { auto lci = m_events.rbegin(); /* get last element */ result = lci->timestamp(); /* get length value */ } return result; } /** * Adds an event to the internal event list without sorting. It is a * wrapper, wrapper for insert() or push_front(), with an option to call * sort(). * * The add() function without sorting, useful to speed up the initial * container loading into the event-list. * * For the std::multimap implementation, This is an option if we want to make * sure the insertion succeed. * * If the std::list implementation has been built in, then the event list is * not sorted after the addition. This is a time-consuming operation. * * We also have to raise some new flags if the event is a Set Tempo or * Time Signature event, so that we do not force the current tempo and * time-signature when writing the MIDI file. * * \warning * This pushing (and, in writing the MIDI file, the popping), * causes events with identical timestamps to be written in * reverse order. Doesn't affect functionality, but it's puzzling * until one understands what is happening. That's why we're * now preferring to use an std::multimap as the container. But see the * new news on using std::vector. * * \param e * Provides the event to be added to the list. * * \return * Returns true. We assume the insertion succeeded, and no longer care * about an increment in container size. It's a multimap, so it always * inserts, and if we don't have memory left, all bets are off anyway. */ bool eventlist::append (const event & e) { m_events.push_back(e); /* std::vector operation */ m_is_modified = true; if (e.is_tempo()) m_has_tempo = true; if (e.is_time_signature()) m_has_time_signature = true; if (e.is_key_signature()) m_has_key_signature = true; return true; } /** * An internal function to add events to a temporary list. Used in * quantization and tightening operations. */ bool eventlist::add (event::buffer & evlist, const event & e) { evlist.push_back(e); /* std::vector operation */ std::stable_sort(evlist.begin(), evlist.end()); return true; } /** * Adds an event to the internal event list in a sorted manner. Note * that, for speed, it is better to call append() for each event, and * then later sort them. * * \param e * Provides the event to be added to the list. * * \return * Returns true. We assume the insertion succeeded, and no longer * care about an increment in container size. It's a multimap, so it * always inserts, and if we don't have memory left, all bets are off * anyway. */ bool eventlist::add (const event & e) { bool result = append(e); if (result) sort(); return result; } /** * Sorts the event list. For the vector, equivalent elements are not * guaranteed to keep their original relative order [see * std::stable_sort(), which we could try at some point]. */ void eventlist::sort () { std::stable_sort(m_events.begin(), m_events.end()); } /** * An internal function to merge events from a temporary list. Used in * quantization and tightening operations. */ void eventlist::merge (const event::buffer & evlist) { std::size_t totalsize = m_events.size() + evlist.size(); m_events.reserve(totalsize); m_events.insert(m_events.end(), evlist.begin(), evlist.end()); sort(); } /** * Provides a merge operation for the event container managed by this * eventlist. The event::buffer container is a vector. We don't have to * presort the container in this case. * * Each element of T is inserted at the position that corresponds to its * value according to the strict weak ordering defined by operator <. The * resulting order of equivalent elements is stable (i.e. equivalent elements * preserve the relative order they had before the call, and existing * elements precede those equivalent inserted from x). The function does * nothing if (&x == this). * * For std::multimap, sorting is automatic. However, unless * move-construction is supported, merging will be less efficient than for * the list (now a vector) version. * * \param el * Provides the event list to be merged into the current event list. * * \param presort * If true (the default), then the events are presorted. This is a * requirement for merging an std::list or std::vector, but is a no-op * for the std::multimap implementation (which no longer exists). */ bool eventlist::merge (const eventlist & el, bool presort) { if (presort) /* not really necessary here */ { eventlist & el_nc = const_cast(el); el_nc.sort(); } std::size_t totalsize = m_events.size() + el.m_events.size(); m_events.reserve(totalsize); m_events.insert(m_events.end(), el.m_events.begin(), el.m_events.end()); /* * Done via verify_and_link(): sort(); */ bool result = m_events.size() == totalsize; if (result) verify_and_link(); return result; } /** * Links a new event. This function checks for a note on, then looks for * its note off. This function is provided in the eventlist because it * does not depend on any external data. Also note that any desired * thread-safety must be provided by the caller. * * Link wraparound: * * This is a Stazed addition; not in seq24. Not sure that we need it, it * is now optional. It can handle cases where the Note Off comes before * the Note On (i.e. the note wraps around to the beginning of the * pattern). * * Without it, we can get unlinked notes when the key press lasts too * long (which can be removed by the 'u' keystroke in the piano roll). * With it, the note extends to the end of the pattern and then wraps * around to the beginning. * * For recording, to avoid issues, make the pattern length one measure * longer than desired while recording. * * We could add a feature to truncate the note. Think! * * \param wrap * Optionally (the default is false) wrap when relinking. Can be used to * override usr().pattern_wraparound(). Defaults to false. * * \return * Returns true if any events were linked. */ bool eventlist::link_new (bool wrap) { bool result = false; for (auto eon = m_events.begin(); eon != m_events.end(); ++eon) { if (eon->on_linkable()) /* note-on, not linked */ { bool endfound = false; /* end-of-note flag */ auto eoff = eon; /* point to note on */ ++eoff; /* get next element */ while (eoff != m_events.end()) { endfound = link_notes(eon, eoff); /* calls off_linkable() */ if (endfound) { result = true; break; } ++eoff; } if (! endfound) { eoff = m_events.begin(); while (eoff != eon) { bool wrapped = eoff->timestamp() < eon->timestamp(); if (link_notes(eon, eoff)) { result = true; if (wrapped && ! wrap) eoff->set_timestamp(get_length() - 1); } ++eoff; } } } } return result; } #if defined SEQ66_LINK_NEWEST_NOTE_ON_RECORD /* undefined */ /** * This function links only the latest note, and should be called * only for Note Off events. It requires the following: * * - The note is the last one appended. * - It is a Note Off. * - All previous notes are already linked. * * The caller assumes the above responsibilities. Additional checks are made: * * - The Note On is not already linked. * - The Note Off is on the same channel as the Note On. * - The note value (pitch) is the same for both notes. * - The time-stamp of the Note Off is at are after the Note On. * If right at it (due to quantization), then a zero-length * correction is made for the Note Off. * * Note that we cannot (easily) use a reverse_iterator because * the event object stores links as normal iterators. It's a real * issue to convert between reverse and forward iterators, or pointers * or references. Luckily, the container (std::vector) has bidirectional * iterators. * * We could change to using the std::bidirectional_iterator once we * graduate to C++20, which won't be for some time. */ void eventlist::link_new_note () { if (count() > 1) { bool done = false; for (auto off = m_events.end(); off != m_events.begin(); /* none */ ) { --off; /* can't use end() val */ if (off->off_linkable()) /* note-off, not linked */ { auto on = off; while (! done) { --on; if (on == m_events.begin()) { done = true; break; } bool ok = on->on_linkable(); if (ok) ok = on->channel() == off->channel(); if (ok) ok = on->get_note() == off->get_note(); if (ok) { ok = on->timestamp() <= off->timestamp(); if (on->timestamp() == off->timestamp()) { long ts = on->timestamp(); ts += m_zero_len_correction; off->set_timestamp(ts); #if defined SEQ66_PLATFORM_DEBUG_TMI printf ("Zero-length note @%ld fixed\n", ts); #endif } } if (ok) { (void) link_notes(on, off); done = true; } } } if (done) break; } } } #endif // defined SEQ66_LINK_NEWEST_NOTE_ON_RECORD /** * If we're in legacy merge mode for a loop, the Note Off is actually earlier * than the Note On. And in replace mode, the Note On is cleared, leaving us * with a dangling Note Off event. * * We should consider, in both modes, automatically adding the Note Off at * the end of the loop and ignoring the next note off on the same note from * the keyboard. * * Careful! * * \param eon * Provides an event already known to satisfy the event::on_linkable() * function. * * \param eoff * Provides an event that will be checked according to * event::off_linkable(). * * \return * Returns true if the notes were linked. */ bool eventlist::link_notes (event::iterator eon, event::iterator eoff) { bool result = eon->off_linkable(eoff); if (result) { eon->link(eoff); eoff->link(eon); if (eon->timestamp() == eoff->timestamp()) { long ts = eon->timestamp(); ts += m_zero_len_correction; eoff->set_timestamp(ts); #if defined SEQ66_PLATFORM_DEBUG_TMI printf ("Zero-length note @%ld fixed\n", ts); #endif } } return result; } /** * This function verifies state: all note-ons have an off, and it links * note-offs with their note-ons. * * No longer correct: * * It also links the tempos in a separate pass (it makes the logic easier * and the amount of time should be unnoticeable to the user). * * Stazed (seq32): * * This function now deletes any notes that are >= m_length, so any * resize or move of notes must modify for wrapping if Note Off is >= * m_length. * * \threadunsafe * As in most case, the caller will use an automutex to call this * function safely. * * \param slength * Provides the length beyond which events will be pruned. Normally the * caller supplies sequence::get_length(). Can be set to 0 ignore * the length, as in expanded step-edit note entry. See the function * sequence::verify_and_link(). * * \param wrap * Optionally (the default is false) wrap when relinking. Can be used to * override usr().pattern_wraparound(). * * \return * Returns true if any events were linked by linked_new(). */ bool eventlist::verify_and_link (midipulse slength, bool wrap) { clear_links(); /* unlink, no unmark all events */ sort(); bool wrap_em = m_link_wraparound || wrap; /* a Stazed extension */ bool result = link_new(wrap_em); if (slength > 0) { if (mark_out_of_range(slength)) (void) remove_marked(); /* prune out-of-range events */ } /* * Not sure we want to draw lines for tempos yet. Also, linking tempos * makes them double-selectable in the event editor. * * link_tempos(); */ return result; } /** * Provides a wrapper for clear(). Sets the modified-flag. */ void eventlist::clear () { if (! m_events.empty()) { m_events.clear(); m_is_modified = true; } } /** * Clears all event links and unmarks them all. We get a segfault here * pretty regulary when recording is enabled and the pattern's event list * is showing. */ bool eventlist::clear_links () { bool result = false; for (auto & e : m_events) { if (e.is_linked()) { result = true; e.clear_link(); /* does unmark() and unlink() */ } } return result; } int eventlist::playable_count () const { int result = 0; for (const auto & e : m_events) { if (e.is_playable()) ++result; } return result; } bool eventlist::is_playable () const { bool result = false; for (const auto & e : m_events) { if (e.is_playable()) { result = true; break; } } return result; } int eventlist::note_count () const { int result = 0; for (const auto & e : m_events) { if (e.is_note_on()) ++result; } return result; } /** * Look at note events within the snap interval. Return the first time-stamp * and the first (or average within the snap interval) note value. This * function is used for centering the seqroll on visible notes. */ bool eventlist::first_notes (midipulse & ts, int & n, midipulse snap) const { bool result = false; bool doaverage = snap > 0; if (doaverage) { midipulse ts_first = (-1); int note_avg = 0; int note_count = 0; for (const auto & e : m_events) { if (e.is_note_on()) { result = true; midipulse ts_temp = e.timestamp(); if (ts_first == (-1)) ts_first = ts_temp; int note = int(e.get_note()); if (ts_temp < (ts_first + snap)) { note_avg += note; ++note_count; } else break; } } if (result) { ts = ts_first; n = note_avg / note_count; } } else { for (const auto & e : m_events) { if (e.is_note_on()) { ts = e.timestamp(); n = int(e.get_note()); result = true; break; } } } return result; } /** * Tries to fix the selected notes that started near the end of the pattern * and wrapped around to the beginning, by moving the note. * * \param snap * Provides the sequence's current snap value. Notes that start at less * than half that from the end of the pattern, and end earlier in the * pattern, will be adjusted. * * \param seqlength * Provides the length of the pattern. * * \return * Returns true if a least one note was adjusted. There must be a note-on * that is linked and fits the criterion noted above. */ bool eventlist::edge_fix (midipulse snap, midipulse seqlength) { bool result = false; for (auto & e : m_events) { if (e.is_selected_note_on() && e.is_linked()) { midipulse onstamp = e.timestamp(); midipulse maximum = seqlength - snap / 2; if (onstamp > maximum) { midipulse delta = seqlength - onstamp; midipulse offstamp = e.link()->timestamp(); if (offstamp < onstamp) { e.set_timestamp(0); /* move to beginning */ e.link()->set_timestamp(offstamp + delta); result = true; } } } } if (result) (void) verify_and_link(); /* sorts as well */ return result; } /** * Removes unlinked notes. We must verify_and_link() to get the pattern roll * to show the new note-list. */ bool eventlist::remove_unlinked_notes () { bool result = false; for (auto i = m_events.begin(); i != m_events.end(); /*++i*/) { if (i->is_note_unlinked()) { auto t = remove(i); i = t; result = true; } else ++i; } if (result) (void) verify_and_link(); /* sorts as well */ return result; } /** * Quantizes the currently-selected set of events that match the type of * event specified. This function first marks the selected events. Then it * grabs the matching events, puts them into a list of events to be quantized * and quantizes them against the snap ticks. Linked events (which are * always Note On or Note Off) are adjusted as well, with Note Offs that wrap * around being adjust to be just at the end of the pattern. This function * them removes the marked event from the sequence, and merges the quantized * events into the pattern's event container. Finally, the modified event * list is verified and linked. * * Seq32: * * If ft is negative, then we have a Note Off previously wrapped before * adjustment. Since the delta is based on the Note On (not wrapped), we * must add back the m_length for the wrapping. If the ft is then >= * m_length, it will be deleted by verify_and_link(), which discards any * notes (ON or OFF) that are >= m_length. So we must wrap if > m_length * and trim if == m_length. Compare to trim_timestamp(). * * \param astatus * Indicates the type of event to be quantized. * * \param cc * The desired control-change to count, if the event is a control-change. * * \param snap_tick * Provides the maximum amount to move the events. Actually, events are * moved to the previous or next snap_tick value depend on whether they * are halfway to the next one or not. * * \param divide * A rough indicator of the amount of quantization. The only values used * in the application are either 1 ("quantize") or 2 ("tighten"). The * latter value reduces the amount of change slightly. This value is not * tested for 0. The caller should do it. */ bool eventlist::quantize_events ( midibyte astatus, midibyte cc, int snap, int divide ) { bool result = false; midipulse len = get_length(); bool tight = divide == 2; for (auto & er : m_events) { if (er.is_selected()) { midibyte d0, d1; er.get_data(d0, d1); bool match = er.match_status(astatus); bool canselect = false; if (er.is_marked()) /* ignore marked events */ { er.unmark(); continue; /* it was a linked note */ } if (astatus == EVENT_CONTROL_CHANGE) canselect = match && d0 == cc; /* correct status and cc */ else canselect = match; /* correct status, any cc */ if (canselect) { result = tight ? er.tighten(snap, len) : er.quantize(snap, len) ; if (er.is_note_on_linked()) { /* * In some cases, the linked Note Off, when quantized, * will end up next to the Note On. How to fix? If they * are closer than half the snap, add the snap. */ event::iterator f = er.link(); if (tight) f->tighten(snap, len); else f->quantize(snap, len); midipulse ts1 = er.timestamp(); midipulse ts2 = f->timestamp(); if (ts2 >= ts1) { if (ts2 - ts1 < (snap / 2)) { ts1 += snap / 2; f->set_timestamp(ts1); } } f->mark(); /* mark linked note, ignore */ } } } } if (result) (void) verify_and_link(); /* sorts the events again!! */ return result; } /** * Quantizes events, unconditionally. No adjustment for wrapped notes * is made. * * \param snap * Provides the maximum amount to move the events. Actually, events are * moved to the previous or next snap_tick value depend on whether they * are halfway to the next one or not. * * \param divide * An indicator of the amount of quantization. The values are either * 1 ("quantize") or 2 ("tighten"). The default value is 1, which makes * the snap parameter the quantization value. * * \param all * If true (the default is false), then all events, not just selected * ones, are quantized. * * \return * Returns true if events were quantized. */ bool eventlist::quantize_events (int snap, int divide, bool all) { bool result = false; bool tight = divide == 2; bool found_note = false; midipulse len = get_length(); for (auto & er : m_events) { if (all || er.is_selected()) { bool ok = tight ? er.tighten(snap, len) : er.quantize(snap, len) ; if (ok) result = true; if (er.is_note()) found_note = true; } } if (result && found_note) (void) verify_and_link(); /* sorts them again!!! */ return result; } /** * Quantize/tighten all Note events, including Aftertouch. * * \param snap * The value to which to snap the events. * * \param divide * Divides the snap, e.g. in order to "tighten" rather than fully * quantize. Use set to 1 (the default) or 2. * * \param all * If false (the default), then only selected notes are acted on. * Otherwise, they all are. * * \return * Returns true if notes were quantized. */ bool eventlist::quantize_notes (int snap, int divide, bool all) { bool result = false; midipulse len = get_length(); bool tight = divide == 2; for (auto & er : m_events) { if (all || er.is_selected_note()) { if (er.is_marked()) /* ignore marked events */ { er.unmark(); continue; } result = tight ? er.tighten(snap, len) : er.quantize(snap, len) ; if (er.is_note_on_linked()) { event::iterator f = er.link(); if (tight) f->tighten(snap, len); else f->quantize(snap, len); midipulse ts1 = er.timestamp(); midipulse ts2 = f->timestamp(); if (ts2 >= ts1) { if (ts2 - ts1 < (snap / 2)) { ts1 += snap / 2; f->set_timestamp(ts1); } } f->mark(); /* mark linked for later */ } } } if (result) (void) verify_and_link(); /* sorts them again!!! */ return result; } /** * Consolidates the adjustment of timestamps in a pattern. * * - If the timestamp plus the delta is greater that m_length, we do * round robin magic. * - If the timestamp is greater than m_length, then it is wrapped * around to the beginning. * - If the timestamp equals m_length, then it is set to 0, and later, * trimmed. * - If the timestamp is less than 0, then it is set to the end. * * Taken from similar code in move_selected_notes() and grow_selected(). Be * careful using this function. * * \param t * Provides the timestamp to be adjusted based on m_length. * * \param isnoteoff * Used for "expanding" the timestamp from 0 to just less than m_length, * if necessary. Should be set to true only for Note Off events; it * defaults to false, which means to wrap the events around the end of * the sequence if necessary, and is used only in movement, not in growth. * * \return * Returns the adjusted timestamp. */ midipulse eventlist::adjust_timestamp (event & er, midipulse delta_tick) { static const bool s_allow_wrap = true; /* wrap: note on after note-off */ midipulse result = er.timestamp() + delta_tick; midipulse len = get_length(); if (result > len) result -= len; if (result < 0) /* only if midipulse is signed */ { if (s_allow_wrap) result += len; else result = 0; } if (er.is_note_off()) { if (result == 0) { if (s_allow_wrap) result = len - note_off_margin(); else result = note_off_margin(); } } else /* if (wrap) */ { if (result == len) { if (s_allow_wrap) result = 0; } } return result; } /** * Removes and adds selected notes in position. Also currently moves any * other events in the range of the selection. * * Another thing this function does is wrap-around when movement occurs. * Any events (except Note Off) that will start just after the END of the * pattern will be wrapped around to the beginning of the pattern. * * After this function, we also have to call verify_and_link(), which sorts * and relinks the notes from scratch. * * \param delta_tick * Provides the amount of time to move the selected notes. Note that it * also applies to events. Note-Off events are expanded to m_length if * their timestamp would be 0. All other events will wrap around to 0. * * \param delta_note * Provides the amount of pitch to move the selected notes. This value * is applied only to Note (On and Off) events. Also, if this value * would bring a note outside the range of 0 to 127, that note is not * changed and the event is not moved. */ bool eventlist::move_selected_notes (midipulse delta_tick, int delta_note) { bool result = false; for (auto & er : m_events) { if (er.is_selected_note()) /* moveable event? */ { int newnote = er.get_note() + delta_note; if (newnote >= 0 && newnote < c_notes_count) { midipulse newts = adjust_timestamp(er, delta_tick); if (er.is_note()) /* Note On or Note Off */ er.set_note(midibyte(newnote)); er.set_timestamp(newts); result = true; } } } if (result) (void) verify_and_link(); /* sort and relink */ return result; } /** * Used only in qstriggereditor. */ bool eventlist::move_selected_events (midipulse delta_tick) { bool result = false; for (auto & er : m_events) { if (er.is_selected() && ! er.is_note()) { midipulse newts = adjust_timestamp(er, delta_tick); er.set_timestamp(newts); result = true; } } return result; } /** * Makes the first event start at time 0. Might also change the length of * the pattern. Hmmm??? * * \param relink * If true (the default is false), the events are sorted and relinked. * * \return * Returns true all timestamps were adjusted. Otherwise, false is * returned, which means the original events should be restored. */ bool eventlist::align_left (bool relink) { bool result = ! empty(); if (result) { const auto startev = m_events.begin(); midipulse shift = startev->timestamp(); result = shift > 0; if (result) { for (auto & ev : m_events) { midipulse newstamp = ev.timestamp() - shift; if (newstamp >= 0) { ev.set_timestamp(newstamp); } else { result = false; break; } } if (result && relink) { sort(); result = verify_and_link(); } } } return result; } /** * Makes the last event end at the end of the pattern. * * \param relink * If true (the default is false), the events are sorted and relinked. * * \return * Returns true all timestamps were adjusted. Otherwise, false is * returned, which means the original events should be restored. */ bool eventlist::align_right (bool relink) { bool result = ! empty(); if (result) { const auto endev = m_events.rbegin(); midipulse endts = get_length(); midipulse shift = endts - endev->timestamp() - 1; result = shift > 0; if (result) { for (auto & ev : m_events) { midipulse newstamp = ev.timestamp() + shift; if (newstamp < endts) { ev.set_timestamp(newstamp); } else { result = false; break; } } if (result && relink) { sort(); result = verify_and_link(); } } } return result; } /** * Helper function for scaling note-off events properly.. */ void eventlist::scale_note_off (event & noteoff, double factor) { midipulse stamp = noteoff.timestamp(); stamp += note_off_margin(); /* remove the margin */ stamp *= factor; /* scale the note off */ stamp -= note_off_margin(); /* put back the margin */ noteoff.set_timestamp(stamp); } /** * Scales the time of all event by the given factor. * * - If the factor <= 1.0: * -# Scale all events. * -# Leave the length of the pattern (in measures) the same; the * user can manually reduce the length in the pattern editor, if * desired. * - if the factor is > 1.0: * -# Scale all events. * -# Find the new maximum timestamp. * -# Increase it to the next full measure, then set the length. * -# Return a non-zero so that the sequence (the caller) can update * the measures count. * * \param factor * The multiplier for each timestamp. * * \param savenotelength * If true (the default is false), then we have to keep track of note * events (on, off, but not aftertouch), to preserve the length of each * note. * * \param relink * If true (the default is false), then sort, verify, and link. * Not sure if this is useful at this point. */ midipulse eventlist::apply_time_factor (double factor, bool savenotelength, bool relink) { midipulse result = 0; bool ok = ! empty() && factor > 0.01; if (ok) { for (auto & ev : m_events) { midipulse stamp = ev.timestamp(); bool linked = ev.is_linked(); /* do note on and off */ if (ev.is_note_on()) { midipulse newstamp = midipulse(stamp * factor); if (linked) { midipulse offstamp = ev.link()->timestamp(); if (savenotelength) { midipulse len = offstamp - stamp; ev.link()->set_timestamp(newstamp + len); } else { offstamp = midipulse(offstamp * factor); scale_note_off(*ev.link(), factor); } } ev.set_timestamp(newstamp); } else if (ev.is_note_off()) { if (! ev.is_linked()) /* correction needed */ scale_note_off(ev, factor); } else { midipulse newstamp = midipulse(stamp * factor); ev.set_timestamp(newstamp); } } if (relink) { sort(); verify_and_link(); } result = get_max_timestamp(); } return result; } /** * This function reverses the events in a sequence. Note events are treated * specially: * * -# The Note Off timestamp (reversed) has to be used as the new Note On * timestamp. * -# Only the Note On gets that new-timestamp at first. * -# The Note Off is placed at the original duration past the new * Note On time. */ bool eventlist::reverse_events (bool inplace, bool relink) { bool result = ! empty(); if (result) { midipulse offset = inplace ? get_min_timestamp() : 0; midipulse ending = inplace ? get_max_timestamp() : get_length() - 1 ; for (auto & ev : m_events) { midipulse stamp = ev.timestamp(); midipulse newstamp = ending - stamp + offset; if (ev.is_note_on()) { bool linked = ev.is_linked(); /* do note on and off */ if (linked) { midipulse offstamp = ev.link()->timestamp(); midipulse duration = offstamp - stamp + 1; newstamp = ending - offstamp + offset; ev.set_timestamp(newstamp); ev.link()->set_timestamp(newstamp + duration); } else ev.set_timestamp(newstamp); } else if (ev.is_note_off()) { if (! ev.is_linked()) /* correction needed */ ev.set_timestamp(newstamp); } else ev.set_timestamp(newstamp); } if (relink) { sort(); verify_and_link(); } } return result; } /** * This function randomizes a portion of each selected event. If the event is * a two-byte message (note on/off, aftertouch, pitch wheel, or control * change), the second byte (e.g. velocity for notes) is altered. If the * event is one byte (program change or channel pressure), the first byte is * altered. * * See http://c-faq.com/lib/randrange.html for details. * * Note that we do not need to call verify_and_link() here, since we are not * altering the timestamps or the note values. * * \param astatus * The kind of event to be randomized. * * \param range * The amount of randomization. A positive non-zero value is enforced. * * \param all * If true, randomize all events regardless of status value. The default * is false. * * \return * Returns true if some randomization occurred. */ bool eventlist::randomize (midibyte astatus, int range, bool all) { bool result = false; if (range > 0) { for (auto & e : m_events) { if (all || e.is_selected_status(astatus)) { if (e.randomize(range)) result = true; } } } return result; } /** * This function randomizes a Note On or Note Off message, and more * thoroughly than randomize(). We want to be able to "jitter" the * velocity (data byte d[1]) of the note. The note pitch (d[0]) is not * altered. * * \param range * Provides the amount of velocity randomization. * * \param all * If true (the default is false), handle all notes, not just the * selected notes. * * \return * Returns true if any event got altered. */ bool eventlist::randomize_note_velocities (int range, bool all) { bool result = range > 0; if (result) { result = false; /* ca 2025-06-18 */ for (auto & e : m_events) { if (all || e.is_selected_note()) /* randomizable event? */ { if (! e.is_note_off_recorded()) /* don't ruin fake Off */ { if (e.randomize(range)) result = true; } } } /* * ca 2025-06-13. We're not changing the order or timing * of notes, why relink? * * if (result) * (void) verify_and_link(); // sort & relink notes */ } return result; } /** * This function randomizes the pitch of a Note On/Note Off message pair. * We want it to fall within the given scale (which might be * scale::chromatic, which includes all twelve notes.) * * This is a little tricky. If the randomized pitch isn't part of the * specified scale we need to go up or down a pitch value until it is. * * \param range * Provides the amount of velocity randomization. * * \param s * Provides the scale to adhere to. If equal to scale::chromatic * (also known as scale::off), no adjustment is needed. * * \param all * If true (the default is false), handle all notes, not just the * selected notes. * * \return * Returns true if any event got altered. */ bool eventlist::randomize_note_pitches ( int range, scales s, keys keyofpattern, bool all ) { bool result = range > 0; if (result) { #if defined SEQ66_PLATFORM_DEBUG_TMI printf ( "Key of %s, %s scale\n", musical_key_name(keyofpattern).c_str(), musical_scale_name(s).c_str() ); #endif result = false; (void) verify_and_link(); /* play safe & sort */ for (auto & e : m_events) { bool ok = all ? e.is_note() : e.is_selected_note() ; if (ok) /* randomizable? */ { #if defined SEQ66_USE_UNIFORM_INT_DISTRIBUTION int delta = seq66::randomize_uniformly(range); #else int delta = seq66::randomize(range); #endif int p = int(e.get_note()); if (s == scales::off) { p += delta; } else { p += delta; for (int offset = 0; ; ++offset) /* find legal note */ { int testp = p + offset; if (scales_policy(s, keyofpattern, testp)) { p = testp; break; } else { testp = p - offset; if (scales_policy(s, keyofpattern, testp)) { p = testp; break; } } } } if (e.is_note_on()) { result = true; e.set_note(midibyte(p)); if (e.is_linked()) { e.link()->set_note(midibyte(p)); } else { #if defined SEQ66_PLATFORM_DEBUG printf("Orphaned Note On in randomizing pitch\n"); #endif } } else if (e.is_note_off()) { if (! e.is_linked()) { result = true; e.set_note(midibyte(p)); #if defined SEQ66_PLATFORM_DEBUG printf("Orphaned Note Off in randomizing pitch\n"); #endif } } } else continue; } (void) verify_and_link(); /* play safe & sort */ } return result; } #if defined SEQ66_USE_JITTER_EVENTS /** * This function jitters the timestamps of all events. If note events were * jittered, then we verify-and-link. * * Since we jitter the timestamps, we have to call verify_and_link() * afterward. * * \param snap * Provides the current snap value, which is used to avoid gross * jittering. * * \param jitr * Provides the amount of time jitter in ticks. * * \return * Returns true if some jittering was actually done. */ bool eventlist::jitter_events (int snap, int jitr) { bool result = false; if (jitr > 0) { bool note_changed = false; for (auto & e : m_events) { if (e.is_marked()) /* ignore marked events */ { e.unmark(); continue; /* it was a linked note */ } if (e.jitter(snap, jitr, get_length())) { result = true; if (e.is_note()) note_changed = true; if (e.is_note_on_linked()) { /* * In some cases, the linked Note Off, when quantized, * will end up next to the Note On. How to fix? If they * are closer than half the snap, add the snap. * * Hmmmm, how about the zero-length correction??? */ event::iterator f = e.link(); f->jitter(snap, jitr, get_length()); midipulse ts1 = e.timestamp(); midipulse ts2 = f->timestamp(); if (ts2 >= ts1) { if (ts2 - ts1 < (snap / 2)) { ts1 += snap / 2; f->set_timestamp(ts1); } } f->mark(); /* mark link note for later */ } } } if (note_changed) verify_and_link(); /* sort and relink notes */ } return result; } #endif // defined SEQ66_USE_JITTER_EVENTS /** * This function jitters the timestamps of all note events. If any * were jittered, then we verify-and-link. * * Since we jitter the timestamps, we have to call verify_and_link() * afterward. * * \param snap * Provides the current snap value, which is used to avoid gross * jittering. * * \param jitr * Provides the amount of time jitter in ticks. * * \param all * If true (the default is false), all events are jittered, * not just the selected events. * * \return * Returns true if some jittering was actually done. */ bool eventlist::jitter_notes (int snap, int jitr, bool all) { bool result = false; if (jitr > 0) { for (auto & e : m_events) { if (all || e.is_selected_note()) { if (e.jitter(snap, jitr, get_length())) result = true; } } if (result) verify_and_link(); /* sort and relink */ } return result; } #if defined SEQ66_USE_FILL_TIME_SIG_AND_TEMPO /** * Scans the event-list for any tempo or time_signature events. * The user may have deleted them and is depending on a setting made in the * user-interface. So we must set/unset the flags before saving. This check * was added to fix issue #141. */ void eventlist::scan_meta_events () { m_has_tempo = false; m_has_time_signature = false; m_has_key_signature = false; for (auto & e : m_events) { if (e.is_tempo()) m_has_tempo = true; else if (e.is_time_signature()) m_has_time_signature = true; else if (e.is_key_signature()) m_has_key_signature = true; } } #endif // SEQ66_USE_FILL_TIME_SIG_AND_TEMPO #if defined SEQ66_LINK_TEMPOS /** * This function tries to link tempo events. Native support for temp tracks * is a new feature of seq66. These links are only in one direction: forward * in time, to the next tempo event, if any. * * Also, at present, tempo events are not markable. * * \threadunsafe * As in most case, the caller will use an automutex to call this * function safely. */ bool eventlist::link_tempos () { bool result = false; clear_tempo_links(); for (auto t = m_events.begin(); t != m_events.end(); ++t) { if (t->is_tempo()) { auto t2 = t; /* next possible Set Tempo... */ ++t2; /* ...starting here */ while (t2 != m_events.end()) { if (t2->is_tempo()) { result = true; t->link(t2); break; /* tempos link only one way */ } ++t2; } } } return result; } /** * Clears all tempo event links. */ void eventlist::clear_tempo_links () { for (auto & e : m_events) { if (e.is_tempo()) e.unlink(); } } #endif // defined SEQ66_LINK_TEMPOS /** * Marks all selected events. * * \return * Returns true if there was even one event selected and marked. */ bool eventlist::mark_selected () { bool result = false; for (auto & e : m_events) { if (e.is_selected()) { e.mark(); result = true; } } return result; } #if defined SEQ66_MARK_ALL /** * Marks all events. Not yet used, but might come in handy with the event * editor dialog. */ bool eventlist::mark_all () { bool result = false; for (auto & e : m_events) { result = true; e.mark(); } return result; } /** * Unmarks all events. */ bool eventlist::unmark_all () { bool result = false; for (auto & e : m_events) { if (e.is_marked()) { result = true; e.unmark(); } } return result; } #endif // defined SEQ66_MARK_ALL /** * Marks all events that have a time-stamp that is out of range. * Used for killing (pruning) those events not in range. If the current * time-stamp is greater than the length, then the event is marked for * pruning. * * \note * This code was comparing the timestamp as greater than or equal to the * sequence length. However, being equal is fine. This may explain why * the midifile code would add one tick to the length of the last note * when processing the end-of-track. * * \param slength * Provides the length beyond which events will be pruned. * * \return * Returns true if any event(s) got marked. */ bool eventlist::mark_out_of_range (midipulse slength) { bool result = false; for (auto & e : m_events) { bool prune = e.timestamp() > slength; /* WAS ">=", SEE BANNER */ if (! prune) prune = e.timestamp() < 0; /* added back, seq66 */ if (prune) { result = true; e.mark(); if (e.is_linked()) e.link()->mark(); } } return result; } /** * A helper function for sequence. Finds the given event, and removes the * first iterator matching that. If there are events that would match after * that, they remain in the container. This matches seq24 behavior. * * \todo * Use the find() function to find the matching event more * conventionally. * * \param e * Provides a reference to the event to be removed. * * \return * Returns true if the event was found and removed. This function * returns immediately after the event is removed. */ bool eventlist::remove_event (event & e) { bool result = false; for (auto i = m_events.begin(); i != m_events.end(); ++i) { event & er = dref(i); if (&e == &er) /* comparing pointers, not values */ { (void) remove(i); /* an iterator is required here */ result = true; break; } } return result; } /** * We want to get event iterators for given events. * * What we want to support: * * - Meta text. * - Song-info: Find (or remove) the first meta text event at * timestamp 0. * - Track-info: Find (and take note of) the first meta text event * at any timestamp (e.timestamp == c_null_midipulse). * - Get the text and timestamp. As en event? * - Set the event to a new value. * * \param e * The event to match. * * \param starttick * The starting point in time of the search. Defaults to 0. * * \return * Returns the iterator to the next match. If end(), the event was * not found. */ event::iterator eventlist::find_first_match (const event & e, midipulse starttick) { event::iterator result = m_events.end(); for (auto i = m_events.begin(); i != m_events.end(); ++i) { event & er = dref(i); midipulse t = er.timestamp(); if (t >= starttick) { if (er.match(e)) /* compares values, not ptrs */ { result = i; m_match_iterator = result; /* keeps track of position */ break; } } } m_match_iterating = result != m_events.end(); return result; } event::iterator eventlist::find_next_match (const event & e) { event::iterator result = m_events.end(); if (m_match_iterating) { for (auto i = m_match_iterator; i != m_events.end(); ++i) { event & er = dref(i); if (er.match(e)) /* comparing values, not pointers */ { result = i; break; } } m_match_iterating = result != m_events.end(); m_match_iterator = result; } else result = find_first_match(e); return result; } /** * Do we need a time fudge-factor of a few ticks? * * \param target * Provides the time at which we expect the time-signature to be. * * \return * Returns true only if the time signature was found and removed. */ bool eventlist::remove_time_signature (midipulse target) { bool result = false; for (auto i = m_events.begin(); i != m_events.end(); ++i) { event & er = dref(i); if (er.is_time_signature()) { midipulse t = er.timestamp(); if (t == target) { (void) remove(i); result = true; break; } } } return result; } /** * Removes the first event where there is a match based on event data, * not event address. * * \param e * Provides a reference to the event for which the first match is to be * removed. * * \return * Returns true if the event was found and removed. This function * returns immediately after the event is removed. */ bool eventlist::remove_first_match (const event & e, midipulse starttick) { bool result = false; for (auto i = m_events.begin(); i != m_events.end(); ++i) { event & er = dref(i); midipulse t = er.timestamp(); if (t >= starttick) { if (er.match(e)) /* comparing values, not pointers */ { (void) remove(i); /* an iterator is required here */ result = true; break; } } } return result; } /** * Removes marked events. Note how this function handles removing a * value to avoid incrementing a now-invalid iterator. * * \threadsafe * * \return * Returns true if at least one event was removed. */ bool eventlist::remove_marked () { bool result = false; for (auto i = m_events.begin(); i != m_events.end(); /*++i*/) { if (i->is_marked()) { auto t = remove(i); i = t; result = true; } else ++i; } if (result) verify_and_link(); return result; } /** * Removes events on or after the given timestamp, which is normally * the beginning of a measure (though it does not have to be that). * * We have to handle notes across the boundary carefully. We can either * removed both parts (On and Off) of the note, or shorten the note. * Probably the latter is better. * * TO BE DETERMINED. * * \param limit * The time stamp at or after which events are to be remove. */ bool eventlist::remove_trailing_events (midipulse limit) { bool result = false; for (auto i = m_events.begin(); i != m_events.end(); /*++i*/) { if (i->timestamp() >= limit) { /* * TODO: handled linked notes */ auto t = remove(i); i = t; result = true; } else { if (i->is_note_on_linked()) { auto ioff = i->link(); if (ioff->timestamp() >= limit) ioff->set_timestamp(limit - 1); } ++i; } } if (result) verify_and_link(); return result; } /** * Removes selected events. Note how this function handles removing a * value to avoid incrementing a now-invalid iterator. * * We want to get rid of the concept of marking events. Selected events can * be handled directly in the event container. * * \return * Returns true if at least one event was removed. */ bool eventlist::remove_selected () { bool result = false; for (auto i = m_events.begin(); i != m_events.end(); /*++i*/) { if (i->is_selected()) { auto t = remove(i); i = t; result = true; } else ++i; } if (result) verify_and_link(); return result; } /** * Unpaints all list-events. */ void eventlist::unpaint_all () { for (auto & er : m_events) er.unpaint(); } /** * Counts the selected Note On events in the event list. */ int eventlist::count_selected_notes () const { int result = 0; for (auto & er : m_events) { if (er.is_selected_note_on()) ++result; } return result; } /** * Indicates that at least one Note On is selected. Acts like * eventlist::count_selected_notes(), but stops after finding a selected * note. * * \return * Returns true if at least one Note On is selected. */ bool eventlist::any_selected_notes () const { bool result = false; for (auto & er : m_events) { if (er.is_selected_note_on()) { result = true; break; } } return result; } /** * Counts the selected events, with the given status, in the event list. * If the event is a control change (CC), then it must also match the * given CC value. One exception is tempo events, which are selected * based on the event::is_tempo() test. * * \param astatus * The desired status value to count. Note that tempo is 0x51. * * \param cc * The desired control-change to count. Used only if the status * parameter indicates a control-change event. * * \return * Returns the number of selected events. */ int eventlist::count_selected_events (midibyte astatus, midibyte cc) const { int result = 0; for (auto & er : m_events) { if (er.is_selected() && er.is_desired(astatus, cc)) ++result; } return result; } /** * Indicates that at least one event of any kind is selected. * * \return * Returns true if at least one event is selected. */ bool eventlist::any_selected_events () const { bool result = false; for (auto & er : m_events) { if (er.is_selected()) { result = true; break; } } return result; } /** * This is an overload of any_selected_events(). It indicates that at least * one matching event is selected. Acts like eventlist :: * count_selected_events(), but stops after finding a selected note. * * \return * Returns true if at least one matching event is selected. */ bool eventlist::any_selected_events (midibyte astatus, midibyte cc) const { bool result = false; for (auto & er : m_events) { if (er.is_selected() && er.is_desired(astatus, cc)) { result = true; break; } } return result; } /** * Selects all events, unconditionally. */ void eventlist::select_all () { for (auto & er : m_events) er.select(); } void eventlist::select_by_channel (int channel) { midibyte target = midibyte(channel); for (auto & er : m_events) { if (er.channel() == target) er.select(); } } /** * Selects all note events with the given channel. Although we can extract * the channel nybble from the status, we access the event channel member. */ void eventlist::select_notes_by_channel (int channel) { midibyte target = midibyte(channel); for (auto & er : m_events) { if (er.is_note() && er.channel() == target) er.select(); } } /** * Allows the events to be permanently set to a given channel. Obviously, * it applies only to channel events such as Note On/Off. * * \param channel * The caller is responsible for ensuring this parameter ranges from * 0 to 15. * * \return * Returns true if even one event was modified. */ bool eventlist::set_channels (int channel) { bool result = false; midibyte target = midibyte(channel); for (auto & er : m_events) { if (er.has_channel()) { er.set_channel(target); result = true; } } return result; } /** * Deselects all events, unconditionally. */ void eventlist::unselect_all () { for (auto & er : m_events) er.unselect(); } /** * Select all events in the given range, and returns the number * selected. Note that there is also an overloaded version of this * function. * * \threadsafe * * \param tick_s * The start time of the selection. * * \param tick_f * The finish time of the selection. * * \param astatus * The desired event in the selection. Now, as a new feature, tempo * events are also selectable, in addition to events selected by this * parameter. Oh, and now time-signature events. * * \param cc * The desired control-change in the selection, if the event is a * control-change. * * \param seltype * The desired selection action. * * \return * Returns the number of events selected. */ int eventlist::select_events ( midipulse tick_s, midipulse tick_f, midibyte astatus, midibyte cc, select seltype ) { int result = 0; for (auto & er : m_events) { if (event_in_range(er, astatus, tick_s, tick_f)) { if (er.is_desired(astatus, cc)) { if (seltype == select::selecting) { er.select(); ++result; } if (seltype == select::select_one) { er.select(); ++result; break; } if (seltype == select::selected) { if (er.is_selected()) { result = 1; break; } } if (seltype == select::would_select) { result = 1; break; } if (seltype == select::toggle) { if (er.is_selected()) er.unselect(); else er.select(); } if (seltype == select::remove) { remove_event(er); ++result; break; } if (seltype == select::deselect) er.unselect(); } } } return result; } /** * Selects the seqdata event handle if in range. One issue in adjusting data * is Pitch events, which have two components [d0() and d1()] which must be * combined. Another issue is tempo events, where the value is converted to a * note-velocity in order to display it in the data pane. * * \param tick_s * Provides the starting tick, which is some small amount below the tick * represented by the mouse position. * * \param tick_f * Provides the finishing tick, which is some small amount above the * tick represented by the mouse position. * * \param astatus * Provides the type of event, such as Note-On/Off, Pitchbend, or Tempo. * * \param cc * For control events, represents the control code. Does not apply to * Notes, Tempo, Pitchbend. For Meta events, cc is the type of Meta * event. * * \param data * Currently represents the note value or d0(). * * \return * Returns the number of matching events in range of the data value. */ int eventlist::select_event_handle ( midipulse tick_s, midipulse tick_f, midibyte astatus, midibyte cc, midibyte data ) { int result = 0; bool have_selected_note_ons = false; if (event::is_note_on_msg(astatus)) { if (count_selected_events(astatus, cc) > 0) have_selected_note_ons = true; } else if (event::is_tempo_status(cc)) { // TODO? } for (auto & er : m_events) { if (event_in_range(er, astatus, tick_s, tick_f)) /* in time-range */ { bool isctrl = event::is_controller_msg(astatus); if (isctrl && er.is_desired(astatus, cc, data)) /* in range */ { unselect_all(); /* or unmark() */ er.select(); /* or mark()??? */ ++result; break; } if (! isctrl) /* chan. pressure? */ { bool twobytes = event::is_two_byte_msg(astatus); if (twobytes) { if (er.is_data_in_handle_range(data)) /* checks d1() */ { if (have_selected_note_ons) /* Note Ons only */ { if (er.is_selected()) { unselect_all(); er.select(); if (result > 0) /* have a marked */ { for (auto & ev : m_events) { if (ev.is_marked()) { ev.unmark(); /* clear it */ break; } } --result; have_selected_note_ons = false; } ++result; /* for selected one */ break; } else { if (result == 0) /* mark only first */ { er.mark(); /* mark for hold */ ++result; /* we got one */ } continue; /* till sel'ed/done */ } } else /* not Note On */ { unselect_all(); er.select(); ++result; break; } } } else { if (er.is_data_in_handle_range(data)) /* checks d0() */ { unselect_all(); er.select(); ++result; break; } } } } } /* * Is it a Note On that is unselected, but in range? Then use it.... * The have_selected_note_ons flag will be false if we found a * selected one in range (?) */ if (result > 0 && have_selected_note_ons) { for (auto & er : m_events) { if (er.is_marked()) { unselect_all(); er.unmark(); er.select(); } } } return result; } /** * This function selects events in range of tick start, note high, tick end, * and note low. * * Compare this function to the convenience function select_all_notes(), which * doesn't use range information. * * Note that we have not offloaded this function to eventlist because it * depends on the sequence::select enumeration, and we're too lazy at the * moment to move that enumeration to eventlist. * * \threadsafe * * \param tick_s * The start time of the selection. * * \param note_h * The high note of the selection, inclusive. * * \param tick_f * The finish time of the selection. * * \param note_l * The low note of the selection, inclusive. * * \param seltype * The action to perform, one of the values of the sequence::select * enumeration. * * \return * Returns the number of events acted on, or 0 if no desired event was * found. */ int eventlist::select_note_events ( midipulse tick_s, int note_h, midipulse tick_f, int note_l, select seltype ) { int result = 0; for (auto & er : m_events) { int n = int(er.get_note()); /* gets byte m_data[0] */ if (er.is_note() && n <= note_h && n >= note_l) { midipulse stick = 0, ftick = 0; if (er.is_linked()) { event::iterator ev = er.link(); if (er.is_note_off()) { stick = ev->timestamp(); /* time of the Note On */ ftick = er.timestamp(); /* time of the Note Off */ } else if (er.is_note_on()) { ftick = ev->timestamp(); /* time of the Note Off */ stick = er.timestamp(); /* time of the Note On */ } /* * "tand" indicates that the event start is less than the * finish parameter, and the event finish is greater than the * start parameter. * * "tor" is the OR of these two tests, and is needed when the * event start is greater than the finish, which occurs in a * note-off. * * Not sure why so complex; all we need to know is that both * the start and end times are within the desired range. * However, then we cannot click on a note to select it. Odd! */ bool tand = (stick <= tick_f) && (ftick >= tick_s); bool tor = (stick <= tick_f) || (ftick >= tick_s); bool ok = tand || ((stick > ftick) && tor); if (ok) { if (seltype == select::selecting) { er.select(); ev->select(); ++result; } if (seltype == select::select_one) { er.select(); ev->select(); ++result; break; } if (seltype == select::selected) { if (er.is_selected()) { result = 1; break; } } if (seltype == select::would_select) { result = 1; break; } if (seltype == select::deselect) { er.unselect(); ev->unselect(); result = 0; /* no break; */ } if (seltype == select::toggle && er.is_note_on()) { if (er.is_selected()) /* don't toggle twice */ { er.unselect(); ev->unselect(); } else { er.select(); ev->select(); } ++result; } if (seltype == select::remove) { remove_event(er); remove_event(*ev); ++result; break; } } } else { /* * Here, the note event is not linked, and so the event is * considered "junk". We still handle the event itself. * There's no way to fix it except by an expensive * verify_and_link() call! */ stick = ftick = er.timestamp(); if (stick >= (tick_s - 16) && ftick <= tick_f) /* why -16? */ { if (seltype == select::selecting) { er.select(); ++result; } if (seltype == select::select_one) { er.select(); ++result; break; } if (seltype == select::selected) { if (er.is_selected()) { result = 1; break; } } if (seltype == select::would_select) { result = 1; break; } if (seltype == select::deselect) { result = 0; er.unselect(); } if (seltype == select::toggle) { ++result; if (er.is_selected()) er.unselect(); else er.select(); } if (seltype == select::remove) { remove_event(er); ++result; break; } } } } } return result; } /** * This function selects notes that lie within a range of pitches, * inclusive. The time-stamps range from 0 to the end of the pattern. * Note that the parameters are ... backwards. * * \param note_h * The high note of the selection, inclusive. * * \param note_l * The low note of the selection, inclusive. */ int eventlist::select_notes_by_pitch (int note_h, int note_l) { midipulse t0 = 0; midipulse t1 = get_length(); select seltype = select::selecting; return select_note_events(t0, note_h, t1, note_l, seltype); } /** * A convenience function used a couple of times. Makes if-clauses * easier to read. * * \param e * Provides the event to be checked. * * \param astatus * Provides the event type that must be matched. However, Set Tempo * events will always be matched. * * \param tick_s * The lower end of the range of timestamps that the event must fall * within. * * \param tick_f * The upper end of the range of timestamps that the event must fall * within. * * \return * Returns true if the event matchs all of the restrictions noted. */ bool eventlist::event_in_range ( const event & e, midibyte astatus, midipulse tick_s, midipulse tick_f ) const { bool result = e.match_status(astatus) || e.is_tempo() || e.is_time_signature(); if (result) result = e.timestamp() >= tick_s && e.timestamp() <= tick_f; return result; } bool eventlist::get_selected_events_interval ( midipulse & first, midipulse & last ) const { bool result = false; midipulse first_ev = midipulse(0x7fffffff); /* timestamp lower limit */ midipulse last_ev = midipulse(0x00000000); /* timestamp upper limit */ for (auto & er : m_events) { if (er.is_selected()) { if (er.timestamp() < first_ev) { first_ev = er.timestamp(); result = true; } if (er.timestamp() >= last_ev) { last_ev = er.timestamp(); result = true; } } } if (result) { first = first_ev; last = last_ev; } return result; } bool eventlist::rescale (int newppqn, int oldppqn) { bool result = oldppqn > 0; if (result) { for (auto & er : m_events) er.rescale(newppqn, oldppqn); set_length(rescale_tick(get_length(), newppqn, oldppqn)); } return result; } /** * Performs a stretch operation on the selected events. This should move * a note off event, according to old comments, but it doesn't seem to do * that. See the grow_selected() function. Rather, it moves any event in * the selection. * * Also, we've moved external push-undo into sequence functions. The caller * shouldn't have to do that. * * Finally, we don't need to mark the selected, only to remove the unmodified * versions later. Just adjust their timestamps directly. * * \param delta_tick * Provides the amount of time to stretch the selected notes. */ bool eventlist::stretch_selected (midipulse delta) { midipulse first_ev, last_ev; bool result = get_selected_events_interval(first_ev, last_ev); if (result) { midipulse old_len = last_ev - first_ev; midipulse new_len = old_len + delta; if (new_len > 1 && old_len > 0) { float ratio = float(new_len) / float(old_len); result = false; for (auto & er : m_events) { if (er.is_selected()) { midipulse t = er.timestamp(); midipulse nt = midipulse(ratio * (t - first_ev)) + first_ev; er.set_timestamp(nt); result = true; } } if (result) verify_and_link(); /* sorts as well */ } } return result; } /** * The original description was "Moves note off event." But this also gets * called when simply selecting a second note via a ctrl-left-click, even in * seq66. And, though it doesn't move Note Off events, it does reconstruct * them. * * This function grows/shrinks only Note On events that are linked. If an * event is not linked, this function ignores the event's timestamp, rather * than risk a segfault on a null pointer. Compare this function to the * stretch_selected() and move_selected_notes() functions. * * This function would strip out non-Notes, but now it at least preserves * them and moves them, to try to preserve their relative position re the * notes. * * In any case, we want to mark the original off-event for deletion, otherwise * we get duplicate off events, for example in the "Begin/End" pattern in the * test.midi file. * * This function now tries to prevent pathological growth, such as trying to * shrink the notes to zero length or less, or stretch them beyond the length * of the sequence. Otherwise we get weird and unexpected results. Also, * we've moved push-undo into sequence functions. The caller shouldn't have * to do that. * * A comment on terminology: The user "selects" notes, while the sequencer * "marks" notes. This function no longer bothers to mark all the selected * notes. * * \threadsafe * * \param delta * An offset for each linked event's timestamp. * * \param snap * The snap amount for the growth. Currently used only for non-notes. * * \return * Returns true if at least one time-stamp was altered. */ bool eventlist::grow_selected (midipulse delta, int snap) { bool result = false; for (auto & er : m_events) { if (er.is_selected()) { if (er.is_note()) { if (er.is_note_on() && er.is_linked()) { event::iterator off = er.link(); midipulse offtime = off->timestamp(); midipulse newtime = trim_timestamp(offtime + delta); off->set_timestamp(newtime); /* new off-time */ result = true; } } else /* non-Note event */ { midipulse ontime = er.timestamp(); midipulse newtime = clip_timestamp ( ontime, ontime + delta, snap ); er.set_timestamp(newtime); /* adjust time-stamp */ result = true; } } } if (result) verify_and_link(); /* sorts as well */ return result; } bool eventlist::copy_selected (eventlist & clipbd) { bool result = false; for (auto & e : m_events) { if (e.is_selected()) clipbd.add(e); /* sorts every time */ } if (! clipbd.empty()) { midipulse first_tick = dref(clipbd.begin()).timestamp(); if (first_tick >= 0) { for (auto & e : clipbd) /* 2019-09-12 */ { midipulse t = e.timestamp(); if (t >= first_tick) { e.set_timestamp(t - first_tick); /* slide left! */ result = true; } } if (result) clipbd.sort(); } } return result; } bool eventlist::paste_selected (eventlist & clipbd, midipulse tick, int note) { bool result = false; if (! clipbd.empty()) { int highest_note = 0; for (auto & e : clipbd) { midipulse t = e.timestamp(); e.set_timestamp(t + tick); result = true; if (e.is_note()) /* includes Aftertouch */ { midibyte n = e.get_note(); if (n > highest_note) highest_note = n; } } int note_delta = note - highest_note; for (auto & e : clipbd) { if (e.is_note()) /* includes Aftertouch */ { midibyte n = e.get_note(); e.set_note(n + note_delta); result = true; } } merge(clipbd); /* will presort clipboard */ verify_and_link(); /* vice remove_selected() */ } return result; } /** * A new function to consolidate the adjustment of timestamps in a pattern. * Similar to adjust_timestamp, but it doesn't have an \a isnoteoff * parameter. Used only in this class. * * \param t * Provides the timestamp to be adjusted based on m_length. * * \return * Returns the adjusted timestamp. */ midipulse eventlist::trim_timestamp (midipulse t) const { if (t >= get_length()) t -= get_length(); if (t < 0) /* only if midipulse is signed */ t += get_length(); if (t == 0) t = get_length() - note_off_margin(); return t; } /** * A new function to consolidate the growth/shrinkage of timestamps in a * pattern. If the new (off) timestamp is less than the on-time, it is * clipped to the snap value. If it is greater than the length of the * sequence, then it is clipped to the sequence length. No wrap-around. * * \param ontime * Provides the original time, which limits the amount of negative * adjustment that can be done. * * \param offtime * Provides the timestamp to be adjusted and clipped. * * \return * Returns the adjusted timestamp. */ midipulse eventlist::clip_timestamp (midipulse ontime, midipulse offtime, int snap) const { if (offtime <= ontime) offtime = ontime + snap - note_off_margin(); else if (offtime >= get_length()) offtime = get_length() - note_off_margin(); return offtime; } /** * Prints a list of the currently-held events. Useful for debugging. */ void eventlist::print () const { std::printf("%d MIDI events:\n", count()); for (auto & e : m_events) e.print(); } /** * Prints a list of the currently-held notes. Useful for debugging. */ void eventlist::print_notes (const std::string & tag) const { std::printf("Notes %s:\n", tag.c_str()); if (count() > 0) { for (auto & e : m_events) e.print_note(); } } /** * Constructs a list of the currently-held events. Useful for debugging. */ std::string eventlist::to_string () const { std::string result = "Events ("; result += std::to_string(count()); result += "):\n"; for (auto & e : m_events) result += e.to_string(); return result; } } // namespace seq66 /* * eventlist.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/jack_assistant.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file jack_assistant.cpp * * This module defines the helper class for using JACK in the performance * mode. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-09-14 * \updates 2025-04-24 * \license GNU GPLv2 or above * * This module was created from code that existed in the performer object. * Moving it into is own module makes it easier to maintain and makes the * performer object a little easier to grok. * * For the summaries of the JACK functions used in this module, and how * the code is supposed to operate, see the Seq66 developer's reference * manual. It discusses the following items: * * - JACK Position Bits * - jack_transport_reposition() * * Only JackPositionBBT is supported so far. * * JACK clients and BPM: * * Does a JACK client need to be JACK Master before it can foist BPM changes * on other clients? What are the conventions? * * - https://linuxmusicians.com/viewtopic.php?t=14913&start=15 * - http://jackaudio.org/api/transport-design.html * * One might be curious as to the origin of the name "jack_assistant". Well, * it is simply so this class can be called "jack_ass" for short :-D. * * Note the confusing (but necessary) orientation of the JACK driver backend * ports: playback ports are "input" to the backend, and capture ports are * "output" from it. * * NSM Support: * * Seq66 supports NSM; see the code in libseq66/ * /sessions and in * libsessions. Also see the Session Management chapter in the PDF user * manual. * * JACK Session (JS) Support: * * A JS-aware application must: * * - Register with a JS manager. * - Respond to messages from the JS manager. * - Be startable with session information. * * Response to a JS message will generally: * * - Save the application's state into a file, where the directory is * given by the session manager. * - Reply to the session manager with a command that can be used to * restart the application, with enough information that it can * restore its state (e.g. the name of the state file). * * JS aware clients identify themselves to the session manager by a UUID * The client makes it up as an integer represented as a string; it is passed * to the session manager when registering, and passed back to the client * when it is restarted by the session manager. This is done by a command * line argument to the application, and the format of the command line is * also up to the client. For Seq66, these are the arguments: * * - --jack-session-uuid (-U for short) * - --home (-H for short) * - --config (-c for short) (Maybe???) * * - Saving a session will save the state of all supported applications. * The application can be told where to save the state. * - The 'rc', 'usr', and other files that need to be saved. * - The current MIDI file, if modified. * - The JACK connections that are active. * - Opening a session will launch the included and supported * applications. The application can be told where to load the * state. * - The 'rc' file should hold the connections to be restored. It can * also specify that the most recent MIDI file be loaded. */ #include #include /* strdup() */ #include "midi/jack_assistant.hpp" /* this seq66::jack_ass class */ #include "play/performer.hpp" /* seq66::performer class */ #include "cfg/settings.hpp" /* "rc" and "user" settings */ #define SEQ66_USE_BPMINUTE_CALCULATION /* portfix branch 2022-02-11 */ #if defined SEQ66_JACK_SESSION /* deprecated, use Non Session Mgr. */ #if defined SEQ66_JACK_METADATA #include #include /** * This item exists in the JACK 2 source code, but not in the installed JACK * headers on our development system. */ const char * JACK_METADATA_ICON_NAME = "http://jackaudio.org/metadata/icon-name"; #endif #include "midi/midifile.hpp" /* seq66::midifile class */ #endif /* * All library code in the Seq66 project is in the seq66 namespace. */ namespace seq66 { #if defined SEQ66_JACK_SUPPORT /* * ------------------------------------------------------------------------- * JACK Transport Callbacks * ------------------------------------------------------------------------- */ /** * For issue #100, storage for the true JACK transport position * Also might was well save them all, if only for display (later). * We are concerned only with the settings germane to JACK offset * calculations. We leave out beats_per_bar and beat_type for now. */ jack_assistant::parameters jack_assistant::sm_jack_parameters; bool jack_assistant::save_jack_parameters ( const jack_position_t & p, int periodsize, int alsanperiod ) { jack_position_t & currpos = sm_jack_parameters.position; bool result = ( (p.ticks_per_beat != currpos.ticks_per_beat) || (p.beats_per_minute != currpos.beats_per_minute) || (p.frame_rate != currpos.frame_rate) ); if (result) { currpos = p; sm_jack_parameters.period_size = periodsize; sm_jack_parameters.alsa_nperiod = alsanperiod; async_safe_errprint("JACK transport changed"); } return result; } const jack_assistant::parameters & jack_assistant::get_jack_parameters () { return sm_jack_parameters; } /** * Apparently, MIDI pulses are 10 times the size of JACK ticks. So we need, * in some places, to convert pulses (ticks) to JACK ticks by multiplying by * 10. */ static const int c_jack_factor = 10; #if defined USE_JACK_DEBUG_PRINT /** * Debugging code for JACK. We made this static so that we can hide it in * this module and enable it without enabling all of the debug code available * in the Seq66 code base. As a side-effect, we added a couple of * const accessors to jack_assistant so that outsiders can monitor some of * its status. * * Now, this is really too much output, so you'll have to enable it * separately by defining USE_JACK_DEBUG_PRINT. The difference in output * between this function and what jack_assitant::show_position() report may * be instructive. * * \param jack * The jack_assistant object for which to show debugging data. * * \param current_tick * The current time location. * * \param tick_delta * The change in ticks to show. */ static void jack_debug_print ( const jack_assistant & jack, double current_tick, double ticks_delta ) { static long s_output_counter = 0; if ((s_output_counter++ % 100) == 0) { const jack_position_t & p = jack.jack_pos(); double jtick = jack.get_jack_tick(); /* * double jack_tick = (p.bar-1) * (p.ticks_per_beat * p.beats_per_bar ) + * (p.beat-1) * p.ticks_per_beat + p.tick; */ long pbar = long(jtick) / long(p.ticks_per_beat * p.beats_per_bar); long pbeat = long(jtick) % long(p.ticks_per_beat * p.beats_per_bar); pbeat /= long(p.ticks_per_beat); long ptick = long(jtick) % long(p.ticks_per_beat); printf ( "* curtick=%4.2f delta=%4.2f BBT=%ld:%ld:%ld " "jbbt=%d:%d:%d jtick=%4.2f\n" // mtick=%4.2f cdelta=%4.2f\n" , current_tick, ticks_delta, pbar+1, pbeat+1, ptick, p.bar, p.beat, p.tick, jtick // , jack_tick, jtick - jack_tick ); } } #endif // USE_JACK_DEBUG_PRINT /** * Provides a dummy callback. * * \param nframes * An unused parameter. * * \param arg * Provides the jack_assistant pointer. * * \return * Does nothing, but returns nframes. If the arg parameter is null, then * 0 is returned. */ int jack_dummy_callback (jack_nframes_t nframes, void * arg) { jack_assistant * j = static_cast(arg); if (is_nullptr(j)) nframes = 0; return nframes; } /** * On 2021-05-02 we reverted some of the code in this function to the code * present in the Seq64 version. This seems to solve issue #51 where running * JACK transport under Arch/KXStudio environments causes very iffy playback, * with either multiple repetitions of Note Ons or notes being dropped from * complex tunes. * * Implemented second patch for JACK Transport from freddix/seq66 GitHub * project, to allow seq66 to follow JACK transport. For more advanced * ideas, see the MetronomeJack::process_callback() function in the "klick" * project. It plays a metronome tick after calculating if it needs to or * not. (Maybe we could use it to provide our own tick for recording * patterns.) * * The code sets the JACK position field bbt_offset to 0. It doesn't seem to * have any effect, though it can be seen when calling show_position() in the * jack_transport_callback() function. * * This callback is called by JACK whether stopped or rolling. JACK calls it * continually with state JackTransportStopped when the (external) Master is * not running, but only once with state JackTransportStarting. * * Issue #34 "seq66 doesn't follow jack_transport tempo changes": we no * longer make this conditional upon performer not running. * * Be vewwy vewwy carweful! This code is really prickly and touchy! * * \param nframes * Unused. * * \param arg * Used for debug output now. Note that this function will be called * very often, and this pointer will currently not be used unless * debugging is turned on. * * \return * Returns 0 on success, non-zero on error. */ int jack_transport_callback (jack_nframes_t /*nframes*/, void * arg) { jack_assistant * j = reinterpret_cast(arg); if (not_nullptr(j)) { jack_position_t pos; jack_transport_state_t s = ::jack_transport_query(j->client(), &pos); performer & p = j->parent(); /* * int psize = ::jack_get_buffer_size(j->client()); * jack_nframes_t rate = ::jack_get_sample_rate(j->client()); * (void) jack_assistant::save_jack_parameters(pos, psize); */ #if defined SEQ66_PLATFORM_DEBUG_TMI static jack_time_t s_last = 0; jack_time_t timeus = ::jack_get_time(); unsigned delta = unsigned(timeus - s_last); if (pos.frame > 0) { printf("[debug] us %u, delta %u, frame %u\n", unsigned(timeus), delta, unsigned(pos.frame)); } s_last = timeus; #endif if (p.is_running()) { if (j->is_slave()) { if (pos.beats_per_minute > 1.0) /* a sanity check */ { static double s_old_bpm = 0.0; if (pos.beats_per_minute != s_old_bpm) { s_old_bpm = pos.beats_per_minute; j->parent().set_beats_per_minute(pos.beats_per_minute); } } } long tick = j->current_jack_position(); p.jack_reposition(tick, j->jack_stop_tick()); } else { /* * At start or FF/RW when not running, start or reposition the * transport marker. Using "! j->is_master()" may lead to a hang * at exit [in ~QApplication()]. Not sure, very tricky, sporadic. */ if (j->is_slave()) { if (pos.beats_per_minute > 1.0) /* a sanity check */ { static double s_old_bpm = 0.0; if (pos.beats_per_minute != s_old_bpm) { s_old_bpm = pos.beats_per_minute; j->parent().set_beats_per_minute(pos.beats_per_minute); } } } if (s == JackTransportRolling || s == JackTransportStarting) { j->m_transport_state_last = JackTransportStarting; p.inner_start(); } else /* reposition transport marker */ { long tick = j->current_jack_position(); p.jack_reposition(tick, j->jack_stop_tick()); } } } return 0; } /* * ------------------------------------------------------------------------- * JACK Helper Functions * ------------------------------------------------------------------------- * * These functions can be call by jack_assistant or the midi_jack * classes. */ /** * A more full-featured initialization for a JACK client, which is meant to * be called by the init() function. Do not call this function if the JACK * client handle is already open. * * Status bits for jack_status_t return pointer: * * JackNameNotUnique means that the client name was not unique. With * JackUseExactName, this is fatal. Otherwise, the name was modified by * appending a dash and a two-digit number in the range "-01" to "-99". * The jack_get_client_name() function returns the exact string used. If * the specified client_name plus these extra characters would be too * long, the open fails instead. * * JackServerStarted means that the JACK server was started as a result * of this operation. Otherwise, it was running already. In either case * the caller is now connected to jackd, so there is no race condition. * When the server shuts down, the client will find out. * * JackOpenOptions: * * JackSessionID | JackServerName | JackNoStartServer | JackUseExactName * * helgrind: * * Valgrind's helgrind tool shows * \verbatim Possible data race during read of size 4 at 0xF854E58 by thread #1 by 0x267602: seq66::create_jack_client(...) This conflicts with a previous write of size 4 by thread #2 by 0x267602: seq66::create_jack_client(...) \endverbatim * * So we add a static mutex to use with our automutex. Does not prevent * that message..... WHY? Now (2021-05-23) we're making the string * parameters copies. * * \param clientname * Provides the name of the client, used in the call to * jack_client_open(). By default, this name is the macro SEQ66_PACKAGE * (i.e. "seq66"). The name scope is local to each server. Unless * forbidden by the JackUseExactName option, the server will modify this * name to create a unique variant, if needed. * * \param uuid * The optional UUID to assign to the new client. If empty, there is no * UUID. * * \return * Returns a pointer to the JACK client if JACK has opened the client * connection successfully. Otherwise, a null pointer is returned. */ jack_client_t * create_jack_client (std::string clientname, std::string uuid) { jack_client_t * result = nullptr; const char * name = clientname.c_str(); jack_status_t status; jack_status_t * ps = &status; jack_options_t options = JackNoStartServer; if (uuid.empty()) { result = ::jack_client_open(name, options, ps); } else { const char * uid = uuid.c_str(); options = static_cast(JackNoStartServer|JackSessionID); result = ::jack_client_open(name, options, ps, uid); if (not_nullptr(result) && rc().investigate()) { char t[80]; snprintf(t, sizeof t, "client UUID %s opened", uid); (void) info_message(t); } } if (not_nullptr(result)) { if (status & JackServerStarted) (void) info_message("JACK server started"); else (void) info_message("JACK server already started"); if (status & JackNameNotUnique) { char t[80]; snprintf(t, sizeof t, "JACK client name '%s' not unique", name); (void) info_message(t); } else show_jack_statuses(status); } else (void) error_message("JACK server not running"); return result; /* bad result handled by caller */ } /** * Computing the BBT information from the frame number is relatively simple * here, but would become complex if we supported tempo or time signature * changes at specific locations in the transport timeline. * \verbatim ticks * 10 = jack ticks; jack ticks / ticks per beat = num beats; num beats / beats per minute = num minutes num minutes * 60 = num seconds num secords * frame_rate = frame \endverbatim * * Modifying frame rate and frame cannot be set from clients, the server * sets them; see transport.h of JACK. * \verbatim jack_nframes_t rate = jack_get_sample_rate(m_jack_client); pos.frame_rate = rate; pos.frame = (jack_nframes_t) ( (tick * rate * 60.0) / (pos.ticks_per_beat * pos.beats_per_minute) ); \endverbatim * * \param pos * Provides the beats/bar, beat-width, ppqn (later modified), etc. * See jack_assistant::set_position() for a usage example. * * \param tick * Provides the current position to be set. */ void jack_set_position ( jack_client_t * client, jack_position_t & pos, midipulse tick ) { pos.ticks_per_beat *= c_jack_factor; tick *= c_jack_factor; long beattype = long(pos.beat_type); /* for mod operation */ long ticksperbeat = long(pos.ticks_per_beat); /* for mod operation */ pos.valid = JackPositionBBT; /* BBT to be modified */ pos.bar = int32_t(tick / long(pos.ticks_per_beat) / pos.beats_per_bar); pos.beat = int32_t(((tick / long(pos.ticks_per_beat)) % beattype)); pos.tick = int32_t((tick % ticksperbeat)); pos.bar_start_tick = pos.bar * pos.beats_per_bar * pos.ticks_per_beat; ++pos.bar; ++pos.beat; /* * Modifying frame rate and frame is a server function. See the banner. */ pos.valid = (jack_position_bits_t)(pos.valid | JackBBTFrameOffset); pos.bbt_offset = 0; int jackcode = ::jack_transport_reposition(client, &pos); if (jackcode != 0) { errprint("JACK reposition bad position structure"); } } /** * A free function in the Seq66 namespace * * This works to get back the correct client name: * * char * name = jack_get_client_name_by_uuid(jc, luuid); */ std::string get_jack_client_uuid (jack_client_t * jc) { std::string result; #if defined SEQ66_JACK_SESSION /* deprecated, use Non Session Mgr. */ char * luuid = ::jack_client_get_uuid(jc); if (not_nullptr(luuid)) { result = luuid; /* see note in the banner */ jack_free(luuid); } #else char * lname = ::jack_get_client_name(jc); if (not_nullptr(lname)) { char * luuid = jack_get_uuid_for_client_name(jc, lname); if (not_nullptr(luuid)) { result = luuid; /* * Apparently not needed here: jack_free(luuid); */ } } #endif return result; } #if defined SEQ66_JACK_METADATA /** * Need type to be "image/png;" in some of these calls. * * \param key * Provides the name of this property, as a URI string. * * \param value * Provides the value of the property, a null-terminated string. * * \param type * Provides the type of data as an empty string (implies UTF-8 * text/plain), a MIME type such as "image/png;base64" (image) or a URI * such as http://www.w3.org/2001/XMLSchema#int (integer). */ bool set_jack_client_property ( jack_client_t * jc, const std::string & key, const std::string & value, const std::string & type ) { std::string uuid = get_jack_client_uuid(jc); bool result = ! uuid.empty(); if (result) { jack_uuid_t u2 = JACK_UUID_EMPTY_INITIALIZER; int rc = ::jack_uuid_parse(uuid.c_str(), &u2); result = rc == 0; if (result) { const char * k = key.c_str(); const char * v = value.c_str(); const char * t = type.c_str(); rc = ::jack_set_property(jc, u2, k, v, t); result = rc == 0; } } return result; } bool set_jack_port_property ( jack_client_t * jc, jack_port_t * jp, const std::string & key, const std::string & value, const std::string & type ) { jack_uuid_t uuid = ::jack_port_uuid(jp); const char * k = key.c_str(); const char * v = value.c_str(); const char * t = type.empty() ? NULL : type.c_str() ; /* important! */ int rc = ::jack_set_property(jc, uuid, k, v, t); return rc == 0; } /** * This version does not seem to work. */ bool set_jack_port_property ( jack_client_t * jc, const std::string & portname, const std::string & key, const std::string & value, const std::string & type ) { jack_port_t * jp = ::jack_port_by_name(jc, portname.c_str()); jack_uuid_t uuid = ::jack_port_uuid(jp); const char * k = key.c_str(); const char * v = value.c_str(); const char * t = type.empty() ? NULL : type.c_str() ; /* important! */ int rc = ::jack_set_property(jc, uuid, k, v, t); // use t == NULL? return rc == 0; } #endif // defined SEQ66_JACK_METADATA /** * Provides a list of JACK status bits, and a brief string to explain the * status bit. Terminated by a 0 value and an empty string. */ jack_status_pair_t s_status_pairs [] = { { JackFailure, "JackFailure: overall operation failed" }, { JackInvalidOption, "JackInvalidOption: used an invalid/unsupported option" }, { JackNameNotUnique, "JackNameNotUnique: client name not unique" }, { JackServerStarted, "JackServerStarted: JACK started by this operation" }, { JackServerFailed, "JackServerFailed: cannot connect to JACK server" }, { JackServerError, "JackServerError: JACK servercommunication error" }, { JackNoSuchClient, "JackNoSuchClient: requested client does not exist" }, { JackLoadFailure, "JackLoadFailure: cannot load internal client" }, { JackInitFailure, "JackInitFailure: cannot initialize client" }, { JackShmFailure, "JackShmFailure: cannot access shared memory" }, { JackVersionError, "JackVersionError: client protocol version mismatch" }, { JackBackendError, "JackBackendError: JACK back-end error" }, { JackClientZombie, "JackClientZombie: JACK zombie process!" }, { /* terminator */ 0, "" } }; /** * Loops through the full set of JACK bits, showing the information for any * bits that are set in the given parameter. For reference, here are the * enumeration values from /usr/include/jack/types.h: * \verbatim JackFailure = 0x01 JackInvalidOption = 0x02 JackNameNotUnique = 0x04 JackServerStarted = 0x08 JackServerFailed = 0x10 JackServerError = 0x20 JackNoSuchClient = 0x40 JackLoadFailure = 0x80 JackInitFailure = 0x100 JackShmFailure = 0x200 JackVersionError = 0x400 JackBackendError = 0x800 JackClientZombie = 0x1000 \endverbatim * * \param bits * The mask of the bits to be shown in the output. */ void show_jack_statuses (unsigned bits) { jack_status_pair_t * jsp = &s_status_pairs[0]; while (jsp->jf_bit != 0) { if (bits & jsp->jf_bit) (void) info_message(jsp->jf_meaning); ++jsp; } } /* * ------------------------------------------------------------------------- * More JACK helper functions * ------------------------------------------------------------------------- */ static double jack_ticks (const jack_position_t & pos) { return ( pos.frame * pos.ticks_per_beat * pos.beats_per_minute / (pos.frame_rate * 60.0) ); } static double jack_ticks_delta (int framediff, const jack_position_t & pos) { return ( framediff * pos.ticks_per_beat * pos.beats_per_minute / (pos.frame_rate * 60.0) ); } /* * ------------------------------------------------------------------------- * JACK Assistant * ------------------------------------------------------------------------- */ /** * This constructor initializes a number of member variables, some * of them public! * * Note that the performer object currently calls jack_assistant::init(), but * that call could be made here instead. * * \param parent * Provides a reference to the main performer object that needs to * control JACK event. * * \param bpminute * The beats/minute to set up JACK to use (applies to Master setup). * * \param ppqn * The parts-per-quarter-note setting in force for the present tune. * * \param bpmeasure * The beats/measure (time signature numerator) in force for the present * tune. * * \param beatwidth * The beat-width (time signature denominator) in force for the present * tune. */ jack_assistant::jack_assistant ( performer & parent, midibpm bpminute, int ppqn, int bpmeasure, int beatwidth ) : m_jack_parent (parent), m_jack_client (nullptr), m_jack_client_name (), m_jack_client_uuid (), m_frame_current (0), m_frame_last (0), m_jack_pos (), m_transport_state (JackTransportStopped), m_transport_state_last (JackTransportStopped), m_jack_tick (0.0), m_jack_running (false), m_timebase (timebase::none), /* or slave, master... */ #if defined USE_TIMEBASE_MASTER m_timebase_tracking (-1), #endif m_frame_rate (0), m_toggle_jack (false), m_jack_stop_tick (0), m_follow_transport (true), m_ppqn (choose_ppqn(ppqn)), m_beats_per_measure (bpmeasure), m_beat_width (beatwidth), m_beats_per_minute (bpminute) { /* * Do this in the rtmidi constructor. * * const char * jv = jack_get_version_string(); * if (not_nullptr(jv) && strlen(jv) > 0) * set_jack_version(std::string(jv)); */ } /** * The destructor doesn't need to do anything yet. The performer object * currently calls jack_assistant::deinit(), but that call could be made here * instead. */ jack_assistant::~jack_assistant () { /* * Anything to do? Call deinit()? */ } /** * Tries to obtain the best information on the JACK client and the UUID * assigned to this client. Sets m_jack_client_name and m_jack_client_info * as side-effects. * * Only store the transport UUID in the rc().jack_session() if not already * filled by opening the with-jack MIDI client. */ void jack_assistant::get_jack_client_info () { char * actualname = ::jack_get_client_name(m_jack_client); if (not_nullptr(actualname)) { m_jack_client_uuid = get_jack_client_uuid(m_jack_client); if (! m_jack_client_uuid.empty()) /* this test okay?? */ { if (rc().jack_session().empty()) rc().jack_session(m_jack_client_uuid); } m_jack_client_name = actualname; } std::string jinfo = "JACK transport client:uuid "; jinfo += m_jack_client_name; if (! m_jack_client_uuid.empty()) { jinfo += ":"; jinfo += m_jack_client_uuid; } (void) info_message(jinfo); } /** * Initializes JACK support. Then we become a new client of the JACK server. * * A sync callback is needed for polling of slow-sync clients. But * seq66 are not slow-sync clients. We don't really need to be a * slow-sync client, as far as we can tell. We can't get JACK working * exactly the way it does in seq66 without the callback in place. Plus, it * does things important to the setup of JACK. So now this setup is * permanent. * * Jack transport settings: * * There are three settings: On, Master, and Master Conditional. * Currently, they can all be selected in the user-interface's File / * Options / JACK page. We really want only the proper combinations * to be set, for clarity (the user-interface now takes care of this. We * need to initialize if any of them are set, and the * rcsettings::with_jack_transport() function tells us that. * * jack_set_process_callback() patch: * * Implemented first patch from freddix/seq66 GitHub project, to fix JACK * transport. One line of code. Well, we added some error-checking. :-) * Found some old notes on the Web the this patch really only works (to * prevent seq66 freeze) if seq66 is set as JACK Master, or if another * client application, such as Qtractor, is running as JACK Master (and * then seq66 will apparently follow it). * * Stazed: * * The call to jack_timebase_callback() to supply jack with BBT, etc. * would occasionally fail when the *pos information had zero or some * garbage in the pos.frame_rate variable. This would occur when there * was a rapid change of frame position by another client... i.e. * qjackctl. From the jack API: * * "pos address of the position structure for the next cycle; * pos->frame will be its frame number. If new_pos is FALSE, this * structure contains extended position information from the current * cycle. If TRUE, it contains whatever was set by the requester. * The timebase_callback's task is to update the extended information * here." * * The "If TRUE" line seems to be the issue. It seems that qjackctl does * not always set pos.frame_rate so we get garbage and some strange BBT * calculations that display in qjackctl. So we need to set it here and * just use m_frame_rate for calculations instead of pos.frame_rate. * * Stazed JACK support uses only the jack_transport_callback(). Makes * sense, since seq66/32/64 are not "slow-sync" clients. * * \return * Returns true if JACK is now considered to be running (or if it was * already running.) */ bool jack_assistant::init () { bool result = rc().with_jack_transport() && ! m_jack_running; if (result) { std::string kind = rc().with_jack_master() ? "master" : "slave" ; std::string package = rc().app_client_name() + kind; m_timebase = timebase::none; m_jack_client = client_open(package); if (m_jack_client == NULL) { result = false; return error_message("No JACK server"); } else { m_frame_rate = ::jack_get_sample_rate(m_jack_client); get_jack_client_info(); ::jack_on_shutdown /* no return value */ ( m_jack_client, jack_transport_shutdown, (void *) this ); int jackcode = ::jack_set_process_callback /* notes in banner */ ( m_jack_client, jack_transport_callback, (void *) this ); if (jackcode != 0) { result = false; return error_message("JACK set callback failed"); } } #if defined SEQ66_JACK_SESSION if (result && usr().want_jack_session()) { int jackcode = ::jack_set_session_callback ( m_jack_client, jack_session_callback, (void *) this ); if (jackcode != 0) { result = false; (void) error_message("jack_set_session_callback() failed]"); } else { if (rc().investigate_disabled()) info_message("JACK session callback set"); } } #endif if (result) { bool master_is_set = false; /* flag to handle trickery */ if (rc().with_jack_master()) /* OR with 'cond' removed */ { /* * 'cond' is true if we want to fail if there is already a JACK * master, i.e. it is a conditional attempt to be JACK master. */ bool cond = rc().with_jack_master_cond(); int jackcode = ::jack_set_timebase_callback ( m_jack_client, cond, jack_timebase_callback, (void *) this ); if (jackcode == 0) { (void) info_message("JACK transport master"); m_timebase = timebase::master; master_is_set = true; } else { result = false; (void) error_message("jack_set_timebase_callback() failed"); } } if (! master_is_set) { m_timebase = timebase::slave; (void) info_message("JACK transport slave"); } } if (result) { result = activate(); if (result) { (void) info_message("JACK transport enabled"); m_jack_running = true; } else (void) info_message("Running without JACK transport"); } } return result; } /** * Tears down the JACK infrastructure. * * \todo * Note that we still need a way to call jack_release_timebase() when * the user turns off the "JACK Master" status of Seq66. * * \return * Returns the value of m_jack_running, which should be false. */ bool jack_assistant::deinit () { bool result = true; if (m_jack_running) { m_jack_running = false; if (is_master()) { m_timebase = timebase::none; if (::jack_release_timebase(m_jack_client) != 0) (void) error_message("Cannot release JACK timebase"); } /* * New: Simply to be symmetric with the startup flow. Not yet sure * why jack_activate() was needed, but assume that jack_deactivate() is * thus important as well. */ if (::jack_deactivate(m_jack_client) != 0) result = error_message("Can't deactivate JACK transport"); if (::jack_client_close(m_jack_client) != 0) result = error_message("Can't close JACK transport"); } return result; } /** * Activate JACK here. This function is called by performer::activate() after * the master bus is activated successfully. * * \return * Returns true if the m_jack_client pointer is null, which means only * that we're not running JACK. Also returns true if the pointer exists * and the jack_active() call succeeds. * * \sideeffect * The m_jack_running and JACK master flags are falsified if * jack_activate() fails. */ bool jack_assistant::activate () { bool result = true; if (not_nullptr(m_jack_client)) { int psize = ::jack_get_buffer_size(m_jack_client); int rc = ::jack_activate(m_jack_client); jack_position_t pos; (void) ::jack_transport_query(m_jack_client, &pos); (void) jack_assistant::save_jack_parameters(pos, psize); result = rc == 0; if (result) { (void) info_message("JACK activated"); } else { m_timebase = timebase::none; (void) error_message("Can't activate JACK transport client"); } } return result; } /** * If JACK is supported, starts the JACK transport. This function assumes * that m_jack_client is not null, if m_jack_running is true. * * QUESTION: Why start transport if no transport enabled? */ void jack_assistant::start () { if (m_jack_running) { ::jack_transport_start(m_jack_client); if (is_master()) set_position(parent().get_tick()); } else if (rc().with_jack()) (void) error_message("Sync start: JACK not running"); } /** * If JACK is supported, stops the JACK transport. This function assumes * that m_jack_client is not null, if m_jack_running is true. * * \param rewind * If true (the default is false), then set the JACK position to 0. */ void jack_assistant::stop (bool rewind) { if (m_jack_running) { ::jack_transport_stop(m_jack_client); if (rewind) set_position(0); } else if (rc().with_jack()) (void) error_message("Sync stop: JACK not running"); } /** * performer::set_beats_per_minute() validates the BPM. Also, since * jack_transport_reposition() can be "called at any time by any client", we * have removed the check for "is master". We do seem to see more "bad * position structure" messages, though. * * \param bpminute * Provides the beats/minute value to set. */ void jack_assistant::set_beats_per_minute (midibpm bpminute) { if (bpminute != m_beats_per_minute) { m_beats_per_minute = bpminute; if (not_nullptr(m_jack_client)) { (void) ::jack_transport_query(m_jack_client, &jack_pos()); jack_pos().beats_per_minute = bpminute; int rc = ::jack_transport_reposition(m_jack_client, &jack_pos()); if (rc != 0) { errprint("JACK transport bad position structure"); } } } } /** * If JACK is supported and running, sets the position of the transport to * the new frame number, frame 0. This new position takes effect in two * process cycles. If there are slow-sync clients and the transport is * already rolling, it will enter the JackTransportStarting state and begin * invoking their sync_callbacks until ready. This function is realtime-safe. * * http://jackaudio.org/files/docs/html/transport-design.html * * This position() function is called via performer::position_jack() in the * mainwnd, perfedit, perfroll, and seqroll graphical user-interface support * objects. * * Stazed: * * The jack_frame calculation is all that is needed to change JACK * position. The BBT calculation can be sent, but will be overridden by the * first call to jack_timebase_callback() of any Master set. If no Master * is set, then the BBT will display the new position but will not change * it, even if the transport is rolling. There is no need to send BBT on * position change -- the fact that jack_transport_locate() exists and only * uses the frame position is proof that BBT is not needed! Upon further * reflection, why not send BBT? Because other programs do not... let's * follow convention. The calculation for jack_transport_locate(), works, * is simpler, and does not send BBT. The calculation for * jack_transport_reposition() will be commented out again. * jack_BBT_position() is not necessary to change jack position! * * Let's follow the example of Stazed's tick_to_jack_frame() function. One odd * effect we want to solve is why Seq66 as JACK slave is messing up the playback * in Hydrogen (it oscillates around the 0 marker). Note that there are * potentially a couple of divide-by-zero opportunities in this function. * Helgrind complains about a possible data race involving * jack_transport_locate() when starting playing. * * \param songmode * True if the caller wants to position while in Song mode. * * Alternate parameter to_left_tick (non-seq32 version): * * If true, the current tick is set to the leftmost tick, instead of the * 0th tick. Now used, but only if relocate is true. One question is, * do we want to performer this function if rc().with_jack_transport() is * true? Seems like we should be able to do it only if JACK master is * true. * * \param tick * If using Song mode for this call then this value is set as the * "current tick" value. If it's value is bad (null_midipulse), * then this parameter is set to 0 before being used. */ void jack_assistant::position (bool songmode, midipulse tick) { #if defined SEQ66_JACK_SUPPORT if (songmode) /* master in song mode */ tick = is_null_midipulse(tick) ? 0 : tick * c_jack_factor ; else tick = 0; int ticks_per_beat = m_ppqn * c_jack_factor; int beats_per_minute = parent().get_beats_per_minute(); uint64_t tick_rate = (uint64_t(m_frame_rate) * tick * 60.0); long tpb_bpm = ticks_per_beat * beats_per_minute * 4.0 / m_beat_width; uint64_t jack_frame = tick_rate / tpb_bpm; if (is_master()) { /* * We don't want to do this unless we are JACK Master. Otherwise, * other JACK clients never advance if Seq66 won't advance. * However, according to JACK docs, "Any client can start or stop * playback, or seek to a new location." */ if (::jack_transport_locate(m_jack_client, jack_frame) != 0) (void) info_message("jack_transport_locate() failed"); } if (parent().is_running()) parent().set_reposition(false); #endif } /** * Computing the BBT information from the frame number is relatively simple * here, but would become complex if we supported tempo or time signature * changes at specific locations in the transport timeline. * \verbatim ticks * 10 = jack ticks; jack ticks / ticks per beat = num beats; num beats / beats per minute = num minutes num minutes * 60 = num seconds num secords * frame_rate = frame \endverbatim * * \param tick * Provides the current position to be set. */ void jack_assistant::set_position (midipulse tick) { jack_position_t pos; pos.beats_per_bar = m_beats_per_measure; pos.beat_type = m_beat_width; pos.ticks_per_beat = m_ppqn; /* only at first */ pos.beats_per_minute = get_beats_per_minute(); jack_set_position(m_jack_client, pos, tick); } #if defined SEQ66_USE_JACK_SYNC_CALLBACK /** * A helper function for syncing up with JACK parameters. Seq66 is not a * slow-sync client (and Stazed support doesn't use it), so that callback is * not really needed, but we probably need this sub-function here to start out * with the right values for interacting with JACK. * * Note the call to jack_transport_query(). This call but seems to be needed * because we put m_jack_pos in the initializer list, which sets all its fields * to 0. Seq24 accesses m_jack_pos before it ever gets set, but its fields * have values. These values are bogus, but are consistent from run to run on * my computer, and allow seq66 to follow another JACK Master, on some * computers. It explains why people had different experiences with JACK * transport. * * If we explicity call jack_transport_query() here, without changing the \a * state parameter, then seq66 also can follow another JACK Master. * (CURRENTLY BUGGY!) * * Note that we should consider massaging the following jack_position_t * members to set them to 0 (or 0.0) if less than 1.0 or 0.5: * * - bar_start_tick * - ticks_per_beat * - beats_per_minute * - frame_time * - next_time * - audio_frames_per_video_frame * * Also, why does bbt_offset start at 2128362496? * * \param state * The JACK transport state to be set. */ int jack_assistant::sync (jack_transport_state_t state) { int result = 0; /* seq66 always returns 1 */ m_frame_current = ::jack_get_current_transport_frame(m_jack_client); (void) ::jack_transport_query(m_jack_client, &jack_pos()); jack_nframes_t rate = jack_pos().frame_rate; if (rate == 0) { /* * The actual frame rate might be something like 48000. Try to make * it work somehow, for now. */ errprint("jack_assistant::sync(): zero frame rate"); rate = 48000; } else result = 1; double ppqn = jack_pos().ticks_per_beat; #if defined SEQ66_USE_BPMINUTE_CALCULATION // portfix branch 2022-02-11 double bpminute = jack_pos().beats_per_minute; m_jack_tick = m_frame_current * ppqn * bpminute / (rate * 60.0); #else double bpbar = beats_per_measure(); // vice beats-per-minute double bw = beat_width(); m_jack_tick = m_frame_current * bpbar * ppqn / (15.0 * rate * bw); #endif m_frame_last = m_frame_current; m_transport_state_last = m_transport_state = state; if (state == JackTransportStarting) parent().inner_start(); return result; } #endif // defined SEQ66_USE_JACK_SYNC_CALLBACK #if defined SEQ66_USE_JACK_SYNC_CALLBACK /** * This JACK synchronization callback informs the specified performer * object of the current state and parameters of JACK. * * The transport state will be: * * - JackTransportStopped when a new position is requested. * - JackTransportStarting when the transport is waiting to start. * - JackTransportRolling when the timeout has expired, and the * position is now a moving target. * * This is the slow-sync callback, which the stazed code replaces with * jack_transport_callback(). * * \param state * The JACK Transport state. * * \param pos * The JACK position value. * * \param arg * The pointer to the jack_assistant object. Currently not checked for * nullity, nor dynamic-casted. * * \return * Returns 1 if the function works, and 0 if something was wrong. */ int jack_sync_callback ( jack_transport_state_t state, jack_position_t * pos, void * arg ) { int result = 0; jack_assistant * jack = static_cast(arg); if (not_nullptr(jack)) { /* * See the following function's modification in portfix branch * on 2022-02-11. */ result = jack->sync(state); /* use the new member function */ } else { errprint("jack_sync_callback(): null JACK pointer"); } return result; } #endif // SEQ66_USE_JACK_SYNC_CALLBACK #if defined SEQ66_JACK_SESSION /* "deprecated" alternative to NSM */ static std::string session_event_name (jack_session_event_t * ev) { std::string result = "JACK Session Event: '"; if (ev->type == JackSessionSave) result += "Save"; else if (ev->type == JackSessionSaveAndQuit) result += "Save and quit"; else if (ev->type == JackSessionSaveTemplate) result += "Save template"; result += "'"; return result; } /** * Writes the state information quits if told to by JACK, and can free the JACK * session event. * * The job of the callback is to save state information, pass information back * to the session manager and perhaps exit. * * Command line: * * qseq66 --jack-session-uuid UUID --home SESSION_DIR * * Event types: * * - JackSessionSave. Save the session completely. The client may save * references to data outside the provided directory, but only so by * creating a link inside the provided directory. The client must * not refer to data files outside the provided directory directly in * save files; this makes it impossible for the session manager to * create a session archive for distribution or archival. * - JackSessionSaveAndQuit. Save the session completely, then quit. * The rules for saving are exactly the same as for JackSessionSave. * - JackSessionSaveTemplate. Save a session template. A session * template is a "skeleton" of the session without any data. Clients * must save a session that, when restored, will create the same * ports as a full save would have. However, the actual data * contained in the session may not be saved (e.g. a DAW would create * the necessary tracks, but not save the actual recorded data). * * Flags: * * - JackSessionSaveError. An error occurred while saving. * - JackSessionNeedTerminal. Client needs to run in a terminal. * * TODO: * * Move this code into libsessions/ * / jack, create a base class * [sessionbase] to use with a new jack_session class and the existing * nsmbase class. The functions needed in the session base class might be * * - is_active() and m_active * - is_a_client() and not_a_client() * - qsessionmanager * - session_manager * - session_path * - session_display_name * - session_client_id */ void jack_assistant::session_event (jack_session_event_t * ev) { bool quit = false; std::string uuid = std::string(ev->client_uuid); std::string filepath = std::string(ev->session_dir); std::string cmd = seq_app_name(); /* e.g. "qseq66" */ cmd += (" --jack-midi"); cmd += (" --jack-"); cmd += rc().with_jack_master() ? "master" : "slave" ; cmd += (" --jack-session "); cmd += uuid; cmd += " --home ${SESSION_DIR}"; ev->command_line = strdup(cmd.c_str()); // NEED TO free() EVENTUALLY! std::string clientname = rc().app_client_name(); /* seq_client_id() */ clientname += ":"; clientname += uuid; rc().app_client_name(clientname); /* set_client_id() */ rc().home_config_directory(filepath); /* * Not sure it is the business of this particular session manager (as * opposed to NSM) to determine where the user's data resides. * * rc().midi_filepath(filepath); */ if (::jack_session_reply(m_jack_client, ev) != 0) { errprint("JACK session reply failed"); } if (ev->type == JackSessionSave) { parent().signal_save(); /* session_save() */ } else if (ev->type == JackSessionSaveAndQuit) { quit = true; } else if (ev->type == JackSessionSaveTemplate) { /* * Not yet directly supported. Seq66 creates bare-bones files only if * no configuration files yet exist in the "home" location. */ } if (rc().investigate_disabled()) { info_message(session_event_name(ev)); file_message("Session command", cmd); file_message("Session path", filepath); } ::jack_session_event_free(ev); if (quit) parent().signal_quit(); /* session_close() */ else rc().jack_session_activate(); /* really have session! */ } /** * Glib is then used to connect in performer::jack_session_event(). However, * the performer object's GUI-support interface is used instead of the * following, so that the libseq66 library can be independent of a specific * GUI framework: * * Glib::signal_idle(). * connect(sigc::mem_fun(*jack, &jack_assistant::session_event)); * * \param ev * The JACK event to be set. * * \param arg * The pointer to the jack_assistant object. Currently not checked * for nullity. */ void jack_session_callback (jack_session_event_t * ev, void * arg) { jack_assistant * jack = static_cast(arg); if (not_nullptr(jack)) jack->session_event(ev); /* * jack->parent().gui().jack_idle_connect(*jack); // see note above */ } #endif // SEQ66_JACK_SESSION /** * Performance output function for JACK, called by the performer function * of the same name. This code comes from performer::output_func() from seq24. * * \note * Follow up on this note found "out there": "Maybe I'm wrong but if I * understood correctly, recent jack1 transport no longer goes into * Jack_Transport_Starting state before going to Jack_Transport_Rolling * (this was deliberately dropped), but seq24 currently needs this to * start off with JACK transport." On the other hand, some people have * no issues. This may have been due to the lack of m_jack_pos * initialization. * * Stazed: * * Another note about JACK. If another JACK client supplies tempo/BBT * different from seq42 (as Master), the perfroll grid will be incorrect. * Perfroll uses internal temp/BBT and cannot update on the fly. Even if * seq42 could support tempo/BBT changes, all info would have to be * available before the transport start, to work. For this reason, the * tempo/BBT info will be plugged from the seq42 internal settings here, * always. This is the method used by probably all other JACK clients * with some sort of time-line. The JACK API indicates that BBT is * optional and AFIK, other sequencers only use frame & frame_rate from * JACK for internal calculations. The tempo and BBT info is always * internal. Also, if there is no Master set, then we would need to plug * it here to follow the JACK frame anyways. * * Issue #48 "Non-JACK-Master p'back not working if built for non-seq32 JACK": * * Using the seq32 code here works to solve issue #48, We scrapped the * old code entirely. As for the setting of beats/minute, we had thought * that we wanted to force a change in BPM only if we are JACK Master, * but this is not true, and prevents Seq66 from playing back when not * the Master. * * \param pad * Provides a JACK scratchpad for sharing certain items between the * performer object and the jack_assistant object. * * \return * Returns true if JACK is running. */ bool jack_assistant::output (jack_scratchpad & pad) { if (m_jack_running) { pad.js_init_clock = false; /* no init until a good lock */ m_transport_state = ::jack_transport_query(m_jack_client, &jack_pos()); /* See Issue #48 above */ jack_pos().beats_per_bar = m_beats_per_measure; jack_pos().beat_type = m_beat_width; jack_pos().ticks_per_beat = m_ppqn * c_jack_factor; jack_pos().beats_per_minute = parent().get_beats_per_minute(); if (transport_rolling_now()) { midipulse midi_ticks; m_frame_current = ::jack_get_current_transport_frame(m_jack_client); m_frame_last = m_frame_current; jack_assistant::set_position(m_frame_current); pad.js_dumping = true; /* "[Start JACK Playback]" */ /* * Here, Seq32 uses the tempo map if in song mode, instead of * making these calculations. */ m_jack_tick = jack_ticks(jack_pos()); midi_ticks = midipulse(m_jack_tick * tick_multiplier() + 0.5); parent().set_last_ticks(midi_ticks); pad.set_current_tick_ex(midi_ticks); pad.js_init_clock = true; if (pad.js_looping && pad.js_playback_mode) { if (pad.js_current_tick >= parent().get_right_tick()) { /* * Let C = pad.js_current_tick, R and L be the looping * ticks. This loop should continue about C / (R-L) * times, if R and L remain the same. */ double r_minus_l = parent().left_right_size(); while (pad.js_current_tick >= parent().get_right_tick()) pad.js_current_tick -= r_minus_l; parent().off_sequences(); parent().set_last_ticks(midipulse(pad.js_current_tick)); } } } if (transport_stopped_now()) { m_transport_state_last = JackTransportStopped; pad.js_jack_stopped = true; } /* * Jack Transport is Rolling Now!?? Transport is in a sane state if * dumping == true. */ if (pad.js_dumping) { m_frame_current = ::jack_get_current_transport_frame(m_jack_client); if (m_frame_current > m_frame_last) /* moving ahead? */ { /* * Seq32 uses tempo map if in song mode here, instead. */ if (jack_pos().frame_rate > 0) /* usually 48000 */ { int diff = int(m_frame_current - m_frame_last); m_jack_tick += jack_ticks_delta(diff, jack_pos()); } else info_message("JACK output 2 zero frame rate"); m_frame_last = m_frame_current; } double midi_ticks = m_jack_tick * tick_multiplier(); double delta = midi_ticks - pad.js_ticks_converted_last; if (delta != 0.0) { /* * On one system, a PPQN of 192 yields deltas around 1.5, * and PPQN yields deltas of around 0.8, leading to many more * "replays" of the same tick value. */ pad.js_clock_tick += delta; pad.js_current_tick += delta; pad.js_total_tick += delta; } m_transport_state_last = m_transport_state; pad.js_ticks_converted_last = midi_ticks; #if defined USE_JACK_DEBUG_PRINT jack_debug_print(*this, pad.js_current_tick, delta); #endif } /* if dumping (sane state) */ } /* if m_jack_running */ return m_jack_running; } #if defined USE_TIMEBASE_MASTER /** * Adapted from Hydrogen source code. If transport is not stopped, check the * timebase tracking count. If 0, the JackTimeBaseCallback isn't called * anymore, so we're the client (slave). Otherwise, if there is no timebase * master anymore, we become a regular client, or there is a timebase master, * so we are a slave. * * jack_set_timebase_callback(..., , jack_timebase_callback, ...); */ void jack_assistant::update_timebase_master (jack_transport_state_t s) { if (s != JackTransportStopped) { if (m_timebase_tracking > 0) { --m_timebase_tracking; if (m_timebase_tracking == 0) m_timebase = timebase::slave; } } if (m_timebase_tracking == 0 && ! (jack_pos().valid & JackPositionBBT)) { m_timebase_tracking = -1; m_timebase = timebase::none; /* i.e. a regular client */ } else if (m_timebase_tracking < 0 && (jack_pos().valid & JackPositionBBT)) { m_timebase_tracking = 0; m_timebase = timebase::slave; /* external master exists */ } } #endif // defined USE_TIMEBASE_MASTER /** * Shows a one-line summary of a JACK position structure. This function is * meant for experimenting and learning. * * The fields of this structure are as follows. Only the fields we care * about are shown. * \verbatim jack_nframes_t frame_rate: current frame rate (per second) jack_nframes_t frame: frame number, always present jack_position_bits_t valid: which other fields are valid \endverbatim * \verbatim JackPositionBBT: int32_t bar: current bar int32_t beat: current beat-within-bar int32_t tick: current tick-within-beat double bar_start_tick float beats_per_bar: time signature "numerator" float beat_type: time signature "denominator" double ticks_per_beat double beats_per_minute \endverbatim * \verbatim JackBBTFrameOffset: jack_nframes_t bbt_offset; frame offset for the BBT fields \endverbatim * * Only the most "important" and time-varying fields are shown. The format * output is brief and inscrutable unless you read this format example: * \verbatim nnnnn frame B:B:T N/D TPB BPM BBT ^ ^ ^ ^ ^ ^ ^ ^ | | | | | | | | | | | | | | | -------- bbt_offset (frame), even if invalid | | | | | | ------------ beats_per_minute | | | | | ---------------- ticks_per_beat (PPQN * 10?) | | | | ------------------- beat_type (denominator) | | | --------------------- beats_per_bar (numerator) | | ------------------------- bar : beat : tick | ------------------------------- frame (number) ------------------------------------- the "valid" bits \endverbatim * * The "valid" field is shown as bits in the same bit order as shown here, but * represented as a five-character string, "nnnnn", n = 0 or 1: * \verbatim JackVideoFrameOffset = 0x100 JackAudioVideoRatio = 0x080 JackBBTFrameOffset = 0x040 JackPositionTimecode = 0x020 JackPositionBBT = 0x010 \endverbatim * * We care most about nnnnn = "00101" in our experiments (the most common * output will be "00001"). And we don't worry about non-integer * measurements... we truncate them to integers. Change the output format if * you want to play with non-Western timings. * * \param pos * The JACK position structure to dump. */ void jack_assistant::show_position (const jack_position_t & pos) { char temp[80]; std::string nnnnn = "00000"; if (pos.valid & JackVideoFrameOffset) nnnnn[0] = '1'; if (pos.valid & JackAudioVideoRatio) nnnnn[1] = '1'; if (pos.valid & JackBBTFrameOffset) nnnnn[2] = '1'; if (pos.valid & JackPositionTimecode) nnnnn[3] = '1'; if (pos.valid & JackPositionBBT) nnnnn[4] = '1'; snprintf ( temp, sizeof temp, "%s %8ld %03d:%d:%04d %d/%d %5d %3d %d", nnnnn.c_str(), long(pos.frame), int(pos.bar), int(pos.beat), int(pos.tick), int(pos.beats_per_bar), int(pos.beat_type), int(pos.ticks_per_beat), int(pos.beats_per_minute), int(pos.bbt_offset) ); infoprint(temp); /* no output in release mode */ } /** * A member wrapper function for the new free function create_jack_client(). * This function is used for creating a JACK transport client and a JACK * sequencer (MIDI) client. * * \param name * Provides the name of the client, used in the call to * create_jack_client(). By default, this name is the macro SEQ66_PACKAGE * (i.e. "seq66"). * * \return * Returns a pointer to the JACK client if JACK has opened the client * connection successfully. Otherwise, a null pointer is returned. * The caller must handle a bad result. */ jack_client_t * jack_assistant::client_open (const std::string & name) { return create_jack_client(name, rc().jack_session()); } /** * This function gets the current JACK position. The Seq32 version also uses * its tempo map to adjust this, but Seq66 currently does not. * * The original equation is: * \verbatim frame * Tpb * Bpbar * ppqn * 4 tick = --------------------------------- 60 * FR * Tpb * Bw \endverbatim * * which simplifies to: * \verbatim frame * Bpbar * ppqn tick = ----------------------- 15 * FR * Bw \endverbatim * * \warning * Currently valgrind flags j->client() as uninitialized. * * \param arg * Provides the putative jack_assistant pointer, assumed to be not null. * * \return * Returns the calculated tick position if no errors occur. Otherwise, * returns 0. */ long jack_assistant::current_jack_position () const { if (not_nullptr(client())) { uint32_t rate = jack_frame_rate(); double ppqn = double(get_ppqn()); jack_nframes_t frame = ::jack_get_current_transport_frame(client()); #if defined SEQ66_USE_BPMINUTE_CALCULATION // portfix branch 2022-02-11 double bpminute = get_beats_per_minute(); double tick2 = frame * ppqn * bpminute / (rate * 60.0); #else double bpbar = beats_per_measure(); double bw = beat_width(); double tick2 = frame * bpbar * ppqn / (15.0 * rate * bw); #endif /* * double tpb = ppqn * c_jack_factor; // ticks/beat * double tick = frame * tpb * bpbar / (jack_frame_rate() * 60.0); * tick *= (ppqn / (tpb * bw / 4.0)); * printf("tick = %f; tick2 = %f\n", tick, tick2); */ return long(tick2); } else { error_message("Null JACK transport client"); return 0; } } /* * JACK callbacks. */ /** * The JACK timebase function defined here sets the JACK position structure. * The original version of the function worked properly with Hydrogen, but * not with Klick. The new code seems to work with both. More testing and * clarification is needed. This new code was "discovered" in the * "SooperLooper" project: http://essej.net/sooperlooper/ * * The first difference with the new code is that it handles the case where * the JACK position is moved (new_pos == true). If this is true, and the * JackPositionBBT bit is off in pos->valid, then the new BBT value is set. * * The second set of differences are in the "else" clause. In the new code, * it is very simple: calculate the new tick value, back it off by the number * of ticks in a beat, and perhaps go to the first beat of the next bar. * * In the old code (complex!), the simple BBT adjustment is always made. * This changes (perhaps) the beats_per_bar, beat_type, etc. We * need to make these settings use the actual global values for beats set for * Seq66. Then, if transitioning from JackTransportStarting to * JackTransportRolling (instead of checking new_pos!), the BBT values (bar, * beat, and tick) are finally adjusted. Here are the steps, with old and new * steps noted: * * -# Calculate the "delta" ticks based on the current frame, the * ticks_per_beat, the beats_per_minute, and the frame_rate. The old * code saves this in a local, the new code assigns it to pos->tick. * -# Old code: save this delta as a positive value. * -# Figure out the settings and modify bar, beat, tick, and * bar_start_tick. The old and new code seem to have the same intent, * but it seems like the new code is faster and also correct. * - Old code: Calculations are made by division and mod * operations. * - New code: Calculations are made by increments and decrements * in a while loop. * * Stazed: * * The call to jack_timebase_callback() to supply JACK with BBT, etc. would * occasionally fail when the pos information had zero or some garbage in the * pos.frame_rate variable. This would occur when there was a rapid change of * frame position by another client... i.e. qjackctl. From the JACK API: * * pos: address of the position structure for the next cycle; pos->frame * will be its frame number. If new_pos is FALSE, this structure contains * extended position information from the current cycle. If TRUE, it * contains whatever was set by the requester. The timebase_callback's * task is to update the extended information here." * * The "If TRUE" line seems to be the issue. It seems that qjackctl does not * always set pos.frame_rate so we get garbage and some strange BBT * calculations that display in qjackctl. So we need to set it here and just * use m_frame_rate for calculations instead of pos.frame_rate. * * \param state * Indicates the current state of JACK transport. * * \param nframes * The number of JACK frames in the current time period. * * \param pos * Provides the position structure to be filled in, the * address of the position structure for the next cycle; pos->frame will * be its frame number. If new_pos is FALSE, this structure contains * extended position information from the current cycle. If TRUE, it * contains whatever was set by the requester. The timebase_callback's * task is to update the extended information here. * * \param new_pos * TRUE (non-zero) for a newly requested pos, or for the first cycle * after the timebase_callback is defined. This is usually 0 in * Seq66 at present, and 1 if one, say, presses "rewind" in * qjackctl. * * \param arg * Provides the jack_assistant pointer, currently unchecked for nullity. */ void jack_timebase_callback ( jack_transport_state_t /*state*/, jack_nframes_t nframes, jack_position_t * pos, int new_pos, void * arg ) { jack_assistant * jack = static_cast(arg); pos->beats_per_minute = jack->get_beats_per_minute(); /* sooperlooper */ pos->beats_per_bar = jack->beats_per_measure(); pos->beat_type = jack->beat_width(); pos->ticks_per_beat = jack->get_ppqn() * double(c_jack_factor); long ticks_per_bar = long(pos->ticks_per_beat * pos->beats_per_bar); long ticks_per_minute = long(pos->beats_per_minute * pos->ticks_per_beat); double framerate = double(pos->frame_rate * 60.0); bool not_bbt = ! (pos->valid & JackPositionBBT); if (new_pos || not_bbt) { /* * This code is hit at Start and Stop actions from all clients. */ double minute = pos->frame / framerate; long abs_tick = long(minute * ticks_per_minute); long abs_beat = long(abs_tick / pos->ticks_per_beat); pos->bar = int(abs_beat / pos->beats_per_bar); pos->beat = int(abs_beat - (pos->bar * pos->beats_per_bar) + 1); pos->tick = int(abs_tick - (abs_beat * pos->ticks_per_beat)); pos->bar_start_tick = int(pos->bar * ticks_per_bar); pos->bar++; /* adjust start to bar 1 */ } else { /* * With this code, computing the BBT based on the previous period, * "klick -j -P" follows Seq66 when it is JACK Master! */ int delta_tick = int(nframes * ticks_per_minute / framerate); pos->tick += delta_tick; while (pos->tick >= pos->ticks_per_beat) { pos->tick -= int(pos->ticks_per_beat); if (++pos->beat > pos->beats_per_bar) { pos->beat = 1; ++pos->bar; pos->bar_start_tick += ticks_per_bar; } } if (jack->is_master()) pos->beats_per_minute = jack->parent().get_beats_per_minute(); } pos->bbt_offset = 0; pos->valid = (jack_position_bits_t) ( pos->valid | JackBBTFrameOffset | JackPositionBBT ); } /** * This callback is to shut down JACK by clearing the jack_assistant :: * m_jack_running flag. * * \param arg * Points to the jack_assistant in charge of JACK support for the performer * object. */ void jack_transport_shutdown (void * arg) { jack_assistant * jack = static_cast(arg); if (not_nullptr(jack)) { jack->set_jack_running(false); info_message("JACK transport shutdown"); } else { (void) error_message("null JACK transport pointer"); } } /** * Converts a JACK transport value to a human-readable string. * * \param state * Provides the transport state value. * * \return * Returns the state name. */ std::string jack_state_name (const jack_transport_state_t & state) { std::string result; switch (state) { case JackTransportStopped: result = "JackTransportStopped"; break; case JackTransportRolling: result = "JackTransportRolling"; break; case JackTransportStarting: result = "JackTransportStarting"; break; case JackTransportLooping: result = "JackTransportLooping"; break; default: errprint("JackTransportUnknown"); break; } return result; } #endif // SEQ66_JACK_SUPPORT /* * ------------------------------------------------------------------------- * JACK scratch-pad * ------------------------------------------------------------------------- */ jack_scratchpad::jack_scratchpad () : js_current_tick (0.0), js_total_tick (0.0), js_clock_tick (0.0), js_jack_stopped (false), js_dumping (false), js_init_clock (true), js_looping (false), js_playback_mode (false), js_ticks_converted (0.0), js_ticks_delta (0.0), js_ticks_converted_last (0.0), js_delta_tick_frac (0L) { // No other code } void jack_scratchpad::initialize ( midipulse currenttick, bool islooping, bool songmode ) { js_current_tick = double(currenttick); js_total_tick = 0.0; js_clock_tick = 0.0; js_jack_stopped = false; js_dumping = false; js_init_clock = true; js_looping = islooping; js_playback_mode = songmode; js_ticks_converted = 0.0; js_ticks_delta = 0.0; js_ticks_converted_last = 0.0; js_delta_tick_frac = 0L; } void jack_scratchpad::set_current_tick (midipulse curtick) { double ct = double(curtick); js_current_tick = js_total_tick = js_clock_tick = ct; } void jack_scratchpad::set_current_tick_ex (midipulse curtick) { double ct = double(curtick); js_current_tick = js_total_tick = js_clock_tick = js_ticks_converted_last = ct; } void jack_scratchpad::add_delta_tick (midipulse deltick) { double dt = double(deltick); js_current_tick += dt; js_total_tick += dt; js_clock_tick += dt; js_dumping = true; } } // namespace seq66 /* * jack_assistant.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/mastermidibase.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file mastermidibase.cpp * * This module declares/defines the base class for handling MIDI I/O via * the ALSA system. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-11-23 * \updates 2025-06-10 * \license GNU GPLv2 or above * * This file provides a base-class implementation for various master MIDI * buss support classes. There is a lot of common code between these MIDI * buss classes. */ #include "cfg/settings.hpp" /* seq66::rc() */ #include "midi/event.hpp" /* seq66::event */ #include "midi/mastermidibase.hpp" /* seq66::mastermidibase */ #include "play/sequence.hpp" /* seq66::sequence */ #include "os/timing.hpp" /* seq66::microsleep() */ namespace seq66 { /** * The mastermidibase default constructor fills the array with our busses. * * \param ppqn * Provides the PPQN value for this object. However, in most cases, the * default baseline PPQN should be specified. Then the caller of this * constructor should call mastermidibase::set_ppqn() to set up the * proper PPQN value. * * \param bpm * Provides the beats per minute value, which defaults to * c_beats_per_minute. */ mastermidibase::mastermidibase ( int ppqn, midibpm bpm ) : m_client_id (0), m_max_busses (c_busscount_max), m_bus_announce (nullptr), /* used only for ALSA announce bus */ m_inbus_array (), m_outbus_array (), m_master_clocks (), m_master_inputs (), m_queue (0), m_ppqn (choose_ppqn(ppqn)), m_beats_per_minute (bpm), /* beats per minute */ m_dumping_input (false), m_vector_sequence (), /* stazed feature */ m_record_by_buss (false), /* set based on configuration */ m_record_by_channel (false), /* ditto, but mutually exclusive */ m_seq (nullptr), m_mutex () { // Empty body now } /** * The virtual destructor deletes all of the output busses, clears out the * ALSA events, stops and frees the queue, and closes ALSA for this * application. * * Valgrind indicates we might have issues caused by the following functions: * * - snd_config_hook_load() * - snd_config_update_r() via snd_seq_open() * - _dl_init() and other GNU function * - init_gtkmm_internals() [version 2.4] */ mastermidibase::~mastermidibase () { if (not_nullptr(m_bus_announce)) { delete m_bus_announce; m_bus_announce = nullptr; } } /** * Initializes and activates the busses, in a partly API-dependent manner. * Currently re-implemented only in the rtmidi JACK API. */ bool mastermidibase::activate () { bool result = m_inbus_array.initialize(); if (result) result = m_outbus_array.initialize(); if (result) set_client_id(m_outbus_array.client_id(0)); return result; } /** * Starts all of the configured output busses up to m_num_out_buses. Calls * the implementation-specific API function for starting. * * \threadsafe */ void mastermidibase::start () { automutex locker(m_mutex); api_start(); m_outbus_array.start(); } /** * Gets the MIDI output busses running again. This function calls the * implementation-specific API function, and then calls * midibus::continue_from() for all of the MIDI output busses. * * \threadsafe * * \param tick * Provides the tick value to continue from. */ void mastermidibase::continue_from (midipulse tick) { automutex locker(m_mutex); api_continue_from(tick); m_outbus_array.continue_from(tick); } /** * Initializes the clock of each of the MIDI output busses. Calls the * implementation-specific API function, and then calls midibus::init_clock() * for each of the MIDI output busses. * * \threadsafe * * \param tick * Provides the tick value with which to initialize the buss clock. */ void mastermidibase::init_clock (midipulse tick) { automutex locker(m_mutex); api_init_clock(tick); m_outbus_array.init_clock(tick); } /** * Stops each of the MIDI output busses. Then calls the * implementation-specific API function to finalize the stoppage. (See the * ALSA implementation in the seq_alsamidi library, for example. It is the * original Seq66 implementation.) * * \threadsafe */ void mastermidibase::stop () { automutex locker(m_mutex); m_outbus_array.stop(); api_stop(); } /** * Generates the MIDI clock for each of the output busses. Also calls the * api_clock() function, which does nothing for the original ALSA * implementation and the PortMidi implementation. * * api_clock() doesn't do anything, so it is not called here. The clock() * call here does flush as well. * * \threadsafe * * \param tick * Provides the tick value with which to set the buss clock. */ void mastermidibase::emit_clock (midipulse tick) { automutex locker(m_mutex); m_outbus_array.clock(tick); } /** * Set the PPQN value (parts per quarter note). Then call the * implementation-specific API function to complete the PPQN setting. * * \threadsafe * * \param ppqn * The PPQN value to be set. */ void mastermidibase::set_ppqn (int ppqn) { automutex locker(m_mutex); m_ppqn = choose_ppqn(ppqn); /* m_ppqn = ppqn */ api_set_ppqn(ppqn); } /** * Set the BPM value (beats per minute). Then call the * implementation-specific API function to complete the BPM setting. * * \threadsafe * * \param bpm * Provides the beats-per-minute value to set. */ void mastermidibase::set_beats_per_minute (midibpm bpm) { automutex locker(m_mutex); m_beats_per_minute = bpm; api_set_beats_per_minute(bpm); } /** * Flushes our local queue events out The implementation-specific API * function is called. For example, ALSA provides a function to "drain" the * output. * * \threadsafe */ void mastermidibase::flush () { automutex locker(m_mutex); api_flush(); } /** * Stops all notes on all channels on all busses. Adapted from Oli Kester's * Kepler34 project. Whether the buss is active or not is ultimately checked * in the busarray::play() function. A bit wasteful, but do we really care? */ void mastermidibase::panic (int displaybuss) { automutex locker(m_mutex); for (int bus = 0; bus < c_busscount_max; ++bus) { if (bus == displaybuss) /* do not clear the Launchpad */ continue; for (int channel = 0; channel < c_midichannel_max; ++channel) { for (int note = 0; note < c_midibyte_data_max; ++note) { event e(0, EVENT_NOTE_OFF, channel, note, 0); m_outbus_array.play(bus, &e, channel); } } } api_flush(); } /** * Handle the sending of SYSEX events. There's currently no * implementation-specific API function for this call. * * \threadsafe * * \param ev * Provides the event pointer to be set. */ void mastermidibase::sysex (bussbyte bus, const event * ev) { automutex locker(m_mutex); m_outbus_array.sysex(bus, ev); } /** * Handle the playing of MIDI events on the MIDI buss given by the parameter, * as long as it is a legal buss number. There's currently no * implementation-specific API function here. * * \threadsafe * * \param bus * The actual system buss to start play on. The caller is expected to * make sure this buss is the correct buss. * * \param e24 * The seq66 event to play on the buss. For speed, we don't bother to * check the pointer. * * \param channel * The channel on which to play the event. */ void mastermidibase::play (bussbyte bus, event * e24, midibyte channel) { automutex locker(m_mutex); m_outbus_array.play(bus, e24, channel); } void mastermidibase::play_and_flush (bussbyte bus, event * e24, midibyte channel) { automutex locker(m_mutex); m_outbus_array.play(bus, e24, channel); api_flush(); } /** * Set the clock for the given (legal) buss number. The legality checks * are a little loose, however. * * There's currently no implementation-specific API function here. * * \threadsafe * * \param bus * The actual system buss to start play on. Checked before usage. * * \param clocktype * The type of clock to be set, either "off", "pos", or "mod", as noted * in the midibus_common module. */ bool mastermidibase::set_clock (bussbyte bus, e_clock clocktype) { automutex locker(m_mutex); bool result = m_outbus_array.set_clock(bus, clocktype); if (result) { flush(); result = save_clock(bus, clocktype); /* save into the vector */ } return result; } /** * Saves the given clock value in m_master_clocks[bus]. * * \param bus * Provides the desired buss to be set. This must be an actual system * buss, not a buss number from the output-port-map. * * \param clock * Provides the clocking value to set. * * \return * Returns true if the buss value is valid. */ bool mastermidibase::save_clock (bussbyte bus, e_clock clock) { bool result = m_master_clocks.set(bus, clock); if (! result) { int currentcount = m_master_clocks.count(); errprint("mmb::save_clock(): missing bus"); for (int i = currentcount; i <= bus; ++i) { e_clock value = e_clock::disabled; if (i == int(bus)) { value = clock; m_master_clocks.add(i, false, value, "Null clock"); } } } return result; } /** * Gets the clock setting for the given (legal) buss number. * * There's currently no implementation-specific API function here. * * \param bus * Provides an actual system buss number to read. Checked before usage. * * \return * If the buss number is legal, and the buss is active, then its clock * setting is returned. Otherwise, e_clock::disabled is returned. */ e_clock mastermidibase::get_clock (bussbyte bus) const { return m_outbus_array.get_clock(bus); } /** * This function copies the buss values from the input and output busarrays * (see the businfo module). They are then added to the masterbus's input and * output containers. * * Question: do we really want to add unavailable ports here? */ void mastermidibase::copy_io_busses () { int buses = m_inbus_array.count(); /* get_num_in_buses() */ m_master_inputs.clear(); /* inputslist container */ for (int bus = 0; bus < buses; ++bus) { bool available = ! m_inbus_array.is_port_unavailable(bus); bool inputflag = m_inbus_array.get_input(bus); std::string name = m_inbus_array.get_midi_bus_name(bus); std::string alias = m_inbus_array.get_midi_alias(bus); m_master_inputs.add(bus, available, inputflag, name, "", alias); } buses = m_outbus_array.count(); /* get_num_out_buses() */ m_master_clocks.clear(); /* clockslist container */ for (int bus = 0; bus < buses; ++bus) { bool available = ! m_outbus_array.is_port_unavailable(bus); e_clock clk = m_outbus_array.get_clock(bus); std::string name = m_outbus_array.get_midi_bus_name(bus); std::string alias = m_outbus_array.get_midi_alias(bus); m_master_clocks.add(bus, available, clk, name, "", alias); } } /** * Used in the performer class to pass the settings read from the "rc" file * to here. There is an converse function defined above. This function gets * the I/O port-map ports and copies them into the provided I/O lists. * * How it works: The portslist::match_system_to_map() function here takes the * master I/O lists, which come from the I/O section in the 'rc' file, and * sets the matching statuses of the I/O settings to the statuses of the * matching internal I/O port-maps. */ void mastermidibase::get_port_statuses (clockslist & outs, inputslist & ins) { get_out_port_statuses(outs); get_in_port_statuses(ins); } /** * Copies the port statuses between the master clockslist and the output * port-map. * * The direction is dependent on whether the port-map is active or not. * If active, then the port-map is copied to the master clockslist. * If inactive, then the master clockslist is copied to the port-map. * Finally, the (possibly update) master clockslist is copied to the \a * outs parameter. * * \param outs * The destination for the current master clockslist port statuses. */ void mastermidibase::get_out_port_statuses (clockslist & outs) { clockslist & opm = output_port_map(); if (opm.active()) { if (! opm.empty()) /* master clocks = opm */ opm.match_system_to_map(m_master_clocks); } else opm.match_map_to_system(m_master_clocks); /* opm = master clocks */ outs = m_master_clocks; /* system clocks information to store */ } void mastermidibase::get_in_port_statuses (inputslist & ins) { inputslist & ipm = input_port_map(); if (ipm.active()) { if (! ipm.empty()) /* master inputs = ipm */ ipm.match_system_to_map(m_master_inputs); } else ipm.match_map_to_system(m_master_inputs); /* ipm = master inputs */ ins = m_master_inputs; /* system inputs information to store */ } /** * Set the status of the given input buss, if a legal buss number. * Why is another buss-count constant, and a global one at that, being * used? And I thought there was only one input buss anyway! Well, * there is only one ALSA input buss, but more can be used with JACK, * apparently. * * There's currently no implementation-specific API function here. * * \threadsafe * * \param bus * Provides the actual system buss number. * * \param inputing * True if the input bus will be inputting MIDI data. * * \return * Returns true if the input buss array item could be set and then saved * into the status container. */ bool mastermidibase::set_input (bussbyte bus, bool inputing) { automutex locker(m_mutex); bool result = m_inbus_array.set_input(bus, inputing); if (result) { flush(); result = save_input(bus, inputing); /* save into the vector */ } return result; } /** * Saves the input status (as selected in the MIDI Input tab). Now, we were * checking this bus number against the size of the vector as gotten from the * performer object, which it got the from the "rc" file's [midi-input] * section. However, the "rc" file won't necessarily match what is on the * system now. So we might have to adjust. * * Do we also have to adjust the performer's vector? What about the name of * the buss? * * \param bus * Provides the actual system buss number. * * \param inputing * True if the input bus will be inputting MIDI data. * * \return * Returns true, always. */ bool mastermidibase::save_input (bussbyte bus, bool inputing) { bool result = m_master_inputs.set(bus, inputing); if (! result) { int currentcount = m_master_inputs.count(); errprint("mmb::save_input(): missing bus"); for (int i = currentcount; i <= bus; ++i) { bool value = false; if (i == int(bus)) { value = inputing; m_master_inputs.add(i, false, value, "Null input"); } } } return result; } /** * Get the input for the given (legal) buss number. * * There's currently no implementation-specific API function here. * * \param bus * Provides the actual system buss number. * * \return * Returns the value of the busarray::get_input(bus) call. */ bool mastermidibase::get_input (bussbyte bus) const { return m_inbus_array.get_input(bus); } /** * Get the system-buss status for the given (legal) buss number. * * \param bus * Provides the actual system buss number. * * \return * Returns the value of the busarray::get_input(bus) call. */ bool mastermidibase::is_input_system_port (bussbyte bus) const { return m_inbus_array.is_system_port(bus); } /** * There is an issue where this function is indirectly used in creating * the lists of I/O ports in the user-interface. There will, in general, * be more ports shown in the map than actually exist on the system. * For that purpose, we need to use the port maps, not the real port lists. */ bool mastermidibase::is_port_unavailable (bussbyte bus, midibase::io iotype) const { if (iotype == midibase::io::input) return m_inbus_array.is_port_unavailable(bus); else return m_outbus_array.is_port_unavailable(bus); } bool mastermidibase::is_port_locked (bussbyte bus, midibase::io iotype) const { if (iotype == midibase::io::input) return m_inbus_array.is_port_locked(bus); else return m_outbus_array.is_port_locked(bus); } /** * * This function adds the retrieval of client and port numbers that are not * needed in the portmidi implementation, but seem generally useful to * support in all implementations. * * We first try a port-mapper lookup, to get the short-name for the port. If * we can't get one, then we return the value obtained from the output * busarray, which has extra information over and above..... * * \param bus * Provides the actual system output buss number. Checked before usage. * * \return * Returns the buss name as a standard C++ string. */ /** * Get the MIDI input/output buss name for the given (legal) buss number. * This function is used for display purposes, and is also written to the * options ("rc") file. * * This function adds the retrieval of client and port numbers that are not * needed in the portmidi implementation, but seem generally useful to * support in all implementations. * * \param bus * Provides the I/O buss number. * * \param iotype * Indicates which I/O list is used for the lookup. * * \return * Returns the buss name as a standard C++ string. Also contains an * indication that the buss is disconnected or unconnected. If the buss * number is illegal, this string is empty. */ std::string mastermidibase::get_midi_bus_name (bussbyte bus, midibase::io iotype) const { portname p = rc().port_naming(); if (iotype == midibase::io::input) return m_master_inputs.get_display_name(bus, p); else return m_master_clocks.get_display_name(bus, p); } /** * Print some information about the available MIDI input and output busses. */ void mastermidibase::print () const { m_inbus_array.print(); m_outbus_array.print(); } /** * Initiate a poll() on the existing poll descriptors. This base-class * implementation could be made identical to portmidi's poll_for_midi() * function, maybe. But currently it is better just call the * implementation-specific API function. * * \warning Do we need to use a mutex lock? No! It causes a deadlock!!! * * \return Returns the result of the poll, or 0 if the API is not supported. */ int mastermidibase::poll_for_midi () { return api_poll_for_midi(); } /** * Provides a default implementation of api_poll_for_midi(). This * implementation adds a millisecond of sleep time unless more than two * events are pending. Some input devices may need to override this * function. * * For a quick threadsafe check, call is_more_input() instead. But see the * warning in the non-API poll_for_midi() function. * * \return * Returns the number of events found by the first successful poll of the * array of input busses (m_inbus_array). */ int mastermidibase::api_poll_for_midi () { int result = m_inbus_array.poll_for_midi(); if (result > 0) { if (result <= 2) (void) microsleep(std_sleep_us()); /* is this sensible? */ } else { (void) microsleep(std_sleep_us()); } return result; } /** * Test the sequencer to see if any more input is pending. Calls the * implementation-specific API function. * * Note that the ALSA implementation calls a single "input-pending" function, * while the PortMidi implementation loops through all of the input midibus * objects, calling the poll_for_midi() function of each. * * \threadsafe * * \return * Returns true if ALSA is supported, and the returned size is greater * than 0, or false otherwise. */ bool mastermidibase::is_more_input () { automutex locker(m_mutex); return m_inbus_array.poll_for_midi() > 0; } /** * Start the given MIDI port. This function is called by * api_get_midi_event() when the ALSA event SND_SEQ_EVENT_PORT_START is * received. Unlike port_exit(), the port_start() function does rely on * API-specific code, so we do need to create a virtual api_port_start() * function to implement the port-start event. * * \threadsafe * Quite a lot is done during the lock for the ALSA implimentation. * * \param client * Provides the client number, which is actually an ALSA concept. * * \param port * Provides the client port, which is actually an ALSA concept. */ void mastermidibase::port_start (int client, int port) { automutex locker(m_mutex); api_client_port_start(client, port); } /** * Turn off the given port for the given client. Both the input and output * busses for the given client are stopped: that is, set to inactive. * * This function is called by api_get_midi_event() when the ALSA event * SND_SEQ_EVENT_PORT_EXIT is received. Since port_exit() has no direct * API-specific code in it, we do not need to create a virtual * api_port_exit() function to implement the port-exit event. * * \threadsafe * * \param client * The client to be matched and acted on. This value is actually an ALSA * concept. * * \param port * The port to be acted on. Both parameter must be matched before the * buss is made inactive. This value is actually an ALSA concept. */ void mastermidibase::port_exit (int client, int port) { automutex locker(m_mutex); m_outbus_array.port_exit(client, port); m_inbus_array.port_exit(client, port); } /** * Set the input sequence object, and set the m_dumping_input value to * the given state. * * The portmidi version only sets m_seq and m_dumping_input, but it seems * like all the code below would apply to any mastermidibus. * * What if m_seq is already set? Do we want to undo that one and set the * new one (which requires being able to remove the red recording circle), * or just ignore the new click? The latter is easier. * * Usages: * * - portmidi mastermidibus::api_init() * - qseqeditframe64::toggle_midi_rec() and _thru() * - sequence::set_input_recording() and _thru() * - performer::set_recording() and _thru() * * \threadsafe * * \param state * Provides the dumping-input (recording) state to be set. This value, * as used in seqedit, can represent the state of the thru button or the * record button. * * \param seqp * Provides the sequence pointer to be logged as the mastermidibase's * current sequence. Can also be used to set a null pointer, to disable * the sequence setting. For issue #129, if we're asking again to make * a sequence the inputing sequence (i.e. to add quantization recording), * we accept the seqp if it matches m_seq. * * \return * Returns true if the sequence pointer is not null. */ bool mastermidibase::set_sequence_input (bool state, sequence * seqp) { automutex locker(m_mutex); bool result = not_nullptr(seqp); if (m_record_by_buss) { m_dumping_input = state; } else if (m_record_by_channel) { if (result) { if (state) /* add sequence if not already in */ { bool have_seq_already = false; for (size_t i = 0; i < m_vector_sequence.size(); ++i) { if (m_vector_sequence[i] == seqp) { have_seq_already = true; break; } } if (! have_seq_already) m_vector_sequence.push_back(seqp); } else /* remove sequence if already in */ { for (size_t i = 0; i < m_vector_sequence.size(); ++i) { if (m_vector_sequence[i] == seqp) { m_vector_sequence.erase(m_vector_sequence.begin() + i); break; } } } if (m_vector_sequence.size() != 0) m_dumping_input = true; } else if (! state) m_vector_sequence.clear(); /* don't record, and clear vector */ } else { if (state) { if (not_nullptr(m_seq)) { if (seqp != m_seq) result = false; /* We already got one! Is very nice */ } else { m_dumping_input = state; m_seq = seqp; } } else { m_dumping_input = false; m_seq = nullptr; } } return result; } /** * This function augments the recording functionality by looking for a * sequence that has a matching channel number, logging the event to that * sequence, and then immediately exiting. It should be called only if * m_record_by_channel is set. * * If we have more than one sequence recording, and the channel-match feature * [the sequence::channels_match() function] is disabled, then only the first * sequence will get the events. So now we add an additional call to the new * sequence::channel_match() function. * * \param ev * The event that was recorded, passed as a copy. (Do we really need a * copy?) */ bool mastermidibase::dump_midi_input (event ev) { bool result = false; size_t sz = m_vector_sequence.size(); for (size_t i = 0; i < sz; ++i) { if (is_nullptr(m_vector_sequence[i])) // error check { errprint("dump_midi_input(): bad sequence"); continue; } else if (m_vector_sequence[i]->stream_event(ev)) { /* * Did we find a match to the sequence channel? Then don't * bother with the remaining sequences. Otherwise, pass the * event to any other recording sequences. */ if (m_vector_sequence[i]->channel_match()) { result = true; break; } } } return result; } } // namespace seq66 /* * mastermidibase.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/midi_splitter.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_splitter.cpp * * This module declares/defines the class for splitting a MIDI track based on * channel number. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-11-24 * \updates 2025-06-20 * \license GNU GPLv2 or above * * We have recently updated this module to put Set Tempo events into the * first track (channel 0). */ #include "cfg/settings.hpp" /* seq66::usr().seqs_in_set() */ #include "midi/eventlist.hpp" /* seq66::eventlist */ #include "midi/midi_splitter.hpp" /* seq66::midi_splitter */ #include "play/performer.hpp" /* seq66::performer */ #include "play/sequence.hpp" /* seq66::sequence */ #include "util/palette.hpp" /* seq66::palette_to_int(), colors */ namespace seq66 { /** * Principal constructor. * */ midi_splitter::midi_splitter () : m_smf0_channels_count (0), m_smf0_channels (), /* array */ m_smf0_main_sequence (nullptr), m_smf0_seq_number (-1) { initialize(); } /** * Resets the SMF 0 support variables in preparation for parsing a new MIDI * file. */ void midi_splitter::initialize () { m_smf0_channels_count = 0; for (int i = 0; i < c_midichannel_max; ++i) m_smf0_channels[i] = false; } /** * Processes a channel number by raising its flag in the m_smf0_channels[] * array. If it is the first entry for that channel, m_smf0_channels_count * is incremented. We won't check the channel number, to save time, * until someday we segfault :-D * * \param channel * The MIDI channel number. The caller is responsible to make sure it * ranges from 0 to 15. */ void midi_splitter::increment (int channel) { if (! m_smf0_channels[channel]) /* channel not yet logged? */ { m_smf0_channels[channel] = true; ++m_smf0_channels_count; } } /** * Logs the main sequence (an SMF 0 track) for later usage in splitting the * track. * * /param seq * The main sequence to be logged. * * /param seqnum * The sequence number of the main sequence. * * /return * Returns true if the main sequence's address was logged, and false if * it was already logged. */ bool midi_splitter::log_main_sequence (sequence & s, int seqnum) { bool result; if (is_nullptr(m_smf0_main_sequence)) { s.sort_events(); /* really necessary? */ s.set_color(palette_to_int(PaletteColor::cyan)); m_smf0_main_sequence = &s; m_smf0_seq_number = seqnum; infoprint("SMF 0 main sequence logged"); result = true; } else { errprint("SMF 0 main sequence already logged"); result = false; } return result; } /** * This function splits an SMF 0 file, splitting all of the channels in the * sequence out into separate sequences, and adding each to the performer * object. Lastly, it adds the SMF 0 track as the last track; the user can * then examine it before removing it. Is this worth the effort? * * There is a little oddity, in that, if the SMF 0 track has events for only * one channel, this code will still create a new sequence, as well as the * main sequence. Not sure if this is worth extra code to just change the * channels on the main sequence and put it into the correct track for the * one channel it contains. In fact, we just want to keep it in pattern slot * number 16, to keep it out of the way. * * \param p * Provides a reference to the performer object into which sequences/tracks * are to be added. * * \param screenset * The screen-set offset to be used when loading a sequence (track) from * the file. * * \param ppqn * Use the provided PPQN. * * \return * Returns true if the parsing succeeded. Returns false if no SMF 0 main * sequence was logged. */ bool midi_splitter::split (performer & p, int screenset, int ppqn) { bool result = not_nullptr(m_smf0_main_sequence); if (result) { if (m_smf0_channels_count > 0) { int seqnum = screenset * usr().seqs_in_set(); for (int chan = 0; chan < c_midichannel_max; ++chan, ++seqnum) { if (m_smf0_channels[chan]) { /* * The master MIDI buss must be set before the split, * otherwise the null pointer causes a segfault. */ sequence * s = new sequence(ppqn); if (split_channel(p, *m_smf0_main_sequence, s, chan)) p.install_sequence(s, seqnum); else delete s; /* empty sequence, not even meta events */ } } m_smf0_main_sequence->set_midi_channel(null_channel()); m_smf0_main_sequence->set_name("Original"); p.install_sequence(m_smf0_main_sequence, seqnum); } } return result; } /** * This function splits the given sequence into a new sequence, for the given * channel found in the SMF 0 track. It is called for each possible channel, * resulting in multiple passes over the SMF 0 track. * * Note that the events that are read from the MIDI file have delta times. * Seq66 converts these delta times to cumulative times. We * need to preserve that here. Conversion back to delta times is needed only * when saving the sequences to a file. This is done in * midi_vector_base::fill(). * * We have to accumulate the delta times in order to be able to set the * length of the sequence in pulses. * * Luckily, we don't have to worry about copying triggers, since the imported * SMF 0 track won't have any Seq24/Sequencer24 triggers. * * It doesn't set the sequence number of the sequence; that is set when the * sequence is added to the performer object. * * \param main_seq * This parameter is the whole SMF 0 track that was read from the MIDI * file. It contains all of the channel data that needs to be split into * separate sequences. * * \param s * Provides the new sequence that needs to have its settings made, and * all of the selected channel events added to it. * * \param channel * Provides the MIDI channel number (re 0) that marks the channel data * the needs to be extracted and added to the new sequence. If this * channel is 0, then we need to add certain Meta events to this * sequence, as well. So far we support only Tempo Meta events. * * \return * Returns true if at least one event got added. If none were added, * the caller should delete the sequence object represented by parameter * \a s. */ bool midi_splitter::split_channel ( const performer & p, const sequence & main_seq, sequence * s, int channel ) { bool result = false; char tmp[32]; if (main_seq.name().empty()) { snprintf(tmp, sizeof tmp, "Track %d", channel+1); } else { snprintf ( tmp, sizeof tmp, "%d: %.13s", channel+1, main_seq.name().c_str() ); } /* * It is necessary to (redundantly, it turns out) set the master MIDI buss * first, before setting the other sequence parameters. */ s->set_master_midi_bus(p.master_bus()); s->set_name(std::string(tmp)); s->set_midi_channel(channel); s->set_midi_bus(main_seq.seq_midi_bus()); s->zero_markers(); midipulse length_in_ticks = 0; /* an accumulator of delta times */ const eventlist & evl = main_seq.events(); for (auto i = evl.cbegin(); i != evl.cend(); ++i) { const event & er = eventlist::cdref(i); if (er.is_ex_data()) { if (channel == 0 || er.is_sysex()) { length_in_ticks = er.timestamp(); if (s->append_event(er)) /* adds event, no sorting */ result = true; /* the event got added */ } } else if (er.match_channel(channel)) { length_in_ticks = er.timestamp(); if (s->append_event(er)) /* adds event, no sorting */ result = true; /* the event got added */ } } /* * No triggers to add. Whew! And setting the length is now a no-brainer, * since the tick value is that of the last logged event in the sequence. * Also, sort all the events after they have been appended. */ s->set_length(length_in_ticks); s->sort_events(); return result; } } // namespace seq66 /* * midi_splitter.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/midi_vector.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_vector.cpp * * This module declares/defines the concrete class for a container of MIDI * data. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-10-11 * \updates 2021-10-04 * \license GNU GPLv2 or above * */ #include "midi/midi_vector.hpp" /* seq66::midi_vector_base class */ #include "play/sequence.hpp" /* seq66::sequence class */ namespace seq66 { /** * This constructor fills in the members of this class. * * \param seq * Provides a reference to the sequence/track for which this container * holds MIDI data. */ midi_vector::midi_vector (sequence & seq) : midi_vector_base (seq), m_char_vector () { // Empty body } /** * Fills this list with an exportable track. Following stazed, we're * consolidate the tracks at the beginning of the song, replacing the actual * track number with a counter that is incremented only if the track was * exportable. Note that this loop is kind of an elaboration of what goes on * in the midi_vector_base :: fill() function for normal Seq66 file writing. * * Exportability ensures that the sequence pointer is valid. This function * adds all triggered events. * * For each trigger in the sequence, add events to the list below; fill * one-by-one in order, creating a single long sequence. Then set a single * trigger for the big sequence: start at zero, end at last trigger end with * snap. We're going to reference (not copy) the triggers now, since the * write_song() function is now locked. * * The we adjust the sequence length to snap to the nearest measure past the * end. We fill the MIDI container with trigger "events", and then the * container's bytes are written. * * tick_end() isn't quite a trigger length, off by 1. Subtracting * tick_start() can really screw it up. */ bool midi_vector::song_fill_track (int track, bool standalone) { bool result = seq().is_exportable(); if (result) { clear(); if (standalone) { fill_seq_number(track); fill_seq_name(seq().name()); #if defined SEQ66_USE_FILL_TIME_SIG_AND_TEMPO // undefined if (track == rc().tempo_track_number) { seq().events().scan_meta_events(); fill_time_sig_and_tempo ( p, seq().events().has_time_signature(), seq().events().has_tempo() ); } #endif } midipulse last_ts = 0; const auto & trigs = seq().get_triggers(); for (auto & t : trigs) last_ts = song_fill_seq_event(t, last_ts); const trigger & ender = trigs.back(); midipulse seqend = ender.tick_end(); midipulse measticks = seq().measures_to_ticks(); if (measticks > 0) { midipulse remainder = seqend % measticks; if (remainder != (measticks - 1)) seqend += measticks - remainder - 1; } song_fill_seq_trigger(ender, seqend, last_ts); } return result; } } // namespace seq66 /* * midi_vector.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/midi_vector_base.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_vector_base.cpp * * This module declares a class for holding and managing MIDI data. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-10-10 (as midi_container.cpp) * \updates 2025-10-16 * \license GNU GPLv2 or above * * This class is important when writing the MIDI and sequencer data out to a * MIDI file. The data handled here are specific to a single * sequence/pattern/track. * * Provides tags used by the midifile class to control the reading and * writing of the extra "proprietary" information stored in a Seq24 MIDI * file. Some of the information is stored with each track (and in the * midi_vector_base-derived classes), and some is stored in the proprietary * header. * * Track (sequencer-specific) data: * \verbatim c_midibus c_midichannel c_timesig c_triggers (deprecated) c_triggers_ex (deprecated) c_trig_transpose (c_triggers_ex plus!) c_musickey (can be in the global footer, as well) c_musicscale (ditto) c_musicchord (stored only with a track) c_backsequence (ditto) c_transpose c_seq_color (performance colors for a sequence) c_seq_edit_mode (unused by Seq66) c_seq_loopcount c_midiinbus (new) \endverbatim * * Note that c_seq_color in Seq66 is stored per sequence, and only if * not PaletteColor::NONE. In Kepler34, all 1024 sequence colors are stored * in a "proprietrary", whole-song section, whether used or not. There are * also numeric differences in many of these "c_feature" constants. * * Footer ("proprietary", whole-song) data: * \verbatim c_midictrl c_midiclocks c_notes c_bpmtag (beats per minute) c_mutegroups c_perf_bp_mes (perfedit's beats-per-measure setting) c_perf_bw (perfedit's beat-width setting) c_tempo_map (seq32's tempo map) c_midiinbus c_tempo_track (holds the song's particular tempo track) \endverbatim * * Also see the PDF file in the following project for more information about * the "proprietary" data: * * https://github.com/ahlstromcj/seq66-doc.git * * Note that the track data is read from the MIDI file, but not written * directly to the MIDI file. Instead, it is stored in the MIDI container as * sequences are edited to used these "sequencer-specific" features. * Also note that c_triggers has been replaced by c_triggers_ex as the code * that marks the triggers stored with a sequence. And c_trig_transpose * extends it even further with a byte-value for transposing a trigger. * * As an extension, we can also grab the key, scale, and background sequence * value selected in a sequence and write these values as track data, where * they can be read in and applied to a specific sequence, when the seqedit * object is created. These values would not be stored in the legacy format. * * Something like this could be done in the "user" configuration file, but * then the key and scale would apply to all songs. We don't want that. * * We could also add snap and note-length to the per-song defaults, but * the "user" configuration file seems like a better place to store these * preferences. * * \note * - The value c_transpose value is from Stazed's seq32 project. * The code to support this option is turned on permanently. * There are additional values from Stazed/seq32, not yet used. * - The values below are compatible with Seq32, but they are not * compatible with Kepler34. It uses 0x24240011 and 0x24240012 for * difference purposes. See the asterisks below. * - Note the new "gap" values. We just noticed this gap, which has * existed between 0x24240009 and 0x24240010 since Seq24! */ #include "cfg/scales.hpp" /* seq66::scales enum class */ #include "cfg/settings.hpp" /* seq66::rc() */ #include "midi/midi_vector_base.hpp" /* seq66::midi_vector_base ABC */ #include "play/performer.hpp" /* seq66::performer master class */ #include "play/seq.hpp" /* seq66::seq */ namespace seq66 { /** * Fills in the few members of this class. * * \param seq * Provides a reference to the sequence/track for which this container * holds MIDI data. */ midi_vector_base::midi_vector_base (sequence & seq) : m_sequence (seq), /* seq() accessor */ m_position_for_get (0) { // Empty body } void midi_vector_base::put_meta ( midibyte metavalue, int datalen, midipulse deltatime ) { add_varinum(midilong(deltatime)); put(EVENT_MIDI_META); /* 0xFF meta marker */ put(metavalue); /* meta marker */ add_varinum(midilong(datalen)); } void midi_vector_base::put_seqspec (midilong spec, int datalen) { datalen += 4; /* size of 0x242400nn */ put_meta(EVENT_META_SEQSPEC, datalen, 0); add_long(spec); /* e.g. c_midibus */ } /** * This function masks off the lower 8 bits of the long parameter, then * shifts it right 7, and, if there are still set bits, it encodes it into * the buffer in reverse order. This function "replaces" * sequence::add_list_var(). It is almost identical to midifile :: * write_varinum(). * * \param v * The data value to be added to the current event in the MIDI container. */ void midi_vector_base::add_varinum (midilong v) { midilong buffer = v & 0x7F; /* mask off a no-sign byte */ while (v >>= 7) /* shift right 7 bits, test */ { buffer <<= 8; /* move LSB bits to MSB */ buffer |= ((v & 0x7F) | 0x80); /* add LSB and set bit 7 */ } for (;;) { put(midibyte(buffer) & 0xFF); /* add the LSB */ if (buffer & 0x80) /* if bit 7 set */ buffer >>= 8; /* get next MSB */ else break; } } /** * Adds a long value (a MIDI pulse/tick value) to the container. * * What is the difference between this function and add_list_var()? * This function "replaces" sequence::add_long_list(). * This was a global internal function called addLongList(). * Let's at least make it a private member now, and hew to the naming * conventions of this class. * * \param x * Provides the timestamp (pulse value) to be added to the container. */ void midi_vector_base::add_long (midilong x) { put((x & 0xFF000000) >> 24); put((x & 0x00FF0000) >> 16); put((x & 0x0000FF00) >> 8); put((x & 0x000000FF)); } /** * Adds a short value (two bytes) to the container. * * \param x * Provides the timestamp (pulse value) to be added to the container. */ void midi_vector_base::add_short (midishort x) { put((x & 0x0000FF00) >> 8); put((x & 0x000000FF)); } /** * Adds an event to the container. It handles regular MIDI events separately * from "extended" (our term) MIDI events (SysEx and Meta events). * * For normal MIDI events, if the sequence's MIDI channel is null_channel() * == 0x80, then it is the copy of an SMF 0 sequence that the midi_splitter * created. We want to be able to save it along with the other tracks, but * won't be able to read it back if all the channels are bad. So we just use * the channel from the event. * * SysEx and Meta events are detected and passed to the add_ex_event() * function for proper dumping. * * Issue #109: * * Had commented out the application of the channel to the event. * Has every earmark of a classic Ahlstrom brainfart. * * \param e * Provides the event to be added to the container. * * \param deltatime * Provides the time-location of the event. */ void midi_vector_base::add_event (const event & e, midipulse deltatime) { if (e.is_ex_data()) { add_ex_event(e, deltatime); } else { midibyte d0 = e.data(0); midibyte d1 = e.data(1); midibyte channel = seq().seq_midi_channel(); midibyte st = e.get_status(); add_varinum(midilong(deltatime)); /* encode delta_time */ if (seq().free_channel() || is_null_channel(channel)) put(st | e.channel()); /* channel from event */ else put(st | channel); /* the sequence channel */ if (e.has_channel()) { switch (event::mask_status(st)) /* 0xF0 */ { case EVENT_NOTE_OFF: /* 0x80 */ case EVENT_NOTE_ON: /* 0x90 */ case EVENT_AFTERTOUCH: /* 0xA0 */ case EVENT_CONTROL_CHANGE: /* 0xB0 */ case EVENT_PITCH_WHEEL: /* 0xE0 */ put(d0); put(d1); break; case EVENT_PROGRAM_CHANGE: /* 0xC0 */ case EVENT_CHANNEL_PRESSURE: /* 0xD0 */ put(d0); break; default: break; } } } } /** * Adds the bytes of a SysEx or Meta MIDI event. This function was using * put() to write a single count byte, but add_varinum() must be used for * this value. * * - Meta: delta FF type len bytes * - SysEx: * - Single: delta F0 len bytes F7 * - Continuation: delta F0 len bytes ; delta F7 len bytes ; ... * delta F7 len bytes F7 * - Escape sequence: TODO * * The SysEx continuation messages are stored as separate events. The only * difference in writing them is the status byte. * * \param e * Provides the MIDI event to add. The caller must ensure that this is * either SysEx or Meta event, using the event::is_ex_data() function. * * \param deltatime * Provides the time of the event, which is encoded into the event. */ void midi_vector_base::add_ex_event (const event & e, midipulse deltatime) { int count = e.sysex_size(); /* applies for meta, too */ add_varinum(midilong(deltatime)); /* encode delta_time */ put(e.get_status()); /* indicates SysEx/Meta */ if (e.is_sysex()) { --count; /* ignore 1st byte (F0/F7) */ add_varinum(midilong(count)); for (int i = 0; i < count; ++i) put(e.get_sysex(i + 1)); } else if (e.is_meta()) { put(e.channel()); /* indicates meta type */ add_varinum(midilong(count)); /* using put() was wrong! */ for (int i = 0; i < count; ++i) put(e.get_sysex(i)); } } /** * Fills in the sequence number. Writes 0xFF 0x00 0x02 ss ss, where ss ss is * the variable-length value for the sequence number. This function is used * in the new midifile::write_song() function, which should be ready to go by * the time you're reading this. Compare this function to the beginning of * midi_vector_base::fill(). * * \warning * This is an optional event, which must occur only at the start of a * track, before any non-zero delta-time. For Format 2 MIDI files, this * is used to identify each track. If omitted, the sequences are numbered * sequentially in the order the tracks appear. For Format 1 files, this * event should occur on the first track only. So, are we writing a * hybrid format? * * \param seq * The sequence/track number to write. */ void midi_vector_base::fill_seq_number (int seq) { put_meta(EVENT_META_SEQ_NUMBER, 2); /* 0x00, 2 bytes long */ add_short(midishort(seq)); } /** * Fills in the sequence name. Writes 0xFF 0x03, and then the track name. * This function is used in the new midifile::write_song() function, which * should be ready to go by the time you're reading this. * * Compare this function to the beginning of midi_vector_base::fill(). * * \param name * The sequence/track name to set. We could get this item from * seq(), but the parameter allows the flexibility to change the * name. */ void midi_vector_base::fill_seq_name (const std::string & name) { int len = int(name.length()); put_meta(EVENT_META_TRACK_NAME, len); /* 0x03, len bytes long */ for (int i = 0; i < len; ++i) put(midibyte(name[i])); } #if defined SEQ66_USE_FILL_META_TEXT // undefined /** * ca 2023-04-27 * A general function for writing the textual MIDI events. But see * add_ex_event(). */ void midi_vector_base::fill_meta_text ( midibyte metacode, const std::string & text ) { int len = int(text.length()); put_meta(metacode, len); /* 0x01-0x07, len bytes */ for (int i = 0; i < len; ++i) put(midibyte(text[i])); } #endif /* * Last, but certainly not least, write the end-of-track meta-event. * * \param deltatime * The MIDI delta time to write before the meta-event itself. */ void midi_vector_base::fill_meta_track_end (midipulse deltatime) { put_meta(EVENT_META_END_OF_TRACK, 0, deltatime); /* no data to add */ } #if defined SEQ66_USE_FILL_TIME_SIG_AND_TEMPO // undefined /** * Combines the two functions fill_tempo() and fill_time_signature(). This * function is called only for track 0. And it only puts out the events if * the track does not contain tempo or time-signature events; in that case, * it needs to grab the global values from the performance object and put * them out. * * \param p * The performance object that holds the time signature and tempo values. * * \param has_time_sig * Indicates whether or not the current track (usually track 0) has a * time signature event. If so, then we do not need to fill in the * global time signature value. * * \param has_tempo * Indicates whether or not the current track (track 0) has a tempo * event. If so, then we do not need to fill in the global tempo value. */ void midi_vector_base::fill_time_sig_and_tempo ( const performer & p, bool has_time_sig, bool has_tempo ) { if (! has_tempo) fill_tempo(p); if (! has_time_sig) fill_time_sig(p); } /** * Fill in the time-signature information. This function is used only for * the first track, and only if no such event is in the track data. * * We now make sure that the proper values are part of the performer object * for usage in this particular track. For export, we cannot guarantee that * the first (0th) track/sequence is exportable. * * \param p * Provides the performance object from which we get some global MIDI * parameters. */ void midi_vector_base::fill_time_sig (const performer & p) { int beatwidth = p.get_beat_width(); int bpb = p.get_beats_per_bar();; int cpm = p.clocks_per_metronome(); int get32pq = p.get_32nds_per_quarter(); int bw = log2_of_power_of_2(beatwidth); put_meta(EVENT_META_TIME_SIGNATURE, 4); /* 0x58 marker, 4 bytes */ put(bpb); put(bw); put(cpm); put(get32pq); } /** * Fill in the tempo information. This function is used only for the first * track, and only if no such event is int the track data. * * We now make sure that the proper values are part of the performer object for * usage in this particular track. For export, we cannot guarantee that the * first (0th) track/sequence is exportable. * * \change ca 2017-08-15 * Fixed issue #103, was writing tempo bytes in the wrong order here. * Accidentally committed along with fruity changes, sigh, so go back a * couple of commits to see the changes. * * \param p * Provides the performance object from which we get some global MIDI * parameters. */ void midi_vector_base::fill_tempo (const performer & p) { midibyte t[4]; /* hold tempo bytes */ int usperqn = p.us_per_quarter_note(); tempo_us_to_bytes(t, usperqn); put_meta(EVENT_META_SET_TEMPO, 3); /* 0x51, 3 bytes long */ put(t[0]); /* NOT 2, 1, 0! */ put(t[1]); put(t[2]); } #endif // SEQ66_USE_FILL_TIME_SIG_AND_TEMPO /** * Fills in the Seq66-specific information for the current sequence: * The MIDI buss number, the time-signature, and the MIDI channel. Then, if * we're not using the legacy output format, we add the "events" for the * musical key, musical scale, and the background sequence for the current * sequence. Finally, if tranpose support has been compiled into the program, * we add that information as well. */ void midi_vector_base::fill_proprietary () { bussbyte b = seq().seq_midi_bus(); /* MIDI out buss number */ if (is_good_buss(b)) { put_seqspec(c_midibus, 1); put(b); /* MIDI out buss number */ } b = seq().seq_midi_in_bus(); /* MIDI out buss number */ if (is_good_buss(b)) { put_seqspec(c_midiinbus, 1); put(b); /* MIDI in buss number */ } put_seqspec(c_timesig, 2); /* Time Sig bytes b/w */ put(seq().timesig_beats_per_measure()); put(seq().timesig_beat_width()); put_seqspec(c_midichannel, 1); /* MIDI output channel */ put(seq().seq_midi_channel()); /* 0 to 15 or 0x80 */ if (! usr().global_seq_feature()) { /** * New feature: save more sequence-specific values, if not saved * globally. We use a single byte for the key and scale, and a long * for the background sequence. We save these values only if they are * different from the defaults; in most cases they will have been left * alone by the user. We save per-sequence values here only if the * global-background-sequence feature is not in force. */ if (seq().musical_key() != c_key_of_C) { put_seqspec(c_musickey, 1); put(seq().musical_key()); } if (seq().musical_scale() != c_scales_off) { put_seqspec(c_musicscale, 1); put(seq().musical_scale()); } if (seq::valid(seq().background_sequence())) { put_seqspec(c_backsequence, 4); add_long(seq().background_sequence()); } } if (seq().musical_chord() != 0) { put_seqspec(c_musicchord, 1); put(seq().musical_chord()); } /** * The musical chord is not really suitable for a global setting, * so it is always saved with the track. */ /** * Generally only drum patterns will not be transposable. */ bool transpose = seq().transposable(); put_seqspec(c_transpose, 1); /* byte */ put(midibyte(transpose)); if (seq().color() != c_seq_color_none) { put_seqspec(c_seq_color, 1); /* byte */ put(midibyte(seq().color())); } if (seq().loop_count_max() > 0) { put_seqspec(c_seq_loopcount, 2); /* short */ add_short(midishort(seq().loop_count_max())); } } /** * Fills in sequence events based on the trigger and events in the sequence * associated with this midi_vector_base. * * This calculation needs investigation. The number of times the pattern is * played is given by how many pattern lengths fit in the trigger length. * But the commented calculation adds to the value of 1 already assigned. * And what about triggers that are somehow of 0 size? Let's try a different * calculation, currently the same. * * int times_played = 1; * times_played += (trig.tick_end() - trig.tick_start()) / len; * * \param trig * The current trigger to be processed. * * \param prev_timestamp * The time-stamp of the previous event. * * \return * The next time-stamp value is returned. */ midipulse midi_vector_base::song_fill_seq_event ( const trigger & trig, midipulse prev_timestamp ) { midipulse len = seq().get_length(); midipulse trig_offset = trig.offset() % len; midipulse start_offset = trig.tick_start() % len; midipulse time_offset = trig.tick_start() + trig_offset - start_offset; int times_played = 1 + (trig.length() - 1) / len; if (trig_offset > start_offset) /* offset len too far */ time_offset -= len; int note_is_used[c_notes_count]; for (int i = 0; i < c_notes_count; ++i) note_is_used[i] = 0; /* initialize to off */ for (int p = 0; p <= times_played; ++p, time_offset += len) { midipulse delta_time = 0; for (auto e : seq().events()) /* use a copy of event */ { midipulse timestamp = e.timestamp() + time_offset; if (timestamp >= trig.tick_start()) /* at/after trigger */ { /* * Save the note; eliminate Note Off if Note On is unused. */ if (e.is_note()) /* includes aftertouch */ { midibyte note = e.get_note(); if (trig.transposed()) e.transpose_note(trig.transpose()); if (e.is_note_on()) { if (timestamp <= trig.tick_end()) ++note_is_used[note]; /* count the note */ else continue; /* skip */ } else if (e.is_note_off()) { if (note_is_used[note] > 0) { /* * We have a Note On, and if past the end of trigger, * use the trigger end. */ --note_is_used[note]; /* turn off the note */ if (timestamp > trig.tick_end()) timestamp = trig.tick_end(); } else continue; /* if no Note On, skip */ } } } else continue; /* before trigger, skip */ /* * If the event is past the trigger end, for non-notes, skip. */ if (timestamp >= trig.tick_end()) /* event past trigger */ { if (! e.is_note()) /* (also aftertouch) */ continue; /* drop the event */ } delta_time = timestamp - prev_timestamp; prev_timestamp = timestamp; add_event(e, delta_time); /* does it sort??? */ } } return prev_timestamp; } /** * Fills in one trigger for the sequence, for a song-performance export. * There will be only one trigger, covering the beginning to the end of the * fully unlooped track. Therefore, we use the older c_triggers_ex SeqSpec, * which saves a byte, while indicating the sequence has already been * transposed. * * Using all the trigger values seems to be the same as these values, but * we're basically zeroing the start and offset values to make "one big * trigger" for the whole pattern. * * add_long(trig.tick_start()); * add_long(trig.tick_end()); * add_long(trig.offset()); * * \param trig * The current trigger to be processed. * * \param length * Provides the total length of the sequence. * * \param prev_timestamp * The time-stamp of the previous event, which is actually the first * event. */ void midi_vector_base::song_fill_seq_trigger ( const trigger & trig, midipulse length, midipulse prev_timestamp ) { put_seqspec(c_triggers_ex, trigger::datasize(c_triggers_ex)); add_long(0); /* start tick (see banner) */ add_long(trig.tick_end()); /* the ending tick */ add_long(0); /* offset is done in event */ fill_proprietary(); fill_meta_track_end(length - prev_timestamp); /* delta time */ } /** * This function fills the given track (sequence) with MIDI data from the * current sequence, preparatory to writing it to a file. Note that some of * the events might not come out in the same order they were stored in (we * see that with program-change events). This function replaces * sequence::fill_list(). * * Now, for sequence 0, an alternate format for writing the sequencer number * chunk is "FF 00 00". But that format can only occur in the first track, * and the rest of the tracks then don't need a sequence number, since it is * assumed to increment. This application doesn't use that shortcut. * * We have noticed differences in saving files in sets=4x8 versus sets=8x8, * and pre-sorting the event list gets rid of some of the differences, except * for the last, multi-line SeqSpec. Some event-reordering still seems to * occur, though. * * Stazed: * * The "stazed" (seq32) code implements a function like this one * using a function sequence::fill_proprietary_list() that we * don't need for our implementation... it is part of our * midi_vector_base::fill() function. * * Triggers: * * Triggers are added by first calling add_varinum(0), which is needed * because why? * * Then 0xFF 0x7F is written, followed by the length value, which is the * number of triggers at 3 long integers per trigger, plus the 4-byte * code for triggers, c_triggers_ex = 0x24240008. * * However, we're now extending triggers (c_trig_transpose = 0x24240020) * to include a transposition byte which allows up to 5 octaves of * tranposition either way, as a way to re-use patterns. Inspired by * Kraftwerk's "Europe Endless" background sequence, with patterns being * shifted up and down in pitch. * * Meta and SysEx Events: * * These events can now be detected and added to the list of bytes to * dump. However, historically Seq24 has forced Time Signature and Set * Tempo events to be written to the container, and has ignored these * events (after the first occurrence). So we need to figure out what to * do here yet; we need to distinguish between forcing these events and * them being part of the edit. * * \threadunsafe * The sequence object bound to this container needs to provide the * locking mechanism when calling this function. * * \param track * Provides the track number, re 0. This number is masked into the track * information. * * \param p * The performance object that will hold some of the parameters needed * when filling the MIDI container. Not used!!! * * \param doseqspec * If true (the default), writes out the SeqSpec information. If false, * we want to write out a regular MIDI track without this information; it * writes a smaller file. */ void midi_vector_base::fill (int track, const performer & /*p*/, bool doseqspec) { eventlist evl = seq().events(); /* used below */ evl.sort(); if (doseqspec) fill_seq_number(track); fill_seq_name(seq().name()); #if defined SEQ66_USE_FILL_TIME_SIG_AND_TEMPO /** * To allow other sequencers to read Seq24/Seq66 files, we should * provide the Time Signature and Tempo meta events, in the 0th (first) * track (sequence). These events must precede any "real" MIDI events. * We also need to skip this if tempo track support is in force. */ if (track == 0) { fill_time_sig_and_tempo(p, evl.has_time_signature(), evl.has_tempo()); } #endif midipulse timestamp = 0; midipulse deltatime = 0; midipulse prevtimestamp = 0; for (const auto & e : evl) { timestamp = e.timestamp(); deltatime = timestamp - prevtimestamp; if (deltatime < 0) /* midipulse == long */ { errprint("midi_vector_base::fill(): Bad delta-time, aborting"); break; } prevtimestamp = timestamp; add_event(e, deltatime); /* does it sort??? */ } if (doseqspec) { /* * Here, we add SeqSpec entries (specific to Seq66) for triggers * (c_triggers_ex or c_trig_transpose), the MIDI buss (c_midibus), * time signature (c_timesig), and MIDI channel (c_midichannel). * Should we restrict this to only track 0? No, Seq66 saves these * events with each sequence. Also, the datasize needs to be * calculated differently for c_trig_transpose versus c_triggers_ex. */ const triggers::container & triggerlist = seq().triggerlist(); bool transtriggers = ! rc().save_old_triggers(); if (transtriggers) transtriggers = seq().any_trigger_transposed(); if (transtriggers) { int datasize = seq().triggers_datasize(c_trig_transpose); put_seqspec(c_trig_transpose, datasize); } else { int datasize = seq().triggers_datasize(c_triggers_ex); put_seqspec(c_triggers_ex, datasize); } for (auto & t : triggerlist) { add_long(t.tick_start()); add_long(t.tick_end()); add_long(t.offset()); if (transtriggers) add_byte(t.transpose_byte()); } fill_proprietary(); } /* * Last, but certainly not least, write the end-of-track meta-event. * If the nominal length of the sequence is less than the last timestamp, * we set the delta-time to 0. Better would be to make sure this can * never happen. */ midipulse len = seq().get_length(); if (len < prevtimestamp) deltatime = 0; else deltatime = len - prevtimestamp; /* meta track end */ fill_meta_track_end(deltatime); } } // namespace seq66 /* * midi_vector_base.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/midibase.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midibase.cpp * * This module declares/defines the base class for handling MIDI I/O via * various MIDI APIs. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-11-25 * \updates 2025-08-18 * \license GNU GPLv2 or above * * This file provides a cross-platform implementation of MIDI support. * * Elements of a MIDI buss: * * - Client. This is the application: seq66, seq66portmidi, or * seq66rtmidi. * - Buss. This is the main MIDI item, such as MIDI Through (14) * or TiMidity (128). The buss numbers are provided by the system. * Currently, the buss name is empty. * - Port. This is one of the items provided by the buss, and the * number usually starts at 0. The port numbers are provided by the * system. Currently, the port name includes the buss name as * provided by the system, as a single unit. * - Index. This number is the order of the input or output MIDI * device as enumerated by the system lookup code, and always starts * at 0. */ #include "cfg/settings.hpp" /* seq66::rc() */ #include "midi/event.hpp" /* seq66::event (MIDI event) */ #include "midi/midibase.hpp" /* seq66::midibase for ALSA */ namespace seq66 { /** * Initialize this static member. */ int midibase::m_clock_mod = 16 * 4; /** * Creates a normal MIDI port, which will correspond to an existing system * MIDI port, such as one provided by Timidity or a running JACK application, * or a virtual port, which has a name made up by the application. Provides * a constructor with client number, port number, name of client, name of * port. * * This constructor is the one that seems to be the one that is used for * the MIDI input and output busses, when the [manual-ports] option is * not in force. Also used for the announce buss, and in the * mastermidibase::port_start() function. * * \param appname * Provides the the name of the application. The derived class will * determine this name. * * \param busname * Provides the ALSA client name or the MIDI subsystem name (e.g. * "TiMidity"). If empty, a name will be assembled by the derived class * at port-setup time. * * \param portname * Provides the port name. This item defaults to empty, which means the * port name should be obtained via the API, or be assembled by the * derived class at port-setup time. * * \param index * Provides the ordinal of this buss/port, mostly for display purposes. * * \param bus_id * Provides the ID code for this bus. It is an index into the midibus * definitions array, and is also used in the constructed human-readable * buss name. Defaults to null_buss(). * * - ALSA (seq66). This is the ALSA buss number, ranging from 1 on * upwards. If null_buss(), the derived class will get the buss * ID at port-setup time. * - PortMidi. This number is not yet used in PortMidi. Perhaps * this should be used instead of the queue parameter. * - RtMidi. Like ALSA, we will use this as a buss number. * * \param port_id * Indicates the port ID. Defaults to SEQ66_NO_PORT. If SEQ66_NO_PORT, * the derived class will get the port ID at port-setup time. * * \param queue * Provides the queue ID. It has different meanings in each of the MIDI * implementations. Defaults to bad_id(). * * - ALSA (seq66). This is the ALSA queue number, which is an ALSA * concept. * - PortMidi. This is the PortMidi buss number, sort of. It is * the PmDeviceID value. * - RtMidi. Not sure yet if it will have meaning here. * * \param ppqn * Provides the PPQN value. * * \param bpm * Provides the BPM value. * * \param iotype * Indicates that this midibus represents and input port, as opposed to * an output port. * * \param porttype * Indicates that the port represented by this object is to be virtual. * Indicates that the port represented by this object is a system port. * This could also be set via the init_in(), * init_out(), init_in_sub(), or init_out_sub() routines. Doing it here * seems okay. * Currently only ALSA does system ports (timer or announce ports). * * \param portalias * In some versions of JACK (or in some configurations of JACK?) the * command "jack_lsp --alias" will show the names of the actual devices * associated with each port. If available, we can take advantage of * that and not have to use the "a2jmidid --export-hw" command. */ midibase::midibase ( const std::string & appname, // application name const std::string & busname, // can be empty const std::string & portname, // can be empty int index, // just an ordinal for display int bus_id, // an index in some implementations int port_id, // an index in some implementations int queue, int ppqn, midibpm bpm, io iotype, port porttype, const std::string & portalias ) : m_bus_index (index), m_client_id (-1), /* ca 2023-12-08 */ m_bus_id (bus_id == (-1) ? 0 : bus_id), /* uninit'd midi_info */ m_port_id (port_id), m_clock_type (e_clock::none), m_io_active (false), m_unavailable (false), m_ppqn (choose_ppqn(ppqn)), m_bpm (bpm), m_queue (queue), m_display_name (), m_bus_name (busname), m_port_name (portname), m_port_alias (portalias), m_lasttick (0), m_io_type (iotype), m_port_type (porttype), m_mutex () { if (m_port_type != port::manual) { if (! busname.empty() && ! portname.empty()) { set_name(appname, busname, portname); } else { errprint("midibase() programmer error"); } } } /** * A rote empty destructor. */ midibase::~midibase() { // empty body } /** * Sets the name of the buss by assembling the name components obtained from * the system in a straightforward manner: * * [0] 128:2 seq66:seq66 port 2 * * We want to see if the user has configured a port name. If so, and this is * an output port, then the buss name is overridden by the entry in the "usr" * configuration file. Otherwise, we fall back to the parameters. Note that * this has been tweaked versus Seq24, where the "usr" devices were also * applied to the input ports. Also note that the "usr" device names should * be kept short, and the actual buss name from the system is shown in * brackets. * * \param appname * This is the name of the client, or application. Not to be confused * with the ALSA client-name, which is actually a buss or subsystem name. * * \param busname * Provides the name of the sub-system, such as "Midi Through" or * "TiMidity". * * \param portname * Provides the name of the port. In ALSA, this is something like * "busname port X". */ void midibase::set_name ( const std::string & appname, const std::string & busname, const std::string & portname ) { char name[128]; std::string bname = usr().bus_name(m_bus_index); bool do_output_name = is_output_port() && ! bname.empty(); if (is_virtual_port()) { /* * Let's also assign any "usr" names to the virtual ports as well. */ if (do_output_name) { snprintf ( name, sizeof name, "%s [%s]", bname.c_str(), portname.c_str() ); bus_name(bname); } else { snprintf ( name, sizeof name, "[%d] %d:%d %s:%s", bus_index(), bus_id(), port_id(), appname.c_str(), portname.c_str() ); bus_name(appname); port_name(portname); } } else { char alias[80]; /* virtual ports can't have aliases */ if (do_output_name) { snprintf ( alias, sizeof alias, "%s [%s]", bname.c_str(), portname.c_str() ); bus_name(bname); } else if (! busname.empty()) { snprintf ( alias, sizeof alias, "%s:%s", busname.c_str(), portname.c_str() ); bus_name(busname); } else snprintf(alias, sizeof alias, "%s", portname.c_str()); snprintf /* copy the client name parts */ ( name, sizeof name, "[%d] %d:%d %s", bus_index(), bus_id(), port_id(), alias ); } display_name(name); } /** * Sets the name of the buss in a different way. If the port is virtual, * this function just calls set_name(). Otherwise, it reassembles the name * so that it refers to a port found on the system, but modified to make it a * unique application port. For example: * * [0] 128:0 yoshimi:midi in * * is transformed to this: * * [0] 128:0 seq66:yoshimi midi in * * As a side-effect, the "short" portname is changed, from (for example) * "midi in" to "yoshimi midi in". * * This function is used only by the MIDI JACK modules. * * \param appname * This is the name of the client, or application. Not to be confused * with the ALSA/JACK client-name, which is actually a buss or subsystem * name. * * \param busname * Provides the name of the sub-system, such as "Midi Through", * "TiMidity", or "seq66". */ void midibase::set_alt_name ( const std::string & appname, const std::string & busname ) { std::string portname = connect_name(); if (is_virtual_port()) { set_name(appname, busname, portname); } else { std::string bname = busname; std::string pname = portname; char alias[128]; snprintf /* copy the client name parts */ ( alias, sizeof alias, "[%d] %d:%d %s", bus_index(), bus_id(), port_id(), pname.c_str() ); bus_name(bname); port_name(pname); display_name(alias); } } /** * \getter m_bus_name and m_port_name * Concatenates the bus and port names into a string of the form * "busname:portname". If either name is empty, an empty string is * returned. */ std::string midibase::connect_name () const { std::string result = m_bus_name; if (! result.empty() && ! m_port_name.empty()) { result += ":"; result += m_port_name; } return result; } /** * Indicates if we can connect a port (even if disabled). Used only in * midi_jack_info.so far. */ bool midibase::is_port_connectable () const { bool result = ! is_virtual_port(); if (result) result = port_enabled() || rc().init_disabled_ports(); return result; } /** * Wrapper function for businfo::initialize(). */ bool midibase::initialize (bool initdisabled) { bool result = true; bool ok = port_enabled() || initdisabled; if (ok) { if (is_input_port()) { if (is_virtual_port()) result = init_in_sub(); else result = init_in(); } else { if (is_virtual_port()) result = init_out_sub(); else result = init_out(); } } else { #if defined SEQ66_PLATFORM_DEBUG_TMI warnprint("breakpoint"); #endif } return result; } /** * Prints m_name. */ void midibase::print () { printf("%s:%s", m_bus_name.c_str(), m_port_name.c_str()); } /** * This play() function takes a native event, encodes it to a MIDI * sequencer event, sets the broadcasting to the subscribers, sets the * direct-passing mode to send the event without queueing, and puts it in the * queue. * * \threadsafe * * However, do we really need this? Playback seems to work fine without * it. * * \param e24 * The event to be played on this bus. For speed, we don't bother to * check the pointer. * * \param channel * The channel of the playback. */ void midibase::play (const event * e24, midibyte channel) { automutex locker(m_mutex); api_play(e24, channel); } /** * Takes a native SYSEX event, encodes it to an ALSA event, and then * puts it in the queue. * * \param e24 * The event to be handled. */ void midibase::sysex (const event * e24) { automutex locker(m_mutex); api_sysex(e24); } /** * Flushes our local queue events out into ALSA. */ void midibase::flush () { automutex locker(m_mutex); api_flush(); } /** * Need to revisit this at some point. If we enable the full set_clock(), we * get two instances of each output port. * * \param clocktype * The value used to set the clock-type. */ bool midibase::set_clock (e_clock clocktype) { m_clock_type = clocktype; m_io_active = clocktype != e_clock::disabled; return true; } /** * Set status to of "inputting" to the given value. If the parameter is * true, then init_in() is called; otherwise, deinit_in() is called. * * \param inputing * The inputing value to set. For input system ports, it is always set * to true, no matter how it is configured in the "rc" file. */ bool midibase::set_input (bool inputing) { bool result = false; if (is_system_port()) { m_io_active = true; result = init_in(); /* is this init really necessary now? */ } else { m_io_active = inputing; result = true; } return result; } /** * Initialize the clock, continuing from the given tick. This function * doesn't depend upon the MIDI API in use. Here, e_clock::none and * e_clock::disabled have the same effect... none. * * \param tick * The starting tick. */ void midibase::init_clock (midipulse tick) { if (port_enabled() && m_ppqn > 0) /* new check for enabled */ { if (m_clock_type == e_clock::pos && tick != 0) { continue_from(tick); } else if (m_clock_type == e_clock::mod || tick == 0) { start(); /* * The next equation is effectively (m_ppqn / 4) * 16 * 4, or * m_ppqn * 16. Note that later we have pp16th = (m_ppqn / 4). * If any left-overs, wait for next beat (16th note) to clock. */ midipulse clock_mod_ticks = (m_ppqn / 4) * m_clock_mod; midipulse leftover = (tick % clock_mod_ticks); midipulse starting_tick = tick - leftover; if (leftover > 0) starting_tick += clock_mod_ticks; m_lasttick = starting_tick - 1; } } } /** * Continue from the given tick. Tell the device that we are going to start * at a certain position (starting_tick). If there is anything left, then * wait for next beat (16th note) to start clocking. * * \param tick * The continuing tick. */ void midibase::continue_from (midipulse tick) { midipulse pp16th = m_ppqn / 4; midipulse leftover = tick % pp16th; midipulse beats = tick / pp16th; midipulse starting_tick = tick - leftover; if (leftover > 0) starting_tick += pp16th; m_lasttick = starting_tick - 1; if (clock_enabled()) api_continue_from(tick, beats); } /** * This function gets the MIDI clock a-runnin', if the clock type is not * e_clock::none or e_clock::disabled. */ void midibase::start () { m_lasttick = -1; if (clock_enabled()) api_start(); } /** * Stop the MIDI buss. */ void midibase::stop () { m_lasttick = -1; if (clock_enabled()) api_stop(); } /** * Generates the MIDI clock, starting at the given tick value. The number * of ticks needed is calculated. * * \threadsafe * * \param tick * Provides the starting tick. */ void midibase::clock (midipulse tick) { automutex locker(m_mutex); if (clock_enabled()) { bool done = m_lasttick >= tick; int ct = clock_ticks_from_ppqn(m_ppqn); /* ppqn / 24 */ while (! done) { ++m_lasttick; done = m_lasttick >= tick; if ((m_lasttick % ct) == 0) /* tick time yet? */ api_clock(tick); } api_flush(); /* and send it out */ } } /** * A static debug function, enabled only for trouble-shooting. * * \param context * Human readable context information (e.g. "ALSA"). * * \param tick * Provides the current tick value. */ void midibase::show_clock (const std::string & context, midipulse tick) { msgprintf(msglevel::error, "%s clock [%ld]", context.c_str(), tick); } #if defined SEQ66_SHOW_BUS_VALUES /** * Shows most midibase members. */ void midibase::show_bus_values () { if (rc().verbose()) { const char * vport = is_virtual_port() ? "virtual" : "non-virtual" ; const char * iport = is_input_port() ? "input" : "output" ; const char * sport = is_system_port() ? "system" : "device" ; printf ( "client id: %d\n" "bus id: %d\n" "port id: %d\n" "display name: %s\n" "connect name: %s\n" "bus : port name: %s : %s\n" "bus type: %s %s %s\n" "clock & enabling: %d & %s\n" , client_id(), bus_id(), port_id(), display_name().c_str(), connect_name().c_str(), m_bus_name.c_str(), m_port_name.c_str(), vport, iport, sport, int(get_clock()), port_enabled() ? "yes" : "no" ); } } #endif // defined SEQ66_SHOW_BUS_VALUES } // namespace seq66 /* * midibase.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/midibytes.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midibytes.cpp * * This module declares a couple of useful data classes. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-09 * \updates 2022-07-09 * \license GNU GPLv2 or above * * These classes were originally structures, but now they are "constant" * classes, filled in at construction time and accessed only through getter * functions. */ #include /* std::invalid_argument */ #include "midi/midibytes.hpp" /* seq66::midi_timing, _measures */ #include "util/basic_macros.h" /* not_nullptr() macro */ namespace seq66 { /* * ------------------------------------------------------------------------- * midi_measures * ------------------------------------------------------------------------- */ /** * Default constructor for midi_measures. */ midi_measures::midi_measures () : m_measures (0), m_beats (0), m_divisions (0) { // Empty body } /** * Principal constructor for midi_measures. * * \param measures * Copied into the m_measures member. * * \param beats * Copied into the m_beats member. * * \param divisions * Copied into the m_divisions member. */ midi_measures::midi_measures (int measures, int beats, int divisions) : m_measures (measures), m_beats (beats), m_divisions (divisions) { // Empty body } /* * ------------------------------------------------------------------------- * midi_timing * ------------------------------------------------------------------------- */ /** * Defaults constructor for midi_timing. */ midi_timing::midi_timing () : m_beats_per_minute (0), m_beats_per_measure (0), m_beat_width (0), m_ppqn (0) { // Empty body } /** * Principal constructor for midi_timing. * * \param bpminute * Copied into the m_beats_per_minute member. * * \param bpmeasure * Copied into the m_beats_per_measure member. * * \param beatwidth * Copied into the m_beat_width member. * * \param ppqn * Copied into the m_ppqn member. */ midi_timing::midi_timing ( midibpm bpminute, int bpmeasure, int beatwidth, int ppqn ) : m_beats_per_minute (bpminute), m_beats_per_measure (bpmeasure), m_beat_width (beatwidth), m_ppqn (ppqn) { // Empty body } /* * Free functions. */ std::string midi_bytes_string (const midibytes & b, int limit) { std::string result; int count = int(b.size()); bool no_0x = limit > 0; int len = count; if (no_0x && (limit < count)) len = limit; if (len > 0) { char tmp[8]; const char * fmt = no_0x ? "%02X" : "0x%02x" ; for (int i = 0; i < len; ++i) { (void) snprintf(tmp, sizeof tmp, fmt, unsigned(b[i])); result += tmp; if (i < (len + 1)) result += " "; } if (len < count) result += " ..."; } return result; } /** * Converts a string to a MIDI byte. Similar to string_to_long() in the * strfunctions module. * * \param s * Provides the string to convert to a MIDI byte. * * \return * Returns the MIDI byte value represented by the string. */ midibyte string_to_midibyte (const std::string & s, midibyte defalt) { midibyte result = defalt; try { int temp = std::stoi(s, nullptr, 0); if (temp >= 0 && temp <= UCHAR_MAX) result = midibyte(temp); } catch (std::invalid_argument const &) { // no code } return result; } /** * Fix up the size of a midibooleans vector. It copies as many midibool * values as is correct from the old vector. Then, if it needs to be longer, * additional 0 values are pushed into the result. */ midibooleans fix_midibooleans (const midibooleans & mbs, int newsz) { midibooleans result; int sz = int(mbs.size()); if (newsz >= sz) { result = mbs; if (newsz > sz) { int diff = newsz - sz; for (int i = 0; i < diff; ++i) result.push_back(0); } } else { for (int i = 0; i < newsz; ++i) result.push_back(mbs[i]); } return result; } } // namespace seq66 /* * midibytes.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/midifile.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midifile.cpp * * This module declares/defines the base class for MIDI files. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2025-10-16 * \license GNU GPLv2 or above * * For a quick guide to the MIDI format, see, for example: * * http://www.mobilefish.com/tutorials/midi/midi_quickguide_specification.html * * It is important to note that most sequencers have taken a shortcut or two * in reading the MIDI format. For example, most will silently ignored an * unadorned control tag (0x242400nn) which has not been packaged up as a * proper sequencer-specific meta event. The midicvt program * (https://github.com/ahlstromcj/midicvt, derived from midicomp, midi2text, * and mf2t/t2mf) does not ignore this lack of a SeqSpec wrapper, and hence * we decided to provide a new, more strict input and output format for the * the SeqSpec (formerly misnomered as "proprietary") track in Seq66. * * Elements written: * * - MIDI header. * - Tracks. * These items are then written, preceded by the "MTrk" tag and * the track size. * - Sequence number. * - Sequence name. * - Time-signature and tempo (track 0 only) * - Sequence events. * * Items handled in midi_vector_base: * * - Key signature. Although Seq66 does not create or use this * event, if present, it is preserved so that it can be written out * to the file when saved. * - Time signature. * - Tempo. * - Sequence number. * - Track end. * - Seq66-specific SeqSpec data. * * Uses the new format for the Seq66 footer section of the Seq24 MIDI * file. * * In the new format, each sequencer-specfic value (0x242400xx) is preceded * by the sequencer-specific prefix, 0xFF 0x7F len). By default, the new * format is used. In Seq66, the old format is no longer supported. * * Running status: * * A recommended approach for a receiving device is to maintain its * "running status buffer" as so: * * -# Buffer is cleared (ie, set to 0) at power up. * -# Buffer stores the status when a Voice Category Status (ie, 0x80 * to 0xEF) is received. * -# Buffer is cleared when a System Common Category Status (ie, 0xF0 * to 0xF7) is received. * -# Nothing is done to the buffer when a RealTime Category message is * received. * -# Any data bytes are ignored when the buffer is 0. */ #include /* std::ifstream and std::ofstream */ #include /* std::unique_ptr<> */ #include "cfg/settings.hpp" /* seq66::rc() and choose_ppqn() */ #include "midi/midifile.hpp" /* seq66::midifile */ #include "midi/midi_vector.hpp" /* seq66::midi_vector container */ #include "midi/wrkfile.hpp" /* seq66::wrkfile class */ #include "play/performer.hpp" /* seq66::performer */ #include "play/sequence.hpp" /* seq66::sequence */ #include "util/filefunctions.hpp" /* seq66::get_full_path() */ #include "util/palette.hpp" /* seq66::palette_to_int(), colors */ namespace seq66 { /** * F0: * * In Dixie04.mid, we find instances of the sequences * * 9D 7D F0 7F 00 F0 * F0 7F nn F0 * * We have found not information on these sequences. They are not legal, and * the 7F is misinterpreted as a bogus length of 127. * * We peek ahead for an F0 and abort if we find one. The result does not * change the patterns, but does strip out sequences like F0 7F nn that "end" * with an F0. */ #undef SEQ66_IGNORE_F0_F7_NN_F0 /** * Minimal MIDI file size. Just used for a sanity check. * *
= * * Bytes: 4 + 4 + 2 + 2 + 2 = 14 */ static const size_t c_minimum_midi_file_size = 14; /** * Magic number for handling mute-group formats. */ static const unsigned c_legacy_mute_group = 1024; /* 0x0400 */ /** * A manifest constant for controlling the length of the stream buffering * array in a MIDI file. */ static const int c_midi_line_max = 1024; /** * The maximum length of a Seq24/Seq66 track nam3. */ static const int c_trackname_max = 256; /** * The maximum allowed variable length value for a MIDI file, which allows * the length to fit in a 32-bit integer. */ static const int c_varlength_max = 0x0FFFFFFF; /** * Highlights the MIDI file header value, "MThd". */ static const miditag c_mthd_tag = 0x4D546864; /* magic number 'MThd' */ /** * Highlights the MIDI file track-marker (chunk) value, "MTrk". */ static const miditag c_mtrk_tag = 0x4D54726B; /* magic number 'MTrk' */ /** * The chunk header value for the Seq66 proprietary/SeqSpec section. We * might try other chunks, as well, since, as per the MIDI specification, * unknown chunks should not cause an error in a sequencer (or our midicvt * program). For now, we stick with "MTrk". */ static const miditag c_prop_chunk_tag = c_mtrk_tag; /** * Provides the track number for the proprietary/SeqSpec data when using * the new format. * Can't use numbers, such as 0xFFFF, that have MIDI meta tags in them, * confuses the "SeqSpec" track parser. */ static const midishort c_prop_seq_number = 0x3FFF; static const midishort c_prop_seq_number_old = 0x7777; /** * Provides the track name for the "proprietary" data when using the new * format. (There is no track-name for the "proprietary" footer track when * the legacy format is in force.) This is more useful for examining a hex * dump of a Seq66 song than for checking its validity. It's overkill * that causes needless error messages. */ static const std::string c_prop_track_name = "Seq66-S"; /** * This const is used for detecting SeqSpec data that Seq66 does not handle. * If this word is found, then we simply extract the expected number of * characters specified by that construct, and skip them when parsing a MIDI * file. */ static const miditag c_prop_tag_word = 0x24240000; /* * Internal functions. */ /** * An easier, shorter test for the c_prop_tag_word part of a long * value, that clients can use. */ static bool is_proptag (midilong p) { return (miditag(p) & c_prop_tag_word) == c_prop_tag_word; } #if defined USE_THIS_CODE /** * Name of the initial text meta events (00 through 07). */ const std::string midifile::sm_meta_text_labels[8] = { "Seq number", "Text", "Copyright", "Track Name", "Instrument Name", "Lyric", "Marker", "Cue Point" }; #endif /** * Principal constructor. * * \param name * Provides the name of the MIDI file to be read or written. * * \param ppqn * Provides the initial value of the PPQN setting. It is handled * differently for parsing (reading) versus writing the MIDI file. * WARNING: It is the responsibility of the caller to make sure the PPQN * value is valid, usually by passing in the result of the choose_ppqn() * function. * - Reading. The caller of read_midi_file(), as well as the function * itself, determine the value of ppqn used here. It is either 0 or * the result of seq66::choose_ppqn(). * - If usr().use_file_ppqn() is true, then m_ppqn is set to the * value read from the MIDI file. No PPQN scaling is done. * - Otherwise, the ppqn value is used as is. If the file uses a * different PPQN than the default, PPQN rescaling is done to * make it so. The PPQN value read from the MIDI file is used to * scale the running-time of the track relative to * usr().default_ppqn(). * - Writing. This value is written to the MIDI file in the header * chunk of the song. Note that the caller must query for the * PPQN set during parsing, and pass it to the constructor when * preparing to write the file. See how it is done in the qsmainwnd * class. * * \param globalbgs * If true, write any non-default values of the key, scale, and * background sequence to the global "proprietary" section of the MIDI * file, instead of to each sequence. Note that this option is only used * in writing; reading can handle either format transparently. Depends on * usr().global_seq_feature() returning true. * * \param verifymode * If set to true, we are opening files just to verify them before * accepting the usage of a playlist. In this case, we clear out each * song after it is read in for verification. It defaults to false. * Actually, the playlist::verify() function clears the song, but this * variable is still useful to cut down on output during verfication. * See grab_input_stream(). */ midifile::midifile ( const std::string & name, int ppqn, bool globalbgs, bool verifymode ) : m_mutex (), m_verify_mode (verifymode), m_file_size (0), m_error_message (), m_error_is_fatal (false), m_disable_reported (false), m_running_status_action (rc().running_status_action()), m_pos (0), m_name (name), m_data (), m_char_list (), m_global_bgsequence (globalbgs), m_use_scaled_ppqn (false), /* scaled() */ m_ppqn (ppqn), /* can start as 0 */ m_file_ppqn (0), /* can change */ m_ppqn_ratio (1.0), /* for scaled() */ m_main_bpm (0.0), m_file_format (-1), m_smf0_splitter () { // no other code needed } /** * A rote destructor. */ midifile::~midifile () { // empty body } /** * Seeks to a new, absolute, position in the data stream. All this function * does is change the value of m_pos. All of the file is already in memory. * * \param pos * Provides the new position to seek. * * \return * Returns true if the seek could be accomplished. No error message is * logged, but the caller should take evasive action if false is * returned. And, in that case, m_pos is unchanged. */ bool midifile::read_seek (size_t pos) { bool result = false; if (pos < m_file_size) { m_pos = pos; // m_d->m_IOStream->device()->seek(pos); result = true; } return result; } /** * Reads 1 byte of data directly from the m_data vector, incrementing * m_pos after doing so. * * \return * Returns the byte that was read. Returns 0 if there was an error, * though there's no way for the caller to determine if this is an error * or a good value. */ midibyte midifile::read_byte () { if (m_pos < m_file_size) { return m_data[m_pos++]; } else if (! m_disable_reported) (void) set_error_dump("End-of-file; aborting reading"); return 0; } /** * Reads 2 bytes of data using read_byte(). * * \return * Returns the two bytes, shifted appropriately and added together, * most-significant byte first, to sum to a short value. */ midishort midifile::read_short () { midishort result = read_byte() << 8; result += read_byte(); return result; } /** * Reads 4 bytes of data using read_byte(). * * \return * Returns the four bytes, shifted appropriately and added together, * most-significant byte first, to sum to a long value. */ midilong midifile::read_long () { midilong result = read_byte(); result <<= 24; result += read_byte() << 16; result += read_byte() << 8; result += read_byte(); return result; } /** * We want to be able to support a special case, the c_mutegroups count. In * Seq24, the count was always 1024 (0x400), which would be represented in * the MIDI file as 0x00 0x04 0x00 0x00. Other numbers use the same * encoding, which does not use the MIDI VLV encoding at all. The bytes are * stored conventionally. * * We also want to be able to support counts other than 1024 (32 groups x 32 * sequences), such as 1024 = 16 groups x 64 sequences and 1536 = 16 groups x * 96 sequences. We can adopt a special encoding if the value is not 0 or * 1024: * * - 2-bytes group countvalue * - 2-bytes sequence count value * * So, 16 groups x 64 sequences would be represented by 0x1040 = 4160 dec. * 16 groups x 96 sequences would be represented by 0x1060 = 4192 dec. And * 1024 dec = 0x0400 would represent 4 groups and 0 sequences, obviously * bogus. * * Recall that midilong (see midi/midibytes.hpp) is still signed. * * \param [out] highbytes * Provides the value of the most significant 2 bytes of the four-byte * ("long") value. If the value read is 0, 0 is written to this * parameter. If the value is 1024, then 32 is written. * * \param [out] lowbytes * Provides the value of the least significant 2 bytes of the four-byte * ("long") value. If the value read is 0, 0 is written to this * parameter. If the value is 1024, then 32 is written. * * \return * Returns the value returned by read_long(). */ midilong midifile::read_split_long (unsigned & highbytes, unsigned & lowbytes) { unsigned short high = static_cast(read_byte()); high <<= 8; high += static_cast(read_byte()); unsigned short low = static_cast(read_byte()); low <<= 8; low += static_cast(read_byte()); midilong result = (midilong(high) << 16) + midilong(low); if (result == c_legacy_mute_group) /* 1024 (0x0400) */ { high = 32U; low = 32U; } else if (result == 0) { high = 0; low = 0; } highbytes = static_cast(high); lowbytes = static_cast(low); return result; } /** * A helper function to simplify reading midi_control data from the MIDI * file. * * \param b * The byte array to receive the data. * * \param len * The number of bytes in the array, and to be read. * * \return * Returns true if any bytes were read. Do not use \a b if false is * returned. */ bool midifile::read_byte_array (midibyte * b, size_t len) { bool result = not_nullptr(b) && len > 0; if (result) { for (size_t i = 0; i < len; ++i) *b++ = read_byte(); } return result; } /** * A helper function for arbitrary, otherwise unhandled meta data. */ bool midifile::read_meta_data (sequence & s, event & e, midibyte metatype, size_t len) { bool result = checklen(len, metatype); if (result) { std::vector bt; for (int i = 0; i < int(len); ++i) bt.push_back(read_byte()); result = e.append_meta_data(metatype, bt); if (result) result = s.append_event(e); } return result; } /** * This function handles data that occurs after the "F0 len" sequence and * until (inclusive) the terminating F7 is encountered. The data is stored * in unencoded format, which means that the F0 is restored to the beginning of * the data bytes and there is no length value. * * We now check for unterminated SysEx messages as found in Dixie04.mid. * We handle F7 as continuation, but not yet escape values. */ bool midifile::read_sysex_data ( sequence & s, event & e, size_t len, bool continuation ) { bool result = len > 0; if (result) { e.reset_sysex(); /* clear m_sysex */ bool started = continuation ? e.append_sysex_byte(EVENT_MIDI_SYSEX_CONTINUE) : /* 0xF7 */ e.append_sysex_byte(EVENT_MIDI_SYSEX) ; /* 0xF0 */ if (started) { for (size_t i = 0; i < len; ++i) { if (at_end()) { e.append_sysex_byte(EVENT_MIDI_SYSEX_END); break; } else { /* * See the "F0" note in the banner at the top of this * module. */ #if defined SEQ66_IGNORE_F0_F7_NN_F0 midibyte p = peek(1); /* peek ahead 1 */ if (p != EVENT_MIDI_SYSEX) /* F0!!! */ { midibyte b = read_byte(); if (! e.append_sysex_byte(b)) /* not F7? */ break; } else { result = false; break; /* preserve 0 */ } #else midibyte b = read_byte(); if (! e.append_sysex_byte(b)) /* not F7? */ break; #endif } } if (result) result = s.append_event(e); } else result = false; } return result; } /** * A overload function to simplify reading midi_control data from the MIDI * file. It uses a standard string object instead of a buffer. * * \param b * The std::string to receive the data. * * \param len * The number of bytes to be read. * * \return * Returns true if any bytes were read. The string \a b will be empty if * false is returned. */ bool midifile::read_string (std::string & b, size_t len) { bool result = len > 0; b.clear(); if (result) { if (len > b.capacity()) b.reserve(len); for (size_t i = 0; i < len; ++i) b.push_back(read_byte()); } return result; } /** * A overload function to simplify reading midi_control data from the MIDI * file. It uses a midibytes object instead of a buffer. * * \param b * The midibytes to receive the data. * * \param len * The number of bytes to be read. * * \return * Returns true if any bytes were read. The string \a b will be empty if * false is returned. */ bool midifile::read_byte_array (midibytes & b, size_t len) { bool result = len > 0; b.clear(); if (result) { if (len > b.capacity()) b.reserve(len); for (size_t i = 0; i < len; ++i) b.push_back(read_byte()); } return result; } /** * Read a MIDI Variable-Length Value (VLV), which has a variable number * of bytes. This function reads the bytes while bit 7 is set in each * byte. Bit 7 is a continuation bit. See write_varinum() for more * information. * * \return * Returns the accumulated values as a single number. */ midilong midifile::read_varinum () { midilong result = 0; midibyte c; while (((c = read_byte()) & 0x80) != 0x00) /* while bit 7 is set */ { result <<= 7; /* shift result 7 bits */ result += c & 0x7F; /* add bits 0-6 */ } result <<= 7; /* bit was clear */ result += c & 0x7F; return result; } /** * Jumps/skips the given number of bytes in the data stream. If too large, * the position is left at the end. Primarily used in the derived class * wrkfile. * * \param sz * Provides the gap size, in bytes. */ void midifile::read_gap (size_t sz) { if (sz > 0) { size_t p = m_pos + sz; if (p >= m_file_size) { p = m_file_size - 1; if (! m_disable_reported) (void) set_error_dump("End-of-file; reading disabled"); } m_pos = p; } } /** * Creates the stream input, reads it into the "buffer", and then closes * the file. No file buffering needed on these beefy machines! :-) * As a side-effect, also sets m_file_size. * * We were using the assignment operator, but this caused an error using old * 32-bit debian stable, g++ 4.9 on one of our old laptops. The assignment * operator was deleted by the compiler. So now we use constructor notation. * A little bit odd, since we thought the compiler would convert assignment * operator notation to constructor notation, but hey, compilers are not * perfect. Also, no need to use the krufty string pointer for the * file-name. * * \param tag * Basically an informative string to denote what kind of file is being * opened, "MIDI" or "WRK". * * \return * Returns true if the input stream was successfully opend on a good * file. Use it only if the return value is true. */ bool midifile::grab_input_stream (const std::string & tag) { m_file_size = file_size(m_name); if (m_name.empty() || m_file_size == 0) { return set_error("No MIDI file or data."); } std::ifstream file(m_name, std::ios::in | std::ios::binary | std::ios::ate); bool result = file.is_open(); m_error_is_fatal = false; if (result) { if (m_file_size < c_minimum_midi_file_size) { result = set_error("File too small."); } else { file.seekg(0, std::ios::beg); /* seek to the file's start */ try { m_data.resize(m_file_size); /* allocate the data */ file.read((char *)(&m_data[0]), m_file_size); } catch (const std::bad_alloc & ex) { result = set_error("File stream memory allocation failed."); } file.close(); } } else { std::string errmsg = "Open failed: "; errmsg += tag; errmsg += " file '"; errmsg += m_name; errmsg += "'"; result = set_error(errmsg); } return result; } /** * This function opens a binary MIDI file and parses it into sequences * and other application objects. * * In addition to the standard MIDI track data in a normal track, * Seq24/Seq66 adds four sequencer-specific events just before the end * of the track: * \verbatim c_midibus: SeqSpec FF 7F 05 24 24 00 01 00 c_midichannel: SeqSpec FF 7F 05 24 24 00 02 06 c_timesig: SeqSpec FF 7F 06 24 24 00 06 04 04 c_triggers_ex: SeqSpec FF 7F 1C 24 24 00 08 00 00 ... c_trig_transpose: SeqSpec FF 7F 1C 24 24 00 20 00 00 ... \endverbatim * * Note that only Seq66 adds "FF 7F len" to the SeqSpec data. * * Standard MIDI provides for port and channel specification meta events, but * they are considered obsolete. They were meant to change port and * channel on the fly during playback, apparently * \verbatim Obsolete meta-event: Replacement: MIDI port (buss): FF 21 01 po Device (port) name: FF 09 len text MIDI channel: FF 20 01 ch \endverbatim * * What do other applications use for specifying port/channel? * * Note the is-modified flag: We now assume that the performer object is * starting from scratch when parsing. But we let mainwnd tell the performer * object when to clear everything with performer::clear_all(). The mainwnd * does this for a new file, opening a file, but not for a file import, which * might be done simply to add more MIDI tracks to the current composition. * So, if parsing succeeds, all we want to do is make sure the flag is set. * Parsing a file successfully is not always a modification of the setup. * For instance, the first read of a MIDI file should start clean, not dirty. * * SysEx notes: * * Some files (e.g. Dixie04.mid) do not always encode System Exclusive * messages properly for a MIDI file. Instead of a varinum length value, * they are followed by extended IDs (0x7D, 0x7E, or 0x7F). * * THE ABOVE IS WRONG! * * We've covered some of those cases by disabling access to m_data if the * position passes the size of the file, but we want try to bypass these * odd cases properly. So we look ahead for one of these special values. * * Currently, Seq66, like Se24, handles SysEx message only to the * extend of passing them via MIDI Thru. We hope to improve on that * capability eventually. * * \param p * Provides a reference to the performer object into which sequences/tracks * are to be added. * * \param screenset * The screen-set offset to be used when loading a sequence (track) from * the file. This value ranges from -31 to 0 to +31 (32 is the maximum * screen-set available in Seq24). This offset is added to the sequence * number read in for the sequence, to place it elsewhere in the imported * tune, and locate it in a specific screen-set. If this parameter is * non-zero, then we will assume that the performer data is dirty. The * default is 0. * * \param importing * Indicates that we are importing a file, and do not want to parse/erase * any "proprietrary" information from the performance. Defaults to false. * Also will flag a modification. * * \return * Returns true if the parsing succeeded. Note that the error status is * saved in m_error_is_fatal, and a message (to display later) is saved * in m_error_message. */ bool midifile::parse (performer & p, int screenset, bool importing) { bool result = grab_input_stream(std::string("MIDI")); if (result) { midilong ID = read_long(); /* hdr chunk info */ midilong hdrlength = read_long(); /* MThd length */ clear_errors(); if (ID != c_mthd_tag && hdrlength != 6) /* magic 'MThd' */ return set_error_dump("Invalid MIDI header chunk detected", ID); midishort fmt = read_short(); /* 0, 1, or 2 */ m_file_format = fmt; if (fmt == 0) { m_smf0_splitter.initialize(); /* SMF 0 support */ result = parse_smf_0(p, screenset); /* p.smf_format(?) */ } else if (fmt == 1) { result = parse_smf_1(p, screenset); p.smf_format(1); } else { m_error_is_fatal = true; result = set_error_dump ( "Unsupported MIDI format number", midilong(fmt) ); } if (result) { if (m_pos < m_file_size) /* any data left? */ { if (! importing) { result = parse_seqspec_track(p, m_file_size); if (result) { // increment the track count? } } } if (result && importing) p.modify(); /* modify flag */ } } else { m_error_is_fatal = true; result = set_error_dump("Cannot open MIDI", 0); } return result; } /** * This function parses an SMF 0 binary MIDI file as if it were an SMF 1 * file, then, if more than one MIDI channel was encountered in the sequence, * splits all of the channels in the sequence out into separate sequences. * The original sequence remains in place, in sequence slot 16 (the 17th * slot). The user is responsible for deleting it if it is not needed. * * \param p * Provides a reference to the performer object into which sequences/tracks * are to be added. * * \param screenset * The screen-set offset to be used when loading a sequence (track) from * the file. * * \return * Returns true if the parsing succeeded. */ bool midifile::parse_smf_0 (performer & p, int screenset) { bool c = usr().convert_to_smf_1(); /* true by default */ bool result = parse_smf_1(p, screenset, c); /* format 0 conversion? */ if (c) { if (result) { result = m_smf0_splitter.split(p, screenset, m_ppqn); if (result) { p.modify(); /* to prompt for save */ p.smf_format(1); /* converted to SMF 1 */ } else result = append_error("SMF 0 split failed."); } } else if (result) { seq::pointer s = p.get_sequence(0); if (s) { s->set_midi_channel(null_channel()); s->set_color(palette_to_int(PaletteColor::cyan)); p.smf_format(0); } } return result; } /** * Internal function to check for and report a bad length value. * A length of zero is now considered legal, but a "warning" message is shown. * The largest value allowed within a MIDI file is 0x0FFFFFFF. This limit is * set to allow variable-length quantities to be manipulated as 32-bit * integers. * * \param len * The length value to be checked, and it should be greater than 0. * However, we have seen files with zero-length events, such as Lyric * events (0x05). * * \param type * The type of meta event. Used for displaying an error. * * \return * Returns true if the length parameter is valid. This now means it is * simply less than 0x0FFFFFFF. */ bool midifile::checklen (midilong len, midibyte type) { bool result = len <= c_varlength_max; /* 0x0FFFFFFF */ if (result) { result = len > 0; if (! result) { char m[40]; snprintf(m, sizeof m, "0 data length for meta type 0x%02X", type); (void) set_error_dump(m); } } else { char m[40]; snprintf(m, sizeof m, "bad data length for meta type 0x%02X", type); (void) set_error_dump(m); } return result; } /** * Internal function to make the parser easier to read. Handles the * c_triggers_ex and c_trig_transpose values, as well as the old and * deprecated c_triggers value. * * If m_ppqn isn't set to the default value, then we must scale these triggers * accordingly, just as is done for the MIDI events. * * \param seq * Provides the sequence to which the trigger is to be added. * * \param oldppqn * Provides the ppqn value to use to scale the tick values if * scaled() is true. If 0, the ppqn value is not used. * * \param transposable * If true, use the new style trigger, which adds a byte-long transpose * value. */ void midifile::add_trigger (sequence & seq, midishort oldppqn, bool transposable) { midilong on = read_long(); midilong off = read_long(); midilong offset = read_long(); midibyte tpose = 0; if (transposable) tpose = read_byte(); if (oldppqn > 0) { on = rescale_tick(on, m_ppqn, oldppqn); /* new and old PPQN */ off = rescale_tick(off, m_ppqn, oldppqn); offset = rescale_tick(offset, m_ppqn, oldppqn); } seq.add_trigger(on, off - on + 1, offset, tpose, false); } void midifile::add_old_trigger (sequence & seq) { midilong on = read_long(); midilong off = read_long(); seq.add_trigger(on, off - on + 1, 0, false, false); } /** * This function parses an SMF 1 binary MIDI file; it is basically the * original seq66 midifile::parse() function. It assumes the file-data has * already been read into memory. It also assumes that the ID, track-length, * and format have already been read. * * If the MIDI file contains both proprietary (c_timesig) and MIDI type 0x58 * then it came from seq42 or seq32 (Stazed versions). In this case the MIDI * type is parsed first (because it is listed first) then it gets overwritten * by the proprietary, above. * * Note that track_count doesn't count the Seq24 "proprietary" footer * section, even if it uses the new format, so that section will still be * read properly after all normal tracks have been processed. * * PPQN: * * Current time (runningtime) is re the ppqn according to the file, we * have to adjust it to our own ppqn. PPQN / ppqn gives us the ratio. * (This change is not enough; a song with a ppqn of 120 plays too fast * in Seq24, which has a constant ppqn of 192. Triggers must also be * modified.) * * Tempo events: * * If valid, set the global tempo to the first encountered tempo; this is * legacy behavior. Bad tempos occur and stick around, munging exported * songs. We log only the first tempo officially; the rest are stored as * events if in the first track. We also adjust the upper draw-tempo * range value to about twice this value, to give some headroom... it * will not be saved unless the --user-save option is in force. * * Channel: * * We are transitioning away from preserving the channel in the status * byte, which will require using the event::m_channel value. * * Time Signature: * * Like Tempo, Time signature is now handled more robustly. * * Key Signature and other Meta events: * * Although we don't support these events, we do want to keep them, so we * can output them upon saving. Instead of bypassing unhandled Meta * events, we now store them, so that they are not lost when * exporting/saving the MIDI data. * * Track name: * * This event is optional. It's interpretation depends on its context. If * it occurs in the first track of a format 0 or 1 MIDI file, then it * gives the Sequence Name. Otherwise it gives the Track Name. * * End of Track: * * "If delta is 0, then another event happened at the same time as * track-end. Class sequence discards the last note. This fixes that. * A native Seq24 file will always have a delta >= 1." Not true! We've * fixed the real issue by commenting the code that increments current * time. Question: What if BPM is set *after* this event? * * Sequences: * * If the sequence is shorter than a quarter note, assume it needs to be * padded to a measure. This happens anyway if the short pattern is * opened in the sequence editor (seqedit). * * Add sorting after reading all the events for the sequence. Then add * the sequence with it's preferred location as a hint. * * Unknown chunks: * * Let's say we don't know what kind of chunk it is. It's not a MTrk, we * don't know how to deal with it, so we just eat it. If this happened * on the first track, it is a fatal error. * * \param p * Provides a reference to the performer object into which sequences/tracks * are to be added. * * \param screenset * The screen-set offset to be used when loading a sequence (track) from * the file. * * \param convert_smf0 * True if we detected that the MIDI file is in SMF 0 format and we * want to convert it to SMF 1. * * \return * Returns true if the parsing succeeded. */ bool midifile::parse_smf_1 (performer & p, int screenset, bool convert_smf0) { bool result = true; bool got_song_info = false; midibyte buss_override = usr().midi_buss_override(); midishort track_count = read_short(); midishort fileppqn = read_short(); file_ppqn(int(fileppqn)); /* original file PPQN */ if (usr().use_file_ppqn()) { p.ppqn(file_ppqn()); /* let performer know */ usr().file_ppqn(file_ppqn()); ppqn(file_ppqn()); /* PPQN == file PPQN */ scaled(false); /* do not scale time */ } else { p.ppqn(usr().default_ppqn()); scaled(file_ppqn() != usr().default_ppqn()); if (scaled()) ppqn_ratio(double(ppqn()) / double(file_ppqn())); } if (rc().investigate()) { infoprintf("Track count %d", int(track_count)); if (! is_null_buss(buss_override)) infoprintf("Buss override %d", int(buss_override)); } bool set_main_timesig = true; /* first time-sig wins */ for (midishort trk = 0; trk < track_count; ++trk) { midibyte tentative_channel = null_channel(); size_t track_position = m_pos; /* save for SeqSpec'ing */ midilong ID = read_long(); /* get track marker */ midilong TrackLength = read_long(); /* get track length */ if (ID == c_mtrk_tag) /* magic number 'MTrk' */ { int evcount = 0; /* for sanity checking */ bool error_reported = false; /* for handling message */ midipulse runningtime = 0; /* reset time */ midipulse currenttime = 0; /* adjusted by PPQN */ midishort seqnum = c_midishort_max; /* either read or set */ midibyte status = 0; midilong seqspec = 0; /* sequencer-specific */ midibyte last_runningstatus = 0; bool skip_to_end = false; midibyte runningstatus = 0; bool done = false; /* done for each track */ sequence * sp = create_sequence(p); /* create new sequence */ if (is_nullptr(sp)) { set_error_dump("MIDI file parse: sequence allocation failed"); return false; } else sp->seq_number(int(trk)); /* tentative number */ sequence & s = *sp; /* references better */ while (! done) /* get events in track */ { event e; /* note-off, no channel */ midilong len; /* important counter! */ midibyte d0, d1; /* the two data bytes */ midipulse delta = read_varinum(); /* time delta from prev */ status = m_data[m_pos]; /* current event byte */ if (event::is_status(status)) /* is there a 0x80 bit? */ { /* * For SysEx, the skip is undone. For meta events, the * correct event type is obtained anyway. */ skip(1); /* get to d0 */ if (event::is_system_common_msg(status)) { runningstatus = 0; /* clear it */ } else if (! event::is_realtime_msg(status)) { runningstatus = status; /* log status */ if (m_running_status_action == rsaction::recover) last_runningstatus = status; } } else /* there's no 0x80 bit */ { /* * Handle data values. If in running status, set that as * status; the next value to be read is the d0 value. If * not running status, is this an error? */ if (skip_to_end) continue; if (runningstatus > 0) /* running status in force? */ { status = runningstatus; /* yes, use running status */ } else if (last_runningstatus > 0) { runningstatus = last_runningstatus; status = runningstatus; } } e.set_status_keep_channel(status); /* set status, channel */ /* * See "PPQN" section in banner. */ runningtime += delta; /* add in the time */ currenttime = runningtime; if (scaled()) /* adjust time via ppqn */ currenttime = midipulse(currenttime * ppqn_ratio()); e.set_timestamp(currenttime); midibyte eventcode = event::mask_status(status); /* F0 */ midibyte channel = event::mask_channel(status); /* 0F */ switch (eventcode) { case EVENT_NOTE_OFF: /* 3-byte events */ case EVENT_NOTE_ON: case EVENT_AFTERTOUCH: case EVENT_CONTROL_CHANGE: case EVENT_PITCH_WHEEL: d0 = read_byte(); d1 = read_byte(); if (event::is_note_off_velocity(eventcode, d1)) e.set_channel_status(EVENT_NOTE_OFF, channel); e.set_data(d0, d1); /* set data and add */ /* * s.append_event() doesn't sort events; sort after we * get them all. Also, it is kind of weird we change the * channel for the whole sequence here. */ if (s.append_event(e)) /* does not sort */ ++evcount; tentative_channel = channel; /* log MIDI channel */ if (convert_smf0) m_smf0_splitter.increment(channel); /* count chan. */ break; case EVENT_PROGRAM_CHANGE: /* 1-data-byte event*/ case EVENT_CHANNEL_PRESSURE: d0 = read_byte(); /* was data[0] */ e.set_data(d0); /* set data and add */ /* * s.append_event() doesn't sort events; they're sorted * after we read them all. */ if (s.append_event(e)) /* does not sort */ ++evcount; tentative_channel = channel; if (convert_smf0) m_smf0_splitter.increment(channel); /* count chan. */ break; case EVENT_MIDI_REALTIME: /* 0xFn MIDI events */ if (status == EVENT_MIDI_META) /* 0xFF */ { midibyte mtype = read_byte(); /* get meta type */ len = read_varinum(); /* if 0 catch later */ if (convert_smf0) m_smf0_splitter.increment(0); /* meta-storage */ switch (mtype) { case EVENT_META_SEQ_NUMBER: /* FF 00 02 ss */ if (! checklen(len, mtype)) /* might log error */ return false; /* * If this number is the highly unlikely value of * 0x3fff, then we assume this track is the Seq66 * SeqSpec track, the last track in the song. So * we delete the sequence and exit so that that * last track can be processed. The reason this * can happen is if a MIDI app saves the SeqSpec * track unaltered. */ seqnum = read_short(); if (seqnum == c_prop_seq_number) /* 0x3ffff */ { delete sp; /* remove pattern */ m_pos = track_position; return true; /* fake it */ } break; case EVENT_META_TRACK_NAME: /* FF 03 len text */ if (checklen(len, mtype)) { int count = 0; char trackname[c_trackname_max]; for (int i = 0; i < int(len); ++i) { char ch = char(read_byte()); if (count < c_trackname_max) { trackname[count] = ch; ++count; } } trackname[count] = '\0'; s.set_name(trackname); } else return false; break; case EVENT_META_END_OF_TRACK: /* FF 2F 00 */ /* * This is an optional event according to the MIDI * specification. */ s.set_length(currenttime, false); s.zero_markers(); done = true; break; case EVENT_META_SET_TEMPO: /* FF 51 03 tttttt */ if (! checklen(len, mtype)) return false; if (len == 3) { midibytes bt; /* "Tempo events" */ bt.push_back(read_byte()); /* tt */ bt.push_back(read_byte()); /* tt */ bt.push_back(read_byte()); /* tt */ double tt = tempo_us_from_bytes(bt); if (tt > 0.0) { if (trk == 0) { midibpm bpm = bpm_from_tempo_us(tt); if (m_main_bpm == 0.0) { m_main_bpm = bpm; p.set_beats_per_minute(bpm); p.us_per_quarter_note(int(tt)); } s.us_per_quarter_note(int(tt)); } bool ok = e.append_meta_data(mtype, bt); if (ok) { if (s.append_event(e)) ++evcount; } } } else skip(len); /* eat it */ break; case EVENT_META_TIME_SIGNATURE: /* FF 58 04 n d c b */ if (! checklen(len, mtype)) return false; if (len == 4) { int bpb = int(read_byte()); // nn int logbase2 = int(read_byte()); // dd int cc = read_byte(); // cc int bb = read_byte(); // bb #if defined SEQ66_USE_TRACK_0_AS_GLOBAL_TIME_SIG /* undefined */ /* * Could use c_perf_bp_mes and c_perf_bw * instead. */ if (trk == 0) { p.set_beats_per_bar(bpb); p.set_beat_width(bw); p.clocks_per_metronome(cc); p.set_32nds_per_quarter(bb); } #endif midibytes bt; bt.push_back(midibyte(bpb)); bt.push_back(midibyte(logbase2)); bt.push_back(midibyte(cc)); bt.push_back(midibyte(bb)); bool ok = e.append_meta_data(mtype, bt); if (ok) { if (s.add_timesig_event(e, set_main_timesig)) { set_main_timesig = false; ++evcount; } } } else skip(len); /* eat it */ break; case EVENT_META_KEY_SIGNATURE: /* FF 59 02 ss kk */ if (len == 2) { midibytes bt; bt.push_back(read_byte()); /* #/b no. */ bt.push_back(read_byte()); /* min/maj */ bool ok = e.append_meta_data(mtype, bt); if (ok) { if (s.append_event(e)) ++evcount; } } else skip(len); /* eat it */ break; case EVENT_META_SEQSPEC: /* FF F7 = SeqSpec */ /* * ca 2025-06-25. * We were assuming a Seq66 SeqSpec always * had data, and thus did not read it if only * 4 bytes. Replaced "len > 4". */ if (len >= 4) /* FF 7F len [data] */ { seqspec = read_long(); len -= 4; if (len == 0) break; } else if (! checklen(len, mtype)) return false; if (seqspec == c_midibus) { (void) s.set_midi_bus(read_byte()); --len; } else if (seqspec == c_midiinbus) { (void) s.set_midi_in_bus(read_byte()); --len; } else if (seqspec == c_midichannel) { midibyte channel = read_byte(); tentative_channel = channel; --len; if (convert_smf0) m_smf0_splitter.increment(channel); } else if (seqspec == c_timesig) { /* * This can override an early time-signature * event. This doesn't make sense, now that * we think about it. The first time-sig event * should win. ca 2025-06-20. */ int bpb = int(read_byte()); int bw = int(read_byte()); if (set_main_timesig) { if (s.add_c_timesig(bpb, bw, trk == 0)) set_main_timesig = false; } len -= 2; } else if (seqspec == c_triggers) { int sz = trigger::datasize(c_triggers); int num_triggers = len / sz; for (int i = 0; i < num_triggers; ++i) { add_old_trigger(s); len -= sz; } } else if (seqspec == c_triggers_ex) { int sz = trigger::datasize(c_triggers_ex); int num_triggers = len / sz; midishort p = scaled() ? file_ppqn() : 0 ; for (int i = 0; i < num_triggers; ++i) { add_trigger(s, p, false); len -= sz; } } else if (seqspec == c_trig_transpose) { int sz = trigger::datasize(c_trig_transpose); int num_triggers = len / sz; midishort p = scaled() ? file_ppqn() : 0 ; for (int i = 0; i < num_triggers; ++i) { add_trigger(s, p, true); len -= sz; } } else if (seqspec == c_musickey) { s.musical_key(read_byte()); --len; } else if (seqspec == c_musicscale) { s.musical_scale(read_byte()); --len; } else if (seqspec == c_musicchord) { s.musical_chord(read_byte()); --len; } else if (seqspec == c_backsequence) { s.background_sequence(int(read_long())); len -= 4; } else if (seqspec == c_transpose) { s.set_transposable(read_byte() != 0); --len; } else if (seqspec == c_seq_color) { s.set_color(read_byte()); --len; } else if (seqspec == c_seq_loopcount) { s.loop_count_max(int(read_short())); len -= 2; } else if (seqspec == c_mutegroups) { /* handled in parse_seqspec_track() */ } else if (is_proptag(seqspec)) { (void) set_error_dump ( "Unknown Seq66 SeqSpec, skipping", seqspec ); } else { /* will skip all other SeqSpecs */ } skip(len); /* eat it */ break; /* * Handled above: EVENT_META_TRACK_NAME */ case EVENT_META_TEXT_EVENT: /* FF 01 len text */ case EVENT_META_COPYRIGHT: /* FF 02 ... */ case EVENT_META_INSTRUMENT: /* FF 04 ... */ case EVENT_META_LYRIC: /* FF 05 ... */ case EVENT_META_MARKER: /* FF 06 ... */ case EVENT_META_CUE_POINT: /* FF 07 ... */ if (checklen(len, mtype)) { midibytes mt; for (int i = 0; i < int(len); ++i) { char ch = char(read_byte()); if (i < int(c_meta_text_limit)) mt.push_back(ch); } bool ok = e.append_meta_data(mtype, mt); if (ok) { if (s.append_event(e)) { bool get_song_info = trk == 0 && mtype == EVENT_META_TEXT_EVENT && ! got_song_info; if (get_song_info) { got_song_info = true; p.song_info(e.get_text()); } ++evcount; } } } else return false; break; case EVENT_META_MIDI_CHANNEL: /* FF 20 01 cc */ case EVENT_META_MIDI_PORT: /* FF 21 01 pp */ case EVENT_META_SMPTE_OFFSET: /* FF 54 03 t t t */ /* * The first two events are obsolete, the SMPTE is * not supported. But we append them anyway to * preserve, somewhat, the original file. * read_meta_data() appends the event. */ if (read_meta_data(s, e, mtype, len)) ++evcount; break; default: if (rc().verbose()) { std::string m = "Illegal meta value skipped"; (void) set_error_dump(m); } break; } } else if (status == EVENT_MIDI_SYSEX) /* 0xF0 len syx */ { len = read_varinum(); if (read_sysex_data(s, e, len)) ++evcount; } else if (status == EVENT_MIDI_SYSEX_END) /* 0xF7 contin */ { len = read_varinum(); if (read_sysex_data(s, e, len, true)) ++evcount; } else { (void) set_error_dump ( "Unexpected meta code", midilong(status) ); } break; default: if (! error_reported) { /* * Some files (e.g. 2rock.mid, which has "00 24 40" * hanging out there all alone at offset 0xba) have * junk in them. Others (trilogy.mid) try to use * running status after a SysEx event. */ std::string msg = "Bad event"; skip_to_end = track_error(msg, trk); if (m_running_status_action == rsaction::abort) return true; /* don't process more tracks */ else error_reported = true; } break; } if (at_end()) break; } /* while loading Trk chunks */ /* * Sequence has been filled, add it to the performance or SMF 0 * splitter. If there was no sequence number embedded in the * track, use the for-loop track number. It's not fool-proof. * "If the ID numbers are omitted, the sequences' locations in * order in the file are used as defaults." */ if (at_end() && ! done) /* done == end-of-track found */ { std::string msg = "Premature end-of-file"; (void) track_error(msg, trk); if (m_running_status_action == rsaction::abort) break; } /* * The sequence number is an optional "event". If not present, * then we use the track counter. Also, if the file is SMF 0, * we don't want to save the track number (which alters the * file, so we might need to reconsider how Seq66 works * in this situation). Instead we set it to 0. */ if (seqnum == c_midishort_max) seqnum = trk; if (m_file_format == 0) seqnum = 0; if (seqnum < c_prop_seq_number) { s.set_midi_channel(tentative_channel); if (! is_null_buss(buss_override)) (void) s.set_midi_bus(buss_override); if (rc().verbose()) { char temp[64]; snprintf ( temp, sizeof temp, "%d events in track %d", evcount, trk ); info_message(temp); } /* * If we haven't read a time-signature event or a * c_timesig SecSpec, get the main time signature in * place now. This should be done only for the first * track; we don't want to add time signatures to the * other tracks if they don't have one. */ if (set_main_timesig && trk == 0) (void) s.set_main_time_signature(); if (convert_smf0) { (void) m_smf0_splitter.log_main_sequence(s, seqnum); } else { bool success = finalize_sequence(p, s, seqnum, screenset); if (success) { if (! at_end()) /* still more data to read */ { // printf("More data!\n"); } } } } } else { if (trk > 0) /* non-fatal later */ { (void) set_error_dump("Bad track ID", ID); break; } else /* fatal in 1st one */ { result = set_error_dump("First track has bad ID", ID); break; } skip(TrackLength); } } /* for each track */ return result; } sequence * midifile::create_sequence (performer & p) { sequence * result = new (std::nothrow) sequence(ppqn()); if (not_nullptr(result)) { mastermidibus * masterbus = p.master_bus(); if (not_nullptr(masterbus)) result->set_master_midi_bus(masterbus); /* set master buss */ } return result; } /** * Sets some MIDI parameters for a new sequence, then calls * performer::install_sequence() to add the sequence pointer to the set-manager * and hook it into the master bus. */ bool midifile::finalize_sequence ( performer & p, sequence & s, int seqnum, int screenset ) { int preferred_seqnum = seqnum + screenset * p.screenset_size(); if (rc().investigate()) s.show_events(); return p.install_sequence(&s, preferred_seqnum, true); } /** * Parse the proprietary header for sequencer-specific data. The new format * creates a final track chunk, starting with "MTrk". Then comes the * delta-time (here, 0), and the event. An event is a MIDI event, a SysEx * event, or a Meta event. * * A MIDI Sequencer Specific meta message includes either a delta time or * absolute time, and the MIDI Sequencer Specific event encoded as * follows: * \verbatim 0x00 0xFF 0x7F length data \endverbatim * * For convenience, this function first checks the amount of file data left. * If enough, then it reads a long value. If the value starts with 0x00 0xFF * 0x7F, then that is a SeqSpec event, which signals usage of the new * Seq66 "proprietary" format. Otherwise, it is probably the old * format, and the long value is a control tag (0x242400nn), which can be * returned immedidately. * * If it is the new format, we back up to the FF, then get the next byte, * which should be a 7F. If so, then we read the length (a variable * length value) of the data, and then read the long value, which should * be the control tag, which, again, is returned by this function. * * \note * Most sequencers seem to be tolerant of both the lack of an "MTrk" * marker and of the presence of an unwrapped control tag, and so can * handle both the old and new formats of the final proprietary track. * * \param file_size * The size of the data file. This value is compared against the * member m_pos (the position inside m_data[]), to make sure there is * enough data left to process. * * \return * Returns the control-tag value found. These are the values, such as * c_midichannel, found in the midi_vector_base module, that indicate the * type of sequencer-specific data that comes next. If there is not * enough data to process, then 0 is returned. */ midilong midifile::parse_seqspec_header (int file_size) { midilong result = 0; if ((file_size - m_pos) > int(sizeof(midilong))) { result = read_long(); /* status (new), or C_tag */ midibyte status = (result & 0x00FF0000) >> 16; /* 2-byte shift */ if (status == EVENT_MIDI_META) /* 0xFF meta marker */ { back_up(2); /* back up to meta type */ midibyte type = read_byte(); /* get meta type */ if (type == EVENT_META_SEQSPEC) /* 0x7F event marker */ { (void) read_varinum(); /* prop section length */ result = read_long(); /* control tag */ } else if (type == EVENT_META_END_OF_TRACK) { msgprintf ( msglevel::warn, "End-of-track, offset ~0x%lx", long(m_pos) ); } else { msgprintf ( msglevel::error, "Unexpected meta type 0x%x offset ~0x%lx", int(type), long(m_pos) ); } } } return result; } /** * After all of the conventional MIDI tracks are read, we're now at the * "proprietary" Seq24 data section, which describes the various features * that Seq24 supports. It consists of series of tags, layed out in the * midi_vector_base.hpp header file (search for c_mutegroups, for example). * * The format is (1) tag ID; (2) length of data; (3) the data. * * First, we separated out this function for a little more clarity. Then we * added code to handle reading both the legacy Seq24 format and the new, * MIDI-compliant format. Note that even the new format is not quite * correct, since it doesn't handle a MIDI manufacturer's ID, making it a * single byte that is part of the data. But it does have the "MTrk" marker * and track name, so that must be processed for the new format. * * Now, in our "midicvt" project, we have a test MIDI file, * b4uacuse-non-mtrk.midi that is good, except for having a tag "MUnk" * instead of "MTrk". We should consider being more permissive, if possible. * Otherwise, though, the only penality is that the "proprietary" chunk is * completely skipped. * * Extra precision BPM: * * Based on a request for two decimals of precision in beats-per-minute, we * now save a scaled version of BPM. Our supported range of BPM is 2.0 to * 600.0. If this range is encountered, the value is read as is. If greater * than this range (actually, we use 999 as the limit), then we divide the * number by 1000 to get the actual BPM, which can thus have more precision * than the old integer value allowed. Obviously, when saving, we will * multiply by 1000 to encode the BPM. * * \param p * The performance object that is being set via the incoming MIDI file. * * \param file_size * The file size as determined in the parse() function. * * There are also implicit parameters, with the m_pos member * variable. */ bool midifile::parse_seqspec_track (performer & p, int file_size) { bool result = true; midilong ID = read_long(); /* Get ID + Length */ if (ID == c_prop_chunk_tag) /* magic number 'MTrk' */ { midilong tracklength = read_long(); if (tracklength > 0) { /* * The old number, 0x7777, is now 0x3FFF. We don't want to * startle people, so we will silently ignore (and replace upon * saving) this number. */ int sn = read_seq_number(); bool ok = (sn == c_prop_seq_number) || (sn == c_prop_seq_number_old); if (ok) /* sanity check */ { std::string trackname = read_track_name(); result = ! trackname.empty(); /* * This "sanity check" is probably a bit much. It causes * errors in Sequencer24 tracks, which are otherwise fine * to scan in the new format. Let the "MTrk" and 0x3FFF * markers be enough. * * if (trackname != c_prop_track_name) * result = false; */ } else if (sn == (-1)) { m_error_is_fatal = false; result = set_error_dump ( "No track number in SeqSpec, extra data" ); } else result = append_error("Unexpected track number in SeqSpec."); } } else back_up(4); /* unread the "ID code" */ if (result) result = prop_header_loop(p, file_size); return result; } /** * This section used to depend on the ordering and presence of all supported * SeqSpecs, and hence was kind of brittle. Now we loop here and use a * switch-statement to figure out which code to execute. * * Seq24 would store the MIDI control setting in the MIDI file. While this * could be a useful feature, it seems a bit confusing, since the * user/musician will more likely define those controls for his set of * equipment to apply to all songs. * * Furthermore, we would need to load these control settings into a * midicontrolin (see ctrl/midicontrolin modules). And, lastly, Seq24 never * wrote these controls to the file. It merely wrote the c_midictrl code, * followed by a long 0. For now, we are going to evade this functionality. * We will continue to write this section, and try to read it, but expect it * to be empty. * * Track-specific SeqSpecs handled in parse_smf_1(): * * c_midibus c_timesig c_midichannel c_musickey * * c_musicscale * c_backsequence * c_transpose * c_seq_color * c_seq_loopcount c_triggers c_triggers_ex c_trig_transpose * * Global SeqSpecs handled here: * * c_midictrl c_midiclocks c_notes c_bpmtag * c_mutegroups c_musickey * c_musicscale * * c_backsequence * c_perf_bp_mes c_perf_bw c_tempo_map ! * c_midiinbus ! c_tempo_track * * Not handled: * * c_gap_A to _F c_reserved_3 c_reserved_4 c_seq_edit_mode */ bool midifile::prop_header_loop (performer & p, int file_size) { bool ok = true; while (ok) { midilong seqspec = parse_seqspec_header(file_size); ok = seqspec > 0; if (ok) { switch (seqspec) { case c_midictrl: ok = parse_c_midictrl(p); break; case c_midiclocks: ok = parse_c_midiclocks(p); break; case c_notes: ok = parse_c_notes(p); break; case c_bpmtag: ok = parse_c_bpmtag(p); break; case c_mutegroups: (void) parse_c_mutegroups(p); break; case c_musickey: ok = parse_c_musickey(); break; case c_musicscale: ok = parse_c_musicscale(); break; case c_backsequence: ok = parse_c_backsequence(); break; case c_perf_bp_mes: ok = parse_c_perf_bp_mes(p); break; case c_perf_bw: ok = parse_c_perf_bw(p); break; #if defined USE_SEQ32_SEQSPECS /* stazed features not implemented */ case c_tempo_map: break; #endif case c_musicchord: ok = parse_c_musicchord(); break; /* * The following are handled in the event-read loop, or are * not handled: * * case c_reserved_2: (unhandled) * case c_seq_color: * case c_seq_edit_mode: (unhandled) * case c_seq_loopcount: * case c_reserved_3: (unhandled) * case c_reserved_4: (unhandled) * case c_trig_transpose: */ case c_tempo_track: ok = parse_c_tempo_track(); break; default: break; } } } return true; } /* * Some old code wrote some bad files, we need to work around that and fix it. * The value of max_sequence() is generally 1024. Note that we no longer * support setting up MIDI control in a song. Too mysterious, and the 'ctrl' * file is far more powerful (and handles keystrokes at the same time). */ bool midifile::parse_c_midictrl (performer & /* p*/) { int ctrls = int(read_long()); if (ctrls > usr().max_sequence()) { back_up(4); (void) set_error_dump ( "Bad MIDI-control sequence count, fixing.\n" "Save the file now!", midilong(ctrls) ); ctrls = midilong(read_byte()); } midibyte a[8]; for (int i = 0; i < ctrls; ++i) { read_byte_array(a, 6); read_byte_array(a, 6); read_byte_array(a, 6); } return true; } /* * Some old code wrote some bad files, we work around and fix it. */ bool midifile::parse_c_midiclocks (performer & p) { int busscount = int(read_long()); mastermidibus * masterbus = p.master_bus(); if (not_nullptr(masterbus)) { for (int buss = 0; buss < busscount; ++buss) { bussbyte clocktype = read_byte(); masterbus->set_clock ( bussbyte(buss), static_cast(clocktype) ); } } return true; } /* * The default number of sets is c_max_sets = 32. This is also the initial * minimum number of screen-set names, though some can be empty. More than 32 * sets can be supported, though that claim is currently untested. The * highest set number is determined by the highest pattern number; the * setmaster::add_set() function creates a new set when a pattern number falls * outside the boundaries of the existing sets. */ bool midifile::parse_c_notes (performer & p) { midishort screen_sets = read_short(); for (midishort x = 0; x < screen_sets; ++x) { midishort len = read_short(); /* string size */ std::string notess; for (midishort i = 0; i < len; ++i) notess += read_byte(); /* unsigned! */ p.screenset_name(x, notess, true); /* load time */ } return true; } /* * Should check here for a conflict between the Tempo meta event and this * tag's value? No. Also, as of 2017-03-22, we want to be able to * handle two decimal points of precision in BPM. See the prop_header_loop() * function banner for a discussion. */ bool midifile::parse_c_bpmtag (performer & p) { midilong longbpm = read_long(); midibpm bpm = usr().unscaled_bpm(longbpm); if (m_main_bpm == 0.0) { p.set_beats_per_minute(bpm); /* 2nd setter */ m_main_bpm = bpm; } return true; } /** * Read in the mute group information. If the length of the mute group * section is 0, then this file is a Seq42 file, and we ignore the section. * (Thanks to Stazed for that catch!) * * Updated mute-groups parsing that supports the old style "32 x 32" * mutegroups, and more variable setup. Lots to do yet! * * Also need to check for any mutes being present. * * What about rows & columns? Ultimately, the set-size must match that * specified by the application's user-interface as must the rows and * columns. * * New: We write the group name. So the new format of mute-groups is: * * -# Split byte count for the number of groups and the size of each * group. Long, 4 bytes. * -# 32 groups. Each of size roughly 43 bytes. * -# Group number. Byte value; 1 byte * -# Mute-group bit values, 1 byte each, and group-size of them, * i.e 32 bytes. * -# Optional: The mute-group name in double quotes. Roughly 10 * bytes by default. * -# 1376 total size (32 x 43) roughly. This is 4096 - 1376 = 2720 * bytes saved. * * One sample file is 2840 bytes shorter with the new format. What are * the extra 120 bytes (roughly) that are saved? */ bool midifile::parse_c_mutegroups (performer & p) { mutegroups & mutes = p.mutes(); bool result = mutes.group_load_from_midi(); if (result) result = ! mutes.loaded_from_mutes(); /* loaded from config? */ if (result) { unsigned groupcount, groupsize; long len = long(read_split_long(groupcount, groupsize)); if (len > 0) { bool legacyformat = len == c_legacy_mute_group; (void) p.mutes().reset_defaults(); /* makes it empty */ if (legacyformat) { result = parse_c_mutegroups_legacy(p, groupcount, groupsize); } else { std::string gname; mutes.legacy_mutes(false); for (unsigned g = 0; g < groupcount; ++g) { midibooleans mutebits; midilong group = read_byte(); gname.clear(); for (unsigned s = 0; s < groupsize; ++s) { midibyte gmutestate = read_byte(); /* byte for bit */ bool status = gmutestate != 0; mutebits.push_back(midibool(status)); } char letter = (char) read_byte(); if (letter == '"') /* next a quote? */ { for (;;) { char letter = (char) read_byte(); if (letter == '"') break; else gname += letter; } } else --m_pos; /* put it back */ if (mutes.load(group, mutebits)) { /* * Related to issue #87. */ mutes.group_name(group, gname); if (mutebits.size() != size_t(p.group_count())) { mutebits = fix_midibooleans ( mutebits, p.group_count() ); rc().auto_mutes_save(true); } } else break; /* a duplicate */ } } } } return result; } /** * Old format: * * -# 32 groups. * -# 32 groupmutes states, each 4 bytes = 128 bytes * -# 4096 total size (32 x 128) */ bool midifile::parse_c_mutegroups_legacy ( performer & p, unsigned groupcount, unsigned groupsize ) { bool result = true; mutegroups & mutes = p.mutes(); mutes.legacy_mutes(true); for (unsigned g = 0; g < groupcount; ++g) { midibooleans mutebits; midilong group = read_long(); for (unsigned s = 0; s < groupsize; ++s) { midilong gmutestate = read_long(); /* long bit !? */ bool status = gmutestate != 0; mutebits.push_back(midibool(status)); } if (mutes.load(group, mutebits)) { /* * Related to issue #87. */ if (mutebits.size() != size_t(p.group_count())) { mutebits = fix_midibooleans ( mutebits, p.group_count() ); rc().auto_mutes_save(true); } } else { result = false; break; /* a duplicate? */ } } return result; } /* * We let Seq66 read this new stuff even the global-background sequence is in * force. That flag affects only the writing of the MIDI file, not the * reading. */ bool midifile::parse_c_musickey () { int key = int(read_byte()); usr().seqedit_key(key); return true; } bool midifile::parse_c_musicscale () { int scale = int(read_byte()); usr().seqedit_scale(scale); return true; } /** * There is currently no "global" version of this setting. * That would make no sense. */ bool midifile::parse_c_musicchord () { (void) read_byte(); // int scale = int(read_byte()); // usr().seqedit_scale(scale); return true; } bool midifile::parse_c_backsequence () { int seqnum = int(read_long()); usr().seqedit_bgsequence(seqnum); return true; } /* * Store the beats/measure and beat-width values from the perfedit window. * * We should also calculate: * * performer::clocks_per_metronome(cc); * performer::set_32nds_per_quarter(bb); */ bool midifile::parse_c_perf_bp_mes (performer & p) { int bpmes = int(read_long()); p.set_beats_per_bar(bpmes); return true; } bool midifile::parse_c_perf_bw (performer & p) { int bw = int(read_long()); p.set_beat_width(bw); return true; } /* * If this value is present and greater than 0, set it into the performance. * It might override the value specified in the "rc" configuration file. */ bool midifile::parse_c_tempo_track () { int tempotrack = int(read_long()); if (tempotrack >= 0) rc().tempo_track_number(tempotrack); return true; } /** * For each groups in the mute-groups, write the status bits to the * c_mutegroups SeqSpec. * * The mutegroups class has rows and columns for each group, but doesn't have * a way to iterate through all the groups. * * The format of the c_mutegroups section: * * a. 32 mute-group specifications. * b. Each specification has the format: * - 1 byte for group number. * - 1 byte per pattern status (usually 32 patterns). * - 1 double-quote, any name bytes if present, and 1 double quote. * * By default, then, the size of each group, ignoring the group name, is * 1 + 32, so the total size is 32 * 33 = 1056. * * The default names are "Group 0" (10 * 9 bytes) through "Group 31" (22 * 10 * bytes which is 310 bytes. So the default total is 1366 bytes. */ bool midifile::write_c_mutegroups (const performer & p) { const mutegroups & mutes = p.mutes(); bool result = mutes.saveable_to_midi(); if (result) { if (rc().save_old_mutes()) { for (const auto & stz : mutes.list()) { int groupnumber = stz.first; const mutegroup & m = stz.second; midibooleans mutebits = m.get(); result = mutebits.size() > 0; if (result) { write_long(groupnumber); for (auto mutestatus : mutebits) write_long(bool(mutestatus) ? 1 : 0); /* waste! */ } else break; } } else { for (const auto & stz : mutes.list()) { int groupnumber = stz.first; const mutegroup & m = stz.second; midibooleans mutebits = m.get(); size_t bitcount = mutebits.size(); result = bitcount > 0; if (result) { write_byte(groupnumber); for (auto mutestatus : mutebits) write_byte(bool(mutestatus) ? 1 : 0); /* better! */ std::string gname = m.name(); write_byte(midibyte('"')); if (! gname.empty()) { for (auto ch : gname) write_byte(midibyte(ch)); } write_byte(midibyte('"')); } else break; } } } return result; } /** * Writes 4 bytes, each extracted from the long value and shifted rightward * down to byte size, using the write_byte() function. * * \param x * The long value to be written to the MIDI file. */ void midifile::write_long (midilong x) { write_byte((x & 0xFF000000) >> 24); write_byte((x & 0x00FF0000) >> 16); write_byte((x & 0x0000FF00) >> 8); write_byte((x & 0x000000FF)); } /** * Writes 4 bytes in the style readable by read_split_long(). If the values * are both 32 (note that 32 x 32 = 1024), then 1024, the legacy value, is * written out in the normal way, via write_long(). Otherwise, the highbytes * and lowbytes values are each written out in two bytes each, with the most * significant byte written first. * * \param [out] highbytes * Provides the value of the most significant 2 bytes of the four-byte * ("long") value. The most significant bytes are masked out; the values * are limited to range from 0 to 65535 = 0xFFFF, a short value. For * mute-groups, this is the number of mute-groups. * * \param [out] lowbytes * Provides the value of the least significant 2 bytes of the four-byte * ("long") value. The most significant bytes are masked out; the values * are limited to the same range as the \a highbytes parameter. For * mute-groups, this is the number of patterns in the mute-groups. * * \param oldstyle * If true (the default is false), then just write a long value of 1024. * Otherwise, the bytes are masked, shifted, and written. */ void midifile::write_split_long (unsigned highbytes, unsigned lowbytes, bool oldstyle) { if (oldstyle) { write_long(c_legacy_mute_group); /* 1024, long-standing Seq24 value */ } else { write_byte((highbytes & 0x0000FF00) >> 8); write_byte(highbytes & 0x000000FF); write_byte((lowbytes & 0x0000FF00) >> 8); write_byte(lowbytes & 0x000000FF); } } /** * Writes 3 bytes, each extracted from the long value and shifted rightward * down to byte size, using the write_byte() function. * * This function is kind of the reverse of tempo_us_to_bytes() defined in the * calculations.cpp module. * * \warning * This code looks endian-dependent. * * \param x * The long value to be written to the MIDI file. */ void midifile::write_triple (midilong x) { write_byte((x & 0x00FF0000) >> 16); write_byte((x & 0x0000FF00) >> 8); write_byte((x & 0x000000FF)); } /** * Writes 2 bytes, each extracted from the long value and shifted rightward * down to byte size, using the write_byte() function. * * \warning * This code looks endian-dependent. * * \param x * The short value to be written to the MIDI file. */ void midifile::write_short (midishort x) { write_byte((x & 0xFF00) >> 8); write_byte((x & 0x00FF)); } /** * Writes a MIDI Variable-Length Value (VLV), which has a variable number * of bytes. * * A MIDI file Variable Length Value is stored in bytes. Each byte has * two parts: 7 bits of data and 1 continuation bit. The highest-order * bit is set to 1 if there is another byte of the number to follow. The * highest-order bit is set to 0 if this byte is the last byte in the * VLV. * * To recreate a number represented by a VLV, first you remove the * continuation bit and then concatenate the leftover bits into a single * number. * * To generate a VLV from a given number, break the number up into 7 bit * units and then apply the correct continuation bit to each byte. * Basically this is a base-128 system where the digits range from 0 to 7F. * * In theory, you could have a very long VLV number which was quite * large; however, in the standard MIDI file specification, the maximum * length of a VLV value is 5 bytes, and the number it represents can not * be larger than 4 bytes. * * Here are some common cases: * * - Numbers between 0 and 127 are represented by a single byte: * 0x00 to 7F. * - 0x80 is represented as 0x81 00. The first number is * 10000001, with the left bit being the continuation bit. * The rest of the number is multiplied by 128, and the second byte * (0) is added to that. So 0x82 would be 0x81 0x01 * - The largest 2-byte MIDI value (e.g. a sequence number) is * 0xFF 7F, which is 127 * 128 + 127 = 16383 = 0x3FFF. * - The largest 3-byte MIDI value is 0xFF FF 7F = 2097151 = 0x1FFFFF. * - The largest number, 4 bytes, is 0xFF FF FF 7F = 536870911 = * 0xFFFFFFF. * * Also see the varinum_size() and midi_vector_base::add_variable() functions. * * \param value * The long value to be encoded as a MIDI varinum, and written to the * MIDI file. */ void midifile::write_varinum (midilong v) { midilong buffer = v & 0x7f; while ((v >>= 7) > 0) { buffer <<= 8; buffer |= 0x80; buffer += (v & 0x7f); } for (;;) { write_byte(midibyte(buffer & 0xff)); if (buffer & 0x80) /* continuation bit? */ buffer >>= 8; /* yes, get next MSB */ else break; /* no, we are done */ } } /** * Calculates the length of a variable length value (VLV). This function is * needed when calculating the length of a track. Note that it handles * only the following situations: * * https://en.wikipedia.org/wiki/Variable-length_quantity * * This restriction allows the calculation to be simple and fast. * \verbatim 1 byte: 0x00 to 0x7F 2 bytes: 0x80 to 0x3FFF 3 bytes: 0x4000 to 0x001FFFFF 4 bytes: 0x200000 to 0x0FFFFFFF \endverbatim * * \param len * The long value whose length, when encoded as a MIDI varinum, is to be * found. * * \return * Returns values as noted above. Anything beyond that range returns * 0. */ int midifile::varinum_size (long len) const { int result = 0; if (len >= 0x00 && len < 0x80) result = 1; else if (len >= 0x80 && len < 0x4000) result = 2; else if (len >= 0x4000 && len < 0x200000) result = 3; else if (len >= 0x200000 && len < 0x10000000) result = 4; return result; } /** * We want to write: * * - 0x4D54726B. * The track tag "MTrk". The MIDI spec requires that software can skip * over non-standard chunks. "Prop"? Would require a fix to midicvt. * - 0xaabbccdd. * The length of the track. This needs to be calculated somehow. * - 0x00. A zero delta time. * - 0x7f7f. Sequence number, a special value, well out of normal range. * - The name of the track: * - "Seq24-Spec" * - "Seq66-S" * * Then follows the proprietary/SeqSpec data, written in the normal manner. * Finally, tack on the track-end meta-event. * * Components of final track size: * * -# Delta time. 1 byte, always 0x00. * -# Sequence number. 5 bytes. OPTIONAL. We won't write it. * -# Track name. 3 + 10 or 3 + 15 * -# Series of proprietary/SeqSpec specs: * -# Prop header: * -# If legacy [obsolete] format, 4 bytes. * -# Otherwise, 2 bytes + varinum_size(length) + 4 bytes. * -# Length of the prop data. * -# Track End. 3 bytes. * * \param numtracks * The number of tracks to be written. For SMF 0 this should be 1. * * \param smfformat * The SMF value to write. Defaults to 1. */ bool midifile::write_header (int numtracks, int smfformat) { midishort fmt = smfformat > 0 ? 1 : 0 ; write_long(0x4D546864); /* MIDI Format 1 header MThd */ write_long(6); /* Length of the header */ write_short(fmt); /* MIDI Format 1 (or 0) */ write_short(numtracks); /* number of tracks */ write_short(m_ppqn); /* parts per quarter note */ return numtracks > 0; } #if defined SEQ66_USE_WRITE_START_TEMPO // undefined /** * Writes the initial or only tempo, occurring at the beginning of a MIDI * song. Compare this function to midi_vector_base::fill_time_sig_and_tempo(). * * \param start_tempo * The beginning tempo value. */ void midifile::write_start_tempo (midibpm start_tempo) { write_byte(0x00); /* delta time at beginning */ write_short(0xFF51); write_byte(0x03); /* message length, must be 3 */ write_triple(midilong(60000000.0 / start_tempo)); } #endif // SEQ66_USE_WRITE_START_TEMPO #if defined SEQ66_USE_WRITE_TIME_SIG // undefined /** * Writes the main time signature, in a more simplistic manner than * midi_vector_base::fill_time_sig_and_tempo(). * * Note that the cc value (MIDI ticks per metronome click) is hardwired to * 0x18 (24) and the bb value (32nd notes per quarter note) is hardwired * to 0x08 (8). * * \param beatsperbar * The numerator of the time signature. * * \param beatwidth * The denominator of the time signature. */ void midifile::write_time_sig (int beatsperbar, int beatwidth) { write_byte(0); /* delta time at beginning */ write_byte(EVENT_MIDI_META); /* 0xFF meta marker */ write_byte(EVENT_META_TIME_SIGNATURE); /* 0x58 */ write_byte(4); /* the message length */ write_byte(beatsperbar); /* nn */ write_byte(beat_log2(beatwidth)); /* dd as the exponent of 2 */ write_short(0x1808); /* cc bb TODO! */ } #endif // SEQ66_USE_WRITE_TIME_SIG /** * Writes a "proprietary" (SeqSpec) Seq24 footer header in the new * MIDI-compliant format. This function does not write the data. It * replaces calls such as "write_long(c_midichannel)" in the proprietary * secton of write(). * * The new format writes 0x00 0xFF 0x7F len 0x242400xx; the first 0x00 is the * delta time. * * In the new format, the 0x24 is a kind of "manufacturer ID". At * http://www.midi.org/techspecs/manid.php we see that most manufacturer IDs * start with 0x00, and are thus three bytes long, or start with codes at * 0x40 and above. Similary, this site shows that no manufacturer uses 0x24: * * http://sequence15.blogspot.com/2008/12/midi-manufacturer-ids.html * * \warning * Currently, the manufacturer ID is not handled; it is part of the * data, which can be misleading in programs that analyze MIDI files. * * \param control_tag * Determines the type of sequencer-specific section to be written. * It should be one of the value in the globals module, such as * c_midibus or c_mutegroups. * * \param data_length * The amount of data that will be written. This parameter does not * count the length of the header itself. */ void midifile::write_seqspec_header (midilong control_tag, long len) { write_byte(0); /* delta time */ write_byte(EVENT_MIDI_META); /* 0xFF meta marker */ write_byte(EVENT_META_SEQSPEC); /* 0x7F sequencer-specific mark */ write_varinum(len + 4); /* data + sizeof(control_tag); */ write_long(control_tag); /* use legacy output call */ } /** * Write a MIDI track to the file. * * \param lst * The MIDI vector containing the events. */ void midifile::write_track (const midi_vector & lst) { midilong tracksize = midilong(lst.size()); write_long(c_mtrk_tag); /* magic number 'MTrk' */ write_long(tracksize); while (! lst.done()) /* write the track data */ write_byte(lst.get()); } /** * Calculates the size of a proprietary item, as written by the * write_seqspec_header() function, plus whatever is called to write the * data. If using the new format, the length includes the sum of * sequencer-specific tag (0xFF 0x7F) and the size of the variable-length * value. Then, for the new format, 4 bytes are added for the Seq24 MIDI * control value, and then the data length is added. * * \param data_length * Provides the data length value to be encoded. * * \return * Returns the length of the item size, including the delta time, meta * bytes, length byes, the control tag, and the data-length itself. */ int midifile::prop_item_size (long data_length) const { long result = 0; int len = data_length + 4; /* data + sizeof(control_tag); */ result += 3; /* count delta time, meta bytes */ result += varinum_size(len); /* count the length bytes */ result += 4; /* write_long(control_tag); */ result += data_length; /* add the data size itself */ return result; } /** * Write the whole MIDI data and Seq24 information out to the file. * Also see the write_song() function, for exporting to standard MIDI. * * Seq66 sometimes reverses the order of some events, due to popping * from its container. Not an issue, but can make a file slightly different * for no reason. * * \param p * Provides the object that will contain and manage the entire * performance. * * \param doseqspec * If true (the default, then the Seq66-specific SeqSpec sections * are written to the file. If false, we want to export the tracks as a * basic MIDI sequence (which is not the same as exporting a Song, with * triggers, as a MIDI sequence). * * \return * Returns true if the write operations succeeded. If false is returned, * then m_error_message will contain a description of the error. */ bool midifile::write (performer & p, bool doseqspec) { automutex locker(m_mutex); bool result = usr().is_ppqn_valid(m_ppqn); m_error_message.clear(); if (! result) m_error_message = "Invalid PPQN for MIDI file."; if (result) { int numtracks = 0; int sequencehigh = p.sequence_high(); if (rc().verbose()) { infoprintf("Highest track is %d", sequencehigh - 1); } for (int i = 0; i < sequencehigh; ++i) { if (p.is_seq_active(i)) ++numtracks; /* count number of active tracks */ } result = numtracks > 0; if (result) { int smfformat = p.smf_format(); bool result = write_header(numtracks, smfformat); if (result) { std::string temp = "Writing "; temp += doseqspec ? "Seq66" : "Normal" ; temp += " SMF "; temp += std::to_string(smfformat); temp += " MIDI file "; temp += std::to_string(m_ppqn); temp += " PPQN"; file_message(temp, m_name); } else m_error_message = "Failed to write header."; } else m_error_message = "No patterns/tracks to write."; } /* * Write out the active tracks. * Note that we don't need to check the sequence pointer. */ if (result) { for (int track = 0; track < p.sequence_high(); ++track) { if (p.is_seq_active(track)) { seq::pointer s = p.get_sequence(track); if (s) { sequence & seq = *s; midi_vector lst(seq); /* * midi_vector_base::fill() also handles the time-signature * and tempo meta events, if they are not part of the * file's MIDI data. All the events are put into the * container, and then the container's bytes are written * out below. */ lst.fill(track, p, doseqspec); write_track(lst); } } } } if (result && doseqspec) { result = write_seqspec_track(p); if (! result) m_error_message = "Could not write SeqSpec."; } if (result) { std::ofstream file ( m_name.c_str(), std::ios::out | std::ios::binary | std::ios::trunc ); if (file.is_open()) { char file_buffer[c_midi_line_max]; /* enable bufferization */ file.rdbuf()->pubsetbuf(file_buffer, sizeof file_buffer); for (auto c : m_char_list) /* list of midibytes */ { char kc = char(c); file.write(&kc, 1); if (file.fail()) { m_error_message = "Error writing byte."; result = false; } } m_char_list.clear(); } else { m_error_message = "Failed to open MIDI file to write."; result = false; } } if (result) p.unmodify(); /* it worked, tell performer about it */ return result; } /** * Write the whole MIDI data and Seq24 information out to the file. * Also see the write_song() function, for exporting to standard MIDI. * * Seq66 sometimes reverses the order of some events, due to popping * from its container. Not an issue, but can make a file slightly different * for no reason. * * \param p * Provides the object that will contain and manage the entire * performance. * * \param track * The sequence number of the single track to write. * * \return * Returns true if the write operations succeeded. If false is returned, * then m_error_message will contain a description of the error. */ bool midifile::write_one_pattern (performer & p, int track) { automutex locker(m_mutex); int sequencehigh = p.sequence_high(); bool result = track >= 0 && track < sequencehigh; if (result) result = p.is_seq_active(track); m_error_message.clear(); if (result) { int smfformat = p.smf_format(); bool result = write_header(1, smfformat); if (result) { std::string temp = "Writing "; temp += "Seq66 track "; temp += std::to_string(track); file_message(temp, m_name); } else m_error_message = "Failed to write MIDI header."; } else m_error_message = "No such pattern/track to write."; if (result) { /* * Once we get the sequence, we need its sequence number to be * zero. */ seq::pointer s = p.get_sequence(track); if (s) { int track = 0; sequence & seq = *s; seq.seq_number(track); midi_vector lst(seq); lst.fill(track, p, true); write_track(lst); } } if (result) { result = write_seqspec_track(p); if (! result) m_error_message = "Could not write SeqSpec."; } if (result) { std::ofstream file ( m_name.c_str(), std::ios::out | std::ios::binary | std::ios::trunc ); if (file.is_open()) { char file_buffer[c_midi_line_max]; /* enable bufferization */ file.rdbuf()->pubsetbuf(file_buffer, sizeof file_buffer); for (auto c : m_char_list) /* list of midibytes */ { char kc = char(c); file.write(&kc, 1); if (file.fail()) { m_error_message = "Error writing byte."; result = false; } } m_char_list.clear(); } else { m_error_message = "Failed to open MIDI file to write."; result = false; } } return result; } /** * Write the whole MIDI data and Seq24 information out to a MIDI file, writing * out patterns based on their song/performance information (triggers) and * ignoring any patterns that are muted. * * We get the number of active tracks, and we don't count tracks with no * triggers, or tracks that are muted. * * This function, write_song(), was not included in Seq24 because it * Seq24 writes standard MIDI files (with SeqSpec information that a * decent sequencer should ignore). But we believe this is a good feature * for export, and created the Export Song command to do this. The * write_song() function doesn't count tracks that are muted or that have no * triggers. For sequences that have triggers, it adds the events in order, * to create a long sequence that plays as if the triggers are present. * * Stazed/Seq32: * * The sequence trigger is not part of the standard MIDI format and is * proprietary to seq32/seq66. It is added here because the trigger * combining has an alternative benefit for editing. The user can split, * slice and rearrange triggers to form a new sequence. Then mute all * other tracks and export to a temporary MIDI file. Now they can import * the combined triggers/sequence as a new item. This makes editing of * long improvised sequences into smaller or modified sequences as well as * combining several sequence parts painless. Also, if the user has a * variety of common items such as drum beats, control codes, etc that can * be used in other projects, this method is very convenient. The common * items can be kept in one file and exported all, individually, or in * part by creating triggers and muting. * * Write out the exportable tracks. Note that we don't need to check the * sequence pointer. We need to use the same criterion we used to count the * tracks in the first place, not just if the track is active and unmuted. * Also, since we already know that an exportable track is valid, no need to * check for a null pointer. * * For each trigger in the sequence, add events to the list below; fill * one-by-one in order, creating a single long sequence. Then set a single * trigger for the big sequence: start at zero, end at last trigger end with * snap. We're going to reference (not copy) the triggers now, since the * write_song() function is now locked. * * The we adjust the sequence length to snap to the nearest measure past the * end. We fill the MIDI container with trigger "events", and then the * container's bytes are written. * * \param p * Provides the object that will contain and manage the entire * performance. * * \return * Returns true if the write operations succeeded. If false is returned, * then m_error_message will contain a description of the error. */ bool midifile::write_song (performer & p) { automutex locker(m_mutex); int numtracks = p.count_exportable(); bool result = numtracks > 0; m_error_message.clear(); if (result) { int midiformat = p.smf_format(); if (midiformat == 0) { if (numtracks == 1) { msgprintf ( msglevel::status, "Exporting song to SMF 0, %d ppqn", m_ppqn ); result = write_header(numtracks, midiformat); } else { result = false; m_error_message = "Song has more than one track; " "it is unsuitable for saving as SMF 0." ; } } else { msgprintf(msglevel::status, "Exporting song, %d ppqn", m_ppqn); result = write_header(numtracks, midiformat); } } else { result = false; m_error_message = "Song has no exportable tracks; " "each track to export must have triggers in the song editor " "and be unmuted." ; } if (result) { /* * Write out the exportable tracks as described in the banner. * Following stazed, we're consolidate the tracks at the beginning of * the song, replacing the actual track number with a counter that is * incremented only if the track was exportable. Note that this loop * is kind of an elaboration of what goes on in the midi_vector_base :: * fill() function for normal Seq66 file writing. */ for (int track = 0; track < p.sequence_high(); ++track) { if (p.is_exportable(track)) { seq::pointer s = p.get_sequence(track); /* guaranteed good */ sequence & seq = *s; midi_vector lst(seq); result = lst.song_fill_track(track); /* standalone fill */ if (result) write_track(lst); } } } if (result) { std::ofstream file ( m_name.c_str(), std::ios::out | std::ios::binary | std::ios::trunc ); if (file.is_open()) { char file_buffer[c_midi_line_max]; /* enable bufferization */ file.rdbuf()->pubsetbuf(file_buffer, sizeof file_buffer); for (auto c : m_char_list) { char kc = char(c); file.write(&kc, 1); } m_char_list.clear(); } else { m_error_message = "Failed to open MIDI file to export."; result = false; } } return result; } /** * Writes out the final proprietary/SeqSpec section, using the new format. * * The first thing to do, for the new format only, is calculate the length * of this big section of data. This was quite tricky; we tweaked and * adjusted until the midicvt program handled the whole new-format file * without emitting any errors. * * Here's the basics of what Seq24 did for writing the data in this part of * the file: * * -# Write the c_midictrl value, then write a 0. To us, this looks like * no one wrote any code to write this data. And yet, the parsing * code can handles a non-zero value, which is the number of sequences * as a long value, not a byte. So shouldn't we write 4 bytes, not * one? Yes, indeed, we made a mistake. However, we should be * writing out the full data set as well. But not even Seq24 does * that! Perhaps they decided it was best kept in the "rc" * configuration file. * -# MORE TO COME. * * We need a way to make the group mute data optional. Why write 4096 bytes * of zeroes? * * \param p * Provides the object that will contain and manage the entire * performance. * * \return * Always returns true. No efficient way to check all of the writes that * can happen. Might revisit this issue if some bug crops up. */ bool midifile::write_seqspec_track (performer & p) { const mutegroups & mutes = p.mutes(); long tracklength = 0; int cnotesz = 2; /* first value is short */ int highset = p.highest_set(); /* high set number re 0 */ int maxsets = c_max_sets; /* i.e. 32 */ if (highset >= maxsets) maxsets = highset + 1; for (int s = 0; s < maxsets; ++s) { if (s <= highset) /* unused tracks = no name */ { const std::string & note = p.set_name(s); cnotesz += 2 + note.length(); /* short + note length */ } } unsigned groupcount = c_max_groups; /* 32, the maximum */ unsigned groupsize = p.screenset_size(); int gmutesz = 0; if (mutes.saveable_to_midi()) { groupcount = unsigned(mutes.count()); /* includes unused groups */ groupsize = unsigned(mutes.group_count()); if (rc().save_old_mutes()) gmutesz = 4 + groupcount * (4 + groupsize * 4); /* 4-->longs */ else gmutesz = groupcount * (1 + groupsize); /* 1-->bytes */ gmutesz += mutes.group_names_letter_count(); /* "Group 22" */ } tracklength += seq_number_size(); /* bogus sequence number */ tracklength += track_name_size(c_prop_track_name); tracklength += prop_item_size(4); /* c_midictrl */ tracklength += prop_item_size(4); /* c_midiclocks */ tracklength += prop_item_size(cnotesz); /* c_notes */ tracklength += prop_item_size(4); /* c_bpmtag, beats/minute */ if (gmutesz > 0) tracklength += prop_item_size(gmutesz); /* c_mutegroups */ if (m_global_bgsequence) { tracklength += prop_item_size(1); /* c_musickey */ tracklength += prop_item_size(1); /* c_musicscale */ tracklength += prop_item_size(4); /* c_backsequence */ tracklength += prop_item_size(4); /* c_perf_bp_mes */ tracklength += prop_item_size(4); /* c_perf_bw */ tracklength += prop_item_size(4); /* c_tempo_track */ } tracklength += track_end_size(); /* Meta TrkEnd */ write_long(c_prop_chunk_tag); /* "MTrk" or something else */ write_long(tracklength); write_seq_number(c_prop_seq_number); /* bogus sequence number */ write_track_name(c_prop_track_name); /* bogus track name */ write_seqspec_header(c_midictrl, 4); /* midi control tag + 4 */ write_long(0); /* Seq24 writes only a zero */ write_seqspec_header(c_midiclocks, 4); /* bus mute/unmute data + 4 */ write_long(0); /* Seq24 writes only a zero */ write_seqspec_header(c_notes, cnotesz); /* namepad data tag + data */ write_short(maxsets); /* data, not a tag */ for (int s = 0; s < maxsets; ++s) /* see "cnotesz" calc */ { if (s <= highset) /* unused tracks = no name */ { const std::string & note = p.set_name(s); write_short(midishort(note.length())); for (unsigned n = 0; n < unsigned(note.length()); ++n) write_byte(midibyte(note[n])); } else write_short(0); /* name is empty */ } write_seqspec_header(c_bpmtag, 4); /* bpm tag + long data */ /* * We now encode the Seq66-specific BPM value by multiplying it * by 1000.0 first, to get more implicit precision in the number. * We should probably sanity-check the BPM at some point. */ midilong scaled_bpm = usr().scaled_bpm(p.get_beats_per_minute()); write_long(scaled_bpm); /* 4 bytes */ if (gmutesz > 0) { write_seqspec_header(c_mutegroups, gmutesz); /* mute groups tag etc. */ write_split_long(groupcount, groupsize, rc().save_old_mutes()); (void) write_c_mutegroups(p); } if (m_global_bgsequence) { write_seqspec_header(c_musickey, 1); /* control tag+1 */ write_byte(midibyte(usr().seqedit_key())); /* key change */ write_seqspec_header(c_musicscale, 1); /* control tag+1 */ write_byte(midibyte(usr().seqedit_scale())); /* scale change */ write_seqspec_header(c_backsequence, 4); /* control tag+4 */ write_long(long(usr().seqedit_bgsequence())); /* background */ } write_seqspec_header(c_perf_bp_mes, 4); /* control tag+4 */ write_long(long(p.get_beats_per_bar())); /* perfedit BPM */ write_seqspec_header(c_perf_bw, 4); /* control tag+4 */ write_long(long(p.get_beat_width())); /* perfedit BW */ write_seqspec_header(c_tempo_track, 4); /* control tag+4 */ write_long(long(rc().tempo_track_number())); /* perfedit BW */ write_track_end(); return true; } /** * Writes out a track name. Note that we have to precede this "event" * with a delta time value, set to 0. The format of the output is * "0x00 0xFF 0x03 len track-name-bytes". * * \param trackname * Provides the name of the track to be written to the MIDI file. */ void midifile::write_track_name (const std::string & trackname) { bool ok = ! trackname.empty(); if (ok) { write_byte(0); /* delta time */ write_byte(EVENT_MIDI_META); /* 0xFF meta tag */ write_byte(EVENT_META_TRACK_NAME); /* 0x03 second byte */ write_varinum(midilong(trackname.size())); for (int i = 0; i < int(trackname.size()); ++i) write_byte(trackname[i]); } } /** * Reads the track name. Meant only for usage in the proprietary/SeqSpec * footer track, in the new file format. * * \return * Returns the track name, or an empty string if there was a problem. */ std::string midifile::read_track_name () { std::string result; (void) read_byte(); /* throw-away delta time */ midibyte status = read_byte(); /* get the seq-spec marker */ if (status == EVENT_MIDI_META) /* 0x7F */ { if (read_byte() == EVENT_META_TRACK_NAME) /* 0x03 */ { midilong tl = int(read_varinum()); /* track length */ for (midilong i = 0; i < tl; ++i) { midibyte c = read_byte(); result += c; } } } return result; } /** * Calculates the size of a trackname and the meta event that specifies * it. * * \param trackname * Provides the name of the track to be written to the MIDI file. * * \return * Returns the length of the event, which is of the format "0x00 0xFF * 0x03 len track-name-bytes". */ long midifile::track_name_size (const std::string & trackname) const { long result = 0; if (! trackname.empty()) { result += 3; /* 0x00 0xFF 0x03 */ result += varinum_size(long(trackname.size())); /* variable length */ result += long(trackname.size()); /* data size */ } return result; } /** * Writes out a sequence number. The format is "00 FF 00 02 ss ss", where * "02" is actually the constant length of the data. We have to precede * these values with a 0 delta time, of course. * * Now, for sequence 0, an alternate format is "FF 00 00". But that * format can only occur in the first track, and the rest of the tracks then * don't need a sequence number, since it is assumed to increment. Our * application doesn't bother with that shortcut. * * \param seqnum * The sequence number to write. */ void midifile::write_seq_number (midishort seqnum) { write_byte(0); /* delta time */ write_byte(EVENT_MIDI_META); /* 0xFF meta tag */ write_byte(EVENT_META_SEQ_NUMBER); /* 0x00 second byte */ write_byte(2); /* 2-bytes of data */ write_short(seqnum); /* write sequence number */ } /** * Reads the sequence number. Meant only for usage in the * proprietary/SeqSpec footer track, in the new file format. * * \return * Returns the sequence number found, or -1 if it was not found. */ int midifile::read_seq_number () { int result = -1; (void) read_byte(); /* throw-away delta time */ midibyte status = read_byte(); /* get the seq-spec marker */ if (status == EVENT_MIDI_META) { if (read_byte() == EVENT_META_SEQ_NUMBER && read_byte() == 2) result = int(read_short()); } return result; } /** * Writes out the end-of-track marker. * * ca 2024-05-19 * We were not writing the delta time. */ void midifile::write_track_end () { write_byte(0); /* delta time */ write_byte(EVENT_MIDI_META); /* 0xFF meta tag */ write_byte(EVENT_META_END_OF_TRACK); /* 0x2F */ write_byte(0); /* no data */ } /** * Common code for track-reading errors. */ bool midifile::track_error (const std::string & context, int track) { bool skip = m_running_status_action == rsaction::skip; std::string msg = context; char temp[80]; snprintf(temp, sizeof temp, " track %d", track); msg += temp; if (skip) msg += " Skipping to end-of-track"; (void) set_error_dump(msg); return skip; } /** * A new function that just sets the fatal-error status and the error message. * * \param msg * Provides the error message. * * \return * Returns false, so that the caller can just assign this as the erroneous * boolean function result. */ bool midifile::set_error (const std::string & msg) { m_error_message = msg; errprint(msg.c_str()); m_error_is_fatal = true; return false; } bool midifile::append_error (const std::string & msg) { m_error_message += ". "; m_error_message += msg; errprint(msg.c_str()); m_error_is_fatal = true; return false; } /** * Helper function to emit more useful error messages. It adds the file * offset to the message. * * \param msg * The main error message string, without an ending newline character. * * \return * Always returns false, to make it easier on the caller. The constructed * string is returned as a side-effect (m_error_message), plus some other * side-effects (m_error_is_fatal, m_disabled_reported) in case we want to * pass it along to the externally-accessible error-message buffer. */ bool midifile::set_error_dump (const std::string & msg) { char temp[80]; snprintf ( /* * temp, sizeof temp, "0x%02x at 0x%zx/0x%zx", * unsigned(m_data[m_pos]), m_pos, m_file_size */ temp, sizeof temp, "at 0x%zx/0x%zx", m_pos, m_file_size ); std::string result = msg; result += ": "; result += temp; /* * msgprintf(msglevel::error, "%s", result.c_str()); */ m_error_message = result; m_error_is_fatal = true; m_disable_reported = true; return false; } /** * Helper function to emit more useful error messages for erroneous long * values. It adds the file offset to the message. * * \param msg * The main error message string, without an ending newline character. * * \param value * The long value to show as part of the message. * * \return * Always returns false, to make it easier on the caller. */ bool midifile::set_error_dump (const std::string & msg, unsigned long value) { char temp[64]; snprintf(temp, sizeof temp, ". Bad value 0x%lx", value); std::string result = msg; result += temp; return set_error_dump(result); } /** * A global function to unify the opening of a MIDI or WRK file. It also * handles PPQN discovery. * * We do not need to clear any existing playlist. The new function, * performer::clear_all(), also does a clear-all, including the playlist, if * its boolean parameter is set to true. We leave it false here. * * \todo * Tighten up wrkfile/midifile handling re PPQN!!! * * \param [in,out] p * Provides the performance object to update with information read from * the file. * * \param fn * The full path specification for the file to be opened. * * \param out ppqn * Provides the PPQN to start with. It can be c_use_file_ppqn, * or a legitimate PPQN value. The performer's PPQN value is updated, * and will affect the rest of the application. * * \param [out] errmsg * If the function fails, this string is filled with the error message. * * \return * Returns true if reading the MIDI/WRK file succeeded. As a side-effect, * the usrsettings::file_ppqn() is set to return the final PPQN to be * used. */ bool read_midi_file ( performer & p, const std::string & fn, int ppqn, /* might get altered */ std::string & errmsg, bool addtorecent ) { bool result = file_readable(fn); /* how to disable Save? */ if (result) { bool is_wrk = file_extension_match(fn, "wrk"); if (usr().use_file_ppqn()) ppqn = c_use_file_ppqn; ppqn = choose_ppqn(ppqn); /* no usr().file_ppqn() yet */ midifile * fp = is_wrk ? new (std::nothrow) wrkfile(fn, ppqn) : new (std::nothrow) midifile(fn, ppqn) ; std::unique_ptr f(fp); p.clear_all(); /* see banner notes */ result = bool(f); if (result) result = f->parse(p, 0); if (result) { if (usr().use_file_ppqn()) { ppqn = f->ppqn(); /* get & return file PPQN */ usr().file_ppqn(ppqn); /* save the value from file */ } usr().midi_ppqn(ppqn); /* save the current value */ p.set_ppqn(ppqn); /* set up PPQN for MIDI */ rc().midi_filename(fn); /* save current file-name */ if (addtorecent) { std::string path = filename_path(fn); if (! path.empty()) rc().last_used_dir(path); (void) rc().add_recent_file(fn); /* Oli Kester's Kepler34! */ } p.announce_playscreen(); /* tell MIDI control out */ file_message("Read", fn); if (! f->error_message().empty()) /* actually a warning here */ errmsg = f->error_message(); } else { errmsg = f->error_message(); if (f->error_is_fatal()) { if (addtorecent) rc().remove_recent_file(fn); } } } else { std::string msg = "File not accessible"; file_error(msg, fn); errmsg = msg + ": " + fn; rc().remove_recent_file(fn); } return result; } bool write_midi_file ( performer & p, const std::string & fn, std::string & errmsg ) { bool result = false; std::string fname = fn.empty() ? rc().midi_filename() : fn ; if (fname.empty()) { errmsg = "No file-name to write"; } else { bool glob = usr().global_seq_feature(); bool doseqspec = p.smf_format() > 0; midifile f(fname, p.ppqn(), glob); result = f.write(p, doseqspec); if (result) { rc().midi_filename(fname); rc().last_used_dir(fname.substr(0, fname.rfind("/") + 1)); (void) rc().add_recent_file(fname); /* rc().midi_filename() */ file_message("Wrote MIDI file", fname); p.unmodify(); } else { errmsg = f.error_message(); file_error("Write failed", fname); } } return result; } } // namespace seq66 /* * midifile.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/patches.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file patches.hpp * * This module defines the array of MIDI patch/program names. * * \library seq66 application * \author Chris Ahlstrom * \date 2025-02-17 * \updates 2026-02-16 * \license GNU GPLv2 or above * * This module is code extracted from the controllers module for better * modularity. */ #include "midi/midibytes.hpp" /* seq66::c_midibyte_data_max() */ #include "midi/patches.hpp" /* seq66::program_name(), etc. */ namespace seq66 { /** * Note that the constructor and destructor are defaulted in the header * file. */ bool patches::add (int patchnumber, const std::string & patchname) { bool result { patchnumber < int(c_midibyte_data_max) }; if (result) { auto p { std::make_pair(patchnumber, patchname) }; auto r { m_patch_map.insert(p) }; result = r.second; } return result; } std::string patches::name (int patchnumber) const { auto it { m_patch_map.find(patchnumber) }; return it == m_patch_map.end() ? "N/A" : it->second ; } std::string patches::name_ex (int patchnumber) const { std::string result { std::to_string(patchnumber) }; auto it { m_patch_map.find(patchnumber) }; result += " "; result += it == m_patch_map.end() ? "N/A" : it->second; return result; } /* * A single global instance of the patches class */ static patches & non_gm_patches () { static patches s_non_gm_patches; return s_non_gm_patches; } /** * Provides the default names of General MIDI program changes. Note that the * numbering starts from 0 internally. We could add support for this kind of * list in usrsettings, or the note-mapper, or in a new 'patch' configuration * file that holds this mapping. */ /* * static const patchpair * c_gm_program_names [c_midibyte_data_max] = */ static patches::container s_gm_program_names { { 0, "Acoustic Grand Piano" }, { 1, "Bright Acoustic Piano" }, { 2, "Electric Grand Piano" }, { 3, "Honky-tonk Piano" }, { 4, "Electric Piano 1" }, // Rhodes Piano { 5, "Electric Piano 2" }, // Chorused Piano { 6, "Harpsichord" }, { 7, "Clavinet" }, // Clavi { 8, "Celesta" }, { 9, "Glockenspiel" }, { 10, "Music Box" }, { 11, "Vibraphone" }, { 12, "Marimba" }, { 13, "Xylophone" }, { 14, "Tubular Bells" }, { 15, "Dulcimer" }, { 16, "Drawbar Organ" }, // Hammond Organ { 17, "Percussive Organ" }, { 18, "Rock Organ" }, { 19, "Church Organ" }, { 20, "Reed Organ" }, { 21, "Accordion" }, { 22, "Harmonica" }, { 23, "Tango Accordion" }, { 24, "Acoustic Nylon Guitar" }, { 25, "Acoustic Steel Guitar" }, { 26, "Electric Jazz Guitar" }, { 27, "Electric Clean Guitar" }, { 28, "Electric Muted Guitar" }, { 29, "Overdriven Guitar" }, { 30, "Distortion Guitar" }, { 31, "Guitar Harmonics" }, { 32, "Acoustic Bass" }, { 33, "Fingered Electric Bass" }, { 34, "Plucked Electric Bass" }, { 35, "Fretless Bass" }, { 36, "Slap Bass 1" }, { 37, "Slap Bass 2" }, { 38, "Synth Bass 1" }, { 39, "Synth Bass 2" }, { 40, "Violin" }, { 41, "Viola" }, { 42, "Cello" }, { 43, "Contrabass" }, { 44, "Tremolo Strings" }, { 45, "Pizzicato Strings" }, { 46, "Orchestral Harp" }, { 47, "Timpani" }, { 48, "String Ensemble 1" }, { 49, "String Ensemble 2" }, { 50, "Synth Strings 1" }, { 51, "Synth Strings 2" }, { 52, "Choir Aah" }, { 53, "Voice Ooh" }, { 54, "Synth Voice" }, { 55, "Orchestra Hit" }, { 56, "Trumpet" }, { 57, "Trombone" }, { 58, "Tuba" }, { 59, "Muted Trumpet" }, { 60, "French Horn" }, { 61, "Brass Section" }, { 62, "Synth Brass 1" }, { 63, "Synth Brass 2" }, { 64, "Soprano Sax" }, { 65, "Alto Sax" }, { 66, "Tenor Sax" }, { 67, "Baritone Sax" }, { 68, "Oboe" }, { 69, "English Horn" }, { 70, "Bassoon" }, { 71, "Clarinet" }, { 72, "Piccolo" }, { 73, "Flute" }, { 74, "Recorder" }, { 75, "Pan Flute" }, { 76, "Bottle Blow" }, { 77, "Shakuhachi" }, { 78, "Whistle" }, { 79, "Ocarina" }, { 80, "Lead 1 (Square)" }, { 81, "Lead 2 (Sawtooth)" }, { 82, "Lead 3 (Calliope)" }, { 83, "Lead 4 (Chiff)" }, { 84, "Lead 5 (Charang)" }, { 85, "Lead 6 (Voice)" }, { 86, "Lead 7 (Fifths)" }, { 87, "Lead 8 (bass)" }, { 88, "Pad 1 (New Age)" }, { 89, "Pad 2 (Warm)" }, { 90, "Pad 3 (Polysynth)" }, { 91, "Pad 4 (Choir)" }, { 92, "Pad 5 (Bowed)" }, { 93, "Pad 6 (Metallic)" }, { 94, "Pad 7 (Halo)" }, { 95, "Pad 8 (Sweep)" }, { 96, "FX 1 (Rain)" }, { 97, "FX 2 (Soundtrack)" }, { 98, "FX 3 (Crystal)" }, { 99, "FX 4 (Atmosphere)" }, { 100, "FX 5 (Brightness)" }, { 101, "FX 6 (Goblins)" }, { 102, "FX 7 (Echoes)" }, { 103, "FX 8 (Sci-Fi)" }, { 104, "Sitar" }, { 105, "Banjo" }, { 106, "Shamisen" }, { 107, "Koto" }, { 108, "Kalimba" }, { 109, "Bagpipe" }, { 110, "Fiddle" }, { 111, "Shanai" }, { 112, "Tinkle Bell" }, { 113, "Agogo" }, { 114, "Steel Drums" }, { 115, "Woodblock" }, { 116, "Taiko Drum" }, { 117, "Melodic Tom" }, { 118, "Synth Drum" }, { 119, "Reverse Cymbal" }, { 120, "Guitar Fret Noise" }, { 121, "Breath Noise" }, { 122, "Seashore" }, { 123, "Bird Tweet" }, { 124, "Telephone Ring" }, { 125, "Helicopter" }, { 126, "Applause" }, { 127, "Gunshot" } }; /** * Adds a patch number/name pair to the non-GM map supported by the * patches class. */ bool add_patch (int patchnumber, const std::string & patchname) { bool result { non_gm_patches().add(patchnumber, patchname) }; if (result) non_gm_patches().activate(); return result; } /** * Adds a comment to the non-GM patch set for the 'patches' file. */ void set_patches_comment (const std::string & c) { if (non_gm_patches().active()) non_gm_patches().comments(c); } const std::string & get_patches_comment () { static const std::string s_gm_comment { "Provides the internal set of GM patches." }; return non_gm_patches().active() ? non_gm_patches().comments() : s_gm_comment ; } /** * Returns the patch number plus the patch name for the hard-wired GM * patch list. This function is used in displays and drop-down lists. * * If the patch number isn't found, the name "N/A" is used. */ std::string gm_program_name (int patchnumber) { std::string result { std::to_string(patchnumber) }; auto it { s_gm_program_names.find(patchnumber) }; result += " "; if (it == s_gm_program_names.end()) result += "N/A"; else result += it->second; return result; } /** * This function returns the patch name and number from the user's * loaded non-GM patch list, if active. Otherwise, it returns the * patch name and number from the internal GM list. */ std::string program_name (int patchnumber) { return non_gm_patches().active() ? non_gm_patches().name_ex(patchnumber) : gm_program_name(patchnumber) ; } /** * This function creates a long string of all the patches in the 'patches' * file format. * * Note that it depends on having the full 128-count of patches. */ std::string program_list () { std::string result; const patches::container & active_patch_set { non_gm_patches().active() ? non_gm_patches().patch_map() : s_gm_program_names }; int patchnumber { 0 }; for (const auto & p : active_patch_set) { auto it { s_gm_program_names.find(patchnumber) }; std::string gmname { it == s_gm_program_names.end() ? "N/A" : it->second }; std::string numb { std::to_string(patchnumber) }; result += "[Patch "; result += numb; result += "]\n\ngm-name = \""; result += gmname; result += "\"\n" "gm-patch = "; result += numb; result += "\ndev-name = \""; result += p.second; result += "\"\n\n"; /* * We don't need to support a dev patch that matches the sound of a * GM patch. Let the user figure it out, if even needed. */ ++patchnumber; } return result; } } // namespace seq66 /* * patches.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/midi/wrkfile.cpp ================================================ /* * WRK File component * Copyright (C) 2010-2018, Pedro Lopez-Cabanillas * * This library 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 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 General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ /** * \file wrkfile.cpp * * This module declares/defines the class for reading WRK files. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-06-04 * \updates 2025-07-19 * \license GNU GPLv2 or above * * For a quick guide to the WRK format, see, for example: * * Implementation of a class managing Cakewalk WRK Files input. * * Format: * * Using drumstick-dumpwrk, the overall structure of an (old, very old) WRK * file is: * * -# First track with ticks, track number, channel, events, and data. * In our sample, ticks = 0, and track and channel are empty here. * -# Wrk file version. * -# PPQN. * -# Global variables, if any. * -# Variable record with string "FILESTATS" * -# Comment = "@" * -# Song name, such as '"Memories of Professor Longhair" by Mac Rebenak...' * -# Some more data: \verbatim 0 -- -- Unknown Chunk 25 (0x19) size=66 0 -- -- String Table MCICmd, Wave, Text, Lyric 0 -- -- Unknown Chunk 28 (0x1c) size=6 0 -- -- Tempo 115.00 0 -- -- Time Signature bar=1, 4/4 0 -- -- Time Signature bar=1, 4/4 0 -- -- Key Signature bar=1, alt=0 0 -- -- SMPTE Time Format 30 frames/second, offset=0 0 -- -- Unknown Chunk 17 (0x11) size=14 0 -- -- Thru Mode mode=-1 port=0 chan=9 key+=0 vel+=10 port=-1 0 -- -- Unknown Chunk 31 (0x1f) size=4 \endverbatim * -# Track data: \verbatim 0 0 7 Track name1='Right hand (99' name2=' | 00 Stereo Pi' 0 0 -- Track Name Right hand (99 | 00 Stereo Piano 60 0 0 Note key=55 vel=64 dur=15 75 0 0 Note key=56 vel=64 dur=15 . . . 47040 0 0 Note key=63 vel=64 dur=480 47040 0 0 Note key=55 vel=64 dur=480 0 0 -- Track Patch 0 0 0 -- Track Volume 127 0 -- -- Unknown Chunk 29 (0x1d) size=6 \endverbatim * * And the rest of the tracks follow the same pattern. A couple empty tracks * (Track, Track Name, and Unknown Chunk only) end the tune. There seems to * be no way of knowing the number of tracks before parsing them all. */ #include /* for the pow() function */ #include "cfg/settings.hpp" /* seq66::rc().show_midi() etc. */ #include "midi/wrkfile.hpp" /* seq66::wrkfile */ #include "play/performer.hpp" /* seq66::performer */ #include "play/seq.hpp" /* seq66::sequence, seq66::seq */ #include "util/strfunctions.hpp" /* seq66::bool_to_string() */ namespace seq66 { /** * Record types within a WRK file. */ enum wrk_chunk_t { WC_NO_CHUNK = 0, ///< Nothing. WC_TRACK_CHUNK = 1, ///< Track prefix. WC_STREAM_CHUNK = 2, ///< Events stream. WC_VARS_CHUNK = 3, ///< Global variables. WC_TEMPO_CHUNK = 4, ///< Tempo map. WC_METER_CHUNK = 5, ///< Meter map. WC_SYSEX_CHUNK = 6, ///< System exclusive bank. WC_MEMRGN_CHUNK = 7, ///< Memory region. WC_COMMENTS_CHUNK = 8, ///< Comments. WC_TRKOFFS_CHUNK = 9, ///< Track offset. WC_TIMEBASE_CHUNK = 10, ///< Timebase. If present, first chunk in file. WC_TIMEFMT_CHUNK = 11, ///< SMPTE time format. WC_TRKREPS_CHUNK = 12, ///< Track repetitions. WC_TRKPATCH_CHUNK = 14, ///< Track patch. WC_NTEMPO_CHUNK = 15, ///< New Tempo map. WC_THRU_CHUNK = 16, ///< Extended thru parameters. WC_LYRICS_CHUNK = 18, ///< Events stream with lyrics. WC_TRKVOL_CHUNK = 19, ///< Track volume. WC_SYSEX2_CHUNK = 20, ///< System exclusive bank. WC_STRTAB_CHUNK = 22, ///< Table of text event types. WC_METERKEY_CHUNK = 23, ///< Meter/Key map. WC_TRKNAME_CHUNK = 24, ///< Track name. WC_VARIABLE_CHUNK = 26, ///< Variable record chunk. WC_NTRKOFS_CHUNK = 27, ///< Track offset. WC_TNUMPLUS_CHUNK = 29, ///< Common, but unknow purpose. WC_TRKBANK_CHUNK = 30, ///< Track bank. WC_NTRACK_CHUNK = 36, ///< Track prefix. WC_NSYSEX_CHUNK = 44, ///< System exclusive bank. WC_NSTREAM_CHUNK = 45, ///< Events stream. WC_SGMNT_CHUNK = 49, ///< Segment prefix. WC_SOFTVER_CHUNK = 74, ///< Software version which saved the file. WC_END_CHUNK = 255 ///< Last chunk, end of file. }; /** * Cakewalk WRK File header id. */ static const std::string CakewalkHeader("CAKEWALK"); /** * wrkfile provides a mechanism to parse Cakewalk WRK Files, without * the burden of a policy forcing to use some internal sequence representation. * * This class is not related to or based on the ALSA library. */ wrkfile::wrkfile_private::wrkfile_private () : m_Now (0), m_From (0), m_Thru (11930), m_KeySig (0), m_Clock (0), m_AutoSave (0), m_PlayDelay (0), m_ZeroCtrls (false), m_SendSPP (true), m_SendCont (true), m_PatchSearch (false), m_AutoStop (false), m_StopTime (4294967295U), m_AutoRewind (false), m_RewindTime (0), m_MetroPlay (false), m_MetroRecord (true), m_MetroAccent (false), m_CountIn (1), m_ThruOn (true), m_AutoRestart (false), m_CurTempoOfs (1), m_TempoOfs1 (32), m_TempoOfs2 (64), m_TempoOfs3 (128), m_PunchEnabled (false), m_PunchInTime (0), m_PunchOutTime (0), m_EndAllTime (0), m_division (120), #if defined USE_UNICODE_SUPPORT m_codec (0), #endif m_tempos () { // no code } /** * Principal constructor. * * \param name * Provides the name of the WRK file to be read or written. * * \param ppqn * Provides the initial value of the PPQN setting. It is handled * differently for parsing (reading) versus writing the MIDI file. * See the midifile class. * * \param playlistmode * If set to true, we are opening files just to verify them before * accepting the usage of a playlist. In this case, we clear out each * song after it is read in for verification. It defaults to false. */ wrkfile::wrkfile ( const std::string & name, int ppqn, bool playlistmode ) : midifile (name, ppqn, true, playlistmode), m_wrk_data (), m_performer (nullptr), m_screen_set (seq::unassigned()), m_importing (false), m_seq_number (0), m_track_number (seq::unassigned()), m_track_name (), m_track_channel (seq::unassigned()), m_track_count (0), m_track_time (0), m_current_seq (nullptr) { // No other code } /** * \dtor * * Nothing to delete, m_wrk_data is now a member, not a pointer. Some might * argue that it is better, as Pedro did in his "Drumstick" project, * to hide the data implement in the C++ file, rather than risk rebuilds by * putting it in the header file. */ wrkfile::~wrkfile() { // no code } /** * Shows a message (to console only) for Cakewalk events not supported in * Seq66. */ void wrkfile::not_supported (const std::string & tag) { if (rc().show_midi()) { warnprintf("! Cakewalk '%s' not supported", tag.c_str()); } } /** * Read the chunk raw data (undecoded). * * Seq66 status: Not handled. * * \param sz * Provides the number of raw data bytes to be read. */ void wrkfile::read_raw_data (int sz) { read_byte_array(m_wrk_data.m_lastChunkData, sz); } /** * Converts two bytes into a single 16-bit value. * * \param c1 * First byte, the most significant byte. * * \param c2 * Second byte. * * \return * The 16-bit value is returned. */ midishort wrkfile::to_16_bit (midibyte c1, midibyte c2) { midishort value = c1 << 8; value += c2; return value; } /** * Converts four bytes into a single 32-bit value. * * \param c1 * 1st byte, the most significant byte. * * \param c2 * 2nd byte. * * \param c3 * 3rd byte. * * \param c4 * 4th byte. * * \return * The 32-bit value is returned. */ midilong wrkfile::to_32_bit (midibyte c1, midibyte c2, midibyte c3, midibyte c4) { midilong value = c1 << 24; value += c2 << 16; value += c3 << 8; value += c4; return value; } /** * Reads a 16-bit value. Tricky, because we reverse the bits before * conversion. * * \return * The 32-bit value, calculated from three bytes and a zero, is returned. */ midilong wrkfile::read_16_bit () { midibyte c1 = read_byte(); midibyte c2 = read_byte(); return to_16_bit(c2, c1); } /** * Reads a 24-bit value. Tricky, because we reverse the bits before * conversion. * * \return * The 32-bit value, calculated from three bytes and a zero, is returned. */ midilong wrkfile::read_24_bit () { midibyte c1 = read_byte(); midibyte c2 = read_byte(); midibyte c3 = read_byte(); return to_32_bit(0, c3, c2, c1); } /** * Reads a 32-bit value. Tricky, because we reverse the bits before * conversion, contrary to read_32_bit(). * * \return * The 32-bit value, calculated from three bytes and a zero, is returned. */ midilong wrkfile::read_32_bit () { midibyte c1 = read_byte(); midibyte c2 = read_byte(); midibyte c3 = read_byte(); midibyte c4 = read_byte(); return to_32_bit(c4, c3, c2, c1); } /** * Reads a string. Unicode will be handled, eventually. Compared this * function to midifile::read_byte_array(). * * \param len * Provides the length to be read. * * \return * Returns a string of unsigned bytes. */ std::string wrkfile::read_string (int len) { std::string s; if (len > 0) { std::string data; midibyte c = 0xff; for (int i = 0; i < len && c != 0; ++i) { c = read_byte(); if (c != 0) data.push_back(static_cast(c)); // CAREFUL!!! } #if defined USE_UNICODE_SUPPORT if (is_nullptr(m_wrk_data.m_codec)) s = std::string(data); else { // Handle Unicode: // s = m_wrk_data.m_codec->toUnicode(data); } #else s = std::string(data); #endif } return s; } /** * Reads a variable length string (C-style). * * Unicode is handled. * * \return * Returns a string. */ std::string wrkfile::read_var_string () { std::string result; std::string data; midibyte b; do { b = read_byte(); if (b != 0) data.push_back(static_cast(b)); // CAREFUL!!! } while (b != 0); #if defined USE_UNICODE_SUPPORT if (is_nullptr(m_wrk_data.m_codec)) result = std::string(data); else { // Handle Unicode: // result = m_wrk_data.m_codec->toUnicode(data); } #else result = std::string(data); #endif return result; } /** * After reading a WRK header: * * - verh WRK file format version major * - verl WRK file format version minor * * void signalWRKHeader(int verh, int verl); * * Note that the filename is set during the construction of this object. */ bool wrkfile::parse (performer & p, int screenset, bool importing) { bool result = grab_input_stream(std::string("WRK")); if (result) { std::string hdr = read_string(int(CakewalkHeader.length())); result = hdr == CakewalkHeader; } if (result) { clear_errors(); m_performer = &p; /* get address, access via perfp() */ m_screen_set = screenset; m_importing = importing; read_gap(1); /* bypasses a 0x1a [SUB] character */ int vme = int(read_byte()); /* minor WRK version number */ int vma = int(read_byte()); /* major WRK version number */ int ck_id; msgprintf(msglevel::status, "WRK Version: %d.%d", vma, vme); do { ck_id = read_chunk(); } while (ck_id != WC_END_CHUNK && ! at_end()); if (! at_end()) result = set_error("Corrupted WRK file."); else EndChunk(); } else result = set_error("Invalid WRK file format."); return result; } /** * An override of the midifile function. All it does is set the m_track_time * value to 0, so far. */ sequence * wrkfile::create_sequence (performer & p) { sequence * result = midifile::create_sequence(p); if (not_nullptr(result)) { m_track_time = 0; } return result; } /** * Calls midifile::finalize_sequence(). Also sets the MIDI buss override if * active, though we could centralize this in that function. * * \param p * The performer object to perhaps modify by the finalizing of this * sequence. * * \param seq * The sequence to be finalized. * * \param seqnum * The prospective sequence number, passed to midifile :: * finalize_sequence(). * * \param screenset * The prospective screen-set number, passed to midifile :: * finalize_sequence(). */ bool wrkfile::finalize_sequence ( performer & p, sequence & seq, int seqnum, int screenset ) { bool result = midifile::finalize_sequence(p, seq, seqnum, screenset); if (result) { ++m_track_count; ++m_seq_number; } return result; } /** * This function seems to get an element that described a track and how it is * to be played. * * Emitted after reading a track prefix chunk: * * - name1. Track 1st name. Do not confuse with the TrackName() function! * - name2. Track 2nd name. Ditto. * - trackno. Track number. * - channel. Track forced channel (-1 = not forced). * - pitch. Track pitch transpose in semitones (-127 to 127). * - velocity. Track velocity increment (-127 to 127). * - port. Track forced port. * - selected. True if track is selected. * - muted. True if track is muted. * - loop. True if loop is enabled. * \verbatim void signalWRKTrack ( const std::string& name1, const std::string& name2, int trackno, int channel, int pitch, int velocity, int port, bool selected, bool muted, bool loop ); \endverbatim * * This function does the following: * * - Get a number of track parameters. * - Show the MIDI parameters read from this chunk, if desired. * - Get the track number, and, if different from the current one, * we have a new track. * - When we encounter a new track, we need to: * - Finalize the current sequence, if any. It remains in memory * to be used during the Seq66 session. * - Create a new sequence (pattern) object. */ void wrkfile::TrackChunk () { std::string name[2]; int trackno = int(read_16_bit()); /* used as provisional seq number */ for (int i = 0; i < 2; ++i) { int namelen = read_byte(); name[i] = read_string(namelen); } int channel = read_byte(); /* will be logged in the sequence */ int pitch = read_byte(); /* not used in Seq64... transpose? */ int velocity = read_byte(); /* hmmmmm... hardcode vel override? */ int port = read_byte(); /* hmmmmm... buss number? */ midibyte flags = read_byte(); /* hmmmmm... hardcode vel override? */ bool selected = ((flags & 1) != 0); /* not used in Seq64... transpose? */ bool muted = ((flags & 2) != 0); /* hmmmmm... could be a new feature */ bool loop = ((flags & 4) != 0); /* always true in Seq66 */ // Q_EMIT signalWRKTrack(name[0], name[1], trackno, channel, pitch, ...); std::string track_name = name[0]; /* will be logged in the sequence */ if (! name[1].empty()) { track_name += " "; track_name += name[1]; } if (rc().show_midi()) { printf ( "Track : Tr %d '%s'\n" " : ch %d port %d selected %s\n" " : muted %s loop %s pitch %d vel %d\n", trackno, track_name.c_str(), channel, ibyte(port), bool_to_string(selected).c_str(), bool_to_string(muted).c_str(), bool_to_string(loop).c_str(), pitch, velocity ); } next_track(trackno, channel, track_name); } /** * Called from NewTrack() or TrackChunk(). It finalizes the current track and * sets up for the next track. * * Handle a new track. All the events in a Cakewalk track are contiguous. * Right now we don't do any error-checking, but we do need to make sure what * exists, so we know what to do. * * Unanswered yet is what to do with the hanging sequence if no more tracks are * found? And we have to have another way to finish off the last track. * This is now done in EndChunk(). */ void wrkfile::next_track ( int trackno, int channel, const std::string & trackname, bool /*end_chunk*/ ) { if (trackno != m_track_number) { m_track_channel = channel; m_track_name = trackname; if (trackno >= 0 && trackno < usr().max_sequence()) { m_track_number = trackno; /* with a new number */ } else { errprint("? Out-of-range track number found in WRK file"); ++m_track_number; } finalize_track(); /* * Set up for the next sequence. The previous one remains. */ m_current_seq = create_sequence(*m_performer); m_current_seq->set_midi_channel(channel); /* channel, whole trk */ m_current_seq->set_name(trackname); } } /** * This override finalizes a WRK track, if the sequence doesn't already * exist. */ void wrkfile::finalize_track () { if (not_nullptr(m_current_seq)) /* a sequence currently exists */ { midipulse duration = m_track_time; if (scaled()) duration = midipulse(duration * ppqn_ratio()); m_current_seq->set_length(duration); (void) finalize_sequence ( *m_performer, *m_current_seq, m_track_number, m_screen_set ); } } /** * Emitted after reading the global variables chunk: This record contains * miscellaneous Cakewalk global variables that can be retrieved using * individual getters. See getNow(), getFrom(), getThru(), etc. However, we * will expose only the values needed by Seq66. * * Fixed-point ratio value of tempo offset 1, 2, or 3. * * \note * The offset ratios are expressed as a numerator in the expression n/64. * To get a ratio from this number, divide the number by 64. To get this * number from a ratio, multiply the ratio by 64. * * Examples: * * - 32 ==> 32/64 = 0.5 * - 63 ==> 63/64 = 0.9 * - 64 ==> 64/64 = 1.0 * - 128 ==> 128/64 = 2.0 * * void signalWRKGlobalVars(); * * Some of the variables filled in below could be useful to save as current * or future feature values for the whole tune. */ void wrkfile::VarsChunk () { m_wrk_data.m_Now = read_32_bit(); m_wrk_data.m_From = read_32_bit(); m_wrk_data.m_Thru = read_32_bit(); m_wrk_data.m_KeySig = read_byte(); /* could be useful */ m_wrk_data.m_Clock = read_byte(); /* could be useful */ m_wrk_data.m_AutoSave = read_byte(); /* new feature? */ m_wrk_data.m_PlayDelay = read_byte(); read_gap(1); m_wrk_data.m_ZeroCtrls = read_byte() != 0; m_wrk_data.m_SendSPP = read_byte() != 0; m_wrk_data.m_SendCont = read_byte() != 0; m_wrk_data.m_PatchSearch = read_byte() != 0; m_wrk_data.m_AutoStop = read_byte() != 0; /* new feature? */ m_wrk_data.m_StopTime = read_32_bit(); /* new feature? */ m_wrk_data.m_AutoRewind = read_byte() != 0; /* new feature? */ m_wrk_data.m_RewindTime = read_32_bit(); m_wrk_data.m_MetroPlay = read_byte() != 0; m_wrk_data.m_MetroRecord = read_byte() != 0; m_wrk_data.m_MetroAccent = read_byte() != 0; m_wrk_data.m_CountIn = read_byte(); /* new feature? */ read_gap(2); m_wrk_data.m_ThruOn = read_byte() != 0; read_gap(19); m_wrk_data.m_AutoRestart = read_byte() != 0; m_wrk_data.m_CurTempoOfs = read_byte(); m_wrk_data.m_TempoOfs1 = read_byte(); m_wrk_data.m_TempoOfs2 = read_byte(); m_wrk_data.m_TempoOfs3 = read_byte(); read_gap(2); m_wrk_data.m_PunchEnabled = read_byte() != 0; /* new feature? */ m_wrk_data.m_PunchInTime = read_32_bit(); m_wrk_data.m_PunchOutTime = read_32_bit(); m_wrk_data.m_EndAllTime = read_32_bit(); // Q_EMIT signalWRKGlobalVars(); if (rc().show_midi()) { printf ( "Global Vars : now = %ld, end = %ld (and many more)\n", m_wrk_data.m_Now, m_wrk_data.m_EndAllTime ); } } /** * Emitted after reading the timebase chunk: * * - timebase ticks per quarter note * * void signalWRKTimeBase(int timebase); */ void wrkfile::TimebaseChunk () { midishort timebase = read_16_bit(); m_wrk_data.m_division = timebase; file_ppqn(int(timebase)); /* original file PPQN */ if (usr().use_file_ppqn()) { m_performer->ppqn(file_ppqn()); /* let performer know */ usr().file_ppqn(file_ppqn()); ppqn(file_ppqn()); /* PPQN == file PPQN */ scaled(false); /* do not scale time */ } else { m_performer->ppqn(usr().default_ppqn()); scaled(file_ppqn() != usr().default_ppqn()); if (scaled()) ppqn_ratio(double(ppqn()) / double(file_ppqn())); } // Q_EMIT signalWRKTimeBase(timebase); if (rc().show_midi()) printf("Time Base : %d PPQN\n", int(timebase)); } /** * Provides the processing of events for a number of other functions. * * Emitted after reading a text message: * * - track track number * - time musical time * - type Text type * - data Text data * * void signalWRKText(int track, long time, int type, const std::string& data); * * Emitted after reading a track bank chunk: * * - track track number * - bank * * void signalWRKTrackBank(int track, int bank); * * Emitted after reading a track volume chunk: * * - track track number * - vol initial volume * * void signalWRKTrackVol(int track, int vol); * * Emitted after reading a chord diagram chunk: * * - track track number * - time event time in ticks * - name chord name * - data chord data definition (not decoded) * * void signalWRKChord(int track, long time, const std::string& name, const * QByteArray& data); * * Emitted after reading an expression indication (notation) chunk: * * - track track number * - time event time in ticks * - code expression event code * - text expression text * * void signalWRKExpression(int track, long time, int code, const std::string& * text); * * Emitted after reading a hairpin symbol (notation) chunk: * * - track track number * - time event time in ticks * - code hairpin code * - dur duration * * void signalWRKHairpin(int track, long time, int code, int dur); */ void wrkfile::NoteArray (int track, int events) { int value = 0; int len = 0; std::string text; midibytes mdata; const char * format = "%12s: Tr %d tick %ld event 0x%02X ch %d data %d.%d value %d dur %d\n"; for (int i = 0; i < events; ++i) { midipulse time = read_24_bit(); midipulse timemax = time; midibyte status = read_byte(); midibyte eventcode = 0; midibyte channel = 0; midibyte d0 = 0; midibyte d1 = 0; midishort dur = 0; dur = 0; /* * This check leaves out Note Off events. This seems wrong, but it * looks like Cakewalk encodes note events as a Note On and a * duration. Do we need to check for channel messages here? * See the near-duplicate code in StreamChunk(). */ if (status >= EVENT_NOTE_ON) // 0x90 { event e; eventcode = event::mask_status(status); // 0xF0 channel = event::mask_channel(status); // 0x0F m_track_channel = channel; d0 = read_byte(); if (event::is_two_byte_msg(eventcode)) // note on/off, ctrl, pitch d1 = read_byte(); if (eventcode == EVENT_NOTE_ON) // 0x90 { dur = read_16_bit(); // Cakewalk thing } else if (eventcode == EVENT_NOTE_OFF) { warnprint("Note Off event encountered in WRK file"); } bool isnoteoff = false; Set_timestamp(e, time); e.set_status(status); /* w/channel */ switch (eventcode) { case EVENT_NOTE_OFF: // 0x80 case EVENT_NOTE_ON: // 0x90 case EVENT_AFTERTOUCH: // 0xA0 case EVENT_CONTROL_CHANGE: // 0xB0 // Q_EMIT signalWRKNote(track, time, channel, d0, d1, dur); // Q_EMIT signalWRKKeyPress(track, time, channel, d0, d1); // Q_EMIT signalWRKCtlChange(track, time, channel, d0, d1); // CUT-AND-PASTE CODE: isnoteoff = event::is_note_off_velocity(eventcode, d1); if (isnoteoff) e.set_channel_status(EVENT_NOTE_OFF, channel); e.set_data(d0, d1); m_current_seq->append_event(e); if (eventcode == EVENT_NOTE_ON && ! isnoteoff) { event e; timemax = time + midilong(dur); Set_timestamp(e, timemax); e.set_channel_status(EVENT_NOTE_OFF, channel); e.set_data(d0, 0); m_current_seq->append_event(e); } m_current_seq->set_midi_channel(channel); if (timemax > m_track_time) { m_track_time = timemax; } break; case EVENT_PROGRAM_CHANGE: // 0xC0 case EVENT_CHANNEL_PRESSURE: // 0xD0 // Q_EMIT signalWRKProgram(track, time, channel, d0); // Q_EMIT signalWRKChanPress(track, time, channel, d0); e.set_data(d0); m_current_seq->append_event(e); m_current_seq->set_midi_channel(channel); /* * if (is_smf0) * m_smf0_splitter.increment(channel); */ break; case EVENT_PITCH_WHEEL: // 0xE0 // Q_EMIT signalWRKPitchBend(track, time, channel, value); value = (d1 << 7) + d0 - 8192; e.set_data(d0, d1); m_current_seq->append_event(e); m_current_seq->set_midi_channel(channel); /* * if (is_smf0) * m_smf0_splitter.increment(channel); */ break; case EVENT_MIDI_SYSEX: // 0xF0 // Q_EMIT signalWRKSysexEvent(track, time, d0); /* * The midifile class handles a bunch of REALTIME and META * events at this point. Does a WRK file even have those * events? */ break; } if (rc().show_midi()) { printf ( format, "Note Array", track, long(time), eventcode, channel, d0, d1, value, dur ); } } else if (status == 5) /* not supported in Seq66 */ { int code = read_16_bit(); len = read_32_bit(); text = read_string(len); // Q_EMIT signalWRKExpression(track, time, code, text); if (rc().show_midi()) { printf ( format, "Expression", track, long(time), eventcode, channel, d0, d1, value, dur ); printf ( " Text: code %d len %d, '%s'\n", code, len, text.c_str() ); } event e; e.set_channel_status(EVENT_CONTROL_CHANGE, channel); e.set_data(EVENT_CTRL_EXPRESSION, d1); m_current_seq->append_event(e); } else if (status == 6) /* not supported in Seq66 */ { int code = int(read_16_bit()); dur = read_16_bit(); read_gap(4); // Q_EMIT signalWRKHairpin(track, time, code, dur); if (rc().show_midi()) { printf ( format, "Hairpin", track, long(time), eventcode, channel, d0, d1, value, dur ); printf(" Code: code %d\n", code); } not_supported("Hairpin"); } else if (status == 7) /* not supported in Seq66 */ { len = read_32_bit(); text = read_string(len); if (read_byte_array(mdata, 13)) { // Q_EMIT signalWRKChord(track, time, text, mdata); if (rc().show_midi()) { printf ( format, "Chord", track, long(time), eventcode, channel, d0, d1, value, dur ); printf(" Text: len %d, '%s'\n", len, text.c_str()); } } not_supported("WRK Chord"); } else if (status == 8) { len = read_16_bit(); if (read_byte_array(mdata, len)) { // Q_EMIT signalWRKSysex(0, std::string(), false, 0, mdata); if (rc().show_midi()) { printf ( format, "SysEx", track, long(time), eventcode, channel, d0, d1, value, dur ); } not_supported("WRK Sysex"); } } else { len = read_32_bit(); text = read_string(len); // Q_EMIT signalWRKText(track, time, status, text); if (rc().show_midi()) { printf ( format, "Text", track, long(time), eventcode, channel, d0, d1, value, dur ); printf(" Text: len %d, '%s'\n", len, text.c_str()); } not_supported("WRK Text"); } } // Q_EMIT signalWRKStreamEnd(time + dur); } /** * Emitted after reading a Note message: * * - track track number * - time musical time * - chan MIDI Channel * - pitch MIDI Note * - vol Velocity * - dur Duration * * void signalWRKNote(int track, long time, int chan, int pitch, int vol, int dur); * * Emitted after reading a Polyphonic Aftertouch message: * * - track track number * - time musical time * - chan MIDI Channel * - pitch MIDI Note * - press Pressure amount * * void signalWRKKeyPress(int track, long time, int chan, int pitch, int press); * * Emitted after reading a Control Change message: * * - track track number * - time musical time * - chan MIDI Channel * - ctl MIDI Controller * - value Control value * * void signalWRKCtlChange(int track, long time, int chan, int ctl, int value); * * Emitted after reading a Bender message: * * - track track number * - time musical time * - chan MIDI Channel * - value Bender value * * void signalWRKPitchBend(int track, long time, int chan, int value); * * Emitted after reading a Program change message: * * - track track number * - time musical time * - chan MIDI Channel * - patch Program number * * void signalWRKProgram(int track, long time, int chan, int patch); * * Emitted after reading a Channel Aftertouch message: * * - track track number * - time musical time * - chan MIDI Channel * - press Pressure amount * * void signalWRKChanPress(int track, long time, int chan, int press); * * Emitted after reading a System Exclusive event: * * - track track number * - time musical time * - bank Sysex Bank number * * void signalWRKSysexEvent(int track, long time, int bank); * * Emitted after reading the last event of a event stream: * * void signalWRKStreamEnd(long time); * * These are seen a lot in our WRK files. */ void wrkfile::StreamChunk () { midishort track = read_16_bit(); int events = read_16_bit(); midibyte laststatus = 0; for (int i = 0; i < events; ++i) { midipulse time = midipulse(read_24_bit()); midipulse timemax = time; midibyte status = read_byte(); midibyte eventcode = event::mask_status(status); // 0xF0 midibyte channel = event::mask_channel(status); // 0x0F m_track_channel = channel; midibyte d0 = read_byte(); midibyte d1 = read_byte(); midishort dur = read_16_bit(); int value = 0; event e; if ((status & 0x80) == 0x00) /* is it a status bit? */ status = laststatus; /* no, it's running status */ bool isnoteoff = false; Set_timestamp(e, time); e.set_status(status); /* includes the channel */ if (eventcode == EVENT_NOTE_OFF) { warnprint("Note Off event encountered in WRK file"); } switch (eventcode) { case EVENT_NOTE_OFF: // 0x80 case EVENT_NOTE_ON: // 0x90 case EVENT_AFTERTOUCH: // 0xA0 case EVENT_CONTROL_CHANGE: // 0xB0 // Q_EMIT signalWRKNote(track, time, channel, d0, d1, dur); // Q_EMIT signalWRKKeyPress(track, time, channel, d0, d1); // Q_EMIT signalWRKCtlChange(track, time, channel, d0, d1); // CUT-AND-PASTE CODE: isnoteoff = event::is_note_off_velocity(eventcode, d1); if (isnoteoff) e.set_channel_status(EVENT_NOTE_OFF, channel); e.set_data(d0, d1); m_current_seq->append_event(e); if (eventcode == EVENT_NOTE_ON && ! isnoteoff) { event e; timemax = time + midilong(dur); Set_timestamp(e, timemax); e.set_channel_status(EVENT_NOTE_OFF, channel); e.set_data(d0, 0); m_current_seq->append_event(e); } m_current_seq->set_midi_channel(channel); if (timemax > m_track_time) m_track_time = timemax; /* * if (is_smf0) * m_smf0_splitter.increment(channel); */ break; case EVENT_PROGRAM_CHANGE: // 0xC0 case EVENT_CHANNEL_PRESSURE: // 0xD0 // Q_EMIT signalWRKProgram(track, time, channel, d0); // Q_EMIT signalWRKChanPress(track, time, channel, d0); e.set_data(d0); m_current_seq->append_event(e); m_current_seq->set_midi_channel(channel); /* * if (is_smf0) * m_smf0_splitter.increment(channel); */ break; case EVENT_PITCH_WHEEL: // 0xE0 // Q_EMIT signalWRKPitchBend(track, time, channel, value); value = (d1 << 7) + d0 - 8192; // hmmmm e.set_data(d0, d1); m_current_seq->append_event(e); m_current_seq->set_midi_channel(channel); /* * if (is_smf0) * m_smf0_splitter.increment(channel); */ break; case EVENT_MIDI_SYSEX: // 0xF0 // Q_EMIT signalWRKSysexEvent(track, time, d0); /* * The midifile class handles a bunch of REALTIME and META events * at this point. Does a WRK file even have those events? */ break; } if (rc().show_midi()) { const char * format = "%12s: Tr %d tick %ld event 0x%02X ch %d " "data %d.%d value %d dur %d\n"; printf ( format, "Stream", track, long(time), eventcode, channel, d0, d1, value, dur ); } } // Q_EMIT signalWRKStreamEnd(time + dur); } /** * Seq66 currently doesn't handle variable time signatures. So we set it * only for the first bar (measure). Also, Cakewalk WRK files do not seem to * handle clocks-per-metronome and 32nds-per-quarter. * * See MeterKeyChunk(). */ void wrkfile::MeterChunk () { int count = read_16_bit(); for (int i = 0; i < count; ++i) { read_gap(4); int measure = read_16_bit(); int num = read_byte(); int den = pow(2.0, read_byte()); read_gap(4); // Q_EMIT signalWRKTimeSig(measure, num, den); if (rc().show_midi()) { printf ( "Time Sig : bar %d timesig %d/%d\n", measure, num, den ); } if (measure == 1) { if (is_nullptr(m_current_seq)) m_current_seq = create_sequence(*m_performer); m_current_seq->set_beats_per_bar(num); m_current_seq->set_beat_width(den); /* * The default (24 and 8) are wired into the performer and * sequence classes. * * m_current_seq->clocks_per_metronome(cpm); * m_current_seq->set_32nds_per_quarter(tpq); */ if (m_track_number == 0) { m_performer->set_beats_per_bar(num); m_performer->set_beat_width(den); /* * See above. * * m_performer->clocks_per_metronome(cpm); * m_performer->set_32nds_per_quarter(tpq); */ } } } } /** * Emitted after reading a WRK Time signature: * * - bar. Measure number. * - num. Numerator. * - den. Denominator (exponent in a power of two). * * void signalWRKTimeSig(int bar, int num, int den); * * Emitted after reading a WRK Key Signature: * * - bar. Measure number. * - alt. Number of alterations (negative=flats, positive=sharps). * * void signalWRKKeySig(int bar, int alt); */ void wrkfile::MeterKeyChunk () { int count = read_16_bit(); for (int i = 0; i < count; ++i) { int measure = read_16_bit(); int num = read_byte(); int den = pow(2.0, read_byte()); midibyte alt = read_byte(); // Q_EMIT signalWRKTimeSig(measure, num, den); // Q_EMIT signalWRKKeySig(measure, alt); if (rc().show_midi()) { printf ( "Time Sig/Key: bar %d timesig %d/%d key %u\n", measure, num, den, unsigned(alt) ); } if (measure == 1) { if (is_nullptr(m_current_seq)) m_current_seq = create_sequence(*m_performer); m_current_seq->set_beats_per_bar(num); m_current_seq->set_beat_width(den); /* * The default (24 and 8) are wired into the performer and * sequence classes. * * m_current_seq.clocks_per_metronome(cpm); * m_current_seq.set_32nds_per_quarter(tpq); */ if (m_track_number == 0) { m_performer->set_beats_per_bar(num); m_performer->set_beat_width(den); /* * See above. * * m_performer->clocks_per_metronome(cpm); * m_performer->set_32nds_per_quarter(tpq); */ /* * We should be able to handle key signature, but it is a two * byte value, not a single alt byte!!! So we're stuck with * major keys. */ event e; midibytes bt; bt.push_back(alt); bt.push_back(0); /* indicates a major key */ bool ok = e.append_meta_data(EVENT_META_KEY_SIGNATURE, bt); if (ok) m_current_seq->append_event(e); } } } } /** * Not used internally in this library. */ double wrkfile::get_real_time (midipulse ticks) const { double division = 1.0 * m_wrk_data.m_division; RecTempo last; last.time = 0; last.tempo = 100.0; last.seconds = 0.0; if (! m_wrk_data.m_tempos.empty()) { for (const RecTempo & rec : m_wrk_data.m_tempos) { if (rec.time >= ticks) break; last = rec; } } return last.seconds + ( ((ticks - last.time) / division) * (60.0 / last.tempo) ); } /** * Emitted after reading a Tempo Change message: * * Tempo units are given in beats * 100 per minute, so to obtain BPM * it is necessary to divide by 100 the tempo. * * - time musical time * - tempo beats per minute multiplied by 100 * * void signalWRKTempo(long time, int tempo); */ void wrkfile::TempoChunk (int factor) { double division = 1.0 * m_wrk_data.m_division; int count = read_16_bit(); for (int i = 0; i < count; ++i) { midipulse time = read_32_bit(); read_gap(4); long tempo = read_16_bit() * factor; read_gap(8); RecTempo next; next.time = time; next.tempo = tempo / 100.0; /* the true BPM??? */ next.seconds = 0.0; RecTempo last; last.time = 0; last.tempo = next.tempo; last.seconds = 0.0; if (! m_wrk_data.m_tempos.empty()) { for (const RecTempo & rec : m_wrk_data.m_tempos) { if (rec.time >= time) break; last = rec; } next.seconds = last.seconds + ( ((time - last.time) / division) * (60.0 / last.tempo) ); } m_wrk_data.m_tempos.push_back(next); // Q_EMIT signalWRKTempo(time, tempo); if (rc().show_midi()) { long t = tempo / 100; printf("Tempo : tick %ld tempo %ld\n", long(time), t); } if (is_nullptr(m_current_seq)) m_current_seq = create_sequence(*m_performer); midibpm bpm = tempo / 100.0; midibpm tt = tempo_us_from_bpm(bpm); if (m_track_number == 0) { m_performer->set_beats_per_minute(bpm); m_performer->us_per_quarter_note(int(tt)); m_current_seq->us_per_quarter_note(int(tt)); } event e; midibytes bt; tempo_us_to_bytes(bt, tt); bool ok = e.append_meta_data(EVENT_META_SET_TEMPO, bt); if (ok) { Set_timestamp(e, time); m_current_seq->append_event(e); } } } /** * Allows for the timestamp to be scaled, if requested. */ void wrkfile::Set_timestamp (event & e, midipulse rawtime) { if (scaled()) /* adjust time via ppqn */ rawtime = midipulse(rawtime * ppqn_ratio()); e.set_timestamp(rawtime); } /** * Emitted after reading a System Exclusive Bank: * * - bank Sysex Bank number * - name Sysex Bank name * - autosend Send automatically after loading the song * - port MIDI output port * - data Sysex bytes * * void signalWRKSysex(int bank, const std::string& name, bool autosend, int port, * const QByteArray& data); */ void wrkfile::SysexChunk () { midibytes data; int bank = read_byte(); int len = read_16_bit(); bool autosend = (read_byte() != 0); int namelen = read_byte(); std::string name = read_string(namelen); if (read_byte_array(data, len)) { // Q_EMIT signalWRKSysex(bank, name, autosend, 0, data); if (rc().show_midi()) { printf ( "Sysex chunk : bank %d length %d name-length %d " "'%s' autosend %s\n", bank, len, namelen, name.c_str(), bool_to_string(autosend).c_str() ); } } not_supported("Sysex Chunk"); } void wrkfile::Sysex2Chunk () { midibytes data; int bank = read_16_bit(); int len = read_32_bit(); midibyte b = read_byte(); int port = (b & 0xf0) >> 4; bool autosend = (b & 0x0f) != 0; int namelen = read_byte(); std::string name = read_string(namelen); if (read_byte_array(data, len)) { // Q_EMIT signalWRKSysex(bank, name, autosend, port, data); if (rc().show_midi()) { printf ( "Sysex2 chunk: bank %d length %d name-length %d '%s' " "port %d autosend %s\n", bank, len, namelen, name.c_str(), ibyte(port), bool_to_string(autosend).c_str() ); } } not_supported("Sysex 2 Chunk"); } void wrkfile::NewSysexChunk () { std::string name; midibytes data; int bank = read_16_bit(); int len = int(read_32_bit()); int port = read_16_bit(); bool autosend = (read_byte() != 0); int namelen = read_byte(); name = read_string(namelen); if (read_byte_array(data, len)) { // Q_EMIT signalWRKSysex(bank, name, autosend, port, data); if (rc().show_midi()) { printf ( "New Sysex : bank %d length %d name-length %d" "'%s' port %d autosend %s\n", bank, len, namelen, name.c_str(), ibyte(port), bool_to_string(autosend).c_str() ); } } not_supported("New Sysex Chunk"); } /** * Handles reading an Extended Thru parameters chunk. This item was * introduced in Cakewalk version 4.0. These parameters are intended to * override the global variables' ThruOn value, so this record should come * after the WC_VARS_CHUNK record. It is optional. * * - mode (auto, off, on) * - port MIDI port * - channel MIDI channel * - keyPlus Note transpose * - velPlus Velocity transpose * - localPort MIDI local port * * void signalWRKThru (int mode, int port, int channel, int keyPlus, int velPlus, * int localPort); */ void wrkfile::ThruChunk () { read_gap(2); midibyte port = read_byte(); // 0 -> 127 midibyte channel = read_byte(); // -1, 0 -> 15 midibyte keyplus = read_byte(); // 0 -> 127 midibyte velplus = read_byte(); // 0 -> 127 midibyte localport = read_byte(); midibyte mode = read_byte(); // Q_EMIT signalWRKThru(mode, port, channel, keyplus, velplus, localport); if (rc().show_midi()) { int m = ibyte(mode); int p = ibyte(port); int lp = ibyte(localport); printf ( "Thru Mode : mode %d port %u channel %u key+%u vel+%u " "localport %d\n", m, p, unsigned(channel), unsigned(keyplus), unsigned(velplus), lp ); } not_supported("Thru Chunk"); } /** * Emitted after reading a track offset chunk: * * - track track number * - offset time offset * * void signalWRKTrackOffset(int track, int offset); */ void wrkfile::TrackOffset () { midishort track = read_16_bit(); midishort offset = read_16_bit(); // Q_EMIT signalWRKTrackOffset(track, offset); if (rc().show_midi()) { printf("Track Offset: Tr %d offset %d\n", int(track), int(offset)); } not_supported("Track Offset"); } /** * Emitted after reading a track repetition chunk. * * - track track number * - reps number of repetitions * * void signalWRKTrackReps(int track, int reps); */ void wrkfile::TrackReps () { midishort track = read_16_bit(); midishort reps = read_16_bit(); // Q_EMIT signalWRKTrackReps(track, reps); if (rc().show_midi()) { printf("Track Reps : Tr %d reps %d\n", int(track), int(reps)); } not_supported("Track Reps"); } /** * Emitted after reading a track patch chunk: * * - track track number * - patch * * void signalWRKTrackPatch(int track, int patch); */ void wrkfile::TrackPatch () { midishort track = read_16_bit(); /* track number */ midibyte patch = read_byte(); /* patch number */ // Q_EMIT signalWRKTrackPatch(track, patch); if (rc().show_midi()) { printf("Track Patch : Tr %d patch %d\n", int(track), int(patch)); } event e; e.set_channel_status(EVENT_PROGRAM_CHANGE, m_track_channel); e.set_data(patch); m_current_seq->append_event(e); } /** * Emitted after reading a SMPTE time format chunk: * * - frames frames/sec (24, 25, 29=30-drop, 30) * - offset frames of offset * * void signalWRKTimeFormat(int frames, int offset); */ void wrkfile::TimeFormat () { midishort fmt = read_16_bit(); midishort ofs = read_16_bit(); // Q_EMIT signalWRKTimeFormat(fmt, ofs); if (rc().show_midi()) { printf("SMPTE Time : frames/s %d offset %d\n", int(fmt), int(ofs)); } not_supported("Time Format"); } /** * Emitted after reading a comments chunk: * * - data file text comments * * void signalWRKComments(const std::string& data); */ void wrkfile::Comments () { int len = read_16_bit(); std::string text = read_string(len); // Q_EMIT signalWRKComments(text); if (rc().show_midi()) { printf("Comments : length %d, '%s'\n", len, text.c_str()); } not_supported("Comments"); } /** * Emitted after reading a variable chunk: * * This record may contain data in text or binary format. * * - name record identifier * - data record variable data * * void signalWRKVariableRecord(const std::string& name, const QByteArray& data); */ void wrkfile::VariableRecord (int max) { int datalen = max - 32; midibytes data; std::string name = read_var_string(); read_gap(31 - name.length()); if (read_byte_array(data, datalen)) { // Q_EMIT signalWRKVariableRecord(name, data); if (rc().show_midi()) { printf("Variable Rec: '%s' (data not shown)\n", name.c_str()); } } not_supported("Variable Record"); } /** * Handles reading an unknown chunk. * * - type chunk type * - data chunk data (not decoded) * * void signalWRKUnknownChunk(int type, const QByteArray& data); */ void wrkfile::UnknownChunk (int id) { // Q_EMIT signalWRKUnknownChunk(id, m_wrk_data.m_lastChunkData); if (rc().show_midi()) { printf ( "Unknown : id %d (%d bytes, not shown)\n", id, int(m_wrk_data.m_lastChunkData.size()) ); } } /** * Emitted after reading a new track prefix: * * - name. Track name. * - trackno. Track number. * - channel. Forced MIDI channel. * - pitch. Note transposition. * - velocity. Velocity increment. * - port. MIDI port number. * - selected. Track is selected. * - muted. Track is muted. * - loop. Track loop enabled. * * void signalWRKNewTrack * ( * const std::string& name, * int trackno, int channel, int pitch, * int velocity, int port, * bool selected, bool muted, bool loop * ); */ void wrkfile::NewTrack () { bool selected = false; bool loop = false; midishort trackno = read_16_bit(); midibyte len = read_byte(); std::string trackname = read_string(len); #if defined USE_Q_EMIT_CODE midishort bank = read_16_bit(); midishort patch = read_16_bit(); #else (void) read_16_bit(); (void) read_16_bit(); #endif midishort vol = read_16_bit(); midishort pan = read_16_bit(); midibyte key = read_byte(); midibyte vel = read_byte(); read_gap(7); midibyte port = read_byte(); midibyte channel = read_byte(); bool muted = read_byte() != 0; // Q_EMIT signalWRKNewTrack // ( // trackname, trackno, channel, key, vel, port, selected, muted, loop // ); if (rc().show_midi()) { printf ( "New Track : Tr %d ch %d key %d port %d " "selected %s muted %s loop %s\n", int(trackno), int(channel), int(key), ibyte(port), bool_to_string(selected).c_str(), bool_to_string(muted).c_str(), bool_to_string(loop).c_str() ); printf ( " : volume %d velocity %d pan %d\n", int(vol), int(vel), int(pan) ); } next_track(trackno, channel, trackname); #if defined USE_Q_EMIT_CODE if (short(bank) >= 0) { // Q_EMIT signalWRKTrackBank(trackno, bank); } if (short(patch) >= 0) { if (short(channel) >= 0) // always true with a byte range { // Q_EMIT signalWRKProgram(trackno, 0, channel, patch); } else { // Q_EMIT signalWRKTrackPatch(trackno, patch); } } #endif // USE_Q_EMIT_CODE } /** * Emitted after reading a software version chunk: * * - version software version string * * void signalWRKSoftVer(const std::string& version); */ void wrkfile::SoftVer() { int len = read_byte(); std::string vers = read_string(len); // Q_EMIT signalWRKSoftVer(vers); if (rc().show_midi()) { printf("Software Ver: %s\n", vers.c_str()); } not_supported("Soft Ver"); } /** * Emitted after reading a track name chunk: * * - track track number * - name track name * * void signalWRKTrackName(int track, const std::string& name); */ void wrkfile::TrackName () { int track = read_16_bit(); int len = read_byte(); std::string name = read_string(len); // Q_EMIT signalWRKTrackName(track, name); if (rc().show_midi()) { printf ( "Track Name : Tr %d name-length %d name '%s'\n", track, len, name.c_str() ); } /* * \todo */ } /** * Emitted after reading a string event types chunk: * * - strs. List of declared string event types. * * void signalWRKStringTable(const std::stringList& strs); */ void wrkfile::StringTable() { std::list table; int rows = read_16_bit(); if (rows > 0 && rc().show_midi()) { printf("String Table: %d items:", rows); } for (int i = 0; i < rows; ++i) { int len = read_byte(); std::string name = read_string(len); int idx = read_byte(); table.push_back(name); if (rc().show_midi()) { printf(" %d. %s", idx, name.c_str()); if (i == (rows-1)) printf("\n"); } } // Q_EMIT signalWRKStringTable(table); not_supported("String Table"); } void wrkfile::LyricsStream () { midishort track = read_16_bit(); int events = read_32_bit(); NoteArray(track, events); not_supported("Lyrics Stream"); } /** * Gets Cakewalk style track volume. */ void wrkfile::TrackVol () { midishort track = read_16_bit(); /* track number */ int vol = read_16_bit(); /* should be 1 byte */ // Q_EMIT signalWRKTrackVol(track, vol); if (rc().show_midi()) { printf("Track Volume: Tr %d volume %d\n", int(track), vol); } event e; e.set_channel_status(EVENT_CONTROL_CHANGE, m_track_channel); e.set_data(EVENT_CTRL_VOLUME, midibyte(vol)); m_current_seq->append_event(e); } /** * See TrackOffset(). This version reads a long offset, instead of a short * offset. */ void wrkfile::NewTrackOffset () { midishort track = read_16_bit(); int offset = read_32_bit(); // Q_EMIT signalWRKTrackOffset(track, offset); if (rc().show_midi()) { printf("N track offs: Tr %d offset %d\n", int(track), offset); } not_supported("New Track Offset"); } /** * Gets the bank ID of the track. */ void wrkfile::TrackBank () { int track = int(read_16_bit()); int bank = int(read_16_bit()); // Q_EMIT signalWRKTrackBank(track, bank); if (rc().show_midi()) { printf("Track Bank : Tr %d bank %d\n", track, bank); } /* * \todo * Use this number as the screenset value. */ not_supported("Track Bank"); } /** * Emitted after reading a segment prefix chunk: * * - track track number * - time segment time offset * - name segment name * * void signalWRKSegment(int track, long time, const std::string& name); * */ void wrkfile::SegmentChunk () { int track = read_16_bit(); int offset = read_32_bit(); read_gap(8); int len = int(read_byte()); std::string name = read_string(len); read_gap(20); // Q_EMIT signalWRKSegment(track, offset, name); if (rc().show_midi()) { printf ( "Segment : Tr %d offset %d name-length %d name '%s'\n", track, offset, len, name.c_str() ); } int events = read_32_bit(); NoteArray(track, events); } void wrkfile::NewStream() { int track = read_16_bit(); int len = int(read_byte()); std::string name = read_string(len); // Q_EMIT signalWRKSegment(track, 0, name); if (rc().show_midi()) { printf ( "New Stream : Tr %d name-length %d name '%s'\n", track, len, name.c_str() ); } int events = int(read_32_bit()); NoteArray(track, events); } void wrkfile::TrackNumPlusChunk () { if (rc().show_midi()) { printf("TrackNumPlus: At seq number %d\n", m_seq_number); } } /** * After reading the last chunk of a WRK file, this function finalizes any * last track that was extant. * * void signalWRKEnd(); * * The original emitted a signal, but that's not needed when simply reading * a WRK file in the Seq66 library. */ void wrkfile::EndChunk () { // Q_EMIT signalWRKEnd(); if (rc().show_midi()) { printf("End chunk : at seq number %d\n", m_seq_number); } finalize_track(); } int wrkfile::read_chunk () { int ck = int(read_byte()); if (ck != WC_END_CHUNK) { int ck_len = read_32_bit(); size_t start_pos = pos(); size_t final_pos = start_pos + ck_len; read_raw_data(ck_len); read_seek(start_pos); switch (ck) { case WC_TRACK_CHUNK: TrackChunk(); // names, number, velocity, mute/loop status break; case WC_VARS_CHUNK: VarsChunk(); // Cakewalk global variables break; case WC_TIMEBASE_CHUNK: TimebaseChunk(); // gets PPQN value m_division for whole tune break; case WC_STREAM_CHUNK: StreamChunk(); // note, control, program, pitchbend, etc. break; case WC_METER_CHUNK: MeterChunk(); // gets a time signature break; case WC_TEMPO_CHUNK: TempoChunk(100); // gets the BPM tempo break; case WC_NTEMPO_CHUNK: TempoChunk(); // gets the BPM tempo break; case WC_SYSEX_CHUNK: SysexChunk(); // handle SysEx messages break; case WC_THRU_CHUNK: ThruChunk(); // Extended Thru: mode, port, channel, ... break; case WC_TRKOFFS_CHUNK: TrackOffset(); // "short" track offset break; case WC_TRKREPS_CHUNK: TrackReps(); // repetition count for a track break; case WC_TRKPATCH_CHUNK: TrackPatch(); // track number and patch number break; case WC_TIMEFMT_CHUNK: TimeFormat(); // SMPTE frames, frames/sec, offset break; case WC_COMMENTS_CHUNK: Comments(); // data file text comments break; case WC_VARIABLE_CHUNK: VariableRecord(ck_len); // record identifier & variable data break; case WC_NTRACK_CHUNK: NewTrack(); // track #, channel, pitch, mute/loop... break; case WC_SOFTVER_CHUNK: SoftVer(); // software version string break; case WC_TRKNAME_CHUNK: TrackName(); // track number and name break; case WC_STRTAB_CHUNK: StringTable(); // list of declared string event types break; case WC_LYRICS_CHUNK: LyricsStream(); // processes the note array break; case WC_TRKVOL_CHUNK: TrackVol(); // Cakewalk style track volume break; case WC_NTRKOFS_CHUNK: NewTrackOffset(); // "long" track offset break; case WC_TNUMPLUS_CHUNK: TrackNumPlusChunk(); break; case WC_TRKBANK_CHUNK: TrackBank(); // the bank ID of the track break; case WC_METERKEY_CHUNK: MeterKeyChunk(); // gets a time signature and key (scale) break; case WC_SYSEX2_CHUNK: Sysex2Chunk(); // handle SysEx messages break; case WC_NSYSEX_CHUNK: NewSysexChunk(); break; case WC_SGMNT_CHUNK: SegmentChunk(); // processes a note array break; case WC_NSTREAM_CHUNK: NewStream(); // processes a note array break; default: UnknownChunk(ck); break; } read_seek(final_pos); } return ck; } } // namespace seq66 /* * wrkfile.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/os/daemonize.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file daemonize.cpp * \library seq66 application (from PSXC library) * \author Chris Ahlstrom * \date 2005-07-03 to 2007-08-21 (pre-Sequencer24/64) * \updates 2024-11-07 * \license GNU GPLv2 or above * * Daemonization module of the POSIX C Wrapper (PSXC) library * Copyright (C) 2005-2025 by Chris Ahlstrom * * Provides a function to make it easy to run an application as a (Linux) * daemon. There are large differences between POSIX daemons and Win32 * services. Thus, this module is mostly Linux/POSIX-specific. * * Updating daemonize(). Instead of calling exit on error, it now * returns the error code; the return value is now int instead of * uint32_t, and uint32_t is now mode_t to reflect the return type of * umask(2). Note that digging reveals that mode_t is an unsigned 32-bit * type anyway. * * We also calls fork() again to insure that the application is not * the session leader, and implement the "flags" parameters; both steps * are described in Michael Kerrisk's book, "The Linux Programming * Interface", 2010. * * Questions: * * - Do we also want to add some code to insure that the application * can run only one instance? * - Do we want to handle SIGHUP? * * The quick-and-dirty story on creating a daemon. * * -# Fork off the parent process. * -# Change file mode mask (umask) * -# Open any logs for writing. * -# Create a unique Session ID (SID) * -# Change the current working directory to a safe place. * -# Close standard file descriptors. * -# Enter actual daemon code. * * This module handles all of the above. Things not yet handled: * * -# Generation of various helpful files: * - PID file * - Lock file * - Error and information output file (though we do log some * information via the syslog). * -# Thorough setting of the environment. * -# Thorough handling of user IDs and groups. * -# Redirection of the standard outputs to files. * * See this project for an application that does all or most of the above: * * https://github.com/bmc/daemonize * * \todo * There is a service wrapper available under Win32. It is called * "srvhost.exe". At this time, we *still* don't know how to use it, but * it is available, and Windows XP seems to use it quite a bit. */ #include /* std::atomic */ #include /* EXIT_FAILURE for 32-bit builds */ #include /* memset() */ #include "seq66_features.hpp" /* seq66::seq_app_name() */ #include "os/daemonize.hpp" /* daemonization functions & macros */ #include "util/basic_macros.hpp" /* errprint() */ #include "util/filefunctions.hpp" /* seq66::get_full_path() etc. */ #if defined SEQ66_PLATFORM_UNIX /* it's not just LINUX, dude! */ #include /* O_RDWR flag */ #include /* struct sigaction */ #include /* umask(), etc. */ #include /* syslog() and related constants */ #include /* exit(), setsid() */ #define STD_CLOSE close #define STD_OPEN open #define STD_O_RDWR O_RDWR #define DEV_NULL "/dev/null" /* * In Linux, dup2(int oldfd, int newfd) uses newfd as the new file descriptor, * closing it if open before reusing it. dup2() returns this new file * descriptor on success, and a (-1) on error (setting errno, too). */ #define STD_DUP2 dup2 #define STD_DUP2_SUCCESS(rc) (rc >= 0) #elif defined SEQ66_PLATFORM_WINDOWS /* * For Windows, only the reroute_stdio() function is defined, currently. */ #include /* WaitForSingleObject(), INFINITE */ #include /* _O_RDWR */ #include /* _open(), _close() */ #include /* Windows _getpid() function */ #include /* recent Windows "wait" functions */ #include /* Windows S_IWUSR, S_IWGRP, etc. */ #define STD_CLOSE _close #define STD_OPEN _open #define STD_O_RDWR _O_RDWR #define DEV_NULL "NUL" /* * In Windows, _dup2() has the same parameters with the same meaning, but * it returns 0 for success. */ #define STD_DUP2 _dup2 #define STD_DUP2_SUCCESS(rc) (rc == 0) #endif namespace seq66 { #if defined SEQ66_PLATFORM_POSIX_API /** * * Performs a number of actions needed by a UNIX daemon. * * These actions are layed out in the following URLs. * * http://www.linuxprofilm.com/articles/linux-daemon-howto.html#s1 * http://www.deez.info/sengelha/projects/sigrandd/doc/#5 * * -# Fork off the parent process. * -# Change file mode mask (umask). * -# Open the system log for writing (optional). * -# Create a unique Session ID (SID). * -# Open any logs for writing. * -# Change the current working directory to a safe place. * -# Close standard file descriptors. * -# Enter actual daemon code. * -# Close the log upon exiting the daemon. * * \algorithm * -# Fork an identical child process. After the fork() call, we now * have a copy of this application running as a child process. We can * then kill off the parent process using exit(). The child inherits * the process group ID of the parent but gets a new process ID, so * we're guaranteed that the child is not a process group leader. This * is a prerequisite for the call to setsid() that is done later. * -# Once this succeeds, we want to change the file-mode mask so that * the daemon has access to system files that it creates. The file * mode creation mask that's inherited could have been set to deny * certain permissions. * -# Optional: Open the system log for writing. We make * the system log our error-log, using the set_syslogging() * function of the errorlogging.c module. * -# The child process must get a unique SID (session ID) from the * kernel in order to operate. Otherwise, the child process becomes * an orphan in the system. The pid_t type is used for the new SID. * The process becomes a session leader of a new session, becomes the * process group leader of a new process group, and has no controlling * terminal. * -# The current working directory should be changed to some place that * is guaranteed to always be there. Since many Linux distributions do * not completely follow the Linux Filesystem Hierarchy standard, the * only directory that is guaranteed to be there is the root (/). * However, we make it a command-line option that defaults to ".". * See the functions in the audio_arguments.c/h module. * Since daemons normally exist until the system is rebooted, if the * daemon stays on a mounted filesystem, that filesystem cannot be * unmounted. * -# An important step in setting up a daemon is closing out the * standard file descriptors (STDIN, STDOUT, STDERR). Since a daemon * cannot use the terminal, these file descriptors are useless, and a * potential security hazard. * -# As the last step, we check out the command-line arguments and the * audio parameters, and use them to start the desired task. * -# If the server is stopped normally, we go ahead and call closelog(), * even though it is optional. * * \param [inout] previousmask * Returns the previous mask for storage. It should be saved for later * restoration. * * \param flags * Provides potential bitmask values (daemonize_flags) for the call * to this function, as per Kerrisk's book. * * \param appname * Name of the application to daemonize. * * \param cwd * Current working directory to set. Defaults to ".", for now. * * \param mask * The umask value to set. Defaults to 0. * * \return * Returns either EXIT_FAILURE or EXIT_SUCCESS. If EXIT_SUCCESS is * returned, the application (the parent) should call exit(). */ daemonization daemonize ( mode_t & previousmask, const std::string & appname, int flags, const std::string & cwd, int mask ) { static std::string s_app_name; /* to survive forking? */ previousmask = 0; s_app_name.clear(); /* blank out the base app name */ if (! appname.empty()) s_app_name = appname; /* copy the base app name */ /* * fork(): * * - On success, the PID of the child is returned in the parent, * and 0 is returned in the child. * - On failure, -1 is returned in the parent, and there is no * child. */ pid_t pid; if ((flags & d_flag_fake_fork) != 0) /* just pretend a fork occurred */ { pid = 0; /* pretend we're in the child */ flags = d_flag_fake_fork_flags; } else pid = fork(); /* 1. Fork the parent process */ if (is_posix_error(pid)) /* -1 */ /* process creation failed */ { errprint("parent fork() failed"); return daemonization::failure; /* exit() parent as failure */ } else if (pid != 0) /* child creation succeeded */ { return daemonization::parent; /* exit() parent w/success */ } else /* we're in child process */ { /* * Now we are the child process. * * Create a new session and set the process group ID. This succeeds * if the calling process is not a process group leader. If it fails, * then we become the leader of the new session and exit with * EXIT_FAILURE. */ pid_t sid = setsid(); /* 2. Get a new session ID... */ if (sid < 0) /* ... couldn't get one */ return daemonization::failure; /* exit() child as a failure */ if ((flags & d_flag_no_fork_twice) == 0) { /* * Now ensure that we are not the session leader. This eliminates * the possibility of the application/device does not become a * controlling terminal. */ pid = fork(); /* fork you again! */ if (is_posix_error(pid)) { errprint("child fork() failed"); return daemonization::failure; } else if (pid != 0) { return daemonization::parent; } } if ((flags & d_flag_no_umask) == 0) { if (mask > 0) previousmask = ::umask(mask); /* 3. Save & set user mask */ else (void) ::umask(0); /* 3. clear file mask */ } if ((flags & d_flag_no_chdir) == 0) /* this is only for ROOT */ { int rc = chdir("/"); if (rc != 0) { errprint("chdir('/') failed"); return daemonization::failure; } } if ((flags & d_flag_no_close_files) == 0) { int maxfd = ::sysconf(_SC_OPEN_MAX); if (maxfd == (-1)) maxfd = c_daemonize_max_fd; /* this is just a guess */ for (int fd = 0; fd < maxfd; ++fd) (void) close(fd); } if ((flags & d_flag_no_reopen_stdio) == 0) (void) reroute_stdio_to_dev_null(); if (s_app_name.empty()) s_app_name = "anonymous daemon"; if ((flags & d_flag_no_syslog) == 0) /* system log */ openlog(s_app_name.c_str(), LOG_CONS|LOG_PID, LOG_USER); if ((flags & d_flag_no_set_currdir) == 0) { bool cwdgood = cwd != "." && ! cwd.empty(); if (cwdgood) { if (! set_current_directory(cwd)) return daemonization::failure; } } /* * No longer needed. Caller can use it. Also see * d_flag_no_reopen_stdio. * * (void) reroute_stdio("", true); // 6. close standard files // */ if ((flags & d_flag_no_syslog) == 0) /* system log */ syslog(LOG_NOTICE, "daemon started"); } return daemonization::child; } /* * This function undoes the daemon setup. It undoes the actions of the * daemonize() function by first restoring the previous umask. Then, it * restores the error-level of the application to the default error-level * ("--error"). Not sure how useful this function is, since the daemon is * probably exiting anyway. * * \param previous_umask * Previous umask value, for later restoring. */ void undaemonize (mode_t previous_umask) { syslog(LOG_NOTICE, "seq66 daemon exited"); closelog(); if (previous_umask != 0) (void) umask(previous_umask); /* restore user mask */ } #endif // SEQ66_PLATFORM_POSIX_API /** * \todo * Implement "daemonizing" for Windows, including redirection to the * Windows Event Log. Still need to figure out a way to do this very * simply, a la' Microsoft's 'svchost' executable. */ /** * Route the standard terminal file descriptors to "/dev/null". */ bool reroute_stdio_to_dev_null () { int rc = STD_CLOSE(STDIN_FILENO); bool result = rc == 0; if (result) { int fd = STD_OPEN(DEV_NULL, STD_O_RDWR); result = fd == STDIN_FILENO; if (result) { int newfd = STD_DUP2(STDIN_FILENO, STDOUT_FILENO); result = STD_DUP2_SUCCESS(newfd); if (result) { newfd = STD_DUP2(STDIN_FILENO, STDOUT_FILENO); result = STD_DUP2_SUCCESS(newfd); } } if (result) warnprint("Standard I/O rerouted to " DEV_NULL); else file_error("Failed to reroute standard I/O", DEV_NULL); } return result; } /** * Alters the standard terminal file descriptors so that they either route to * to a log file, under Linux or Windows. * * \param logfile * The optional name of the file to which to log messages. Defaults to * an empty string. * * \param closem * Just closes the standard file descriptors, rather than rerouting them * to /dev/null. Defaults to false. This is the value needed if the * \a logfile parameter is not empty. * * \return * Returns true if the log-file functionality has been enabled. * I think :-D. */ bool close_stdio () { bool result = true; int rc = STD_CLOSE(STDIN_FILENO); if (rc == (-1)) result = false; rc = STD_CLOSE(STDOUT_FILENO); if (rc == (-1)) result = false; rc = STD_CLOSE(STDERR_FILENO); if (rc == (-1)) result = false; return result; } /** * Reroute stdout and stderr to the same log file. This is allowed * only once. * * \param logfile * Provides the full path to the log file. * * \return * Returns true if the rerouting worked or was already done. */ bool reroute_stdio (const std::string & logfile) { static bool s_not_rerouted = true; bool result = false; if (s_not_rerouted) { if (logfile.empty()) /* route output to /dev/null */ { result = reroute_stdio_to_dev_null(); } else { int rc = STD_CLOSE(STDOUT_FILENO); result = rc == 0; if (result) { int flags = O_WRONLY | O_CREAT | O_APPEND ; mode_t mode = S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP ; int fd = open(logfile.c_str(), flags, mode); result = fd != (-1); if (result) { int newfd = STD_DUP2(fd, STDOUT_FILENO); result = STD_DUP2_SUCCESS(newfd); if (result) { newfd = STD_DUP2(fd, STDERR_FILENO); result = STD_DUP2_SUCCESS(newfd); if (result) { std::string logpath = get_full_path(logfile); std::string normedpath = normalize_path(logpath); printf ( "\n%s\n%s\n%s\n", seq_app_name().c_str(), normedpath.c_str(), current_date_time().c_str() ); s_not_rerouted = false; } else file_error("Dup2 failed", "stderr"); } else file_error("Dup2 failed", "stdout"); } } if (! result) file_error("Failed to reroute standard I/O", logfile); } } else result = true; /* simulate success */ return result; } /* * -------------------------------------------------------------------------- * Functions common to Linux and Windows * -------------------------------------------------------------------------- */ /* * Session-handling atomic booleans. */ static std::atomic sg_needs_close {}; static std::atomic sg_needs_save {}; static std::atomic sg_restart {}; bool session_restart () { bool result = sg_restart; if (sg_needs_close) result = false; return result; } /** * Returns the boolean to indicate a request to close the application. */ bool session_close () { bool result = sg_needs_close; if (result) warn_message("App marked for close..."); sg_needs_close = false; return result; } /** * Returns the boolean to indicate a request to save the current sequence * file. */ bool session_save () { bool result = sg_needs_save; if (result) warn_message("Marked for file_save..."); sg_needs_save = false; return result; } void signal_for_save () { sg_needs_save = true; } void signal_for_exit () { sg_needs_close = true; } /** * Sets the flag for restarting the main() functions, instead of exiting. */ void signal_for_restart () { sg_restart = true; } void signal_end_restart () { sg_restart = false; } /* * -------------------------------------------------------------------------- * Functions for Linux * -------------------------------------------------------------------------- */ #if defined SEQ66_PLATFORM_UNIX // LINUX /** * Provides a basic session handler, called upon receipt of a POSIX signal. * Note that SIGSTOP and SIGKILL cannot be blocked, ignored, or caught by a * handler. Also note that SIGKILL bypasses the SIGTERM handler; it is a last * resort for runaway processes that don't respond to SIGTERM. */ static void session_handler (int sig) { psignal(sig, "Signal caught"); switch (sig) { case SIGINT: /* 2: Ctrl-C "terminal interrupt" */ sg_needs_close = true; break; case SIGTERM: /* 15: "terminate process" */ sg_needs_close = true; break; case SIGUSR1: /* 10: "user-defined signal 1 */ sg_needs_save = true; break; } } /** * Sets up the application to intercept SIGINT, SIGTERM, and SIGUSR1. * * \param earlyexit * It turns out that the test for the need to remap ports occurs * (in Seq66} before this function is called. We don't want to continue * to run in this case. * * \return * Returns true if the application can continue. */ bool session_setup (bool earlyexit) { bool result = ! earlyexit; if (result) { struct sigaction action; memset(&action, 0, sizeof action); action.sa_handler = session_handler; sg_needs_close = sg_needs_save = sg_restart = false; sigaction(SIGINT, &action, NULL); /* SIGINT is 2 */ sigaction(SIGTERM, &action, NULL); /* SIGTERM is 15 */ sigaction(SIGUSR1, &action, NULL); /* SIGUSR1 is 10 */ } return result; } /** * Looks up an executable in the process list using the pidof program. This * function copies the pidof command line, then opens a pipe to that process * to read from it. If anything is read, then the process ID is calculated * and returned. Otherwise, 0 is returned. * * Example: "pidof nsmd", which will emit a PID if nsmd is running and return * 1 if the nsmd is not running. * * This is actually way too strict, disabling the detection of NSM workalikes * such as RaySession. */ pid_t get_pid_by_name (const std::string & exename) { #if defined SEQ66_DEFINE_GET_PID_BY_NAME static const int s_pid_size = 200; /* really only need about 10! */ pid_t result = 0; char cmd[s_pid_size + 1]; snprintf(cmd, s_pid_size, "pidof %s", exename.c_str()); FILE * fp = popen(cmd, "r"); if (not_nullptr(fp)) { size_t count = fread(cmd, sizeof(char), s_pid_size, fp); fclose(fp); if (count > 0) { result = atoi(cmd); file_message(exename, std::to_string(result)); } } return result; #else (void) exename; return 0; #endif } bool pid_exists (const std::string & exename) { return get_pid_by_name(exename) > 0; } std::string get_pid () { long p = long(getpid()); return std::to_string(p); } std::string get_process_name () { pid_t pid = getpid(); return get_process_name(pid); } /** * This Linux-only function reads /proc/PID/comm to get the (short) name of * the process with the give PID. * * It has been stated that the size of the process name in /proc/PID/comm is * less than TASK_COMM_LEN = 16. Thus, the return value might be a truncated * process name. */ std::string get_process_name (pid_t pid) { std::string result; char temp[32]; snprintf(temp, sizeof temp, "/proc/%d/comm", int(pid)); FILE * f = fopen(temp, "r"); if (not_nullptr(f)) { size_t sz = fread(temp, sizeof(char), sizeof temp , f); if (sz > 0) { if (temp[sz - 1] == '\n') temp[sz - 1] = 0; result = std::string(temp); } fclose(f); } return result; } std::string get_parent_process_name () { pid_t parentpid = getppid(); return get_process_name(parentpid); } #else /* * -------------------------------------------------------------------------- * Functions for Windows * -------------------------------------------------------------------------- */ bool session_setup (bool earlyexit) { bool result = ! earlyexit; if (result) { sg_needs_close = sg_needs_save = sg_restart = false; } return result; } bool pid_exists (const std::string & /*exename*/) { return false; /* to do, if possible */ } pid_t get_pid_by_name (const std::string & /*exename*/) { return 0; } std::string get_pid () { long p = long(_getpid()); return std::to_string(p); } std::string get_process_name () { pid_t pid = _getpid(); return get_process_name(pid); } std::string get_process_name (pid_t /*pid*/) { std::string result; return result; /* TO DO */ } /* * See https://stackoverflow.com/questions/29939893/get-parent-process-name-windows */ std::string get_parent_process_name () { /* * Not available on Windoze. And used only for the Non/New Session Manager, * which is not supported on Windoze. * * long parentpid = long(_getppid()); */ return std::string("None"); } #endif // defined SEQ66_PLATFORM_UNIX } // namespace seq66 /* * vim: ts=4 sw=4 et ft=cpp */ ================================================ FILE: libseq66/src/os/shellexecute.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file shellexecute.cpp * \library seq66 application * \author Chris Ahlstrom * \date 2022-05-13 * \updates 2025-05-30 * \license GNU GPLv2 or above * * Provides support for cross-platform time-related functions. */ #include /* int std::system(commandline) */ #include "seq66_platform_macros.h" /* detecting Linux vs Windows */ #include "cfg/settings.hpp" /* for usr().use_pdf_viewer() etc. */ #include "os/shellexecute.hpp" /* seq66::open_document(), etc. */ #include "util/filefunctions.hpp" /* seq66::file_exists() */ #include "util/strfunctions.hpp" /* seq66::widen_string() */ #if defined SEQ66_PLATFORM_WINDOWS #include /* ShellExecute() [shellapi.h] */ #endif namespace seq66 { /** * Generic C++ method for executing a command-line. */ bool command_line (const std::string & cmdline) { bool result = ! cmdline.empty(); if (result) { int rc = std::system(cmdline.c_str()); result = rc == 0; if (! result) (void) file_error("Command failed", cmdline); } return result; } /** * Opens a PDF file. Meant to be made more flexible later: * * - Add a user-configurable path to a PDF view, if specified. * Currently, qsmainwnd uses open_url() if the local file * is not found. * - Handle executable and file paths with spaces. */ bool open_pdf (const std::string & pdfspec) { std::string cmd = usr().user_pdf_viewer(); if (cmd.empty() || is_questionable_string(cmd)) { return open_document(pdfspec); } else { cmd += " "; cmd += pdfspec; #if ! defined SEQ66_PLATFORM_WINDOWS cmd += "&"; #endif return command_line(cmd); } } bool open_url (const std::string & url) { std::string cmd = usr().user_browser(); if (cmd.empty() || is_questionable_string(cmd)) { return open_document(url); } else { cmd += " "; cmd += url; #if ! defined SEQ66_PLATFORM_WINDOWS cmd += "&"; #endif return command_line(cmd); } } /** * Currently not used anywhere. */ bool open_local_url (const std::string & url) { return open_url(url); } #if defined SEQ66_PLATFORM_UNIX // LINUX bool open_document (const std::string & documentpath) { bool result = ! documentpath.empty(); if (result) { std::string cmd = "/usr/bin/xdg-open"; cmd += " "; cmd += documentpath; cmd += "&"; result = command_line(cmd); if (! result) (void) file_error("xdg-open failed", documentpath); } return result; } /** * Creates a command-line of the following form and executes it: * * cp -r sourcedir/ destdir/ * * We also check that the destdir is really a directory. */ bool copy_directory_recursive ( const std::string & sourcedir, const std::string & destdir ) { bool result = file_exists(sourcedir) && file_is_directory(destdir); if (result) { std::string cmdline = "cp -r "; cmdline += unix_normalize_path(sourcedir); /* end with a solidus */ cmdline += "*"; cmdline += " "; cmdline += unix_normalize_path(destdir); /* end with a solidus */ result = command_line(cmdline); } return result; } #elif defined SEQ66_PLATFORM_WINDOWS /* * Information for the future. However, Because ShellExecute() can delegate * execution to Shell extensions implementations) that are activated using * Component Object Model (COM), COM should be initialized before * ShellExecuteEx() is called. * \verbatim HINSTANCE ShellExecuteA ( [in, optional] HWND hwnd, [in, optional] LPCSTR lpOperation, [in] LPCSTR lpFile, [in, optional] LPCSTR lpParameters, [in, optional] LPCSTR lpDirectory, [in] INT nShowCmd ); \endverbatim * * - hwnd. A handle to the parent window used for displaying a UI or error * messages. This value can be NULL if the operation is not associated * with a window. * - lpOperation. A verb that specifies the action to be performed. * Generally, the actions available from an object's shortcut menu are * usable. Common verbs for Seq66 usage: edit. Launches an editor and * opens the document for editing. If lpFile is not a document file, the * function will fail. * - open. Opens the item specified by the lpFile parameter. Can be a * file or folder. * - NULL. The default verb is used, if available. If not, the "open" * verb is used. If neither verb is available, the system uses the * first verb listed in the registry. * - lpFile. A string that specifies the file/object on which to execute * the verb. * - lpParameters. If lpFile specifies an executable file, this parameter * is a pointer to a null-terminated string that specifies the parameters * to be passed to the application. If lpFile specifies a document, * lpParameters should be NULL. * - lpDirectory. A string specifying the default (working) directory for * the action. If this value is NULL, the current working directory is * used. * - nShowCmd. The flags that specify how an application is to be displayed * when it is opened. * - Return value. Success is a value greater than 32. If the function * fails, it returns an error value that indicates the cause of the * failure. The return value is cast as an HINSTANCE for backward * compatibility with 16-bit Windows applications. It is not a true * HINSTANCE, however. It can be cast only to an INT_PTR and compared to * either 32 or error codes. (See the web page). */ bool open_document (const std::string & documentpath) { bool result = ! documentpath.empty(); if (result) { std::wstring op = widen_string("open"); std::wstring path = widen_string(documentpath); HINSTANCE rc = ::ShellExecute ( NULL, op.c_str(), path.c_str(), NULL, NULL, SW_SHOW ); result = uintptr_t(rc) > 32; if (! result) (void) file_error("Command failed", documentpath); } return result; } /** * Currently a quick-and-dirty implementation using system() rather than * nftw(). The flags for xcopy used are: * * - /s. Copy all directories and subdirectories. * - /e. Copy directories/subdirectories, including empty ones. * - /k. Copies the file attributes. * - /h. Copy hidden and system files as well. * - /i. If destination does not exist and copying more than one * file, assume the destination is a directory. * - /q. Quiet; do no display filenames while copying. * - /y. Suppresses prompting to confirm overwriting an existing * file. * * Here is the command line: * * xcopy sourcedir/ destdir /s /e /k /h /i /q /y * * Do we need to os_normalize()? */ bool copy_directory_recursive ( const std::string & sourcedir, const std::string & destdir ) { bool result = file_exists(sourcedir) && file_is_directory(destdir); if (result) { std::string cmdline = "xcopy "; cmdline += unix_normalize_path(sourcedir); /* end with a solidus */ cmdline += " "; cmdline += destdir; /* no solidus */ cmdline += "* "; cmdline += "/s /e /k /h /i /q /y"; result = command_line(cmdline); } return result; } #endif } // namespace seq66 /* * vim: ts=4 sw=4 et ft=cpp */ ================================================ FILE: libseq66/src/os/timing.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file timing.cpp * \library seq66 application (from PSXC library) * \author Chris Ahlstrom * \date 2005-07-03 to 2007-08-21 (pre-Sequencer24/64) * \updates 2023-12-22 * \license GNU GPLv2 or above * * Provides support for cross-platform time-related functions. */ #include "util/basic_macros.hpp" /* error_message() */ #include "os/timing.hpp" /* seq66::microsleep(), etc. */ #if defined SEQ66_PLATFORM_UNIX // _LINUX #include /* error numbers */ #include /* pthread_setschedparam() */ #include /* sched_yield(), _get_priority() */ #include /* snprintf() */ #include /* memset() */ #include /* C::nanosleep(2) */ #include /* exit(), setsid() */ #elif defined SEQ66_PLATFORM_WINDOWS /* * For Windows, only the microsleep() function is defined, currently. */ #include /* WaitForSingleObject(), INFINITE */ #include /* Win32 timeGetTime() [!timeapi.h] */ #include /* recent Windows "wait" functions */ #endif namespace seq66 { /** * Provides a standard wait time to use, in an explicit function. */ int std_sleep_us () { return 10; /* was c_default_sleep_time_us */ } /* * -------------------------------------------------------------------------- * microsleep() and millisleep() * -------------------------------------------------------------------------- */ /* * This free-function in the seq66 namespace provides a way to suspend a * thread for a small amount of time, or to yield the processor. * * \linux * We can use the usleep(3) function. * * \unix * In POSIX, select() can return early if any signal occurs. We don't * correct for that here at this time. Actually, it is a convenient * feature, and we wish that Sleep() would provide it. * * \win32 * In Windows, the Sleep(0) function does not sleep, but it does cede * control of the thread to the operating system, which then schedules * another thread to run. * * \warning * Please note that this function isn't all that accurate for small * sleep values, due to the time taken to set up the operation, and * resolution issues in many operating systems. * * \param ms * The number of milliseconds to "sleep". For Linux and Windows, the * microsleep() function handles this, including the case of ms == 0. * * \return * Returns true if the ms parameter was 0 or greater. */ bool millisleep (int ms) { bool result = ms >= 0; if (result) { #if defined SEQ66_PLATFORM_UNIX // _LINUX result = microsleep(ms * 1000); #elif defined SEQ66_PLATFORM_UNIX struct timeval tv; struct timeval * tvptr = &tv; tv.tv_usec = long(ms % 1000) * 1000; tv.tv_sec = long(ms / 1000); result = select(0, 0, 0, 0, tvptr) != (-1); #else result = microsleep(ms * 1000); #endif } return result; } #if defined SEQ66_PLATFORM_UNIX // _LINUX /** * Sleeps for the given number of microseconds. nanosleep() is a Linux * function which has some advantage over sleep(3) and usleep(3), such as not * interacting with signals. It seems that it supports a non-busy wait. * * The typical stack call is around 10 x 5 us, or 50 us. We've been using 100 * us for calls to microsleep(), which does division, modulo, and * multiplication calls. * * \param us * Provides the desired number of microseconds to wait. Must be greater * than 0. Use thread_yield() if you just want to yield the CPU. * Use std_sleep_us() if you want the normal sleep time. * * \return * Returns true if the full sleep occurred, or if interruped by a signal. */ bool microsleep (int us) { bool result = us > 0; if (result) { int rc; if (us == std_sleep_us()) /* an optimization */ { static bool s_uninitialized = true; static timespec s_ts; if (s_uninitialized) { s_uninitialized = false; s_ts.tv_sec = 0; s_ts.tv_nsec = us * 1000; } rc = nanosleep(&s_ts, NULL); } else { struct timespec ts; ts.tv_sec = us / 1000000; ts.tv_nsec = (us % 1000000) * 1000; /* 1000 ns granularity */ rc = nanosleep(&ts, NULL); } result = rc == 0 || rc == EINTR; } return result; } #elif defined SEQ66_PLATFORM_WINDOWS /** * This implementation comes from https://gist.github.com/ngryman/6482577 and * performs busy-waiting, meaning it will NOT relinquish the processor. * * \param us * Provides the desired number of microseconds to wait. Must be greater * than 0. * * \return * Returns true only if all calls succeeded. It doesn't matter if the * wait completed, at this point. */ bool microsleep (int us) { bool result = us > 0; if (result) { HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL); bool result = timer != NULL; if (result) { LARGE_INTEGER ft; ft.QuadPart = -(10 * (__int64) us); result = SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0) != 0; if (result) result = WaitForSingleObject(timer, INFINITE) != WAIT_FAILED; CloseHandle(timer); } } return result; } #endif /* * -------------------------------------------------------------------------- * thread_yield() * -------------------------------------------------------------------------- */ #if defined SEQ66_PLATFORM_UNIX // _LINUX void thread_yield () { (void) sched_yield(); /* always succeeds in Linux */ } #elif defined SEQ66_PLATFORM_WINDOWS void thread_yield () { Sleep(0); } #endif /* * -------------------------------------------------------------------------- * microtime() and millitime() * -------------------------------------------------------------------------- */ #if defined SEQ66_PLATFORM_UNIX // _LINUX /** * Gets the current system time in microseconds. * * Should we try rounding off the nanoseconds here and in millitime()? */ long microtime () { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return (t.tv_sec * 1000000) + (t.tv_nsec * 0.0010); } /** * Gets the current system time in milliseconds. * * Better? * * return (t.tv_sec * 1000) + ((t.tv_nsec + 500000) / 1000000); */ long millitime () { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return (t.tv_sec * 1000) + (t.tv_nsec * 1.0e-6); } #elif defined SEQ66_PLATFORM_WINDOWS /** * Gets the current system time in microseconds. Currently, we use * millitime() and multiply by 1000, in effect what Seq32 does. * * GetSystemTimeAsFileTime: * * It gives you accuracy in 0.1 microseconds or 100 nanoseconds. * * Note that it's Epoch different from POSIX Epoch. * So to get POSIX time in microseconds you need: * \verbatim FILETIME ft; GetSystemTimeAsFileTime(&ft); unsigned long long tt = ft.dwHighDateTime; tt <<=32; tt |= ft.dwLowDateTime; tt /=10; tt -= 11644473600000000ULL; \endverbatim * */ long microtime () { #if defined SEQ66_PLATFORM_WINDOWS__THIS_CODE_IS_READY // NOT! FILETIME ft; GetSystemTimeAsFileTime(&ft); unsigned long long tt = ft.dwHighDateTime; tt <<=32; tt |= ft.dwLowDateTime; tt /=10; tt -= 11644473600000000ULL; return tt; #else return millitime() * 1000; #endif } /** * Gets the current system time in milliseconds. Uses the Win32 function * timeGetTime(). * * The default precision of timeGetTime() can be five milliseconds or more. * One can use the timeBeginPeriod() and timeEndPeriod() functions to increase * the precision of timeGetTime(). If done, the minimum difference between * successive values returned by timeGetTime() can be as large as the minimum * period value set using timeBeginPeriod() and timeEndPeriod(). Use * QueryPerformanceCounter() and QueryPerformanceFrequency() to measure * short time intervals at a high resolution. */ long millitime () { return long(timeGetTime()); } #endif /* * -------------------------------------------------------------------------- * set_thread_priority() and set_timer_services() * -------------------------------------------------------------------------- */ #if defined SEQ66_PLATFORM_UNIX // LINUX /** * In Linux, sets the thread priority for the calling thread, either the * performer input thread or output thread. * * Investigate to see if another scheduler is better than SCHED_FIFO. Also, * under JACK, see if setting the priority high mitigates the playback issue * at large frame sizes. Also take note of other apps usages: * * - RtMidi: changed pthread attribute to SCHED_OTHER (from SCHED_RR) to * avoid thread problem when realtime cababilities are not enabled. * Also see SCHED_RR in RtAudio.cpp. * - PortMidi (ptlinux.c): If running as superuser, use setpriority() * to renice thread to -20. One could also set the timer thread to a * real-time priority (SCHED_FIFO and SCHED_RR), but this is * dangerous.... if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20); * - Also see the schedule settings in contrib/code/ttymidi.c. * * Do we also need to call pthread_attr_setschedpolicy(SCHED_FIFO)? * Do we need negative params to use other scheduling methods? * * \param p * This is the desired priority of the thread, ranging from 1 (low), to * 99 (high). The default value is 1. * * \return * Returns true if the scheduling call succeeded. */ bool set_thread_priority (std::thread & t, int p) { const int policy = SCHED_FIFO; int minp = sched_get_priority_min(policy); int maxp = sched_get_priority_max(policy); if (minp == (-1) || maxp == (-1)) { error_message("Cannot get scheduler priority values"); return false; } if (p >= minp && p <= maxp) { struct sched_param schp; memset(&schp, 0, sizeof(sched_param)); schp.sched_priority = p; /* Linux range: 1 to 99 */ #if defined SEQ66_PLATFORM_PTHREADS int rc = pthread_setschedparam(t.native_handle(), policy, &schp); #else int rc = sched_setscheduler(t.native_handle(), policy, &schp); #endif return rc == 0; } else { char temp[80]; snprintf ( temp, sizeof temp, "Priority error: %d outside of range %d-%d", p, minp, maxp ); error_message(temp); return false; } } /** * Linux doesn't need this, so we just act like it works. */ bool set_timer_services (bool /*on*/) { return true; } #elif defined SEQ66_PLATFORM_WINDOWS /** * In Windows, currently does nothing. An upgrade for the future. * The handle must have the THREAD_SET_INFORMATION or * THREAD_SET_LIMITED_INFORMATION access right. The main priority values are * * 0: THREAD_PRIORITY_NORMAL * 1: THREAD_PRIORITY_ABOVE_NORMAL * 2: THREAD_PRIORITY_HIGHEST * 15: THREAD_PRIORITY_TIME_CRITICAL * * See https://docs.microsoft.com/en-us/windows/win32/api/ * processthreadsapi/nf-processthreadsapi-setthreadpriority * * \param p * This is the desired priority of the thread. For Windows we restrict it * to the first three values shown above. * * \return * Returns true if p > 0; no functionality at present. */ bool set_thread_priority (std::thread & t, int p) { #if defined SEQ66_PLATFORM_WINDOWS__THIS_CODE_IS_READY bool result = false; if (p >= THREAD_PRIORITY_NORMAL && p <= THREAD_PRIORITY_HIGHEST) { HANDLE hthread = t.native_handle(); BOOL ok = SetThreadPriority(hthread, p); result = ok != 0; if (! result) { error_message("Windows set-priority error... access rights?"); } } return result; #else return p > 0 || t.joinable(); #endif } /** * Necessary for proper input and output timing using our portmidi * implementation under windows. */ bool set_timer_services (bool on) { MMRESULT mmr = on ? timeBeginPeriod(1) : timeEndPeriod(1) ; return mmr == TIMERR_NOERROR; } #endif // SEQ66_PLATFORM_UNIX, SEQ66_PLATFORM_WINDOWS } // namespace seq66 /* * vim: ts=4 sw=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/clockslist.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file clockslist.cpp * * This module defines some of the more complex functions of the clockslist. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-12-10 * \updates 2025-08-18 * \license GNU GPLv2 or above * */ #include "play/clockslist.hpp" /* seq66::clockslist class */ #include "util/strfunctions.hpp" /* seq66::string_format() template */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /** * Default and principal constructor defined in the header. */ /** * Saves the clock settings read from the "rc" file so that they can be * passed to the mastermidibus after it is created. Also used in the * creation of a port-map, in which the port nick-name is ultimately used to * look up an index into the ports actually discovered in the system. * The input string is of the following form (in ALSA), which includes the * quotes: * * 1 0 "[1] 32:0 Launchpad Mini MIDI 1" * * \param buss * The buss number read from the "rc" file. * * \param clocktype * The clock value read from the "rc" file. * * \param name * The full name of the port, except when a port-map is being formed. * Then, this is just a string version of the buss number. If this * parameter is empty, nothing is added to the list. * * \param nickname * The short name for the port, normally. This is generally the text * after the last colon in the bus/port name discovered by the system. * By default, it is empty. * * \return * Returns true if the item was added to the list. */ bool clockslist::add ( int buss, bool available, e_clock clocktype, const std::string & name, const std::string & nickname, const std::string & alias ) { bool result = buss >= 0 && ! name.empty(); if (result) { std::string portname = next_quoted_string(name); if (portname.empty()) /* was already parsed */ portname = name; int pstatus = (-1); io ioitem; ioitem.io_available = available; if (! available) { pstatus = clock_to_int(e_clock::unavailable); ioitem.io_enabled = false; ioitem.out_clock = e_clock::unavailable; } else { ioitem.io_enabled = clocktype != e_clock::disabled; ioitem.out_clock = clocktype; } ioitem.io_name = portname; ioitem.io_alias = alias; ioitem.io_client_number = ioitem.io_port_number = pstatus; result = portslist::add(buss, ioitem, nickname); } return result; } bool clockslist::add_list_line (const std::string & line) { int pnumber; int pstatus; std::string pname; bool result = parse_port_line(line, pnumber, pstatus, pname); if (result) { e_clock clocktype = int_to_clock(pstatus); bool available = pstatus != (-2); result = add(pnumber, available, clocktype, pname); } return result; } /** * Parses a string of the form: * * 0 1 "Nickname of the Port" (nick-name or alias) * * These lines are created by input_ or output_port_map_list(). Their * format is strict. These lines are those created in the * port_map_list() function. * * \return * Returns true if the line started with a number, followed by text * contained inside double-quotes. */ bool clockslist::add_map_line (const std::string & line) { int pnumber; int pstatus; std::string pname; bool result = parse_port_line(line, pnumber, pstatus, pname); if (result) { bool available = true; if (pstatus == (-2)) available = false; e_clock clocktype = int_to_clock(pstatus); std::string pnum = std::to_string(pnumber); result = add(pnumber, available, clocktype, pname, pnum); /* no alias */ } return result; } /** * Sets a single clock item, if in the currently existing range. * Mostly meant for use by the Options / MIDI Input tab and configuration * files. * * \param bus * The buss number, used to look up the io structure. * * \param clocktype * The type of clock setting. Also used to set the enabled status. * * \return * Returns true if the buss number lookup succeeded. */ bool clockslist::set (bussbyte bus, e_clock clocktype) { auto it = m_master_io.find(bus); bool result = it != m_master_io.end(); if (result) { bool enabled = clocktype != e_clock::disabled; it->second.io_enabled = enabled; it->second.out_clock = clocktype; } return result; } e_clock clockslist::get (bussbyte bus) const { auto it = m_master_io.find(bus); return it != m_master_io.end() ? it->second.out_clock : e_clock::none ; } std::string clockslist::io_list_lines () const { std::string result; int bus = 0; for (const auto & iopair : m_master_io) { const io & item = iopair.second; int status = clock_to_int(item.out_clock); result += io_line(bus, status, item.io_name, item.io_alias); ++bus; } return result; } /* * Free functions */ clockslist & output_port_map () { static clockslist s_clocks_list(true); /* flag this as a port-map */ return s_clocks_list; } #if defined USE_IOPUT_PORT_NAME_FUNCTION /** * Gets the nominal port name for the given bus, from the internal port-map * object for clocks. */ std::string output_port_name (bussbyte b, bool addnumber) { const clockslist & opm = output_port_map(); portname style = addnumber ? portname::full : portname::brief ; return opm.get_name(b, style); } #endif /** * Gets the port-string (e.g. "1") from the internal port-map object for * clocks. */ bussbyte output_port_number (bussbyte b) { bussbyte result = b; const clockslist & opm = output_port_map(); std::string nickname = opm.get_nick_name(b, portname::brief); if (! nickname.empty()) result = string_to_int(nickname); return result; } /** * Builds the internal clockslist which holds a simplified list of nominal * outputs where the io_name field of each element is the nick-ndame of the * source clockslist's element, and the io_nick_name field is the index * number (starting from 0) converted to a string. * * If an alias exists, it is used in preference to the nick-name. See * the add() function. */ bool build_output_port_map (const clockslist & cl) { bool result = ! cl.empty(); if (result) { clockslist & opm = output_port_map(); opm.clear(); int bus = 0; for (const auto & iopair : cl.master_io()) { const portslist::io & item = iopair.second; std::string number = std::to_string(bus); bool available = item.io_available; e_clock ec = e_clock::none; if (! item.io_enabled) ec = e_clock::disabled; if (item.io_alias.empty()) result = opm.add(bus, available, ec, item.io_nick_name, number); else result = opm.add(bus, available, ec, item.io_alias, number); if (! result) { opm.clear(); break; } ++bus; } opm.active(result); } return result; } void clear_output_port_map () { clockslist & opm = output_port_map(); opm.activate(portslist::status::cleared); } void activate_output_port_map (bool flag) { clockslist & opm = output_port_map(); portslist::status s = flag ? portslist::status::on : portslist::status::off ; opm.activate(s); } /** * If an output map exists and is not empty [see the output_port_map() * function], this function looks up the nominal buss number in order to find * the registered (in the '[midi-clocks-map]' section of the 'rc' file) name * of this port. That name is then used to look up the actual buss number of * that port as set up by the system according to existing MIDI equipment. * * If there is an error, the caller can assemble an error message. * * \param cl * Provides the clockslist that holds the actual existing MIDI output * ports. * * \param seqbuss * Provides the 'virtual' (nominal) buss number to be mapped to the true * buss number. The 'virtual' (nominal) buss number is the number stored * with each pattern in the MIDI tune, and should never change just because * the set of MIDI equipment changes. In this manner, one can easily remap * the configuration to fit the setup on someone else's system. * * \return * If the port map exists, the looked-up port/buss number is returned. If * that port cannot be found by name, then null_buss() (0xFF) is * returned. Otherwise, the nominal buss parameter is returned, which * preserves the legacy behavior of the pattern buss number. Also, * null_buss() will be returned if the nomimal buss is that value. * Test with the is_null_buss() function. */ bussbyte true_output_bus (const clockslist & cl, bussbyte seqbuss) { bussbyte result = seqbuss; if (! is_null_buss(result)) { const clockslist & opm = output_port_map(); if (opm.active()) { std::string shortname = opm.port_name_from_bus(seqbuss); if (shortname.empty()) { std::string msg = string_format("Bad output buss %d", seqbuss); errprint(msg); result = null_buss(); } else { result = cl.bus_from_alias(shortname); if (is_null_buss(result)) result = cl.bus_from_nick_name(shortname); } } } return result; } /** * Returns a string representing the two columns of the internal clocks list. * It is suitable for writing to a configuration file. Quotes are included * for readability and parse-ability. * \verbatim 0 "MIDI Port 1 Through" 1 "Jazzy MIDI Out 1" 2 "Jazzy MIDI Out 2" \endverbatim * * \return * Returns a string like the above. If it is empty, the output port map * is empty. */ std::string output_port_map_list () { const clockslist & opm = output_port_map(); return opm.port_map_list(true); /* is clock */ } } // namespace seq66 /* * clockslist.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/inputslist.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file inputslist.cpp * * This module defines some of the more complex functions of the inputslist. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-12-10 * \updates 2025-08-18 * \license GNU GPLv2 or above * */ #include "play/inputslist.hpp" /* seq66::inputslist class */ #include "util/strfunctions.hpp" /* seq66::string_format() template */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /** * Default and principal constructor defined in the header. */ /** * Saves the input settings read from the "rc" file so that they can be * passed to the mastermidibus after it is created. * * \param available * Indicates that the input is available. * * \param enabled * Indicates in the input is enabled. * * \param name * The full name of the port, except when a port-map is being formed. * Then, this is just a string version of the buss number. If this * parameter is empty, nothing is added to the list. * * \param nickname * The short name for the port, normally. This is generally the text * after the last colon in the bus/port name discovered by the system. * By default, it is empty. * * \return * Returns true if the item was added to the list. * */ bool inputslist::add ( int buss, bool available, bool enabled, const std::string & name, const std::string & nickname, const std::string & alias ) { bool result = buss >= 0 && ! name.empty(); if (result) { std::string portname = next_quoted_string(name); if (portname.empty()) /* was already parsed */ portname = name; io ioitem; ioitem.io_available = available; ioitem.io_enabled = enabled; ioitem.out_clock = e_clock::none; /* not e_clock::disabled */ ioitem.io_name = portname; ioitem.io_alias = alias; result = portslist::add(buss, ioitem, nickname); } return result; } bool inputslist::add_list_line (const std::string & line) { int pnumber; int pstatus; std::string pname; bool result = parse_port_line(line, pnumber, pstatus, pname); if (result) { bool available = pstatus != (-2); bool enabled = pstatus > 0; result = add(pnumber, available, enabled, pname); } return result; } /** * Parses a string of the form: * * 0 1 "Nickname of the Port" (nick-name or alias) * * These lines are created by input_ or output_port_map_list(). Their * format is strict. These lines are those created in the * port_map_list() function. * * \return * Returns true if the line started with a number, followed by text * contained inside double-quotes. */ bool inputslist::add_map_line (const std::string & line) { int pnumber; int pstatus; std::string pname; bool result = parse_port_line(line, pnumber, pstatus, pname); if (result) { bool enabled = pstatus > 0; bool available = pstatus != (-2); std::string pnum = std::to_string(pnumber); result = add(pnumber, available, enabled, pname, pnum); /* no alias */ } return result; } /** * Sets a single clock item, if in the currently existing range. * Mostly meant for use by the Options / MIDI Input tab and configuration * files. * * \param bus * The buss number, used to look up the io structure. * * \param input * The desired enabled status of the port. * * \return * Returns true if the buss number lookup succeeded. */ bool inputslist::set (bussbyte bus, bool inputing) { auto it = m_master_io.find(bus); bool result = it != m_master_io.end(); if (result) { it->second.io_enabled = inputing; it->second.out_clock = e_clock::none; } return result; } bool inputslist::get (bussbyte bus) const { auto it = m_master_io.find(bus); return it != m_master_io.end() ? it->second.io_enabled : false ; } std::string inputslist::io_list_lines () const { std::string result; int bus = 0; for (const auto & iopair : m_master_io) { const io & item = iopair.second; int status = item.io_enabled ? 1 : 0 ; result += io_line(bus, status, item.io_name, item.io_alias); ++bus; } return result; } /* * Free functions */ inputslist & input_port_map () { static inputslist s_inputs_list(true); /* flag this as a port-map */ return s_inputs_list; } #if defined USE_IOPUT_PORT_NAME_FUNCTION /** * Gets the nominal port name for the given bus, from the internal port-map * object for inputs. */ std::string input_port_name (bussbyte b, bool addnumber) { const inputslist & ipm = input_port_map(); portname style = addnumber ? portname::full : portname::brief ; return ipm.get_name(b, style); } #endif /** * Gets the port-string (e.g. "1") from the internal port-map object for * inputs. */ bussbyte input_port_number (bussbyte b) { bussbyte result = b; const inputslist & ipm = input_port_map(); std::string nickname = ipm.get_nick_name(b, portname::brief); if (! nickname.empty()) result = string_to_int(nickname); return result; } /** * Builds the internal inputslist which holds a simplified list of nominal * inputs where the io_name field of each element is the nick-ndame of the * source inputslist's element, and the io_nick_name field is the index * number (starting from 0) converted to a string. */ bool build_input_port_map (const inputslist & il) { bool result = ! il.empty(); if (result) { inputslist & ipm = input_port_map(); ipm.clear(); int bus = 0; for (const auto & iopair : il.master_io()) { const portslist::io & item = iopair.second; std::string number = std::to_string(bus); bool available = item.io_available; bool enabled = item.io_enabled; if (item.io_alias.empty()) { result = ipm.add ( bus, available, enabled, item.io_nick_name, number ); } else { result = ipm.add ( bus, available, enabled, item.io_alias, number ); } if (! result) { ipm.clear(); break; } ++bus; } ipm.active(result); } return result; } void clear_input_port_map () { inputslist & ipm = input_port_map(); ipm.activate(portslist::status::cleared); } void activate_input_port_map (bool flag) { inputslist & ipm = input_port_map(); portslist::status s = flag ? portslist::status::on : portslist::status::off ; ipm.activate(s); } /** * If an input map exists and is not empty [see the input_port_map() * function], this function looks up the nominal buss number in order to find * the registered (in the '[midi-clocks-map]' section of the 'rc' file) name * of this port. That name is then used to look up the actual buss number of * that port as set up by the system according to existing MIDI equipment. * * If there is an error, the caller can assemble an error message. * * \param cl * Provides the clockslist that holds the actual existing MIDI input * ports. * * \param seqbuss * Provides the buss number to be mapped to the true buss number. The * nominal buss number is the number stored with each pattern in the * tune, and should never change just because the set of MIDI equipment * changes. * * \return * If the port map exists, the looked-up port/buss number is returned. If * that port cannot be found by name, then null_buss() (0xFF) is * returned. Otherwise, the nominal buss parameter is returned, which * preserves the legacy behavior of the pattern buss number. Also, * null_buss() will be returned if the nomimal buss is that value. * Test with the is_null_buss() function. */ bussbyte true_input_bus (const inputslist & cl, bussbyte seqbuss) { bussbyte result = seqbuss; if (! is_null_buss(result)) { const inputslist & ipm = input_port_map(); if (ipm.active()) { std::string shortname = ipm.port_name_from_bus(seqbuss); if (shortname.empty()) { std::string msg = string_format("Bad input buss %d", seqbuss); errprint(msg); result = null_buss(); } else { result = cl.bus_from_alias(shortname); if (is_null_buss(result)) result = cl.bus_from_nick_name(shortname); } } } return result; } /** * Returns a string representing the two columns of the internal inputs list. * It is suitable for writing to a configuration file. Quotes are included * for readability and parse-ability. * \verbatim 0 "MIDI Port 1 Through" 1 "Jazzy MIDI In 1" 2 "Jazzy MIDI In 2" \endverbatim * * \return * Returns a string like the above. If it is empty, the input port map * is empty. */ std::string input_port_map_list () { const inputslist & ipm = input_port_map(); return ipm.port_map_list(false); /* not clock */ } } // namespace seq66 /* * inputslist.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/metro.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file metro.cpp * * This module declares/defines a special pattern for the metronome. * * \library seq66 application * \author Chris Ahlstrom * \date 2022-08-05 * \updates 2024-12-14 * \license GNU GPLv2 or above * */ #include "cfg/settings.hpp" /* seq66::usr() config accessor */ #include "play/metro.hpp" /* seq66::metro sequence class */ #include "play/performer.hpp" /* seq66::performer class functions */ namespace seq66 { /* *--------------------------------------------------------------------- * metrosettings *--------------------------------------------------------------------- * * See https://music.arts.uci.edu/dobrian/maxcookbook/ * metronome-using-general-midi-sounds */ metrosettings::metrosettings () : m_buss (0), m_channel (9), /* MIDI channel 10, drums */ m_recording_buss (0), m_thru_buss (0), m_thru_channel (0), m_beats_per_bar (4), m_beat_width (4), m_main_patch (0), /* Standard drum kit */ m_sub_patch (0), /* Standard drum kit */ m_main_note (75), /* Claves */ m_main_note_velocity (96), m_main_note_length (0), m_sub_note (76), /* High Wood Block */ m_sub_note_velocity (84), m_sub_note_length (0), m_main_note_fraction (0.0), m_sub_note_fraction (0.0), m_count_in_active (false), m_count_in_measures (1), m_count_in_recording (false), m_recording_measures (0) { /* * See the principal constructor below. */ } /** * A helper function for metro::initialize(). */ midipulse metrosettings::calculate_length (int increment, float fraction) { midipulse result; if (fraction > 0.1) /* sanity float check */ result = midipulse(increment * fraction); else result = increment / 2; return result; } /** * "A computer metronome could trigger any sort of sound: MIDI notes, * percussion sounds, recorded samples, synthesized beeps, etc. In this * example, I’ve chosen to use two specific MIDI notes, key numbers 75 and 76 * on MIDI channel 10 which, according to the General MIDI standard, plays * the sounds of Claves and High Wood Block. Those sounds should be roughly * the same on any GM-compliant synthesizer, including the synthesizers built * into the Mac OS and Windows OS." * * https://music.arts.uci.edu/dobrian/maxcookbook/ * metronome-using-general-midi-sounds * * This function is called in rcsettings::set_defaults(), happening in the * smanager constructor. */ void metrosettings::set_defaults () { m_buss = 0; m_channel = 9; /* Channel 10, Percussion */ m_beats_per_bar = 4; m_beat_width = 4; m_main_patch = 0; /* Standard drum kit */ m_sub_patch = 0; /* Standard drum kit */ m_main_note = 75; /* Claves. 72 = middle C + 12 */ m_main_note_velocity = 96; m_main_note_length = 0; m_sub_note = 76; /* H. Wood Block: 60 = middle C */ m_sub_note_velocity = 84; m_sub_note_length = 0; m_main_note_fraction = 0.0; /* same as 0.5 */ m_sub_note_fraction = 0.0; /* ditto */ m_count_in_active = false; m_count_in_measures = 1; m_count_in_recording = false; m_recording_measures = 0; } bool metrosettings::initialize (int increment) { m_main_note_length = calculate_length(increment, m_main_note_fraction); m_sub_note_length = calculate_length(increment, m_sub_note_fraction); return true; } /* *--------------------------------------------------------------------- * metro *--------------------------------------------------------------------- */ /** * Default constructor. */ metro::metro () : sequence (), m_metro_settings () { /* * See the initialize() function below. */ } /** * Principal constructor. */ metro::metro (const metrosettings & mc) : sequence (), m_metro_settings (mc) { /* * See the initialize() function below. */ } /** * A rote destructor. */ metro::~metro () { // Empty body } /** * Helper function for initialize() and its overrides. */ bool metro::init_setup (performer * p, int measures) { bool result = not_nullptr(p); if (result) { result = settings().sanity_check(); if (result) set_parent(p); } if (result) { int ppq = p->ppqn(); /* p->get_ppqn() */ int bpb = settings().beats_per_bar(); /* get_beats_per_bar() */ int bw = settings().beat_width(); /* get_beat_width() */ midibyte channel = settings().channel(); /* seq_midi_channel() */ (void) set_midi_bus(settings().buss()); /* ...uses master-bus */ (void) set_midi_channel(channel); /* metro output channel */ set_beats_per_bar(bpb); /* hmm, add bool return */ set_beat_width(bw); /* ditto */ if (measures > 0) (void) apply_length(bpb, ppq, bw, measures); } return result; } /** * Fills the event list for the metronome. Requires that all the setting * functions noted above be called first. * * For finding the length, can use measures_to_ticks() or * sequence::apply_length(). */ bool metro::initialize (performer * p) { bool result = init_setup(p, 1); /* set up one measure */ if (result) { int ppq = p->ppqn(); /* p->get_ppqn() */ int bpb = settings().beats_per_bar(); /* get_beats_per_bar() */ int bw = settings().beat_width(); /* get_beat_width() */ midibyte channel = settings().channel(); /* seq_midi_channel() */ int increment = pulses_per_beat(ppq, bw); if (settings().initialize(increment)) { /* * Must set this before the possibility of raising the modify * flag. */ seq_number(metronome()); /* magic metro number */ set_name("Metronome"); } midipulse tick = 0; for (int count = 0; count < bpb; ++count, tick += increment) { midibyte patch, note, vel, len; if (count == 0) { patch = settings().main_patch(); note = settings().main_note(); vel = settings().main_note_velocity(); len = settings().main_note_length(); } else { patch = settings().sub_patch(); note = settings().sub_note(); vel = settings().sub_note_velocity(); len = settings().sub_note_length(); } event prog(tick, EVENT_PROGRAM_CHANGE | channel, patch); event on(tick + 1, EVENT_NOTE_ON, channel, note, vel); event off(tick + len, EVENT_NOTE_OFF, channel, note, vel); result = add_event(prog); if (result) result = add_event(on); if (result) result = add_event(off); if (! result) break; } if (result) { sort_events(); armed(true); unmodify(); /* not part of song */ } } return result; } /* *--------------------------------------------------------------------- * recorder *--------------------------------------------------------------------- */ /* * The color is set to 1, which is the "Red" entry in the * application palette. */ static const int s_recorder_color = 1; /** * Default constructor. Also see the metro constructor. */ recorder::recorder () : metro () { // set_color(s_recorder_color, true); } /** * Principal constructor. */ recorder::recorder (const metrosettings & mc) : metro (mc) { // set_color(s_recorder_color, true); } /** * A rote destructor. */ recorder::~recorder () { // Empty body } /** * Fills the event list for the recorder. Requires that all the setting * functions noted above be called first. * * For finding the length, can use measures_to_ticks() or * sequence::apply_length(). * * Life-cycle: * * - Create the recorder sequence and call this initialize() function. * - It sets a few things up for recording. Note especially the * set_recording() function. It calls mastermidibus :: * set_sequence_input() to log this pattern as the recording pattern. * * Must set this before the possibility of raising the modify * flag. Also note we select the new-pattern quantities. To recollect: * recordstyle covers merge, overwrite, expand...; alteration covers * none, quantize, notemap...; */ bool recorder::initialize (performer * p) { bool result = init_setup(p, settings().recording_measures()); if (result) { int ppq = p->ppqn(); /* p->get_ppqn() */ int bw = settings().beat_width(); /* get_beat_width() */ int increment = pulses_per_beat(ppq, bw); if (settings().initialize(increment)) { bool unmute = usr().pattern_armed(); alteration alter = usr().record_alteration(); recordstyle rs = usr().pattern_record_style(); bool usethru = usr().pattern_thru(); bussbyte outbuss = settings().thru_buss(); midibyte channel = settings().thru_channel(); armed(unmute); set_recording(alter, toggler::on); /* eg. quantize... */ set_recording_style(rs); /* merge, expand, etc. */ set_thru(usethru); set_midi_bus(outbuss); /* for playback */ set_midi_channel(channel); set_name("Background Recording"); set_color(s_recorder_color, true); /* * Do not make these settings. * * seq_number(sequence::recorder()); // magic recorder seq * expanded_recording(true); * wrap-around? */ unmodify(); /* not part of song */ } } return result; } bool recorder::uninitialize () { set_recording(alteration::none, toggler::off); /* doesn't clear expand */ set_color(0, true); /* * Probably want the user to remember to modify these settings. * * set_midi_bus(0); * set_midi_channel(0); */ return true; } } // namespace seq66 /* * recorder.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/mutegroup.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file mutegroup.cpp * * This module declares a two dimensional vector class solely to hold the * mute status of a number of sequences in a set. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-12-01 * \updates 2022-06-28 * \license GNU GPLv2 or above * * This class manages one of the lines in the "[mute-group]" section of the * new "rc" files: * \verbatim ----- Screenset/bank number | | --- Row 0 --- Row 1 --- Row 2 --- Row 3 | | | | | v v v v v 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] ^ ^ ^ . . . ^ ^ ^ ^ | | | | | | | | | | --- Loop 8 --- Loop 16 --- Loop 24 | | | | Loop 31 - | | --- Loop 2 | ----- Loop 1 ------- Loop 0 \endverbatim * * The stanza above describes the default Seq66/Seq64/Seq24 setup, where a * set consists of 4 rows and 8 columns of 32 patterns. * * How can we handle using sets of a larger size? * * The following might work for a 8-column x 8 row = 64 screen-set. * \verbatim 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] 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] \endverbatim * * The following might work for a 16-column x 4 row = 64 screen-set. * \verbatim 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] 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] \endverbatim * * What if we want to swap rows and columns? * \verbatim 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 0] \endverbatim */ #include /* std::cout */ #include "cfg/settings.hpp" /* seq66::usr() */ #include "play/mutegroup.hpp" /* seq66::mutegroup class */ #include "util/strfunctions.hpp" /* seq66::write_stanza_bits() */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /** * Principal constructor. * * \param group * Provides the group number. Group numbers range from 0 to 31; * only 32 mute-groups, no more, no less, are supported. But * each mute-group does not have to be restricted to this size. * * \param rows * Provides the number of virtual rows in a single mute-group. * * \param columns * Provides the number of virtual columns in a single mute-group. */ mutegroup::mutegroup (mutegroup::number group, int rows, int columns) : m_name ("Group"), m_group_state (false), m_group_size (int(rows * columns)), /* order important */ m_mutegroup_vector (m_group_size, midibool(false)), m_rows (rows), m_columns (columns), m_swap_coordinates (usr().swap_coordinates()), m_group (group >= 0 ? group : 0), m_group_offset (m_group * m_group_size) { m_name += " "; m_name += std::to_string(int(group)); } /** * Copies the given bits-vector into the mute-group vector. * * \param bits * The vector of bits to be copied. The length of this vector is checked. * * \return * Returns true if the \a bits parameter was of the proper length. */ bool mutegroup::set (const midibooleans & bits) { bool result = bits.size() == size_t(m_group_size); if (result) m_mutegroup_vector = bits; return result; } /** * Clears the mute-group vector and refills it with values of "unarmed" * (false). */ void mutegroup::clear () { m_mutegroup_vector.clear(); m_mutegroup_vector.reserve(m_group_size); for (auto & mg : m_mutegroup_vector) mg = midibool(false); } /** * Checks to see if the mute-group is worth being saved. * * \return * Returns true if any of the "bits" in the mutegroup is set to armed * (true). */ bool mutegroup::any () const { bool result = false; for (auto mg : m_mutegroup_vector) { if (bool(mg)) { result = true; break; } } return result; } /** * Counts the number of armed mute settings in the vector. * * \return * Returns true if any of the "bits" in the mutegroup is set to armed * (true). */ int mutegroup::armed_count () const { int result = 0; for (auto mg : m_mutegroup_vector) { if (bool(mg)) ++result; } return result; } /** * Calculates the row and column for a given index. * * Compare to screenset::index_to_grid(). * * \param index * This is the group or pattern number which, by default, can range * from 0 to 31 (same as the size of a mute group is set to). * * \return * Returns true if the calculation is valid and the result can be * used. */ bool mutegroup::mute_to_grid (int group, int & row, int & column) const { int offset = group - int(m_group_offset); bool result = offset >= 0 && offset < int(m_group_size); if (result) { if (swap_coordinates()) { row = group / m_columns; column = group % m_columns; } else { row = group % m_rows; column = group / m_rows; } } return result; } int mutegroup::grid_to_mute (int row, int column) { if (row < m_rows && column < m_columns) { if (swap_coordinates()) return m_group_offset + column + m_columns * row; else return m_group_offset + row + m_rows * column; } else return 0; } /** * Gets the muting value, actually whether the loop is to be armed or not, * for the given index. It is used in controlling the active set. * * \param index * The offset into the mute-group vector of booleans. * * \return * Returns true if the index was valid and the mute-group bit was set. */ bool mutegroup::armed (int index) const { bool result = index >= 0 && index < m_group_size; if (result) return bool(m_mutegroup_vector[index]); return result; } void mutegroup::armed (int index, bool flag) { if (index >= 0 && index < m_group_size) m_mutegroup_vector[index] = flag; } /** * Just a simple display of a mute group. The get() function gets the * midibooleans vector, and we tell write_stanza_bits to group by the * actuall number of columns in a mute-group (which is the same as in a * screenset). */ void mutegroup::show () const { std::string stanzabits = write_stanza_bits(get(), columns()); std::cout << "Group #" << group() << " " << stanzabits << " " << name() << std::endl ; } /* * Free functions devoted to mutegroups. */ /** * Converts a vector of boolean values in a parseable string. * * \param bitbucket * The vector of bit values to be written. Currently, this function * assumes that the number of bit values is perfectly divisible by 8. * If the user makes a mistake, tough shitsky. * * \param grouping * The number of values between each "[ ]" pair. Normally 8, it is * whatever the number of columns have been specified for a screenset. * * \param hexstyle * If true (the default), then hexadecimal values are written, in groups * of 8 bits. Hexadecimal values are better when set-size is greater than * the legacy value, 32. * * \return * Returns the assembled string, of the form "[ bits ]". */ std::string write_stanza_bits ( const midibooleans & bitbucket, int grouping, bool hexstyle ) { std::string result("[ "); int bitcount = int(bitbucket.size()); if (bitcount > 0) { if (hexstyle) { int bitcount = grouping; /* group by 8 bits, ... */ unsigned hexvalue = 0x00; for (auto b : bitbucket) { unsigned bitvalue = b != 0 ? 1 : 0 ; hexvalue |= bitvalue; --bitcount; if (bitcount == 0) { char temp[16]; (void) snprintf(temp, sizeof temp, "0x%02x ", hexvalue); result += temp; bitcount = 8; hexvalue = 0x00; } else hexvalue <<= 1; } /* * Less than 8 bits encountered, emit the number anyway, after * undoing the last left-shift. */ if (bitcount > 0 && bitcount < grouping) { char temp[16]; (void) snprintf(temp, sizeof temp, "0x%02x ", hexvalue >> 1); result += temp; } } else { int counter = 0; for (auto b : bitbucket) { bool ender = ++counter % grouping == 0 && counter < int(bitbucket.size()); result += (b != 0) ? "1" : "0" ; result += " "; if (ender) result += "] [ "; } } } result += "]"; return result; } /** * Adds the 8 bits of an unsigned value to a vector of midibools. * */ static void push_8_bits (midibooleans & target, unsigned bits) { unsigned bitmask = 0x80; /* start with the highest (MSB) bit */ for (int i = 0; i < 8; ++i) { midibool mb = (bits & bitmask) != 0 ? midibool(1) : midibool(0) ; target.push_back(mb); bitmask >>= 1; } } /** * We want to support both the legacy mute-group settings, with 4x8 * groups of "bits", and a newer setting, using an unsigned char (8 bits) * to hold the bits. The number of bits is based on the row and column * settings for [mute-group]. * \verbatim --- loop 0 loop 31 --- | | v v [0 0 0 0 0 0 0 0 ] [ 1 1 1 1 1 1 1 1 ] [ 0 1 0 1 0 1 0 1 ] [1 0 1 0 1 0 1 0 ] [ 0x00 ] [ 0xFF ] [ 0x55 ] [ 0xAA ] [ 0x00 0xFF 0x55 0xAA ] \endverbatim * * NB: we need another mute-group flag to indicate how the groups will be * written, in case some people don't want to deal with bit-masks. * * The styles cannot be mixed; a single 'x' character on the line indicates * the new format, and is scanned for before processing the line.. * * As with the legacy, the new style will support at least 8 bits per * grouping. The groupings are purely organizational. The bits are set in * order from loop 0 on up, with no gaps or 2-D organization. * */ bool parse_stanza_bits ( midibooleans & target, const std::string & mutestanza ) { bool result = ! mutestanza.empty(); if (result) { midibooleans bitbucket; auto p = mutestanza.find_first_of("xX"); auto bleft = mutestanza.find_first_of("["); bool hexstyle = p != std::string::npos; tokenization tokens; int tokencount = tokenize_stanzas(tokens, mutestanza, bleft); result = tokencount > 0; if (result) { for (int tk = 0; tk < tokencount; ++tk) { std::string temp = tokens[tk]; if (temp == "[" || temp == "]") { /* nothing to do */ } else if (temp[0] == '"') /* beginning of group name */ { break; } else { unsigned v = unsigned(string_to_int(temp)); if (hexstyle) { if (v < 256) push_8_bits(bitbucket, v); else push_8_bits(bitbucket, 0); /* error */ } else { if (v != 0) v = 1; bitbucket.push_back(midibool(v)); } } } bleft = mutestanza.find_first_of("[", bleft + 1); result = bitbucket.size() > 0; if (result) target = bitbucket; else target.clear(); } } return result; } } // namespace seq66 /* * mutegroup.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/mutegroups.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file mutegroups.cpp * * This module declares a container for a number of optional mutegroup objects. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-12-01 * \updates 2023-10-06 * \license GNU GPLv2 or above * * The mutegroups object contains the mute-group data read from a mute-group * file. It provides the mute-groups that are available for access by the * performer. * * Do not confuse it with the mutegroupmanager, which supports the performer's * processing of mute-group selections. */ #include /* std::setw() manipulator */ #include /* std::cerr to note errors */ #include "cfg/settings.hpp" /* seq66::usr() */ #include "play/mutegroups.hpp" /* seq66::mutegroups class */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /* * Static members */ bool mutegroups::s_swap_coordinates = false; mutegroups::saving mutegroups::string_to_group_save (const std::string & v) { if (v == "both" || v == "stomp") return saving::both; else if (v == "mutes") return saving::mutes; else if (v == "midi" || v == "preserve") return saving::midi; else return saving::max; /* an illegal value */ } /** * The "inverse" of grid_to_group(). This function is static. * * Compare this to the non-static function mutegroup::mute_to_grid(). */ bool mutegroups::group_to_grid (mutegroup::number group, int & row, int & column) { bool result = group >= 0 && group < Size(); if (result) { if (Swap()) { row = group / Columns(); column = group % Columns(); } else { row = group % Rows(); column = group / Rows(); } } return result; } /** * Returns the mute group number for the given row and column. Remember that * the layout of mutes doesn't match that of sets and sequences. The row and * column here currently match the 4 x 8 mute-group grid in the qmutemaster * module. This function is static. * * \param row * Provides the desired row of the virtual mute-group grid, clamped to a * legal value. * * \param column * Provides the desired column of the virtual mute-group grid, clamped to * a legal value. * * \return * Returns the calculated mute-group value, which will range from 0 to * (m_rows * m_columns) - 1 = m_set_count. */ mutegroup::number mutegroups::grid_to_group (int row, int column) { if (row < 0) row = 0; else if (row >= Rows()) row = Rows() - 1; if (column < 0) column = 0; else if (column >= Columns()) column = Columns() - 1; return Swap() ? (column + row * Columns()) : (row + column * Rows()); } /** * Creates an empty, default mutegroups object. The default size is 4 x 8, * but this is currently the only size we will support: 32 mute-groups. * (The size of a mute-group can vary, however.) * * \param rows * Provides the number of virtual rows in a mute-group. This value * ultimately comes from usr().mainwnd_rows(). * * \param columns * Provides the number of virtual columns in a mute-group. This value * ultimately comes from usr().mainwnd_rows(). */ mutegroups::mutegroups (int rows, int columns) : basesettings (), m_container (), m_container_name ("Default"), m_rows (rows), m_columns (columns), m_group_format_hex (false), m_loaded_from_mutes (false), m_group_event (false), m_group_error (false), m_group_mode (true), /* see its description */ m_group_learn (false), m_group_selected (c_null_mute_group), m_group_present (false), m_group_save (saving::midi), /* midi or mutes files? */ m_group_load (loading::midi), /* midi or mutes files? */ m_toggle_active_only (false), m_strip_empty (true), m_legacy_mutes (false) { s_swap_coordinates = usr().swap_coordinates(); create_empty_mutes(); } /** * Creates an empty, named mutegroups object. The number of mutegroup object * in a mutegroups object is \a rows times \a columns. * * \param name * Provides the name of the mutegroups object, sometimes useful in * troubleshooting. * * \param rows * Provides the number of virtual rows in the set of mute-groups. * * \param columns * Provides the number of virtual columns in the set of mute-groups. * */ mutegroups::mutegroups (const std::string & name, int rows, int columns) : basesettings (), m_container (), m_container_name (name), m_rows (rows), m_columns (columns), m_group_format_hex (false), m_loaded_from_mutes (false), m_group_event (false), m_group_error (false), m_group_mode (true), m_group_learn (false), m_group_selected (c_null_mute_group), m_group_present (false), m_group_save (saving::midi), m_group_load (loading::midi), m_toggle_active_only (false), m_legacy_mutes (false) { s_swap_coordinates = usr().swap_coordinates(); create_empty_mutes(); } /** * Creates a new mutegroup from the incoming bit vector, and adds it at the * given mute-group number. */ bool mutegroups::load (mutegroup::number gmute, const midibooleans & bits) { bool result = gmute >= 0; if (result) { #if defined USE_OLD_CODE mutegroup m(gmute, m_rows, m_columns); m.set(bits); // there is no load(bits) result = add(gmute, m); #else result = update(gmute, bits); #endif } return result; } /** * Similar to load(), but assumes an existing group that is to be modified, * rather than added to the container. * * \param gmute * Provides the mute-group number to be set. * * \return * Returns true if the mute-group was found. */ bool mutegroups::set (mutegroup::number gmute, const midibooleans & bits) { auto mgiterator = m_container.find(gmute); bool result = mgiterator != m_container.end(); if (result) mgiterator->second.set(bits); return result; } /** * Copies the mute bits into a boolean container. * * \param gmute * Provides the mute-group number to be obtained. * * \return * Returns the contents of the mute group. If empty, it is likely that * the mute-group number is bad. */ midibooleans mutegroups::get (mutegroup::number gmute) const { auto mgiterator = m_container.find(gmute); if (mgiterator != m_container.end()) { return mgiterator->second.get(); } else { midibooleans no_bits; return no_bits; } } /** * Creates a vector of the size of the set of mutegroups, and sets the "bit" * for each one that corresponds to an existing mutegroup. */ midibooleans mutegroups::get_active_groups () const { midibooleans result; result.resize(Size()); for (const auto & mgpair : m_container) { int g = mgpair.first; /* const mutegroup & m = mgpair.second */ const mutegroup & m = mgpair.second; if (g >= 0 && g < Size()) result[g] = midibool(m.any()); } return result; } /** * Creates a mute-group container with the full set of 32 mute-groups that * Seq66 supports. Meant to be called only in the constructors. We need * to pre-create the container to avoid issues with partial sets of groups * in different situations. */ void mutegroups::create_empty_mutes () { if (list().empty()) { for (mutegroup::number group = 0; group < Size(); ++group) { mutegroup empty_mg(group, rows(), columns()); (void) add(group, empty_mg); } } } /** * Conditionally adds a mute-group to the container. */ bool mutegroups::add (mutegroup::number gmute, const mutegroup & m) { container::size_type sz = m_container.size(); auto p = std::make_pair(gmute, m); (void) m_container.insert(p); bool result = m_container.size() == (sz + 1); if (! result) std::cerr << "[Duplicate group " << gmute << "]" << std::endl; return result; } bool mutegroups::update (mutegroup::number gmute, const midibooleans & bits) { mutegroup & mdestination = mute_group(gmute); bool result = mdestination.valid(); if (result) { result = mdestination.set(bits); if (! result) std::cerr << "[Group " << gmute << " bits not set]" << std::endl; } else std::cerr << "[Group " << gmute << " not found]" << std::endl; return result; } /** * \return * Returns true if any of the mutegroup objects has an armed (true) * status. */ bool mutegroups::any () const { bool result = false; for (const auto & m : m_container) { if (m.second.any()) { result = true; break; } } return result; } bool mutegroups::any (mutegroup::number gmute) const { return mute_group(gmute).any(); } /** * An accessor to a specific mute-group. */ const mutegroup & mutegroups::mute_group (mutegroup::number gmute) const { static bool s_dummy_uninitialized = true; static mutegroup s_mute_group_dummy; if (s_dummy_uninitialized) { s_dummy_uninitialized = false; s_mute_group_dummy.invalidate(); } const auto cmi = m_container.find(gmute); return cmi != m_container.end() ? cmi->second : s_mute_group_dummy; } mutegroup & mutegroups::mute_group (mutegroup::number gmute) { static bool s_dummy_uninitialized = true; static mutegroup s_mute_group_dummy; if (s_dummy_uninitialized) { s_dummy_uninitialized = false; s_mute_group_dummy.invalidate(); } auto mi = m_container.find(gmute); return mi != m_container.end() ? mi->second : s_mute_group_dummy; } /** * Applies a mute group. The caller, who knows the screenset, must do the * unapply operation. */ bool mutegroups::apply (mutegroup::number group, midibooleans & bits) { auto mgiterator = list().find(clamp_group(group)); bool result = mgiterator != list().end(); if (result) { mutegroup & mg = mgiterator->second; result = mg.any(); /* ignore an inactive mute-group */ if (result) { bits = mg.get(); mg.group_state(true); m_group_selected = group; } } return result; } /** * Unapplies a mute group. If less than zero (see c_null_mute_group), then * the unapply is applied (heh heh) to the current group. */ bool mutegroups::unapply (mutegroup::number group, midibooleans & bits) { bool result = false; if (group >= 0) { auto mgiterator = list().find(clamp_group(group)); result = mgiterator != list().end(); if (result) { mutegroup & mg = mgiterator->second; result = mg.any(); /* ignore an inactive mute-group */ if (result) { bits = mg.zeroes(); mg.group_state(false); m_group_selected = c_null_mute_group; } } } else if (m_group_selected >= 0) { result = unapply(m_group_selected, bits); } return result; } /** * If another group was selected when we enter this function, we first need * to turn off its group state before setting up the currently desired group. * * An issue to solve: * * Let's say we start up with group 1 active (a new feature for issue * #27). The MIDI file is loaded and group 1 is applied. Now we select * group 0. At this point, we need to do what? */ bool mutegroups::toggle (mutegroup::number group, midibooleans & bits) { auto mgiterator = list().find(clamp_group(group)); bool result = mgiterator != list().end(); if (result) { if (group != m_group_selected && m_group_selected >= 0) { auto mgiterator = list().find(clamp_group(m_group_selected)); bool result = mgiterator != list().end(); if (result) { mutegroup & mg = mgiterator->second; mg.group_state(false); } } mutegroup & mg = mgiterator->second; result = mg.any(); /* ignore an inactive mute-group */ if (result) { bool mgnewstate = ! mg.group_state(); bits = mgnewstate ? mg.get() : mg.zeroes() ; mg.group_state(mgnewstate); m_group_selected = mgnewstate ? group : c_null_mute_group ; } } return result; } /** * Toggles a mute group to the current play-screen in an alternative way. * This alternative is to disarm only the patterns that are marked as active * in the mute group, leaving the other ones set to their current status. */ bool mutegroups::toggle_active (mutegroup::number group, midibooleans & armedbits) { auto mgiterator = list().find(clamp_group(group)); bool result = mgiterator != list().end(); if (result) { if (group != m_group_selected && m_group_selected >= 0) { (void) toggle_active(m_group_selected, armedbits); } mutegroup & mg = mgiterator->second; midibooleans mutebits = mg.get(); /* get mutes set */ bool active = mg.group_state(); result = mutebits.size() == armedbits.size(); if (result) { auto ait = armedbits.begin(); auto mit = mutebits.begin(); for ( ; mit != mutebits.end(); ++mit, ++ait) { if (active) { if (*mit) /* on in group? */ *ait = midibool(false); /* force it off */ } else { *ait = *mit || *ait; } } active = ! active; mg.group_state(active); m_group_selected = active ? group : c_null_mute_group ; } } return result; } void mutegroups::group_learn (bool flag) { if (flag) { m_group_mode = m_group_learn = true; } else { m_group_learn = false; m_group_selected = c_null_mute_group; } } bool mutegroups::group_save (saving mgh) { if (mgh < saving::max) { m_group_save = mgh; return true; } else return false; } bool mutegroups::group_save (const std::string & v) { saving value = string_to_group_save(v); if (value != saving::max) return group_save(value); else return false; } /** * The performer class uses this. It can "modify()". We want to * return true if we affect MIDI. That is, with a status of saving::both * or saving::midi. */ bool mutegroups::group_save (bool midi, bool mutes) { if (midi && mutes) return group_save(saving::both); else if (mutes) return group_save(saving::mutes); else if (midi) return group_save(saving::midi); else { (void) group_save(saving::none); /* new for version 0.99.10 */ return false; } } /** * \getter m_mute_group_save, string version */ std::string mutegroups::group_save_label () const { std::string result = "bad"; if (m_group_save == saving::mutes) result = "mutes"; else if (m_group_save == saving::midi) result = "midi"; else if (m_group_save == saving::both) result = "both"; return result; } bool mutegroups::group_load (loading mgh) { if (mgh >= loading::none && mgh < loading::max) { m_group_load = mgh; return true; } else return false; } bool mutegroups::group_load (const std::string & v) { if (v == "none") return group_load(loading::none); else if (v == "both" || v == "stomp") return group_load(loading::both); else if (v == "mutes") return group_load(loading::mutes); else if (v == "midi" || v == "preserve") return group_load(loading::midi); else return false; } bool mutegroups::group_load (bool midi, bool mutes) { if (midi && mutes) return group_load(loading::both); else if (mutes) return group_load(loading::mutes); else if (midi) return group_load(loading::midi); else return group_load(loading::none); } /** * \getter m_mute_group_load, string version */ std::string mutegroups::group_load_label () const { std::string result = "bad"; if (m_group_load == loading::none) result = "none"; else if (m_group_load == loading::mutes) result = "mutes"; else if (m_group_load == loading::midi) result = "midi"; else if (m_group_load == loading::both) result = "both"; return result; } bool mutegroups::load_mute_groups (bool midi, bool mutes) { if (mutes && midi) m_group_load = loading::both; else if (mutes) m_group_load = loading::mutes; else if (midi) m_group_load = loading::midi; else m_group_load = loading::none; return true; } bool mutegroups::clear () { bool result = const_cast(this)->any(); m_container.clear(); create_empty_mutes(); return result; } /** * Loads all empty mute-groups. Useful in writing an "empty" mutegroups * file. Also useful when needing to create a blank group in qmutemaster. * * Hmmm, rows x columns isn't the way to count the mute groups -- that is the * size of one mute group. Generally, we support only 32 mute-groups. */ bool mutegroups::reset_defaults () { (void) clear(); return true; } /** * Counts the letters of each mute-group name, adding 2 to account for the * double-quote characters used in specifying a mute-group name. The names * are written to the 'mutes' file and to the MIDI file, but, for the latter, * we have to check for the names byte-by-byte. Also, even if empty, the * quotes are still written (and counted here). Otherwise we cannot * determine the end of the group safely. */ int mutegroups::group_names_letter_count () const { int result = 0; for (const auto & mgpair : m_container) { const mutegroup & m = mgpair.second; const std::string & gname = m.name(); result += gname.length() + 2; } return result; } /** * Just shows each mute-group object. For troubleshooting, mainly. */ void mutegroups::show (const std::string & tag, mutegroup::number gmute) const { std::cout << "Mute-group " << tag << " size: " << count() << std::endl; if (gmute == c_null_mute_group) { int index = 0; for (const auto & mgpair : m_container) { int g = mgpair.first; const mutegroup & m = mgpair.second; std::cout << "[" << std::setw(2) << index++ << "] " << g << ": "; m.show(); } } else { auto mgiterator = m_container.find(gmute); bool result = mgiterator != m_container.end(); std::cout << "Mute-group "; std::cout << "[" << std::setw(2) << int(gmute) << "]: "; if (result) mgiterator->second.show(); else std::cout << "MISSING" << std::endl; } } } // namespace seq66 /* * mutegroups.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/notemapper.cpp ================================================ /* * This file is part of seq66. * * 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., * 675 Mass Ave, Cambridge, MA 02139, USA. * * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ /** * \file notemapper.cpp * * This module provides functions for advanced MIDI/text conversions. * * \library libseq66 * \author Chris Ahlstrom * \date 2014-04-24 * \updates 2025-01-09 * \version $Revision$ * \license GNU GPL * */ #include /* std::setw manipulator */ #include /* std::cerr to note errors */ #include "play/notemapper.hpp" /* this module's functions & stuff */ #include "util/strfunctions.hpp" /* seq66::bool_to_string() */ namespace seq66 { /** * Principal constructor for the notemapper::pair class. * * \param devvalue * The integer value of the incoming (key) value. * This might be a non-GM device, like an old drum-pad * machine, that needs to be converted to a GM-drum value to be * properly played on modern hardware or software. * * \param gmvalue * The value to which the device value is to be converted. * * \param devname * The name of the drum note or patch represented by the key value, * which is the the name of the drum on the non-GM input device.. * * \param gmname * The name of the drum note or patch represented by the integer * value gmvalue. * * \param reverse * If true, the conversion is reversed. This value is the same for * all mapping pairs. */ notemapper::pair::pair ( int devvalue, int gmvalue, const std::string & devname, const std::string & gmname, bool reverse ) : m_is_reverse (reverse), m_dev_value (devvalue), m_gm_value (gmvalue), m_dev_name (devname), m_gm_name (gmname), m_remap_count (0) { // no other code } /** * Looks up the device-note in the map, decides if the map is a reversed * map or note, and then reconstructs the "drum" section in a string. */ std::string notemapper::pair::to_string () const { std::string result; int gmnote; int devnote; std::string gmname; std::string devname; if (m_is_reverse) { devnote = gm_value(); devname = gm_name(); gmnote = dev_value(); gmname = dev_name(); } else { gmnote = gm_value(); gmname = gm_name(); devnote = dev_value(); devname = dev_name(); } result += "dev-name = \""; result += dev_name(); result += "\"\n"; result += "gm-name = \""; result += gm_name(); result += "\"\n"; result += "dev-note = "; result += std::to_string(devnote); result += "\n"; result += "gm-note = "; result += std::to_string(gmnote); result += "\n"; return result; } void notemapper::pair::show () const { std::cout << "'" << dev_name() << "' " << dev_value() << " --> " << gm_value() << " '" << gm_name() << "'" << std::endl ; } /** * Default constructor for the note-mapper. * * The default setting of dir is direction::file. */ notemapper::notemapper (direction dir) : basesettings ("Note Mapper"), m_direction (dir), m_mode (false), m_map_type (), m_note_minimum (999), m_note_maximum (0), m_gm_channel (0), m_device_channel (0), m_map_reversed (false), m_note_map (), m_note_array (), m_is_valid (false) { if (dir != direction::file) map_reversed(dir == direction::reverse); for (int n = 0; n < c_notes_count; ++n) m_note_array[n] = midibyte(n); } bool notemapper::add ( int devnote, int gmnote, const std::string & devname, const std::string & gmname ) { bool result = ( devnote >= 0 && devnote < c_notes_count && gmnote >= 0 && gmnote < c_notes_count ); if (result) { auto count = m_note_map.size(); if (map_reversed()) { pair np(gmnote, devnote, devname, gmname, true); /* reversed */ auto p = std::make_pair(gmnote, np); (void) m_note_map.insert(p); m_note_array[gmnote] = devnote; if (devnote < m_note_minimum) m_note_minimum = devnote; if (devnote > m_note_maximum) m_note_maximum = devnote; } else { pair np(devnote, gmnote, devname, gmname, false); /* !reverse */ auto p = std::make_pair(devnote, np); (void) m_note_map.insert(p); m_note_array[devnote] = gmnote; if (gmnote < m_note_minimum) m_note_minimum = gmnote; if (gmnote > m_note_maximum) m_note_maximum = gmnote; } result = m_note_map.size() == (count + 1); /* clunky! */ if (! result) { std::cerr << "Duplicate note pair " << devnote << " & " << gmnote << std::endl ; } } else std::cerr << "Note-mapper note out of range" << std::endl; return result; } /** * Looks up an incoming note, and, if found, returns the mapped note value. * * \param incoming * The note to be remapped. * * \return * Returns the mapped note, if found. Otherwise, the original note is * returned. */ int notemapper::convert (int incoming) const { int result = incoming; auto noteiterator = m_note_map.find(incoming); if (noteiterator != m_note_map.end()) result = noteiterator->second.gm_value(); return result; } /** * Looks up the device-note in the map, decides if the map is a reversed * map or note, and then reconstructs the "drum" section in a string. */ std::string notemapper::to_string (int devnote) const { std::string result; auto noteiterator = m_note_map.find(devnote); if (noteiterator != m_note_map.end()) { const pair & np = noteiterator->second; int gmnote = map_reversed() ? np.dev_value() : np.gm_value(); result = "[Drum "; result += std::to_string(gmnote); result += "]\n\n"; result += np.to_string(); } return result; } void notemapper::show () const { std::cout << "Note-map size: " << list().size() << "\n" << " Type: " << map_type() << "\n" << " Reversed: " << bool_to_string(map_reversed()) << "\n" << " Note minimum: " << note_minimum() << "\n" << " Note maximum: " << note_maximum() << "\n" << " Dev channel: " << std::dec << device_channel() << "\n" << " GM channel: " << std::dec << gm_channel() << "\n" << std::endl ; for (auto & np : list()) { std::cout << "Key " << np.first << ": "; np.second.show(); } } } // namespace seq66 /* * notemapper.cpp * * vim: sw=4 ts=4 wm=8 et ft=cpp */ ================================================ FILE: libseq66/src/play/performer.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file performer.cpp * * This module defines the base class for the performer of MIDI patterns. * * \library seq66 application * \author Chris Ahlstrom and others * \date 2018-11-12 * \updates 2026-04-17 * \license GNU GPLv2 or above * * Also read the comments in the Seq64 version of this module, perform. * This class is probably the single most important class in Seq66, as it * supports sequences, mute-groups, sets, playback, JACK, and more. * * The automation slots supported are defined in the enumeration seq66 :: * automation :: slot. Their human readable names are defined in opcontrol :: * automation_slot_name (). Their default keystrokes are defined in * keycontainer :: keys_automation (). Their internal name are defined in the * automation.cpp module, in the static array s_slotnamelist[]. The * automation call-back functions are defined in this module, the performer * module. More information is included in the user's manual, in section * 9.5.7 "'ctrl' File / Keyboard / Default Assignments" and in the * Libreoffice spreadsheets in the "doc" directory, which may be out-of-date. * * Keystrokes versus MIDI controls: MIDI can support toggle, on, and off * actions. Keystrokes can only be pressed and released. Each keystroke can * be used for a toggle, which should be triggered on a press event or a * release event, but not both. A keystroke's press event can also be used * for an on, and the release event can be used for an off. These two modes * of operation depend on the slot(s) involved. * * Playscreen vs screenset in Seq24: * * m_playing_screen is used in: * * - select_group_mute(). Sets the selected mute group number and * stores the mute group if learn is active. Used in midifile, * optionsfile, and userfile. * - select_mute_group(). Almost the same in a stilted way, but also * saves the state of the mute group in a small set array, "tracks * mute". Used indirectly in mainwnd to "activate" the desired * mute-group. * - mute_group_tracks(). If in group mode, sets the sequences * according to the state in "tracks mute". * - set_playing_screenset(). Sets "tracks mute" per the current * playing screen. Changes the playing screen to the current screen * set, then calls mute_group_tracks(). Called by MIDI control * "play_ss", and the Home key handling in mainwnd. * - sequence_playing_on() and _off(). If in group mode and the playing * set is the screen set, sets "tracks mute" for that sequence. * * Playback/recording coordination via condition variables: * * - performer(). Create an seq66:condition instance "c". * - inner_start(). * -# c.lock(), which locks the mutex. * -# Start running and flag it. * -# c.signal(), which calls notify_one(). * -# c.unlock(), which unlocks the mutex. * - output_func(). * -# c.lock(), which locks the mutex. * -# While not running, c.wait() [on the signal]. * -# Once signalled, if not outputing, we are stopping. * -# c.unlock(), which unlocks the mutex. * - ~ performer(). * -# c.signal() * -# Join the output and input threads. * * MIDI CLOCK Support: * * MIDI beat clock (MIDI timing clock or MIDI clock) is a clock signal that * is broadcast via MIDI to ensure that several MIDI-enabled devices such * as a synthesizer or music sequencer stay in synchronization. MIDI beat * clock is tempo-dependent. Clock events are sent at a rate of 24 times * every quarter note. Those pulses maintain a synchronized tempo for * synthesizers with BPM-dependent voices, and for arpeggiator * synchronization. Location information is specified using the Song * Position Pointer (SPP) although many simple MIDI devices ignore this * message. Because of limitations in MIDI and synthesizers, devices * driven by MIDI beat clock are often subject to clock drift. * * On output: * * - perform::m_usemidiclock starts at false; * - It is set to false in pause_playing(). * - It is set to the midiclock parameter of inner_stop(). * - If m_usemidiclock is true: * - It affects m_midiclocktick in output. * - The position in output cannot be repositioned. * - The tick location cannot be changed. * * On input: * * - If MIDI Start is received, m_midiclockrunning and m_usemidiclock * become true, and m_midiclocktick and m_midiclockpos become 0. * - If MIDI Continue is received, m_midiclockrunning is set to true and * we start according to song-mode. * - If MIDI Stop is received, m_midiclockrunning is set to false, * m_midiclockpos is set to the current tick (!), all_notes_off(), and * inner_stop(true) [sets m_usemidiclock = true]. * - If MIDI Clock is received, and m_midiclockrunning is true, then * m_midiclocktick += m_midiclockincrement. * - If MIDI Song Position is received, then m_midiclockpos is set as per * in data in this event. * - MIDI Active Sense and MIDI Reset are currently filtered by the JACK * implementation. * * Locking: * * -# The flags m_inputing and m_outputing start out true. * -# When the perform starts, the input thread starts. * -# When the perform starts, the output thread then starts. * -# The output thread then waits on the condition variable for * inner_start() to set is_running() to true. It then proceeds to run * forever. * -# In the destructor, the flags m_inputing and m_outputing are * replaced with m_io_active, set to false, and the condition * variable is signalled. This causes the output thread to exit. * The input thread detects that m_io_active is false and exits. * -# The two threads are then joined. * * Threads: * * https://eli.thegreenplace.net/2016/c11-threads-affinity-and-hyperthreading/ * * https://www.acodersjourney.com/top-20-cplusplus-multithreading-mistakes/ * * $ taskset -c 5,6 ./Seq66qt/qseq66 ... * * https://www.glennklockwood.com/hpc-howtos/process-affinity.html * * Assuming your executable is called application.x, see what cores each * thread is using by issuing the following command in bash: * * $ for i in $(pgrep application.x); * do ps -mo pid,tid,fname,user,psr -p $i; done * * The PSR field is the OS identifier for the core each TID (thread id) * is utilizing. * * pthreads: * * We were using sched_setscheduler(), but user gresade reported issue * #179, with some jitter in playback/recording, and we noticed that the * man page indicated to use the pthread_setsched_param function instead * when using pthreads, which Sequencer64 does use in Linux. * * http://ccrma.stanford.edu/planetccrma/software/understandlowlat.html: * * Summarizing, you need tuned drivers that do not disable interrupts * for long, low latency patches in the kernel so that the scheduler * runs often enough and your application itself has to run with the * SCHED_FIFO scheduling policy so that it gets the best chance of * grabbing the processor when it needs it. * * When everything is in place things work incredibly well. The * system can be running an audio task with no dropouts and a few * milliseconds of latency while the computer is being loaded with * disk accesses, screen refreshes and whatnot. The mouse gets jerky, * windows update very slowly but not a dropout to be heard. * * http://www.informit.com/articles/article.aspx?p=101760&seqNum=4: * * Two or more SCHED_FIFO tasks at the same priority run round robin. * If a SCHED_FIFO task is runnable, all tasks at a lower priority * cannot run until it finishes. * * SCHED_RR is identical to SCHED_FIFO except that each process can * only run until it exhausts a predetermined timeslice. That is, * SCHED_RR is SCHED_FIFO with timeslices—it is a real-time * round-robin scheduling algorithm. * * Real-time priorities range inclusively from one to MAX_RT_PRIO * minus one. By default, MAX_RT_PRIO is 100—therefore, the default * real-time priority range is one to 99. This priority space is * shared with the nice values of SCHED_OTHER tasks; they use the * space from MAX_RT_PRIO to (MAX_RT_PRIO + 40). By default, this * means the –20 to +19 nice range maps directly onto the 100 to 140 * priority range. * * Settings lifecycle: * * First, note that performer will use only the "global" rcsettings * object, as retrieved by the seq66::rc() function. The same is true * for seq66::usr(). * * -# The static rcsettings value creates its own copy of the key and * MIDI control containers. * -# In the keycontainer constructor, it calls its set_defaults() * function to set up the default keystrokes. * -# The midicontrolin does not do this. It remains empty. * -# The performer constructor creates its own key and MIDI control * containers. Again, only the keycontainer has default values in * it. * -# In main(), we run parse_options_files(), which creates an rcfile * object. If there is a "[midi-control-file]" section, it is parsed, * otherwise the control data is parsed from the "rc" file. This * data goes into the "global" settings object, rc(). * -# If parse_options_file() succeeds, then the performer gets the * settings from rc(), and launches. * -# After ending, we get the latest settings from the performer, and * copy them into the "global" rc(). * -# The options are then written. * * Modify action: * * A modify action is any change that would require the current MIDI tune * to be saved before closing the application or loading a new MIDI tune. * These actions include: a change in a song/pattern parameter setting; * modification of the triggers in the Song editor; a change in output * buss (though selection of the input buss is saved in the "rc" file); * and anything else? When they occur, performer :: modify() is called. * * One issue with modification is that we don't have comprehensive * tracking of all "undo" operations, so that, once the modify flag is * set, only saving the MIDI tune will unset it. See the calls to * performer :: unmodify(). * * Also note that some of the GUI windows have their own, unrelated, * modify() function. */ #include /* std::find() for std::vector */ #include /* std::round() */ #include /* std::cout */ #include /* std::ostringstream */ #include "cfg/mutegroupsfile.hpp" /* seq66::mutegroupsfile */ #include "cfg/notemapfile.hpp" /* seq66::notemapfile */ #include "cfg/playlistfile.hpp" /* seq66::playlistfile */ #include "cfg/settings.hpp" /* seq66::rcsettings rc(), etc. */ #include "ctrl/keystroke.hpp" /* seq66::keystroke class */ #include "midi/midifile.hpp" /* seq66::read_midi_file() */ #include "play/notemapper.hpp" /* seq66::notemapper */ #include "play/performer.hpp" /* seq66::performer, this class */ #include "os/daemonize.hpp" /* seq66::signal_for_exit() */ #include "os/timing.hpp" /* seq66::microsleep(), microtime() */ #include "util/filefunctions.hpp" /* seq66::filename_base(), etc. */ namespace seq66 { /** * This value is the "trigger width" in microseconds, a Seq24 concept. * It also had a "lookahead" time of 2 ms, not used however. */ static const int c_thread_trigger_width_us = 4 * 1000; /** * When operating a playlist, especially from a headless seq66cli run, and * with JACK transport active, the change from a playing tune to the next * tune would really jack up JACK, crashing the app (corrupted double-linked * list, double frees in destructors, etc.) and sometimes leaving a loud tone * buzzing. So after we stop the current tune, we delay a little bit to * allow JACK playback to exit. * * Actually also an issue with ALSA, finding null events or deleted sequences in * the middle of play(). */ static const int c_delay_start = 1000; /** * Indicates how much of a long file-path we will show using the * shorten_file_spec() function. */ static const int c_long_path_max = 56; /** * Principal constructor. */ performer::performer (int ppq, int rows, int columns) : m_song_info (), m_smf_format (1), m_error_pending (false), m_error_messages (), m_play_set (), m_play_set_storage (), m_play_list (), m_note_mapper (new (std::nothrow) notemapper()), m_metronome (), /* no metronome by default */ m_recorder (nullptr), /* no background recording */ m_metronome_count_in (false), m_song_start_mode (sequence::playback::automatic), m_reposition (false), m_excell_FF_RW (1.0), m_FF_RW_button_type (ff_rw::none), m_old_seqno (seq::unassigned()), m_current_seqno (seq::unassigned()), m_moving_seq (), m_seq_clipboard (), m_queued_replace_slot (seq::unassigned()), m_clocks (), /* vector wrapper class */ m_inputs (), /* vector wrapper class */ m_port_map_error (false), m_key_controls ("Key controls"), m_midi_control_in ("Performer ctrl in"), m_midi_control_out ("Performer ctrl out"), m_mute_groups ("Mute groups", rows, columns), /* mutes() */ m_operations ("Performer operations"), m_set_master (rows, columns), /* 32 row x column sets */ m_set_mapper /* access via set_mapper() */ ( m_set_master, m_mute_groups, rows, columns ), m_transpose (0), m_out_thread (), m_in_thread (), m_out_thread_launched (false), m_in_thread_launched (false), m_io_active (false), /* !done(), set in launch() */ m_is_running (false), m_is_pattern_playing (false), m_needs_update (true), m_is_busy (false), /* try this flag for now */ m_looping (false), m_song_recording (false), m_song_record_snap (true), m_record_snap_length (0), m_record_alteration (usr().record_alteration()), m_record_style (usr().pattern_record_style()), m_resume_note_ons (usr().resume_note_ons()), m_ppqn (choose_ppqn(ppq)), m_bpm (usr().midi_beats_per_minute()), m_resolution_change (true), m_current_beats (0), m_delta_us (0), m_base_time_ms (0), m_last_time_ms (0), m_beats_per_bar (usr().midi_beats_per_bar()), m_beat_width (usr().midi_beat_width()), m_clocks_per_metronome (c_midi_clocks_per_metronome), m_32nds_per_quarter (c_midi_32nds_per_quarter), m_us_per_quarter_note (0), /* depends on the tempo */ m_master_bus (), /* this is a shared pointer */ m_record_by_buss (false), m_record_by_channel (false), m_buss_patterns (), m_one_measure (0), m_fast_ticks (0), m_left_tick (0), m_right_tick (0), m_start_tick (0), m_tick (0), m_max_extent (0), m_jack_pad (), /* data for JACK... & ALSA */ m_jack_tick (0), m_usemidiclock (false), /* MIDI Clock support */ m_midiclockrunning (false), m_midiclocktick (0), m_midiclockincrement (clock_ticks_from_ppqn(m_ppqn)), m_midiclockpos (0), m_dont_reset_ticks (false), /* support for pausing */ m_is_modified (false), #if defined USE_SONG_BOX_SELECT m_selected_seqs (), #endif m_condition_var (*this), /* private access via cv() */ #if defined SEQ66_JACK_SUPPORT m_jack_asst ( *this, usr().bpm_default(), /* beats per minute */ m_ppqn, usr().bpb_default(), /* beats per bar (measure) */ usr().bw_default() /* beat width (denominator) */ ), #endif m_have_undo (false), m_undo_vect (), m_have_redo (false), m_redo_vect (), m_notify (), m_signalled_changes (! seq_app_cli()), /* !usr().app_is_headless() */ m_seq_edit_pending (false), m_event_edit_pending (false), m_record_toggle_pending (false), m_pending_loop (seq::unassigned()), m_slot_shift (0), m_hidden (false), m_show_hide_pending (false) { /* * Generally will be parsing the 'rc' files after creating the performer. * (void) get_settings(rc(), usr()); */ (void) populate_default_ops(); } /** * The destructor sets some running flags to false, signals this condition, * then joins the input and output threads if the were launched. * * A thread that has finished executing code, but has not yet been joined is * still considered an active thread of execution and is therefore joinable. */ performer::~performer () { (void) finish(); /* sets m_io_active to false, etc. */ } /** * Register a class that inherits from performer::callbacks to be notified of * various happenings. */ void performer::enregister (callbacks * pfcb) { if (not_nullptr(pfcb)) { auto it = std::find(m_notify.begin(), m_notify.end(), pfcb); if (it == m_notify.end()) m_notify.push_back(pfcb); } } /** * Removes a class from the notification list. Used in transitory windows * and frames that need notification. */ void performer::unregister (callbacks * pfcb) { if (not_nullptr(pfcb)) { auto it = std::find(m_notify.begin(), m_notify.end(), pfcb); if (it != m_notify.end()) (void) m_notify.erase(it); } } /** * This function emits an error message to cerr via the basic_macros * global function error_message(). */ void performer::append_error_message (const std::string & msg) const { static std::vector s_old_msgs; std::string newmsg = msg; m_error_pending = true; /* a mutable boolean */ if (newmsg.empty()) newmsg = "Performer error"; if (! m_error_messages.empty()) { const auto finding = std::find ( s_old_msgs.cbegin(), s_old_msgs.cend(), newmsg ); if (finding == s_old_msgs.cend()) { m_error_messages += " "; m_error_messages += newmsg; s_old_msgs.push_back(newmsg); seq66::error_message("Performer", newmsg); } } else { m_error_messages = newmsg; s_old_msgs.push_back(newmsg); seq66::error_message("Performer", newmsg); } } /** * Changes the track-info data. * * We have to find the original first Meta Text event, if any, and then * remove it and add its replacement. * * \param s * The string to be saved as song info. It must already have been * converted to "midi-bytes" format. * * \param trk * The track number, pattern number. * * \return * Returns true if the track was found. */ bool performer::set_track_info (const std::string & s, seq::number trk) { seq::pointer seqp = get_sequence(trk); bool result = bool(seqp); if (result) { event metatext(0, EVENT_MIDI_META, 0); /* tricky, d0 = 0 */ metatext.set_channel(EVENT_META_TEXT_EVENT); metatext.set_text(s); /* not used in the match */ (void) seqp->remove_first_match(metatext); if (seqp->add_event(metatext)) { seqp->sort_events(); /* important! */ notify_sequence_change(0, change::yes); } } return result; } /** * Get the first (or next) matching Meta Text event and return it. * * \param trk * The track number, pattern number. * * \param nextmatch * If true, get the next match instead of the first match. * * \return * Returns a copy of the found event. The can use event::timestamp() * and event::get_text() to get the data relevant to the qsessionframe * (for example). If event::get_status() returns 0, the event is * not found and not usable. */ event performer::get_track_info_event (seq::number trk, bool nextmatch) { static event s_null_event{0, 0, 0}; seq::pointer seqp = get_sequence(trk); bool ok = bool(seqp); if (ok) { event metatext(0, EVENT_MIDI_META, 0); /* tricky, d0 = 0 */ metatext.set_channel(EVENT_META_TEXT_EVENT); return seqp->find_event(metatext, nextmatch); } else return s_null_event; } void performer::song_info (const std::string & s, seq::number trk) { if (s != m_song_info) { (void) set_track_info(s, trk); if (trk == 0) m_song_info = s; } } std::string performer::song_info () const { return midi_bytes_to_string(m_song_info); } std::string performer::get_all_track_text (seq::number trk) { static event s_null_result{0, 0, 0}; std::string result; seq::pointer seqp = get_sequence(trk); bool ok = bool(seqp); if (ok) { auto cev = seqp->cbegin(); for (;;) { if (seqp->get_next_meta_match(EVENT_META_TEXT_EVENT, cev)) { result += cev->get_text(); result += " "; ++cev; } else break; } } return result; } void performer::unmodify () { m_is_modified = false; set_mapper().unmodify_all_sequences(); } /** * This improved version checks all of the sequences. This allow the user to * unmodify a sequence without using performer::modify(). First usage of * sequence::modify() and unmodify() is in qpatternfix. */ bool performer::modified () const { bool result = m_is_modified; if (! result) result = set_mapper().any_modified_sequences(); return result; } void performer::notify_automation_change (automation::slot s) { for (auto notify : m_notify) (void) notify->on_automation_change(s); } /* * Note that we need to call modify() before telling the subscribers, so that * they can check the status of the performer. This is not strictly * necessary, but some subscribers still call performer::modified() instead * of using the parameter. */ void performer::notify_set_change (screenset::number setno, change mod) { if (changed(mod)) modify(); for (auto notify : m_notify) (void) notify->on_set_change(setno, mod); } void performer::notify_mutes_change (mutegroup::number mutesno, change mod) { for (auto notify : m_notify) (void) notify->on_mutes_change(mutesno, mod); if (mod == change::yes) modify(); } /** * Called by qseqeventframe. This function will eventually cause a call to * recreate all the slot buttons in qslivegrid, and when qslivegrid :: * refresh() is called, it can find all the buttons deleted. */ void performer::notify_sequence_change (seq::number seqno, change mod) { bool redo = mod == change::recreate; if (mod == change::yes || redo) modify(); if (get_sequence(seqno)) { for (auto notify : m_notify) (void) notify->on_sequence_change(seqno, mod); } } /** * Added for processing sequence deletion (as opposed to sequence cutting). * It removes the test for sequence existence so that notification can * occur. Called by qslivebase::delete_seq(). */ void performer::notify_sequence_removal (seq::number seqno, change mod) { bool redo = mod == change::recreate; if (mod == change::yes || redo) modify(); for (auto notify : m_notify) (void) notify->on_sequence_change(seqno, mod); } /** * This notification currently does not cause a modify action. */ void performer::notify_ui_change (seq::number seqno, change /*mod*/) { for (auto notify : m_notify) (void) notify->on_ui_change(seqno); } void performer::notify_trigger_change (seq::number seqno, change mod) { for (auto notify : m_notify) (void) notify->on_trigger_change(seqno, mod); if (mod == change::yes) { modify(); } else if (mod == change::no) { if (seq_in_playing_screen(seqno)) { const seq::pointer s = get_sequence(seqno); seqno %= screenset_size(); announce_sequence(s, seqno); } } } /** * Allows notification of changes in the PPQN and tempo (beats-per-minute, * BPM). */ void performer::notify_resolution_change (int ppq, midibpm bpm, change mod) { m_resolution_change = true; for (auto notify : m_notify) (void) notify->on_resolution_change(ppq, bpm, mod); if (mod == change::yes) modify(); } /** * Notifies when the use selects a new song or playlist. * * \param signalit * If true, emit a signal, to avoid conflict with the GUI. */ void performer::notify_song_action (bool signalit, playlist::action act) { for (auto notify : m_notify) (void) notify->on_song_action(signalit, act); } /* * ------------------------------------------------------------------------- * Settings Get/Put * ------------------------------------------------------------------------- */ /** * Gets the settings and applies them to the performer. The clocks and input * settings will eventually be copied to the mastermidibus, which might change * them due to changes in plugged devices. * * The clocks and inputs values will later be updated wth the masterbus * clocks and inputs as retrieved at run-time. Generally, we need at least * one output device, or we will fail. * * WHAT ABOUT THE PLAYLIST? * * \note * Playlist filename is handled by rcsettings, but the playlist itself is * handled by the performer. * * \param rcs * Provides the source of the settings. * * \return * Returns true if all of the settings were obtained. However, it isn't * necessarily an error. Something to think about. */ bool performer::get_settings (const rcsettings & rcs, const usrsettings & usrs) { int buses = rcs.clocks().count(); bool result = buses > 0; /* at least 1 output */ if (result) { m_clocks = rcs.clocks(); int inputs = rcs.inputs().count(); if (inputs > 0) m_inputs = rcs.inputs(); /* * At this point, the names are not yet set in the clocks and inputs. * * result = build_output_port_map(m_clocks); */ } /* * If using virtual (manual) ports, then we disable the input and output * port maps. */ if (rcs.manual_ports()) { inputslist & ipm = input_port_map(); clockslist & opm = output_port_map(); ipm.active(false); opm.active(false); } int kcount = rcs.key_controls().count(); int micount = rcs.midi_control_in().count(); int moacount = rcs.midi_control_out().action_count(); int momcount = rcs.midi_control_out().macro_count(); if (kcount > 0) m_key_controls = rcs.key_controls(); msgprintf ( msglevel::status, "Controls: %d keys; %d MIDI in; %d automation displays; %d macros", kcount, micount, moacount, momcount ); /* * We need to copy the MIDI input controls whether the user has enabled * them or not. Otherwise, the controls are replaced by the defaults * during the 'ctrl' file save at exit, which is surprising to the poor * user. See issue #47. */ m_midi_control_in = rcs.midi_control_in(); if (micount == 0 && kcount > 0) m_midi_control_in.add_blank_controls(m_key_controls); m_midi_control_out = rcs.midi_control_out(); if (rc().mute_group_file_active()) { const std::string & mgf = rc().mute_group_filespec(); (void) open_mutegroups(mgf); } if (! rc().song_start_auto()) /* detect live vs song */ song_start_mode(rcs.get_song_start_mode()); /* force the mode */ record_by_buss(rcs.record_by_buss()); record_by_channel(rcs.record_by_channel()); m_resume_note_ons = usrs.resume_note_ons(); return result; } /** * If ALSA is active, checks for the presence of the usage of a MIDI * Through port for both control and display. * * \return * Returns true if both MIDI Throughs are not used, and hence the * control setup is safe. */ bool performer::alsa_midi_through_check () { bool result = true; std::string outbusname; e_clock ec; int oldindex = midi_control_out().configured_buss(); bool good = rc().with_alsa_midi(); if (good) { good = midi_control_in().is_enabled(); if (good) good = ui_get_clock(bussbyte(oldindex), ec, outbusname); if (good) { bool out_is_thru = contains(outbusname, "Midi Through"); std::string inbusname; bool inputing; good = midi_control_out().is_enabled(); if (good) { oldindex = midi_control_in().configured_buss(); good = ui_get_input(bussbyte(oldindex), inputing, inbusname); if (good) { bool both = out_is_thru && contains(inbusname, "Midi Through"); if (both) result = false; } } } } return result; } /** * Copies the settings to an external settings object. * * The clocks and input settings might be modified by mastermidibus. * Therefore, we refill these containers before passing them back to * rcsettings. * * Please note that we will upgrade mastermidibus to use the clockslist and * inputslist classes, rather than accessing the vectors directly. * * \param rcs * Provides the destination for the 'rc' settings. Normally, this is * the global settings object returned by rc(). * * \param usrs * Provides the destination for the 'usr' settings. Normally, this is * the global settings object returned by usr(). * * \return * Returns true if the settings were proper and were copied. */ bool performer::put_settings (rcsettings & rcs, usrsettings & usrs) { /* * We cannot allow certain changes made outside of the Preferences GUI to * be saved (e.g the Live/Song button in the main window). * * bool pb = song_mode(); * rcs.song_start_mode(pb); */ if (m_master_bus) { m_master_bus->get_port_statuses(m_clocks, m_inputs); rcs.clocks() = m_clocks; rcs.inputs() = m_inputs; } rcs.key_controls() = m_key_controls; rcs.midi_control_in() = m_midi_control_in; rcs.midi_control_out() = m_midi_control_out; if (mutes().is_modified() && rc().mute_group_file_active()) { const std::string & mgf = rc().mute_group_filespec(); (void) save_mutegroups(mgf); } /* * We don't want to disable the user's desires here just because * the conditions weren't met by the loaded song. * * rcs.record_by_buss(m_record_by_buss); * * No, we need to be consistent. */ rcs.record_by_buss(m_record_by_buss); /* ca 2024-08-18 */ rcs.record_by_channel(m_record_by_channel); usrs.resume_note_ons(m_resume_note_ons); /* * We also need to update the playlist file-name in case the user loaded * or removed the playlist. We don't want the result of * performer::playlist_filename() because that contains the path needed to * open the playlist. * * rcs.playlist_filename(playlist_filename()); */ rcs.playlist_filename(rc().playlist_filename()); rcs.playlist_active(playlist_active()); return true; } /** * A helper function for the user-interface, this function retrieves the * name of the keystroke for a given automation control. */ std::string performer::automation_key (automation::slot s) { int index = slot_to_int_cast(s); return m_key_controls.automation_key(index); } /** * We need to restrict even the playlist files to the configuration directory * for the session. * * m_play_list->file_name(rc().playlist_filespec()); */ void performer::playlist_filename (const std::string & basename) { if (name_has_path(basename)) { m_play_list->file_name(basename); } else { rc().playlist_filename(basename); m_play_list->file_name(basename); } } /** * Reloads the mute groups from the "mutes" file. * * \param errmessage * A pass-back parameter for any error message the file-processing might * cause. * * \return * Returns true if the reload succeeded. */ bool performer::reload_mute_groups (std::string & errmessage) { const std::string filename = rc().mute_group_filespec(); bool result = open_mutegroups(filename); if (result) { result = get_settings(rc(), usr()); } else { std::string msg = filename; msg += ": reading mutes failed"; errmessage = msg; append_error_message(errmessage); /* show it on console */ } return result; } bool performer::store_io_maps () { bool oki = build_input_port_map(m_inputs); bool oko = build_output_port_map(m_clocks); bool result = oki && oko; if (result) { /* * Not until user sets this flag: rc().portmaps_active(true); */ if (! rc().first_run_in_progress()) rc().auto_rc_save(true); } return result; } void performer::clear_io_maps () { clear_input_port_map(); clear_output_port_map(); rc().portmaps_active(false); rc().auto_rc_save(true); } void performer::activate_io_maps (bool active) { activate_input_port_map(active); activate_output_port_map(active); rc().auto_rc_save(true); } /** * Provides a way to store the I/O maps and restart in a const context. * See qt5nsmanager::show_error(). */ void performer::store_io_maps_and_restart () const { performer * ncperf = const_cast(this); bool ok = ncperf->store_io_maps(); if (ok) signal_for_restart(); } bussbyte performer::true_input_bus (bussbyte nominalbuss) const { bussbyte result = nominalbuss; if (! is_null_buss(result)) { result = seq66::true_input_bus(m_inputs, nominalbuss); if (is_null_buss(result)) { bool busstatus; /* not used here */ std::string busname; /* this is what we want */ (void) ui_get_input(nominalbuss, busstatus, busname, false); std::string msg = "Unavailable input bus "; msg += std::to_string(unsigned(nominalbuss)); if (! busname.empty()) { msg += " \""; msg += busname; msg += "\""; } msg += ". Check ports in the rc/ctrl files."; m_port_map_error = true; /* mutable boolean */ append_error_message(msg); } } return result; } /** * Gets the status of the is bus from the input port-map or, if the map is * not active, from the master bus. * * \param bus * The index number for the bus entry to be retrieved. * * \param [out] active * Set to true if the bus is enabled for I/O. * * \param [out] n * Holds the name of the bus, if found. * * \param statusshow * If true (the default) and the bus is unavailable, then append * "(unavailable)" to the name of * the bus. * * \return * Returns true if the bus was found. This means the name was * able to be retrieved. */ bool performer::ui_get_input ( bussbyte bus, bool & active, std::string & n, bool statusshow ) const { const inputslist & ipm = input_port_map(); bool unavailable = false; std::string name; std::string alias; if (ipm.active()) /* Should we do this in one call? */ { name = ipm.get_name(bus); alias = ipm.get_alias(bus, rc().port_naming()); active = ipm.get(bus); /* input enabled for this bus? */ unavailable = ! ipm.is_available(bus); } else if (m_master_bus) /* Should we do this in one call? */ { name = m_master_bus->get_midi_bus_name(bus, midibase::io::input); alias = m_master_bus->get_midi_alias(bus, midibase::io::input); active = m_master_bus->get_input(bus); } if (! alias.empty()) { name += " '"; name += alias; name += "'"; } if (unavailable && statusshow) name += " (unavailable)"; n = name; return ! name.empty(); } bool performer::is_input_system_port (bussbyte bus) const { return master_bus() ? master_bus()->is_input_system_port(bus) : false ; } /** * This check could be made more robust by not only seeing if there aren't * enough mapped ports, but also by enumating to see if any * real ports are not mapped (given an active map). */ bool performer::new_ports_available () const { bool result = false; if (not_nullptr(master_bus())) { const mastermidibus * mbus = master_bus(); bool new_outputs = false; const clockslist & opm = output_port_map(); if (opm.active()) { int mappedbuses = opm.available_count(); int realbuses = mbus->get_num_out_buses(); new_outputs = mappedbuses < realbuses; } bool new_inputs = false; const inputslist & ipm = input_port_map(); if (ipm.active()) { int mappedbuses = ipm.available_count(); int realbuses = mbus->get_num_in_buses(); new_inputs = mappedbuses < realbuses; } if (! m_port_map_error) /* might have earlier errors */ { m_port_map_error = result = new_outputs || new_inputs; } } return result; } bool performer::is_port_unavailable (bussbyte bus, midibase::io iotype) const { bool result = true; bool processed = false; if (iotype == midibase::io::output) { const clockslist & opm = output_port_map(); bool outportmap = opm.active(); if (outportmap) { result = ! opm.is_available(bus); processed = true; } } else if (iotype == midibase::io::input) { const inputslist & ipm = input_port_map(); bool inportmap = ipm.active(); if (inportmap) { result = ! ipm.is_available(bus); processed = true; } } if (! processed) { result = master_bus() ? master_bus()->is_port_unavailable(bus, iotype) : true ; } return result; } /** * Checks for unavailable system ports. * * ISSUE: * * Given these output settings (ignoring port counts, map is active): * * [midi-clock] * 0 0 "[0] 14:0 Midi Through Port-0" * 1 0 "[1] 128:0 VMPK Input:in" * 2 0 "[2] 130:0 FLUID Synth (3063):Synth input port (3063:0)" * * [midi-clock-map] * 0 0 "Midi Through Port-0" * 1 -2 "Yamaha PSS-680" * 2 -2 "Yamaha PSS-790" * 3 0 "VMPK Input:in" * 4 0 "FLUID Synth" * * Below we get the mapped-bus count, but iterate through the * masterbus's list. We need to get the true output bus instead! * Then we get a much more informative startup error message. */ bool performer::any_ports_unavailable (bool accept_zero_inputs) const { bool result = is_nullptr(master_bus()); const mastermidibus * mbus = master_bus(); if (! result) { const clockslist & opm = output_port_map(); bool outportmap = opm.active(); int buses = outportmap ? opm.count() : mbus->get_num_out_buses() ; if (buses == 0) { result = true; } else { for (int bus = 0; bus < buses; ++bus) { bussbyte b = true_output_bus(bus); /* maybe translate */ if (is_null_buss(result)) { result = true; break; } else { if (mbus->is_port_unavailable(b, midibase::io::output)) { if (! mbus->is_port_locked(b, midibase::io::output)) { result = true; break; } } } } } } if (! result) { const inputslist & ipm = input_port_map(); bool inportmap = ipm.active(); int buses = inportmap ? ipm.count() : mbus->get_num_in_buses() ; if (buses == 0) { result = ! accept_zero_inputs; } else { for (int bus = 0; bus < buses; ++bus) { bussbyte b = true_input_bus(bus); /* maybe translate */ if (is_null_buss(result)) { result = true; break; } else { if (mbus->is_port_unavailable(b, midibase::io::input)) { if (! mbus->is_port_locked(b, midibase::io::input)) { result = true; break; } } } } } } return result; } /** * Sets the main input bus, and handles the special "key labels on sequence" * and "sequence numbers on sequence" functionality. This function is called * by qinputcheckbox :: input_callback_clicked(). Note that the * mastermidibus :: set_input() function passes the setting along to the * input busarray. * * \param bus * Provides the buss number, less than c_busscount_max, not checked. * * \param active * Indicates whether the buss or the user-interface feature is active or * inactive. */ bool performer::ui_set_input (bussbyte bus, bool active) { bussbyte truebus = true_input_bus(bus); bool result = m_master_bus->set_input(truebus, active); if (result) { inputslist & ipm = input_port_map(); if (ipm.active()) result = ipm.set(bus, active); set_input(bus, active); set_mapper().set_dirty(); rc().auto_rc_save(true); /* force the save of 'rc' */ } return result; } bool performer::ui_get_clock ( bussbyte bus, e_clock & e, std::string & n, bool statusshow ) const { const clockslist & opm = output_port_map(); bool unavailable = false; std::string name; std::string alias; if (opm.active()) /* Should we do this in one call? */ { unavailable = ! opm.is_available(bus); name = opm.get_name(bus); alias = opm.get_alias(bus, rc().port_naming()); e = opm.get(bus); /* type of clocking for this bus */ } else if (m_master_bus) /* Should we do this in one call? */ { name = m_master_bus->get_midi_bus_name(bus, midibase::io::output); alias = m_master_bus->get_midi_alias(bus, midibase::io::output); e = m_master_bus->get_clock(bus); } if (! alias.empty()) { name += " '"; name += alias; name += "'"; } if (unavailable && statusshow) /* e == e_clock::unavailable */ name += " (unavailable)"; n = name; return ! name.empty(); } bool performer::port_maps_active () const { return input_port_map().active() && output_port_map().active(); } bussbyte performer::true_output_bus (bussbyte nominalbuss) const { bussbyte result = nominalbuss; if (! is_null_buss(result)) { result = seq66::true_output_bus(m_clocks, nominalbuss); if (is_null_buss(result)) { e_clock clockvalue; /* not used here */ std::string busname; /* this is what we want */ (void) ui_get_clock(nominalbuss, clockvalue, busname, false); if (busname.empty()) busname = ""; std::string msg = "Unavailable output bus "; msg += std::to_string(unsigned(nominalbuss)); msg += " \""; msg += busname; msg += "\""; msg += ". Check ports in tune, rc, ctrl, & " "usr files, and MIDI I/O & Metronome tabs. " ; m_port_map_error = true; /* mutable boolean */ append_error_message(msg); } } return result; } /** * Sets the clock value, as specified in the Preferences / MIDI Clocks tab. * Note that the call to mastermidibus::set_clock() also sets the clock in * the output busarray. * * \param bus * The bus index to be set. It is converted to the actual bus number, if * necessary. * * \param clocktype * Indicates whether the buss or the user-interface feature is * e_clock_off, e_clock_pos, e_clock_mod, or (new) e_clock_disabled. */ bool performer::ui_set_clock (bussbyte bus, e_clock clocktype) { bussbyte truebus = true_output_bus(bus); bool result = m_master_bus->set_clock(truebus, clocktype); if (result) { clockslist & opm = output_port_map(); if (opm.active()) result = opm.set(bus, clocktype); set_clock(bus, clocktype); set_mapper().set_dirty(); rc().auto_rc_save(true); /* force the save of 'rc' */ } return result; } /* * ------------------------------------------------------------------------- * Labeling Functions * ------------------------------------------------------------------------- */ /** * Provides a way to format the sequence parameters string for display in the * mainwnd or perfnames modules. This string goes on the bottom-left of * those user-interface elements. * * The format of this string is something like the following example. The * values shown are: sequence number, buss number, channel number, beats per * bar, and beat width. * \verbatim 9 31-16 4/4 \endverbatim * * The sequence number and buss number are re 0, while the channel number is * displayed re 1, unless it is an SMF 0 null channel (0xFF), in which case * it is 0. * * "%-3d%d-%d %d/%d" (old) * * \note * Later, we could add the sequence hot-key to this string, though * showing that is not much use in perfnames. Also, this function is a * stilted mix of direct access and access through sequence number. * * \param seq * Provides the reference to the sequence, use for getting the sequence * parameters to be written to the label string. * * \return * Returns the filled in label if the sequence is active. * Otherwise, an empty string is returned. */ std::string performer::sequence_label (seq::cref seq) const { std::string result; int sn = seq.seq_number(); if (is_seq_active(sn)) { bussbyte bus = seq.seq_midi_bus(); int bpb = int(seq.get_beats_per_bar()); int bw = int(seq.get_beat_width()); int chanvar = int(seq.midi_channel()); char tmp[32]; if (is_null_channel(chanvar)) { snprintf(tmp, sizeof tmp, "%-3d %d-F %d/%d", sn, bus, bpb, bw); } else { int chan = seq.is_smf_0() ? 0 : int(seq.seq_midi_channel()) + 1; snprintf ( tmp, sizeof tmp, "%-3d %d-%d %d/%d", sn, bus, chan, bpb, bw ); } result = std::string(tmp); } return result; } /** * A pass-through to the other sequence_label() function. * * \param seq * Provides the reference to the sequence, use for getting the sequence * parameters to be written to the label string. * * \return * Returns the filled in label if the sequence is active. * Otherwise, an empty string is returned. */ std::string performer::sequence_label (seq::number seqno) const { const seq::pointer s = get_sequence(seqno); return s ? sequence_label(*s) : std::string("") ; } /** * Creates the sequence title, adjusting it for scaling down. This title is * used in the slots to show the (possibly shortened) pattern title. Note * that the sequence title will also show the sequence length, in measures. * * \param seq * Provides the reference to the sequence, use for getting the sequence * parameters to be written to the label string. * * \return * Returns the filled in label if the sequence is active. * Otherwise, an empty string is returned. */ std::string performer::sequence_title (seq::cref seq) const { std::string result; int sn = seq.seq_number(); if (is_seq_active(sn)) { char temp[16]; const char * fmt = usr().window_scaled_down() ? "%.11s" : "%.14s" ; snprintf(temp, sizeof temp, fmt, seq.title().c_str()); result = std::string(temp); } return result; } /** * Creates a sequence ("seqedit") window title, a longer version of * sequence_title(). * * \param seq * Provides the reference to the sequence, use for getting the sequence * parameters to be written to the string. * * \return * Returns the filled in label if the sequence is active. * Otherwise, an incomplete string is returned. * */ std::string performer::sequence_window_title (seq::cref seq) const { std::string result = seq_app_name(); int sn = seq.seq_number(); if (is_seq_active(sn)) { int ppq = seq.get_ppqn(); char temp[32]; snprintf(temp, sizeof temp, " (%d ppqn)", ppq); result += " #"; result += seq.seq_number_string(); result += " \""; result += sequence_title(seq); result += "\""; result += temp; } else { result += "[inactive]"; } return result; } /** * Creates the main window title. Unlike the disabled code in * mainwnd::update_window_title(), this code does not (yet) handle * conversions to UTF-8. * * \return * Returns the filled-in main window title. */ std::string performer::main_window_title (const std::string & filename) const { std::string result = seq_package_name() + std::string(" "); std::string itemname = rc().no_name(); if (filename.empty()) { std::string fn = rc().midi_filename(); if (! fn.empty()) { std::string path; std::string name; if (filename_split(fn, path, name)) itemname = name; else itemname = shorten_file_spec(fn, c_long_path_max); } } else itemname = filename; result += itemname; return result; } std::string performer::pulses_to_measure_string (midipulse tick) const { midi_timing mt(bpm(), get_beats_per_bar(), get_beat_width(), ppqn()); return seq66::pulses_to_measurestring(tick, mt); } std::string performer::pulses_to_time_string (midipulse tick) const { return seq66::pulses_to_time_string(tick, bpm(), ppqn()); } std::string performer::client_id_string () const { std::string result = seq_client_name(); result += ':'; if (rc().with_jack_midi() && ! rc().jack_session().empty()) result += rc().jack_session(); else { if (m_master_bus) result += std::to_string(m_master_bus->client_id()); else result += "no master bus"; } return result; } /* * ------------------------------------------------------------------------- * Sequence Creation/Installation * ------------------------------------------------------------------------- */ /** * A private helper function for add_sequence() and new_sequence(). It is * common code and using it prevents inconsistencies. It assumes values have * already been checked. It does not set the "is modified" flag, since * adding a sequence by loading a MIDI file should not set it. Compare * new_sequence(), used by mainwnd and seqmenu, with add_sequence(), used by * midifile. This function *does not* delete the sequence already present * with the given sequence number; instead, it keeps incrementing the * sequence number until an open slot is found. * * \param seq * The pointer to the pattern/sequence to add. * * \param seqno * The sequence number of the pattern to be added. Not validated, to * save some time. This is only the starting value; if already filled, * then next open slot is used, and this value will be updated to the * actual number. * * \param fileload * If true (the default is false), the modify flag will not be set. * * \return * Returns true if the sequence was successfully added. */ bool performer::install_sequence (sequence * s, seq::number & seqno, bool fileload) { bool result = set_mapper().install_sequence(s, seqno); if (result) { s->set_parent(this); /* also sets a lot of stuff */ if (rc().is_setsmode_clear()) /* i.e. normal or auto-arm */ { /* * This code is wasteful. It clears the playset and refills it * with the latest set of patterns in the screenset. */ if (is_running()) result = add_to_play_set(s); else result = fill_play_set(); } else if (rc().is_setsmode_allsets()) { /* * This code covers only allsets; the additive mode is in play when * changing the current set. * * result = set_mapper().add_to_play_set(play_set(), s); */ result = add_to_play_set(s); } /* * Check the buss number to make sure it is an available output buss. */ if (! fileload) modify(); } return result; } bool performer::add_to_play_set (sequence * s) { bool result = set_mapper().add_to_play_set(play_set(), s); if (result) record_by_buss(sequence_inbus_setup()); /* not a change */ return result; } bool performer::fill_play_set (bool clearit) { bool result = set_mapper().fill_play_set(play_set(), clearit); if (result) record_by_buss(sequence_inbus_setup()); /* not a change */ return result; } /** * Retrieves the actual sequence, based on the pattern/sequence number. * This is the const version. Note that it is more efficient to call * this function and check the result than to call is_active() and then * call this function. * * This function is not used for the background recorder track. * * \deprecated * We will replace this with loop() eventually. * * \param seq * The prospective sequence number. * * \return * Returns the value of "m_seqs[seq]" if seq is valid. Otherwise, a * null pointer is returned. Now also can return a special pointer * (the metronome or recording pointer) if it exists. */ const seq::pointer performer::get_sequence (seq::number seqno) const { if (sequence::is_normal(seqno)) return loop(seqno); else if (sequence::is_metronome(seqno)) return m_metronome; return loop(seqno); } seq::pointer performer::get_sequence (seq::number seqno) { if (sequence::is_normal(seqno)) return loop(seqno); else if (sequence::is_metronome(seqno)) return m_metronome; return loop(seqno); } /** * Meant to record the last pattern touched by the mouse or a hot-key. * However, if recording is on for the current sequence, we do not set * the new sequence, to avoid mystery to the user. */ bool performer::set_current_sequence (seq::number seqno) { const seq::pointer s = get_sequence(seqno); bool result = not_nullptr(s); if (result) { const seq::pointer sold = get_sequence(m_current_seqno); if (sold && ! sold->recording()) { m_old_seqno = m_current_seqno; m_current_seqno = seqno; } } else m_current_seqno = seq::unassigned(); return result; } /** * We start with a default metronome while we figure out a good way to * configure it. * * The initialize() function fills the metro pattern with metronomic events. * Then is sets the sequence number to the special value metro(). * It also makes the metro pattern active. */ bool performer::install_metronome () { if (bool(m_metronome)) { arm_metronome(); return true; } const metrosettings & ms = rc().metro_settings(); m_metronome.reset(new (std::nothrow) metro(ms)); bool result = bool(m_metronome); if (result) { result = m_metronome->initialize(this); /* add events and arm */ if (result) result = play_set().add(m_metronome); /* not set-mapped */ else m_metronome.reset(); } return result; } bool performer::is_metronome (seq::number seqno) const { bool result = sequence::is_metronome(seqno); if (result) result = bool(m_metronome); return result; } bool performer::reload_metronome () { bool wasrunning = is_running(); if (wasrunning) auto_stop(); /* or pause? */ remove_metronome(); bool result = install_metronome(); if (wasrunning) auto_play(); return result; } void performer::remove_metronome () { if (m_metronome) { seq::number seqno = m_metronome->seq_number(); auto_stop(); /* or pause? */ play_set().remove(seqno); if (m_metronome) m_metronome.reset(); } m_metronome_count_in = false; } /** * This sometimes fails to turn off the metronome. */ void performer::arm_metronome (bool on) { if (m_metronome) { m_metronome->set_armed(on); (void) m_metronome->loop_count_max(0); } } /** * ca 2023-09-13 * Refactoring to immediately create a new pattern before recording so that * the user sees it. The functionality is similar to new_sequence(). */ bool performer::install_recorder () { if (bool(m_recorder)) /* transitory pointer */ return true; /* already in progress */ metrosettings & ms = rc().metro_settings(); m_recorder = new (std::nothrow) recorder(ms); bool result = not_nullptr(m_recorder); if (result) { result = new_sequence(m_recorder, 0); /* earliest slot */ if (result) { result = m_recorder->initialize(this); /* make settings & mute */ if (! result) { remove_recorder(); } } } return result; } bool performer::reload_recorder () { remove_recorder(); return install_recorder(); } void performer::remove_recorder () { if (not_nullptr(m_recorder)) { delete m_recorder; m_recorder = nullptr; // TODO notify all subscribers } } /** * ca 2023-09-13 * We now install the sequence immediately rather than at the end * of recording. All we do to the m_recorder pointer is nullify it, * simply to indicate the sequence is logged and we're not recording * to it anymore. */ bool performer::finish_recorder () { bool result = not_nullptr(m_recorder); if (result) result = m_recorder->event_count() > 0; m_recorder->uninitialize(); m_recorder = nullptr; /* nullify */ // TODO notify all subscribers return result; } /** * When Live playback is requested: * * - Verify that the metronome pattern and count-in status are set. This * means that the metronome pattern is part of the current playset. * The user must have enabled the metronome. * - Copy the current playset into the storage playset. * - Clear the current playset and add the metronome alone. * - Set the loop-count for the metronome to the count-in value. * - Start playback. * - Play until the desired number of bars have happened. This is detected * via the metronome pattern. * - Stop the playback. * - Repopulate the playset with the stored patterns (current set or * all sets) and the metronome pattern. * - Start playback (again). */ bool performer::start_count_in () { bool result = rc().metro_settings().count_in_active(); if (result) result = bool(m_metronome); /* the metronome pattern exists */ if (result) { m_play_set_storage.clear(); result = m_play_set_storage.add(m_metronome); if (result) { (void) m_metronome-> loop_count_max(rc().metro_settings().count_in_measures()); m_dont_reset_ticks = false; m_metronome_count_in = true; } } return result; } bool performer::finish_count_in () { bool result = m_metronome_count_in; if (result) { auto_stop(); /* halt playback */ set_tick(0); arm_metronome(); m_play_set_storage.clear(); /* don't keep it around */ m_metronome_count_in = false; start_playing(); /* resume normal playback */ is_pattern_playing(true); } return result; } /** * Creates a new pattern/sequence for the given slot, and sets the new * pattern's master MIDI bus address. Then it activates the pattern [this is * done in the install_sequence() function]. It doesn't deal with thrown * exceptions. * * This function is called by the seqmenu and mainwnd objects to create a new * sequence. We now pass this sequence to install_sequence() to better * handle potential memory leakage, and to make sure the sequence gets * counted. Also, adding a new sequence from the user-interface is a * significant modification, so the "is modified" flag gets set. * * If enabled, wire in the MIDI buss override. * * \param [out] finalseq * Holds the resulting sequence number. Use it only if this function * returns true. * * \param seqno * The prospective sequence number of the new sequence. If not set to * seq::unassigned() (-1), then the sequence is also installed, and this * value will be updated to the actual number. * * \return * Returns true if the sequence is valid. Do not use the * sequence if false is returned, it will be null. */ bool performer::new_sequence (seq::number & finalseq, seq::number seqno) { sequence * seqptr = new (std::nothrow) sequence(ppqn()); bool result = new_sequence(seqptr, seqno); if (result) finalseq = seqptr->seq_number(); return result; } bool performer::new_sequence (sequence * seqptr, seq::number seqno) { bool result = not_nullptr(seqptr); if (result && seqno != seq::unassigned()) { result = install_sequence(seqptr, seqno); if (result) { const seq::pointer s = get_sequence(seqno); result = not_nullptr(s); if (result) { seq::number finalseq = s->seq_number(); screenset::number setno = set_mapper().seq_set(seqno); s->set_dirty(); record_by_buss(sequence_inbus_setup(true)); announce_sequence(s, finalseq); /* issue #112 */ notify_sequence_change(finalseq, change::recreate); notify_set_change(setno, change::yes); } } } return result; } /** * Copies the sequence to the clipboard and then sets the channel * for all channel events, if not using c_midichannel_null. * * \param seqno * Provides the track number. * * \param channel * Provides the channel to be applied to all events. * * \return * Returns true if the patterns was copied, whether or not any * events were modified. */ bool performer::channelize_sequence (seq::number seqno, int channel) { bool result = channel != c_midichannel_null; if (result) { result = copy_sequence(seqno); /* partial-assign to clipboard */ if (result) (void) m_seq_clipboard.set_channels(channel); } return result; } /** * Simply clears the event from the pattern. That is all. * It does not modify the song. Be aware! */ bool performer::clear_sequence (seq::number seqno) { const seq::pointer s = get_sequence(seqno); bool result = bool(s); if (result) { result = s->clear_events(); /* ultimately calls modify() */ set_start_tick(0); } return result; } /** * Doubles the length of the sequence. */ bool performer::double_sequence (seq::number seqno) { const seq::pointer s = get_sequence(seqno); bool result = bool(s); if (result) result = s->double_length(); /* ultimately calls modify() */ return result; } /** * Deletes a pattern/sequence by number. We now also solidify the deletion * by setting the pointer to null after deletion, so it will blow up if * accidentally accessed. The final act is to raise the "is modified" flag, * since deleting an existing sequence is always a significant modification. * * Now, this function obviously sets the "active" flag for the sequence to * false. But there are a few other flags that are not modified; shouldn't * we also falsify them here? * * What about notify_sequence_change()? Checking in-edit status? * * \param seq * The sequence number of the sequence to be deleted. It is validated. * * \return * Returns true if the sequence was removed. */ bool performer::remove_sequence (seq::number seqno) { bool result = set_mapper().remove_sequence(seqno); if (result) { seq::number buttonno = seqno - playscreen_offset(); send_seq_event(buttonno, midicontrolout::seqaction::removed); record_by_buss(sequence_inbus_setup(true)); notify_sequence_change(seqno, change::recreate); modify(); } return result; } /** * The clipboard is the destination for the trigger-less sequence. * We have to make sure that the source sequence's properties * are copied, but we also need to remove the events and the triggers. * See sequence::partial_assign(). */ bool performer::flatten_sequence (seq::number seqno) { const seq::pointer s = get_sequence(seqno); bool result = bool(s); if (result) { m_seq_clipboard.partial_assign(*s); /* get/reset all */ m_seq_clipboard.clear_events(); m_seq_clipboard.clear_triggers(); result = s->flatten(m_seq_clipboard); if (result) s->partial_assign(m_seq_clipboard); /* paste_sequence() */ } return result; } bool performer::export_sequence (seq::number seqno, const std::string & filename) { const seq::pointer s = get_sequence(seqno); bool result = bool(s); if (result) { midifile f(filename, ppqn()); result = f.write_one_pattern(*this, int(seqno)); } return result; } bool performer::copy_sequence (seq::number seqno) { const seq::pointer s = get_sequence(seqno); bool result = bool(s); if (result) m_seq_clipboard.partial_assign(*s, false); /* do not "modify" */ return result; } bool performer::cut_sequence (seq::number seqno) { bool result = is_seq_active(seqno) && ! is_seq_in_edit(seqno); if (result) { seq::pointer s = get_sequence(seqno); result = bool(s); if (result) { m_seq_clipboard.partial_assign(*s); result = remove_sequence(seqno); /* handles notification */ } } return result; } bool performer::paste_sequence (seq::number seqno) { bool result = ! is_seq_active(seqno); if (result) { static seq::number s_dummy; if (new_sequence(s_dummy, seqno)) /* handles notification */ { seq::pointer s = get_sequence(seqno); s->partial_assign(m_seq_clipboard); s->seq_number(seqno); /* ca 2025-05-14 */ } } return result; } bool performer::merge_sequence (seq::number seqno) { bool result = false; if (! is_seq_active(seqno)) { result = paste_sequence(seqno); /* handles notification */ } else { seq::pointer s = get_sequence(seqno); result = s->merge_events(m_seq_clipboard); if (result) { s->set_dirty(); notify_sequence_change(seqno, change::recreate); } } return result; } /** * Takes the given sequence number, makes sure the sequence is active * and copies it to m_moving_seq via a partial-assign. However, * the current sequence is left in place until finish_move() is called. * * \param seqno * The pattern-slot number of the source pattern. * * \return * Returns true if all actions completed successfully. */ bool performer::move_sequence (seq::number seqno) { bool result = is_seq_active(seqno); if (result) { seq::pointer s = get_sequence(seqno); m_old_seqno = seqno; m_moving_seq.partial_assign(*s, false); /* don't modify right now */ } else { // printf("inactive move(%d), old seq = %d\n", seqno, m_old_seqno); } return result; } /** * Creates a new sequence at the destination pattern-slot number or * the old sequence number. The old sequence number is the number of * the pattern being moved by the move_sequence() function. * * If the destination sequence is active, a new empty pattern is added * at the source slot. Not sure if this is reasonable, but will * not change that now. * * The remove_sequence() function calls the setmapper version, which then * recounts the sequences to get the high-sequence number, etc. * * \param seqno * The pattern-slot number of the destination pattern slot. * * \return * Returns true if all actions completed successfully. */ bool performer::finish_move (seq::number seqno) { if (seqno != m_current_seqno) { static seq::number s_dummy; seq::number newslot = is_seq_active(seqno) ? m_old_seqno : seqno ; bool result = new_sequence(s_dummy, newslot); if (result) { seq::pointer s = get_sequence(seqno); if (s) { s->partial_assign(m_moving_seq); s->seq_number(seqno); result = remove_sequence(m_old_seqno); m_current_seqno = seqno; } else result = false; } return result; } else return false; } /** * Do a fix-pattern operation on a sequence. * * Issue: * * Using notify_trigger_change(), the pattern editor is redrawn * only when focus moves from the qpatternfix to qseqeditframe64. * * With the following notification, no updates at all occur! * * notify_sequence_change(seqno, change::yes); */ bool performer::fix_pattern (seq::number seqno, fixparameters & params) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { result = s->fix_pattern(params); if (result) notify_trigger_change(seqno, change::yes); } return result; } /* * ------------------------------------------------------------------------- * More settings * ------------------------------------------------------------------------- */ /** * Sets the PPQN for the master buss, JACK assistant, and the performer. * Note that we do not set the modify flag or do notification notification * here. See the change_ppqn() function instead. * * Setting the "R" marker to 4 times a measure seems wrong, and makes the R * in the seqedit unseen. But it works for the perfedit. We could loop * through all patterns to find the shortest one, but it's simpler to * just set the "R" to 1 measure. Perhaps multiply it by 4 if the song editor * is opened. * * \setter m_ppqn * Also sets other related members. * * While running it is better to call change_ppqn(), in order to run * though ALL patterns and user-interface objects to fix them. * * \param ppq * Provides a PPQN that should be different from the current value and be * in the legal range of PPQN values. */ bool performer::set_ppqn (int ppq) { /* * ca 2025-09-15. * This needs to be done even if the value has not changed. * Somehow we set m_ppqn to the file's PPQN before we reach here, * so it was unchanged, and we would not update the master bus, * and so playback was the wrong speed. */ bool result = ppqn_in_range(ppq); /* && m_ppqn != ppq */ if (result) { if (m_master_bus) { m_ppqn = ppq; m_one_measure = m_fast_ticks = 0; (void) jack_set_ppqn(ppq); m_master_bus->set_ppqn(ppq); notify_resolution_change /* ca 2023-10-30 */ ( ppqn(), get_beats_per_minute(), change::no ); } else { append_error_message("set_ppqn() null master bus."); result = false; } } if (m_one_measure == 0) { m_right_tick = m_one_measure = ppq * 4; /* simplistic! */ m_fast_ticks = m_one_measure / 2; } return result; } int performer::get_ppqn_from_master_bus () const { int result = ppqn(); if (m_master_bus) { int mbppq = master_bus()->get_ppqn(); if (mbppq != result) { warnprint("master PPQN != performer PPQN"); } result = mbppq; } return result; } /** * Goes through all sets and sequences, updating the PPQN of the events and * triggers. It also, via notify_resolution_change(), sets the modify flag. * * Currently operates only on the current screenset. */ bool performer::change_ppqn (int p) { bool result = set_ppqn(p); /* performer & master bus */ if (result) { set_mapper().exec_set_function ( [p] (seq::pointer sp, seq::number /*sn*/) { bool result = bool(sp); if (result) sp->change_ppqn(p); return result; } ); if (result) { change ch = rc().midi_filename().empty() ? change::no : change:: yes; notify_resolution_change(ppqn(), get_beats_per_minute(), ch); } } return result; } /** * Goes through all the sequences in the current play-set, updating the * output buss to the same (global) buss number. * * \param buss * Provides the number of the buss to be set. Note that this buss number * has already effectively been remapped if port-mapping is in place. */ bool performer::ui_change_set_bus (int buss) { bussbyte b = bussbyte(buss); bool result = is_good_buss(b); if (result) { for (auto seqi : play_set().seq_container()) { if (seqi) { if (seqi->is_normal_seq()) /* not a hidden one */ seqi->set_midi_bus(b, true); /* calls notify() */ } else append_error_message("set bus on null sequence"); } screenset::number setno = playscreen_number(); notify_set_change(setno, change::yes); } return result; } /** * This function provides a way to set the song-mode depending on if the * loaded song has triggers or not. If there are no triggers, then all * tracks are unmuted automatically. This feature is useful for headless * play. * * Note that turning on song_recording() is a user function, and not set by * the presence of triggers. */ void performer::next_song_mode () { (void) set_playing_screenset(screenset::number(0)); if (rc().song_start_auto()) /* detect live vs song */ { bool has_triggers = set_mapper().trigger_count() > 0; song_mode(has_triggers); if (has_triggers || playlist_auto_arm()) /* ca 2023-10-29 */ set_song_mute(mutegroups::action::off); } else { bool mutem = rc().is_setsmode_normal(); bool songmode = rc().song_start_mode(); /* song vs live here */ mute_all_tracks(mutem); song_mode(songmode); } } /** * Locks on m_condition_var [accessed by function cv()]. Then, if not * is_running(), the playback mode is set to the given state. If that state * is true, call off_sequences(). Set the running status, unlock, and signal * the condition. * * Note that we reverse unlocking/signalling from what Seq64 does (BUG!!!) * Manual unlocking should be done before notifying, to avoid waking waking * up the waiting thread, only to lock again. See the notify_one() notes for * details. * * This function should be considered the "second thread", that is the thread * that starts after the worker thread is already working. * * In ALSA mode, restarting the sequence moves the progress bar to the * beginning of the sequence, even if just pausing. This is fixed by * disabling calling off_sequences() when starting playback from the song * editor / performance window. * * \param songmode * Sets the playback mode, and, if true, turns off all of the sequences * before setting the is-running condition. */ void performer::inner_start () { if (! done()) /* won't start when exiting */ { if (! is_running()) { /* * Issue #89. This happens all the time! Thus announce_pattern() * spews events! However, the cause is not here. */ if (song_mode()) off_sequences(); /* mute for song playback */ is_running(true); /* part of cv()'s predicate */ pad().js_jack_stopped = false; cv().signal(); /* signal we are running */ send_onoff_event(midicontrolout::uiaction::play, true); send_onoff_event(midicontrolout::uiaction::panic, false); send_onoff_event(midicontrolout::uiaction::pause, false); send_onoff_event(midicontrolout::uiaction::stop, false); } } } /** * Unconditionally, and without locking, clears the running status and resets * the sequences. Sets m_usemidiclock to the given value. Note that we do * need to set the running flag to false here, even when JACK is running. * Otherwise, JACK starts ping-ponging back and forth between positions under * some circumstances. * * However, if JACK is running, we do not want to reset the sequences... this * causes the progress bar for each sequence to move to near the end of the * sequence. * * \param midiclock * If true, indicates that the MIDI clock should be used. The default * value is false. */ void performer::inner_stop (bool midiclock) { is_running(false); reset_sequences(); /* resets, and flushes the buss */ m_usemidiclock = midiclock; send_onoff_event(midicontrolout::uiaction::stop, true); send_onoff_event(midicontrolout::uiaction::panic, true); send_onoff_event(midicontrolout::uiaction::pause, false); send_onoff_event(midicontrolout::uiaction::play, false); } int performer::increment_slot_shift () // const { if (++m_slot_shift > 2) clear_slot_shift(); if (slot_shift() > 0) send_onoff_event(midicontrolout::uiaction::slot_shift, true); return slot_shift(); } void performer::clear_slot_shift () { m_slot_shift = 0; send_onoff_event(midicontrolout::uiaction::slot_shift, false); } /** * Copies the given string into the desired set's name. * * \param sset * The ID number of the screen-set, an index into the setmapper. * * \param name * Provides the string data to copy into the name. * * \param is_load_modification * If true (the default is false), we do not want to set the modify flag, * otherwise the user is prompted to save even if no changes have * occurred. */ void performer::screenset_name ( screenset::number sn, const std::string & name, bool is_load_modification ) { bool changed = set_mapper().name(sn, name); if (changed) { change mod = is_load_modification ? change::no : change::yes ; notify_set_change(sn, mod); } } /** * New for the Qt 5 version, to stop endless needless redraws upon * ticking of the redraw timer. Also see (for Qt 5) the qseqbase :: * needs_update() function. Most useful in seqedit or qseqedit. * * \param seq * The sequence to check. If set to seq::all(), the default, check * them all, exiting when the first dirty one is found. * * \return * Returns true if the performer is running or if a sequence is found * to be dirty, and in need of refreshing in the user interface. */ bool performer::needs_update (seq::number seqno) const { bool result = false; if (m_is_busy) { warn_message("performer busy!"); } else { result = is_running(); if (! result) { if (m_needs_update) { m_needs_update = false; /* mutable */ result = true; } else { if (seqno == seq::all()) result = set_mapper().needs_update(); /* check all */ else result = is_dirty_main(seqno); /* check one */ } } } return result; } /** * Sets the value of the BPM into the master MIDI buss, after making * sure it is squelched to be between 20 and 500. Replaces * performer::set_bpm() from seq24. * * The value is set only if neither JACK nor this performer object are * running. * * It's not clear that we need to set the "is modified" flag just because we * changed the beats per minute. This setting does get saved to the MIDI * file, with the c_bpmtag. * * Do we need to adjust the BPM of all of the sequences, including the * potential tempo track??? It is "merely" the putative main tempo of the * MIDI tune. Actually, this value can now be recorded as a Set Tempo event * by user action in the main window (and, later, by incoming MIDI Set Tempo * events). * * \param bp * Provides the beats/minute value to be set. Checked for validity. It * is a wide range of speeds, well beyond what normal music needs. * * \param user_change * True if the user (as opposed to the tune) is making a tempo change. * * \return * Returns true if the tempo was changed. */ bool performer::set_beats_per_minute (midibpm bp, bool user_change) { bool result = usr().bpm_is_valid(bp); if (result) result = bp != get_beats_per_minute(); if (result) { /* * Not just JACK though. */ bp = fix_tempo(bp); result = jack_set_beats_per_minute(bp, user_change); } return result; } /** * This is a faster version, meant for jack_assistant to call. This logic * matches the original seq24, but is it really correct? Well, we fixed it * so that, whether JACK transport is in force or not, we can modify the BPM * and have it stick. No test for JACK Master or for JACK and normal running * status needed. * * Note that the JACK server, especially when transport is stopped, * sends some artifacts (really low BPM), so we avoid dealing with low * values. * * Also, and this is weird, if we put the call to get_ppqn() in the * parameter list of notify_resolution_change(), the master-bus pointer is * bad and we get a segfault. And this, only in debug code! Valgrind does * not help in tracking this down. * * \param bp * Provides the beats/minute value to be set. Checked for validity. It * is a wide range of speeds, well beyond what normal music needs. * * \param user_change * True if the user (as opposed to the tune) is making a tempo change. * * \return * Returns true if the tempo was changed. */ bool performer::jack_set_beats_per_minute (midibpm bp, bool user_change) { bool result = bp != m_bpm && usr().bpm_is_valid(bp); if (result) { #if defined SEQ66_JACK_SUPPORT m_jack_asst.set_beats_per_minute(bp); /* see banner note */ #endif int ppq = ppqn(); /* was get_ppqn() */ if (m_master_bus) m_master_bus->set_beats_per_minute(bp); m_bpm = bp; m_us_per_quarter_note = tempo_us_from_bpm(bp); /* seqfault cause? */ /* * ca 2023-04-06 During playlist, changing the BPM by loading the * next song triggers a bogus modify() call. */ change ch = rc().midi_filename().empty() ? change::no : change::yes ; if (rc().playlist_active() || ! user_change) ch = change::no; notify_resolution_change(ppq, bp, ch); /* get_ppqn() */ } return result; } /** * Encapsulates some calls used in mainwnd. Actually does a lot of * work in those function calls. * * \return * Returns the resultant BPM, as a convenience. */ midibpm performer::decrement_beats_per_minute () { midibpm result = get_beats_per_minute() - usr().bpm_step_increment(); set_beats_per_minute(result, true); return result; } /** * Encapsulates some calls used in mainwnd. Actually does a lot of * work in those function calls. * * \return * Returns the resultant BPM, as a convenience. */ midibpm performer::increment_beats_per_minute () { midibpm result = get_beats_per_minute() + usr().bpm_step_increment(); set_beats_per_minute(result, true); return result; } /** * Provides additional coarse control over the BPM value, which comes into * force when the Page-Up/Page-Down keys are pressed. * * Encapsulates some calls used in mainwnd. Actually does a lot of * work in those function calls. * * \return * Returns the resultant BPM, as a convenience. */ midibpm performer::page_decrement_beats_per_minute () { midibpm result = get_beats_per_minute() - usr().bpm_page_increment(); set_beats_per_minute(result, true); return result; } /** * Provides additional coarse control over the BPM value, which comes into * force when the Page-Up/Page-Down keys are pressed. * * Encapsulates some calls used in mainwnd. Actually does a lot of * work in those function calls. * * \return * Returns the resultant BPM, as a convenience. */ midibpm performer::page_increment_beats_per_minute () { midibpm result = get_beats_per_minute() + usr().bpm_page_increment(); set_beats_per_minute(result, true); return result; } /** * Should we pass the current value of BPM to the set_beats_per_minute() * function? */ midibpm performer::update_tap_bpm () { midibpm bpm = 0.0; long ms = millitime(); if (m_current_beats == 0) { m_base_time_ms = ms; m_last_time_ms = 0; } else if (m_current_beats >= 1) { int diffms = ms - m_base_time_ms; bpm = diffms > 0 ? (m_current_beats * 60000.0 / diffms) : m_bpm ;; m_last_time_ms = ms; } ++m_current_beats; return bpm; } bool performer::tap_bpm_timeout () { bool result = false; if (m_current_beats > 0 && m_last_time_ms > 0) { long ms = millitime(); long difference = ms - m_last_time_ms; if (difference > usr().tap_button_timeout()) { clear_current_beats(); result = true; } } return result; } /** * Used by callers to insert tempo events. Note that, if the current tick * position is past the end of pattern 0's length, then the length of the * tempo track pattern (0 by default) is increased in order to hold the tempo * event. * * Note that we allow the user to change the tempo track from the default of * 0 to any pattern from 0 to 1023. This is done in the File / Options / * MIDI Clock tab, and is saved to the "rc" file. * * \return * Returns true if the tempo-track sequence exists. */ bool performer::log_current_tempo () { seq::pointer s = get_sequence(rc().tempo_track_number()); bool result = bool(s); if (result) { midipulse tick = get_tick(); midibpm bpm = get_beats_per_minute(); seq66::event e = create_tempo_event(tick, bpm); /* event.cpp */ if (s->add_event(e)) /* sorts too */ { s->set_dirty(); if (tick > s->get_length()) s->set_length(tick); modify(); /* notify_sequence_change(seqno) too problematic */ } } return result; } /** * Also calls set_mapper().set_playscreen(), and notifies any * performer::callbacks subscribers. Note that the setsmode values of normal * and autoarm indicate to clear the play-set before adding the next set to * it. * * Process: * * - setmapper::set_playing_screenset(setno) * - setmapper::set_playscreen(setno). Sets the playscreen as the * specified set, creating it if it doesn't already exist. Sets * a member pointer to it. * - setmapper::fill_play_set(playset &, bool clearit) * - screenset::fill_play_set(playset &, bool clearit) * - playset::fill(ditto). Optionally clears the playset, * then adds the set's patterns to the playset. * * If setsmode is autoarm, unmute the patterns. */ screenset::number performer::set_playing_screenset (screenset::number setno) { bool ok = ! done(); if (ok) ok = set_mapper().set_playing_screenset(setno); if (ok) { bool clearit = rc().is_setsmode_clear(); /* remove all patterns? */ announce_exit(false); /* blank the device */ unset_queued_replace(); /* clear queueing */ ok = fill_play_set(clearit); if (ok) { if (rc().is_setsmode_normal()) { set_song_mute(mutegroups::action::on); /* mute them all */ } else if (rc().is_setsmode_autoarm()) { set_song_mute(mutegroups::action::off); /* arm them all */ } else if (rc().is_setsmode_allsets()) { /* * Nothing to do? */ } announce_playscreen(); /* inform ctrl-out */ notify_set_change(setno, change::signal); /* change::no */ return playscreen_number(); } } return screenset::unassigned(); } /** * Clears the whole play-set and refills it with the current playscreen. * If auto-arm is in force, will unmute them. Does not signal a set-change, * because the playing set hasn't changed. */ void performer::reset_playset () { announce_exit(false); /* blank the device */ unset_queued_replace(); /* clear queueing */ (void) fill_play_set(); /* true: clear it first */ if (rc().is_setsmode_autoarm()) set_song_mute(mutegroups::action::off); /* unmute them all */ announce_playscreen(); /* inform control-out */ } bool performer::copy_playscreen () { screenset::number pscreen = playscreen_number(); return set_mapper().save_screenset(pscreen); } bool performer::paste_to_playscreen () { screenset::number pscreen = playscreen_number(); bool result = set_mapper().paste_screenset(pscreen); if (result) notify_set_change(pscreen, change::yes); return result; } /** * Removes the given screenset, then notifies all subscribers. * * \return * Returns true if the set was found (and removed). */ bool performer::remove_set (screenset::number setno) { bool result = set_mapper().remove_set(setno); if (result) notify_set_change(setno, change::removed); return result; } /** * Clears the given screenset, then notifies all subscribers. * * \return * Returns true if the set was found (and cleared). */ bool performer::clear_set (screenset::number setno) { bool result = set_mapper().clear_set(setno); if (result) notify_set_change(setno, change::removed); return result; } /** * Swaps the sets, useful in moving sets around in the set-master (class * qsetmaster). */ bool performer::swap_sets (seq::number set0, seq::number set1) { bool result = set_mapper().swap_sets(set0, set1); if (result) { notify_set_change(set0); notify_set_change(set1); } return result; } /** * Clears all of the patterns/sequences. Attempts to reset the performer to * its startup condition when no MIDI file is loaded * * The mainwnd module calls this function, and the midifile and wrkfile * classes, if a play-list is being loaded and verified. Note that perform * now handles the "is modified" flag on behalf of all external objects, to * centralize and simplify the dirtying of a MIDI tune. * * Anything else to clear? What about all the other sequence flags? We can * beef up remove_sequence() for them, at some point. * * Added stazed code from 1.0.5 to abort clearing if any of the sequences are * in editing. * * \warning * We have an issue. Each GUIs conditional_update() function can call * this one, potentially telling later GUIs that they do not need to * update. This affect the needs_update() function when not running * playback. * * \param clearplaylist * Defaults to false. If true, the playlist is cleared completely, which * also clears the original playlist file. TODO: if true, get the * playlist tab to clear itself. * * \return * Returns true if the clear-all operation could be performed. If false, * then at least one active sequence was in editing mode. */ bool performer::clear_all (bool /* clearplaylist */ ) { bool result = clear_song(); usr().clear_global_seq_features(); m_song_info.clear(); if (result) { play_set().clear(); /* dump active patterns */ sequence_inbus_clear(); #if defined WE_REALLY_NEED_TO_RESET_PLAYLIST m_is_busy = true; (void) m_play_list->reset_list(clearplaylist); m_is_busy = false; #endif set_needs_update(); /* tell all GUIs to refresh. BUG! */ announce_exit(); announce_playscreen(); announce_mutes(); announce_automation(); } return result; } bool performer::clear_song () { bool result = ! set_mapper().any_in_edit() && ! m_is_busy; if (result) { m_is_busy = true; /* { */ reset_sequences(); rc().clear_midi_filename(); set_have_undo(false); m_undo_vect.clear(); set_have_redo(false); m_redo_vect.clear(); set_mapper().reset(); /* clears and recreates empty set */ m_is_busy = false; /* } */ unmodify(); /* new, we start afresh */ set_tick(0); /* force a "rewind" */ pad().set_current_tick(0); /* another necessary rewind */ m_max_extent = 0; /* force an "empty" song */ set_needs_update(); /* tell all GUIs to refresh. BUG! */ } return result; } /** * For all active patterns/sequences, get its playing state, turn off the * playing notes, set playing to false, zero the markers, and, if not in * playback mode, restore the playing state. Note that these calls are * folded into one member function of the sequence class. Finally, flush the * master MIDI buss. * * Could use a member function pointer to avoid having to code two loops. * We did it. Note that std::shared_ptr does not support operator::->*, so * we have to get() the pointer. * * Another option is to call set_mapper().reset_sequences(pause, playback_mode()). * This would result in a call to screenset::reset_sequences(), which does * the same thing but also checks the sequence for being active. Is it worth * it? * * \param p * Try to prevent notes from lingering on pause if true. By default, it * is false. */ void performer::reset_sequences (bool p) { void (sequence::* f) (bool) = p ? &sequence::pause : &sequence::stop ; bool songmode = song_mode(); for (auto & seqi : play_set().seq_container()) (seqi.get()->*f)(songmode); /* * Alread flushed in the loop above. * * if (m_master_bus) * m_master_bus->flush(); */ } /** * What about the GM channel? */ void performer::repitch (event & ev) const { if (notemap_exists() && ev.is_note()) { midibyte incoming = ev.d0(); midibyte outgoing = m_note_mapper->fast_convert(incoming); if (rc().investigate()) printf("Note %d in --> %d out\n", incoming, outgoing); ev.d0(outgoing); } } bool performer::repitch_all (const std::string & nmapfile, seq::ref s) { bool result = open_note_mapper(nmapfile); if (result) result = s.repitch(*m_note_mapper, true); if (result) modify(); return result; } /** * The caller sets it all up, so error-checking is reduced. * This function is independent of the note-map active 'rc' setting. */ bool performer::repitch_fix ( const std::string & nmapfile, seq::ref s, bool reverse ) { bool result = file_readable(nmapfile); if (result) { notemapper::direction d = reverse ? notemapper::direction::reverse : notemapper::direction::forward ; notemapper nmap(d); notemapfile nmf(nmap, nmapfile, rc()); result = nmf.parse(); if (result) result = s.repitch(nmap, true); if (result) modify(); } return result; } bool performer::repitch_selected (const std::string & nmapfile, seq::ref s) { bool result = open_note_mapper(nmapfile); if (result) result = s.repitch(*m_note_mapper); if (result) modify(); return result; } /** * Provides for various settings of the song-mute status of all sequences in * the song. The sequence::set_song_mute() and toggle_song_mute() functions * do all the work, including mp-dirtying the sequence. * * We've modified this function to call mute_all_tracks() and * toggle_all_tracks() in order to consolidate the code and (cough cough) fix * a bug in this functionality from the mainwnd menu. * * \question * Do we want to replace the call to toggle_all_tracks() with a call to * toggle_playing_tracks()? * * \param op * Provides the "flag" that indicates if this function is to set mute on, * off, or to toggle the mute status. */ void performer::set_song_mute (mutegroups::action op) { switch (op) { case mutegroups::action::on: mute_all_tracks(true); break; case mutegroups::action::off: mute_all_tracks(false); break; case mutegroups::action::toggle: toggle_all_tracks(); break; case mutegroups::action::toggle_active: default: break; } } /** * Creates the mastermidibus. We need to delay creation until launch time, * so that settings can be obtained before determining just how to set up the * application. * * Once the master buss is created, we then copy the clocks and input setting * that were read from the "rc" file, via the mastermidibus :: * set_port_statuses() function, to use in determining whether to initialize * and connect the input ports at start-up. Seq24 wouldn't connect * unconditionally, and Sequencer64 shouldn't, either. * * However, the devices actually on the system at start time might be * different from what was saved in the "rc" file after the last run of * Sequencer64. * * For output, both apps have always connected to all ports automatically. * But we want to support disabling some output ports, both in the "rc" * file and via the operating system indicating that it cannot open an * output port. So how do we get the port-settings from the OS? Probably * at initialization time. See the mastermidibus constructor for PortMidi. * * \return * Returns true if the creation succeeded, or if the buss already exists. */ bool performer::create_master_bus () { bool result = false; if (! m_master_bus) /* no master buss yet? */ { /* * Cannot use std::make_unique because its copy * constructor is deleted. * * Also, at this point, do we have the actual complement of inputs and * clocks, as opposed to what's in the rc file? Not if an rtmidi * error is thrown. We catch that now (2023-04-15). */ try { m_master_bus.reset(new (std::nothrow) mastermidibus(m_ppqn, m_bpm)); if (m_master_bus) { mastermidibus * mmb = m_master_bus.get(); mmb->record_by_buss(m_record_by_buss); mmb->record_by_channel(m_record_by_channel); mmb->set_port_statuses(m_clocks, m_inputs); if (! alsa_midi_through_check()) { midi_control_in().is_enabled(false); midi_control_out().is_enabled(false); append_error_message ( "MIDI Through ports used for both control &display. " "Disabled. Please change the Clock and Input ports." ); } else midi_control_out().set_master_bus(mmb); result = true; } } catch (...) { append_error_message ( "Creating master bus failed; check MIDI drivers or " "reboot." ); } } return result; } /** * Calls the MIDI buss and JACK initialization functions and the input/output * thread-launching functions. This function is called in main(). We * collected all the calls here as a simplification, and renamed it because * it is more than just initialization. This function must be called after * the perform constructor and after the configuration file and command-line * configuration overrides. The original implementation, where the master * buss was an object, was too inflexible to handle a JACK implementation. * * \param ppqn * Provides the PPQN value, which is determined by the caller and assumed * to be valid. * * \todo * We probably need a bpm parameter for consistency at some point. */ bool performer::launch (int ppqn) { #if defined SEQ66_PLATFORM_WINDOWS bool allow_unavailable_devices = true; #else bool allow_unavailable_devices = false; #endif bool result = create_master_bus(); /* calls set_port_statuses() */ if (result) { if (init_jack_transport()) debug_message("jack transport active"); m_master_bus->init(ppqn, m_bpm); /* calls api_init() per API */ debug_message("bus API init'd"); result = activate(); if (result) { debug_message("master bus active"); } else { append_error_message ( "Master bus activation error; " "fix or (re)create port-maps or " "verify MIDI engine (e.g. JACK) is running." ); } /* * Get and store the clocks and inputs created (disabled or not) * by the mastermidibus during api_init(). After this call, the * clocks and inputs now have names. These calls are necessary to * populate the port lists the first time Seq66 is run. * We need to do this even if activation has failed, such as * when the Windows MIDI Mapper prevents the opening of the * built-in GS wave-table synthesizer. * * m_master_bus->get_port_statuses(m_clocks, m_inputs); the * statuses from e.g. midi_jack_info are already obtained in the * call stack of create_master_bus(). */ m_master_bus->copy_io_busses(); m_master_bus->get_port_statuses(m_clocks, m_inputs); if (result || allow_unavailable_devices) { debug_message("master bus set up"); #if defined SEQ66_USE_DEFAULT_PORT_MAPPING if (! rc().portmaps_present()) /* don't mung existing port-map */ { bool ok = store_io_maps(); /* auto-rc-save if not 1st run */ if (ok) { rc().portmaps_active(true); session_message("Created initial port maps"); /* * Not necessary? signal_for_restart(); */ } else append_error_message("Creating port maps failed"); } #endif /* * Moved from get_settings() so that aliases, if present, are * obtained by this point. */ if (midi_control_in().is_enabled()) { bussbyte namedbus = m_midi_control_in.nominal_buss(); bussbyte truebus = true_input_bus(namedbus); m_midi_control_in.true_buss(truebus); } if (midi_control_out().is_enabled()) { bussbyte namedbus = m_midi_control_out.nominal_buss(); bussbyte truebus = true_output_bus(namedbus); m_midi_control_out.true_buss(truebus); } m_io_active = true; /* set done() */ launch_input_thread(); launch_output_thread(); midi_control_out().send_macro(midimacros::startup); announce_playscreen(); announce_mutes(); announce_automation(); (void) set_playing_screenset(screenset::number(0)); if (any_ports_unavailable()) { static bool s_already_added = false; if (! s_already_added) { std::string msg = "Remap if needed. " "OK preserves the map. " "Suppress this message in Preferences / Display." ; m_port_map_error = true; /* mutable boolean */ append_error_message(msg); s_already_added = true; } } } if (! result) m_error_pending = true; } return result; } /** * Iterate through the current set of patterns (in the playset only!) to find * those that might specify an input buss. Only one pattern can grab ahold of * an input buss. All current busses are present in this vector, but some * might have a null pointer. * * This function should be called whenever the buss setup changes, a pattern * pattern is added or removed, or when its input buss is set. Might also * need to be updated when the playset changes. * * \param changed * If true (the default is false), then the setup is due to the user * selecting the input bus. Otherwise (such as when reading a MIDI * file), do not raise the modified flag. * * \return * Returns true if record-by-buss was true and if any patterns with an * input buss set were found. As a side-effect, performer :: * record_by_buss() is set to the result. */ bool performer::sequence_inbus_setup (bool changed) { bool result = false; if (rc().sequence_lookup_support()) { /* * We have to assume that there may be gaps in the busses, and * sometimes we open a file on a new system that does not have as many * busses, and the result is a mystery to the user. We can handle * this situation more robustly later. * * size_t buscount = master_bus()->get_num_in_buses(); */ m_buss_patterns.clear(); for (auto seqi : play_set().seq_container()) { if (seqi->has_in_bus()) { bussbyte b = seqi->true_in_bus(); if (! is_null_buss(b)) /* b < buscount */ { change mod = changed ? change::recreate : change::no ; int seqno = int(seqi->seq_number()); m_buss_patterns.push_back(seqi.get()); result = true; record_by_buss(result); notify_sequence_change(seqno, mod); #if defined SEQ66_PLATFORM_DEBUG_TMI char temp[64]; snprintf ( temp, sizeof temp, "Added pattern %d, input bus %d", int(seqi->seq_number()), int(b) ); status_message(temp); #endif } } } record_by_buss(result); } return result; } /** * Clears the in-buss setup. Does not affect the 'rc' setting. */ void performer::sequence_inbus_clear () { m_buss_patterns.clear(); record_by_buss(false); } /** * Looks for the first matching input-buss in the list of patterns that * have an input bus set. */ sequence * performer::sequence_inbus_lookup (const event & ev) { sequence * result = nullptr; #if defined USE_OLD_CODE size_t b = size_t(ev.input_bus()); if (b < m_buss_patterns.size()) result = m_buss_patterns[b]; #else bussbyte b = ev.input_bus(); for (auto seqi : m_buss_patterns) { if (b == seqi->true_in_bus()) { result = seqi; break; } } #endif return result; } /** * Announces the current mute states of the now-current play-screen. This * function is handled by creating a slothandler that calls the * announce_sequence() function. The proper working of this function depends * on announce_sequence() returning true for all slots, even empty ones. */ void performer::announce_playscreen () { if (midi_control_out().is_enabled()) { screenset::slothandler sh = std::bind ( &performer::announce_sequence, this, std::placeholders::_1, std::placeholders::_2 ); exec_slot_function(sh, false); /* do not use set-offset */ m_master_bus->flush(); } } /** * This action is similar to announce_playscreen(), but it unconditionally * turns off (removes) all of the sequences in the MIDI status device (e.g. * the Launchpad Mini). * * It also optionally turns off all of the automation buttons and * mute-group buttons as well. * * \param playstatesoff * If true, also blank the automation and mute-group buttons. * Defaults to true. */ void performer::announce_exit (bool playstatesoff) { if (midi_control_out().is_enabled()) { midi_control_out().clear_sequences(); if (playstatesoff) { announce_automation(false); midi_control_out().clear_mutes(); } } } /** * Announces the initial and ending statues of the automation output display. * * \param activate * If true (the default), then we try to set the status of each control to * "off", which means active, but not yet used. If false, all are set to * the "del" state, which normally display as an unilluminated button. * Defaults to true, to be used at start-up. */ void performer::announce_automation (bool activate) { midi_control_out().send_automation(activate); } /** * This function sets the buttons of all mutes_groups that have mute settings * to red, and the rest to off. */ void performer::announce_mutes () { for (int g = 0; g < mutegroups::Size(); ++g) { bool hasany = mutes().any(mutegroup::number(g)); if (hasany) send_mutes_event(g, false); /* should turn red */ else send_mutes_inactive(g); /* should turn off */ } } /** * Provides a screenset::slothandler function to announce the current status * of a sequence to an external device via the midicontrolout container. * This function has to have both the sequence and its number as parameters, * and must return a boolean value. * * Also note that exec_slot_function() must be called with the use_set_offset * parameter set to false, in order to keep the slot number below the * set-size, otherwise a crash occurs. * * Issue #89: * * Had not added code to send the "queue" status! Fixed. * * \param s * Provides the pointer to the sequence. * * \param sn * Provides the slot number to be used for display, and should range from * 0 to the set-size. * * \return * Returns true all the time, because we want to be able to handle empty * slots as well, and screenset::slot_function() is meant to use a false * result only under abnormal conditions. */ bool performer::announce_sequence (seq::pointer s, seq::number sn) { bool ok = not_nullptr(s); midicontrolout::seqaction what; if (ok) { if (! s->is_normal_seq()) return true; /* pretend success */ if (s->armed()) { what = s->get_queued() ? midicontrolout::seqaction::queued : /* unq'ing pending */ midicontrolout::seqaction::armed ; } else { if (s->get_queued() || s->one_shot()) what = midicontrolout::seqaction::queued; else what = midicontrolout::seqaction::muted; } } else what = midicontrolout::seqaction::removed; send_seq_event(sn, what); return true; } bool performer::announce_pattern (seq::number seqno) { seq::pointer s = get_sequence(seqno); bool result = bool(s); if (result) result = announce_sequence(s, set_mapper().seq_to_offset(*s)); return result; } /** * Sets the beats per measure and measures for all existing patterns. * Compare this to set_beats_per_bar(), which merely sets a performer * member. * * Note that a lambda function is used to make the changes. */ bool performer::set_beats_per_measure (int bpm, bool user_change) { bool result = bpm != m_beats_per_bar; /* the current performer value */ if (result) { int bw = m_beat_width; /* available for lambda capture */ set_beats_per_bar(bpm); /* also sets in jack_assistant */ set_mapper().exec_set_function ( [bpm, user_change, bw] (seq::pointer sp, seq::number /*sn*/) { bool result = bool(sp); if (result) { result = sp->update_time_signature ( bpm, bw, user_change ); } return result; } ); if (! user_change) /* likely at startup time */ unmodify(); } return result; } /** * \setter m_beat_width * * \param bw * Provides the value for beat-width. Also used to set the * beat-width in the JACK assistant object. */ bool performer::set_beat_width (int bw, bool user_change) { bool result = bw != m_beat_width; if (result) { int bpb = m_beats_per_bar; /* available for lambda capture */ set_beat_length(bw); /* also sets in jack_assistant */ set_mapper().exec_set_function ( [bw, user_change, bpb] (seq::pointer sp, seq::number /*sn*/) { bool result = bool(sp); if (result) { result = sp->update_time_signature ( bpb, bw, user_change ); } return result; } ); if (! user_change) /* likely at startup time */ unmodify(); } return result; } /** * Creates the output thread using output_thread_func(). This might be a * good candidate for a small thread class derived from a small base class. * * - We may want more control over lifetime of object, for example to * initialize it "lazily". The std::thread already supports it. It can be * made in "not representing a thread" state, assigned a real thread * later when needed, and it has to be explicitly joined or detacheded * before destruction. * - We may want a member to be transferred to/from some other ownership. * No need of pointer for that since std::thread already supports move * and swap. * - We may want opaque pointer for to hide implementation details and * reduce dependencies, but std::thread is standard library class so we * can't make it opaque. * - We may want to have shared ownership. That is fragile scenario with * std::thread. Multiple owners who can assign, swap, detach, or join it * (and there are no much other operations with thread) can cause * complications. * - There is mandatory cleanup before destruction like if * (xthread.joinable()) xthread.join(); or xthread.detach();. That is * also more robust and easier to read in destructor of owning class * instead of code that instruments same thing into deleter of a smart * pointer. * * So unless there is some uncommon reason we should use thread as data * member directly. */ void performer::launch_output_thread () { if (rc().verbose()) { unsigned num_cpus = std::thread::hardware_concurrency(); infoprintf("%u CPUs detected", num_cpus); } if (! m_out_thread_launched) { m_out_thread = std::thread(&performer::output_func, this); m_out_thread_launched = true; debug_message("Output thread launched"); if (rc().priority()) /* Not in MinGW RCB */ { int p = rc().thread_priority(); bool ok = set_thread_priority(m_out_thread, p); if (ok) { warn_message("Output priority", std::to_string(p)); } else { warn_message ( "Output: couldn't set priority; need root priviledges." ); /* * We don't need to exit. Let the app limp along. * * pthread_exit(0); * m_out_thread_launched = false; */ } } } } /** * Creates the input thread using input_thread_func(). This might be a good * candidate for a small thread class derived from a small base class. */ void performer::launch_input_thread () { if (! m_in_thread_launched) { m_in_thread = std::thread(&performer::input_func, this); m_in_thread_launched = true; debug_message("Input thread launched"); if (rc().priority()) /* Not in MinGW RCB */ { int p = rc().thread_priority(); bool ok = set_thread_priority(m_in_thread, p); if (ok) { warn_message("Input priority", std::to_string(p)); } else { warn_message ( "Input: couldn't set priority; need root priviledges." ); /* * We don't need to exit. Let the app limp along. * * pthread_exit(0); * m_in_thread_launched = false; */ } } } } /** * The rough opposite of launch(); it doesn't stop the threads. A minor * simplification for the main() routine, hides the JACK support macro. * We might need to add code to stop any ongoing outputing. * * Also gets the settings made/changed while the application was running from * the mastermidibase class to here. This action is the converse of calling * the set_port_statuses() function defined in the mastermidibase module. * * Note that we call stop_playing(). This will stop JACK transport. If we * restart Seq66 without doing this, transport keeps running (as can be seen * in QJackCtl). So playback starts while loading a MIDI file while Seq66 * starts. Not only is this kind of surprising, it can lead to a seqfault at * random times. * * Also note that m_is_running and m_io_active are both used in the * performer::synch::predicate() override. */ bool performer::finish () { bool result = true; if (! done()) /* m_io_active is true */ { stop_playing(); /* see notes in banner */ reset_sequences(); /* stop all output upon exit */ announce_exit(true); /* blank device completely */ midi_control_out().send_macro(midimacros::shutdown); m_io_active = false; /* set done() for predicate */ m_is_running = false; /* set is_running() off */ cv().signal(); /* signal the end of play */ if (m_out_thread_launched && m_out_thread.joinable()) { m_out_thread.join(); m_out_thread_launched = false; } if (m_in_thread_launched && m_in_thread.joinable()) { m_in_thread.join(); m_in_thread_launched = false; } result = deinit_jack_transport(); /* * Will be done externally (by smanager::close_session) in * put_settings()! That assumes that m_clocks and m_inputs are * kept up-to-date with user changes. * * result = bool(m_master_bus); * if (result) * m_master_bus->get_port_statuses(m_clocks, m_inputs); */ } return result; } /** * Performs a controlled activation of the jack_assistant and other JACK * modules. Currently does work only for JACK; the activate() calls for other * APIs just return true without doing anything. However... * * ca 2021-07-14 Move this. Why doing it even if no JACK transport specified? */ bool performer::activate () { bool result = m_master_bus && m_master_bus->activate(); #if defined SEQ66_JACK_SUPPORT_ACTIVATE_HERE // init_jack_transport() instead if (result) result = m_jack_asst.activate(); #endif return result; } /* * ------------------------------------------------------------------------- * Tick Support * ------------------------------------------------------------------------- */ void performer::set_tick (midipulse tick, bool dontreset) { if (tick >= 0) { m_tick = tick; if (dontreset) { m_dont_reset_ticks = true; set_start_tick(tick); set_needs_update(); } } } /** * Moves the current tick by the value of ticks (negative or positive). * If 0, move to the beginning. */ void performer::move_tick (midipulse ticks, bool dontreset) { midipulse curtick = m_tick; if (ticks != 0) { curtick += ticks; if (curtick < 0) curtick = 0; else if (curtick > m_max_extent) curtick = m_max_extent; /* but can change!! */ } else curtick = get_left_tick(); /* get_star_tick()? */ set_tick(curtick, dontreset); if (is_jack_running()) /* stazed Seq32 */ position_jack(true, curtick); else set_reposition(); /* ditto! */ } /** * Set the left marker at the given tick. We let the caller determine if * this setting is a modification. If the left tick is later than the right * tick, the right tick is move to one measure past the left tick. * * \todo * The performer::m_one_measure member is currently hardwired to * m_ppqn*4. * * \param tick * The tick (MIDI pulse) at which to place the left tick. If the left * tick is greater than or equal to the right tick, then the right ticked * is moved forward by one "measure's length" (m_ppqn * 4) past the left * tick. */ void performer::set_left_tick (midipulse tick) { m_left_tick = tick; set_start_tick(tick); m_reposition = false; if (is_jack_master()) /* don't use in slave mode */ { position_jack(true, tick); set_tick(tick); } else if (! is_jack_running()) set_tick(tick); if (m_left_tick >= m_right_tick) m_right_tick = m_left_tick + m_one_measure; } /** * Set the right marker at the given tick. This setting is made only if the * tick parameter is at or beyond the first measure. We let the caller * determine if this setting is a modification. * * \param tick * The tick (MIDI pulse) at which to place the right tick. If less than * or equal to the left tick setting, then the left tick is backed up by * one "measure's worth" (m_ppqn * 4) worth of ticks from the new right * tick. */ void performer::set_right_tick (midipulse tick) { if (tick == 0) tick = m_one_measure; if (tick >= m_one_measure) { m_right_tick = tick; if (m_right_tick <= m_left_tick) { m_left_tick = m_right_tick - m_one_measure; set_start_tick(m_left_tick); m_reposition = false; if (is_jack_master()) position_jack(true, m_left_tick); else set_tick(m_left_tick); } } } void performer::set_left_tick_snap (midipulse tick, midipulse snap) { midipulse remainder = tick % snap; if (remainder > (snap / 2)) tick += snap - remainder; /* move up to next snap */ else tick -= remainder; /* move down to next snap */ if (m_right_tick <= tick) set_right_tick_snap(tick + 4 * snap, snap); m_left_tick = tick; set_start_tick(tick); m_reposition = false; if (is_jack_master()) /* don't use in slave mode */ position_jack(true, tick); else if (! is_jack_running()) set_tick(tick); } void performer::set_right_tick_snap (midipulse tick, midipulse snap) { midipulse remainder = tick % snap; if (remainder > (snap / 2)) tick += snap - remainder; /* move up to next snap */ else tick -= remainder; /* move down to next snap */ if (tick > m_left_tick) { m_right_tick = tick; set_start_tick(m_left_tick); m_reposition = false; if (is_jack_master()) position_jack(true, m_left_tick); else set_tick(m_left_tick); } } void performer::set_last_tick_seq (sequence & s, midipulse tick, midipulse snap) { set_left_tick_snap(tick, snap); s.set_last_tick(get_left_tick()); } /* * Old code: * * bool result = set_mapper().color(seqno, c); if (result) modify(); */ bool performer::set_color (seq::number seqno, int c) { seq::pointer s = get_sequence(seqno); bool result = bool(s); if (result) result = s->set_color(c, true); /* a user change */ return result; } bool performer::set_midi_bus (seq::number seqno, int buss) { seq::pointer s = get_sequence(seqno); bool result = bool(s); if (result) { result = s->set_midi_bus(buss, true); /* a user change */ if (result) notify_sequence_change(seqno, change::yes); } return result; } bool performer::set_midi_in_bus (seq::number seqno, int buss) { seq::pointer s = get_sequence(seqno); bool result = bool(s); if (result) { result = s->set_midi_in_bus(buss, true); /* a user change */ if (result) { record_by_buss(sequence_inbus_setup(true)); /* ditto */ notify_sequence_change(seqno, change::yes); } } return result; } /** * The only legal values for channel are 0 through 15, and null_channel(), * which is 0x80, and indicates a "Free" channel (i.e. the pattern is * channel-free. * * The live-grid popup-menu calls this function, while the pattern-editor * dropdown calls sequence::set_midi_channel() directly. Both calls set the * user-change flag. * * \param seqno * Provides the sequence number for the channel setting. * * \paramm channel * Provides the channel setting, 0 to 15. If greater than that, it is * coerced to the null-channel (0x80). */ bool performer::set_midi_channel (seq::number seqno, int channel) { seq::pointer s = get_sequence(seqno); bool result = bool(s); if (result) { if (channel >= c_midichannel_max) /* 0 to 15, Free */ channel = null_channel(); /* Free */ result = s->set_midi_channel(midibyte(channel), true); /* user ch. */ if (result) notify_sequence_change(seqno, change::yes); } return result; } /** * Also modify()'s. */ bool performer::set_sequence_name (seq::ref s, const std::string & name) { bool result = name != s.name(); if (result) { seq::number seqno = s.seq_number(); s.set_name(name); notify_sequence_change(seqno, performer::change::recreate); set_needs_update(); /* tell GUIs to refresh. FIXME */ } return result; } /* * ------------------------------------------------------------------------- * Recording * ------------------------------------------------------------------------- */ /** * Handles setting the status of basic recording. * Encapsulates code used by the sequence editing frames' recording-change * callbacks. * * \param recordon * Provides the current status of the Record button. * * \param toggle * If set to toggler::flip, toggle the record status. Otherwise, turn it * on or off. * * \param s * The sequence that the seqedit window represents. This pointer is * checked. */ bool performer::set_recording (seq::ref s, toggler t) { bool result = s.set_recording(t); if (result) set_needs_update(); return result; } /** * Handles a particular alteration in juxtaposition with the recording * flag. */ bool performer::set_recording (seq::ref s, alteration q, toggler t) { bool result = s.set_recording(q, t); if (result) set_needs_update(); return result; } bool performer::set_recording_flip () { bool result = m_current_seqno != seq::unassigned(); if (result) { seq::pointer sp = get_sequence(m_current_seqno); result = bool(sp); if (result) result = set_recording_flip(*sp); } return result; } /** * A version to make setting recording, record loop-mode (style), and * alterations more uniform and based on the selections in the live grid. * * See the code in qslivegrid::record_sequence () */ bool performer::set_recording_flip (seq::ref s) { alteration alt = alteration::none; toggler t = toggler::flip; bool altered_recording = usr().alter_recording(); if (altered_recording) alt = usr().record_alteration(); recordstyle rs = usr().pattern_record_style(); bool result = s.set_recording_style(rs); if (result) result = set_recording(s, alt, t); if (result) set_needs_update(); return result; } /** * Toggles recording for all patterns in the play-set that specify an input * buss. */ bool performer::set_recording_buss_flip () { bool result = false; for (auto seqi : play_set().seq_container()) { if (seqi->has_in_bus()) { result = set_recording(*seqi, toggler::flip); if (! result) break; } } return result; } bool performer::set_recording_chan_flip () { bool result = false; for (auto seqi : play_set().seq_container()) { if (! seqi->free_channel()) { result = set_recording(*seqi, toggler::flip); if (! result) break; } } return result; } bool performer::set_recording_ex (bool /*record*/) { bool result = false; if (record_by_buss()) result = set_recording_buss_flip(); else if (record_by_channel()) result = set_recording_chan_flip(); else result = set_recording_flip(); return result; } /** * Encapsulates code used internally by performer's automation mechanism. This * is a private function. * * \param seqno * The sequence number; the resulting pointer is checked. * * \param recordon * Provides the current status of the Record button. * * \param toggle * If true, ignore the first flag and let the sequence toggle its * setting. Passed along to sequence::input_recording(). */ bool performer::set_recording (seq::number seqno, toggler flag) { sequence * s = get_sequence(seqno).get(); bool result = not_nullptr(s); if (result) result = set_recording(*s, flag); /* s->set_recording(flag) */ return result; } /** * Encapsulates code used by seqedit::thru_change_callback(). * * \param thruon * Provides the current status of the Thru button. * * \param toggle * Indicates to toggle the status. * * \param s * The sequence that the seqedit window represents. This pointer is * checked. */ bool performer::set_thru (seq::ref s, bool thruon, bool toggle) { return s.set_thru(thruon, toggle); } /** * Encapsulates code used by seqedit::thru_change_callback(). However, this * function depends on the sequence, not the seqedit, for obtaining the * recording status. * * \param thruon * Provides the current status of the Thru button. * * \param seq * The sequence number; the resulting pointer is checked. * * \param toggle * If true, ignore the first flag and let the sequence toggle its * setting. Passed along to sequence::set_input_thru(). */ bool performer::set_thru (seq::number seqno, bool thruon, bool toggle) { sequence * s = get_sequence(seqno).get(); bool result = not_nullptr(s); if (result) result = set_thru(*s, thruon, toggle); return result; } /* * ------------------------------------------------------------------------- * JACK Transport * ------------------------------------------------------------------------- */ /** * Encapsulates behavior needed by perfedit. Note that we moved some of the * code from perfedit::set_jack_mode() [the seq32 version] to this function. * * \param connect * Indicates if JACK is to be connected versus disconnected. * * \return * Returns true if JACK is running currently, and false otherwise. */ bool performer::set_jack_mode (bool connect) { if (! is_running()) { if (connect) (void) init_jack_transport(); else (void) deinit_jack_transport(); } #if defined SEQ66_JACK_SUPPORT m_jack_asst.set_jack_mode(is_jack_running()); /* seqroll keybinding */ #endif /* * For setting the transport tick to display in the correct location. * FIXME: does not work for slave from disconnected; need JACK position. */ if (song_mode()) { set_reposition(false); set_start_tick(get_left_tick()); } else set_start_tick(get_tick()); return is_jack_running(); } /** * * \param tick * The current JACK position in ticks. * * \param stoptick * The current JACK stop-tick. */ void performer::jack_reposition (midipulse tick, midipulse stoptick) { midipulse diff = tick - stoptick; if (diff != 0) { set_reposition(true); set_start_tick(tick); jack_stop_tick(tick); } } /** * Set up the performance and start the thread. This function should be * considered the "worker thread". We rely on C++11's thread handling to set * up the thread properly on Linux and Windows. It runs while m_io_active is * true, which is set in the constructor, stays that way basically for the * duration of the application. We do not use std::unique_lock, * because we want a recursive mutex. * * \warning * Valgrind shows that output_func() is being called before the JACK * client pointer is being initialized!!! * * See the old global output_thread_func() in Sequencer64. This locking is * similar to that of inner_start(), except that signalling (notification) is * not done here. While running, we: * * -# Before the "is-running" loop: If in any view (song, grid, or pattern * editor), we care about starting from the m_start_tick offset. * However, if the pause key is what resumes playback, we do not want * to reset the position. So how to detect that situation, since * m_is_pause is now false? * -# At the top of the "is-running" loop: * -# Get delta time (current - last). * -# Get delta ticks from time. * -# Add to current_ticks. * -# Compute prebuffer ticks. * -# Play from current tick to prebuffer. * -# Delta time to ticks; get delta ticks. seq24 0.9.3 changes * delta_tick's type and adds code -- delta_ticks_frac is in 1000th of * a tick. This code is meant to correct for clock drift. However, * this code breaks the MIDI clock speed. So we use the "Stazed" * version of the code, from seq32. We get delta ticks, delta_ticks_f * is in 1000th of a tick. * * microsleep() call: * * Figure out how much time we need to sleep, and do it. Then we want to * trigger every c_thread_trigger_width_us; it took delta_us microseconds * to play(). Also known as the "sleeping_us". Check the MIDI clock * adjustment: 60000000.0f / m_ppqn * / bpm. * * With the long patterns in a rendition of Kraftwerk's "Europe Endless", * we found that that the Qt thread was getting starved, as evidenced by * a great slow-down in counting in the timer function in qseqroll. And * many other parts of the user interface were slow. This bug did not * appear in version 0.91.3, but we never found out the difference that * caused it. However, a microsleep(1) call mitigated the problem * without causing playback issues. * * What might be happening is the the call on line 3144 is not being * made. But it's not being called in 0.91.3 either! However, we found * we were using milliseconds, not microseconds! Once corrected, we * were getting sleep deltas from 3800, down to around 1000 as the load * level (number of playing patterns) increased. * * Now, why the 2.0 factor in this? * * if (next_clock_delta_us < (c_thread_trigger_width_us * 2.0)) * * Stazed code (when ready): * * If we reposition key-p, FF, rewind, adjust delta_tick for change then * reset to adjusted starting. We have to grab the clock tick if looping * is unchecked while we are running the performance; we have to * initialize the MIDI clock (send EVENT_MIDI_SONG_POS); we have to * restart at the left marker; and reset the tempo list (which Seq64 * doesn't have). */ void performer::output_func () { if (! set_timer_services(true)) /* wrapper for Win-only func. */ { (void) set_timer_services(false); return; } show_cpu(); while (! done()) /* the variable is atomic */ { cv().wait(); /* lock mutex, predicate wait */ if (done()) /* if stopping, kill the thread */ break; pad().initialize(0, looping(), song_mode()); /* * If song-mode Master, then start the left tick marker if the Stazed * "key-p" position was set. If live-mode master, start at 0. This * code is also present at about line #3209, and covers more * complexities. */ if (! m_dont_reset_ticks) /* no repositioning in pause */ { if (song_mode()) { if (is_jack_master() && m_reposition) position_jack(true, get_left_tick()); } else position_jack(false, 0); } /* * See note 1 in the function banner. */ midipulse startpoint; if (m_dont_reset_ticks) startpoint = get_tick(); else if (looping()) startpoint = get_left_tick(); else startpoint = get_start_tick(); pad().set_current_tick(startpoint); set_last_ticks(startpoint); /* * We still need to make sure the BPM and PPQN changes are airtight! * Check jack_set_beats_per_minute() and change_ppqn() */ double bwdenom = 4.0 / get_beat_width(); midibpm bpmfactor = m_master_bus->get_beats_per_minute() * bwdenom; int ppqn = m_master_bus->get_ppqn(); int bpm_times_ppqn = bpmfactor * ppqn; double dct = double_ticks_from_ppqn(ppqn); double pus = pulse_length_us(bpmfactor, ppqn); long current; /* current time */ long elapsed_us, delta_us; /* current - last */ long last = microtime(); /* beginning time */ m_resolution_change = false; /* BPM/PPQN */ while (is_running()) { if (m_resolution_change) /* an atomic boolean */ { bwdenom = 4.0 / get_beat_width(); bpmfactor = m_master_bus->get_beats_per_minute() * bwdenom; ppqn = m_master_bus->get_ppqn(); bpm_times_ppqn = bpmfactor * ppqn; dct = double_ticks_from_ppqn(ppqn); pus = pulse_length_us(bpmfactor, ppqn); m_resolution_change = false; } /** * See note 2 and the microsleep() note in the function banner. * See note 3 in the function banner. */ current = microtime(); delta_us = elapsed_us = current - last; long long delta_tick_num = bpm_times_ppqn * delta_us + pad().js_delta_tick_frac; long delta_tick = long(delta_tick_num / 60000000LL); pad().js_delta_tick_frac = long(delta_tick_num % 60000000LL); if (m_usemidiclock) { delta_tick = m_midiclocktick; /* int to long */ m_midiclocktick = 0; if (m_midiclockpos >= 0) /* was after this if */ { delta_tick = 0; pad().set_current_tick(midipulse(m_midiclockpos)); m_midiclockpos = -1; } } bool jackrunning = jack_output(pad()); if (jackrunning) { // No additional code needed besides the output() call above. } else pad().add_delta_tick(delta_tick); /* add to current ticks */ /* * pad().js_init_clock will be true when we run for the first time, * or as soon as JACK gets a good lock on playback. */ if (pad().js_init_clock) { m_master_bus->init_clock(midipulse(pad().js_clock_tick)); pad().js_init_clock = false; } if (pad().js_dumping) { if (looping()) { /* * This stazed JACK code works better than the original * code, so it is now permanent code. */ static bool jack_position_once = false; midipulse rtick = get_right_tick(); /* can change? */ if (pad().js_current_tick >= rtick) { if (is_jack_master() && ! jack_position_once) { position_jack(true, get_left_tick()); jack_position_once = true; } double leftover_tick = pad().js_current_tick - rtick; if (jack_transport_not_starting()) /* no FF/RW xrun */ { play(rtick - 1); } reset_sequences(); midipulse ltick = get_left_tick(); set_last_ticks(ltick); pad().js_current_tick = double(ltick) + leftover_tick; } else jack_position_once = false; } /* * Don't play during JackTransportStarting to avoid xruns on * FF or RW. */ if (jack_transport_not_starting()) { play(midipulse(pad().js_current_tick)); } /* * The next line enables proper pausing in both old and seq32 * JACK builds. */ set_jack_tick(pad().js_current_tick); m_master_bus->emit_clock(midipulse(pad().js_clock_tick)); } /* * See "microsleep() call" in banner. Code is similar to line * 3096 above. */ last = current; current = microtime(); elapsed_us = current - last; delta_us = c_thread_trigger_width_us - elapsed_us; double next_clock_delta = dct - 1; double next_clock_delta_us = next_clock_delta * pus; if (next_clock_delta_us < (c_thread_trigger_width_us * 2.0)) delta_us = long(next_clock_delta_us); if (delta_us > 0) { (void) microsleep(int(delta_us)); /* timing.hpp */ m_delta_us = 0; } else { #if defined SEQ66_PLATFORM_DEBUG && ! defined SEQ66_PLATFORM_WINDOWS if (seq_app_cli()) { if (delta_us != 0) { print_client_tag(msglevel::warn); fprintf ( stderr, "Play underrun %ld us \r", delta_us ); } } #endif m_delta_us = delta_us; } if (pad().js_jack_stopped) inner_stop(); } /* * Disabling this setting allows all of the progress bars (seqroll, * perfroll, and the slots in the mainwnd) to stay visible where * they paused. However, the progress still restarts when playback * begins again, without some other changes. m_tick is the progress * play tick that determines the progress bar location. */ if (! m_dont_reset_ticks) { midipulse start = song_mode() ? get_left_tick() : 0 ; if (is_jack_master()) position_jack(song_mode(), start); else if (! m_usemidiclock && ! is_jack_running()) set_tick(start); } /* * This means we leave m_tick at stopped location if in slave mode or * if m_usemidiclock == true. */ m_master_bus->flush(); m_master_bus->stop(); } (void) set_timer_services(false); } /** * Trying to prevent seqfaults when stopping playback and starting the next * song, as in play-lists. */ void performer::is_pattern_playing (bool flag) { m_is_pattern_playing = flag; } /** * This function is called by input_thread_func(). It handles certain MIDI * input events. Many of them are now handled by functions for easier reading * and trouble-shooting (of MIDI clock). * * For events less than or equal to SysEx, we call midi_control_event() to * handle the MIDI controls that Sequencer64 supports. (These are * configurable in the "rc" configuration file.) We test for MIDI control * events even if "dumping". Otherwise, we cannot handle any more control * events once recording is turned on. Warning: This can slow down * recording. * * "Dumping" means that we are dumping MIDI input events into a sequence. * It means "recording". * * We currently ignore these events on input. MIGHT NOT BE VALID. STILL * INVESTIGATING. EVENT_MIDI_ACTIVE_SENSE and EVENT_MIDI_RESET are filtered * in midi_jack. Send out the current event, if "dumping". * * ev.get_status() == * * EVENT_MIDI_ACTIVE_SENSE handled elsewhere * EVENT_MIDI_RESET handled elsewhere * EVENT_MIDI_QUARTER_FRAME * EVENT_MIDI_SONG_SELECT * EVENT_MIDI_SONG_F4 * EVENT_MIDI_SONG_F5 * EVENT_MIDI_TUNE_REQUEST * EVENT_MIDI_SYSEX_END * EVENT_MIDI_SYSEX_CONTINUE * EVENT_MIDI_SONG_F9 * EVENT_MIDI_SONG_FD */ void performer::input_func () { if (set_timer_services(true)) /* wrapper for a Windows-only func. */ { while (! done()) { if (! poll_cycle()) break; } set_timer_services(false); } } /** * A helper function for input_func(). */ bool performer::poll_cycle () { bool result = ! done(); if (result && m_master_bus->poll_for_midi() > 0) { do { if (done()) { result = false; break; /* spurious exit events */ } event ev; if (m_master_bus->get_midi_event(&ev)) { #if defined USE_EXPERIMENTAL_CODE /* * EXPERIMENTAL: start playing on first event. This causes * a barrage of notes! */ if (! is_pattern_playing()) /* ! is_running() */ inner_start(); /* start_playing() */ #endif if (ev.below_sysex()) /* below 0xF0 */ { if (m_master_bus->is_dumping_input()) { if (midi_control_event(ev, true)) /* quick check */ { // No code at this time } else { ev.set_timestamp(get_tick()); if (record_by_buss()) { sequence * sp = sequence_inbus_lookup(ev); /* * mastermidibase::m_seq is not ever set here. * * if (is_nullptr(sp)) * sp = m_master_bus->get_sequence(); */ if (not_nullptr(sp)) (void) sp->stream_event(ev); #if defined SEQ66_PLATFORM_DEBUG else warn_message("no buss-recording pattern"); #endif } else if (record_by_channel()) { #if defined SEQ66_PLATFORM_DEBUG if (! m_master_bus->dump_midi_input(ev)) warn_message("no matching channel"); #else (void) m_master_bus->dump_midi_input(ev); #endif } else { sequence * sp = m_master_bus->get_sequence(); if (not_nullptr(sp)) (void) sp->stream_event(ev); #if defined SEQ66_PLATFORM_DEBUG else error_message("no active pattern"); #endif } } } else (void) midi_control_event(ev); } else if (ev.is_midi_start()) { midi_start(); } else if (ev.is_midi_continue()) { midi_continue(); } else if (ev.is_midi_stop()) { midi_stop(); } else if (ev.is_midi_clock()) { midi_clock(); } else if (ev.is_midi_song_pos()) { midi_song_pos(ev); } else if (ev.is_tempo()) /* added for issue #76 */ { /* * Should we do this only if JACK transport is not * enabled? */ if (is_jack_master() || ! is_jack_running()) (void) set_beats_per_minute(ev.tempo()); } else if (ev.is_sysex()) { midi_sysex(ev); } #if defined USE_ACTIVE_SENSE_AND_RESET else if (ev.is_sense_reset()) { return false; /* see note in banner */ } #endif else { /* ignore the event */ } } } while (m_master_bus->is_more_input()); } return result; } /** * http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec/ssp.htm * * Example: If a Song Position value of 8 is received, then a sequencer * (or drum box) should cue playback to the third quarter note of the * song. Since there are 24 MIDI Clocks in a quarter note, the first * quarter occurs on a time of 0 MIDI Clocks, the second quarter note * occurs upon the 24th MIDI Clock, and the third quarter note occurs on * the 48th MIDI Clock). * * 8 MIDI beats * 6 MIDI clocks per MIDI beat = 48 MIDI Clocks. * * The MIDI-control check will limit the controls to start, stop and * record only. The function returns a a bool flag indicating whether the * event was used or not. The flag is used to exclude from recording the * events that are used for control purposes and should not be recorded * (dumping). If the used event is a note on, then the linked note off * will also be excluded. * * http://midi.teragonaudio.com/tech/midispec/seq.htm * * Provides a description of how the following events and Song Position * work. * * EVENT_MIDI_START: * * Starts the MIDI Time Clock. The Master sends this message, which * alerts the slave that, upon receipt of the very next MIDI Clock * message, the slave should start playback. MIDI Start puts the slave * in "play mode", and the receipt of that first MIDI Clock marks the * initial downbeat of the song. MIDI B * * Kepler34 does "stop(); set_playback_mode(false); start();" in its * version of this event. This sets the playback mode to Live mode. This * behavior seems reasonable, though the function names Seq66 uses are * different. Used when starting from the beginning of the song. Obey * the MIDI time clock. * * Issue #76: * * Somehow auto_stop() was placed after auto_play(). Brain fart? * * Unreported Issue: * * MIDI clock works, but when stopped, continue reverts to 0. Now fixed. * * To test: * * -# Run seq66 with virtual ports and with ALSA. * -# Use "aplaymidi -l" to determine the client:port to use. For * example, use the virtual port "128:0". * -# Also use a song that plays on that port. * -# Build contrib/code ametro: "./make_ametro". * -# Run "./ametro -M -o 128:0". * -# Use these keystrokes in ametro to control the MIDI clock: * - s = START * - c = CONTINUE * - x = STOP * - . = CLOCK * - Esc = quit */ void performer::midi_start () { start_playing(); m_midiclockrunning = m_usemidiclock = true; m_midiclocktick = m_midiclockpos = 0; if (rc().verbose()) infoprint("MIDI Start"); } /** * EVENT_MIDI_CONTINUE: * * MIDI continue: start from current position. Some master device that * controls sequence playback sends this message to make a slave device * resume playback from its current "Song Position". The current Song * Position is the point when the song/sequence was previously stopped, * or previously cued with a Song Position Pointer message. * * Sent immediately after EVENT_MIDI_SONG_POS, and is used for starting * from other than beginning of the song, or for starting from previous * location at EVENT_MIDI_STOP. Again, converted to Kepler34 mode of * setting the playback mode to Live mode. */ void performer::midi_continue () { song_start_mode(sequence::playback::live); m_midiclockpos = get_tick(); m_dont_reset_ticks = true; m_midiclockrunning = m_usemidiclock = true; start_playing(); if (rc().verbose()) infoprint("MIDI Continue"); } /** * EVENT_MIDI_STOP: * * A master stops the slave simultaneously by sending a MIDI Stop * message. The master may then continue to send MIDI Clocks at the rate * of its tempo, but the slave should ignore these, and not advance its * song position. * * Do nothing, just let the system pause. Since we're not getting ticks * after the stop, the song won't advance when start is received, we'll * reset the position. Or, when continue is received, we won't reset the * position. We do an inner_stop(); the m_midiclockpos member holds the * stop position in case the next event is "continue". This feature is * not in Kepler34. */ void performer::midi_stop () { all_notes_off(); m_usemidiclock = true; m_midiclockrunning = false; m_midiclockpos = get_tick(); m_dont_reset_ticks = false; auto_stop(); if (rc().verbose()) infoprint("MIDI Stop"); } /** * EVENT_MIDI_CLOCK: * * MIDI beat clock (MIDI timing clock or simply MIDI clock) is a clock * signal broadcast via MIDI to ensure that MIDI-enabled devices stay in * synchronization. It is not MIDI timecode. Unlike MIDI timecode, MIDI * beat clock is tempo-dependent. Clock events are sent at a rate of 24 * ppqn (pulses per quarter note). Those pulses maintain a synchronized * tempo for synthesizers that have BPM-dependent voices and for * arpeggiator synchronization. Location information can be specified * using MIDI Song Position Pointer. Many simple MIDI devices ignore * this message. */ void performer::midi_clock () { #if defined SEQ66_PLATFORM_DEBUG_TMI if (rc().verbose()) { infoprint("MIDI Clock"); if (m_midiclockrunning) m_midiclocktick += m_midiclockincrement; else infoprint("Clock not running"); } else #endif if (m_midiclockrunning) m_midiclocktick += m_midiclockincrement; } /** * EVENT_MIDI_SONG_POS: * * MIDI song position pointer message tells a MIDI device to cue to a * point in the MIDI sequence to be ready to play. This message consists * of three bytes of data. The first byte, the status byte, is 0xF2 to * flag a song position pointer message. Two bytes follow the status * byte. These two bytes are combined in a 14-bit value to show the * position in the song to cue to. The top bit of each of the two bytes * is not used. Thus, the value of the position to cue to is between * 0x0000 and 0x3FFF. The position represents the MIDI beat, where a * sequence always starts on beat zero and each beat is a 16th note. * Thus, the device will cue to a specific 16th note. Also see the * combine_bytes() function. */ void performer::midi_song_pos (const event & ev) { midibyte d0, d1; ev.get_data(d0, d1); m_midiclockpos = combine_bytes(d0, d1); } /** * EVENT_MIDI_SYSEX: * * These messages are system-wide messages. We filter system-wide * messages. If the master MIDI buss is dumping, set the timestamp of * the event and stream it on the sequence. Otherwise, use the event * data to control the sequencer, if it is valid for that action. * * "Dumping" is set when a seqedit window is open and the user has * clicked the "record MIDI" or "thru MIDI" button. In this case, if * seq32 support is in force, dump to it, else stream the event, with * possibly multiple sequences set. Otherwise, handle an incoming MIDI * control event. * * Also available (but macroed out) is Stazed's parse_sysex() function. * It seems specific to certain Yamaha devices, but might prove useful * later. * * Not sure what to do with this code, so we just show the data if * allowed to. We would need to add back the non-buss version of the * various sysex() functions. */ void performer::midi_sysex (const event & ev) { if (rc().show_midi()) ev.print(); /* * if (rc().pass_sysex()) * { * m_master_bus->sysex(&ev); * } */ } /** * Encapsulates a series of calls used in mainwnd. We've reversed the * start() and start_jack() calls so that JACK is started first, to match all * of the other use-cases for playing that we've found in the code. Note * that the complementary function, stop_playing(), is an inline function * defined in the header file. * * The performer::start() function passes its boolean flag to * performer::inner_start(), which sets the playback mode to that flag; if * that flag is false, that turns off "song" mode. So that explains why * mute/unmute is disabled. * * Playback use cases: * * These use cases are meant to apply to either a Seq32 or a regular * build of Sequencer64, eventually. Currently, the regular build does * not have a concept of a "global" perform song-mode flag. * * -# mainwnd. * -# Play. If the perform song-mode is "Song", then use that mode. * Otherwise, use "Live" mode. * -# Stop. This action is modeless here. In ALSA, it will cause * a rewind (but currently seqroll doesn't rewind until Play is * clicked, a minor bug). * -# Pause. Same processing as Play or Stop, depending on current * status. When stopping, the progress bars in seqroll and * perfroll remain at their current point. * -# perfedit. * -# Play. Override the current perform song-mode to use "Song". * -# Stop. Revert the perfedit setting, in case play is restarted * or resumed via mainwnd. * -# Pause. Same processing as Play or Stop, depending on current * status. * -# ALSA versus JACK. One issue here is that, if JACK isn't "running" * at all (i.e. we are in ALSA mode), then we cannot be JACK Master. * * Helgrind shows a read/write race condition in m_start_from_perfedit * bewteen jack_transport_callback() and start_playing() here. Is inline * function access of a boolean atomic? * * song_mode() indicates if the caller wants to start the playback in Song * mode (sometimes erroneously referred to as "JACK mode"). In the seq32 * code at GitHub, this flag was identical to the "global_jack_start_mode" * flag, which is true for Song mode, and false for Live mode. False * disables Song mode, and is the default, which matches seq24. Generally, * we pass true in this parameter if we're starting playback from the * perfedit window. It alters the m_start_from_perfedit member, not the * m_song_start_mode member (which replaces the global flag now). * * Flicker: * * Allow to start at key-p position if set; for cosmetic reasons, to stop * transport line flicker on start, position to the left tick. * * m_jack_asst.position(true, m_left_tick); // position_jack() * * The "! m_reposition" doesn't seem to make sense. */ void performer::start_playing () { if (! song_recording()) m_max_extent = get_max_extent(); if (song_mode()) { /* * Moved to above since it's needed in mixed song/live play-lists. * * if (! song_recording()) * m_max_extent = get_max_extent(); */ if (is_jack_master() && ! m_reposition) /* see "Flicker" above */ position_jack(true, get_left_tick()); } else { if (is_jack_master() && ! m_dont_reset_ticks) position_jack(false, 0); if (resume_note_ons()) { for (auto seqi : play_set().seq_container()) seqi->resume_note_ons(get_tick()); } } if (playlist_auto_arm()) set_song_mute(mutegroups::action::off); start_jack(); start(); notify_automation_change(automation::slot::start); } void performer::play_count_in () { if (start_count_in()) { if (is_jack_master() && ! m_dont_reset_ticks) position_jack(false, 0); } start_jack(); start(); notify_automation_change(automation::slot::start); } /** * Pause playback, so that progress bars stay where they are, and playback * always resumes where it left off, at least in ALSA mode, which doesn't * have to worry about being a "slave". * * Currently almost the same as stop_playing(), but expanded as noted in the * comments so that we ultimately have more granular control over what * happens. We're researching the whole sequence of stopping and starting, * and it can be tricky to make correct changes. * * We still need to make restarting pick up at the same place in ALSA mode; * in JACK mode, JACK transport takes care of that feature. * * User layk noted this call, and it makes sense to not do this here, since * it is unknown at this point what the actual status is. Note that we STILL * need to FOLLOW UP on calls to pause_playing() and stop_playing() in * perfedit, mainwnd, etc. * * is_pattern_playing(false); * * But what about is_running()? * * \param songmode * Indicates that, if resuming play, it should play in Song mode (true) * or Live mode (false). See the comments for the start_playing() * function. */ void performer::pause_playing () { m_dont_reset_ticks = true; is_running(! is_running()); stop_jack(); if (! is_jack_running()) m_usemidiclock = false; reset_sequences(true); /* don't reset "last-tick" */ send_onoff_play_states(midicontrolout::uiaction::pause); } /** * Encapsulates a series of calls used in mainwnd. Stops playback, turns off * the (new) m_dont_reset_ticks flag, and set the "is-pattern-playing" flag * to false. With stop, reset the start-tick to either the left-tick or the * 0th tick (to be determined, currently resets to 0). If looping, act like * pause_playing(), but allow reset to the left tick (as opposed to 0). * * \param rewind * If true (the default is false), then set the performer and JACK positions * to 0. */ void performer::stop_playing (bool rewind) { m_max_extent = 0; if (looping()) { pause_playing(); m_dont_reset_ticks = false; } else { stop_jack(rewind); stop(); m_dont_reset_ticks = false; if (rewind) set_tick(0); /* ca 2022-09-25 */ notify_automation_change(automation::slot::stop); } } void performer::auto_play () { bool isplaying = false; bool onekey = false; /* keys().start() == keys().stop(); */ if (onekey) { if (is_running()) { stop_playing(); } else { if (rc().metro_settings().count_in_active()) play_count_in(); else start_playing(); isplaying = true; } } else if (! is_running()) { if (rc().metro_settings().count_in_active()) { play_count_in(); } else { m_play_list->reengage_auto_play(); start_playing(); } isplaying = true; } is_pattern_playing(isplaying); } void performer::auto_pause () { bool isplaying = false; if (is_running()) { pause_playing(); send_onoff_event(midicontrolout::uiaction::play, false); send_onoff_event(midicontrolout::uiaction::panic, false); send_onoff_event(midicontrolout::uiaction::stop, false); send_onoff_event(midicontrolout::uiaction::pause, true); } else { start_playing(); isplaying = true; send_onoff_event(midicontrolout::uiaction::play, true); send_onoff_event(midicontrolout::uiaction::panic, false); send_onoff_event(midicontrolout::uiaction::stop, false); send_onoff_event(midicontrolout::uiaction::pause, false); } is_pattern_playing(isplaying); } /** * Added an is_running() check for when JACK transport is running at startup, * which sets that flag, but not is_pattern_playing(); the result was that * we could not stop playback with Seq66's Stop button. * * \param rewind * If true (the default is false), then reset the performer and JACK * positions to 0. */ void performer::auto_stop (bool rewind) { if (is_pattern_playing() || is_running()) /* normal & JACK, hmmmm */ { m_play_list->disengage_auto_play(); stop_playing(rewind); is_pattern_playing(false); /* * Is problematic because metronome count-in calls auto_stop(). * * (void) finish_recorder(); */ } send_onoff_event(midicontrolout::uiaction::pause, false); } /** * If the play-list auto-play feature is engaged, then restart playback. * This also implies auto-arming, currently enforced in the user-interface. * But auto-arming is done in start_playing(), */ bool performer::auto_play_start () { bool result = false; if (m_play_list->auto_play_engaged()) { millisleep(c_delay_start); start_playing(); result = true; } return result; } /** * auto_stop() disengages auto-play. Instead we just stop with rewind. * * Here's an issue: we can stopy and go to the next song in the play-list * when in song mode. But non-Seq66 MIDI files do not support song mode. * And we should be able to get a decent max-extent even without triggers, * at least for imported songs. * * \return * Returns true if stopping is needed. */ bool performer::auto_play_stop (midipulse tick) { bool result = false; if (m_max_extent > 0 && tick >= m_max_extent) { if (playlist_active()) { result = m_play_list->auto_advance_engaged(); if (result) { stop_playing(true); if (playlist_active()) (void) clear_song(); /* get ready for the next song */ } } else { if (song_mode()) { stop_playing(true); result = true; } } } return result; } /** * Starts the playing of all the patterns/sequences. This function just runs * down the list of sequences and has them dump their events. It skips * sequences that have no playable MIDI events. * * Note how often the "sp" (sequence) pointer was used. It was worth * offloading all these calls to a new sequence function. Hence the new * sequence::play_queue() function. * * This function is called twice in a row with the same tick value, causing * notes to be played twice. This happens because JACK "ticks" are 10 times * as fast as MIDI ticks, and the conversion can result in the same MIDI tick * value consecutively, especially at lower PPQN. However, it also can play * notes twice when the tick changes by a small amount. Not yet sure what to * do about this. * * \param tick * Provides the tick at which to start playing. This value is also * copied to m_tick. */ void performer::play (midipulse tick) { if (tick != get_tick() || tick == 0) /* avoid replays */ { if (auto_play_stop(tick)) { (void) open_next_song(); auto_play_start(); } else { bool songmode = song_mode(); set_tick(tick); for (auto seqi : play_set().seq_container()) { if (seqi) { seqi->play_queue(tick, songmode, resume_note_ons()); } else append_error_message("play on null sequence"); } m_master_bus->flush(); /* flush MIDI buss */ } } } void performer::play_all_sets (midipulse tick) { if (tick > get_tick() || tick == 0) /* avoid replays */ { set_tick(tick); sequence::playback songmode = song_start_mode(); set_mapper().play_all_sets(tick, songmode, resume_note_ons()); m_master_bus->flush(); /* flush MIDI buss */ } } int performer::count_exportable () const { int result = 0; for (int i = 0; i < sequence_high(); ++i) /* count exportable tracks */ { if (is_exportable(i)) /* unmuted, has triggers */ ++result; } return result; } /** * Seq66 can split an SMF 0 file into multiple tracks, effectively converting * it to SMF 1, via midi_splitter. This function performers the opposite * process, creating an SMF 0 track from all the other tracks, for saving as * an SMF 0 file. * * Prerequisites: * * -# The same prequisites for exporting a song: * -# Events in each track to be part of the export. * -# Each track unmuted. * -# Trigger(s) in the tracks to combine. * -# At least one valid pattern slot available. This will normally not be * an issue. * * Process: * * -# If slot 0 has a pattern, move it to the first open slot. * -# Set up the destination pattern in slot 0 to be channel-free. * -# For all other patterns, no matter the set (or in the playset): * -# Check the export of that pattern for validity. * -# Make sure all channel events have the desired channel. * -# Copy that pattern to the performers's pattern clipboard using * performer::copy_sequence(), which replaces the clipboard's * contents. Alternative: use cut_sequence(). * -# Merge the clipboard pattern into the destination pattern. * -# Finalize the file: * -# Make sure the midifile class gets the SMF value (0) and provides * it to write_midi_file(), for one track. The performer can store * this format. * -# midifile::write_song(perf()) is called by the Song Export menu * item in qsmainwnd. It still calls performer::count_exportable(). * -# write_header(). * * We start with slot 0, and search for the first open slot [as a side-effect * of new_sequence() and install_sequence()] to put the SMF 0 data. * * We no longer use count_exportable() here. Too confusing. Export all * active tracks. */ bool performer::convert_to_smf_0 (bool remove_old) { int numtracks = sequence_count(); /* count_exportable() */ bool result = numtracks > 0; if (result && smf_format() == 0) return true; /* already SMF 0 */ seq::number newslot = seq::unassigned(); result = ! song_mode(); /* must flatten first */ if (result) { result = new_sequence(newslot, 0); if (result) { seq::pointer s = get_sequence(newslot); (void) s->set_name("SMF 0"); result = s->set_midi_channel(null_channel(), true); } } if (result) { for (seq::number track = 0; track < sequence_high(); ++track) { if (track == newslot) continue; if (is_seq_active(track)) /* is_exportable() */ { const seq::pointer s = get_sequence(track); bool ok = bool(s); if (s->free_channel()) { ok = copy_sequence(track); /* to the clipboard */ } else { int channel = int(s->midi_channel()); ok = channelize_sequence(track, channel); /* ditto */ } if (ok) { result = merge_sequence(newslot); if (! result) break; } } } if (result) { /* * Remove the exported sequences, then move the SMF 0 track to * slot 0. We use remove_sequence() though it modifies too many * times. We don't check for failure because removing any * intervening empty slots will fail. */ if (remove_old) { for (seq::number track = 0; track < sequence_high(); ++track) { if (track == newslot) continue; (void) remove_sequence(track); } } if (newslot > 0) { result = move_sequence(newslot); if (result) result = finish_move(0); } if (result) { /* * Find the actual last timestamp and use that as the new * length of the sequence, since the user will forget to * modify that. */ seq::pointer s = get_sequence(newslot); if (s) { (void) s->extend_length(); smf_format(0); notify_sequence_change(newslot, change::recreate); } } } } return result; } /** * For all active patterns/sequences, turn off its playing notes. * Then flush the master MIDI buss. */ void performer::all_notes_off () { set_mapper().all_notes_off(); if (m_master_bus) m_master_bus->flush(); /* flush MIDI buss */ } /** * Similar to all_notes_off(), but also sends Note Off events directly to the * active busses. Adapted from Oli Kester's Kepler34 project. */ bool performer::panic () { bool result = bool(m_master_bus); stop_playing(); inner_stop(); /* force inner stop */ set_mapper().panic(); if (result) { /* * Works, but can cause lights to remain on at exit. Weird. */ int displaybuss = int(midi_control_out().true_buss()); m_master_bus->panic(displaybuss); /* flush the MIDI buss */ } set_tick(0); return result; } /** * Toggles the m_hidden flag and sets m_show_hide_pending. The latter will * be toggled off by the qt5nsmanager, which is the only class that cares * about the pending flag. */ bool performer::visibility (automation::action a) { if (a == automation::action::toggle) m_hidden = ! m_hidden; else if (a == automation::action::on) m_hidden = true; else if (a == automation::action::off) m_hidden = false; m_show_hide_pending = true; return true; } /* * ------------------------------------------------------------------------- * Box Selection * ------------------------------------------------------------------------- */ #if defined USE_SONG_BOX_SELECT /** * A prosaic implementation of calling a function on the set of stored * sequences. Used for redrawing selected sequences in the graphical user * interface. * * \param func * The (bound) function to call for each sequence in the set. It has two * parameters, the sequence number and a pulse value. The sequence * number parameter is a place-holder and it obtained here. The pulse * parameter is bound by the caller to create func(). * * \return * Returns true if at least one set item was found to operate on. */ bool performer::selection_operation (SeqOperation func) { bool result = false; for (auto s : m_selected_seqs) func(s); /* not "*s" */ return result; } /* * ------------------------------------------------------------------------- * Box selection of multiple tracks/triggers. * ------------------------------------------------------------------------- */ /** * Selects the desired trigger for this sequence. If this is the first * selection, then the sequence is inserted into the box container. * * \param dropseq * The sequence to operate on. * * \param droptick * Indicates the trigger to be selected. */ void performer::box_insert (seq::number dropseq, midipulse droptick) { seq::pointer s = get_sequence(dropseq); if (s) { bool can_add_seq = s->selected_trigger_count() == 0; if (s->select_trigger(droptick)) /* able to select? */ { if (can_add_seq) m_selected_seqs.insert(dropseq); } } } /** * Unselects only the desired trigger for this sequence. If there are no * more selected triggers for this sequence, then the sequence is erased from * the box container. * * \param dropseq * The sequence to operate on. * * \param droptick * Indicates the trigger to be unselected. */ void performer::box_delete (seq::number dropseq, midipulse droptick) { seq::pointer s = get_sequence(dropseq); if (s) { s->unselect_trigger(droptick); if (s->trigger_count() == 0) m_selected_seqs.erase(dropseq); } } /** * If the sequence is not in the "box set", add it. Otherwise, we are * "reselecting" the sequence, so remove it from the list of selections. * Used in the performerance window's on-button-press event. * * \param dropseq * The number of the sequence where "the mouse was clicked", in the * performerance roll. */ void performer::box_toggle_sequence (seq::number dropseq, midipulse droptick) { auto s = m_selected_seqs.find(dropseq); if (s != m_selected_seqs.end()) box_delete(*s, droptick); else box_insert(dropseq, droptick); } /** * If the current sequence is not part of the selection, then we need to * unselect all sequences. */ void performer::box_unselect_sequences (seq::number dropseq) { if (m_selected_seqs.find(dropseq) == m_selected_seqs.end()) { unselect_all_triggers(); m_selected_seqs.clear(); } } /** * Moves the box-selected set of triggers to the given tick. * * \param tick * The destination location for the trigger. */ void performer::box_move_triggers (midipulse tick) { for (auto s : m_selected_seqs) { seq::pointer selseq = get_sequence(s); if (selseq) /* needlessly safe */ selseq->move_triggers(tick, true); } } /** * Offset the box-selected set of triggers by the given tick amount. * * \param tick * The destination location for the trigger. */ void performer::box_move_triggers (midipulse offset) { for (auto s : m_selected_seqs) { seq::pointer selseq = get_sequence(s); if (selseq) /* needlessly safe */ selseq->offset_triggers(offset); } } #endif // defined USE_SONG_BOX_SELECT /* * ------------------------------------------------------------------------- * Trigger handling * ------------------------------------------------------------------------- */ midipulse performer::get_max_extent () const { midipulse timelen = get_max_timestamp(); midipulse triglen = get_max_trigger(); midipulse result = set_mapper().max_extent(); if (triglen > result) result = triglen; if (timelen > result) result = timelen; return result; } std::string performer::duration (bool dur) const { midipulse tick = get_max_extent(); return dur ? pulses_to_time_string(tick) : pulses_to_measure_string(tick) ; } /** * Selectes a trigger for the given sequence. * * \param dropseq * The sequence number that was in play for the location of the mouse in * the (for example) perfedit roll. * * \param droptick * The location of the mouse horizonally in the perfroll. * * \return * Returns true if a trigger was there to select, and was selected. */ bool performer::select_trigger (seq::number dropseq, midipulse droptick) { seq::pointer s = get_sequence(dropseq); bool result = bool(s); if (result) result = s->select_trigger(droptick); return result; } /** * Encapsulates getting the trigger limits without putting the burden on the * caller. The more code moved out of the user-interface, the better. * * \param seqno * The number of the sequence of interest. * * \param droptick * The tick location, basically where the mouse was clicked. * * \param [out] tick0 * The output location for the start of the trigger. * * \param [out] tick1 * The output location for the end of the trigger. * * \return * Returns true if the sequence is valid and we can select the trigger. */ bool performer::selected_trigger ( seq::number seqno, midipulse droptick, midipulse & tick0, midipulse & tick1 ) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) result = s->selected_trigger(droptick, tick0, tick1); return result; } bool performer::clear_triggers (seq::number seqno) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { result = s->clear_triggers(); if (result) notify_trigger_change(seqno); } return result; } bool performer::print_triggers (seq::number seqno) const { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { s->print_triggers(); result = true; } return result; } bool performer::get_trigger_state (seq::number seqno, midipulse tick) const { const seq::pointer s = get_sequence(seqno); return not_nullptr(s) ? s->get_trigger_state(tick) : false ; } /** * Adds a trigger on behalf of a sequence. The Seq24 behavior is that * the beginning of the sequence is snapped to the nearest value that is a * multiple of the sequence length. It grows forward or backward by one whole * sequence length. * * With song-record-snap off, we * allow the user to place the trigger anywhere in tick-time, and provide the * whole sequence at that time, which can then be grown in either direction. * * With song-record-snap on, we want the beginning of the trigger to go to * the nearest snap, but offer a snap length of 0 to indicate to snap to the * sequence length. * * \param seqno * Indicates the sequence that needs to have its trigger handled. * * \param tick * The MIDI pulse number at which the trigger should be handled. * * \param snap * Provides the snap value to use for snapping start of the tick. */ bool performer::calculate_snap (midipulse & tick) { bool result = song_record_snap() && record_snap_length() > 0; if (result) { tick = closest_snap(record_snap_length(), tick); } return result; } bool performer::add_trigger (seq::number seqno, midipulse tick, midipulse snap) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { midipulse seqlength = s->get_length(); if (snap == 0 || ! calculate_snap(tick)) /* side-effect on tick */ snap = seqlength; if (song_record_snap()) { if (snap == 0) snap = seqlength; tick -= tick % snap; } push_trigger_undo(seqno); result = s->add_trigger(tick, seqlength); /* offset=0 fixoff=true */ if (result) notify_trigger_change(seqno); } return result; } bool performer::copy_triggers (seq::number seqno) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { push_trigger_undo(seqno); result = s->copy_selected_triggers(); } return result; } /** * Delete the existing specified trigger. * * \param seqno * Indicates the sequence that needs to have its trigger handled. * * \param tick * The MIDI pulse number at which the trigger should be handled. */ bool performer::cut_triggers (seq::number seqno) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { push_trigger_undo(seqno); result = s->cut_selected_triggers(); if (result) notify_trigger_change(seqno); } return result; } bool performer::delete_triggers (seq::number seqno) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { push_trigger_undo(seqno); result = s->delete_selected_triggers(); if (result) notify_trigger_change(seqno); } return result; } bool performer::delete_trigger (seq::number seqno, midipulse tick) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { push_trigger_undo(seqno); result = s->delete_trigger(tick); if (result) notify_trigger_change(seqno); } return result; } /** * ca 2025-05-28. Apply transposition even if 0. Needed for * resetting transposition. */ bool performer::transpose_trigger ( seq::number seqno, midipulse tick, int transposition ) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { push_trigger_undo(seqno); result = s->transpose_trigger(tick, transposition); if (result) notify_trigger_change(seqno); } return result; } /** * Add a new trigger if nothing is selected, otherwise delete the existing * trigger. * * \param seqno * Indicates the sequence that needs to have its trigger handled. * * \param tick * The MIDI pulse number at which the trigger should be handled. */ bool performer::add_or_delete_trigger (seq::number seqno, midipulse tick) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { bool state = s->get_trigger_state(tick); push_trigger_undo(seqno); if (state) { result = s->delete_trigger(tick); } else { midipulse seqlength = s->get_length(); result = s->add_trigger(tick, seqlength); } if (result) notify_trigger_change(seqno); } return result; } /** * Convenience function for perfroll's split-trigger functionality. * * \param seqno * Indicates the sequence that needs to have its trigger split. * * \param tick * The MIDI pulse number at which the trigger should be split. * * \param splittype * The type of split to perform. * * \return * Returns true if a split was able to be made. */ bool performer::split_trigger ( seq::number seqno, midipulse tick, trigger::splitpoint splittype ) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { push_trigger_undo(seqno); result = s->split_trigger(tick, splittype); if (result) notify_trigger_change(seqno); } return result; } /** * This version of grow_trigger() is used for manual growing in the perfroll. */ bool performer::grow_trigger ( seq::number seqno, midipulse tickfrom, midipulse tickto, midipulse len ) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { push_trigger_undo(seqno); result = s->grow_trigger(tickfrom, tickto, len); if (result) notify_trigger_change(seqno); } return result; } const trigger & performer::find_trigger (seq::number seqno, midipulse tick) const { static trigger s_dummy; seq::pointer s = get_sequence(seqno); if (s) return s->find_trigger(tick); return s_dummy; } /** * Convenience function for perfroll's paste-trigger functionality. * * \param seqno * Indicates the sequence that needs to have its trigger pasted. * * \param tick * The MIDI pulse number at which the trigger should be pasted. */ bool performer::paste_trigger (seq::number seqno, midipulse tick) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { push_trigger_undo(seqno); result = s->paste_trigger(tick); if (result) notify_trigger_change(seqno); } return result; } /** * Convenience function for perfroll's paste-or-split-trigger functionality. * * \param seqno * Indicates the sequence that needs to have its trigger handled. * * \param tick * The MIDI pulse number at which the trigger should be handled. */ bool performer::paste_or_split_trigger (seq::number seqno, midipulse tick) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { bool state = s->get_trigger_state(tick); push_trigger_undo(seqno); if (state) result = s->split_trigger(tick, trigger::splitpoint::exact); else result = s->paste_trigger(tick); if (result) notify_trigger_change(seqno); } return result; } bool performer::offset_triggers ( triggers::grow tg, int seqlow, int seqhigh, midipulse offset ) { bool result = false; if (tg == triggers::grow::end) --offset; for (int seqid = seqlow; seqid <= seqhigh; ++seqid) { seq::pointer seq = get_sequence(seqid); if (seq) { result = true; seq->offset_triggers(offset, tg); } } if (result) notify_trigger_change(seqlow); return result; } bool performer::move_triggers ( seq::number seqno, midipulse tick, bool adjust_offset ) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { s->move_triggers(tick, adjust_offset); notify_trigger_change(seqno); return true; } return result; } bool performer::move_triggers (bool direction) { bool result = set_mapper().move_triggers(m_left_tick, m_right_tick, direction); if (result) notify_trigger_change(seq::all()); return result; } bool performer::move_trigger ( seq::number seqno, midipulse starttick, midipulse distance, bool direction, bool single ) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) { s->move_triggers(starttick, distance, direction, single); notify_trigger_change(seqno); return true; } return result; } #if defined USE_INTERSECT_FUNCTIONS /** * Finds the trigger intersection. * * \param seqno * The number of the sequence in question. * * \param tick * The time-location desired. * * \return * Returns true if the sequence exists and the * sequence::intersect_triggers() function returned true. */ bool performer::intersect_triggers (seq::number seqno, midipulse tick) { bool result = false; seq::pointer s = get_sequence(seqno); if (s) result = s->intersect_triggers(tick); return result; } #endif // defined USE_INTERSECT_FUNCTIONS /** * For every active sequence, call that sequence's push_trigger_undo() * function. Too bad we cannot yet keep track of all the undoes for the sake * of properly handling the "is modified" flag. * * This function now has a new parameter. Not added to this function is the * seemingly redundant undo-push the seq32 code does; is this actually a * seq42 thing? * * Also, there is still an issue with our undo-handling for a single track. * See pop_trigger_undo(). * * \param track * A new parameter (found in the stazed seq32 code) that allows this * function to operate on a single track. A parameter value of * seq::all() (-2, the default) implements the original behavior. */ void performer::push_trigger_undo (int track) { m_undo_vect.push_back(track); /* stazed */ if (track == seq::all()) { set_mapper().push_trigger_undo(); } else { seq::pointer s = get_sequence(track); if (s) s->push_trigger_undo(); } set_have_undo(true); /* stazed */ } /** * For every active sequence, call that sequence's pop_trigger_undo() * function. * * \todo * Look at seq32/src/perform.cpp and the perform :: * push_trigger_undo(track) function, which has a track parameter that * has a -1 value that supports all tracks. It requires two new vectors * (one for undo, one for redo), two new flags (likewise). We've put * this code in place, no longer macroed out, now permanent. */ void performer::pop_trigger_undo () { if (! m_undo_vect.empty()) { int track = m_undo_vect.back(); m_undo_vect.pop_back(); m_redo_vect.push_back(track); if (track == seq::all()) { set_mapper().pop_trigger_undo(); } else { seq::pointer s = get_sequence(track); if (s) s->pop_trigger_undo(); } set_have_undo(! m_undo_vect.empty()); set_have_redo(! m_redo_vect.empty()); } } /** * For every active sequence, call that sequence's pop_trigger_redo() * function. */ void performer::pop_trigger_redo () { if (! m_redo_vect.empty()) { int track = m_redo_vect.back(); m_redo_vect.pop_back(); m_undo_vect.push_back(track); if (track == seq::all()) { set_mapper().pop_trigger_redo(); } else { seq::pointer s = get_sequence(track); if (s) s->pop_trigger_redo(); } set_have_undo(! m_undo_vect.empty()); set_have_redo(! m_redo_vect.empty()); } } /* * ------------------------------------------------------------------------- * Other handling * ------------------------------------------------------------------------- */ void performer::show_cpu () { #if defined SEQ66_PLATFORM_UNIX // LINUX if (rc().verbose()) infoprintf("Output function on CPU #%d", sched_getcpu()); #endif } /** * Simple error reporting for debugging. We have to cast the ordinal back to * unsigned, otherwise odd or empty strings are emitted. */ void performer::show_key_error (const keystroke & k, const std::string & tag) { ctrlkey ordinal = k.key(); std::string name = qt_ordinal_keyname(ordinal); std::string pr = k.is_press() ? "Press" : "Release" ; std::string mods = modifier_names(unsigned(k.modifiers())); std::cerr << "Key '" << name << "' Ordinal 0x" << std::hex << unsigned(ordinal) << " Modifier(s) " << mods << ": " << pr << ": "<< tag << std::endl ; } /** * This static function merely prints the parameters passed to it. * This function, enabled by the --verbose flag (and in the 'rc' file), * allows one to see the incoming automation commands. */ void performer::print_parameters ( const std::string & tag, automation::action a, int d0, int d1, int index, bool inverse ) { if (rc().investigate()) { std::ostringstream os; os << tag << " '" << opcontrol::action_name(a) << "'; " << "d0 = " << d0 << "; " << "d1 = " << d1 << "; " << "index = " << index << "; " << "inv = " << inverse ; info_message(os.str()); } } /* * ------------------------------------------------------------------------- * Control * ------------------------------------------------------------------------- */ /** * Set the MIDI control output object */ void performer::set_midi_control_out () { if (m_master_bus) { mastermidibus * temp = m_master_bus.get(); midi_control_out().set_master_bus(temp); } } /** * Sets or unsets the keep-queue functionality, used by the "Q" * button in the main window to set keep-queue. Also see the * automation_keep_queue() function. */ void performer::set_keep_queue (bool activate) { automation::action a = automation_action(activate); (void) set_ctrl_status(a, automation::ctrlstatus::keep_queue); } /** * If the given status is present in the automation::ctrlstatus::snapshot, * the playing state is saved. Then the given status is OR'd into the * control-status. * * If the given status is present in the automation::ctrlstatus::snapshot, * the playing state is restored. Then the given status is reversed in * control-status. * * If the given status includes automation::ctrlstatus::queue, this is a * signal to stop queuing (which is already in place elsewhere). It also * unsets the new queue-replace feature. * * \param a * The action to be applied. On sets the status, and Off unsets * the status. * * \param status * The status item to be applied. */ bool performer::set_ctrl_status ( automation::action a, automation::ctrlstatus cs ) { bool on = a == automation::action::on || a == automation::action::toggle; if (on && midi_control_in().is_set(cs)) on = false; bool snap = midi_control_in().is_snapshot(cs) || midi_control_in().is_replace(cs); if (on) { if (snap) save_snapshot(); midi_control_in().add_status(cs); if (midi_control_in().is_keep_queue(cs)) midi_control_in().add_status(automation::ctrlstatus::queue); } else { bool k = midi_control_in().is_keep_queue(cs); /* keep-queue only */ bool q = midi_control_in().is_queue(cs); /* queue present */ bool s = midi_control_in().is_solo(cs); /* queued replace */ if (k) { midi_control_in().clear_status(); } else if (s) { midi_control_in().clear_status(); } else if (q) { if (! midi_control_in().is_keep_queue()) /* keep q current? */ { midi_control_in().clear_status(); } } else { midi_control_in().clear_status(); } if (snap) restore_snapshot(); } notify_trigger_change(seq::unassigned(), change::no); display_ctrl_status(cs, on); return true; } bool performer::toggle_ctrl_status (automation::ctrlstatus status) { bool on = ! midi_control_in().is_set(status); automation::action a = on ? automation::action::on : automation::action::off ; return set_ctrl_status(a, status); } void performer::display_ctrl_status (automation::ctrlstatus s, bool on) { if (midi_control_in().is_keep_queue(s)) send_onoff_event(midicontrolout::uiaction::queue, on); if (midi_control_in().is_oneshot(s)) send_onoff_event(midicontrolout::uiaction::oneshot, on); if (midi_control_in().is_replace(s)) send_onoff_event(midicontrolout::uiaction::replace, on); if (midi_control_in().is_snapshot(s)) send_onoff_event(midicontrolout::uiaction::snapshot, on); } /** * A helper function to make the code a tad more readable. */ void performer::send_onoff_event (midicontrolout::uiaction a, bool on) { midicontrolout::actionindex ai = on ? midicontrolout::action_on : midicontrolout::action_off ; midi_control_out().send_event(a, ai); } /** * A helper function to make the code a tad more readable. */ void performer::send_mutes_event (int group, bool on) { midicontrolout::actionindex a = on ? midicontrolout::action_on : midicontrolout::action_off ; midi_control_out().send_mutes_event(group, a); } void performer::send_mutes_events (int groupon, int groupoff) { bool wasactive = mutes().group_valid(groupoff); if (wasactive && (groupoff != groupon)) { midi_control_out().send_mutes_event ( groupoff, midicontrolout::action_off ); } midi_control_out().send_mutes_event(groupon, midicontrolout::action_on); } void performer::send_mutes_inactive (int group) { midi_control_out().send_mutes_event(group, midicontrolout::action_del); } /** * Sets the state of the Start, Stop, and Play button(s) as configured in the * "ctrl" file. It first turns off all of the states (which might be mapped to * one button or to three buttons), then turns on the desired state. * * \param a * Provides the desired state to set, which is one of the following values * of uiaction: play, stop, and pause. The corresponding event is sent. * If another value (max is the best one to use), then all are off. */ void performer::send_onoff_play_states (midicontrolout::uiaction a) { if (a < midicontrolout::uiaction::max) send_onoff_event(a, true); else announce_automation(); } /** * Helper function that clears the queued-replace feature. This also clears * the queue mode; we shall see if this disrupts any user's workflow. * * \param clearbits * If true (the default), then clear the queue and replace status bits. * If the user is simply replacing the current replace pattern with * another pattern, we pass false for this parameter. */ void performer::unset_queued_replace (bool clearbits) { if (m_queued_replace_slot != seq::unassigned()) { m_queued_replace_slot = seq::unassigned(); clear_snapshot(); if (clearbits) midi_control_in().remove_queued_replace(); } } /** * Sets the group-mute mode, then the group-learn mode, then notifies all of * the notification subscribers. This function is called via a MIDI control * c_midi_control_mod_glearn and via the group-learn keystroke. * * \param learning * If true, sets group-learn mode, otherwise, unsets it. If false, the * "good" status will refer to the success of memorizing the mute status. */ void performer::group_learn (bool learning) { automation::action a = learning ? automation::action::on : automation::action::off; (void) set_ctrl_status(a, automation::ctrlstatus::learn); mutes().group_learn(learning); midi_control_out().send_learning(learning); for (auto notify : m_notify) (void) notify->on_group_learn(learning); } /** * TODO: We need to: * * - Update the Mutes tab. * - Activate (red) the selected mute button. * * \param k * Inidates the keystroke involved in the transaction, useful for * reporting. * * \param good * If true, either the learning or the mute-setting succeeded. Otherwise, * the operation failed. */ void performer::group_learn_complete (const keystroke & k, bool good) { group_learn(false); for (auto notify : m_notify) (void) notify->on_group_learn_complete(k, good); notify_mutes_change(0, change::yes); } /** * If the given sequence is active, then it is toggled as per the current * value of control-status. If control-status is * automation::ctrlstatus::queue, then the sequence's toggle_queued() * function is called. This is the "mod queue" implementation. * * Otherwise, if it is automation::ctrlstatus::replace, then the status is * unset, and all sequences are turned off. Then the sequence's * toggle-playing() function is called, which should turn it back on. This is * the "mod replace" implementation; it is like a Solo. But can it be undone? * * This function is called in loop_control() to implement a toggling of the * sequence of the pattern slot in the current screen-set that is represented * by the keystroke. * * This function is also called in midi_control_event() if the control number * represents a sequence number in a screen-set, that is, it ranges from 0 to * 31 (by default). This function also supports the queued-replace * (queued-solo) feature. * * One-shots are allowed only if we are not playing this sequence. * * \param seqno * The sequence number of the sequence to be potentially toggled. * This value must be a valid and active sequence number. If in * queued-replace mode, and if this pattern number is different from the * currently-stored number (m_queued_replace_slot), then we clear the * currently stored set of patterns and set new stored patterns. */ bool performer::sequence_playing_toggle (seq::number seqno) { seq::pointer s = get_sequence(seqno); bool result = bool(s); if (result) { bool is_queue = midi_control_in().is_queue(); bool is_replace = midi_control_in().is_replace(); bool is_oneshot = midi_control_in().is_oneshot(); bool is_solo = midi_control_in().is_solo(); /* queued replace */ if (is_oneshot && ! s->armed()) { s->toggle_one_shot(); } else if (is_solo) { if (m_queued_replace_slot != seq::unassigned()) { if (seqno != m_queued_replace_slot) save_queued(seqno); } else save_queued(seqno); /* not current set? */ unqueue_sequences(seqno); m_queued_replace_slot = seqno; } else if (is_queue) { s->toggle_queued(); } else { if (is_replace) { unset_queued_replace(); off_sequences(); } s->toggle_playing(get_tick(), resume_note_ons()); /* kepler34 */ } /* * For issue #89, sequence::toggle_playing() already announces the * sequence change, so don't do it here. * * announce_sequence(s, set_mapper().seq_to_offset(*s)); */ /* * If we're in song playback, temporarily block the events until the * next sequence boundary. And if we're recording, add "Live" sequence * playback changes to the Song/Performance data as triggers. * * \todo * Would be nice to delay song-recording start to the next queue, * if queuing is active for this sequence. */ if (song_mode()) s->song_playback_block(true); if (song_recording()) { midipulse tick = get_tick(); bool trigger_state = s->get_trigger_state(tick); if (trigger_state) /* if sequence already playing */ { /* * If this play is us recording live, end the new trigger * block here. */ if (s->song_recording()) { /* * For issue #44 part deux, snap at end of trigger as * well as at the beginning. Handled by the parent of the * pattern (performer). */ s->song_recording_stop(tick); } else /* ...else need to trim block already in place */ { /* * Hmmm, for issue #44, can we make the splitpoint an * option? */ s->split_trigger(tick, trigger::splitpoint::exact); s->delete_trigger(tick); } } else /* if not playing, start recording a new strip */ { /* * Issue #44 part deux. */ (void) calculate_snap(tick); /* possible side-effect */ s->song_recording_start(tick, song_record_snap()); } } } return result; } /** * Needs some work! Using the grid-mode for solo. * This mode can only be turned off by selecting another grid-mode. */ bool performer::replace_for_solo (seq::number seqno, bool queued) { seq::pointer s = get_sequence(seqno); bool result = bool(s); if (result) { automation::ctrlstatus cs = automation::ctrlstatus::replace; if (queued) cs = add_queue(cs); if (seqno == m_solo_seqno) /* user toggle of slot */ { #if defined SEQ66_PLATFORM_DEBUG msgprintf(msglevel::debug, "Pattern %d solo cleared", seqno); #endif (void) set_ctrl_status /* restores snapshot */ ( automation::action::off, cs ); m_solo_seqno = seq::unassigned(); /* clear_snapshot() */ } else { #if defined SEQ66_PLATFORM_DEBUG msgprintf(msglevel::debug, "Pattern %d soloed", seqno); #endif (void) set_ctrl_status /* saves snapshot */ ( automation::action::on, cs ); if (s->muted()) s->toggle_playing(get_tick(), resume_note_ons()); /* * TODO: how can we wait until queuing is complete? */ off_sequences(seqno); /* off all but seqno */ m_solo_seqno = seqno; } notify_trigger_change(seq::all(), change::no); announce_sequence(s, set_mapper().seq_to_offset(*s)); } return result; } sequence::playback performer::toggle_song_start_mode () { song_start_mode ( live_mode() ? sequence::playback::song : sequence::playback::live ); if (song_mode()) (void) unapply_mutes(mutes().null_mute_group()); set_needs_update(); /* ca 2023-12-19 */ infoprint(live_mode() ? "Live Mode" : "Song Mode"); return m_song_start_mode; } /** * Hmmmm, this stops recording on all patterns, but does not start it. * Re issue #44. * * song_recording_stop() stops recording on all sequences. * * \param on * If true, turn song-recording on, otherwise turn it off. * * \param atstart * If true, recording on all patterns begin as soon as playback starts. */ void performer::song_recording (bool on, bool atstart) { if (on != m_song_recording) { m_song_recording = on; if (on) { if (atstart) { set_mapper().song_recording_start /* issue #44 */ ( pad().js_current_tick, song_record_snap() ); } } else set_mapper().song_recording_stop(pad().js_current_tick); send_onoff_event(midicontrolout::uiaction::song_record, on); } } /** * This code handles the use of the Shift key to toggle the mute state of all * other sequences. See perfnames::on_button_press_event(). If the Shift * key is pressed, toggle the mute state of all other sequences. * Inactive sequences are skipped. Compare it to toggle_other_seqs(). * * \param seqno * The sequence that is being clicked on. It must be active in order to * allow toggling. * * \param isshiftkey * Indicates if the shift-key functionality for toggling all of the other * sequences is active. * * \return * Returns true if the toggling was able to be performed. */ bool performer::toggle_other_names (seq::number seqno, bool isshiftkey) { bool result = is_seq_active(seqno); if (result) { if (isshiftkey) set_mapper().toggle_song_mute(); else { /* * Relating to issue #44, here we want the same functionality as * toggling the grid button. Yields better visual feedback. * Uh, no, it disables showning the muting in perfnames, even if * accompanied by the toggle_song_mute() call. Song mute and pattern * mute are bit conflicted at this time. * * result = sequence_playing_toggle(seqno); */ set_mapper().toggle_song_mute(seqno); } } return result; } /** * Changes the play-state of the given sequence. This does not cause a modify * action. * * \param seqno * The number of the sequence to be turned on or off. * * \param on * The state (true = armed) to which to set the sequence. */ bool performer::sequence_playing_change (seq::number seqno, bool on) { bool qinprogress = midi_control_in().is_queue(); set_mapper().sequence_playscreen_change(seqno, on, qinprogress); return true; } /** * Seq/Event-edit pending flag support. * * Sets the edit-pending flags to false, and disables the pending sequence * number. We moved the toggles here for debugging. Weird. */ void performer::clear_seq_edits () { m_seq_edit_pending = m_event_edit_pending = false; m_pending_loop = seq::unassigned(); } void performer::toggle_seq_edit () { m_seq_edit_pending = ! m_seq_edit_pending; } void performer::toggle_event_edit () { m_event_edit_pending = ! m_event_edit_pending; } void performer::toggle_record_edit () { m_record_toggle_pending = ! m_record_toggle_pending; } /* * End of Seq/Event-edit pending flag support. */ /** * Handle a control key. The caller (e.g. a Qt key-press event handler) * grabs the event text and modifiers and converts it to an ctrlkey value * (ranging from 0x00 to 0xFE). We show the caller code here for reference: * \verbatim ctrlkey kkey = event->key(); unsigned kmods = static_cast(event->modifiers()); ctrlkey ordinal = qt_modkey_ordinal(kkey, kmods); keystroke ks = keystroke(ordinal, press); \endverbatim * * We made a Qt function for this, qt_keystroke(), in the qt5_helpers.cpp/hpp * module. * * Next, we look up the keycontrol based on the ordinal value. If this * keycontrol is usable (it is not a default-constructed keycontrol), then we * can use its slot value to look up the midioperation associated with this * slot. * * Also part of keystroke is whether the key was pressed or released. A * press sets inverse = false, while a release sets inverse = true. For some * keystrokes, this difference matters. For most, we want to ignore the * release. * * Note that the default action for most keys is automation::action::toggle, * but some keys are configured to do automation::action::on during a * key-press, and automation::action::off during a key-release. To * summarize: * * - Pattern keys. The action is always automation::action::toggle * upon a key-press. Key-release is ignored. * - Mute-group keys. The action is always automation::action::toggle * upon a key-press. Key-release is ignored. * - Automation keys. The action is specified by the location in the * list, as defined in keycontainer::sm_keys_automation. * - on. Causes an action, such as BPM Up. Operate only upon * key-press. * - toggle. Operate a toggling operation. Operate only upon * key-press. * - off. Not used with keystrokes, just with MIDI control. * - Modal keys. The action is automation::action::on. But the state * of the key-press is used. If a press, the mode is activated. If * a release, the mode is deactivated. This operating mode is * determined by the automation callback function. * * Example: * * - BPM Up. Uses automation::action::on, and increments BPM at each * key-press, with key-release being ignored. * - Pause. Uses automation::action::toggle. At each key-press, the * playing state is toggled. Key-release is ignored. * - Replace, queue, snapshot, one-shot, and group-learn. Uses * automation::action::on. On key-press, enter the given mode. On * key-release, leave the given mode. * * If the midioperation is usable, then we can call the midioperation * function, passing it the parameters based on the keystroke. * * Notes: * * 1. Note that the "inverse" parameter is based on key press versus release. * Not all automation functions care about this setting. The * opcontrol::allowed(int, bool) function checks for the * non-keystroke-release status. Too tricky. Also, the index is meant * only for pattern and mute-group control. * * 2. If we start group-learn mode, and then press the group-learn key, * we're in learning mode, but should not learn that key. We still need * to report it, though. * * \param key * Provides the ordinal number of the keystroke. See above for how to * get it (in Qt 5). * * \return * Returns true if the action was handled. Returns false if the action * failed or was not handled. The caller has to know what the context * is. */ bool performer::midi_control_keystroke (const keystroke & k) { bool result = true; keystroke kkey = k; if (is_group_learn()) { if (kkey.is_press()) { if (m_key_controls.use_auto_shift()) kkey.shift_lock(); /* employ the auto-shift feature */ } else result = false; /* ignore the control-key release */ } if (result) { const keycontrol & kc = m_key_controls.control(kkey.key()); result = kc.is_usable(); if (result) { automation::slot s = kc.slot_number(); const midioperation & mop = m_operations.operation(s); if (mop.is_usable()) { /* * See Notes 1 (inverse) and 2 (group-learn) in the banner. */ automation::action a = kc.action_code(); bool invert = ! kkey.is_press(); int d0 = (-1); /* key flag */ int d1 = 0; /* no d1 */ int index = kc.control_code(); bool learning = is_group_learn(); /* before */ if (kc.is_glearn_control()) /* ignore? */ { if (invert) { result = false; } else if (learning) { group_learn_complete(kkey, false); /* fail */ result = false; } } /* * ca 2023-03-18. * If the control is usable, but fails, we still want to return * true, so that the grid-base keystroke * doesn't fall through * to the main window, causing toggles to be done twice. * Use "ok" instead of "result". */ if (result) { bool ok = mop.call(a, d0, d1, index, invert); if (! ok) { if (rc().investigate()) { printf ( "Action %d: code %d, d0 %d, d1 %d ignored\n", index, static_cast(a), d0, d1 ); } } } if (result) { if (learning) group_learn_complete(kkey, ! is_group_learn()); } else { /* * Using the "=" or "-" keys deliberately returns false. */ if (! m_seq_edit_pending && ! m_event_edit_pending) show_key_error(kkey, "call returned false"); } } else show_key_error(kkey, "call unusable"); } } return result; } /** * Looks up the MIDI event and calls the corresponding function, if any. * * Note: * * Pattern-edit can turn recording on, potentially disabling the next * pattern-edit, so we check for it here. Anything else to check??? We * actually need to see if there is any control that CANNOT occur while * recording, otherwise loop-control etc. is disabled as well! * * \param ev * The MIDI event to process. * * \param recording * This parameter, if true, restricts the handled controls to start, * stop, and record. * * \return * Returns true if the event was valid and usable, and the call to the * automation function returned true. Also returns true if the event * came in on the control buss, so that it will not be recorded. (A fix * for issue #80.) Returns false if the action was not on the control * bus. */ bool performer::midi_control_event (const event & ev, bool recording) { bool result = m_midi_control_in.is_enabled(); if (result) result = ev.input_bus() == m_midi_control_in.true_buss(); if (result) { midicontrol::key k(ev); const midicontrol & incoming = m_midi_control_in.control(k); bool good = incoming.is_usable(); if (good) { automation::slot s = incoming.slot_number(); const midioperation & mop = m_operations.operation(s); if (mop.is_usable()) { bool process_the_action = incoming.in_range(ev.d1()); if (recording) { /* * See Note above. */ } if (process_the_action) { automation::action a = incoming.action_code(); bool invert = incoming.inverse_active(); int d0 = incoming.d0(); int d1 = incoming.d1(); int index = incoming.control_code(); /* in lieu of d1() */ good = mop.call(a, d0, d1, index, invert); } else good = false; } } /* * This warning can be misleading, as often the release of a control * button emits an event (e.g. Note Off) that the user has not * bothered to define in the 'ctrl' file. * * if (! good) * warnprint("control event not processed"); */ } return result; } void performer::signal_save () { stop_playing(); signal_for_save(); /* provided by the daemonize module */ } void performer::signal_quit () { stop_playing(); signal_for_exit(); /* provided by the daemonize module */ } /** * Adds a member function to an automation slot. */ bool performer::add_automation (automation::slot s, automation_function f) { std::string name = opcontrol::category_name ( automation::category::automation ); midioperation func ( name, automation::category::automation, s, [this, f] ( automation::action a, int d0, int d1, int index, bool inverse ) { return (this->*f) (a, d0, d1, index, inverse); } ); return m_operations.add(func); } /** * Tries to populate the opcontainer with simulated versions of a pattern * control function, a mute-group control function, and functions to handle * each of the automation controls. * * Pattern: * * A single loop-control function, a lambda function that calls * performer::loop_control(). * * Mute-group: * * A single mute-control function, a lambda function that calls * performer::mute_group_control(). * * Automation: * * A number of performer functions stuffed into lambdas. See the * sm_auto_func_list[] array. Works like a champ and a lot fewer lines of * code. See the bottom of this file for the function table. */ bool performer::populate_default_ops () { midioperation patmop ( opcontrol::category_name(automation::category::loop), /* name */ automation::category::loop, /* category */ automation::slot::loop, /* opnumber */ [this] ( automation::action a, int d0, int d1, int index, bool inverse ) { return loop_control(a, d0, d1, index, inverse); } ); bool result = m_operations.add(patmop); if (result) { midioperation mutmop ( opcontrol::category_name(automation::category::mute_group), automation::category::mute_group, automation::slot::mute_group, /* opnumber */ [this] ( automation::action a, int d0, int d1, int index, bool inverse ) { return mute_group_control(a, d0, d1, index, inverse); } ); result = m_operations.add(mutmop); } for (int index = 0; /* breaker */ ; ++index) { if (sm_auto_func_list[index].ap_slot != automation::slot::max) { result = add_automation ( sm_auto_func_list[index].ap_slot, sm_auto_func_list[index].ap_function ); if (! result) { std::string errmsg = "Failed to insert automation function #"; errmsg += std::to_string(index); append_error_message(errmsg); break; } } else break; } return result; } /* * ------------------------------------------------------------------------- * Mutes / Mute-groups * ------------------------------------------------------------------------- */ bool performer::group_name (mutegroup::number gmute, const std::string & n) { bool result = (mutes().group_save_to_midi() && n != mutes().group_name(gmute)); mutes().group_name(gmute, n); /* * Commented out to avoid load issues. The on-change callback should * cause a modify(). * * if (result) * modify(); */ return result; } void performer::group_format_hex (bool flag) { if (flag != mutes().group_format_hex()) modify(); mutes().group_format_hex(flag); } bool performer::group_save (bool bmidi, bool bmutes) { bool result = bmidi != group_save_to_midi(); if (result) { bool changed = mutes().group_save(bmidi, bmutes); if (changed && bmidi) modify(); } return result; } bool performer::strip_empty (bool flag) { bool result = flag != mutes().strip_empty(); mutes().strip_empty(flag); if (result) modify(); return result; } /** * Sets the given mute group. If there is a change, then the subscribers are * notified. If the 'rc' "save-mutes-to" setting indicates saving it to the * MIDI file, then this becomes a modify action. Associated with the "Update * Group" button in the qmutemaster tab. * * \param gmute * Provides the number of the mute-group to be updated. * * \param bits * Provides the bits representing the layout of the mute-group's * armed/unarmed statuses. * * \param putmutes * If true, then the mute-group in the rc() mute-groups object is * updated. * * \return * Returns true if the mutes were able to be set. */ bool performer::set_mutes ( mutegroup::number gmute, const midibooleans & bits, bool putmutes ) { midibooleans original = get_mutes(gmute); bool result = bits != original; if (result) { result = set_mapper().set_mutes(gmute, bits); if (result) { change c = mutes().group_save_to_midi() ? change::yes : change:: no ; notify_mutes_change(mutegroup::unassigned(), c); if (putmutes) mutes().set(gmute, bits); } } return result; } /** * Clears the mute groups. If there any to clear, then the subscribers are * notified. If the "mutes" "save-mutes-to" setting indicates saving it to * the MIDI file, then this becomes a modify action. Compare to the * clear_mute_groups() function. */ bool performer::clear_mutes () { bool result = mutes().any(); if (result) { result = mutes().reset_defaults(); /* clears and adds all 0s */ if (result) { change c = mutes().group_save_to_midi() ? change::yes : change::no ; notify_mutes_change(mutegroup::unassigned(), c); } } return result; } bool performer::clear_mute_groups () { bool result = reset_mute_groups(); /* mutes().reset_defaults() */ if (result) modify(); return result; } bool performer::apply_mutes (mutegroup::number group) { mutegroup::number oldgroup = mutes().group_selected(); bool result = set_mapper().apply_mutes(group); if (result) { send_mutes_events(group, oldgroup); notify_mutes_change(group, change::no); /* ca 2023-11-06 */ } return result; } bool performer::unapply_mutes (mutegroup::number group) { bool result = set_mapper().unapply_mutes(group); if (result) { midi_control_out().send_mutes_event(group, midicontrolout::action_off); notify_mutes_change(group, change::no); /* ca 2023-11-06 */ } return result; } /** * Does a learn-action if in group-learn mode, followed by * mute_group_tracks. */ void performer::select_and_mute_group (mutegroup::number mg) { set_mapper().select_and_mute_group(mg); notify_mutes_change(mg, change::no); /* ca 2023-11-06 */ } bool performer::toggle_mutes (mutegroup::number group) { mutegroup::number oldgroup = mutes().group_selected(); bool result = set_mapper().toggle_mutes(group); if (result) { mutegroup::number newgroup = mutes().group_selected(); send_mutes_events(newgroup, oldgroup); notify_mutes_change(newgroup, change::no); /* ca 2023-11-06 */ } return result; } bool performer::toggle_active_mutes (mutegroup::number group) { mutegroup::number oldgroup = mutes().group_selected(); bool result = set_mapper().toggle_active_mutes(group); if (result) { mutegroup::number newgroup = mutes().group_selected(); send_mutes_events(newgroup, oldgroup); notify_mutes_change(group, change::no); /* ca 2023-11-06 */ } return result; } /** * Provides a solution to "SM: pattern state isn't recalled with session * (#27). It actually applies to normal operation as well. */ bool performer::apply_session_mutes () { bool result = mutes().any() && mutes().group_valid(); if (result) { if (rc().song_start_auto()) /* detect live vs song */ result = set_mapper().trigger_count() == 0; /* triggers = song mode */ else result = ! rc().song_start_mode(); /* don't use in "song" */ if (result) result = apply_mutes(mutes().group_selected()); } return result; } bool performer::learn_mutes (mutegroup::number group) { bool result = set_mapper().learn_mutes(true, group); /* true == learn */ if (result) { change c = mutes().group_save_to_midi() ? change::yes : change:: no ; notify_mutes_change(group, c); } return result; } /* * ------------------------------------------------------------------------- * Automation "slots" * ------------------------------------------------------------------------- */ /* * For the next two functions, compare to performer::set_record_style(). * These function move between recording style of merge, expand, overwrite, * etc. */ void performer::next_record_style () { (void) usr().next_record_style(); recordstyle rs = usr().pattern_record_style(); /* * if (m_record_style == recordstyle::oneshot_reset) * set_start_tick(0); * * notify_automation_change(automation::slot::record_style); */ set_record_style(rs); } void performer::previous_record_style () { (void) usr().previous_record_style(); recordstyle rs = usr().pattern_record_style(); /* * if (m_record_style == recordstyle::oneshot_reset) * set_start_tick(0); * * notify_automation_change(automation::slot::record_style); */ set_record_style(rs); } void performer::next_record_alteration () { (void) usr().next_record_alteration(); m_record_alteration = usr().record_alteration(); notify_automation_change(automation::slot::quan_record); } void performer::previous_record_alteration () { (void) usr().previous_record_alteration(); m_record_alteration = usr().record_alteration(); notify_automation_change(automation::slot::quan_record); } void performer::set_record_alteration (alteration rm) { (void) usr().record_alteration(rm); m_record_alteration = rm; notify_automation_change(automation::slot::quan_record); } /** * Provides the pattern-control function... hot-keys that toggle the patterns * in the current set. * * \param a * Provides the action code: toggle, on, or off. Keystrokes that use this * function will always provide automation::action::toggle. * * \param d0 * Provides the first MIDI data byte's value. For a keystroke, this value * is currently always 0. * * \param loopnumber * Provides the second MIDI data byte's value. For keystrokes, this value * provides the sequence number (an offset in the active set), the group * number (an offset into the mutesgroups), or is ignored, and is set * via the keycontrol constructor. See seq66 :: keycontainer :: * add_defaults() for an example of this setup. * * \param inverse * If true, no action is performed, as this means that it is a likely to be * a key release, which would then undo the key press. * * \return * Returns true if \a loopnumber was valid. */ bool performer::loop_control ( automation::action a, int d0, int d1, int loopnumber, bool inverse ) { std::string name = "Pattern "; name += std::to_string(loopnumber); print_parameters(name, a, d0, d1, loopnumber, inverse); /* * We need to enforce a rule to use the playscreen offset when needed. */ seq::number seqno = set_mapper().play_seq(loopnumber); bool result = seqno >= 0; if (result && ! inverse) { if (slot_shift() > 0) { if (columns() == setmaster::Columns()) { if (rows() > setmaster::Rows()) seqno += slot_shift() * rows(); /* move down x rows */ } else seqno += slot_shift() * screenset_size(); clear_slot_shift(); } m_pending_loop = seqno; if (m_record_toggle_pending) { m_pending_loop = seq::unassigned(); m_record_toggle_pending = false; seq::pointer sp = get_sequence(seqno); if (sp) result = set_recording_flip(*sp); } else if (m_seq_edit_pending || m_event_edit_pending) { result = false; /* let caller do it */ } else { (void) set_current_sequence(seqno); if (usr().no_grid_record()) { gridmode gm = usr().grid_mode(); if (gm == gridmode::loop) { if (a == automation::action::toggle) (void) sequence_playing_toggle(seqno); else if (a == automation::action::on) (void) sequence_playing_change(seqno, true); else if (a == automation::action::off) (void) sequence_playing_change(seqno, false); } else if (gm == gridmode::mutes) { mutegroup::number mg = static_cast(seqno); result = toggle_mutes(mg); } else if (gm == gridmode::copy) result = copy_sequence(seqno); else if (gm == gridmode::paste) result = paste_sequence(seqno); else if (gm == gridmode::clear) result = clear_sequence(seqno); else if (gm == gridmode::remove) result = remove_sequence(seqno); else if (gm == gridmode::thru) result = set_thru(seqno, false, true); /* true=toggle */ else if (gm == gridmode::solo) (void) sequence_playing_change(seqno, true); else if (gm == gridmode::cut) result = cut_sequence(seqno); else if (gm == gridmode::double_length) result = double_sequence(seqno); } else { toggler flag = toggler::off; /* i.e. "false" */ seq::pointer seqp = get_sequence(seqno); bool result = bool(seqp); if (result) { if (a == automation::action::toggle) flag = toggler::flip; else if (a == automation::action::on) flag = toggler::on; result = set_recording ( *seqp, usr().record_alteration(), flag ); } } } if (result) notify_sequence_change(seqno, change::no); } return result; } /** * A boolean setter for the setmapper's mode-group value. If in group-learn * mode, this function will memorize the state of the current (play) screen and * save it in the desired mute group. Otherise, it will grab the desired * mute-group and apply it to the current play-screen (and mute all other * screens). * * \param a * Provides the action code: toggle, on, or off. Keystrokes that use this * function will always provide automation::action::toggle. * * \param d0 * Provides the first MIDI data byte's value. For a keystroke, this value * is currently always 0. * * \param groupnumber * Provides the second MIDI data byte's value. This value provides the * group number (an offset into the mutesgroups). * * \param inverse * If true, then the mute-group key is being released. In this case, * nothing is done. * * \return * Returns true if \a groupnumber was valid. */ bool performer::mute_group_control ( automation::action a, int d0, int d1, int groupnumber, bool inverse ) { std::string name = is_group_learn() ? "Mute Learn " : "Mutes " ; name += std::to_string(d0); print_parameters(name, a, d0, d1, groupnumber, inverse); mutegroup::number gn = static_cast(groupnumber); bool result = gn >= 0; if (result && ! inverse) { if (is_group_learn()) { if (a == automation::action::toggle) { result = learn_mutes(gn); } else if (a == automation::action::on) { result = learn_mutes(gn); } else if (a == automation::action::off) { result = learn_mutes(gn); } /* * This causes a message to popup with the parent in a * different thread, causing a crash. However, if all works, * the MIDI controller will display the new mutegroup. * * keystroke k = m_key_controls.mute_keystroke(gn); * group_learn_complete(k, result); */ std::string statusmsg = result ? "Succeeded" : "Failed" ; std::string msg = "Learning of mute-group key "; msg += m_key_controls.mute_key(gn); session_message(statusmsg, msg); group_learn(false); announce_mutes(); if (result) { modify(); // TODO: can we update qmutemaster? } } else { /* * Treat all mute-group controls the same for now. We might * eventually be able to somehow "toggle" mute groups. */ if (a == automation::action::toggle) { if (mutes().toggle_active_only()) (void) toggle_active_mutes(gn); else (void) toggle_mutes(gn); /* apply_mutes(gn); */ } else if (a == automation::action::on) { select_and_mute_group(gn); } else if (a == automation::action::off) { select_and_mute_group(gn); } } } return true; } screenset::number performer::decrement_screenset (int amount) { screenset::number newnumber = playscreen_number() - amount; return set_playing_screenset(newnumber); } screenset::number performer::increment_screenset (int amount) { screenset::number newnumber = playscreen_number() + amount; return set_playing_screenset(newnumber); } /* * ------------------------------------------------------------------------- * Automation functions * ------------------------------------------------------------------------- */ /** * Implements a no-op function for reserved slots not yet implemented, or it * can can serve as an indication that the caller (e.g. a user-interface) * should handle the functionality itself. * * \return * Because the slot is not implemented, false is returned. */ bool performer::automation_no_op ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = "No-op"; print_parameters(name, a, d0, d1, index, inverse); return false; } /** * Implements BPM Up and BPM Down for MIDI control. There would be no need * for two BPM configuration lines for MIDI control, except that we need two * different keystrokes, one for up, and one for down. * * All keystrokes are handled such that the key-press sets inverse to * "false", and the key-release sets inverse to "true". For most keystrokes, * then, we have to ignore inverse == true. * * For the configured BPM Up keystroke, this function is called with an action * of "on", to implement BPM Up. But a second function, automation_bpm_dn(), * is provided to implement BPM Down for keystrokes. It can also be * configured for MIDI usage, and it will work like Seq24/Sequencer64, which * just checks for the event irregardless of whether it is toggle, on, or off. * * \param a * Provides the action code: toggle, on, or off. Keystrokes that use this * function will always provide automation::action::toggle. * * \param d0 * Unused. * * \param d1 * Unused. * * \param inverse * Unused. * * \return * Always returns true. */ bool performer::automation_bpm_up_dn ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::bpm_up); print_parameters(name, a, d0, d1, index, inverse); if (inverse) { if (opcontrol::allowed(d0, inverse)) /* not a key-release */ { if (a == automation::action::on) decrement_beats_per_minute(); else if (a == automation::action::off) increment_beats_per_minute(); } } else { if (automation::actionable(a)) increment_beats_per_minute(); else if (a == automation::action::off) decrement_beats_per_minute(); } return true; } /** * No matter how BPM Down is configured for MIDI control, if present and the * MIDI event matches, it will act like a BPM Down. This matches the behavior * of Seq24/Sequencer64. Remember that d0 < 0 flags a keystroke, and when * true, we ignore inverse == true (a key-release) via a call to * opcontrol::allowed(d0, inverse). */ bool performer::automation_bpm_dn ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::bpm_dn); print_parameters(name, a, d0, d1, index, inverse); if (opcontrol::allowed(d0, inverse)) { return automation_bpm_up_dn ( automation::action::off, d0, d1, index, inverse ); } else return true; /* pretend the key release worked */ } /** * Implements screenset Up and Down. The default keystrokes are "]" for up * and "[" for down. Note that all keystrokes generate toggles, and the * release sets "inverse" to true. */ bool performer::automation_ss_up_dn ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::ss_up); print_parameters(name, a, d0, d1, index, inverse); if (inverse) { if (opcontrol::allowed(d0, inverse)) /* not a key-release */ { if (a == automation::action::on) decrement_screenset(); else if (a == automation::action::off) increment_screenset(); } } else { if (automation::actionable(a)) increment_screenset(); else if (a == automation::action::off) decrement_screenset(); } return true; } /** * No matter how Screenset Down is configured for MIDI control, if present and * the MIDI event matches, it will act like a Screenset Down. This matches the * behavior of Seq24/Sequencer64. */ bool performer::automation_ss_dn ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::ss_dn); print_parameters(name, a, d0, d1, index, inverse); if (opcontrol::allowed(d0, inverse)) { return automation_ss_up_dn ( automation::action::off, d0, d1, index, inverse ); } else return true; /* pretend the key release worked */ } /** * Implements mod_replace. This action permanently replaces all unmuted * patterns with the pattern selected after this function is engaged. * The replacement is queue. At the beginning of the next repeat, the * select pattern is on, and all others are off. * * For MIDI control, there should be no support for * toggle, but we're not sure how to implement this feature. * * For keystrokes, the user-interface's key-press callback should set the * inverse flag to false, and the key-release callback should set it to true. * The action will always be toggle for keystrokes. */ bool performer::automation_replace ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::mod_replace); print_parameters(name, a, d0, d1, index, inverse); if (opcontrol::allowed(d0, inverse)) { automation::ctrlstatus cs = automation::ctrlstatus::replace; result = set_ctrl_status(a, cs); } return result; } /** * Implements mod_snapshot. */ bool performer::automation_snapshot ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::mod_snapshot); print_parameters(name, a, d0, d1, index, inverse); if (opcontrol::allowed(d0, inverse)) { /* * Add queuing. Currently this queues only the * click pattern; it does not queue the restored snapshot. * For now we do not implement the queuing. * * automation::ctrlstatus cs = * add_queue(automation::ctrlstatus::snapshot); */ automation::ctrlstatus cs = automation::ctrlstatus::snapshot; result = set_ctrl_status(a, cs); } return result; } /** * Implements mod_queue. */ bool performer::automation_queue ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::mod_queue); print_parameters(name, a, d0, d1, index, inverse); if (opcontrol::allowed(d0, inverse)) return set_ctrl_status(a, automation::ctrlstatus::queue); else return true; /* pretend the key release worked */ } /** * Implements mod_gmute. When set, this sets the group-mute mode that will * take the current screenset and memorize its statuses to the mute-group * specified via the next key that is struck. * * Does not quite capture the keyboard group-on key versus group-off key of * Sequencer64. */ bool performer::automation_gmute ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::mod_gmute); print_parameters(name, a, d0, d1, index, inverse); if (opcontrol::allowed(d0, inverse)) { if (a == automation::action::toggle) set_mapper().toggle_group_mode(); else if (a == automation::action::on) set_mapper().group_mode(true); else if (a == automation::action::off) set_mapper().group_mode(false); } return true; } /** * Implements mod_glearn. This function is related to automation_gmute(). * Like that one, it sets the group-mute mode. In addition, it sets * group-learn mode, and then notifies all of the subscribers to this event. * * Another avenue for this is the learn_toggle() function, which a GUI like * qsmainwnd can call directly, either via the "Learn" button or a keystroke * like "L". */ bool performer::automation_glearn ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::mod_glearn); print_parameters(name, a, d0, d1, index, inverse); if (opcontrol::allowed(d0, inverse)) { if (a == automation::action::toggle) learn_toggle(); /* also notifies clients */ else if (a == automation::action::on) group_learn(true); /* also notifies clients */ else if (a == automation::action::off) group_learn(false); /* also notifies clients */ } return true; } /** * Implements play_ss, initiated by the Home key by default. * * This function saves the current state of the screenset, then sets the * play-screen to it, then it seems to redundantly set the states again. * * This function is called after changing the screenset. The d0 and d1 * parameters are not used, as this is merely a "flag". The current * playscreen number is used. Then, no matter what the setsmode is, * the playset is unmuted. * * Compare to automation_ss_set(). */ bool performer::automation_play_ss ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::play_ss); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { screenset::number ps = playscreen_number(); if (ps != screenset::unassigned()) { (void) set_playing_screenset(ps); set_song_mute(mutegroups::action::off); /* unmute them all */ } } return true; } /** * Implements playback. That is, start, pause, and stop. These are MIDI * controls. However, note that we also support separate actions for the * sake of keyboard control: * * - automation_start() * - automation_stop() * * If the action is a toggle (as happens with the "pause" key), then the * toggling is ignored if \a inverse is true. * * \param a * Provides the action to perform. Toggle = pause; On = start; and Off = * stop. * * \return * Returns true if the action was handled. */ bool performer::automation_playback ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::playback); print_parameters(name, a, d0, d1, index, inverse); if (a == automation::action::toggle) /* key "." press */ { if (! inverse) auto_pause(); } else if (a == automation::action::on) { if (inverse) auto_stop(); else auto_play(); } else if (a == automation::action::off) { if (inverse) auto_play(); else auto_stop(); } return true; } /** * Implements song_record, which sets the status to recording live events * into song triggers. If \a inverse is true, nothing is done. */ bool performer::automation_song_record ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::song_record); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { if (a == automation::action::toggle) song_recording(! song_recording()); /* at-start is false */ else if (a == automation::action::on) song_recording(true); else if (a == automation::action::off) song_recording(false); } return true; } /** * Implements solo. This isn't clear even in Sequencer64. We have * queued-replace and queued-solo which seem to be the same thing. * Replace, though, is not queued, while solo is queued. */ bool performer::automation_solo ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::solo); print_parameters(name, a, d0, d1, index, inverse); if (opcontrol::allowed(d0, inverse)) { automation::ctrlstatus cs = add_queue(automation::ctrlstatus::replace); result = set_ctrl_status(a, cs); } return result; } /** * Implements thru. If \a inverse is true, nothing is done. */ bool performer::automation_thru ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::thru); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { seq::number seqno = seq::number(d1); if (a == automation::action::toggle) set_thru(seqno, false, true); /* toggles */ else if (a == automation::action::on) set_thru(seqno, true, false); /* on */ else if (a == automation::action::off) set_thru(seqno, false, false); /* off */ } return true; } /** * Implements BPM Up and BPM Down. If \a inverse is true, nothing is done. We * need this behavior to support keystrokes. */ bool performer::automation_bpm_page_up_dn ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::bpm_page_up); print_parameters(name, a, d0, d1, index, inverse); if (inverse) { if (opcontrol::allowed(d0, inverse)) /* not a keystroke */ { if (a == automation::action::on) page_decrement_beats_per_minute(); else if (a == automation::action::off) page_increment_beats_per_minute(); } } else { if (automation::actionable(a)) page_increment_beats_per_minute(); else if (a == automation::action::off) page_decrement_beats_per_minute(); } return true; } /** * No matter how BPM Down is configured for MIDI control, if present and the * MIDI event matches, it will act like a BPM Down. This matches the behavior * of Seq24/Sequencer64. */ bool performer::automation_bpm_page_dn ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::bpm_page_dn); print_parameters(name, a, d0, d1, index, inverse); if (opcontrol::allowed(d0, inverse)) { return automation_bpm_page_up_dn ( automation::action::off, d0, d1, index, inverse ); } else return true; /* pretend the key release worked */ } /** * Sets the screen by number. Needs to be clarified. If \a inverse is true, * nothing is done, to support keystroke-release properly. * * Currently, d0 should be 0 and d1 should be the screenset number. For * example, send CC 0 (bank select) and the screenset number. * * Another option would be d0 = note number = screenset number, but * we don't do that here. */ bool performer::automation_ss_set ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::ss_set); print_parameters(name, a, d0, d1, index, inverse); if (opcontrol::allowed(d0, inverse)) (void) set_playing_screenset(screenset::number(d1)); return true; } /** * Implements the recording control. This function sets the recording status * of incoming MIDI events. If \a inverse is true, nothing is done. The same * operation occurs for the actions of toggle, on, and off (the latter two are * not supported by keystrokes. * * As of 2021-11-13, this function sets the loop-control mode. Normally, this * is "none", which means that loop-control toggles the mute status of the * specified pattern. This function increments the mode to the next one, * looping back to none. See usrsettings :: pattern_record_style() and the * usrsettings :: recordstyle enumeration. */ bool performer::automation_record_style ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::record_style); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { if (automation::actionable(a)) next_record_style(); else if (a == automation::action::off) previous_record_style(); /* * Done in the functions called above: * * notify_automation_change(automation::slot::record_style); */ } return true; } /** * Like record, but quantized. */ bool performer::automation_quan_record ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::quan_record); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { if (automation::actionable(a)) next_record_alteration(); else if (a == automation::action::off) previous_record_alteration(); notify_automation_change(automation::slot::quan_record); } return true; } /** * We now use it for a call to reset_sequences() and reset_playset(). */ bool performer::automation_reset_sets ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::reset_sets); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { reset_sequences(); reset_playset(); } return true; } /** * Handle one-shot mode, in a manner similar to queue, replace, etc. The * \a inverse parameter, if true, indicates exiting the mode, as upon * keystroke-release. */ bool performer::automation_oneshot ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::mod_oneshot); print_parameters(name, a, d0, d1, index, inverse); if (opcontrol::allowed(d0, inverse)) return set_ctrl_status(a, automation::ctrlstatus::oneshot); else return true; /* pretend the key release worked */ } bool performer::automation_FF ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::FF); print_parameters(name, a, d0, d1, index, inverse); move_tick(m_fast_ticks, true); return true; } bool performer::automation_rewind ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::rewind); print_parameters(name, a, d0, d1, index, inverse); move_tick(-m_fast_ticks, true); return true; } /** * Sets the time to the song beginning or the L marker. * Needs work. */ bool performer::automation_top ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::top); print_parameters(name, a, d0, d1, index, inverse); move_tick(0, true); /* slighly tricky */ return true; } /** * Implements playlist control. If \a inverse is true, nothing is done. Note * that currently the GUI may hardwire the usage of the arrow keys for this * functionality. * * Tricky: * * For the GUI, we need to handle the arrow keys and the automation in * the same way, so the notify_song_action() call does it. For the CLI, * we do the work here. */ bool performer::automation_playlist ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::playlist); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { if (a == automation::action::toggle) /* select-by-value */ { result = open_select_list_by_midi(d1); } else if (a == automation::action::on) /* select-next */ { if (signalled_changes()) notify_song_action(true, playlist::action::next_list); else result = open_next_list(); } else if (a == automation::action::off) /* select-previous */ { if (signalled_changes()) notify_song_action(true, playlist::action::previous_list); else result = open_previous_list(); } } return result; } /* * ---------------------------------------------------------------------- * More performer::automation_xxx() functions follow this group of * functions. * ---------------------------------------------------------------------- */ /** * This function calls the seq66::read_midi_file() free function, and then * sets the PPQN value. * * \param fn * Provides the full path file-specification for the MIDI file. * * \param pp * Provides the destination for the effective PPQN, generally read from the * file. * * \param [out] errmsg * Provides the destination for an error message, if any. * * \return * Returns true if the function succeeded. If false is returned, there * should be an errmsg to display. */ bool performer::read_midi_file ( const std::string & fn, std::string & errmsg, bool addtorecent ) { errmsg.clear(); /* * Hmmmm, should we call clear_all(true/false) here??? */ usr().clear_global_seq_features(); m_song_info.clear(); bool result = seq66::read_midi_file(*this, fn, ppqn(), errmsg, addtorecent); if (result) { mutegroup::number mg = mutegroup::unassigned(); /* not 0 */ metrosettings & ms = rc().metro_settings(); ms.beats_per_bar(get_beats_per_bar()); ms.beat_width(get_beat_width()); next_song_mode(); if (! errmsg.empty()) append_error_message(errmsg); /* actually not an error */ m_max_extent = get_max_extent(); /* analyze current file */ set_tick(0); /* enforce beginning */ announce_mutes(); /* cannot forget this one! */ notify_mutes_change(mg, change::no); } return result; } bool performer::open_note_mapper (const std::string & notefile) { bool result = false; m_note_mapper.reset(new (std::nothrow) notemapper()); if (m_note_mapper) { if (notefile.empty() || ! rc().notemap_active()) { // anything to do? } else { if (file_readable(notefile)) { notemapfile nmf(*m_note_mapper, notefile, rc()); result = nmf.parse(); if (! result) append_error_message(nmf.get_error_message()); } else { std::string msg = "Cannot read: " + notefile; append_error_message(msg); } } } return result; } bool performer::save_note_mapper (const std::string & notefile) { bool result = bool(m_note_mapper); if (result) { std::string nfname = rc().notemap_filespec(); if (! notefile.empty()) nfname = notefile; if (nfname.empty()) { result = false; } else { notemapfile nmf(*m_note_mapper, nfname, rc()); result = nmf.write(); if (! result) append_error_message(nmf.get_error_message()); } } return result; } std::string performer::playlist_song_basename () const { return filename_base(playlist_song()); } /** * This function is used only in the user-interface to turn on activation. */ bool performer::playlist_activate (bool on) { bool result = bool(m_play_list); if (result) result = m_play_list->activate(on); return result; } void performer::playlist_auto_arm (bool on) { if (m_play_list->loaded()) /* loaded successfully? */ m_play_list->auto_arm(on); } void performer::playlist_auto_play (bool on) { if (m_play_list->loaded()) /* loaded successfully? */ m_play_list->auto_play(on); } /** * Opens the next playlist after calling auto_stop(), which disengages * play-list auto-play. * * TODO: One thing doesn't make sense... why not do the next-song-mode stuff * even if headless. * * \param opensong * Indicates to open the first song in the next list. Passed to * playlist::open_next_list(). Also, if true, and if not headless (no * signalled changes), then next_song_mode() is called to unmute all * tracks if the song does not have triggers and set the live/song mode * appropriately. * * \param loading * Passed to playlist::open_next_list(). * * \return * Returns true if open_next_list() succeeds. */ bool performer::open_next_list (bool opensong, bool loading) { auto_stop(true); bool result = m_play_list->open_next_list(opensong, loading); if (result) handle_list_change(opensong); return result; } /** * Why the lack of a loading boolean parameter???? */ bool performer::open_previous_list (bool opensong) { auto_stop(true); bool result = m_play_list->open_previous_list(opensong); if (result) handle_list_change(opensong); return result; } void performer::handle_list_change (bool opensong) { if (opensong) next_song_mode(); if (signalled_changes()) notify_song_action(false); } bool performer::open_select_song_by_index (int index, bool opensong) { bool result = bool(m_play_list); if (result) { if (signalled_changes()) { result = m_play_list->open_select_song(index, opensong); } else { result = m_play_list->open_select_song(index, opensong); if (result) { if (opensong) next_song_mode(); notify_song_action(false); } } } return result; } bool performer::open_select_song_by_midi (int ctrl, bool opensong) { bool result = bool(m_play_list); if (result) { if (signalled_changes()) { result = m_play_list->open_select_song_by_midi(ctrl, opensong); } else { result = m_play_list->open_select_song_by_midi(ctrl, opensong); if (result) { if (opensong) next_song_mode(); notify_song_action(false); } } } return result; } /* * Make sure first song is enabled, if applicable. */ bool performer::open_current_song () { bool result = bool(m_play_list); if (result) { result = m_play_list->open_current_song(); /* * Nothing else to do? * * if (m_play_list->auto_play()) * start_playing(); */ } return result; } /** * Why no auto_stop()? */ bool performer::open_next_song (bool opensong) { auto_stop(true); bool result = bool(m_play_list); if (result) { result = m_play_list->open_next_song(opensong); if (result) handle_song_change(opensong); } return result; } /** * Why no auto_stop()? */ bool performer::open_previous_song (bool opensong) { auto_stop(true); bool result = bool(m_play_list); if (result) { result = m_play_list->open_previous_song(opensong); if (result) handle_song_change(opensong); } return result; } void performer::handle_song_change (bool opensong) { if (opensong) next_song_mode(); if (signalled_changes()) notify_song_action(false); start_playing(); } bool performer::open_mutegroups (const std::string & mgf) { bool result = false; std::string mgfname = mgf; if (mgf.empty()) mgfname = rc().mute_group_filespec(); if (mgfname.empty()) { append_error_message("no mute-group filename"); } else { result = seq66::open_mutegroups(mgfname, mutes()); if (result) mutes().group_save(rc().mute_group_save()); } return result; } bool performer::save_mutegroups (const std::string & mgf) { bool result = false; std::string mgfname = mgf; if (mgf.empty()) mgfname = rc().mute_group_filespec(); if (mgfname.empty()) { // what to do? } else { result = seq66::save_mutegroups(mgfname, mutes()); } return result; } /** * Imports a play-list from one directory to another. * * -# Provide the full path to the source playlist file. This path is * normally acquired from a playlist dialog box that starts in * the current home configuration directory; but it can reside * anywhere in the file system. * -# Copy the playlist file to the session configuration directory: * -# Normal: home config directory * -# NSM: session_directory/config * -# Load the playlist to set its filename and to get its parameters. * -# Copy the playlist's MIDI files to session MIDI directory: * -# Normal: home config directory/midi * -# NSM: session_directory/midi * -# Adjust the playlist base directory and its internal directory * hierarchy to match the new location of files. * -# Make playlist active and official in the 'rc' file. * -# The 'active' flag. * -# The base 'name' of the file. * -# The 'base-directory' * * What to do about absolute directories? Leave them be? * * \param sourcefile * Provides the full path to the playlist file to import. * * \param cfgpath * The destination where the caller wants to put the playlist. For NSM * session destinations, this path will end with "config". * * \param midipath * The destination where the caller wants to put the MIDI file. For NSM * session destinations or for normal home destinations, this path will * end with "midi". Or maybe something more specific? Should we ignore * absolute paths and leave them be? */ bool performer::import_playlist ( const std::string & sourcefile, const std::string & cfgpath, const std::string & midipath ) { bool result = file_readable(sourcefile); if (result) result = ! cfgpath.empty() && ! midipath.empty(); if (result) { result = make_directory_path(cfgpath); /* make it exist */ if (result) result = make_directory_path(midipath); if (result) { std::string sourcebase = filename_base(sourcefile); std::string filespec = filename_concatenate(cfgpath, sourcebase); result = file_copy(sourcefile, cfgpath); if (result) result = open_playlist(filespec); if (result) result = copy_playlist_songs(*m_play_list, filespec, midipath); if (result) m_play_list->loaded(true); } } return result; } /** * Creates a playlist object and opens it. If there is a playlist object * already in existence, it is replaced. If there is no playlist file-name, * then an "empty" playlist object is created. We do not want to constantly * check for its existence. The perform object needs to own the playlist. * * \param pl * Provides the full path file-specification for the play-list file to be * opened. If empty, a single playlist with only one play-list and no * songs is created. * * \param show_on_stdout * If true (the default is false), the playlist is opened to show * song selections on stdout. This is useful for trouble-shooting or for * making the CLI version of Sequencer64 easier to follow when running. * * \return * Returns true if the playlist object was able to be created. If the * file-name is not empty, this also means that it was opened, and the * play-list read. If false is returned, then the previous playlist, if * any, still exists, but is marked as inactive. */ bool performer::open_playlist (const std::string & pl) { bool show_on_stdout = rc().verbose(); if (m_play_list) m_play_list->loaded(false); /* just in case */ /* * This call adds the full path specification as the file-name for * the playlist. */ m_play_list.reset ( new (std::nothrow) playlist(this, pl, show_on_stdout) ); bool result = bool(m_play_list); if (result) { result = seq66::open_playlist(*m_play_list, pl, show_on_stdout); if (result) { if (rc().playlist_active()) /* if (playlist_active())??? */ { clear_all(); /* reset, not clear, playlist */ } else /* something more important? */ { /* * ca 2023-04-09. * This disables saving (for example) the recent files * list. But let's fix that problem elsewhere first. * ca 2025-05-03. * This cannot be fixed elsewhere. * * rc().auto_rc_save(false); // could be TRICKY! */ m_play_list->loaded(false); /* disable it by choice */ } } else { /* * This will fail if the user hasn't yet created the default * play-list file. No need to report it. * * append_error_message(m_play_list->error_message) */ m_play_list->loaded(false); /* disable it by error */ } } else append_error_message("Could not create playlist"); return result; } /** * Writes the play-list, whether it is active or not, as long as it exists. * * \param pl * Provides the full path file-specification for the play-list file to be * saved. If empty, the file-name with which the play-list was created * is used. * * \return * Returns true if the write operation succeeded. */ bool performer::save_playlist (const std::string & pl) { bool result = bool(m_play_list); if (result) { std::string plname = pl; if (plname.empty()) plname = rc().playlist_filespec(); if (plname.empty()) { // what to do? } else { result = seq66::save_playlist(*m_play_list, plname); /* * TODO * * if (! result) * append_error_message(m_play_list->error_message()); */ } } else { error_message("null playlist pointer"); } return result; } /* * ---------------------------------------------------------------------- * More performer::automation_xxx() functions: * ---------------------------------------------------------------------- */ /** * Implements playlist control. If \a inverse is true, nothing is done. * Note that currently the GUI may hardwire the usage of the arrow keys for * this functionality. * * Tricky: * * For the GUI, we need to handle the arrow keys and the automation in * the same way, so the notify_song_action() call does it. For the CLI, * we do the work here. */ bool performer::automation_playlist_song ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = false; std::string name = auto_name(automation::slot::playlist_song); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { if (a == automation::action::toggle) /* select-by-value */ { result = open_select_song_by_midi(d1); } else if (a == automation::action::on) /* select-next */ { if (signalled_changes()) notify_song_action(true, playlist::action::next_song); else result = open_next_song(); } else if (a == automation::action::off) /* select-previous */ { if (signalled_changes()) notify_song_action(true, playlist::action::previous_song); else result = open_previous_song(); } } return result; } /** * Implements setting the BPM by tapping a key. */ bool performer::automation_tap_bpm ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::tap_bpm); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { midibpm bpm = update_tap_bpm(); if (bpm != get_beats_per_minute()) set_beats_per_minute(bpm, true); } return true; } /** * Starts playback if not playing, or stops playback, with auto-rewind, if * already playing. This "one-key" feature is now hard-wired, so that one * does not need to keep remembering to reach for the Esc key. Also, this is * consistent with the hard-wired role of the Space key in the seqroll and * the perfroll. * * The \a inverse parameter, if true, does nothing. We don't want a * double-clutch on start when a keystroke is released. */ bool performer::automation_start ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::start); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { if (is_pattern_playing()) auto_stop(); else auto_play(); } return true; } /** * Stops playback. The \a inverse parameter, if true, does nothing. We don't * want a double-clutch on start when a keystroke is released. */ bool performer::automation_stop ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::stop); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) auto_stop(); return true; } bool performer::automation_looping ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::loop_LR); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { bool loopy = looping(); looping(! loopy); } return true; } bool performer::automation_toggle_mutes ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::toggle_mutes); print_parameters(name, a, d0, d1, index, inverse); if (a == automation::action::toggle) { if (! inverse) set_song_mute(mutegroups::action::toggle); } else if (a == automation::action::on) { if (inverse) set_song_mute(mutegroups::action::off); else set_song_mute(mutegroups::action::on); } else if (a == automation::action::off) { if (inverse) set_song_mute(mutegroups::action::on); else set_song_mute(mutegroups::action::off); } return true; } bool performer::automation_song_pointer ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::song_pointer); print_parameters(name, a, d0, d1, index, inverse); /* * TO BE DETERMINED TODO */ return true; } /** * I think we don't want to support inverse for this one. See the support for * the "Q" button and the set_keep_queue() function. */ bool performer::automation_keep_queue ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::keep_queue); print_parameters(name, a, d0, d1, index, inverse); if (opcontrol::allowed(d0, inverse)) { automation::ctrlstatus cs = automation::ctrlstatus::keep_queue; if (a == automation::action::toggle) return toggle_ctrl_status(cs); else return set_ctrl_status(a, cs); } else return true; } /** * \return * Returns true so that the caller can take action on it, unless the * user has pressed the key more than twice. */ bool performer::automation_slot_shift ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = false; std::string name = auto_name(automation::slot::slot_shift); name += std::to_string(slot_shift() + 1); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { (void) increment_slot_shift(); result = true; } return result; } /** * \return * Returns true so that the caller can take action on it, unless the user * has pressed the key more than twice. */ bool performer::automation_mutes_clear ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = false; std::string name = auto_name(automation::slot::mutes_clear); name += std::to_string(slot_shift() + 1); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { clear_mutes(); result = true; } return result; } /** * Signals that the application should exit. */ bool performer::automation_quit ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::quit); print_parameters(name, a, d0, d1, index, inverse); if (automation::actionable(a) && ! inverse) signal_quit(); return true; } /** * \return * Returns true so that the caller can take action on it. */ bool performer::automation_edit_pending ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::pattern_edit); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) m_seq_edit_pending = true; return true; } /** * \return * Returns true so that the caller can take action on it. */ bool performer::automation_event_pending ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::event_edit); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) m_event_edit_pending = true; return true; } /** * Toggles the Song/Live mode, but only on a key press, not on a key release. * * \return * Always returns true. */ bool performer::automation_song_mode ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::song_mode); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) (void) toggle_song_start_mode(); return true; } /** * Toggles the JACK transport mode, but only on a key press, not on a key * release. * * \return * Always returns true. */ bool performer::automation_toggle_jack ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::toggle_jack); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { std::string mode("JACK Transport "); toggle_jack_mode(); mode += get_jack_mode() ? "On" : "Off" ; infoprint(mode); } return true; } /** * Not sure we really need this one. However, now that we have the * button to hide the menu and the bottom rows, this might be * useful. * * TODO */ bool performer::automation_menu_mode ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::menu_mode); bool result = false; print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { notify_automation_change(automation::slot::menu_mode); /* toggle */ } return result; } /** * Toggles the following of JACK Transport upon a "key" press. */ bool performer::automation_follow_transport ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::follow_transport); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) { std::string mode{name}; toggle_follow_transport(); mode += get_follow_transport() ? "On" : "Off" ; infoprint(mode); } return result; } bool performer::automation_panic ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::panic); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) result = panic(); return result; } bool performer::automation_visibility ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::visibility); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) result = visibility(a); return result; } bool performer::automation_save_session ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::save_session); print_parameters(name, a, d0, d1, index, inverse); if (automation::actionable(a) && ! inverse) signal_save(); /* actually just raises a flag */ return true; } bool performer::automation_record_toggle ( automation::action a, int d0, int d1, int index, bool inverse ) { std::string name = auto_name(automation::slot::record_toggle); print_parameters(name, a, d0, d1, index, inverse); if (! inverse) m_record_toggle_pending = true; return true; } /** * Values are in the recordstyle enumeration in the usersettings module: * * - merge * - overwrite * - expand * - oneshot * - oneshot_reset */ void performer::set_record_style (recordstyle rs) { if (rs < recordstyle::max) /* recordstyle::oneshot_reset */ { usr().set_pattern_record_style(rs); if (rs == recordstyle::oneshot_reset) { set_tick(0); set_start_tick(0); m_record_style = recordstyle::oneshot; } else m_record_style = rs; notify_automation_change(automation::slot::record_style); } } /** * Selects the style of recording: recordstyle::merge, overwrite, expand, * oneshot, and oneshot_reset. * * Also see performer::automation_record_style() above. */ bool performer::automation_record_style_select ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::record_style); /* tricky */ if (automation::actionable(a) && ! inverse) { automation::slot s = int_to_slot_cast(index); recordstyle rs; name += " "; switch (s) { case automation::slot::record_overdub: rs = recordstyle::merge; name += auto_name(automation::slot::record_overdub); break; case automation::slot::record_overwrite: rs = recordstyle::overwrite; name += auto_name(automation::slot::record_overwrite); break; case automation::slot::record_expand: rs = recordstyle::expand; name += auto_name(automation::slot::record_expand); break; case automation::slot::record_oneshot: rs = recordstyle::oneshot; name += auto_name(automation::slot::record_oneshot); break; default: rs = recordstyle::max; name += "Error"; break; } print_parameters(name, a, d0, d1, index, inverse); set_record_style(rs); } return result; } /** * Values are specified in the gridmode enumeration in the usersettings * module: loop, record, copy, paste, clear, remove, ... double (length). */ void performer::set_grid_mode (gridmode gm) { if (gm < gridmode::max) { usr().grid_mode(gm); if (gm != gridmode::record) { usr().record_alteration(alteration::none); /* * Why do this??? [ca 2024-11-11] * * usr().record_style(recordstyle::merge); */ automation::ctrlstatus cs = add_queue(automation::ctrlstatus::replace); if (gm == gridmode::solo) (void) set_ctrl_status(automation::action::on, cs); else (void) set_ctrl_status(automation::action::off, cs); } notify_automation_change(automation::slot::grid_loop); } } bool performer::automation_grid_mode ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = "Mode: "; if (automation::actionable(a) && ! inverse) { automation::slot s = int_to_slot_cast(index); gridmode gm; name += auto_name(s); print_parameters(name, a, d0, d1, index, inverse); switch (s) { case automation::slot::grid_mutes: gm = gridmode::mutes; break; case automation::slot::grid_loop: gm = gridmode::loop; break; case automation::slot::grid_record: gm = gridmode::record; break; case automation::slot::grid_copy: gm = gridmode::copy; break; case automation::slot::grid_paste: gm = gridmode::paste; break; case automation::slot::grid_clear: gm = gridmode::clear; break; case automation::slot::grid_delete: gm = gridmode::remove; break; case automation::slot::grid_thru: gm = gridmode::thru; break; case automation::slot::grid_solo: gm = gridmode::solo; break; case automation::slot::grid_cut: gm = gridmode::cut; break; case automation::slot::grid_double: gm = gridmode::double_length; break; default: gm = gridmode::max; break; } set_grid_mode(gm); } return result; } /** * This merely sets the kind of alteration to employ once recording * is set. */ void performer::set_grid_quant (alteration q) { if (q < alteration::max) m_record_alteration = q; } bool performer::automation_grid_quant ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; if (automation::actionable(a) && ! inverse) { automation::slot s = int_to_slot_cast(index); std::string name = auto_name(s); alteration q; /* calculations module */ print_parameters(name, a, d0, d1, index, inverse); switch (s) { case automation::slot::grid_quant_none: q = alteration::none; break; case automation::slot::grid_quant_tighten: /* partial q'zation */ q = alteration::tighten; break; case automation::slot::grid_quant_full: /* full q'zation */ q = alteration::quantize; break; case automation::slot::grid_quant_jitter: /* note time & vel. */ q = alteration::jitter; break; case automation::slot::grid_quant_random: /* event d0 or d1 */ q = alteration::random; break; case automation::slot::grid_quant_notemap: /* for expansion */ q = alteration::notemap; break; default: q = alteration::none; break; } set_grid_quant(q); } return result; } bool performer::automation_bbt_hms ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::mod_bbt_hms); print_parameters(name, a, d0, d1, index, inverse); if (automation::actionable(a) && ! inverse) { notify_automation_change(automation::slot::mod_bbt_hms); } return result; } bool performer::automation_LR_loop ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::mod_LR_loop); print_parameters(name, a, d0, d1, index, inverse); if (automation::actionable(a) && ! inverse) { notify_automation_change(automation::slot::mod_LR_loop); } return result; } bool performer::automation_undo ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::mod_undo); print_parameters(name, a, d0, d1, index, inverse); if (automation::actionable(a) && ! inverse) { notify_automation_change(automation::slot::mod_undo); } return result; } bool performer::automation_redo ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::mod_redo); print_parameters(name, a, d0, d1, index, inverse); if (automation::actionable(a) && ! inverse) { notify_automation_change(automation::slot::mod_redo); } return result; } bool performer::automation_copy_set ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::mod_copy_set); print_parameters(name, a, d0, d1, index, inverse); if (automation::actionable(a) && ! inverse) { result = copy_playscreen(); notify_automation_change(automation::slot::mod_copy_set); } return result; } bool performer::automation_paste_set ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; std::string name = auto_name(automation::slot::mod_paste_set); print_parameters(name, a, d0, d1, index, inverse); if (automation::actionable(a) && ! inverse) { result = paste_to_playscreen(); notify_automation_change(automation::slot::mod_paste_set); } return result; } bool performer::automation_set_mode ( automation::action a, int d0, int d1, int index, bool inverse ) { bool result = true; if (automation::actionable(a) && ! inverse) { automation::slot s = int_to_slot_cast(index); std::string name = auto_name(s); print_parameters(name, a, d0, d1, index, inverse); switch (s) { case automation::slot::set_mode_normal: rc().sets_mode(rcsettings::setsmode::normal); break; case automation::slot::set_mode_auto: rc().sets_mode(rcsettings::setsmode::autoarm); break; case automation::slot::set_mode_additive: rc().sets_mode(rcsettings::setsmode::additive); break; case automation::slot::set_mode_all_sets: rc().sets_mode(rcsettings::setsmode::allsets); break; default: break; } } return result; } /** * Provides a list of all the functions that can be configured to be called * upon configured keystrokes or incoming MIDI messages. */ performer::automation_pair performer::sm_auto_func_list [] = { { automation::slot::bpm_up, &performer::automation_bpm_up_dn }, { automation::slot::bpm_dn, &performer::automation_bpm_dn }, { automation::slot::ss_up, &performer::automation_ss_up_dn }, { automation::slot::ss_dn, &performer::automation_ss_dn }, { automation::slot::mod_replace, &performer::automation_replace }, { automation::slot::mod_snapshot, &performer::automation_snapshot }, { automation::slot::mod_queue, &performer::automation_queue }, { automation::slot::mod_gmute, &performer::automation_gmute }, { automation::slot::mod_glearn, &performer::automation_glearn }, { automation::slot::play_ss, &performer::automation_play_ss }, { automation::slot::playback, &performer::automation_playback }, { automation::slot::song_record, &performer::automation_song_record }, { automation::slot::solo, &performer::automation_solo }, { automation::slot::thru, &performer::automation_thru }, { automation::slot::bpm_page_up, &performer::automation_bpm_page_up_dn }, { automation::slot::bpm_page_dn, &performer::automation_bpm_page_dn }, { automation::slot::ss_set, &performer::automation_ss_set }, { automation::slot::record_style, &performer::automation_record_style }, { automation::slot::quan_record, &performer::automation_quan_record }, { automation::slot::reset_sets, &performer::automation_reset_sets }, { automation::slot::mod_oneshot, &performer::automation_oneshot }, { automation::slot::FF, &performer::automation_FF }, { automation::slot::rewind, &performer::automation_rewind }, { automation::slot::top, &performer::automation_top }, { automation::slot::playlist, &performer::automation_playlist }, { automation::slot::playlist_song, &performer::automation_playlist_song }, { automation::slot::tap_bpm, &performer::automation_tap_bpm }, { automation::slot::start, &performer::automation_start }, { automation::slot::stop, &performer::automation_stop }, { automation::slot::loop_LR, &performer::automation_looping }, { automation::slot::toggle_mutes, &performer::automation_toggle_mutes }, { automation::slot::song_pointer, &performer::automation_song_pointer }, { automation::slot::keep_queue, &performer::automation_keep_queue }, { automation::slot::slot_shift, &performer::automation_slot_shift }, { automation::slot::mutes_clear, &performer::automation_mutes_clear }, { automation::slot::quit, &performer::automation_quit }, { automation::slot::pattern_edit, &performer::automation_edit_pending }, { automation::slot::event_edit, &performer::automation_event_pending }, { automation::slot::song_mode, &performer::automation_song_mode }, { automation::slot::toggle_jack, &performer::automation_toggle_jack }, { automation::slot::menu_mode, &performer::automation_menu_mode }, { automation::slot::follow_transport, &performer::automation_follow_transport }, { automation::slot::panic, &performer::automation_panic }, { automation::slot::visibility, &performer::automation_visibility }, { automation::slot::save_session, &performer::automation_save_session }, { automation::slot::record_toggle, &performer::automation_record_toggle }, /* * Should have thought of adding this much earlier. So see have to * put this one in the reserved section as a special case, unless * we want to possibly break the users' setups. */ { automation::slot::grid_mutes, &performer::automation_grid_mode }, { automation::slot::reserved_47, &performer::automation_no_op }, { automation::slot::reserved_48, &performer::automation_no_op }, /* * Proposed massive expansion in automation. Grid mode selection. */ { automation::slot::record_overdub, &performer::automation_record_style_select }, { automation::slot::record_overwrite, &performer::automation_record_style_select }, { automation::slot::record_expand, &performer::automation_record_style_select }, { automation::slot::record_oneshot, &performer::automation_record_style_select }, { automation::slot::grid_loop, &performer::automation_grid_mode }, { automation::slot::grid_record, &performer::automation_grid_mode }, { automation::slot::grid_copy, &performer::automation_grid_mode }, { automation::slot::grid_paste, &performer::automation_grid_mode }, { automation::slot::grid_clear, &performer::automation_grid_mode }, { automation::slot::grid_delete, &performer::automation_grid_mode }, { automation::slot::grid_thru, &performer::automation_grid_mode }, { automation::slot::grid_solo, &performer::automation_grid_mode }, { automation::slot::grid_cut, &performer::automation_grid_mode }, { automation::slot::grid_double, &performer::automation_grid_mode }, /* * Grid quantization type selection. */ { automation::slot::grid_quant_none, &performer::automation_grid_quant }, { automation::slot::grid_quant_full, &performer::automation_grid_quant }, { automation::slot::grid_quant_tighten, &performer::automation_grid_quant }, { automation::slot::grid_quant_random, &performer::automation_grid_quant }, { automation::slot::grid_quant_jitter, &performer::automation_grid_quant }, { automation::slot::grid_quant_notemap, &performer::automation_grid_quant }, /* * A few more likely candidates. */ { automation::slot::mod_bbt_hms, &performer::automation_bbt_hms }, { automation::slot::mod_LR_loop, &performer::automation_LR_loop }, { automation::slot::mod_undo, &performer::automation_undo }, { automation::slot::mod_redo, &performer::automation_redo }, /* * Transpose song... what does this even mean? We forget! */ { automation::slot::mod_transpose_song, &performer::automation_no_op }, { automation::slot::mod_copy_set, &performer::automation_copy_set }, { automation::slot::mod_paste_set, &performer::automation_paste_set }, { automation::slot::mod_toggle_tracks, &performer::automation_toggle_mutes /* duplicate functionality! */ }, /* * Set playing modes. */ { automation::slot::set_mode_normal, &performer::automation_set_mode }, { automation::slot::set_mode_auto, &performer::automation_set_mode }, { automation::slot::set_mode_additive, &performer::automation_set_mode }, { automation::slot::set_mode_all_sets, &performer::automation_set_mode }, /* * Terminator */ { automation::slot::max, &performer::automation_no_op } }; } // namespace seq66 /* * performer.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/playlist.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file playlist.cpp * * This module declares/defines the base class for managing the qseq66.playlist * file family. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-08-26 * \updates 2023-10-31 * \license GNU GPLv2 or above * * See the playlistfile class for information on the file format. */ #include /* std::toupper() function */ #include /* std::cout */ #include /* std::make_pair() */ #include /* memset() */ #include "cfg/settings.hpp" /* seq66::rc() */ #include "midi/wrkfile.hpp" /* seq66::midifile & seq66::wrkfile */ #include "play/playlist.hpp" /* seq66::playlist support class */ #include "play/performer.hpp" /* seq66::performer anchor class */ #include "util/filefunctions.hpp" /* functions for file-names */ #include "util/strfunctions.hpp" /* strip_quotes() */ namespace seq66 { /** * This object is used in returning a reference to a bogus object. */ playlist::song_list playlist::sm_dummy; /** * Principal constructor. * * \param p * Provides the performer object that will interface between this module * and the rest of the application. * * \param filename * Provides the name of the options file; this is usually a full path * file-specification. * * \param show_on_stdout * If true (the default is false), then the list/song information is * written to stdout, to help with debugging. */ playlist::playlist ( performer * p, const std::string & filename, bool show_on_stdout ) : basesettings (filename), m_performer (p), /* owner of this object */ m_play_lists (), m_loaded (false), /* playlist loaded */ m_deep_verify (false), m_current_list (m_play_lists.end()), m_current_song (sm_dummy.end()), /* song-list iterator */ m_auto_arm (false), m_auto_play (false), m_engage_auto_play (false), m_auto_advance (false), m_midi_base_directory (rc().midi_base_directory()), m_show_on_stdout (show_on_stdout) { // No code } /** * This destructor unregisters this playlist from the performer object. */ playlist::~playlist () { // No code } /** * Indicates only if the loaded data is usable. A lesser version of verify() * that is used before the play-lists are saved to a play-list file. */ bool playlist::validated () const { bool result = ! m_play_lists.empty(); if (result) { auto beginning = m_play_lists.cbegin(); result = beginning->second.ls_song_count > 0; if (! result) return true; /* no songs to verify */ } if (result) { for (const auto & plpair : m_play_lists) { if (plpair.second.ls_song_count > 0) { const song_list & sl = plpair.second.ls_song_list; for (const auto & sci : sl) { const song_spec_t & s = sci.second; std::string fname = song_filepath(s); if (fname.empty()) { result = false; break; } } if (! result) break; } else { result = false; break; } } } return result; } /** * Used (only) in the user interface to validate that usable playlist data is * present and to activate the playlist. * * \return * Returns true if the settings were made. */ bool playlist::activate (bool flag) { bool result = false; bool change = flag != rc().playlist_active(); if (change) { if (flag) { bool ok = validated(); if (ok) { loaded(true); /* i.e. the play-lists are loaded */ result = true; } } else result = true; rc().playlist_active(flag); rc().auto_rc_save(true); } return result; } /** * Returns true if in playlist mode and the playlist is active. These are * two separate conditions at present. */ bool playlist::active () const { return rc().playlist_active() && loaded(); } /** * Helper function for error-handling. It assembles a message and then * passes it to append_error_message(). * * \param additional * Additional context information to help in finding the error. * * \return * Always returns false. */ bool playlist::set_error_message (const std::string & additional) const { if (! additional.empty()) { std::string msg = "Play-list: "; msg += additional; basesettings::set_error_message(msg); /* add to error message */ } return false; } /** * This function checks for no songs in a play-list structure. This is a * workaround for an issue that occurs when a play-list file gets corrupted. * We don't want to exit just because there is no playlist loaded. And this * check does not work: * * result = ! plist.ls_song_list.empty(); */ bool playlist::check_song_list (const play_list_t & plist) { return plist.ls_song_count > 0; } /** * Given a file-name, opens that file as a song. This function holds common * code. It is similar to read_midi_file() in the midifile module, but * does not affect the recent-files list. * * Before the song is loaded, the current song is cleared from memory. * Remember that clear_all() will fail if it detects a sequence being edited. * In that case, this function will fail as well. * * \param fname * The full path to the file to be opened. If this parameter is empty, * no load is attempted, but playback is stopped and the song is cleared. * * \param verifymode * If true, open the file in play-list mode. Currently, this means * is that some output from the file-opening process is suppressed, and * the performer::clear_all() function is called right after parsing the * song file to verify it. */ bool playlist::open_song (const std::string & fname, bool verifymode) { bool result = not_nullptr(m_performer); #if defined USE_OLD_CODE // ca 2023-07-12 if (result) { /* * These could be done in performer:delay_stop(), which * all playlist traversal functions call. */ if (m_performer->is_pattern_playing()) m_performer->stop_playing(); result = m_performer->clear_song(); } #endif if (result) { std::string errmsg_dummy; result = m_performer->read_midi_file(fname, errmsg_dummy, false); if (result && verifymode) { /* nothing to do yet */ } } return result; } /** * Selects the song based on the index (row) value, and optionally opens it. * * \param index * The ordinal location of the song within the current playlist. * * \param opensong * If true (the default), the song is opened. * * \return * Returns true if the song is selected, and if specified, is opened * succesfully as well. */ bool playlist::open_select_song (int index, bool opensong) { bool result = select_song(index); if (result && opensong) result = open_current_song(); return result; } /** * Selects the song based on the MIDI control value, and optionally opens it. * * \param ctrl * The MIDI control to access the song within the current playlist. * * \param opensong * If true (the default), the song is opened. * * \return * Returns true if the song is selected, and if specified, is opened * succesfully as well. */ bool playlist::open_select_song_by_midi (int ctrl, bool opensong) { bool result = select_song_by_midi(ctrl); if (result && opensong) result = open_current_song(); return result; } /** * Goes through all of the playlists and makes sure that all of the song * files are accessible. * * \param strong * If true, also make sure the MIDI files open without error as well. * The code is similar to read_midi_file() in the midifile module, but it * does not make configuration settings. Setting this option to true can * slow startup way down if there are a lot of big files in the playlist. * * \return * Returns true if all of the MIDI files are verifiable. A blank * filename (empty playlist) results in a false return value. */ bool playlist::verify (bool strong) { bool result = ! m_play_lists.empty(); if (result) { /* * This can add a new (and empty) play-list :-( Not good to access a * map via operator []. * * result = m_play_lists[0].ls_song_count > 0; */ auto beginning = m_play_lists.cbegin(); result = beginning->second.ls_song_count > 0; if (! result) return true; /* no songs to verify */ } if (result) { for (const auto & plpair : m_play_lists) { const song_list & sl = plpair.second.ls_song_list; for (const auto & sci : sl) { const song_spec_t & s = sci.second; std::string fname = song_filepath(s); if (fname.empty()) { result = false; break; } if (file_exists(fname)) { if (strong) { /* * The file is parsed. If the result is false, then * the play-list mode ends up false. Let the caller * do the reporting on errors. */ result = open_song(fname, true); if (result) { if (rc().verbose()) file_message("Verified", fname); } else { set_file_error_message("song '%s' missing", fname); break; } } } else { std::string fmt = plpair.second.ls_list_name; fmt += ": song '%s' missing; check relative directories."; result = set_file_error_message(fmt, fname); break; } } if (! result) break; } } else { std::string msg = "empty list file '"; msg += file_name(); msg += "'"; set_error_message(msg); } return result; } /** * This function copies all of the MIDI files in all of the play-lists to a * new root directory. * * It should also update the MIDI base directory in the "rc" file. * * The copy will silently fail at the first file that is not found to exist. * * \param destination * Provides the path to the destination directory for the MIDI files. * This directory is created if it does not exist, after normalizing this * path. * * \return * Returns true if every copy operation was able to be completed. */ bool playlist::copy_songs (const std::string & destination) { bool result = ! m_play_lists.empty(); if (result) { std::string dst = os_normalize_path(destination); result = make_directory_path(dst); if (result) { file_message("Playlist directory", dst); for (const auto & plpair : m_play_lists) { const play_list_t & pl = plpair.second; const song_list & sl = pl.ls_song_list; file_message("Playlist", pl.ls_list_name); for (const auto & sci : sl) { const song_spec_t & s = sci.second; std::string fname = song_filepath(s); file_message("Song", fname); result = file_exists(fname); if (result) { std::string d = append_path(dst, s.ss_song_directory); result = make_directory_path(d); if (result) { d = append_file(d, s.ss_filename); result = file_copy(fname, d); if (! result) { set_file_error_message("Copy failed", d); break; } } else { set_file_error_message("Create failed", d); break; } } else { set_file_error_message("File does not exist", fname); break; } } if (! result) break; } if (result) { /* * Now we need to make each playlist directory relative. */ for (auto & plpair : m_play_lists) { play_list_t & pl = plpair.second; std::string playdir = pl.ls_file_directory; pl.ls_file_directory = make_path_relative(playdir); } } } else { set_file_error_message("Failed to create", dst); } } else { std::string msg = "empty list file '"; msg += file_name(); msg += "'"; set_error_message(msg); } return result; } /** * Opens/loads the current song. * * \return * Returns true if there was a song to be opened, and it opened properly. */ bool playlist::open_current_song () { bool result = true; if (active()) { result = m_current_list != m_play_lists.end(); if (result) { play_list_t & plist = m_current_list->second; result = check_song_list(plist); if (! result) return true; /* no songs to open */ if (result) result = m_current_song != plist.ls_song_list.end(); if (result) { std::string fname = song_filepath(m_current_song->second); if (! fname.empty()) { result = open_song(fname); if (! result) { (void) set_file_error_message ( "Open failed: song '%s'", fname ); } } } } } return result; } bool playlist::open_next_list (bool opensong, bool loading) { bool result = false; if (active() || loading) { result = next_list(true); /* select the next list, first song */ if (result && opensong) result = open_current_song(); } return result; } bool playlist::open_previous_list (bool opensong) { bool result = false; if (active()) { result = previous_list(true); /* select the prev. list, first song */ if (result && opensong) result = open_current_song(); } return result; } bool playlist::open_select_list (int index, bool opensong) { bool result = select_list(index, opensong); if (active()) { if (result && opensong) result = open_current_song(); } return result; } bool playlist::open_select_list_by_midi (int ctrl, bool opensong) { bool result = true; if (active()) { result = select_list_by_midi(ctrl, opensong); if (result && opensong) result = open_current_song(); } return result; } bool playlist::open_next_song (bool opensong) { bool result = false; // true; if (active()) { result = next_song(); if (result && opensong) result = open_current_song(); } return result; } bool playlist::open_previous_song (bool opensong) { bool result = false; // true; if (active()) { result = previous_song(); if (result && opensong) result = open_current_song(); } return result; } /** * Makes a file-error message. */ bool playlist::set_file_error_message ( const std::string & fmt, const std::string & filename ) { char tmp[256]; snprintf(tmp, sizeof tmp, fmt.c_str(), filename.c_str()); set_error_message(tmp); return false; } /** * Clears the comments and the play-lists, sets the play-list mode to false, * and disables the list and song iterators. */ void playlist::clear () { comments_block().clear(); m_play_lists.clear(); loaded(false); m_current_list = m_play_lists.end(); m_current_song = sm_dummy.end(); } /** * Resets to the first play-list and the first-song in that playlist. * * \param listindex * Set the current list to this value, which defaults to 0. * * \param clearit * If true, clear the playlist no matter what. Then false is returned. * * \return * Returns true if the play-lists were present and the first song of the * first play-list was able to be selected. */ bool playlist::reset_list (int listindex, bool clearit) { bool result = false; if (clearit) { clear(); } else { result = ! m_play_lists.empty(); if (result) { int index = 0; for (auto p = m_play_lists.begin(); p != m_play_lists.end(); ++p) { if (index == listindex) { m_current_list = p; break; } ++index; } result = select_song(0); } } return result; } /* * List-container functions. */ /** * Adds an already set playlist structure. It is copied into the list of * play-lists. It assumes all of the fields in the play-list have been set, * including an empty song-list. * * \note * Do not reorder! * * \param plist * Provides a reference to the new playlist to be added. This parameter * is copied into the list. * * \return * Returns true if the count of playlists has changed. However, if a * playlist was simply being modified, this value is false. So the usage * of the return parameter is dependent upon the context of the call. */ bool playlist::add_list (const play_list_t & plist) { bool result = false; int count = int(m_play_lists.size()); int listnumber = plist.ls_midi_number; /* MIDI control number */ if (listnumber >= 0) { /* * std::pair */ auto ls = std::make_pair(listnumber, plist); m_play_lists.insert(ls); result = int(m_play_lists.size()) == count + 1; } return result; } /** * Selects a play-list with the given index (i.e. a row value). * * \param index * The index of the play-list re 0. * * \param selectsong * If true, then the first (0th) song in the play-list is selected. * * \return * Returns true if the selected play-list is valid. If true, then the * m_current_list iterator points to the current list. */ bool playlist::select_list (int index, bool selectsong) { bool result = false; int count = 0; auto minimum = m_play_lists.begin(); auto maximum = m_play_lists.end(); for (auto pci = minimum; pci != maximum; ++pci, ++count) { if (count == index) { if (m_show_on_stdout) show_list(pci->second); m_current_list = pci; if (selectsong) select_song(0); result = true; } } return result; } /** * We want to provide the user with an unused list/control number. The user * can scroll to the bottom of the list in the user interface, but this * is annoying and easy to forget. We look at the last entry, and increment * the key value by one, if possible. * * \return * Returns the next number after the last one. If there's none * available, then (-1) is returned. */ int playlist::next_available_list_number () const { int result = (-1); auto last = m_play_lists.rbegin(); if (last != m_play_lists.rend()) { int controlnumber = last->first; if (controlnumber < 127) result = controlnumber + 1; } return result; } /** * Selects a play-list with the given MIDI control value. * * \param index * The MIDI control value of the play-list. Generally should be * restricted to the range of 0 to 127, to be suitable for MIDI control. * * \param selectsong * If true, then the first (0th) song in the play-list is selected. * * \return * Returns true if the selected play-list is valid. If true, then the * m_current_list iterator points to the current list. */ bool playlist::select_list_by_midi (int ctrl, bool selectsong) { bool result = false; int count = 0; auto minimum = m_play_lists.begin(); auto maximum = m_play_lists.end(); for (auto pci = minimum; pci != maximum; ++pci, ++count) { int midinumber = pci->second.ls_midi_number; if (midinumber == ctrl) { if (m_show_on_stdout) show_list(pci->second); m_current_list = pci; if (selectsong) select_song(0); result = true; } } return result; } /** * Moves to the next play-list. If the iterator reaches the end, this * function wraps around to the beginning. Also see the other return value * conditions. * * \param selectsong * If true (the default), the first song in the play-list is selected. * * \return * Returns true if the play-list iterator was able to be moved, or if * there was only one play-list, so that movement was unnecessary. If the * there are no play-lists, then false is returned. */ bool playlist::next_list (bool selectsong) { bool result = m_play_lists.size() > 0; /* there's at least one */ if (m_play_lists.size() > 1) { ++m_current_list; if (m_current_list == m_play_lists.end()) m_current_list = m_play_lists.begin(); if (m_show_on_stdout) show_list(m_current_list->second); if (selectsong) select_song(0); } return result; } /** * Moves to the previous play-list. If the iterator reaches the beginning, * this function wraps around to the end. Also see the other return value * conditions. * * \param selectsong * If true (the default), the first song in the play-list is selected. * * \return * Returns true if the play-list iterator was able to be moved, or if * there was only one play-list, so that movement was unnecessary. If the * there are no play-lists, then false is returned. */ bool playlist::previous_list (bool selectsong) { bool result = m_play_lists.size() > 0; if (m_play_lists.size() > 1) { if (m_current_list == m_play_lists.begin()) m_current_list = std::prev(m_play_lists.end()); else --m_current_list; if (m_show_on_stdout) show_list(m_current_list->second); if (selectsong) select_song(0); } return result; } /** * The following four functions are to be used for a playlist editor, though * they can also be used for parsing/loading the playlist. Kind of a low * priority, but we are leaving room for the concept. * * In usage, the playlist and its songs are ordered by MIDI control number. * That is, the MIDI control number is the key, which allows for the fast * lookup of the MIDI control number during a live performance involving MIDI * control from the musician. * * In editing, the playlist and its songs are ordered by MIDI control number. * When a playlist is added, we are adding a row to the table, keyed by the * control number. We then have to re-order the playlist and repopulate the * table. The song-list for the new playlist is empty, and the playlist * table must reflect that. * * Adding a song to the new playlist, if it is indeed new, should be * straightforward. * * Adding a song to an existing playlist (which might be a new playlist) * means inserting the song and re-ordering the playlist's songs as * necessary). * * The add_list() function adds a playlist and re-orders the list of * playlists. The playlist is initially empty, and an empty song-list is * created. * * \todo * * \return * Returns the return-value of the add_list() overload, which returns * true if the count has changed. */ /** * An overloaded function to encapsulate adding a playlist and make the * callers simpler. The inserted list has an empty song-list. This function * is intended for use by a playlist editor. * * \todo * Add the ability to replace a play-list as well. * * \param index * Provides the location of the active list in the table. The actual * stored value may change after reordering. */ bool playlist::add_list ( int index, int midinumber, const std::string & name, const std::string & directory ) { play_list_t plist; /* will be copied upon insertion */ plist.ls_index = index; /* an ordinal value from list table */ plist.ls_midi_number = midinumber; /* MIDI control number to use */ plist.ls_list_name = name; plist.ls_file_directory = directory; plist.ls_song_count = 0; /* no songs to start in new list */ /* * Song list is empty at first, created by the playlist default constructor. * * plist.ls_song_list = slist; */ bool result = add_list(plist); reorder_play_list(); return result; } bool playlist::modify_list ( int index, int midinumber, const std::string & name, const std::string & directory ) { bool result = m_current_list != m_play_lists.end(); if (result) { play_list_t & plist = m_current_list->second; plist.ls_index = index; /* ordinal value in list table */ plist.ls_midi_number = midinumber; /* MIDI control number to use */ plist.ls_list_name = name; plist.ls_file_directory = directory; } return result; } /** * This function removes a playlist at the given index. The index is an * ordinal, not a key, therefore we have to iterate through the whole list * until we encounter the desired index. Useful for removing the selected * playlist in a table. * * This function works by iterating to the index'th element in the playlist * and deleting it. * * \param midinumber * The ordinal value (not a key) of the desired table row. * * \return * Returns true if the desired list was found and removed. */ bool playlist::remove_list (int index) { bool result = false; int count = 0; auto minimum = m_play_lists.begin(); auto maximum = m_play_lists.end(); for (auto pci = minimum; pci != maximum; /* ++pci, */ ++count) { if (count == index) { pci = m_play_lists.erase(pci); result = true; break; } else ++pci; } if (result) reorder_play_list(); return result; } /** * Moves through the play-list container in key (MIDI control number) order, * modifying the index value of each playlist in the main list. */ void playlist::reorder_play_list () { auto minimum = m_play_lists.begin(); auto maximum = m_play_lists.end(); int index = 0; for (auto pci = minimum; pci != maximum; ++pci, ++index) { play_list_t & p = pci->second; p.ls_index = index; } } /* * Song-container functions. */ /** * Obtains the current song index, which is a number starting at 0 that * indicates the song's position in the list. This value is useful to put * the song information at the right place in a table, for example. If the * current-list iterator is invalid, or the current-song iterator is invalid, * then (-1) is returned. */ int playlist::song_index () const { int result = (-1); if (m_current_list != m_play_lists.end()) { play_list_t & plist = m_current_list->second; if (m_current_song != plist.ls_song_list.end()) result = m_current_song->second.ss_index; } return result; } /** * Used to return m_current_list->second.ls_file_directory. */ std::string playlist::file_directory () const { std::string result; if (m_current_list != m_play_lists.end()) { play_list_t & plist = m_current_list->second; return plist.ls_file_directory; } return result; } /** * Used to return m_current_list->second.ls_file_directory. */ std::string playlist::song_directory () const { std::string result; if (m_current_list != m_play_lists.end()) { play_list_t & plist = m_current_list->second; if (m_current_song != plist.ls_song_list.end()) result = m_current_song->second.ss_song_directory; } return result; } /** * Used to return m_current_list->second.ls_file_directory. */ bool playlist::is_own_song_directory () const { bool result = false; if (m_current_list != m_play_lists.end()) { play_list_t & plist = m_current_list->second; if (m_current_song != plist.ls_song_list.end()) result = m_current_song->second.ss_embedded_song_directory; } return result; } /** * Obtains the current song MIDI control number, which is a number ranging * from 0 to 127 that indicates the control number that triggers the song. If * the current-list iterator is invalid, or the current-song iterator is * invalid, then (-1) is returned. */ int playlist::song_midi_number () const { int result = (-1); if (m_current_list != m_play_lists.end()) { play_list_t & plist = m_current_list->second; if (m_current_song != plist.ls_song_list.end()) result = m_current_song->second.ss_midi_number; } return result; } /** * Returns the name of the current song for display purposes. This name may * contain a hard-wired relative path, but always contains the base name of * the song (*.midi). */ std::string playlist::song_filename () const { std::string result; if (m_current_list != m_play_lists.end()) { play_list_t & plist = m_current_list->second; if (m_current_song != plist.ls_song_list.end()) { /* * This would return the full path for display, for those cases * where the directory is different from the play-list's * directory. However, that is too long to display in some cases. * We need to think this through some more. * * if (m_current_song->second.ss_embedded_song_directory) * result = song_filepath(m_current_song->second); * else */ result = m_current_song->second.ss_filename; } } return result; } void playlist::midi_base_directory (const std::string & basedir) { m_midi_base_directory = os_normalize_path(basedir); } /** * Gets the MIDI base directory, if non-empty. Gets the song directory, if * non-empty. Gets the base filename of the song, which might include a * relative path. Then it concatenates the MIDI base directory, * song-directory, and the song name, and returns it. Note the calls to * clean_path(), which ensure the paths end with a slash character. */ std::string playlist::song_filepath (const song_spec_t & sinfo) const { std::string songdir = clean_path(sinfo.ss_song_directory); std::string base = midi_base_directory(); std::string basedir = clean_path(base); std::string result = basedir + songdir + sinfo.ss_filename; return result; } /** * Gets the current song-specification from the current play-list, and, if * valid concatenates the song's base directory, specificed sub-directory and * file-name, which might include a relative path. * * \return * Returns the song's directory and file-name as a full path * specification. However, if there's an error, then an empty string is * returned. */ std::string playlist::song_filepath () const { std::string result; if (m_current_list != m_play_lists.end()) { play_list_t & plist = m_current_list->second; if (m_current_song != plist.ls_song_list.end()) result = song_filepath(m_current_song->second); } return result; } /** * Provides a one-line description containing the current play-list name and * song file. * * \return * Returns the play-list name and song file-name. If not in playlist * mode, or an item cannot be found, then an empty string is returned. */ std::string playlist::current_song () const { std::string result; if (loaded()) { if (m_current_list != m_play_lists.end()) { play_list_t & plist = m_current_list->second; if (m_current_song != plist.ls_song_list.end()) { int mnumber = m_current_song->second.ss_midi_number; result = plist.ls_list_name; result += ": "; result += int_to_string(mnumber); result += " "; result += m_current_song->second.ss_filename; } } } return result; } /** * Selects a song with the given index (i.e. its ordinal position in the * playlist or in a table in a GUI). * * \param index * The index of the song re 0. * * \return * Returns true if the current play-list and the current song are valid. * If true, then the m_current_song iterator points to the current song. */ bool playlist::select_song (int index) { bool result = false; if (m_current_list != m_play_lists.end()) { int count = 0; play_list_t & plist = m_current_list->second; song_list & slist = plist.ls_song_list; for (auto sci = slist.begin(); sci != slist.end(); ++sci, ++count) { if (count == index) { if (m_show_on_stdout) show_song(sci->second); m_current_song = sci; result = true; break; } } } return result; } /** * We want to provide the user with an unused song/control number. The user * can scroll to the bottom of the list in the user interface, but this * is annoying and easy to forget. We look at the last entry, and increment * the key value by one, if possible. * * \return * Returns the next number after the last one. If there's none * available, then (-1) is returned. */ int playlist::next_available_song_number () const { int result = (-1); if (m_current_list != m_play_lists.end()) { play_list_t & plist = m_current_list->second; song_list & slist = plist.ls_song_list; auto last = slist.rbegin(); if (last != slist.rend()) { int controlnumber = last->first; if (controlnumber < 127) result = controlnumber + 1; } } return result; } /** * Selects a song with the given MIDI control value (the key of the map). * * \param index * The MIDI control value. Generally should be restricted to the * range of 0 to 127, to be suitable for MIDI control. * * \return * Returns true if the current play-list and the current song are valid. * If true, then the m_current_song iterator points to the current song. */ bool playlist::select_song_by_midi (int ctrl) { bool result = false; if (m_current_list != m_play_lists.end()) { int count = 0; play_list_t & plist = m_current_list->second; song_list & slist = plist.ls_song_list; for (auto sci = slist.begin(); sci != slist.end(); ++sci, ++count) { int midinumber = sci->second.ss_midi_number; if (midinumber == ctrl) { if (m_show_on_stdout) show_song(sci->second); m_current_song = sci; result = true; } } } return result; } /** * Moves to the next song in the current playlist, wrapping around to the * beginning. * * \return * Returns true if the next song was selected. */ bool playlist::next_song () { bool result = false; if (m_current_list != m_play_lists.end()) { play_list_t & plist = m_current_list->second; ++m_current_song; if (m_current_song == plist.ls_song_list.end()) m_current_song = plist.ls_song_list.begin(); result = m_current_song != plist.ls_song_list.end(); if (result) { const std::string & fname = m_current_song->second.ss_filename; result = ! is_empty_string(fname); } if (result && m_show_on_stdout) show_song(m_current_song->second); } return result; } bool playlist::previous_song () { bool result = false; if (m_current_list != m_play_lists.end()) { play_list_t & plist = m_current_list->second; if (m_current_song == plist.ls_song_list.begin()) m_current_song = std::prev(plist.ls_song_list.end()); else --m_current_song; result = m_current_song != plist.ls_song_list.end(); if (result) { const std::string & fname = m_current_song->second.ss_filename; result = ! is_empty_string(fname); } if (result && m_show_on_stdout) show_song(m_current_song->second); } return result; } /** * Adds a song to the current playlist, if available. Calls the add_song() * overload taking the current song-list and the provided song-specification. * * \param sspec * Provides the infromation about the song to be added. * * \return * Returns true if the song was added. */ bool playlist::add_song (song_spec_t & sspec) { bool result = m_current_list != m_play_lists.end(); if (result) { play_list_t & plist = m_current_list->second; result = add_song(plist, sspec); /* ultimately reorders the list */ } return result; } /** * Adds the given song to the given song-list. * * \param slist * Provides the song-list to hold the new song. * * \param sspec * Provides the infromation about the song to be added. * * \return * Returns true if the song was added. That is, if the size of the * song-list increased by 1. */ bool playlist::add_song (song_list & slist, song_spec_t & sspec) { bool result = false; int count = int(slist.size()); int songnumber = sspec.ss_midi_number; auto s = std::make_pair(songnumber, sspec); /* std::pair */ slist.insert(s); result = int(slist.size()) == count + 1; if (result) reorder_song_list(slist); return result; } /** * Adds the given song to the song-list of the given play-list. * * \param plist * Provides the playlist whose song-list is to be updated. * * \param sspec * Provides the infromation about the song to be added. * * \return * Returns true if the song was added. That is, the return value of the * song-list, song-specification add_song() overload. */ bool playlist::add_song (play_list_t & plist, song_spec_t & sspec) { std::string listdir = plist.ls_file_directory; if (! listdir.empty()) { std::string songdir = sspec.ss_song_directory; if (songdir.empty()) sspec.ss_embedded_song_directory = false; else sspec.ss_embedded_song_directory = songdir != listdir; } song_list & sl = plist.ls_song_list; bool result = add_song(sl, sspec); /* calls reorder_song_list() */ if (result) ++plist.ls_song_count; return result; } /** * An overloaded function to encapsulate adding a song and make the * callers simpler. This function is intended for use by a playlist editor. * It supports the replacement of existing songs. * * \param index * Provides the location of the active item in the table. The actual * stored value may change after reordering. */ bool playlist::add_song ( int index, int midinumber, const std::string & name, const std::string & directory ) { bool result = ctrl_is_valid(midinumber); if (result) result = m_current_list != m_play_lists.end(); if (result) { play_list_t & plist = m_current_list->second; song_list & slist = plist.ls_song_list; song_spec_t sspec; /* copied upon insertion */ if (do_ctrl_lookup(midinumber)) /* handle -1 via lookup */ { int indexmax; last_song_indices(slist, indexmax, midinumber); if (do_ctrl_lookup(index)) /* do -1 via lookup */ index = indexmax; } sspec.ss_index = index; sspec.ss_midi_number = midinumber; sspec.ss_song_directory = directory; sspec.ss_embedded_song_directory = false; /* adjusted later */ sspec.ss_filename = name; /* * Song list is empty at first. We need to increment the song count * for the current playlist, by calling the playlist overload of * add_song(). */ result = add_song(plist, sspec); /* add song, reorder list */ if (! result) { if (remove_song(index)) result = add_song(sspec); /* add song, reorder list */ } } return result; } bool playlist::add_song (const std::string & fullpath) { bool result = ! fullpath.empty(); if (result) { play_list_t & plist = m_current_list->second; song_list & slist = plist.ls_song_list; int index = (-1); // how to get next available index? int midinumber = (-1); std::string basename; std::string directory; (void) filename_split(fullpath, directory, basename); last_song_indices(slist, index, midinumber); if (directory == plist.ls_file_directory) { result = add_song(index, midinumber, basename, directory); } else { std::string dir; result = add_song(index, midinumber, fullpath, dir); } } return result; } bool playlist::modify_song ( int index, int midinumber, const std::string & name, const std::string & directory ) { bool result = ctrl_is_valid(midinumber); if (result) result = m_current_list != m_play_lists.end(); if (result) { play_list_t & plist = m_current_list->second; song_list & slist = plist.ls_song_list; if (m_current_song != slist.end()) { /* * We must make a copy of the original, which gets deleted. */ song_spec_t sspec = m_current_song->second; sspec.ss_index = index; sspec.ss_midi_number = midinumber; sspec.ss_song_directory = directory; sspec.ss_filename = name; if (remove_song(index)) result = add_song(sspec); /* add song, reorder list */ } } return result; } /** * Looks up the largest index and MIDI control number, and returns one higher * for each. */ void playlist::last_song_indices (song_list & slist, int & index, int & midinumber) { int indexmax = (-1); int midimax = (-1); for (auto sci = slist.begin(); sci != slist.end(); ++sci) { const song_spec_t & s = sci->second; if (s.ss_midi_number > midimax) midimax = s.ss_midi_number; if (s.ss_index > indexmax) indexmax = s.ss_index; } midinumber = midimax == (-1) ? 0 : midimax + 1 ; index = indexmax == (-1) ? 0 : indexmax + 1 ; } /** * This function removes a song from the current playlist at the given index. * The index is an ordinal, not a key, therefore we have to iterate through * the whole list until we encounter the desired index. Useful for removing * the selected song in a table. * * This function works by iterating to the index'th element in the song-list * and deleting it. * * \param index * The ordinal value (not a key) of the desired table row. * * \return * Returns true if the desired song was found and removed. */ bool playlist::remove_song (int index) { bool result = false; if (m_current_list != m_play_lists.end()) { int count = 0; play_list_t & plist = m_current_list->second; song_list & slist = plist.ls_song_list; for (auto sci = slist.begin(); sci != slist.end(); ++sci, ++count) { if (count == index) { (void) slist.erase(sci); --plist.ls_song_count; result = true; break; } } if (result) reorder_song_list(slist); } return result; } /** * Moves through the song-list container in key (MIDI control number) order, * modifying the index value of each song in the list. */ void playlist::reorder_song_list (song_list & sl) { int index = 0; for (auto sci = sl.begin(); sci != sl.end(); ++sci, ++index) { song_spec_t & s = sci->second; s.ss_index = index; } } /** * Shows a summary of a playlist. * * \param pl * The playlist structure to show. */ void playlist::show_list (const play_list_t & pl) const { #if defined USE_OLD_CODE std::cout << " Playlist MIDI #" << pl.ls_midi_number << ", slot " << pl.ls_index << ": '" << pl.ls_list_name << "'" << std::endl << " " << pl.ls_file_directory << " " << pl.ls_song_count << " songs" << std::endl ; #else char temp[80]; (void) snprintf ( temp, sizeof temp, "Playlist MIDI #%d, slot %d: '%s'", int(pl.ls_midi_number), int(pl.ls_index), pl.ls_list_name.c_str() ); info_message(temp); (void) snprintf ( temp, sizeof temp, "%s, %d songs", pl.ls_file_directory.c_str(), int(pl.ls_song_count) ); info_message(temp); #endif } /** * Shows a summary of a song. The directory of the song is currently not * shown because it makes the summary more difficult to read in the CLI * output. * * \param s * The song-specification structure to show. */ void playlist::show_song (const song_spec_t & s) const { #if defined USE_OLD_CODE std::cout << " Song MIDI #" << s.ss_midi_number << ", slot " << s.ss_index << ": " /* << s.ss_song_directory */ << s.ss_filename << std::endl ; #else char temp[80]; (void) snprintf ( temp, sizeof temp, "Song MIDI #%d, slot %d: '%s'", int(s.ss_midi_number), int(s.ss_index), s.ss_filename.c_str() ); info_message(temp); #endif } /** * Performs a simple dump of the playlists, mostly for troubleshooting. */ void playlist::show () const { if (m_play_lists.empty()) { printf("No items in playist.\n"); } else { for (auto pci = m_play_lists.cbegin(); pci != m_play_lists.cend(); ++pci) { const play_list_t & pl = pci->second; show_list(pl); const song_list & sl = pl.ls_song_list; for (auto sci = sl.cbegin(); sci != sl.cend(); ++sci) { const song_spec_t & s = sci->second; show_song(s); } } } } /** * A function for running tests of the play-list handling. Normally * not needed. */ void playlist::test () { show(); show_list(m_current_list->second); show_song(m_current_song->second); for (int i = 0; i < 8; ++i) { if (next_song()) { std::cout << "Next song: "; show_song(m_current_song->second); } else break; } for (int i = 0; i < 8; ++i) { if (previous_song()) { std::cout << "Prev song: "; show_song(m_current_song->second); } else break; } for (int i = 0; i < 8; ++i) { if (next_list()) { std::cout << "Next list: "; show_list(m_current_list->second); } else break; } for (int i = 0; i < 8; ++i) { if (previous_list()) { std::cout << "Prev list: "; show_list(m_current_list->second); } else break; } /* * reset_list(); * write(); */ } } // namespace seq66 /* * playlist.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/portslist.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file portslist.cpp * * This module defines some of the more complex functions of the portslist. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-12-10 * \updates 2025-08-18 * \license GNU GPLv2 or above * * The listbase provides common code for the clockslist and inputslist * classes. */ #include /* std::cout, etc. */ #include /* std::invalid_argument */ #include "cfg/settings.hpp" /* seq66::rc() accessor */ #include "play/portslist.hpp" /* seq66::portslist class */ #include "util/strfunctions.hpp" /* seq66::strncompare() */ namespace seq66 { /** * Looks for the port name in the short-name list. We are interested in * seeing if it is a generic name such as "midi in". * * \param portname * The name to be checked. This is the name after the colon in a * "client:port" pair. * * \return * Returns true if the port-name is found in the short-name list, or is * empty. This is a signal to get the nick-name from the client name and * the portname. */ static bool detect_short_name (const std::string & portname) { static const std::string s_short_names [] = { "midi_", "midi ", "in", "out", "input", "output", "" /* empty string is a terminator */ }; bool result = portname.empty(); if (! result) { for (int i = 0; /* forever */; ++i) { std::string compared = s_short_names[i]; if (compared.empty()) { break; /* there is no match */ } else { result = strncompare(compared, portname); if (result) break; /* a match was found */ } } } return result; } /* * The simple destructor defined in the header file. A few functions * included here for better debugging. */ portslist::portslist (bool pmflag) : m_master_io (), m_is_active (false), m_is_port_map (pmflag) { // Nothing to do } void portslist::activate (status s) { m_is_active = s == status::on; if (s == status::cleared) clear(); } bool portslist::add ( int buss, bool available, int status, /* covers bool or e_clock */ const std::string & name, const std::string & nickname, const std::string & alias ) { bool result = buss >= 0 && ! name.empty(); if (result) { io ioitem; int client, port; if (extract_port_pair(name, client, port)) { ioitem.io_client_number = client; ioitem.io_port_number = port; } else { ioitem.io_client_number = (-1); ioitem.io_port_number = (-1); } ioitem.io_available = available; ioitem.io_enabled = status > 0; ioitem.out_clock = int_to_clock(status); ioitem.io_name = name; ioitem.io_alias = alias; result = add(buss, ioitem, nickname); } return result; } bool portslist::add ( int buss, io & ioitem, const std::string & nickname ) { bool result = buss >= 0; if (result) { if (nickname.empty()) { std::string nick = extract_nickname(ioitem.io_name); ioitem.io_nick_name = nick; } else ioitem.io_nick_name = nickname; auto p = std::make_pair(bussbyte(buss), ioitem); m_master_io.insert(p); // later, check the insertion } return result; } /** * Added for clarity, convenience, and, last, but not least, cohesion. * The issue is that this can mess up the clock type, so we leave that alone * and rely on the boolean, which we should have been doing all along. * * \param portio * The iterator to the io structure for the port. * * \param input * The desired enabled status of the port. * * \return * Returns true if the buss number lookup succeeded. */ bool portslist::set_enabled (bussbyte bus, bool enabled) { auto it = m_master_io.find(bus); bool result = it != m_master_io.end(); if (result) { it->second.io_available = true; it->second.io_enabled = enabled; } return result; } bool portslist::is_available (bussbyte bus) const { bool result = false; auto it = m_master_io.find(bus); if (it != m_master_io.end()) result = it->second.io_available; return result; } /** * New rule: whether input or output, an input value of "disabled" marks the * port as missing or otherwise unusable. This is enforced in the child * classes' set() functions. The old check has some issues, in retrospect: * * result = it->second.out_clock == e_clock::disabled; */ bool portslist::is_enabled (bussbyte bus) const { bool result = false; auto it = m_master_io.find(bus); if (it != m_master_io.end()) result = it->second.io_enabled; return result; } void portslist::set_name (bussbyte bus, const std::string & name) { auto it = m_master_io.find(bus); if (it != m_master_io.end()) { std::string nick = extract_nickname(name); it->second.io_name = name; it->second.io_nick_name = nick; } } #if defined USE_SET_NICK_NAME void portslist::set_nick_name (bussbyte bus, const std::string & name) { auto it = m_master_io.find(bus); if (it != m_master_io.end()) it->second.io_nick_name = name; } #endif void portslist::set_alias (bussbyte bus, const std::string & alias) { auto it = m_master_io.find(bus); if (it != m_master_io.end()) it->second.io_alias = alias; } static std::string buss_string (const std::string & name, bussbyte bus) { std::string result; if (! name.empty()) { result = "[" + std::to_string(int(bus)) + "] " + name; } return result; } std::string portslist::get_name (bussbyte bus) const { static std::string s_dummy; auto it = m_master_io.find(bus); std::string result = it != m_master_io.end() ? it->second.io_name : s_dummy ; return result; } std::string portslist::get_nick_name (bussbyte bus, portname style) const { static std::string s_dummy; bool addnumber = style != portname::brief; auto it = m_master_io.find(bus); std::string result = it != m_master_io.end() ? it->second.io_nick_name : s_dummy ; if (addnumber) result = buss_string(result, bus); return result; } std::string portslist::get_alias (bussbyte bus, portname style) const { static std::string s_dummy; bool addnumber = style != portname::brief; auto it = m_master_io.find(bus); std::string result = it != m_master_io.end() ? it->second.io_alias : s_dummy ; if (addnumber) result = buss_string(result, bus); return result; } std::string portslist::get_pair_name (bussbyte bus) const { std::string result; std::string name = get_name(bus); std::string nick = get_nick_name(bus); int client, port; bool ok = extract_port_pair(name, client, port); /* side-effects */ if (ok) { std::string pairdigits = std::to_string(client); pairdigits += ":"; pairdigits += std::to_string(port); result = pairdigits + " " + nick; } else result = name; return result; } std::string portslist::get_display_name (bussbyte bus, portname style) const { std::string result; switch (style) { case portname::brief: result = get_nick_name(bus, style); break; case portname::pair: result = get_pair_name(bus); break; case portname::full: result = get_name(bus); break; default: break; } return result; } static int count_colons (const std::string & name) { int result = 0; for (std::string::size_type cpos = 0; ; ++cpos) { cpos = name.find_first_of(":", cpos + 1); if (cpos != std::string::npos) ++result; else break; } return result; } /** * The nick-name of a port is roughly all the text following the last colon * in the display-name [see midibase::display_name()]. It seems to be the * same text whether the port name comes from ALSA or from a2jmidid when * running JACK. We don't have any MIDI hardware that JACK detects without * a2jmidid. * * QSynth has a name like the following, which breaks the algorithm and makes * the space position far outside the bounds of the string. In that case, we * punt and get the whole string. Also see extract_port_names() in the * calculations module. Another issue is that each incarnation of QSynth * produces a name with a different port number. * \verbatim [6] 130:0 FLUID Synth (125507):Synth input port (125507:0) \endverbatim * * Other cases to handle: * \verbatim "[3] 36:0 Launchpad Mini MIDI 1" a2j:Midi Through [14] (playback): Midi Through Port-0 \endverbatim * */ std::string portslist::extract_nickname (const std::string & name) const { std::string result; int colons = count_colons(name); if (colons > 2) { if (rc().with_jack_midi()) { auto cpos = name.find_last_of(":"); ++cpos; if (name[cpos] == ' ') cpos = name.find_first_not_of(" ", cpos); result = name.substr(cpos); } else { auto cpos = name.find_first_of(":"); auto spos = name.find_first_of(" ", cpos); if (spos != std::string::npos) { ++spos; cpos = name.find_first_of(":", cpos + 1); result = name.substr(spos, cpos - spos); } } } else { auto cpos = name.find_last_of(":"); if (cpos != std::string::npos) { ++cpos; if (std::isdigit(name[cpos])) { cpos = name.find_first_of(" ", cpos); if (cpos != std::string::npos) ++cpos; } else if (std::isspace(name[cpos])) ++cpos; if (cpos == std::string::npos) cpos = 0; result = name.substr(cpos); } } if (detect_short_name(result)) { std::string clientname, portname; bool extracted = extract_port_names(name, clientname, portname); if (extracted) result = clientname + ":" + portname; if (result == name) result = simplify(result); /* can we call only this?? */ } else { auto ppos = result.find_first_of("("); /* happens with fluidsynth */ if (ppos != std::string::npos && ppos > 1) { --ppos; if (result[ppos] == ' ') --ppos; result = result.substr(0, ppos + 1); } } if (result.empty()) result = name; return result; } bool portslist::extract_port_pair ( const std::string & name, int & client, int & port ) const { int colons = count_colons(name); bool result = colons >= 1; /* was 2, too much! */ if (result) { tokenization tokens = tokenize(name); result = tokens.size() >= 2; if (result) result = string_to_int_pair(tokens[1], client, port, ":"); } return result; } int portslist::available_count () const { int result = 0; for (const auto & iopair : m_master_io) { if (iopair.second.io_available) ++result; } return result; } bussbyte portslist::bus_from_name (const std::string & nick) const { bussbyte result = null_buss(); for (const auto & iopair : m_master_io) { if (nick == iopair.second.io_name) { result = iopair.first; break; } } return result; } /** * This function is used to get the buss number from the main clockslist or * main inputslist, using its nick-name. * * \param nick * Provides the nick-name to be looked up. This name is obtained from * the internal clockslist or (pending) inputslist by lookup given a * nominal buss number. * * \return * Returns the actual buss number that will be used for I/O. */ bussbyte portslist::bus_from_nick_name (const std::string & nick) const { bussbyte result = null_buss(); for (const auto & iopair : m_master_io) { if (nick == iopair.second.io_nick_name) { result = iopair.first; break; } } return result; } bussbyte portslist::bus_from_alias (const std::string & alias) const { bussbyte result = null_buss(); for (const auto & iopair : m_master_io) { if (alias == iopair.second.io_alias) { result = iopair.first; break; } } return result; } /** * Looks up the nick-name, which should be a string version of the nominal * buss number. Returns the port name (short name) if found in the list. * This function should be used only on the internal clockslist [returned by * output_port_map() in the clockslist module] or (pending) the internal * inputslist. Only these lists stored the buss number as a string. It is a * linear lookup, but the lists are short, usually a half-dozen elements. * * \param nominalbuss * Provides the external, nominal buss number which is often stored in a * pattern to indicate what output port is to be used. */ std::string portslist::port_name_from_bus (bussbyte nominalbuss) const { std::string result; if (is_null_buss(nominalbuss)) { result = "0xFF"; } else { std::string nick = std::to_string(int(nominalbuss)); for (const auto & iopair : m_master_io) { if (nick == iopair.second.io_nick_name) { result = iopair.second.io_name; break; } } } return result; } /** * Sets the enabled/disabled status in the destination port-list based on the * statuses set in the port-map. The port-map is what the user will see * in the MIDI Clocks and Inputs tabs, and that is where the user will * enable/disable the ports, if port-mapping is enabled (recommended). * * Used to prepare the lists for showing the port-map along with the status * of the disabled ports. Each port in the port-map is looked up in the * given source list. If not found, it is unavailabl3 and hence disabled. * * It is assumed that the "this" here is the portslist object * returned by the input_port_map() or output_port_map() functions. Recall * that its full-name is the nick-name of an actual port, and its nick-name * is a string version of the port-number. Too tricky... unless it works. * :-) * * Call this function when the port-map is enabled. * * \param destination * The destination for the statuses to be applied. Ultimately, the * destinations are the clocks and inputs from the performer, provided by * mastermidibase :: get_port_statuses(), which gets the system ports. * * \return * Returns true if all the port-map entries mapped to a source port item. * If false is returned, the port-map may need to be reconstructed. */ void portslist::match_system_to_map (portslist & destination) const { if (is_port_map()) { for (const auto & iopair : m_master_io) { const io & item = iopair.second; const std::string & portname = item.io_name; /* nick-name */ io & destinitem = destination.io_block(portname); if (valid(destinitem)) { destinitem.io_available = true; destinitem.io_enabled = item.io_enabled; destinitem.out_clock = item.out_clock; } else { io & ncitem = const_cast(item); ncitem.io_available = false; ncitem.io_enabled = false; ncitem.out_clock = e_clock::unavailable; } } } } /** * The opposite of match_system_to_map(), this function takes the source * portslists and makes the statuses of the map match the system (the * mastermidibus). * * Call this function when the port-map is disabled. */ void portslist::match_map_to_system (const portslist & source) { if (is_port_map()) { for (auto & iopair : m_master_io) { io & destinitem = iopair.second; const std::string & portname = destinitem.io_name; /* nick-name */ const io & srcitem = source.const_io_block(portname); if (valid(srcitem)) { destinitem.io_available = srcitem.io_available; destinitem.io_enabled = srcitem.io_enabled; destinitem.out_clock = srcitem.out_clock; } } } } /** * Given a port-name (which might be a nick-name), this function checks if * the master (internal) I/O item's nick-name or alias matches the given * nick-name. If it matches, then that internal item is returned. * * Issue: * * The [midi-clock] name and the [midi-clock-map] nick-name might be like: * * - "FLUID Synth (3088150):Synth input port (3088150:0)" * - "FLUID Synth (1070760)" * */ const portslist::io & portslist::const_io_block (const std::string & nickname) const { static bool s_needs_initing = true; static io s_dummy_io; if (s_needs_initing) { s_needs_initing = false; s_dummy_io.io_available = false; s_dummy_io.io_enabled = false; s_dummy_io.out_clock = e_clock::disabled; } for (const auto & iopair : m_master_io) { const io & item = iopair.second; const std::string & comparison = item.io_alias.empty() ? item.io_nick_name : item.io_alias ; /* TODO */ bool matches = contains(comparison, nickname); if (matches) return item; /* iopair.second */ } return s_dummy_io; } std::string portslist::e_clock_to_string (e_clock e) const { std::string result; switch (e) { case e_clock::disabled: result = "Disabled"; break; case e_clock::none: result = "Off"; break; case e_clock::pos: result = "Pos"; break; case e_clock::mod: result = "Mod"; break; default: result = "Unknown"; break; } return result; } /** * This function is used by input_port_map_list() and output_port_map_list() * in rcfile to dump the maps into the 'rc' file. * * Compare this function to io_list_lines(). This one does not emit an alias, * as port-maps don't use them directly. * * \param isclock * Unfortunately, we need to determine the derived object (clockslist vs * inputslist) with this freakin' flag. */ std::string portslist::port_map_list (bool isclock) const { std::string result; if (! empty()) { for (const auto & iopair : m_master_io) { const io & item = iopair.second; std::string pname = item.io_name; int pnumber = string_to_int(item.io_nick_name); int pstatus; if (isclock) { pstatus = clock_to_int(item.out_clock); } else { if (! item.io_available) pstatus = clock_to_int(e_clock::unavailable); else pstatus = item.io_enabled ? 1 : 0 ; } std::string tmp = io_line(pnumber, pstatus, pname); result += tmp; } } return result; } /** * Static function to parse port lines in a unified fashion. */ bool portslist::parse_port_line ( const std::string & line, int & portnumber, int & portstatus, std::string & portname ) { tokenization tokens = tokenize_quoted(line); bool result = tokens.size() >= 3; /* buss, status, & quoted name */ if (result) { int pnumber = string_to_int(tokens[0]); int pstatus = string_to_int(tokens[1], (-1)); std::string pname = tokens[2]; /* next_quoted_string(line) */ portnumber = pnumber; portstatus = pstatus; portname = pname; } return result; } /** * Static function to test an io object for validity. To be valid it must * have a non-empty io_name field. */ bool portslist::valid (const io & item) { return ! item.io_name.empty(); } /** * This virtual base-class function writes a port line (for the 'rc' file) * from a clockslist or inputslist. The line consists of two integers, * followed by the quoted port name, and optionally followed by the alias, * shown as a comment. */ std::string portslist::io_line ( int portnumber, int status, const std::string & portname, const std::string & portalias ) const { std::string name = add_quotes(portname); char tmp[128]; if (portalias.empty()) { snprintf ( tmp, sizeof tmp, "%2d %2d %s\n", portnumber, status, name.c_str() ); } else { snprintf ( tmp, sizeof tmp, "%2d %2d %-40s # '%s'\n", portnumber, status, name.c_str(), portalias.c_str() ); } return std::string(tmp); } std::string portslist::to_string (const std::string & tag) const { std::string result = "I/O List: '" + tag + "'\n"; int count = 0; for (const auto & iopair : m_master_io) { const io & item = iopair.second; std::string temp = std::to_string(count) + ". "; temp += item.io_enabled ? "Enabled; " : "Disabled; " ; if (! item.io_available) temp += "Unavailable "; temp += "Clock = " + e_clock_to_string(item.out_clock); temp += "\n "; temp += "Name: " + item.io_name + "\n "; temp += "Nickname: " + item.io_nick_name + "\n "; temp += "Alias: " + item.io_alias + "\n"; result += temp; ++count; } return result; } void portslist::show (const std::string & tag) const { std::string listdump = to_string(tag); std::cout << listdump << std::endl; } } // namespace seq66 /* * portslist.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/screenset.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file screenset.cpp * * This module declares a two dimensional vector class solely to hold the * mute status of a number of sequences in a set. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-02-12 * \updates 2025-06-25 * \license GNU GPLv2 or above * * Implements the screenset class. The screenset class represent all of the * sequences in a screen-set. A screen has a size given by the concept of * rows and columns, mirroring what the user sees in the Sequencer66 user * interface. */ #include /* std::find_if() */ #include /* std::setw() manipulator */ #include /* std::cout */ #include /* std::ostringstream */ #include "cfg/settings.hpp" /* seq66::usr() and rc() */ #include "play/screenset.hpp" /* seq66::screenset class */ #include "util/strfunctions.hpp" /* seq66::bool_to_string() */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /* * ------------------------------------------------------------------------- * screenset * ------------------------------------------------------------------------- */ /** * Principal constructor with optional parameters. * * \param setnum * Provides the set number for this screenset. This number ranges from 0 * to whatever the maximum set size is (normally this is 32 - 1.) * * \param rows * Provides the number of virtual rows in the set. * * \param columns * Provides the number of virtual columns in the set. */ screenset::screenset (screenset::number setnum, int rows, int columns) : m_rows (rows), m_columns (columns), m_swap_coordinates (usr().swap_coordinates()), m_set_size (rows * columns), m_container (), m_set_number (setnum), m_set_offset (m_set_number * m_set_size), m_set_maximum (m_set_offset + m_set_size), m_set_name ("empty"), /* usable() ? "New" : "Empty" */ m_is_playscreen (false), m_sequence_high (0) { clear(); } /** * If we move (swap) sets in the qsetmaster set-list, we have a lot of work to * do. The screensets are first moved, so that they have new set numbers (and * a new set-offset and set-maximum number). Then, we have to renumber all * existing sequences in both sets to reflect the new set number. */ void screenset::change_set_number (screenset::number setno) { int seq_offset = setno * m_set_size; m_set_number = setno; m_set_offset = seq_offset; m_set_maximum = m_set_offset + m_set_size; for (auto & s : m_container) /* renumber all the sequences */ { seq::number oldno = s.seq_number(); if (oldno != seq::unassigned()) /* inactive sequence? */ s.change_seq_number(seq_offset); ++seq_offset; } } void screenset::clear () { seq emptyseq; m_container.clear(); for (int s = 0; s < m_set_size; ++s) m_container.push_back(emptyseq); } void screenset::initialize (int rows, int columns) { m_rows = rows; m_columns = columns; m_swap_coordinates = usr().swap_coordinates(); m_set_size = rows * columns; m_set_maximum = m_set_size; m_is_playscreen = false; clear(); } /** * Adds a sequence (and its seq maintenance object) to the container. Recall * that the container is a vector of constant size greater than 0. * * \param s * Provides the sequencer pointer, assumed to be valid. * * \param [inout] seqno * The desired sequence number, which is a linear value ranging from 0 to * a number determined by the active set size multiplied by the number of * sets. The actual value is returned if the function result is true. * * \return * Returns true if the sequence number didn't already exist and the * sequence pointer was able to be added and activated. */ bool screenset::add (sequence * s, seq::number & seqno) { bool result = false; if (not_nullptr(s)) { for (seq::number i = seqno - offset(); i < m_set_maximum; ++i) { seq sseq = seqinfo(i); /* get seq info in the set */ if (! sseq.active()) /* no seq already in slot? */ { seqno = i + offset(); /* change to unused seqno */ result = sseq.activate(s, seqno); if (result) { m_container[i] = sseq; break; } } } } return result; } /** * Removes the given sequence, by number. The seq object is replaced with a * default/inactive one. This action will not be done if the sequence * currently is being edited. * * \param seqno * Provides the number of the sequence to be removed. * * \return * Returns true if the sequence existed, was not being edited, * and was erased. */ bool screenset::remove (seq::number seqno) { bool result = false; seq::pointer sp = seqinfo(seqno).loop(); if (sp && ! sp->seq_in_edit()) { seq newseq; /* non-functional pattern */ sp->set_armed(false); /* turns off all notes as well */ m_container[seqno - offset()] = newseq; result = true; } return result; } /** * Tests to see if the screenset is active. By "active", we mean that the * screen-set has at least one active pattern. * * \return * Returns true if the screenset has an active pattern. */ bool screenset::active () const { for (auto & s : m_container) { if (s.active()) return true; } return false; } /** * Counts the number of active patterns in this screenset. Also updates * m_sequence_high, in the same way that setmapper::add_sequence() does. */ int screenset::active_count () const { int result = 0; seq::number seqno = m_set_offset; m_sequence_high = 0; /* a mutable member */ for (auto & s : m_container) { if (s.active()) { ++result; if (seqno > m_sequence_high) m_sequence_high = seqno; } ++seqno; } ++m_sequence_high; /* one more than high index */ return result; } /** * Obtains the number of the first active sequence found in the screenset. * * \return * Returns the first sequence number, or a bad value if none exists. */ seq::number screenset::first_seq () const { seq::number result = seq::unassigned(); for (auto & s : m_container) { if (s.active()) { result = s.seq_number(); break; } } return result; } seq::number screenset::grid_to_index (int row, int column) const { if (row < 0 || row >= m_rows || column < 0 || column >= m_columns) return seq::unassigned(); else if (swap_coordinates()) return column + m_columns * row; else return row + m_rows * column; } seq::number screenset::grid_to_seq (int row, int column) const { seq::number result = grid_to_index(row, column); if (! seq::unassigned(result)) result += offset(); return result; } seq::number screenset::grid_to_seq (screenset::number setno, int row, int column) const { seq::number result = grid_to_index(row, column); if (! seq::unassigned(result)) result += setno * m_set_size; /* add the set offset */ return result; } /** * Converts a sequence number to a (row, column) pair according to the layout * shown below. * /verbatim 0 4 8 12 16 20 24 28 1 5 9 13 17 21 25 29 2 6 10 14 18 22 26 30 3 7 11 15 19 23 27 31 /endverbatim * * Note that this layout varies the row number faster than the column. The * calculation for this layout is * /verbatim (row = s % rows, column = [s / rows] % columns) /endverbatim * * where s is offset from 0, not from the set number. As an aside, to get * the more conventional layout, where the column number varies faster, use: * /verbatim (row = s / rows, column = s % columns) /endverbatim * */ bool screenset::seq_to_grid ( seq::number seqno, int & row, int & column, bool global ) const { seq::number index = seqno - offset(); bool result = global || (index >= 0 && index < m_set_size); if (result) result = index_to_grid(index, row, column); return result; } bool screenset::index_to_grid (seq::number index, int & row, int & column) const { index %= m_set_size; /* force it to be in range */ if (swap_coordinates()) { row = index / m_columns; column = index % m_columns; } else { row = index % m_rows; column = index / m_rows; } return true; } bool screenset::is_seq_in_edit (seq::number seqno) const { seq::pointer track = seqinfo(seqno).loop(); return track ? track->seq_in_edit() : false ; } bool screenset::any_in_edit () const { for (auto & s : m_container) { if (s.active() && s.loop()->seq_in_edit()) return true; } return false; } /** * Indicates if there is at least one armed sequence in this screen-set. */ bool screenset::armed () const { for (auto & s : m_container) { if (s.active() && s.loop()->armed()) return true; } return false; } bool screenset::needs_update () const { for (auto & s : m_container) { if (s.active() && s.loop()->is_dirty_main()) return true; } return false; } /** * Converts a sequence number from its raw form (o to 1023) to its range * within a set (e.g. 0 to 31). If the value is still outside this * range, it is clamped to that range. */ seq::number screenset::clamp (seq::number seqno) const { if (seqno >= offset()) seqno -= offset(); if (seqno < 0) return 0; else if (seqno >= m_set_size) return m_set_size - 1; return seqno; } /** * Updated to skip an soloed sequence to try to get around a weird error * where soloing only works with small numbers of patterns in a screenset or * when paused under the debugger. * * \param seqno * If not equal to the default, seq::unassigned(), this sequence is * set to be soloed, so it won't be turned off. */ void screenset::off_sequences (seq::number seqno) { bool solo = seqno != seq::unassigned(); for (auto & s : m_container) { if (s.active()) { bool disarm = ! solo || (s.seq_number() != seqno); if (disarm) s.loop()->set_armed(false); } } } void screenset::song_recording_start (midipulse tick, bool snap) { for (auto & s : m_container) { if (s.active()) s.loop()->song_recording_start(tick, snap); /* issue #44 */ } } void screenset::song_recording_stop (midipulse tick) { for (auto & s : m_container) { if (s.active()) s.loop()->song_recording_stop(tick); } } void screenset::clear_snapshot () { for (auto & s : m_container) s.clear_snapshot(); } void screenset::save_snapshot () { for (auto & s : m_container) s.save_snapshot(); } void screenset::restore_snapshot () { for (auto & s : m_container) s.restore_snapshot(); } /** * This function assumes that this set is already created with the proper set * number and pattern offsets. This is true when we first move to the next * set, which creates a screenset if it doesn't already exist. * For copy/paste of screensets, we cannot use operator =(), because that * makes the two sets have the same set number, etc. */ bool screenset::copy_patterns (const screenset & source) { bool result = source.active_count() > 0; if (result) { m_set_name = source.m_set_name; /* * Too goofy when moving a set up or down, since our method now * uses copy_patterns() for the prevention of segfaults. * * m_set_name += "'"; */ clear(); /* clear our sequences, init the container */ int srci = int(source.offset()); int destend = int(offset()) + set_size(); for (int desti = int(offset()); desti < destend; ++desti, ++srci) { seq::pointer s = source.loop(srci); if (s) { sequence * d = new (std::nothrow) sequence(); if (not_nullptr(d)) { d->partial_assign(*s, false); /* to clipboard, no mod */ add(d, desti); } } } } return result; } void screenset::set_last_ticks (midipulse tick) { for (auto & s : m_container) { if (s.active()) s.loop()->set_last_tick(tick); } } void screenset::armed_status (seq::number seqno, bool flag) { seq & s = seqinfo(seqno); if (s.active()) s.armed_status(flag); } /* * Replaces set_playing(). Should we also call set_song_mute()??? */ void screenset::armed (seq::number seqno, bool flag) { const seq::pointer track = seqinfo(seqno).loop(); if (track) track->set_armed(flag); } /** * Runs a slot-handler function for all of the slots in this set. Recall * that a slot is a place to "store" a sequence, and a slot can be empty. * All slots can be drawn, but empty slots are drawn differently in general. * * if (s.active()) // ca 2025-06-25 * * \param p * Provides a function with parameters of seq::pointer and seq::number. * * \param use_set_offset * If true, start the sequence counter from this set's seq::number * offset. This is the default. Otherwise, start from 0. In this case, * if the true sequence number is needed, the function must get it from * the sequence itself. */ bool screenset::exec_slot_function (slothandler p, bool use_set_offset) { bool result = false; seq::number sn = use_set_offset ? offset() : 0 ; for (auto & s : m_container) { result = p(s.loop(), sn++); /* note post-increment of sn */ if (! result) /* false only if serious */ { if (s.active()) /* ca 2025-06-25 */ break; else result = true; } } return result; } /** * Run one function on this set, and another function on each sequence in * this set. * * - sethandler: bool (screenset &, screenset::number) * - slothandler: bool (seq::pointer, seq::number) */ bool screenset::exec_set_function (sethandler s, slothandler p) { bool result = s(*this, 0); /* handle set, index not used */ if (result) result = exec_slot_function(p); /* handle set's slots/sequences */ return result; } bool screenset::any_modified_sequences () const { bool result = false; for (const auto & s : m_container) { if (s.active()) { if (s.loop()->modified()) { result = true; break; } } } return result; } void screenset::unmodify_all_sequences () { for (auto & s : m_container) { if (s.active()) s.loop()->unmodify(); } } /** * Sets one sequence (or all of them) to be dirty. * * \param seqno * Either a track number or seq::all() (the default value). */ void screenset::set_dirty (seq::number seqno) { if (seqno == seq::all()) { for (auto & s : m_container) { if (s.active()) s.loop()->set_dirty(); } } else { seq::pointer sp = find_by_number(seqno); if (sp) sp->set_dirty(); } } midipulse screenset::max_timestamp () const { midipulse result = 0; int seqno = 0; for (const auto & s : m_container) { if (s.active()) { if (s.loop()) { midipulse t = s.loop()->get_max_timestamp(); if (t > result) result = t; } else errprintf("max_timestamp(): nullptr seq %d\n", seqno); } ++seqno; } return result; } midipulse screenset::max_extent () const { midipulse result = 0; for (const auto & s : m_container) { if (s.active()) { midipulse t = s.loop()->get_length(); if (t > result) result = t; } } return result; } /* * ------------------------------------------------------------------------- * Triggers * ------------------------------------------------------------------------- */ /** * Counts the triggers in the screenset. */ int screenset::trigger_count () const { int result = 0; for (const auto & s : m_container) { if (s.active()) result += s.loop()->trigger_count(); } return result; } /** * Finds the maximum trigger in the screenset. */ midipulse screenset::max_trigger () const { midipulse result = 0; for (const auto & s : m_container) { if (s.active()) { midipulse t = s.loop()->get_max_trigger(); if (t > result) result = t; } } return result; } /** * \param seqno * Either a track number or seq::all() (the default value). */ bool screenset::move_triggers ( midipulse lefttick, midipulse distance, bool direction, seq::number seqno ) { bool result = false; if (seqno == seq::all()) { for (auto & s : m_container) { if (s.active()) /* guarantees a valid pointer */ { if (s.loop()->move_triggers(lefttick, distance, direction)) result = true; } } } else { seq::pointer sp = find_by_number(seqno); if (sp) { if (sp->move_triggers(lefttick, distance, direction)) result = true; } } return result; } void screenset::push_trigger_undo () { for (auto & s : m_container) { if (s.active()) s.loop()->push_trigger_undo(); } } void screenset::pop_trigger_undo () { for (auto & s : m_container) { if (s.active()) s.loop()->pop_trigger_undo(); } } void screenset::pop_trigger_redo () { for (auto & s : m_container) { if (s.active()) s.loop()->pop_trigger_redo(); } } /* * ------------------------------------------------------------------------- * More stuff * ------------------------------------------------------------------------- */ bool screenset::color (seq::number seqno, int c) { bool result = false; seq::pointer track = seqinfo(seqno).loop(); if (track) result = track->set_color(c); return result; } void screenset::set_seq_name (seq::number seqno, const std::string & name) { seq::pointer track = seqinfo(seqno).loop(); if (track) track->set_name(name); } bool screenset::name (const std::string & nm) { bool result = nm != m_set_name; m_set_name = nm; return result; } void screenset::arm (seq::number seqno) { const seq::pointer track = seqinfo(seqno).loop(); if (track) track->set_armed(true); } /** * Do not set song mute here. */ void screenset::mute (seq::number seqno) { const seq::pointer track = seqinfo(seqno).loop(); if (track) track->set_armed(false); } void screenset::all_notes_off () { for (auto & s : m_container) { if (s.active()) s.loop()->off_playing_notes(); } } /** * Looks up a seq::pointer by sequence number. The alternative is to clamp * the sequence number and do a bogus lookup. We don't want to deal with * try/catch blocks. * * \param seqno * Provides the desired sequence number. * * \return * Returns the sequence pointer, which should be checked using operator * bool() by the caller. */ seq::pointer screenset::find_by_number (seq::number seqno) { static seq::pointer s_dummy; auto seqit = std::find_if ( m_container.begin(), m_container.end(), [ & seqno ] (const seq & sn) { return sn.seq_number() == seqno; } ); return seqit != m_container.end() ? seqit->loop() : s_dummy ; } /** * Fills the performer's play-set. * * \param p * The play-set vector, owned by performer. * * \param * Indicates to clear the play-set first. Defaults to true. Set it to * false to append more play-set sequences. * * \return * Returns true if there were any active sequences found in the current * call of this function. */ bool screenset::fill_play_set (playset & p, bool clearit) { return p.fill(*this, clearit); } bool screenset::add_to_play_set (playset & p, seq::number seqno) { return p.add(*this, seqno); } seq::number screenset::play_seq (seq::number seqno) { seq::number result = seqno; if (result < set_size()) { if (offset() != seq::unassigned()) result = seqno + offset(); } return result; } /** * \param seqno * Either a track number or seq::all() (the default value). */ void screenset::copy_triggers ( midipulse lefttick, midipulse distance, seq::number seqno ) { if (seqno == seq::all()) { for (auto & s : m_container) { if (s.active()) /* guarantees a valid pointer */ s.loop()->copy_triggers(lefttick, distance); } } else { seq::pointer sp = find_by_number(seqno); if (sp) sp->copy_triggers(lefttick, distance); } } /** * Selects the set of triggers bounded by a low and high sequence number and * a low and high tick selection. If there is an inactive sequence in this * range, it is simply ignored. Also, will not cross a set boundary. The * setmapper makes sure this is the case before calling this function. * * \param seqlow * Provides the low track to be selected, the low sequence number in the * pattern range. * * \param seqhigh * Provides the high track to be selected. The high sequence number in * the pattern range. If not in the same set, nothing is done. We need * a way to report that. * * \param tick_start * Provides the low end of the box. * * \param tick_finish * Provides the high end of the box. */ void screenset::select_triggers_in_range ( seq::number seqlow, seq::number seqhigh, midipulse tick_start, midipulse tick_finish ) { for (seq::number s = seqlow; s <= seqhigh; ++s) { auto lambdafunc = [s] (const seq & sseq) -> bool { return sseq.seq_number() == s; }; auto seqit = std::find_if ( m_container.begin(), m_container.end(), lambdafunc ); if (seqit != m_container.end()) { if (seqit->loop()->unselect_triggers()) { for (long tick = tick_start; tick <= tick_finish; ++tick) seqit->loop()->select_trigger(tick); } } } } /** * * \param seqno * Either a track number or seq::all() (the default value). */ void screenset::unselect_triggers (seq::number seqno) { if (seqno == seq::all()) { for (auto & s : m_container) { if (s.active()) /* guarantees a valid pointer */ s.loop()->unselect_triggers(); } } else { seq::pointer sp = find_by_number(seqno); if (sp) sp->unselect_triggers(); } } #if defined SEQ66_USE_SCREENSET_RESET_SEQUENCES /* currently unused */ /** * For all active patterns/sequences, get its playing state, turn off the * playing notes, set playing to false, zero the markers, and, if not in * playback mode, restore the playing state. Note that these calls are * folded into one member function of the sequence class. * * Note that std::shared_ptr<> does not provide operator->*, therefore we * have to get the raw pointer and use that to access a sequence member * function. */ void screenset::reset_sequences (bool pause, sequence::playback mode) { void (sequence::* f) (bool) = pause ? &sequence::pause : &sequence::stop ; bool songmode = mode == sequence::playback::song; for (auto & s : m_container) { if (s.active()) /* guarantees a valid pointer */ { sequence * sp = s.loop().get(); (sp->*f)(songmode); } } } #endif void screenset::arm () { for (auto & s : m_container) { if (s.active()) /* guarantees a valid pointer */ { seq::pointer sp = s.loop(); sp->set_armed(true); } } } void screenset::mute () { for (auto & s : m_container) { if (s.active()) /* guarantees a valid pointer */ { seq::pointer sp = s.loop(); sp->set_armed(false); } } } void screenset::toggle (seq::number seqno) { if (seqno == seq::all()) { for (auto & s : m_container) { if (s.active()) /* guarantees valid pointer */ { seq::pointer sp = s.loop(); bool armed = sp->armed(); sp->set_armed(! armed); } } } else { const seq::pointer track = seqinfo(seqno).loop(); if (track) { bool armed = track->armed(); track->set_armed(! armed); } } } /** * This looks like a gprof hot-spot! */ void screenset::play (midipulse tick, sequence::playback mode, bool resumenoteons) { bool songmode = mode == sequence::playback::song; for (auto & s : m_container) { if (s.active()) s.loop()->play_queue(tick, songmode, resumenoteons); } } void screenset::toggle_song_mute (seq::number seqno) { if (seqno == seq::all()) { for (auto & s : m_container) { if (s.active()) /* guarantees a valid pointer */ s.loop()->toggle_song_mute(); } } else { const seq::pointer track = seqinfo(seqno).loop(); if (track) track->toggle_song_mute(); } } /** * Restores the armed statuses saved when learn_armed_statuses() saved the * current status, before toggling them. Remember that these statuses have * nothing to do with mute-groups! */ void screenset::apply_armed_statuses () { for (auto & s : m_container) { if (s.armed_status()) /* checks active() */ { seq::pointer sp = s.loop(); /* guaranteed valid */ sp->toggle_song_mute(); sp->toggle_playing(); } } } /** * Learns the current armed status, and also toggles them. Remember that these * statuses have nothing to do with mute-groups! * * \return * Returns true if an armed status was found. */ bool screenset::learn_armed_statuses () { bool result = false; for (auto & s : m_container) { seq & seqstatus = s; if (seqstatus.active()) /* guarantees a valid pointer */ { seq::pointer sp = seqstatus.loop(); bool armed = sp->armed(); seqstatus.armed_status(armed); if (armed) { result = true; sp->toggle_song_mute(); sp->toggle_playing(); } } } return result; } /** * * \param seqno * Either a track number or seq::all() (the default value). */ void screenset::apply_song_transpose (seq::number seqno) { if (seqno == seq::all()) { for (auto & s : m_container) { if (s.active()) /* guarantees a valid pointer */ s.loop()->apply_song_transpose(); } } else { seq::pointer sp = find_by_number(seqno); if (sp) sp->apply_song_transpose(); } } /** * * \param seqno * The sequence number to be affected. * * \param on * True if the sequence is to be turned on, false if it is to be turned * off. * * \param qinprogress * Indicates if queuing is in progress. This status is obtained from the * performer. */ void screenset::sequence_playing_change ( seq::number seqno, bool on, bool qinprogress ) { auto seqit = std::find_if /* also see find_by_number() */ ( m_container.begin(), m_container.end(), [ & seqno ] (const seq & sn) { return sn.seq_number() == seqno; } ); if (seqit != m_container.end()) { seqit->sequence_playing_change(on, qinprogress); } else { infoprintf("pattern %d is empty", seqno); } } /** * For all active patterns/sequences in the current (playing) screen-set, * this function gets the playing status and saves it in m_sequence_queued. * Inactive patterns get the value set to false. Used in saving the * screen-set state during the queued-replace (queued-solo) operation, which * occurs when the c_status_replace is performed while c_status_queue is * active. This information is used in unqueue(). * * \param repseq * Provides the number of the pattern for which the replace functionality * is invoked. This pattern will set to "playing" whether it is on or * off, so that it can stay active while toggling between "solo" and * "playing with the rest of the patterns". */ void screenset::save_queued (seq::number repseq) { for (auto & s : m_container) { if (s.active()) { seq::pointer sp = s.loop(); seq::number seqno = sp->seq_number(); bool on = sp->armed() || (seqno == repseq); s.queued(on); } } } /** * Does a toggle-queueing for all of the sequences in this screenset, * for all sequences that are on, and for the currently hot-keyed sequence. * This function supports the queued-replace feature, which is * activated when the keep-queue feature is turned on via its hot-key, and * then the replace hot-key is used on a pattern. When that happens, the * current sequence is queued to be toggled, and all unmuted sequences are * queued to toggle off at the same time. Thus, this is a kind of * queued-solo feature. * * This function assumes we have called save_queued() first, * so that the soloing can be exactly toggled. Only sequences that were * initially on should be toggled. * * \param hotseq * This number is that of the sequence/pattern whose hot-key was struck. * We don't want to toggle this one off, just on. */ void screenset::unqueue (seq::number hotseq) { for (auto & s : m_container) { if (s.active()) { seq::pointer sp = s.loop(); seq::number seqno = sp->seq_number(); if (seqno == hotseq) { if (! sp->armed()) sp->toggle_queued(); } else if (s.queued()) { sp->toggle_queued(); } } } } /** * Applies the bits as track-muting values. Note that not every sequence in a * set will be active, in general. Therefore we must check all seqs, and not * quit if a sequence is missing. * * Note that an unapply_bits() function is not needed here; we just follow the * bits given. * * \param bits * Provides the boolean container that is the source of mute statuses. * * \return * Returns true if the bits were able to be applied. A set of bits with a * different count than this screenset's sequence count would cause this. */ bool screenset::apply_bits (const midibooleans & bits) { bool result = count() == int(bits.size()); if (result) { int bit = 0; seq::number seqend = offset() + m_set_size; for (seq::number seqno = offset(); seqno != seqend; ++seqno, ++bit) { seq::pointer sp = find_by_number(seqno); if (sp) { bool armed = bits[bit]; sp->set_song_mute(! armed); /* calls set_armed() */ } } } return result; } /** * Copies the current bits status of the screenset's sequences into the given * boolean vector. The vector is cleared before adding in the new bits. * * \param [out] bits * Provides the destination for the sequence statuses. * * \return * Returns true if the bits were filled with statuses. */ bool screenset::learn_bits (midibooleans & bits) { bool result = count() > 0; if (result) { int bit = 0; bits.clear(); for (seq::number s = offset(); s != m_set_maximum; ++s, ++bit) { seq::pointer sp = find_by_number(s); if (sp) { bool armed = sp->armed(); bits.push_back(midibool(armed)); } else bits.push_back(midibool(false)); } } return result; } std::string screenset::to_string (bool showseqs, int limit) const { std::ostringstream result; int index = 0; result << "Set " << set_number() << " ('" << name() << "')" << std::endl; if (showseqs) { int counter = 0; for (auto & s : m_container) { result << s.to_string(index++); if (++counter == limit) break; } } return result.str(); } void screenset::show (bool showseqs) const { std::cout << to_string(showseqs); } /* * ------------------------------------------------------------------------- * playset * ------------------------------------------------------------------------- */ playset::playset () : m_screen_sets (), m_sequence_array () { // No code needed at this time } bool playset::set_found (screenset::number setno) const { const auto seqiterator = m_screen_sets.find(setno); return seqiterator != m_screen_sets.cend(); } bool playset::fill (const screenset & sset, bool clearit) { bool result = false; if (clearit) clear(); auto p = std::make_pair(sset.set_number(), &sset); auto r = m_screen_sets.insert(p); if (r.second) { for (auto & s : sset.seq_container()) { if (s.active()) { m_sequence_array.push_back(s.loop()); result = true; } } } return result; } /** * This function does not clear the containers each time. It assumes the * sequence has been installed via the setmapper, so that the necessary set * already exists, and is provided here. If the set already exists, it won't * be inserted, which is not an error. */ bool playset::add (const screenset & sset, seq::number seqno) { const seq & s = sset.seqinfo(seqno); bool result = s.active(); if (result) m_sequence_array.push_back(s.loop()); return result; } bool playset::add (seq::pointer s) { bool result = bool(s); if (result) m_sequence_array.push_back(s); return result; } void playset::remove (seq::number seqno) { auto seqit = std::find_if ( m_sequence_array.begin(), m_sequence_array.end(), [ & seqno ] (const seq::pointer & sp) { return sp->seq_number() == seqno; } ); if (seqit != m_sequence_array.end()) (void) m_sequence_array.erase(seqit); } /** * Moved here because sometimes gdb cannot evaluate it. */ int playset::count () const { return seq_count(); } std::string playset::to_string () const { std::string result = "Playset:\n"; for (const auto & sp : m_sequence_array) { std::string seqno = std::to_string(int(sp->seq_number())); result += " Seq "; result += seqno; result += ": '"; result += sp->name(); result += "' armed = "; result += bool_to_string(sp->armed()); result += "\n"; } return result; } } // namespace seq66 /* * screenset.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/seq.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file seq.cpp * * This module declares a two dimensional vector class solely to hold the * mute status of a number of sequences in a set. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-02-12 * \updates 2022-07-29 * \license GNU GPLv2 or above * * We added three classes: seq, screenset, and setmapper, which replace a * number of data items and functions in the seq66::performer class. The * seq66::seq class holds state data that is an adjunct to the data already * stored in a sequence. * * The state-saving buffers are incorporated into a seq object, which, in a * way, simply adds some more state variables to the seq66::sequence class. * These state-saving buffers were arrays of hardwired size. */ #include /* std::setw() manipulator */ #include /* std::cout */ #include /* std::ostringstream */ #include "play/seq.hpp" /* seq66::seq class */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /** * Default and principal constructor. * * The seq class is a small class to hold additional information about a * sequence. It replaces a number of arrays. * * Initialize the members. The computer-generated default constructor does * not clear the boolean members. * * Please note that the activate() function must be called before this object * can be used. */ seq::seq () : m_seq (), /* seq::pointer, shared pointer */ m_seq_active (false), /* indicates a valid pointer */ m_was_active_main (false), m_was_active_edit (false), m_was_active_perf (false), m_was_active_names (false), m_snapshot_status (false), m_armed_status (false), m_queued (false) { m_seq.reset(); /* must call seq::activate(s) */ } /** * Debuggable destructor. */ seq::~seq () { // no code needed } bool seq::activate (sequence * s, number seqno) { bool result = not_nullptr(s); if (result) { m_seq.reset(s); result = activate(seqno, true); } return result; } /** * Sets or unsets the active state of the given pattern/sequence number. If * setting it active, the sequence::seq_number() setter is called. It won't * modify the sequence's internal copy of the sequence number if it has * already been set. * * \param seqno * Provides the prospective sequence number. * * \param active * True if the sequence is to be set to the active state, which is the * default value. * * \return * Returns true if the sequence existed and could have its sequence number * set. */ bool seq::activate (number seqno, bool active) { bool result = true; if (m_seq_active && ! active) set_was_active(); /* weird, investigate */ if (active) { if (not_nullptr(m_seq)) { m_seq_active = true; m_seq->seq_number(seqno); if (m_seq->name().empty()) m_seq->set_name(); } else m_seq_active = result = false; } else m_seq_active = result = false; return result; } bool seq::deactivate () { bool result = not_nullptr(m_seq); if (m_seq_active) set_was_active(); /* weird, investigate */ m_seq_active = false; return result; } /** * Sets was-active flags: main, edit, perf, and names. * Why do we need this routine? * * \param seq * The pattern number. It is checked for validity. */ void seq::set_was_active () { if (active()) { m_was_active_main = m_was_active_edit = m_was_active_perf = m_was_active_names = true; } } /** * Indicates that the desired sequence is active, unmuted, and has * a non-zero trigger count. * * \param seq * The index of the desired sequence. * * \return * Returns true if the sequence has the three properties noted above. */ bool seq::is_exportable () const { bool result = active(); if (result) result = m_seq->is_exportable(); return result; } /** * Checks the pattern/sequence for main-dirtiness. See the * sequence::is_dirty_main() function. * * \param seq * The pattern number. It is checked for validity. * * \return * Returns the was-active-main flag value, before setting it to * false. Returns false if the pattern was invalid. */ bool seq::is_dirty_main () const { bool was_active = false; if (active()) { was_active = m_seq->is_dirty_main(); } else { was_active = m_was_active_main; m_was_active_main = false; /* mutable */ } return was_active; } /** * Checks the pattern/sequence for edit-dirtiness. * * \param seq * The pattern number. It is checked for validity. * * \return * Returns the was-active-edit flag value, before setting it to * false. Returns false if the pattern was invalid. */ bool seq::is_dirty_edit () const { bool was_active = false; if (active()) { was_active = m_seq->is_dirty_edit(); } else { was_active = m_was_active_edit; m_was_active_edit = false; /* mutable */ } return was_active; } /** * Checks the pattern/sequence for perf-dirtiness. * * \param seq * The pattern number. It is checked for validity. * * \return * Returns the was-active-perf flag value, before setting it to * false. Returns false if the pattern/sequence number was invalid. */ bool seq::is_dirty_perf () const { bool was_active = false; if (active()) { was_active = m_seq->is_dirty_perf(); } else { was_active = m_was_active_perf; m_was_active_perf = false; /* mutable */ } return was_active; } /** * Checks the pattern/sequence for names-dirtiness. * * \param seq * The pattern number. It is checked for validity. * * \return * Returns the was-active-names flag value, before setting it to * false. Returns false if the pattern/sequence number was invalid. */ bool seq::is_dirty_names () const { bool was_active = false; if (active()) { was_active = m_seq->is_dirty_names(); } else { was_active = m_was_active_names; m_was_active_names = false; /* mutable */ } return was_active; } /** * Turn the playing of a sequence on or off. Used for the implementation of * sequence_playing_on() and sequence_playing_off(). * * \param on * True if the sequence is to be turned on, false if it is to be turned * off. * * \param qinprogress * Indicates if queuing is in progress. This status is obtained from the * performer. */ void seq::sequence_playing_change (bool on, bool qinprogress) { if (active()) { bool queued = m_seq->get_queued(); if (on) { if (qinprogress) { if (! queued) m_seq->toggle_queued(); } else m_seq->set_armed(on); } else { if (queued && qinprogress) m_seq->toggle_queued(); else m_seq->set_armed(on); } } } std::string seq::to_string (int /*index*/) const { std::ostringstream result; if (active()) { result << " [" << std::setw(4) << std::right << m_seq->seq_number() << "]: '" << m_seq->name() << "'" << std::endl ; } else { /* * Too much information. * * result * << " [" << std::setw(4) << std::right << index * << "]: inactive slot" << std::endl * ; */ } return result.str(); } /** * Simply dumps the to_string() result to the console. */ void seq::show (int index) const { std::string seqmsg = to_string(index); if (! seqmsg.empty()) std::cout << seqmsg; } } // namespace seq66 /* * seq.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/sequence.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file sequence.cpp * * This module declares/defines the base class for handling the data and * management of patterns/sequences. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2025-10-24 * \license GNU GPLv2 or above * * The functionality of this class also includes handling some of the * operations of pattern triggers. * * We had added null-pointer checks for the master MIDI buss pointer, but * these just take up needless time, in most cases. * * Provides an option to save the Time Signature and Tempo data that may be * present in a MIDI file (in the first track) in the sequence object, and * write them back to the MIDI file when saved again, in Seq66 format. * The SeqSpec events that Seq24 and Seq66 save for these "events" are * not readable by other MIDI applications, such as QTractor. So other * sequencers can read the correct time-signature and tempo values. * * \note * We leave a small gap in various functions where mark_selected() locks * and unlocks, then we lock again. This should only be an issue if * altering selected notes while recording. We will test this at some * point, and add better locking coverage if necessary. */ #include /* std::memset() */ #include /* std::trunc() */ #include "cfg/settings.hpp" /* seq66::rc() and usr() */ #include "cfg/scales.hpp" /* key and scale constants */ #include "midi/mastermidibus.hpp" /* seq66::mastermidibus */ #include "midi/midibus.hpp" /* seq66::midibus */ #include "play/notemapper.hpp" /* seq66::notemapper */ #include "play/performer.hpp" /* seq66::performer */ #include "play/sequence.hpp" /* seq66::sequence */ #include "os/timing.hpp" /* seq66::microsleep() */ #include "util/palette.hpp" /* seq66::palette_to_int(), colors */ #include "util/strfunctions.hpp" /* bool_to_string() */ namespace seq66 { /** * This value is used as the minimal increment for growing a trigger during * song-recording. This value was originally 10, but let's use a power of 2. * This increment allows the rest of the threads to notice the change. */ static const int c_song_record_incr = 16; static const int c_maxbeats = 0xFFFF; /** * The c_handlesize value is an internal variable for handle size. Note * that, with the default PPQN of 192, a sixteenth note (a typical snap * value) is 48 pulses (ticks), so that a sixteenth note is broken into equal * left, center, and right sides. However, for a PPQN of, say, 960, 16 * pulses is 5 times smaller in width. We really need to scale the handle * size. */ static const midipulse c_handlesize { 16 }; /** * Static members for validating scale factors in pattern compression and * expanding. */ static const double c_scale_min { 0.01 }; static const double c_scale_max { 200.00 }; static const double c_measure_max { 1000.00 }; /** * The divisor for detecting when to reset auto-step. The original value * was 2. Let's try something else. Maybe 8 would work, too. */ static const midipulse c_reset_divisor { 4 }; /* * Member value. A fingerprint size of 0 means to not use a fingerprint... * display the whole track in the progress box, no matter how long. */ int sequence::sm_fingerprint_size { 0 }; /* * Member for convenience. */ short sequence::sm_preserve_velocity; /** * Provides the default name/title for the sequence. */ const std::string sequence::sm_default_name { "Untitled" }; /** * A static clipboard for holding pattern/sequence events. Being static * allows for copy/paste between patterns. Please note that this is used * only for selected events. For whole patterns, see the sequence object * performer::m_seq_clipboard. */ eventlist sequence::sm_clipboard; /** * Shows the note_info values. Purely for dev trouble-shooting. */ void sequence::note_info::show () const { printf ( "note_info %d: ticks %ld to %ld, velocity %d\n", ni_note, long(ni_tick_start), long(ni_tick_finish), ni_velocity ); } /** * Principal constructor. * * \param ppqn * Provides the PPQN parameter to perhaps alter the default PPQN value of * this sequence. */ sequence::sequence (int ppqn) : m_parent (nullptr), /* set when seq installed */ m_events (), m_triggers (*this), m_time_signatures (), m_events_undo_hold (), m_have_undo (false), m_have_redo (false), m_events_undo (), m_events_redo (), m_channel_match (false), m_midi_channel (0), /* null_channel() better? */ m_free_channel (false), m_nominal_bus (0), /* out buss default value */ m_true_bus (null_buss()), m_nominal_in_bus (null_buss()), /* optional input buss no. */ m_true_in_bus (null_buss()), m_song_mute (false), m_transposable (true), m_notes_on (0), m_master_bus (nullptr), m_playing_notes (c_notes_count, 0), m_armed (false), m_recording (false), m_draw_locked (false), m_recording_style (usr().pattern_record_style()), m_record_alteration (usr().record_alteration()), m_thru (false), m_has_popup (false), m_queued (false), m_one_shot (false), m_one_shot_tick (0), m_loop_count_max (0), m_off_from_snap (false), m_song_playback_block (false), m_song_recording (false), m_song_recording_snap (true), m_song_record_tick (0), m_loop_reset (false), m_unit_measure (0), m_dirty_main (true), m_dirty_edit (true), m_dirty_perf (true), m_dirty_names (true), m_is_modified (false), m_seq_in_edit (false), m_status (0), m_cc (0), m_name (), m_last_tick (0), m_queued_tick (0), m_trigger_offset (0), m_maxbeats (c_maxbeats), m_ppqn (choose_ppqn(ppqn)), m_seq_number (unassigned()), m_seq_color (c_seq_color_none), m_seq_edit_mode (sequence::editmode::note), m_length (4 * midipulse(m_ppqn)), /* 1 bar of ticks */ m_next_boundary (0), m_measures (0), m_snap_tick (int(m_ppqn) / 4), m_step_edit_note_length (int(m_ppqn) / 4), m_time_beats_per_measure (4), m_time_beat_width (4), m_timesig_beats_per_measure (0), m_timesig_beat_width (0), m_clocks_per_metronome (c_midi_clocks_per_metronome), m_32nds_per_quarter (c_midi_32nds_per_quarter), m_us_per_quarter_note (tempo_us_from_bpm(usr().bpm_default())), m_rec_vol (usr().preserve_velocity()), m_note_on_velocity (usr().note_on_velocity()), m_note_off_velocity (usr().note_off_velocity()), m_musical_key (usr().seqedit_key()), m_musical_scale (usr().seqedit_scale()), m_musical_chord (0), m_background_sequence (usr().seqedit_bgsequence()), m_mutex () { sm_preserve_velocity = usr().preserve_velocity(); sm_fingerprint_size = usr().fingerprint_size(); m_events.set_length(m_length); m_events.zero_len_correction(m_snap_tick / 2); m_triggers.set_ppqn(int(m_ppqn)); m_triggers.set_length(m_length); // for (auto & p : m_playing_notes) /* no notes playing now */ // p = 0; } /** * A rote destructor. */ sequence::~sequence () { // Empty body } /** * A convenience function that we have to put here so that the m_parent * pointer can be used without an additional include-file in the sequence.hpp * module. One minor issue is how can we unmodify the performance? We'd * need to keep a count/stack of modifications over all sequences in the * performance. Probably not practical, in general. We will probably keep * track of the modification of the buss (port) and channel numbers, as per * GitHub Issue #47. * * Issue #19: Crash when recording note. * * The notify_change() call eventually causes the "64" version of the edit * frame to crash, and also makes it do a lot of unnecessary rebuilding of * the grid buttons. So we revert to the original call. There's a chance * this might cause updates to be missed, but that's a lesser issue than a * segfault. But now we have added a feature that a complete recreation * requires a performer::change::recreate value; the default is * performer::change::yes. * * Note that now we don't call performer::modify(), now we call its * notification function for sequence-changes, which notifies all subscribers * and also calls modify(). * * Lastly, the metronome pattern (#2047) is set up programmatically, and * we will rebuild it if its configuration is changed on the fly. So * no flag-raising needed. * * \param notifychange * If true (the default), then notification is done (via a * performer::callbacks function). */ void sequence::modify (bool notifychange) { if (is_normal_seq()) /* currently, a seq-number < 1024 */ { m_is_modified = true; set_dirty(); if (notifychange) notify_change(); } } /** * A cut-down version of principal assignment operator or copy constructor. * * \threadsafe * * \param rhs * Provides the source of the new member values. * * \param domodify * Indicates if the modify flag is to be raised. It defaults to true. * It needs to set to false if the sequence is the clipboard sequence, * or if a pattern drag-and-drop is not yet finished., */ void sequence::partial_assign (const sequence & rhs, bool domodify) { if (this != &rhs) { automutex locker(m_mutex); m_parent = rhs.m_parent; /* pointer */ m_events = rhs.m_events; /* vector */ m_triggers = rhs.m_triggers; /* vector */ m_time_signatures = rhs.m_time_signatures; /* vector */ /* * The triggers class has a parent that cannot be reassigned. * Is this an issue? We don't care about copying undo/redo containers. * * m_events_undo_hold * m_have_undo * m_have_redo * m_events_undo * m_events_redo */ m_channel_match = rhs.m_channel_match; m_midi_channel = rhs.m_midi_channel; m_free_channel = rhs.m_free_channel; m_nominal_bus = rhs.m_nominal_bus; m_true_bus = rhs.m_true_bus; m_nominal_in_bus = rhs.m_nominal_in_bus; m_true_in_bus = rhs.m_true_in_bus; m_song_mute = rhs.m_song_mute; m_transposable = rhs.m_transposable; m_notes_on = 0; m_master_bus = rhs.m_master_bus; /* pointer */ m_unit_measure = rhs.m_unit_measure; m_name = rhs.m_name; m_ppqn = rhs.m_ppqn; /* * m_playing_notes is now a vector. */ std::fill(m_playing_notes.begin(), m_playing_notes.end(), 0); m_armed = false; m_recording = false; m_draw_locked = false; m_recording_style = usr().pattern_record_style(); m_record_alteration = usr().record_alteration(); m_thru = false; m_has_popup = false; m_queued = false; m_one_shot = false; m_one_shot_tick = 0; m_loop_count_max = rhs.m_loop_count_max; m_off_from_snap = false; m_song_playback_block = false; m_song_recording = false; m_song_recording_snap = true; m_song_record_tick = 0; m_loop_reset = false; m_unit_measure = rhs.m_unit_measure; m_dirty_main = true; m_dirty_edit = true; m_dirty_perf = true; m_dirty_names = true; m_is_modified = false; m_seq_in_edit = false; m_status = 0; m_cc = 0; m_name = rhs.m_name; m_last_tick = m_queued_tick = m_trigger_offset = 0; /* * Read-only: m_maxbeats = rhs.m_maxbeats; */ // m_ppqn = rhs.m_ppqn; m_seq_number = rhs.m_seq_number; // ? m_seq_color = rhs.m_seq_color; m_seq_edit_mode = rhs.m_seq_edit_mode; m_length = rhs.m_length; m_next_boundary = 0; m_measures = rhs.m_measures; m_snap_tick = rhs.m_snap_tick; m_step_edit_note_length = rhs.m_step_edit_note_length; m_time_beats_per_measure = rhs.m_time_beats_per_measure; m_time_beat_width = rhs.m_time_beat_width; m_clocks_per_metronome = rhs.m_clocks_per_metronome; m_32nds_per_quarter = rhs.m_32nds_per_quarter; m_us_per_quarter_note = rhs.m_us_per_quarter_note; m_rec_vol = rhs.m_rec_vol; m_note_on_velocity = rhs.m_note_on_velocity; m_note_off_velocity = rhs.m_note_off_velocity; m_musical_key = rhs.m_musical_key; m_musical_scale = rhs.m_musical_scale; m_musical_chord = rhs.m_musical_chord; m_background_sequence = rhs.m_background_sequence; /* * m_mutex * * Not necessary: (void) verify_and_link(); // NoteOn <---> NoteOff */ if (domodify) modify(); } } /* * These two functions are an attempt to remove a seqfault that can occur * in qseqdata, qseqroll, qloopbutton, etc. when processing multiple * inputs hitting keys wildly. May need to update all event-drawing GUI * classes if the segfaults keep occuring. */ void sequence::draw_lock () const { if (recording() && ! m_draw_locked) { m_mutex.lock(); m_draw_locked = true; /* mutable */ } } void sequence::draw_unlock () const { if (m_draw_locked) { m_draw_locked = false; /* mutable */ m_mutex.unlock(); } } void sequence::musical_key (int key, bool user_change) { if (legal_key(key)) { bool change = key != m_musical_key; if (change) { m_musical_key = midibyte(key); if (user_change) modify(); } } } void sequence::musical_scale (int scale, bool user_change) { if (legal_scale(scale)) { bool change = scale != m_musical_scale; if (change) { m_musical_scale = midibyte(scale); if (user_change) modify(); } } } void sequence::musical_chord (int c, bool user_change) { if (legal_chord(c)) { bool change = c != m_musical_chord; if (change) { m_musical_chord = midibyte(c); if (user_change) modify(); } } } /** * This result is tested in qseqeditframe64::set_background_sequence() * before passing the change to the qseqroll. At the opening of * the pattern editor, the value has been already set, so we * the value anyway. Suckage. */ bool sequence::background_sequence (int bs, bool user_change) { bool result = false; if (seq::legal(bs)) { result = bs != m_background_sequence || ! user_change; if (result) { m_background_sequence = short(bs); if (user_change) modify(); } } return result; } /** * \setter m_seq_color * * \param c * Provides the index into the color-palette. The only rules here are * that -1 represents no color or a default color, and values of zero * and above (to an unknown limit) represent a legal palette color. * * \param user_change * If true (the default value is false), the user has decided to change * this value, and we might need to modify the performer's dirty flag, so * that the user gets prompted for a change. * * \return * Returns true if the color actually changed. */ bool sequence::set_color (int c, bool user_change) { automutex locker(m_mutex); bool result = false; if (c >= 0 || c == c_seq_color_none) { if (colorbyte(c) != m_seq_color) { m_seq_color = colorbyte(c); result = true; if (user_change) modify(); /* no easy way to undo this */ } } return result; } bool sequence::loop_count_max (int m, bool user_change) { automutex locker(m_mutex); bool result = false; if (m >= 0 && m != m_loop_count_max) { m_loop_count_max = m; if (user_change) result = true; } if (result) modify(); /* have pending changes */ return result; } /** * If empty, sets the color to classic Sequencer64 yellow. Called by * performer when installing a sequence. */ void sequence::empty_coloring () { if (event_count() == 0 && m_seq_color == c_seq_color_none) (void) set_color(palette_to_int(PaletteColor::yellow)); } bool sequence::clear_events () { automutex locker(m_mutex); bool result = ! m_events.empty(); if (result) { m_events.clear(); modify(); /* have pending changes */ } return result; } /** * Returns the number of events stored in m_events. Note that only playable * events are counted in a sequence. If a sequence class function provides a * mutex, call m_events.count() instead. * * \threadsafe * * \return * Returns m_events.count(). */ int sequence::event_count () const { automutex locker(m_mutex); return m_events.count(); } int sequence::note_count () const { automutex locker(m_mutex); return m_events.note_count(); } /** * Gets the average of the notes within a snap value of the first note. */ bool sequence::first_notes (midipulse & ts, int & n) const { automutex locker(m_mutex); return m_events.first_notes(ts, n, m_snap_tick); } int sequence::playable_count () const { automutex locker(m_mutex); return m_events.playable_count(); } bool sequence::is_playable () const { automutex locker(m_mutex); return m_events.is_playable(); } /*------------------------------------------------------------------------- * Experimental time-signature functions *-------------------------------------------------------------------------*/ /** * Here, we count the time-signatures, if any, in the pattern. If there * are not any, then we create one representing the default beats and beat * width and push it on the time-signatures stack. Apart from this * possible default one, only actual existing time-signature events are saved * in the pattern. This stack is used only in drawing in the panes of the * pattern editor. It is not saved with the pattern. * * What if there is no beginning time signature, but ones deeper into the * pattern? We detect this and add a default one at the beginning. It gets * tweaked later in the process unless it is the only one. * * Note that this function assumes there are not two time-signature events * at the same timestamp. If there are, one will be ignored. Unlikely to * be a big issue. * * Currently the seqedit calls this function. Should the event editor * also call it? Yes, because it needs an up-to-date list of time signatures * in order to add events properly. * * \return * Returns true if a true time-signature was found. If false, then * the only time-signature saved will be the default one. */ bool sequence::analyze_time_signatures () { bool result = false; midipulse start = 0; midipulse limit = snap() / 2; /* allow some slop at the beginning */ bool found = false; int count = 0; int ppq = get_ppqn(); m_time_signatures.clear(); for (auto cev = cbegin(); ! cend(cev); ++cev) { if (get_next_meta_match(EVENT_META_TIME_SIGNATURE, cev, start)) { midipulse ts = cev->timestamp(); if (count == 0 && ts > limit) { push_default_time_signature(); /* ensure one at the start */ ++count; } timesig t; /* push a real time-sig */ t.sig_start_measure = 0.0; /* calculated later */ t.sig_measures = 0.0; /* ditto */ t.sig_beats_per_bar = int(cev->get_sysex(0)); t.sig_beat_width = beat_power_of_2(int(cev->get_sysex(1))); t.sig_ticks_per_beat = pulses_per_beat(ppq, t.sig_beat_width); t.sig_start_tick = ts; t.sig_end_tick = 0; /* tritto */ m_time_signatures.push_back(t); start = ts + 1; /* better than ++start; */ ++count; found = result = true; } else break; } if (! found) { push_default_time_signature(); found = true; } if (found) { /* * Calculate the "calculate later" values here. These will save * valuable time in drawing. */ size_t sz = m_time_signatures.size(); if (sz > 1) { size_t count = 0; double lastmeasure = 1.0; /* always at least one measure, #1 */ for (auto & t : m_time_signatures) { midipulse ender = count < (sz - 1) ? m_time_signatures[count + 1].sig_start_tick : get_length() ; t.sig_end_tick = ender; ender -= t.sig_start_tick; double mcurrent = pulses_to_measures ( ender, ppq, t.sig_beats_per_bar, t.sig_beat_width ); t.sig_start_measure = lastmeasure; t.sig_measures = mcurrent; t.sig_ticks_per_beat = pulses_per_beat(ppq, t.sig_beat_width); lastmeasure += mcurrent; ++count; } } else /* 1 time signature for whole song */ { auto & t = m_time_signatures[0]; t.sig_start_measure = 1; t.sig_measures = measures(); t.sig_end_tick = get_length(); } } return result; } /** * Makes the default time signature. */ sequence::timesig sequence::default_time_signature () const { timesig t; t.sig_start_measure = 0.0; t.sig_measures = 0.0; t.sig_beats_per_bar = m_time_beats_per_measure; t.sig_beat_width = m_time_beat_width; t.sig_ticks_per_beat = pulses_per_beat(get_ppqn(), m_time_beat_width); t.sig_start_tick = t.sig_end_tick = 0; return t; } /** * Pushes a default time-signature based on the beats/bar and beat width set * for the pattern. The extent and measure are calculated at the end of the * time-signature analysis stage. */ void sequence::push_default_time_signature () { timesig t = default_time_signature(); m_time_signatures.push_back(t); } const sequence::timesig & sequence::get_time_signature (size_t index) const { static timesig s_ts_dummy; /* { 0.0, 0.0, 0, 0, ... }; */ static bool s_uninitialized = true; if (s_uninitialized) { s_ts_dummy = default_time_signature(); s_uninitialized = false; } return index < m_time_signatures.size() ? m_time_signatures[index] : s_ts_dummy ; } /** * Do we want to call analyze_time_signatures() here? Probably better to * let the caller do it. * * \param p * Provides the current time in ticks (pulses). * * \param [out] beats * The value to which the time-signatures beats/pbar is passed if the * function succeeds. * * \param [out] beatwidth * The value to which the time-signatures beat width is passed if the * function succeeds. * * \return * Returns true if a time-signature was found. */ bool sequence::current_time_signature ( midipulse p, int & beats, int & beatwidth ) const { bool result = false; int count = time_signature_count(); if (count > 0) { for (int i = 1; i < count; ++i) { const timesig & current = get_time_signature(i); midipulse p0 = current.sig_start_tick; midipulse p1 = current.sig_end_tick; if (p >= p0 && p < p1) { beats = current.sig_beats_per_bar; beatwidth = current.sig_beat_width; result = true; break; } } } else { beats = get_beats_per_bar(); beatwidth = get_beat_width(); result = true; } return result; } /** * Finds the current measure number for a tick based on the list of * time-signatures. Although it is possible for a time-signature to last * for a non-integral number of measures, here we force the value to * be integral. * * This function is meant to be used in the time-lines of the pattern or song * editors. * * If the tick is past the start of the last time-signature (there is always * one, the default time signature), then we get the duration since that start, * convert it to a measure value, then add it to the firts measure of the last * time-signature. * * Note: for speed, we should use the old method, just incrementing the measure. * * \param p * Provides the tick for which we want to get the measure it it in. * * \return * Returns the rounded up integer version of the measure. */ int sequence::measure_number (midipulse p) const { int result = 0; int count = time_signature_count(); if (count > 0) { int ppq = get_ppqn(); for (int i = 0; i < count; ++i) { const timesig & t = get_time_signature(i); midipulse p0 = t.sig_start_tick; if (p >= p0) { midipulse p1 = t.sig_end_tick; midipulse duration = p - p0; double mnew = t.sig_start_measure; double m = pulses_to_measures ( duration, ppq, t.sig_beats_per_bar, t.sig_beat_width ); result += int(mnew + m + 0.5); /* round up for now */ if (p >= p1) { /* * We're past the last time-signature event. See banner. */ break; } } } } else result = measures(); return result; } /** * Converts a "B:B:T" string to pulses, given that the analysis, if done, * added time signatures. Compare it to seq66::measurestring_to_pulses() * or string_to_pulses(). * * - We have a string such as "4:l:000". * - Fill a midi_measures structure from that string. * - Extract the 4 (measures). * - Extract the 1 (beat). * - Extract the ticks. * - Iterate the time-signatures to get to the one where * measure >= t.sig_start_measure and if not at the end * where measure < t+1.sig_start_measure. * - Set pulses = t.sig_start_tick. * - Get the measure size, * - Get the percentage of the measure size: * percent = beat/t.sig_beats_per_bar as doubles; * - pulsesbeat = percent * (t.sig_end_tick - t.sig_start_tick); * - pulses += pulsebeat * - pulses += ticks. * - If after the last time-signature change: * * After completion, a re-analysis will be required. */ midipulse sequence::time_signature_pulses (const std::string & s) const { midipulse result = 0; midi_measures mm = string_to_measures(s); /* measures, beats, ticks */ int count = time_signature_count(); if (count > 0) { double mtarget = double(mm.measures()); bool got_it = false; for (int i = 0; i < count; ++i) { const timesig & t0 = get_time_signature(i); double m0 = t0.sig_start_measure; double m1; if (i < (count - 1)) { const timesig & t1 = get_time_signature(i + 1); m1 = t1.sig_start_measure; if (mtarget >= m0 && mtarget < m1) got_it = true; } else /* target measure is after last time-signature change. */ got_it = true; if (got_it) { /* * Some minor updates needed to fix inserting, e.g., * Program events, with the proper timestamp. */ midibpm bpminute = perf()->get_beats_per_minute(); int bpb = t0.sig_beats_per_bar; int bw = t0.sig_beat_width; midi_timing mt{bpminute, bpb, bw, get_ppqn()}; midipulse mticks = midi_measures_to_pulses(mm, mt); result = t0.sig_start_tick + mticks; break; } } } else { /* * Convert midi_measures to midi_timing, which has beats/bar and width * elements, and adds beats-per-minute and PPQN. */ midibpm bpminute = perf()->get_beats_per_minute(); int bpb = get_beats_per_bar(); int bwidth = get_beat_width(); midi_timing mt{bpminute, bpb, bwidth, get_ppqn()}; result = seq66::string_to_pulses(s, mt); } return result; } /** * Rescales the eventlist, then sets the pattern length to the result. */ midipulse sequence::apply_time_factor ( double factor, bool savenotelength, bool relink ) { midipulse result = m_events.apply_time_factor ( factor, savenotelength, relink ); if (result > 0) (void) set_length(result); /* triggers, verify defaults */ return result; } /*------------------------------------------------------------------------- * Undo/redo functions *-------------------------------------------------------------------------*/ /** * Pushes the event-list into the undo-list or the upcoming undo-hold-list. * * \threadsafe * * \param hold * A new parameter for the stazed undo/redo support, not yet used. * If true (the default is false), then the events go into the * undo-hold-list. */ void sequence::push_undo (bool hold) { automutex locker(m_mutex); if (hold) m_events_undo.push(m_events_undo_hold); /* stazed */ else m_events_undo.push(m_events); set_have_undo(); /* stazed */ } /** * Do not modify the performer here! First, just because we push-undo does * not mean a change will occur. Second, we are now checking for changed * sequences at exit, so do not need (as far as we know) to modify the * performer at this point. */ void sequence::set_have_undo () { m_have_undo = m_events_undo.size() > 0; } /** * If there are items on the undo list, this function pushes the event-list * into the redo-list, puts the top of the undo-list into the event-list, pops * from the undo-list, calls verify_and_link(), and then calls unselect(). * * We would like to be able to set performer's modify flag to false here, but * other sequences might still be in a modified state. We could add a modify * flag to sequence, and falsify that flag here. Something to think about. * * \threadsafe */ void sequence::pop_undo () { automutex locker(m_mutex); if (! m_events_undo.empty()) { m_events_redo.push(m_events); m_events = m_events_undo.top(); m_events_undo.pop(); (void) verify_and_link(); unselect(); } set_have_undo(); set_have_redo(); } /** * If there are items on the redo list, this function pushes the * event-list into the undo-list, puts the top of the redo-list into the * event-list, pops from the redo-list, calls verify_and_link(), and then * calls unselect. * * \threadsafe */ void sequence::pop_redo () { automutex locker(m_mutex); if (! m_events_redo.empty()) // move to triggers module? { m_events_undo.push(m_events); m_events = m_events_redo.top(); m_events_redo.pop(); (void) verify_and_link(); unselect(); } set_have_undo(); set_have_redo(); } /** * Calls triggers::push_undo() with locking. * * \threadsafe */ void sequence::push_trigger_undo () { automutex locker(m_mutex); m_triggers.push_undo(); } /** * Calls triggers::pop_undo() with locking. * * \threadsafe */ void sequence::pop_trigger_undo () { automutex locker(m_mutex); m_triggers.pop_undo(); } /** * Calls triggers::pop_redo() with locking. * * \threadsafe */ void sequence::pop_trigger_redo () { automutex locker(m_mutex); m_triggers.pop_redo(); } /** * \setter m_master_bus * Do we need to call set_dirty_mp() here? It doesn't affect any * user-interface elements. * * \threadsafe * * \param mmb * Provides a pointer to the master MIDI buss for this sequence. This * should be a reference, but isn't, nor is it checked. */ bool sequence::set_master_midi_bus (const mastermidibus * mmb) { automutex locker(m_mutex); m_master_bus = const_cast(mmb); return not_nullptr(mmb); } /** * \setter m_time_beats_per_measure * * \threadsafe * * \param bpb * The new setting of the beats-per-bar value. * * \param user_change * If true (default is false), then call the change a modification. * This change can happen at load time, which is not a modification. */ void sequence::set_beats_per_bar (int bpb, bool user_change) { automutex locker(m_mutex); bool modded = false; if (bpb != int(m_time_beats_per_measure)) { m_time_beats_per_measure = (unsigned short)(bpb); if (user_change) modded = true; int m = get_measures(); if (m != m_measures) { /* * Adjust the markers so that R matches END after a time-signature * change? No, the markers are a song-wide thing and the user * can move "R" if desired. * * midipulse E = perf()->get_length(); // END * midipulse R = perf()->get_right_tick(); // R */ m_measures = m; if (user_change) modded = true; } if (modded) modify(); } } /** * \setter m_time_beat_width * * \threadsafe * * \param bw * The new setting of the beat width value. * * \param user_change * If true (default is false), then call the change a modification. * This change can happen at load time, which is not a modification. */ void sequence::set_beat_width (int bw, bool user_change) { automutex locker(m_mutex); bool modded = false; if (bw != int(m_time_beat_width)) { m_time_beat_width = (unsigned short)(bw); if (user_change) modded = true; int m = get_measures(); if (m != m_measures) { m_measures = m; if (user_change) modded = true; } if (modded) modify(); } } /** * Used in midifile, this function replaces consecutive calls to * set_beats_per_bar() and set_beat_width() with a single call that * gets all the necessary information before making calculations. */ void sequence::set_time_signature (int bpb, int bw) { m_time_beats_per_measure = (unsigned short)(bpb); /* get first item */ set_beat_width(bw, false); /* no user change */ int m = get_measures(); m_measures = m; } /** * Calculates and sets u = 4BP/W, where u is m_unit_measure, B is the * beats/bar, P is the PPQN, and W is the beat-width. When any of these * quantities change, we need to recalculate. * * \param reset * If true (the default is false), make the calculateion anyway. * * \return * Returns the size of a measure. */ midipulse sequence::unit_measure (bool reset) const { automutex locker(m_mutex); if (m_unit_measure == 0 || reset) m_unit_measure = measures_to_ticks(); /* length of 1 measure */ return m_unit_measure; } /** * Changed this from a void to a boolean. Most usages still do not use the * return value. A fix for the double_length() function. * * \param measures * The number of measures to add. * * \param user_change * True if the change was initiated by the user, and not automatic. * The default is false. */ bool sequence::set_measures (int measures, bool user_change) { bool modded = set_length(measures * unit_measure(true)); if (modded) { m_measures = measures; if (user_change) modify(); } return modded; } int sequence::increment_measures () { int m = get_measures(); bool ok = set_measures(m + 1); return ok ? m + 1 : m ; } /** * Provides a consistent threshold value for the triggering of the * addition of a measure when recording a pattern in Expand mode. * * We want to have the threshold be a quarter of a beat, that is, * unit_measure() / 4 / 4, but that setting makes the drawing occur twice, in * slightly different places, and we have not figured that out yet. So we * stick with unit_measure() / 4, which is one beat. * * \return * Returns the current length of the sequence minus a beat. */ midipulse sequence::expand_threshold () const { return get_length() - unit_measure() / 4; } /** * Provides the new value of the horizontal scroll bar to set when doing * expanded recording. See expand_threshold(). * * If the latest (recorded) note has a tick value greater than the threshold, * then the number of measures is increment. * * \return * Returns the expand_threshold() minus a unit_measure() and a quarter. * * return expand_threshold() - (unit_measure() + unit_measure() / 4); * * This is wasteful and might be wrong. Call it "x". */ midipulse sequence::expand_value () { midipulse result = get_last_tick(); if (result >= expand_threshold()) { #if defined SEQ66_PLATFORM_DEBUG_TMI int m = increment_measures(); printf("expanded measures = %d\n", m); #else (void) increment_measures(); result = get_length(); #endif } else result = 0; return result; } /** * Calculates the number of measures in the sequence based on the * unit-measure and the current length, in pulses, of the sequence. Used by * seqedit. If m_unit_measure hasn't been calculated yet, it is calculated * here. * * \change ca 2017-08-15 * Fixed issue #106, where the measure count of a pattern kept * incrementing when edited. * * \return * Returns the sequence length divided by the measure length, roughly. * m_unit_measure is 0. The lowest valid measure is 1. */ int sequence::calculate_measures (bool reset) const { midipulse um = unit_measure(reset); return um > 0 ? (1 + (get_length() - 1) / um) : 1 ; } /** * Encapsulates a calculation needed in the qseqbase class (and elsewhere). * We could just assume m_unit_measures is always up-to-date and use that * value. * * Does the number of measures depend on the beat-width? What is the size of * a measure in ticks? What if we change the beats-per-bar and beat-width? * * Let's say we have a measure of notes in a 4/4 sequence. If we want to fit * it into a 3/4 measure, the number of ticks is 3/4th of the original, and * we'd have to rescale the notes to that new number. If we leave the notes * alone, then the measure-count increments, and in playback an space of * silence is introduced. Better to avoid changing the numerator. * * If we change 4/4 to 4/8, then playback slows down by half. Be aware of * this feature. * * \param newlength * If 0 (the default), then the current pattern length is used in the * calculation of measures. Otherwise, this parameter is used, and is * useful in the process of changing the number of measures in the * fix_pattern() function. * * \return * Returns the whole number of measure in the specified length of the * sequence. Essentially rounds up if there is some leftover ticks. */ int sequence::get_measures (midipulse newlength) const { midipulse um = unit_measure(); midipulse len = newlength > 0 ? newlength : get_length() ; int measures = 1; if (um > 0) { measures = int(len / um); if (len % int(um) != 0) ++measures; } return measures; } int sequence::get_measures () const { m_measures = get_measures(0); return m_measures; } /** * \setter m_rec_vol * If this velocity is greater than zero, then m_note_on_velocity will * be set as well. * * \threadsafe * * \param recvol * The new setting of the recording volume setting. It is used only if * it ranges from 1 to usr().max_note_on_velocity(), or is set to * the preserve-velocity (-1). */ void sequence::set_rec_vol (int recvol) { automutex locker(m_mutex); bool valid = recvol > 0 && recvol <= usr().max_note_on_velocity(); if (! valid) valid = recvol == usr().preserve_velocity(); if (valid) { m_rec_vol = short(recvol); if (m_rec_vol > 0) m_note_on_velocity = m_rec_vol; } } /** * Replaces toggle_playing(), and allows play to be toggled in the same way * as done by the automation function performer::loop_control(). * Enhances the consistency of control of the patterns. It's a little * round-about, though. */ bool sequence::sequence_playing_toggle () { return perf()->sequence_playing_toggle(seq_number()); } /** * A simple version of toggle_playing(). * * \return * Returns true if playing. */ bool sequence::toggle_playing () { return toggle_playing(perf()->get_tick(), perf()->resume_note_ons()); } /** * Toggles the playing status of this sequence. How exactly does this differ * from toggling the mute status? The midipulse and bool parameters of the * overload of this function are new, to support song-recording. * * \param tick * The position from which to resume Note Ons, if appplicable. Resuming * is a song-playback/song-recording feature. * * \param resumenoteons * A song-recording option. (This option, "note-resume", is stored in * the "usr" file. */ bool sequence::toggle_playing (midipulse tick, bool resumenoteons) { set_armed(! armed()); if (armed() && resumenoteons) resume_note_ons(tick); off_from_snap(false); return armed(); } /** * \setter m_song_mute * This function also calls set_dirty_mp() to make sure that the * perfnames panel is updated to show the new mute status of the * sequence. * * \param mute * True if the sequence is to be muted. * * For Seq66, this function also calls set_armed() to the opposite of the * mute value. */ void sequence::set_song_mute (bool mute) { m_song_mute = mute; set_armed(! mute); set_dirty_mp(); } void sequence::toggle_song_mute () { bool mute = ! m_song_mute; set_song_mute(mute); } /** * Toggles the queued flag and sets the dirty-mp flag. Also calculates the * queued tick based on m_last_tick. * * Issue #89: This function is called only when queing is turned on! To fix * this for status announcements, see set_armed() below. Perhaps this * function should merely be "set_queued()". * * \threadsafe */ bool sequence::toggle_queued () { automutex locker(m_mutex); set_dirty_mp(); m_queued = ! m_queued; #if defined SEQ66_PLATFORM_DEBUG_TMI printf("seq %d: queuing %s\n", int(seq_number()), m_queued ? "on" : "off"); #endif m_queued_tick = m_last_tick - mod_last_tick() + get_length(); off_from_snap(true); perf()->announce_pattern(seq_number()); /* for issue #89 */ return true; } /** * The play() function outputs notes starting from the given tick, and it * pre-buffers ahead. This function is called by the sequencer thread in * performer. The tick comes in as global tick. It turns the sequence off * after we play in this frame. * * \note * With pause support, the progress bar for the pattern/sequence editor * does what we want: pause with the pause button, and rewind with the * stop button. Works with JACK, with issues, but we'd like to have * the stop button do a rewind in JACK, too. * * If we are playing the song data (sequence on/off triggers, we are in * playback mode. And if we are song-recording, we then keep growing the * sequence's song-data triggers. * * Note that the song_playback_block() is handled in the trigger :: play() * function. If we have reached a new chunk of drawn patterns in the song * data, and we are not recording, then trigger unsets the playback block on * this pattern's events. * * The trigger calculations have been offloaded to the triggers :: play() * function. Its return value and side-effects tell if there's a change in * playing based on triggers, and provides the ticks that bracket it. * * Issue #103: * * No problem with running in ALSA. However, if unmuted, the progress bar * kept going after the loop-count. Fixed that. * * Running as a JACK Transport Slave, the count works the first time, but * then the Master's time confuses the loop-count mechanism and the progress * stops at odd spots. Rewinding the Master (e.g. qjackctl) makes the * loop-count work again. * * Running as JACK Master, no problem with the loop-count. * * Can we somehow reset the times-played? * * \param tick * Provides the current end-tick value. The tick comes in as a global * tick. * * \param playback_mode * Provides how playback is managed. True indicates that it is * performance/song-editor playback, controlled by the set of patterns * and triggers set up in that editor, and saved with the song in seq66 * format. False indicates that the playback is controlled by the main * window, in live mode. * * \param resumenoteons * A song-recording parameter. * * \threadsafe */ void sequence::play ( midipulse tick, bool playback_mode, bool resumenoteons ) { automutex locker(m_mutex); bool trigger_turning_off = false; /* turn off after in-frame play */ int trigtranspose = 0; /* used with c_trig_transpose */ midipulse start_tick = m_last_tick; /* modified in triggers::play() */ midipulse len = get_length() > 0 ? get_length() : m_ppqn ; /* * Issue #103. This fix allows the progress bar to behave well under * JACK Slave transport. * * midipulse times_played = m_last_tick / len; */ midipulse times_played = tick / len; m_trigger_offset = 0; /* from Seq24 */ if (m_song_mute) { set_armed(false); } else { /* * We make the song_recording() clause active for both Live and Song * mode now. From Sequencer64 on 2021-05-06. */ if (song_recording()) /* song-record triggers */ { (void) perf()->calculate_snap(tick); /* issue #44 redux */ bool added = grow_trigger(song_record_tick(), tick); if (added) notify_trigger(); } if (playback_mode) /* song mode: triggers */ { trigger_turning_off = m_triggers.play /* side-effects !!! */ ( start_tick, tick, trigtranspose, resumenoteons ); } } if (armed()) /* play notes in frame */ { midipulse offset = len - m_trigger_offset; midipulse start_tick_offset = start_tick + offset; midipulse end_tick_offset = tick + offset; midipulse offset_base = times_played * len; if (loop_count_max() > 0) { if (times_played >= loop_count_max()) { if (is_metro_seq()) /* count-in is complete */ perf()->finish_count_in(); return; } } int transpose = trigtranspose; if (transpose == 0) transpose = transposable() ? perf()->get_transpose() : 0 ; auto e = m_events.begin(); while (e != m_events.end()) { #if defined USE_NULL_EVENT_DETECTION /* * This doesn't solve the problem of hiccups when moving to the * next song in the playlist. */ if (is_nullptr(e)) return; #endif event & er = eventlist::dref(e); midipulse ts = er.timestamp(); midipulse stamp = ts + offset_base; if (stamp >= start_tick_offset && stamp <= end_tick_offset) { if (transpose != 0 && er.is_note()) /* includes Aftertouch */ { event trans_event = er; /* assign ALL members */ trans_event.transpose_note(transpose); put_event_on_bus(trans_event); } else { if (er.is_tempo()) { perf()->set_beats_per_minute(er.tempo()); } else { if (er.is_ex_data()) { if (er.is_sysex()) put_event_on_bus(er); /* ca 2024-05-22 */ } else put_event_on_bus(er); /* frame still going */ } } } else if (stamp > end_tick_offset) break; /* frame is done */ ++e; /* go to next event */ if (e == m_events.end()) /* did we hit the end ? */ { e = m_events.begin(); /* yes, start over */ offset_base += len; /* for another go at it */ /* * Putting this sleep here doesn't reduce the total CPU load, * but it does prevent one CPU from being hammered at 100%. * millisleep(1) made the live-grid progress bar jittery when * unmuting shorter patterns, which play() relentlessly. */ (void) microsleep(1); } } } else { if (loop_count_max() > 0 && times_played >= loop_count_max()) return; /* issue #103 */ } if (trigger_turning_off) /* triggers: "turn off" */ { set_armed(false); } m_last_tick = tick + 1; /* for next frame */ /* * This causes control-output spewage during playback, but we need to * send an announcement when queuing is in force. At that point there * is a quick burst of events (all Note 2, Vel 62) we still have to figure * out. They all apply to the slot being queued/unqueued. So we move this * code to toggle_queued(). * * if (get_queued()) * perf()->announce_pattern(seq_number()); // for issue #89 // */ } /** * This function plays without supporting song-mode, triggers, transposing, * resuming notes, loop count, meta events, and song recording. It is * meant to be used for a metronome pattern. * * Do we want to support tempo in a metronome pattern? * * One issue is in removing the metronome, and we end up with a null event, * causing a segfault. We should just mute the metronome. */ void sequence::live_play (midipulse tick) { automutex locker(m_mutex); midipulse start_tick = m_last_tick; /* modified in triggers::play() */ midipulse end_tick = tick; /* ditto */ if (m_song_mute) set_armed(false); if (armed()) /* play notes in the frame */ { midipulse len = get_length() > 0 ? get_length() : m_ppqn ; midipulse start_tick_offset = start_tick + len; midipulse end_tick_offset = end_tick + len; midipulse times_played = m_last_tick / len; midipulse offset_base = times_played * len; if (loop_count_max() > 0) { if (times_played >= loop_count_max()) { if (is_metro_seq()) /* count-in is complete */ perf()->finish_count_in(); return; } } auto e = m_events.begin(); while (e != m_events.end()) { event & er = eventlist::dref(e); midipulse stamp = er.timestamp() + offset_base; if (stamp >= start_tick_offset && stamp <= end_tick_offset) { #if defined SUPPORT_TEMPO_IN_LIVE_PLAY if (er.is_tempo()) { perf()->set_beats_per_minute(er.tempo()); } #endif put_event_on_bus(er); /* frame still going */ } else if (stamp > end_tick_offset) break; /* frame is done */ ++e; /* go to next event */ if (e == m_events.end()) /* did we hit the end ? */ { e = m_events.begin(); /* yes, start over */ offset_base += len; /* for another go at it */ (void) microsleep(1); } } } m_last_tick = end_tick + 1; /* for next frame */ } /** * This function verifies state: all note-ons have a note-off, and it links * note-offs with their note-ons and vice-versa. * * \threadsafe * * \param wrap * Optionally (the default is false) wrap when relinking. Can be used to * override usr().pattern_wraparound(). Defaults to false. */ bool sequence::verify_and_link (bool wrap) { automutex locker(m_mutex); midipulse len = expanded_recording() ? 0 : get_length() ; return m_events.verify_and_link(len, wrap); } /** * Fixes selected notes that started near the very end of the pattern, due to * a clumsy keyboard artist (like the author of this module). Used by * qseqroll. */ bool sequence::edge_fix () { automutex locker(m_mutex); m_events_undo.push(m_events); /* push_undo(), no lock */ bool result = m_events.edge_fix(snap(), get_length()); if (result) modify(); return result; } /** * Removes unlinked notes. */ bool sequence::remove_unlinked_notes () { automutex locker(m_mutex); m_events_undo.push(m_events); /* push_undo(), no lock */ bool result = m_events.remove_unlinked_notes(); if (result) modify(); return result; } #if defined USE_SEQUENCE_REMOVE_EVENTS /* * These functions are currently not used. Might as well save some space-time. */ /** * A helper function, which does not lock/unlock, so it is unsafe to call * without supplying an iterator from the event-list. If the event is a note * off, and that note is currently playing, then send a note off before * removing the note. * * \threadunsafe * * \param i * Provides the iterator to the event to remove from the event list. */ void sequence::remove (event::buffer::iterator evi) { if (evi != m_events.end()) { event & er = eventlist::dref(evi); if (er.is_note_off() && m_playing_notes[er.get_note()] > 0) { master_bus()->play_and_flush(m_true_bus, &er, midi_channel(er)); --m_playing_notes[er.get_note()]; } if (m_events.remove(evi)) modify(); } } /** * A helper function, which does not lock/unlock, so it is unsafe to call * without supplying an iterator from the event-list. * * Finds the given event in m_events, and removes the first iterator * matching that. If there are events that would match after that, they * remain in the container. This matches seq66 behavior. * * \todo * Use the find() function to find the matching event more * conventionally. * * \threadunsafe * * \param e * Provides a reference to the event to be removed. */ void sequence::remove (event & e) { if (m_events.remove_event(e)) modify(); } #endif // defined USE_SEQUENCE_REMOVE_EVENTS /** * This function is use in replacing the "song info" Meta Text event. */ bool sequence::remove_first_match (const event & e, midipulse starttick) { automutex locker(m_mutex); return m_events.remove_first_match(e, starttick); } /** * Clears all events from the event container. Also see copy_events(). */ bool sequence::remove_all () { automutex locker(m_mutex); bool result = false; int count = m_events.count(); if (count > 0) { m_events.clear(); count = m_events.count(); result = count == 0; if (result) modify(); /* issue #90 */ } return result; } /** * Removes any events timestamps after the last measure. See * the use of would_truncate() in qseqeditframe64. */ bool sequence::remove_orphaned_events () { automutex locker(m_mutex); bool result = m_events.remove_trailing_events(get_length()); if (result) { if (result) modify(); } return result; } /** * Removes marked events. Before removing the events, any Note Ons are * turned off, just in case. This function forwards the call to * m_event.remove_marked(). * * \threadsafe * * \return * Returns true if at least one event was removed. */ bool sequence::remove_marked () { automutex locker(m_mutex); for (auto & e : m_events) { if (e.is_marked() && e.is_note_on()) play_note_off(int(e.get_note())); } bool result = m_events.remove_marked(); if (result) modify(); return result; } /** * Marks the selected events. * * \threadsafe * * \return * Returns true if there were any events that got marked. */ bool sequence::mark_selected () { automutex locker(m_mutex); return m_events.mark_selected(); } /** * Removes selected events. This is a new convenience function to fold in * the push_undo() and mark_selected() calls. It makes the process slightly * faster, as well. * * \threadsafe * Also makes the whole process threadsafe. */ bool sequence::remove_selected () { automutex locker(m_mutex); m_events_undo.push(m_events); /* push_undo() without lock */ bool result = m_events.remove_selected(); if (result) modify(); return result; } /** * Unpaints all events in the event-list. * * \threadsafe */ void sequence::unpaint_all () { automutex locker(m_mutex); m_events.unpaint_all(); } /** * Returns the 'box' of the selected items. Note the common-code betweem * this function and clipboard_box(). Also note we return a * boolean indicating if the return values were filled in. * * \threadsafe * * \param [out] tick_s * Side-effect return reference for the start time. * * \param [out] note_h * Side-effect return reference for the high note. * * \param [out] tick_f * Side-effect return reference for the finish time. * * \param [out] note_l * Side-effect return reference for the low note. * * \return * Returns true if all the values are usable. */ bool sequence::selected_box ( midipulse & tick_s, int & note_h, midipulse & tick_f, int & note_l ) { automutex locker(m_mutex); tick_s = m_maxbeats * m_ppqn; /* the largest tick/pulse we allow */ tick_f = 0; /* the smallest tick possible */ note_l = c_midibyte_data_max; /* the largest note value possible */ note_h = (-1); /* the lowest, impossible note */ for (auto & e : m_events) { if (e.is_selected()) { midipulse time = e.timestamp(); if (time < tick_s) tick_s = time; if (time > tick_f) tick_f = time; int note = e.get_note(); if (note < note_l) note_l = note; if (note > note_h) note_h = note; } } bool result = ( (tick_s < m_maxbeats * m_ppqn) && (tick_f > 0) && (note_l < c_midibyte_data_max) && (note_h >= 0) ); return result; } /** * Returns the 'box' of the selected items for only Note On values. * Compare to selected_box(). * * \threadsafe * * \param [out] tick_s * Side-effect return reference for the start time. * * \param [out] note_h * Side-effect return reference for the high note. * * \param [out] tick_f * Side-effect return reference for the finish time. * * \param [out] note_l * Side-effect return reference for the low note. * * \return * Returns true if a selected Note On event is found. */ bool sequence::onsets_selected_box ( midipulse & tick_s, int & note_h, midipulse & tick_f, int & note_l ) { automutex locker(m_mutex); bool result = false; tick_s = m_maxbeats * m_ppqn; tick_f = note_h = 0; note_l = c_midibyte_data_max; for (auto & e : m_events) { if (e.is_selected_note_on()) { /* * We cannot check On/Off here. It screws up seqevent selection, * which has no "off". */ midipulse time = e.timestamp(); if (time < tick_s) tick_s = time; if (time > tick_f) tick_f = time; int note = e.get_note(); if (note < note_l) note_l = note; if (note > note_h) note_h = note; result = true; } } return result; } /** * Returns the 'box' of the clipboard items. Note the common-code betweem * this function and selected_box(). This function is called in * qstriggereditor and in qseqroll. * * \threadsafe * * \param [out] tick_s * Side-effect return reference for the start time. * * \param [out] note_h * Side-effect return reference for the high note. * * \param [out] tick_f * Side-effect return reference for the finish time. * * \param [out] note_l * Side-effect return reference for the low note. * * \return * Returns true if all the values are usable. */ bool sequence::clipboard_box ( midipulse & tick_s, int & note_h, midipulse & tick_f, int & note_l ) { automutex locker(m_mutex); bool result = false; tick_s = m_maxbeats * m_ppqn; tick_f = 0; note_h = 0; note_l = c_midibyte_data_max; if (sm_clipboard.empty()) { tick_s = tick_f = note_h = note_l = 0; } else { result = true; /* FIXME */ for (auto & e : sm_clipboard) { midipulse time = e.timestamp(); int note = e.get_note(); if (time < tick_s) tick_s = time; if (time > tick_f) tick_f = time; if (note < note_l) note_l = note; if (note > note_h) note_h = note; } } return result; } /** * Counts the selected notes in the event list. * * \threadsafe * * \return * Returns m_events.count_selected_notes(). */ int sequence::get_num_selected_notes () const { automutex locker(m_mutex); return m_events.count_selected_notes(); } /** * Counts the selected events, with the given status, in the event list. * If the event is a control change (CC), then it must also match the * given CC value. * * \threadsafe * * \param status * The desired kind of event to count. * * \param cc * The desired control-change to count, if the event is a control-change. * * \return * Returns m_events.count_selected_events(). */ int sequence::get_num_selected_events (midibyte status, midibyte cc) const { automutex locker(m_mutex); return m_events.count_selected_events(status, cc); } /** * This function selects events in range of tick start, note high, tick end, * and note low. * * Compare this function to the convenience function select_all_notes(), which * doesn't use range information. * * Note that we have not offloaded this function to eventlist because it * depends on the sequence::select enumeration, and we're too lazy at the * moment to move that enumeration to eventlist. * * \threadsafe * * \param tick_s * The start time of the selection. * * \param note_h * The high note of the selection, inclusive. * * \param tick_f * The finish time of the selection. * * \param note_l * The low note of the selection, inclusive. * * \param action * The action to perform, one of the values of the sequence::select * enumeration. * * \return * Returns the number of events acted on, or 0 if no desired event was * found. */ int sequence::select_note_events ( midipulse tick_s, int note_h, midipulse tick_f, int note_l, eventlist::select action ) { automutex locker(m_mutex); return m_events.select_note_events(tick_s, note_h, tick_f, note_l, action); } /** * This function selects notes that lie within a range of pitches, * inclusive. The time-stamps range from 0 to the end of the pattern. * Note that the parameters are ... backwards. * * \param note_h * The high note of the selection, inclusive. * * \param note_l * The low note of the selection, inclusive. */ int sequence::select_notes_by_pitch (int note_h, int note_l) { automutex locker(m_mutex); midipulse t0 = 0; midipulse t1 = get_length(); eventlist::select seltype = eventlist::select::selecting; return m_events.select_note_events(t0, note_h, t1, note_l, seltype); } /** * Select all events in the given range, and returns the number * selected. Note that there is also an overloaded version of this * function. * * \threadsafe * * \param tick_s * The start time of the selection. * * \param tick_f * The finish time of the selection. * * \param status * The desired event in the selection. Now, as a new feature, tempo * events are also selectable, in addition to events selected by this * parameter. * * \param cc * The desired control-change in the selection, if the event is a * control-change. * * \param action * The desired selection action. * * \return * Returns the number of events selected. */ int sequence::select_events ( midipulse tick_s, midipulse tick_f, midibyte status, midibyte cc, eventlist::select action ) { automutex locker(m_mutex); return m_events.select_events(tick_s, tick_f, status, cc, action); } /** * Select all events with the given status, and returns the number * selected. Note that there is also an overloaded version of this * function. * * \threadsafe * * \warning * This used to be a void function, so it just returns 0 for now. * * \param status * Provides the status value to be selected. * * \param cc * If the status is EVENT_CONTROL_CHANGE, then data byte 0 must * match this value. * * \param inverse * If true, invert the selection. * * \return * Always returns 0. */ int sequence::select_events (midibyte status, midibyte cc, bool inverse) { automutex locker(m_mutex); midibyte d0, d1; for (auto & er : m_events) { er.get_data(d0, d1); bool match = er.match_status(status); bool canselect; if (status == EVENT_CONTROL_CHANGE) canselect = match && d0 == cc; /* correct status and correct cc */ else canselect = match; /* correct status, cc irrelevant */ if (canselect) { if (inverse) { if (! er.is_selected()) er.select(); else er.unselect(); } else er.select(); } } return 0; } int sequence::select_event_handle ( midipulse tick_s, midipulse tick_f, midibyte astatus, midibyte cc, midibyte data ) { automutex locker(m_mutex); int result = m_events.select_event_handle ( tick_s, tick_f, astatus, cc, data ); set_dirty(); return result; } /** * Selects all events, unconditionally. * * \threadsafe */ void sequence::select_all () { automutex locker(m_mutex); m_events.select_all(); } void sequence::select_by_channel (int channel) { if (is_good_channel(midibyte(channel))) { automutex locker(m_mutex); m_events.select_by_channel(channel); } } void sequence::select_notes_by_channel (int channel) { if (is_good_channel(midibyte(channel))) { automutex locker(m_mutex); m_events.select_notes_by_channel(channel); } } /** * Deselects all events, unconditionally. * * \threadsafe */ void sequence::unselect () { automutex locker(m_mutex); m_events.unselect_all(); } /** * Removes and adds selected notes in position. Also currently moves any * other events in the range of the selection. * * Also, we've moved external calls to push_undo() into this function. * The caller shouldn't have to do that. * * Another thing this function does is wrap-around when movement occurs. * Any events (except Note Off) that will start just after the END of the * pattern will be wrapped around to the beginning of the pattern. * * Fixed: * * Select all notes in a short pattern that starts at time 0 and has non-note * events starting at time 0 (see contrib/midi/allofarow.mid); move them with * the right arrow, and move them back with the left arrow; then view in the * event editor, and see that the non-Note events have not moved back, and in * fact move way too far to the right, actually to near the END marker. * We've fixed that in the new adjust_timestamp() function. * * This function checks for any marked events in seq66, but now we make sure * the event is a Note On or Note Off event before dealing with it. We now * handle properly events like Program Change, Control Change, and Pitch * Wheel. Remember that Aftertouch is treated like a note, as it has * velocity. For non-Notes, event::get_note() returns m_data[0], and we don't * want to adjust that. * * \note * We leave a small gap where mark_selected() locks and unlocks, then * we lock again. This should only be an issue if moving notes while * the sequence is playing. * * \param delta_tick * Provides the amount of time to move the selected notes. Note that it * also applies to events. Note-Off events are expanded to m_length if * their timestamp would be 0. All other events will wrap around to 0. * * \param delta_note * Provides the amount of pitch to move the selected notes. This value * is applied only to Note (On and Off) events. Also, if this value * would bring a note outside the range of 0 to 127, that note is not * changed and the event is not moved. */ bool sequence::move_selected_notes (midipulse delta_tick, int delta_note) { automutex locker(m_mutex); m_events_undo.push(m_events); /* push_undo(), no lock */ bool result = m_events.move_selected_notes(delta_tick, delta_note); if (result) modify(); return result; } bool sequence::move_selected_events (midipulse delta_tick) { automutex locker(m_mutex); m_events_undo.push(m_events); /* push_undo(), no lock */ bool result = m_events.move_selected_events(delta_tick); if (result) modify(); return result; } /** * Performs a stretch operation on the selected events. Also, we've moved * external calls to push_undo() into this function. The caller shouldn't have * to do that. * * \threadsafe * * \param delta_tick * Provides the amount of time to stretch the selected notes. */ bool sequence::stretch_selected (midipulse delta_tick) { automutex locker(m_mutex); m_events_undo.push(m_events); /* push_undo(), no lock */ bool result = m_events.stretch_selected(delta_tick); if (result) modify(); return result; } /** * The original description was "Moves note off event." But this also gets * called when simply selecting a second note via a ctrl-left-click, even in * seq66. And, though it doesn't move Note Off events, it does reconstruct * them. * * This function is called when doing a ctrl-left mouse move on the selected * notes or when using ctrl-left-arrow or ctrl-right-arrow to shrink or * stretch the selected notes. Using the mouse allows pretty much any amount * of growth or shrinkage, but use the arrow keys limits the changes to the * current snap value. * * This function grows/shrinks only Note On events that are marked and * linked. If an event is not linked, this function now ignores the event's * timestamp, rather than risk a segfault on a null pointer. Compare this * function to the stretch_selected() and move_selected_notes() functions. * * This function would strip out non-Notes, but now it at least preserves * them and moves them, to try to preserve their relative position re the * notes. * * In any case, we want to mark the original off-event for deletion, * otherwise we get duplicate off events, for example in the "Begin/End" * pattern in the test.midi file. * * This function now tries to prevent pathological growth, such as trying to * shrink the notes to zero length or less, or stretch them beyond the length * of the sequence. Otherwise we get weird and unexpected results. Also, * we've moved external calls to push_undo() into this function. The caller * shouldn't have to do that. * * A comment on terminology: The user "selects" notes, while the sequencer * "marks" notes. The first thing this function does is mark all the selected * notes. * * \threadsafe * * \param delta * An offset for each linked event's timestamp. */ bool sequence::grow_selected (midipulse delta) { automutex locker(m_mutex); /* lock it again, dude */ m_events_undo.push(m_events); /* push_undo(), no lock */ bool result = m_events.grow_selected(delta, snap()); if (result) modify(); return result; } /** * Randomizes the selected events. Adapted from Seq32 with the unused * control parameter removed. * * \param status * The kind of events to be randomized. * * \param plus_minus * The range of the randomization. * * \return * Returns true if the randomization was performed. If true, the * sequence is flagged as modified. */ bool sequence::randomize (midibyte status, int range, bool all) { automutex locker(m_mutex); m_events_undo.push(m_events); /* push_undo(), no lock */ if (range == (-1)) range = usr().randomization_amount(); bool result = m_events.randomize(status, range, all); if (result) modify(); return result; } bool sequence::randomize_note_velocities (int range, bool all) { automutex locker(m_mutex); m_events_undo.push(m_events); /* push_undo(), no lock */ if (range == (-1)) range = usr().randomization_amount(); bool result = m_events.randomize_note_velocities(range, all); if (result) modify(); return result; } bool sequence::randomize_note_pitches (int range, bool all) { automutex locker(m_mutex); m_events_undo.push(m_events); /* push_undo(), no lock */ if (range == (-1)) range = usr().randomization_amount(); scales s = int_to_scale(int(m_musical_scale)); keys k = int_to_key(int(m_musical_key)); bool result = m_events.randomize_note_pitches(range, s, k, all); if (result) modify(); return result; } /** * For usage by fix_pattern() and by Tools / Timing / Jitter. * Note the snap() parameter, to avoid gross jittering. * * \param jitr * Provides the maximum range of the jittering in MIDI tick units. * * \param all * If true (the default is false), all events are jittered, * not just the selected events. The value "true" is to be * used by fix_pattern(). * * \return * Returns true if events got jittered. */ bool sequence::jitter_notes (int jitr, bool all) { automutex locker(m_mutex); bool result = m_events.jitter_notes(snap(), jitr, all); if (result) modify(); return result; } /** * Used for moving the data value of an event in the seqdata pane up or * down. * * One issue in adjusting data is Pitch events, which have two * components [d0() and d1()] which must be combined. But d0() is the * least-significant byte, and d1() is the most-significant byte. * Since pitch is a 2-byte message, d1() will be adjusted. * * \param astatus * Provides the status byte of the event, which can indicate * a normal MIDI event or a Meta event (currently only Tempo is * handled.) */ void sequence::adjust_event_handle (midibyte astatus, midibyte adata) { midibyte data[2]; midibyte datitem; int dataindex = event::is_two_byte_msg(astatus) ? 1 : 0 ; automutex locker(m_mutex); for (auto & er : m_events) { if (er.is_selected_status(astatus)) { if (er.is_tempo()) { midibpm tempo = note_value_to_tempo(midibyte(adata)); if (er.set_tempo(tempo)) modify(); } else if (er.is_program_change()) { /* * Currently handled by the line-draw mechanism. But let's * support handle dragging as well. */ er.set_data(adata); /* test later */ modify(); } else { astatus = event::mask_status(astatus); er.get_data(data[0], data[1]); /* \tricky code */ datitem = adata; if (datitem > (c_midibyte_data_max - 1)) datitem = (c_midibyte_data_max - 1); data[dataindex] = datitem; er.set_data(data[0], data[1]); modify(); } } } } /** * Increments events the match the given status and control values. * The supported statuses are: * * - EVENT_NOTE_ON * - EVENT_NOTE_OFF * - EVENT_AFTERTOUCH * - EVENT_CONTROL_CHANGE * - EVENT_PITCH_WHEEL * - EVENT_PROGRAM_CHANGE * - EVENT_CHANNEL_PRESSURE * * \threadsafe * * \param astat * The desired event. * * Parameter "acontrol", the desired control-change, is unused. * This might be a bug, or at least a missing feature. */ void sequence::increment_selected (midibyte astat, midibyte /*acontrol*/) { automutex locker(m_mutex); bool modded = false; for (auto & e : m_events) { if (e.is_selected_status(astat)) /* && er.get_control == acontrol */ { if (event::is_two_byte_msg(astat)) { e.increment_d1(); modded = true; } else if (event::is_one_byte_msg(astat)) { e.increment_d0(); modded = true; } } } if (modded) modify(); /* issue #90 */ } /** * Decrements events the match the given status and control values. * The supported statuses are: * * - One-byte messages * - EVENT_PROGRAM_CHANGE * - EVENT_CHANNEL_PRESSURE * - Two-byte messages * - EVENT_NOTE_ON * - EVENT_NOTE_OFF * - EVENT_AFTERTOUCH * - EVENT_CONTROL_CHANGE * - EVENT_PITCH_WHEEL * * \threadsafe * * \param astat * The desired event. * * Parameter "acontrol", the desired control-change, is unused. * This might be a bug, or at least a missing feature. */ void sequence::decrement_selected (midibyte astat, midibyte /*acontrol*/) { automutex locker(m_mutex); bool modded = false; for (auto & e : m_events) { if (e.is_selected_status(astat)) /* && er.get_control == acontrol */ { if (event::is_two_byte_msg(astat)) { e.decrement_d1(); modded = true; } else if (event::is_one_byte_msg(astat)) { e.decrement_d0(); modded = true; } } } if (modded) modify(); /* issue #90 */ } /** * Why "change" velocity here? Why verify_and_link()? FIXME */ bool sequence::repitch (const notemapper & nmap, bool all) { automutex locker(m_mutex); bool result = false; push_undo(); for (auto & e : m_events) { if (e.is_note() && (all || e.is_selected())) { midibyte pitch, velocity; /* d0 & d1 */ e.get_data(pitch, velocity); pitch = midibyte(nmap.convert(int(pitch))); e.set_data(pitch, velocity); result = true; } } if (result && ! all) { if (verify_and_link()) modify(); } return result; } /** * Copies the selected events. This function also has the danger, discovered * by user 0rel, of events being modified after being added to the clipboard. * So we add his reconstruction fix here as well. To summarize the steps: * * -# Clear the sm_clipboard. NO! If we have no events to * copy to the clipboard, we do not want to clear it. This kills * cut-and-paste functionality. * -# Add all selected events in this clipboard to the sequence. * -# Normalize the timestamps of the events in the clip relative to the * timestamp of the first selected event. * -# Reconstruct/reconstitute the sm_clipboard. * * This process is a bit easier to manage than erase/insert on events because * std::multimap has no erase() function that returns the next valid * iterator. Also, we use a local clipboard first, to save on copying. * We've enhanced the error-checking, too. * * Finally, note that sm_clipboard is a static member of sequence, so: * * -# Copying can be done between sequences. * -# Access to it needs to be protected by a mutex. * * \threadsafe */ bool sequence::copy_selected () { automutex locker(m_mutex); eventlist clipbd; bool result = m_events.copy_selected(clipbd); if (result) sm_clipboard = clipbd; return result; } /** * Cuts the selected events. Pushes onto the undo stack, may copy the * events, marks the selected events, and removes them. Now also sets the * dirty flag so that the caller doesn't have to. Also raises the modify * flag on the parent performer object. * * \threadsafe * * \param copyevents * If true, copy the selected events before marking and removing them. */ bool sequence::cut_selected (bool copyevents) { push_undo(); if (copyevents) copy_selected(); return remove_selected(); /* works and modifies */ } /** * Pastes the selected notes (and only note events) at the given tick and * the given note value. * * Also, we've moved external calls to push_undo() into this function. * The caller shouldn't have to do that. * * The event::key used to access/sort the multimap eventlist is not updated * after changing timestamp/rank of the stored events. Regenerating all * key/value pairs before merging them solves this issue, so that * the order of events in the sequence will be preserved. This action is not * needed for moving or growing events. Nor is it needed if the old * std::list implementation of the event container is compiled in. However, * it is needed in any operation that modifies the timestamp of an event * inside the container: * * - copy_selected() * - paste_selected() * - quantize_events() [quantizes or tightens base on a parameter] * * The alternative to reconstructing the map is to erase-and-insert the * events modified in the code above, rather than just tweaking their values, * which have an effect on sorting for the event-map implementation. * However, multimap does not provide an erase() function that returns the * next valid iterator, which would complicate this method of operation. So * we're inclined to stick with this solution. * * There was an issue with copy/pasting a whole sequence. The pasted events * did not go to their destination, but overlayed the original events. This * bug also occurred in Seq24 0.9.2. It occurs with the allofarow.mid file * when doing Ctrl-A Ctrl-C Ctrl-V Move-Mouse Left-Click. It turns out the * original code was checking only the first event to see if it was a Note * event. For sequences that started with a Control Change or Program Change * (or other non-Note events), the highest note was never modified, and none * of the note events were adjusted. * * Finally, we only want to transpose note events (i.e. alter m_data[0]), * and not other kinds of events. We still need to figure out what to do * with aftertouch, though. Currently likely to be covered by the processing * of the note that it accompanies. * * \threadsafe * * \param tick * The time destination for the paste. This represents the "x" coordinate * of the upper left corner of the paste-box. It will be converted to an * offset, for example pasting every event 48 ticks forward from the * original copy. * * \param note * The note/pitch destination for the paste. This represents the "y" * coordinate of the upper left corner of the paste-box. It will be * converted to an offset, for example pasting every event 7 notes * higher than the original copy. */ bool sequence::paste_selected (midipulse tick, int note) { automutex locker(m_mutex); eventlist clipbd = sm_clipboard; /* copy the clipboard */ push_undo(); /* push undo, no lock */ bool result = m_events.paste_selected(clipbd, tick, note); if (result) modify(); return result; } /** * This function can be tricky. Do we want to make sure the beat-widths and * beats-per-bar match, or force the destination to match? We also need to * lengthen the destination if necessary. */ bool sequence::merge_events (const sequence & source) { const eventlist & clipbd = source.events(); bool result = true; int bw = source.get_beat_width(); int bpb = source.get_beats_per_bar(); midipulse len = source.get_length(); automutex locker(m_mutex); bool same = len == get_length(); /* no change no problem */ set_beat_width(bw); set_beats_per_bar(bpb); if (! same) { if (len > get_length()) /* enlarge, not shrink */ result = set_length(len, false, false); } if (result) { push_undo(); /* push undo, no lock */ result = m_events.merge(clipbd); /* merge clipbd to here */ if (result) modify(); } return result; } /** * Changes the event data range. Changes only selected events, if there are * any selected events. Otherwise, all events intersected are changed. This * function is used in qseqdata to implement the line/height functionality. * We also match tempo events here. But we have to treat them differently from * the matched status events. We also match tempo events here. But we have to * treat them differently from the matched status events. * * \threadsafe * * Let t == the current tick value; ts == tick start value; tf == tick * finish value; ds = data start value; df == data finish value; d = the * new data value. Then * \verbatim df (t - ts) + ds (tf - t) d = -------------------------- tf - ts \endverbatim * * If this were an interpolation formula it would be: * \verbatim t - ts d = ds + (df - ds) --------- tf - ts \endverbatim * * Something is not quite right; to be investigated. * * \param tick_s * Provides the starting tick value. * * \param tick_f * Provides the ending tick value. * * \param status * Provides the event status that is to be changed. * * \param cc * Provides the event control value. * * \param data_s * Provides the starting data value. * * \param data_f * Provides the finishing data value. * * \return * Returns true if the data was changed. */ bool sequence::change_event_data_range ( midipulse tick_s, midipulse tick_f, midibyte status, midibyte cc, int data_s, int data_f, bool finalize ) { automutex locker(m_mutex); bool result = false; bool haveselection = any_selected_events(status, cc); for (auto & er : m_events) { bool match = false; if (haveselection && ! er.is_selected()) continue; midipulse tick = er.timestamp(); match = er.is_desired_ex(status, cc); if (match) { if (tick > tick_f) /* in range? */ break; else if (tick < tick_s) /* in range? */ continue; } else continue; if (tick_f == tick_s) tick_f = tick_s + 1; /* no divide-by-0 */ int newdata = ( (tick - tick_s) * data_f + (tick_f - tick) * data_s ) / (tick_f - tick_s); newdata = int(clamp_midibyte_value(newdata)); /* 0 to 127 */ if (er.is_tempo()) { midibpm tempo = note_value_to_tempo(midibyte(newdata)); result = er.set_tempo(tempo); } else { midibyte d0, d1; er.get_data(d0, d1); if (event::is_one_byte_msg(status)) /* patch | pressure */ d0 = newdata; else if (event::is_two_byte_msg(status)) d1 = newdata; er.set_data(d0, d1); result = true; } if (result && finalize) modify(); } return result; } /** * Changes the event data range in a relative fashion, as opposed to plain * (line) fashion. This function is used if there are events in the given * tick range. * * \threadsafe * * \param tick_s * Provides the starting tick value. * * \param tick_f * Provides the ending tick value. * * \param status * Provides the event status that is to be changed. * * \param cc * Provides the event control value. * * \param newval * Provides the new data value for (additive) "scaling". * * \return * Returns true if the data was changed. */ bool sequence::change_event_data_relative ( midipulse tick_s, midipulse tick_f, midibyte status, midibyte cc, int newval, bool finalize ) { automutex locker(m_mutex); bool result = false; bool haveselection = any_selected_events(status, cc); for (auto & er : m_events) { bool match = false; if (haveselection && ! er.is_selected()) continue; midipulse tick = er.timestamp(); match = er.is_desired_ex(status, cc); if (match) { if (tick > tick_f) /* in range? */ break; else if (tick < tick_s) /* in range? */ continue; } else continue; if (er.is_tempo()) { midibpm tempo = note_value_to_tempo(midibyte(newval)); result = er.set_tempo(tempo); } else { /* * Two-byte messages: Note On/Off, Aftertouch, Control, Pitch. * One-byte messages: Program or Channel Pressure. */ midibyte d0, d1; er.get_data(d0, d1); int newdata = int(clamp_midibyte_value(d1 + newval)); if (event::is_one_byte_msg(status)) d0 = newdata; else d1 = newdata; er.set_data(d0, d1); result = true; } if (result && finalize) modify(); } return result; } /** * Modifies data events according to the parameters active in the LFO window. * If the event is in the selection, or there is no selection at all, and if * it has the desired status and not CC, or the desired status and the correct * control-change number, then we will modify (set) the event. * * These parameters are now encapsulated in struct lfoparameters, plus * a new parameters to flag scaling existing events by the waveform values. * * lfo_dc_offset (DC): * Provides the base amplitude for the event data value. Ranges from 0 * to 127 in increments of 0.1. This amount is added to the result of * the wave_func() calculation. * * lfo_range (depth, R): * Provides the range for the event data value. Ranges from 0 to * 127 in increments of 0.1. * * lfo_periods (speed, P): * Provides the number of periods in the measure or the full length for the * modifications. Ranges from 0 to 16 in increments of 0.01. * * lfo_phase (phi): * The phase of the event modification. Ranges from 0 to 360 degrees. * * Old: Ranges from 0 to 1 in increments of 0.01. This represents a * phase shift of 0 to 360 degrees. * * lfo_waveform (wavetype): * The wave type to apply. See enum class wave in the calculations * module. * * lfo_use_measure: * If true, then use a measure as the length for wave periodicity, rather * than the full length of the sequence. * * lfo_multiply: * If true, scale the existing data using the waveform, rather than * generating data via the waveform. * * \param status * The status (event type) for the kind of events to be modified. * * \param cc * Provides the control-change value for Control Change events that are * to be modified. * * Mapping: * * a = 0 a = 360 degrees a = 360 * P * ω = 0 ω = 2π radians ω = 2π * P * t = 0 t = T ticks t = T * | | | | * |-----------------------|-------...---|-----------------------| * | | | | * x = 0 1.O ... 1.0 x = 1.0 * * ω(t) t 2πPt * ----- = --- ===> ω(t) = -----, so the factor is 2πP/T = Wf * 2πP T T * * Note that a, ω (omega, w), and T are always increasing, while x is a * sawtooth ramp going from 0.0 to 1.0 for every period P. */ void sequence::change_event_data_lfo ( const lfoparameters & lp, midibyte status, midibyte cc ) { automutex locker(m_mutex); if (get_length() == 0) /* should never happen */ return; waveform wavetype = lp.lfo_waveform; double DC = lp.lfo_dc_offset; double R = lp.lfo_range; double P = lp.lfo_periods; double phi = lp.lfo_phase * M_PI / 180.0; /* degrees to radians */ double T = lp.lfo_use_measure ? double(measures_to_ticks()) : double(get_length()); double Wf = 2.0 * M_PI * P / T; /* tick to radians */ bool multiply = lp.lfo_multiply; bool modified = false; bool noselection = ! any_selected_events(status, cc); bool dc_only = wavetype == waveform::dc; m_events_undo.push(m_events); /* save original data */ if (event::is_pitchbend_msg(status)) { /* * Data values range of 0 to 128, but pitch is a range of 1 << 7. * For generating bend from existing events, we need to simply * multiply the wave-function result by 8192.0. The R in this case * has these meanings: 0 == no pitchbend possible; 64 == use half * height; 128 == full bend. */ R /= 128.0; /* the range ratio */ DC -= 64.0; /* -64 to 64 offset */ DC = 8192.0 * DC / 64.0; /* -8192 to +8192 */ } if (dc_only) R = 1.0; for (auto & er : m_events) { bool match = false; if (noselection || er.is_selected()) match = er.is_desired_ex(status, cc); if (match) { double t = double(er.timestamp()); double w = Wf * t + phi; /* angle in radians */ double v = wave_func(w, wavetype); /* -1.0 to 1.0 */ if (er.is_pitchbend()) { midibyte d0, d1; er.get_data(d0, d1); int p = pitch_value(d0, d1); /* -8192 to +8192 */ if (multiply) { int p2 = v * p + 8192; pitch_data_bytes(p2, d0, d1); er.set_data(d0, d1); } else { int p2 = dc_only ? p + DC : int(8192.0 * R * v + DC) ; p2 += 8192 ; pitch_data_bytes(p2, d0, d1); er.set_data(d0, d1); } } else { if (multiply) { if (er.is_tempo()) { midibpm t = er.tempo(); double t2 = t * v + DC; /* no R */ er.set_tempo(t2); } else { midibyte d0, d1; er.get_data(d0, d1); double datum = event::is_one_byte_msg(status) ? double(er.d0()) : double(er.d1()); int newdata = int(datum * v + DC); /* no R */ if (event::is_one_byte_msg(status)) d0 = midibyte(newdata); else if (event::is_two_byte_msg(status)) d1 = midibyte(newdata); er.set_data(d0, d1); } } else { int newdata = int(R * v + DC); newdata = int(abs_midibyte_value(newdata)); /* 0 - 127 */ if (er.is_tempo()) { midibpm tempo = note_value_to_tempo(midibyte(newdata)); (void) er.set_tempo(tempo); } else { midibyte d0, d1; er.get_data(d0, d1); if (event::is_one_byte_msg(status)) d0 = midibyte(newdata); else if (event::is_two_byte_msg(status)) d1 = midibyte(newdata); er.set_data(d0, d1); } } } modified = true; } } if (modified) modify(); } /** * Validates a scale-factor or measures value for scaling. A static function. * * \param ismeasure * If true, then a much larger value is allowed. * * \return * Returns true if valid. */ bool sequence::valid_scale_factor (double s, bool ismeasure) { bool result = s >= c_scale_min; if (result) { double maximum = ismeasure ? c_measure_max : c_scale_max ; result = s <= maximum; } return result; } /** * Recalculates the number of measures, making sure that values less than 1.0 * become 1, and that, otherwise, the measure count is either very close * (0.01) to the lower integer number, or moved up the next integer measure * value. A static function. */ int sequence::trunc_measures (double measures) { return int(seq66::trunc_measures(measures)); } /** * Does a lot of things at once, to all events in the pattern. * See the qpatternfix dialog. * * Here's the process: * * -# If align_left is set, all events timestamps are decreased by the * offset of the first event, so that the pattern starts at time 0. * This will not change pattern length, note length, measures, and * time signature. The align_right setting is similar. * -# If the fix_type is lengthfix::measures: * -# The scale-factor is calculated by the desired measures divided * by the pattern's current measure value. * -# The pattern length is scaled to the new measure size. * -# If a time-signature is specifed, that is applied to the * pattern. * -# If the fix_type is lengthfix::rescale, then the scale factor is * used directly. What should happen? * - All event timestamps are moved according to the expansion or * compression scale factor. * - The measure count and time signature remain the same. * -# If quantization or tightened are supplied, these operations are * performed. * -# The maximum timestamp is calculated, and the measures (and hence * length) may end up being modified. * * See the documentation for the fixparameters structure in the sequence.hpp * module. Apart from the numeric settings (and note-map file-name), the * following enumerations from the caluclations module apply: * * - lengthfix: none, measures, or rescale. * - The effect depends on the measures number as entered by the * user. There are three choices for the measures number: * - Strict integer. "lengthfix::measures" is used, and * the changes yields a non-1.0 scale factor. * - Float (i.e. with a decimal point), then * "lengthfix::rescale" is used. * The difference??? TBD. * - Time signature (e.g. "3/4"). Again the setting is * "lengthfix::measures), the scale factor is set, and * in addition, the use-time-signature flag is set. In this * process, the number of measures might not actually change. * Note that once specified, the number of measures is rounded * up to the nearest integral value. * * - alteration: none, quantize, jitter, etc. * - fixeffect: none, shifted, shrunk, etc. This starts out as none. * The fix function below then sets bits according to the adjustments * actually made. * * \param [inout] fp * Provides input to the changes to be made, and also receives any * side-effects of the change. * * \return * Returns true if the fixup succeeded. */ bool sequence::fix_pattern (fixparameters & fp) { automutex locker(m_mutex); double newmeasures = fp.fp_measures; double newscalefactor = fp.fp_scale_factor; bool result = valid_scale_factor(newscalefactor) && valid_scale_factor(newmeasures, true); if (result) { bool doscale = fnotequal(newscalefactor, 1.0); bool shrunk = flessthan(newscalefactor, 1.0); midipulse currentlen = get_length(); midipulse newlength = 0; fixeffect tempefx = fixeffect::none; push_undo(); if (fp.fp_align_left) { fp.fp_align_left = m_events.align_left(); /* realigned? */ if (fp.fp_align_left) tempefx = bit_set(tempefx, fixeffect::shifted); else result = false; /* op failed */ } else if (fp.fp_align_right) { fp.fp_align_right = m_events.align_right(); /* realigned? */ if (fp.fp_align_right) tempefx = bit_set(tempefx, fixeffect::shifted); else result = false; /* op failed */ } if (result && (fp.fp_reverse || fp.fp_reverse_in_place)) { result = m_events.reverse_events(fp.fp_reverse_in_place); if (result) { if (fp.fp_reverse) tempefx = bit_set(tempefx, fixeffect::reversed); else tempefx = bit_set(tempefx, fixeffect::reversed_abs); } } if (fp.fp_alter_type != alteration::none) { switch (fp.fp_alter_type) { case alteration::tighten: result = m_events.quantize_events ( fp.fp_tighten_range, 1, true /* all events */ ); if (result) tempefx = bit_set(tempefx, fixeffect::alteration); break; case alteration::quantize: result = m_events.quantize_events ( fp.fp_quantize_range, 1, true /* all events */ ); if (result) tempefx = bit_set(tempefx, fixeffect::alteration); break; case alteration::jitter: result = jitter_notes(fp.fp_jitter_range, true); if (result) tempefx = bit_set(tempefx, fixeffect::alteration); break; case alteration::random: result = randomize_note_velocities(fp.fp_random_range, true); if (result) tempefx = bit_set(tempefx, fixeffect::alteration); break; case alteration::random_pitch: result = randomize_note_pitches(fp.fp_pitch_range, true); if (result) tempefx = bit_set(tempefx, fixeffect::alteration); break; case alteration::notemap: result = ! fp.fp_notemap_file.empty(); if (result) { result = perf()->repitch_fix ( fp.fp_notemap_file, *this, false /* forward */ ); if (result) tempefx = bit_set(tempefx, fixeffect::alteration); } break; case alteration::rev_notemap: result = ! fp.fp_notemap_file.empty(); if (result) { result = perf()->repitch_fix ( fp.fp_notemap_file, *this, true /* reverse */ ); if (result) tempefx = bit_set(tempefx, fixeffect::alteration); } break; default: break; } } else if (result) { bool fixmeasures = fp.fp_fix_type == lengthfix::measures; bool fixscale = fp.fp_fix_type == lengthfix::rescale; bool timesig = fp.fp_use_time_signature; if (timesig) { /* * We no longer apply length or set the beats and width * here. It is done in qpatternfix::slot_set(), along * with a verify-and-link to ensure refresh. We do have * to scale here, unless converting 4/4/to 8/8. */ if (doscale) { newlength = apply_time_factor ( newscalefactor, fp.fp_save_note_length ); result = newlength > 0; if (result) tempefx = bit_set(tempefx, fixeffect::time_sig); } } else if (fixmeasures) { if (doscale) /* must do 1st */ { newlength = apply_time_factor ( newscalefactor, fp.fp_save_note_length ); result = newlength > 0; } } else if (fixscale) { newlength = apply_time_factor ( newscalefactor, fp.fp_save_note_length ); result = newlength > 0; } if (result && ! timesig && (newlength != currentlen)) { int measures = get_measures(newlength); if (fixmeasures) { if (measures < int(newmeasures)) measures = int(newmeasures); } (void) apply_length(measures); } if (result) { fp.fp_length = newlength; fp.fp_measures = double(get_measures()); fp.fp_scale_factor = newscalefactor; fp.fp_effect = tempefx; if (shrunk) tempefx = bit_set(tempefx, fixeffect::shrunk); else tempefx = bit_set(tempefx, fixeffect::expanded); } } else pop_undo(); } return result; } /** * Adds a note of a given length and note value, at a given tick * location. It adds a single Note-On/Note-Off pair. * * Supports the step-edit (auto-step) feature, where we are entering notes * without playback occurring, so we set the generic default note length and * volume to the snap. There are two ways to enter notes: * * - Mouse movement in the seqroll. Here, velocity defaults to the * preserve_velocity> * - Input from a MIDI keyboard. Velocity ranges from 0 to 127. * * Will be consistent with how Note On velocity is handled; enable 0 velocity * (a standard?) for Note Off when not playing. Note that the event * constructor sets channel to 0xFF, while event::set_data() currently sets * it to 0!!! * * The paint parameter indicates if we care about the painted event, so then * the function runs though the events and deletes the painted ones that * overlap the ones we want to add. An event is painted if manually * created in the seqroll. There is also a case in the add_chord() function. * * Also note that push_undo() is not incorporated into this function, for * the sake of speed. * * Here, we could ignore events not on the sequence's channel, as an option. * We have to be careful because this function can be used in painting notes. * * Stazed: * * http://www.blitter.com/~russtopia/MIDI/~jglatt/tech/midispec.htm * * Note Off: The first data is the note number. There are 128 possible * notes on a MIDI device, numbered 0 to 127 (where Middle C is note * number 60). This indicates which note should be released. The second * data byte is the velocity, a value from 0 to 127. This indicates how * quickly the note should be released (where 127 is the fastest). It's * up to a MIDI device how it uses velocity information. Often velocity * will be used to tailor the VCA release time. MIDI devices that can * generate Note Off messages, but don't implement velocity features, * will transmit Note Off messages with a preset velocity of 64. * * Seq24 never used the recording-velocity member (m_rec_vol). We use it to * modify the new m_note_on_velocity member if the user changes it in the * seqroll. * * \threadsafe * * \param tick * The time destination of the new note, in pulses. * * \param len * The duration of the new note, in pulses. * * \param note * The pitch destination of the new note. We no longer check it. Either * it is a good value from a MIDI device, or the caller (qseqroll) ensures * it. * * \param repaint * If true, repaint the whole set of events, in order to be left with * a clean view of the inserted event. We run through the events, deleting * the painted ones that overlap the one we want to add. The default is * false. * * \param velocity * If not set to the preserve-velocity, the velocity of the note is * set to this value. Otherwise, it is hard-wired to the stored note-on * velocity. The name of this macro is counter-intuitive here. * * \return * Returns true if the event was added. */ bool sequence::add_painted_note ( midipulse tick, midipulse len, int note, bool repaint, int velocity ) { bool result { false }; bool ignoreit { false }; if (repaint) /* see banner above */ { automutex locker(m_mutex); ignoreit = remove_duplicate_events(tick, note); } if (ignoreit) { result = true; } else { bool hardwire { velocity == usr().preserve_velocity() }; midibyte v { midibyte(hardwire ? m_note_on_velocity : velocity) }; event e(tick, EVENT_NOTE_ON, midi_channel(), note, v); if (repaint) e.paint(); result = add_event(e); if (result) { midibyte v { midibyte(hardwire ? m_note_off_velocity : 0) }; event e(tick + len, EVENT_NOTE_OFF, midi_channel(), note, v); result = add_event(e); } if (result && expanded_recording()) set_last_tick(tick + len); } if (result) { if (verify_and_link()) /* do it again anyway */ modify(); /* no easy way to undo this */ } return result; } /** * Overload for use with auto-step MIDI keyboard input. The version above * always sets channel to 0, and can repaint, and is used by the seqroll. * * Note: Like the version above, this code simulates a Note Off. We will * fix that AT SOME POINT. * * \param len * The length between the Note On and Note Off events to be added here. * * \param e * The Note On to use for the Note On and Note Off event additions. This * event already has its timestamp and velocity tailored. */ bool sequence::add_note (midipulse len, const event & e) { bool result = add_event(e); if (result) { midipulse tick = e.timestamp() + len; midibyte v = midibyte(m_note_off_velocity); event eoff(tick, EVENT_NOTE_OFF, e.channel(), e.get_note(), v); result = add_event(eoff); } if (result) result = verify_and_link(); if (result) { /* * Notification of step-edit note entry from a keyboard causes: * * QObject::setParent: Cannot set parent, new parent is in a different * thread * * So we pass false to avoid the notification. However, this causes a * delay in refreshing the notes in the pattern editor. * * modify(false); */ m_is_modified = true; set_dirty(); perf()->notify_sequence_change(seq_number(), performer::change::no); } return result; } /** * An overload to ignore painting values and increase efficiency during input * recording. * * Will be consistent with how Note On velocity is handled; enable 0 velocity * (a standard?) for Note Off when not playing. Note that the event * constructor sets channel to 0xFF, while event::set_data() currently sets * it to 0!!! * * Add note, preceded by a push-undo. This is meant to be used only by the * user-interface, when manually entering notes. */ bool sequence::push_add_note ( midipulse tick, midipulse len, int note, bool repaint, int velocity ) { m_events_undo.push(m_events); /* push_undo(), no lock */ return add_painted_note(tick, len, note, repaint, velocity); } bool sequence::push_add_chord ( int chord, midipulse tick, midipulse len, int note, int velocity ) { m_events_undo.push(m_events); /* push_undo(), no lock */ return add_chord(chord, tick, len, note, velocity); } /** * Adds a chord of a given length and note value, at a given tick * location. * * \todo * Add the ability to preserve the incoming velocity. * * \threadsafe * * \param chord * If greater than 0 (and valid), a chord (multiple notes) will be * generated using this chord in the c_chord_table[] array. Otherwise, * only a single note will be added. * * \param tick * The time destination of the new note, in pulses. * * \param len * The duration of the new note, in pulses. * * \param note * The pitch destination of the new note. * * \return * Returns true if the events were added. */ bool sequence::add_chord ( int chord, midipulse tick, midipulse len, int note, int velocity ) { bool result = false; if (chord > 0 && chord_number_valid(chord)) { const chord_notes & cn = chord_entry(chord); for (auto cnote : cn) { if (cnote == -1) break; result = add_painted_note(tick, len, note + cnote, false, velocity); if (! result) break; } } else result = add_painted_note(tick, len, note, true, velocity); return result; } bool sequence::add_tempo (midipulse tick, midibpm tempo, bool repaint) { automutex locker(m_mutex); bool valid = tempo >= usr().midi_bpm_minimum() && tempo <= usr().midi_bpm_maximum(); bool result = valid && tick >= 0; if (result) { if (repaint) (void) remove_duplicate_events(tick); event e(tick, tempo); if (repaint) e.paint(); result = add_event(e); /* * Currently we draw tempo as a circle, not a bar, so we don't need * to know about the "next" tempo event. We don't (yet) need * to link tempo events: verify_and_link(); */ if (result) modify(); /* added ca 2022-04-25 */ } return result; } /** * Use for adding Tempo events via a drag-line in qseqdata. The events are * added at each snap point in the tick range. Currently only a linear * sequence of tempos can be added. The formula is linear: * \verbatim (B1 - B0) (t - t0) B = -------------------- + B0 (t1 - t0) \endverbatim * * We should generalize this is the calculations module at some point. * * \param tick_s * Provides the starting tick value. Might get adjusted to the previous * snap value. This is t0 above. * * \param tick_f * Provides the ending tick value. Might get adjusted to the next * snap value. This is t1 above. * * \param tempo_s * Provides the starting tempo value. This is B0 above. * * \param tempo_f * Provides the finishing tempo value. This is B1 above. * * \return * Returns true if all tempos in the time-range were added. */ bool sequence::add_tempos ( midipulse tick_s, midipulse tick_f, int data_s, int data_f ) { automutex locker(m_mutex); bool result = false; midipulse S = snap(); midibpm B0 = note_value_to_tempo(midibyte(data_s)); midibpm B1 = note_value_to_tempo(midibyte(data_f)); midipulse t0 = down_snap(S, tick_s); midipulse t1 = up_snap(S, tick_f); /* later truncate to seq length */ double slope = (B1 - B0) / double(t1 - t0); for (midipulse t = t0; t <= t1; t += S) { double value = slope * double(t - t0) + B0; result = add_tempo(t, value); if (! result) break; } return result; } /** * Constructs a time-signature event and appends it to the pattern. It is * used in the pattern editor. * * In setting the time signature here, all we want to do is change the * beats and beat width. The clocks_per_metronome() and * get_32nds_per_quarter() stay the same, as they would affect the whole tune. * * Data: FF 58 04 n d c b */ bool sequence::log_time_signature ( midipulse tick, int beats, int bw, bool user_change ) { automutex locker(m_mutex); bool result = beats > 0 && is_power_of_2(bw); if (result) { if (user_change) m_events_undo.push(m_events); /* push_undo(), no lock */ result = add_timesig_event(tick, beats, bw); } return result; } bool sequence::update_time_signature (int bpb, int bw, bool user_change) { set_beats_per_bar(bpb, user_change); set_beat_width(bw, user_change); set_measures(get_measures(), user_change); bool ok = seq_number() == 0; if (ok) return log_time_signature(0, bpb, bw); else return true; } /** * Gets the data from a time-signature event and makes some settings. * Then it adds the event. See its usage in the midifile class. * It is *not* considered a modification. * * Variables: * * - bpb: Provides beats/bar (i.e. beats per measure). * - bw: Provides beat-width (i.e. denominator of the time signature). * - cpm: Provides clocks/metronome. * - tpq: Provides 32nds/quarter. * * \param e * The event, which must be a well-defined time-signature event. * * \param settimesig * True (the default is false) if it needs to be set as the main * sequence time-signature via a c_timesig SeqSpec. * * \return * Returns true if the event was a time-signature and was added to * the pattern. */ bool sequence::add_timesig_event (const event & e, bool settimesig) { automutex locker(m_mutex); bool result = e.is_time_signature(); if (result) { int bpb = e.get_sysex(0); int bw = beat_power_of_2(e.get_sysex(1)); int cpm = e.get_sysex(2); int tpq = e.get_sysex(3); /* was 4, oops!! */ if (cpm == 0) cpm = c_midi_clocks_per_metronome; /* 24 */ if (tpq == 0) tpq = c_midi_32nds_per_quarter; /* 8 */ if (settimesig) { clocks_per_metronome(cpm); set_32nds_per_quarter(tpq); set_time_signature(bpb, bw); m_timesig_beats_per_measure = bpb; m_timesig_beat_width = bw; } result = append_event(e); if (result) sort_events(); } return result; } /** * This overloaded version creates a time-signature event. If there is a * time-signature event at the same time, it can be eliminated. This * function is meant to insert a time-signature specified in the * user-interface, or in setting up a new time-signature for * a MIDI pattern that does not have one. * * It does not change the clocks/metronome and 32nds/quarter, and in * fact they will default to 24 and 8, respectively. Is this an issue? * * This function flags a modification. * * \param tstamp * Provides the time-stamp (divisions, ticks) at which the event * should be added. * * \param bpb * Provides the beats/bar to apply. Defaults to 4. * * \param bw * Provides the beat-width to apply. Defaults to 4. * * \param replace * If true (the default), delete any existing time-signature * at that time. * * \return * Returns true if the event could be added. */ bool sequence::add_timesig_event ( midipulse tstamp, int bpb, int bw, bool replace ) { automutex locker(m_mutex); midibytes bt; bt.push_back(midibyte(bpb)); bt.push_back(midibyte(log2_of_power_of_2(bw))); /* or beat_log2()? */ bt.push_back(midibyte(clocks_per_metronome())); bt.push_back(midibyte(get_32nds_per_quarter())); event e(tstamp, EVENT_META_TIME_SIGNATURE, bt); bool modification = false; if (replace) modification = m_events.remove_time_signature(tstamp); bool result = append_event(e); if (result) { if (tstamp == 0) set_time_signature(bpb, bw); sort_events(); if (modification) modify(true); } return result; } /** * Look for a starting time-signature event. If present, make sure it * is set in the beats/bar and beat-width fields for later storage * in the c_timesig SeqSpec. * * If not present, use the c_timesig-related fields to create a * time-signature event. If they are zero, devolve to 4/4. */ bool sequence::set_main_time_signature () { automutex locker(m_mutex); bool result = false; midipulse tstamp; int numerator; int denominator; if (detect_time_signature(tstamp, numerator, denominator, 0, get_ppqn()/4)) { m_time_beats_per_measure = m_timesig_beats_per_measure = numerator; m_time_beat_width = m_timesig_beat_width = denominator; result = true; } else { int bpb = m_timesig_beats_per_measure; if (bpb == 0) bpb = 4; int bw = m_timesig_beat_width; if (bw == 0) bw = 4; result = add_c_timesig(bpb, bw, true); } return result; } /** * Called if there is a c_timesig present *and* no time-signature event * has been encountered yet. See the midifile module/class. * * \param bpb * Provides the beats/bar to apply. * * \param bw * Provides the beat-width to apply. * * \param settimesig * Set to true if the first time-signature (event or SeqSpec) has been * encountered. If true, a time-signature at time 0 is added. * * \return * Returns true if the event could be added, or did not need to be * added. */ bool sequence::add_c_timesig (int bpb, int bw, bool settimesig) { automutex locker(m_mutex); bool result = true; bool hardwired_bpb_bw = false; if (bpb == 0 || bw == 0) { if (bpb == 0) bpb = get_beats_per_bar(); /* from 1st time sig event */ if (bpb == 0) { bpb = 4; hardwired_bpb_bw = true; } if (bw == 0) bpb = get_beat_width(); /* from 1st time sig event */ if (bw == 0) { bw = 4; hardwired_bpb_bw = true; } } if (hardwired_bpb_bw) { set_beats_per_bar(bpb); set_beat_width(bw); } if (m_timesig_beats_per_measure == 0) { if (hardwired_bpb_bw) warnprint("Null c_timesig SeqSpec, setting to 4/4"); else warnprint("Null c_timesig SeqSpec, fixing"); m_time_beats_per_measure = bpb; m_timesig_beat_width = bw; } if (settimesig) { midibyte logbase2 = beat_log2(bw); midibytes bt; bt.push_back(midibyte(bpb)); bt.push_back(logbase2); bt.push_back(0); /* c_midi_clocks_per_metronome) */ bt.push_back(0); /* c_midi_32nds_per_quarter) */ midipulse tstamp = 0; /* time of actual time-signature event */ event e(tstamp, EVENT_META_TIME_SIGNATURE, bt); result = add_timesig_event(e, true); } return result; } bool sequence::delete_time_signature (midipulse tick) { bool result = false; event e(tick, EVENT_MIDI_META); midibytes bt(4); /* bt[0] = bt[1] = bt[2] = bt[3] = 0; */ result = e.append_meta_data(EVENT_META_TIME_SIGNATURE, bt); if (result) result = remove_first_match(e, tick); return result; } /** * This function looks for a time-signature, and if it is within the first * snap interval, sets the beats value accordingly. * * \param [out] tstamp * The timestamp of the time-signature event, if one is found. * * \param [out] numerator * A return parameter for the beats per bar. Use the value only if true * is returned. * * \param [out] denominator * A return parameter for the beat width. Use the value only if true * is returned. * * \param start * Provides the starting point for the search. Events before this tick * are ignored. The default value is 0, the beginning of the pattern. * * \param range * Provides how far to look for a time signature from the start time. * The default is c_null_midipulse, which means just detect the * first time-signature no matter how deep into the pattern. * Another useful value is the snap() value from the seqedit. * See qseqeditframe64::detect_time_signature() for an example. * * \return * Returns true if a time signature was detected before the range was * reached. The three return values can then be assumed valid. */ bool sequence::detect_time_signature ( midipulse & tstamp, int & numerator, int & denominator, midipulse start, midipulse range ) { bool result = false; auto cev = cbegin(); if (! cend(cev)) { if (get_next_meta_match(EVENT_META_TIME_SIGNATURE, cev, start, range)) { tstamp = cev->timestamp(); numerator = int(cev->get_sysex(0)); denominator = beat_power_of_2(int(cev->get_sysex(1))); result = true; } } return result; } /** * Adds an event to the internal event list in a sorted manner. Then it * resets the draw-marker and sets the dirty flag. * * Currently, when reading a MIDI file [see the midifile::parse() function], * only the main events (notes, after-touch, pitch, program changes, etc.) * are added with this function. So, we can rely on reading only playable * events into a sequence. Well, actually, certain meta-events are also * read, to obtain channel, buss, and more settings. Also read for a * sequence, if the global-sequence flag is not set, are the new key, scale, * and background sequence parameters. * * This module (sequencer) adds all of those events as well, but it * can surely add other events. We should assume that any events * added by sequencer are playable/usable. * * Here, we could ignore events not on the sequence's channel, as an option. * We have to be careful because this function can be used in painting events. * * To speed things up a bit, do not try to verify and link notes unless the * incoming event is a note. We will first try allowing it only for a Note * Off for even more savings :-D * * Also, instead of modifying and notifying, we just modify. We need to see * if this prevents a weird unknown-signal error in qseqeditframe64 in the * update_midi_buttons() function. We don't need that to see added note * events anyway. * * \threadsafe * * \warning * This pushing (and, in writing the MIDI file, the popping), * causes events with identical timestamps to be written in * reverse order. Doesn't affect functionality, but it's puzzling * until one understands what is happening. Actually, this is true only * in Seq24, we've fixed that behavior for Seq66. * * \param er * Provide a reference to the event to be added; the event is copied into * the events container. * * \return * Returns true if the event was added. */ bool sequence::add_event (const event & er) { automutex locker(m_mutex); bool result = m_events.append(er); /* no-sort insertion of event */ if (result) { if (er.is_note_off()) (void) verify_and_link(); /* for proper seqroll draw; sorts */ /* * Is this change safe? No. Do not allow it to call notify_change(), * otherwise one gets a segfault when pounding the keyboard with notes * and/or pitchbend. Keep it false. */ modify(false); } return result; } /** * An alternative to add_event() that does not sort the events, even if the * event list is implemented by an std::list. This function is meant mainly * for reading the MIDI file, to save a lot of time. We could also add a * channel parameter, if the event has a channel. This reveals that in * midifile and wrkfile, we update the channel setting too many times. * * \param er * Provide a reference to the event to be added; the event is copied into * the events container. * * \return * Returns true if the event was appended. */ bool sequence::append_event (const event & er) { automutex locker(m_mutex); return m_events.append(er); /* does *not* sort, too time-consuming */ } void sequence::sort_events () { automutex locker(m_mutex); m_events.sort(); } event sequence::find_event (const event & e, bool nextmatch) { automutex locker(m_mutex); static event s_null_result{0, 0, 0}; event::iterator evi = nextmatch ? m_events.find_next_match(e) : m_events.find_first_match(e) ; return evi != m_events.end() ? *evi : s_null_result ; } sequence::note_info sequence::find_note (midipulse tick, int note) { bool found = false; note_info result; for (auto cev = cbegin(); ! cend(cev); ++cev) { draw status = get_note_info(result, cev); if (status == draw::linked || status == draw::note_on) { found = tick >= result.start() && tick < result.finish() && result.note() == note; if (found || result.start() > tick) break; } } if (! found) result.ni_note = (-1); return result; } /** * If we add a check for note equality before setting the result flag, * the result is that moving the mouse vertically when a note is already * present at that time does not add a new note, but causes the existing * note to move vertically. So now we don't mark a matching note * for removal; we tell the caller to ignore the event and not * add it. * * Also, this function is used when adding tempos as well! * * \param tick * The insertion time of the current note. * * \param note * The value of the note. Or, if equal to the default, -1, the * event is not a note. * * \return * Returns true if the current note should be ignored, because * there is already a matching note present. */ bool sequence::remove_duplicate_events (midipulse tick, int note) { automutex locker(m_mutex); bool result { false }; bool marked { false }; for (auto & er : m_events) { bool evpresent { er.is_painted() && er.timestamp() == tick }; if (evpresent) { if (note != (-1)) { bool evmatch { er.is_note_on() && note == er.get_note() }; if (evmatch) { result = true; /* ignore (don't add) the note */ break; } } else /* not a note event, mark it */ { er.mark(); if (er.is_linked()) er.link()->mark(); marked = true; set_dirty(); } } } if (marked) (void) remove_marked(); return result; } /** * Adds a event of a given status value and data values, at a given tick * location. * * The paint parameter indicates if we care about the painted event, * so then the function runs though the events and deletes the painted * ones that overlap the ones we want to add. * * \threadsafe * * \param tick * The time destination of the event. * * \param status * The type of event to add. * * \param d0 * The first data byte for the event. * * \param d1 * The second data byte for the event (if needed). * * \param repaint * If true, the inserted event is marked for painting. The default value * is false. Also, if true, then events that are marked as painted and match * the tick parameter are marked. */ bool sequence::add_event ( midipulse tick, midibyte status, midibyte d0, midibyte d1, bool repaint ) { automutex locker(m_mutex); bool result = tick >= 0; if (result) { if (repaint) (void) remove_duplicate_events(tick); event e(tick, status, d0, d1); if (repaint) e.paint(); result = m_events.append(e); /* (add_event(e) locks & verifies) */ if (result) { (void) verify_and_link(); /* might be no note events to link */ modify(); /* call notify_change() */ } } return result; } /** * The following two functions are meant only for the "Insert macro at L" * functionality. * * The add_event() version adds a single event. The add_macro() checks * for multiple events in one macro... */ bool sequence::add_event (midipulse tick, const midibytes & dbytes) { event ev = create_event(tick, dbytes); return add_event(ev); } bool sequence::add_macro (midipulse tick, const midimacro & macro) { bool result = macro.is_valid(); if (result) { for (int i = 0; i < macro.event_count(); ++i) { const midibytes & dbytes = macro.bytes(i); result = add_event(tick, dbytes); if (! result) break; } } return result; } /** * Handles loop/replace status on behalf of seqrolls. This sets the * loop-reset status, which is checked in the stream_event() function in * this module [WRONG; it is check in qseqeditframe64]. This status is * set when the time-stamp remainder is less than a quarter note, * meaning we have just gotten back to the beginning of the loop. * See the call in qseqeditframe64. */ bool sequence::check_loop_reset () { bool result = false; midipulse ts = perf()->get_tick(); midipulse len = get_length(); if (len > 0 && ts > len) { midipulse tsmod = ts % len; if (tsmod < (m_ppqn / c_reset_divisor)) { bool check = overwriting(); if (check && perf()->is_running()) { loop_reset(true); result = true; } } } return result; } /** * This function is meant to return false until a note is struck, whether before * the first end-of-pattern is reached or after that. * * Special case, call only when playback is running: perf()->is_running()). * * \return * Returns true if one-shot is over. */ bool sequence::check_oneshot_recording () { bool result = false; if (oneshot_recording()) { midipulse len = get_length(); if (len > 0) { /* * Issues: (1) the second note is recalculated; (2) a note off * might appear outside. */ midipulse ts = perf()->get_tick(); if (note_count() > 0) { if (note_count() == 1) { midipulse tmod = ts % len; midipulse tbeg = ts - tmod; m_next_boundary = tbeg + len - 1; } else result = ts > m_next_boundary; } } } return result; } /** * Streams (records) the given event. The event's timestamp is adjusted, if * needed. If recording: * * - Pattern is playing. * - Pattern is no playing. * - If one-shot recording is in force, and the loop has reset, * return with a value of false. * - If quantized/tightened/note-mapped recording is in force, * the note timestamp or pitch value is altered. * - If the pattern is playing, the event is added. * - If not playing, but the event is a Note On or Note Off, we add it * and keep track of it. * * If MIDI Thru is enabled, the event is also put on the buss. * * This function supports rejecting events if the channel doesn't match that * of the sequence. We do it here for comprehensive event support. Also * make sure the event-channel is preserved before this function is called, * and also need to make sure that the channel is appended on both playback * and in saving of the MIDI file. * * If in overwrite loop-record mode, any events after reset should clear the * old items from the previous pass through the loop. * * \todo * If the last event was a Note Off, we should clear it here, and * how? * * The m_rec_vol member includes the "Free" menu entry in seqedit, which sets * the velocity to the preserve-velocity (-1). * * If the pattern is not playing, this function supports the step-edit * (auto-step) feature, where we are entering notes without playback * occurring, so we set the generic default note length and volume to the * snap. For Note Ons, this isgnores the actual Note Off and synthesizes a * matching Note Off. * * \threadsafe * * \param ev * Provides the event to stream. * * \return * Returns true if the event's channel matched that of this sequence, and * the channel-matching feature was set to true. Also returns true if * we're not using channel-matching. A return value of true means the * event should be saved. */ bool sequence::stream_event (event & ev) { automutex locker(m_mutex); bool result = channels_match(ev); /* set if channel matches */ if (result) { if (loop_reset()) { if (overwriting()) { loop_reset(false); remove_all(); /* vs m_events.clear() */ set_dirty(); } else if (oneshot_recording()) /* is this necessary??? */ { loop_reset(false); set_recording(toggler::off); set_dirty(); } } /* * If we are in expand mode, we do not want to wrap the timestamp. * Expansion will occur only here, when an event is received. */ if (expanded_recording()) { int m = get_measures(perf()->get_tick()); if (m != m_measures) (void) apply_length(m); } else ev.mod_timestamp(get_length()); /* adjust tick */ if (recording()) { if (perf()->is_pattern_playing()) /* playhead moving */ { if (check_oneshot_recording()) return true; if (ev.is_note_on() && m_rec_vol > usr().preserve_velocity()) ev.note_velocity(m_rec_vol); /* modify incoming */ /* * We need to do this before adding the event. Issue #119. */ if (alter_recording() && ev.is_note()) { /* * We want to quantize or tighten note-related events that * comes in, This could potentially alter the note length * by a couple of snaps. So what? Play better! * * Actually, it could result in zero-length notes. */ if (quantizing()) (void) ev.quantize(snap(), get_length()); else if (tightening()) (void) ev.tighten(snap(), get_length()); if (notemapping()) perf()->repitch(ev); } #if defined SEQ66_LINK_NEWEST_NOTE_ON_RECORD /* has issues :-( */ m_events.append(ev); /* does *not* sort */ if (ev.is_note_off()) /* later, tempo? */ m_events.link_new_note(); /* one link no sort */ modify(false); /* no notify call */ #else add_event(ev); /* locks and sorts */ #endif } else /* use auto-step */ { /* * Supports the step-edit (auto-step) feature; see banner. */ if (ev.is_note_off()) { m_last_tick += snap(); if (m_last_tick >= get_length()) { loop_reset(true); m_last_tick = 0; } } else if (ev.is_note_on()) /* WHAT ABOUT AFTERTOUCH? */ { /* * For issue #97, check the last time-stamp only when * one-shot is in force. This allows normal Seq24 * looping-back when the end is reached. */ bool add = true; if (oneshot_recording()) add = m_last_tick < get_length(); if (add) { if (m_rec_vol != usr().preserve_velocity()) ev.note_velocity(m_rec_vol); /* keep veloc. */ midipulse lasttick = mod_last_tick(); perf()->set_left_tick(lasttick + snap()); ev.set_timestamp(lasttick); /* loop back */ bool ok = add_note ( snap() - m_events.note_off_margin(), ev ); if (ok) ++m_notes_on; } } else { /* * Handle everything else without moving the time. * * ev.is_controller() * ev.is_sysex() * ev.is_program_change() */ (void) add_event(ev); } } } if (m_thru) put_event_on_bus(ev); /* * We don't need to link note events until a note-off comes in. * Commenting this out has no apparently effect, but we still can * get extra long notes. (ca 2024-11-26) * * ca 2024-12-27 Shouldn't this be verify_and_link()??? * * (void) m_events.link_new(); */ if (ev.is_note_off()) (void) m_events.verify_and_link(); } return result; } /** * Sets the dirty flags for names, main, and performance. These flags are * meant for causing user-interface refreshes, not for performance * modification. * * m_dirty_names is set to false in is_dirty_names(); m_dirty_main is set to * false in is_dirty_main(); m_dirty_perf is set to false in * is_dirty_perf(). * * \threadunsafe */ void sequence::set_dirty_mp () { m_dirty_names = m_dirty_main = m_dirty_perf = true; } /** * Call set_dirty_mp() and then sets the dirty flag for editing. Note that it * does not call performer::modify(). */ void sequence::set_dirty () { set_dirty_mp(); m_dirty_edit = true; } /** * Returns the value of the dirty names (heh heh) flag, and sets that * flag to false. Not sure that we need to lock a boolean on modern * processors. * * At this point, another thread might read the initial value of the flag, * before it is falsified. Since it is used to initiate updates in callers, * the penalty is that the caller updates when it doesn't have to. * * \threadsafe * * \return * Returns the dirty status. */ bool sequence::is_dirty_names () const { bool result = m_dirty_names; /* atomic */ m_dirty_names = false; /* mutable */ return result; } /** * Returns the value of the dirty main flag, and sets that flag to false * (i.e. resets it). This flag signals that a redraw is needed from * recording. * * \threadsafe * * \return * Returns the dirty status. */ bool sequence::is_dirty_main () const { bool result = m_dirty_main; /* atomic */ m_dirty_main = false; /* mutable */ return result; } /** * Returns the value of the dirty performance flag, and sets that * flag to false. * * \threadsafe * * \return * Returns the dirty status. */ bool sequence::is_dirty_perf () const { bool result = m_dirty_perf; /* atomic */ m_dirty_perf = false; /* mutable */ return result; } /** * Returns the value of the dirty edit flag, and sets that flag to false. * The m_dirty_edit flag is set by the function set_dirty(). * * \threadsafe * * \return * Returns the dirty status. */ bool sequence::is_dirty_edit () const { bool result = m_dirty_edit; /* atomic */ m_dirty_edit = false; /* mutable */ return result; } /** * Plays a note from the piano roll on the main bus on the master MIDI * buss. It flushes a note to the midibus to preview its sound, used by * the virtual piano. * * \threadsafe * * \param note * The note to play. It is not checked for range validity, for the sake * of speed. */ void sequence::play_note_on (int note) { automutex locker(m_mutex); event e(0, EVENT_NOTE_ON, midibyte(note), midibyte(m_note_on_velocity)); if (rc().investigate()) perf()->repitch(e); master_bus()->play_and_flush(m_true_bus, &e, midi_channel(e)); } /** * Turns off a note from the piano roll on the main bus on the master MIDI * buss. * * \threadsafe * * \param note * The note to turn off. It is not checked for range validity, for the * sake of speed. */ void sequence::play_note_off (int note) { automutex locker(m_mutex); event e(0, EVENT_NOTE_OFF, midibyte(note), midibyte(m_note_on_velocity)); if (rc().investigate()) perf()->repitch(e); master_bus()->play_and_flush(m_true_bus, &e, midi_channel(e)); } /* * Track-specific trigger functions. The performer object calls these functions * on behalf of the user-interface. */ bool sequence::clear_triggers () { automutex locker(m_mutex); int count = m_triggers.count(); bool result = count > 0; m_triggers.clear(); if (result) modify(false); /* issue #90 flag change w/o notify */ return result; } void sequence::print_triggers () const { automutex locker(m_mutex); m_triggers.print(m_name); } /** * Adds a trigger. A pass-through function that calls triggers::add(). * See that function for more details. * * \threadsafe * * \param tick * The time destination of the trigger. * * \param len * The duration of the trigger. * * \param offset * The performance offset of the trigger. Defaults to 0. * * \param tpose * The transposition value to store with the trigger. * defaults to 0 (no transposition). * * \param fixoffset * If true, adjust the offset. Defaults to true. */ bool sequence::add_trigger ( midipulse tick, midipulse len, midipulse offset, midibyte tpose, bool fixoffset ) { automutex locker(m_mutex); m_triggers.add(tick, len, offset, tpose, fixoffset); modify(false); /* issue #90 flag change w/o notify */ return true; } #if defined USE_INTERSECT_FUNCTIONS /** * This function examines each trigger in the trigger list. If the given * position is between the current trigger's tick-start and tick-end * values, the these values are copied to the start and end parameters, * respectively, and then we exit. See triggers::intersect(). * * \threadsafe * * \param position * The position to examine. * * \param start * The destination for the starting tick of the matching trigger. * * \param ender * The destination for the ending tick of the matching trigger. * * \return * Returns true if a trigger was found whose start/end ticks * contained the position. Otherwise, false is returned, and the * start and end return parameters should not be used. */ bool sequence::intersect_triggers ( midipulse position, midipulse & start, midipulse & ender ) { automutex locker(m_mutex); return m_triggers.intersect(position, start, ender); } bool sequence::intersect_triggers (midipulse position) { automutex locker(m_mutex); return m_triggers.intersect(position); } /** * This function examines each note in the event list. If the given position * is between the current note's on and off time values, the these * values are copied to the start and end parameters, respectively, and the * note value is copied to the note parameter, and then we exit. * * \threadsafe * * \param position * The tick position to examine; where the mouse pointer is horizontally, * already converted to a tick number. * * \param position_note * This is the position of the mouse pointer vertically, already * converted to a note number. * * \param [out] start * The destination for the starting timestamp of the matching note. * * \param [out] ender * The destination for the ending timestamp of the matching note. * * \param [out] note * The destination for the note of the matching event. * * \return * Returns true if a note-on event was found whose start/end ticks * contained the position. Otherwise, false is returned, and the * start and end return parameters should not be used. */ bool sequence::intersect_notes ( midipulse position, int position_note, midipulse & start, midipulse & ender, int & note ) { automutex locker(m_mutex); auto on = m_events.begin(); auto off = m_events.begin(); while (on != m_events.end()) { event & eon = eventlist::dref(on); if (position_note == eon.get_note() && eon.is_note_on()) { off = on; /* for next "off" */ ++off; /* hope this is it! */ /* * Find the next Note Off event for the current Note On that * matches the mouse position. */ bool notematch = false; for ( ; off != m_events.end(); ++off) { event & eoff = eventlist::dref(off); if (eon.get_note() == eoff.get_note() && eoff.is_note_off()) { notematch = true; break; } } if (notematch) { event & eoff = eventlist::dref(off); midipulse ontime = eon.timestamp(); midipulse offtime = eoff.timestamp(); if (ontime <= position && position <= offtime) { start = eon.timestamp(); ender = eoff.timestamp(); note = eon.get_note(); return true; } } } ++on; } return false; } /** * This function examines each event in the event list. If the given * position is between the current notes's timestamp-start and timestamp-end * values, the these values are copied to the posstart and posend parameters, * respectively, and then we exit. * * \threadsafe * * \param posstart * The starting position to examine. * * \param posend * The ending position to examine. * * \param status * The desired status value. * * \param start * The destination for the starting timestamp of the matching trigger. * * \return * Returns true if a event was found whose start/end timestamps contained * the position. Otherwise, false is returned, and the start and end * return parameters should not be used. */ bool sequence::intersect_events ( midipulse posstart, midipulse posend, midibyte status, midipulse & start ) { automutex locker(m_mutex); midipulse poslength = posend - posstart; for (auto & eon : m_events) { if (eon.match_status(status)) { midipulse ts = eon.timestamp(); if (ts <= posstart && posstart <= (ts + poslength)) { start = eon.timestamp(); /* side-effect return value */ return true; } } } return false; } #endif // defined USE_INTERSECT_FUNCTIONS /** * Grows a trigger. See triggers::grow_trigger() for more information. Also * indicate we're modified. Tricky, because if modify(true) were called, that * would call on_sequence_change(), which causes a segfault. Here, we need to * modify the performer, but call a different notification function. * * \threadsafe * * \param tickfrom * The desired from-value back which to expand the trigger, if necessary. * * \param tickto * The desired to-value towards which to expand the trigger, if necessary. * * \param len * The additional length to append to tickto for the check. * */ bool sequence::grow_trigger (midipulse tickfrom, midipulse tickto, midipulse len) { automutex locker(m_mutex); m_triggers.grow_trigger(tickfrom, tickto, len); modify(false); /* issue #90 flag change w/o notify */ set_dirty_mp(); /* force redraw */ return true; } /** * This grows a trigger continuously. */ bool sequence::grow_trigger (midipulse tickfrom, midipulse tickto) { automutex locker(m_mutex); m_triggers.grow_trigger(tickfrom, tickto, c_song_record_incr); modify(false); /* issue #90 flag change w/o notify */ set_dirty_mp(); /* force redraw */ return true; } const trigger & sequence::find_trigger (midipulse tick) const { automutex locker(m_mutex); return m_triggers.find_trigger(tick); } /** * Deletes a trigger, that brackets the given tick, from the trigger-list. * See triggers::remove(). * * \threadsafe * * \param tick * Provides the tick to be used for finding the trigger to be erased. */ bool sequence::delete_trigger (midipulse tick) { automutex locker(m_mutex); bool result = m_triggers.remove(tick); if (result) modify(false); /* issue #90 flag change w/o notify */ return result; } /** * Sets m_trigger_offset and wraps it to m_length. If m_length is 0, then * m_trigger_offset is simply set to the parameter. * * \threadsafe * * \param trigger_offset * The full trigger offset to set. */ void sequence::set_trigger_offset (midipulse trigger_offset) { automutex locker(m_mutex); if (get_length() > 0) { m_trigger_offset = trigger_offset % get_length(); m_trigger_offset += get_length(); m_trigger_offset %= get_length(); } else m_trigger_offset = trigger_offset; } /** * Splits a trigger. * * \threadsafe * * \param splittick * The time location of the split. * * \param splittype * The nature of the split. */ bool sequence::split_trigger (midipulse splittick, trigger::splitpoint splittype) { automutex locker(m_mutex); bool result = m_triggers.split(splittick, splittype); if (result) modify(false); /* issue #90 flag change w/o notify */ return result; } /** * Adjusts trigger offsets to the length specified for all triggers, and undo * triggers. * * \threadsafe * * Might can get rid of this function? * * \param newlength * The new length of the adjusted trigger. */ void sequence::adjust_trigger_offsets_to_length (midipulse newlength) { automutex locker(m_mutex); m_triggers.adjust_offsets_to_length(newlength); } /** * Copies triggers to another location. * * \threadsafe * * \param starttick * The current location of the triggers. * * \param distance * The distance away from the current location to which to copy the * triggers. */ void sequence::copy_triggers (midipulse starttick, midipulse distance) { automutex locker(m_mutex); m_triggers.copy(starttick, distance); } bool sequence::selected_trigger ( midipulse droptick, midipulse & tick0, midipulse & tick1 ) { automutex locker(m_mutex); bool result = m_triggers.select(droptick); tick0 = m_triggers.get_selected_start(); tick1 = m_triggers.get_selected_end(); return result; } /** * Gets the last-selected trigger's start tick. * * \threadsafe * * \return * Returns the tick_start() value of the last-selected trigger. If no * triggers are selected, then -1 is returned. */ midipulse sequence::selected_trigger_start () { automutex locker(m_mutex); return m_triggers.get_selected_start(); } /** * Gets the selected trigger's end tick. * * \threadsafe * * \return * Returns the tick_end() value of the last-selected trigger. If no * triggers are selected, then -1 is returned. */ midipulse sequence::selected_trigger_end () { automutex locker(m_mutex); return m_triggers.get_selected_end(); } /** * Moves triggers in the trigger-list. * * Note the dependence on the m_length member being kept in sync with the * parent's value of m_length. * * \threadsafe * * \param starttick * The current location of the triggers. * * \param distance * The distance away from the current location to which to move the * triggers. * * \param direction * If true, the triggers are moved forward. If false, the triggers are * moved backward. * * \param single * If true, move only the first trigger encountered. * * \return * Currently just returns true. */ bool sequence::move_triggers ( midipulse starttick, midipulse distance, bool direction, bool single ) { automutex locker(m_mutex); m_triggers.move(starttick, distance, direction, single); modify(false); /* issue #90 flag change w/o notify */ return true; } /** * Moves selected triggers as per the given parameters. * \verbatim min_tick][0 1][max_tick 2 \endverbatim * * The \a which parameter has three possible values: * * -# If we are moving the 0, use first as offset. * -# If we are moving the 1, use the last as the offset. * -# If we are moving both (2), use first as offset. * * \threadsafe * * \param tick * The tick at which the trigger starts. * * \param adjustoffset * Set to true if the offset is to be adjusted. * * \param which * Selects which movement will be done, as discussed above. * * \return * Returns the value of triggers::move_selected(), which indicates * that the movement could be made. Used in * Seq24PerfInput::handle_motion_key(). */ bool sequence::move_triggers ( midipulse tick, bool adjustoffset, triggers::grow which ) { automutex locker(m_mutex); bool result = m_triggers.move_selected(tick, adjustoffset, which); if (result) modify(false); /* issue #90 flag change w/o notify */ return result; } /** * Used in the song-sequence grid TODO TODO TODO */ void sequence::offset_triggers (midipulse tick, triggers::grow editmode) { automutex locker(m_mutex); m_triggers.offset_selected(tick, editmode); } /** * Get the ending value of the last trigger in the trigger-list. * * \threadsafe * * \return * Returns the maximum trigger value. */ midipulse sequence::get_max_trigger () const { automutex locker(m_mutex); return m_triggers.get_maximum(); } midipulse sequence::get_max_timestamp () const { automutex locker(m_mutex); return m_events.get_max_timestamp(); } bool sequence::get_trigger_state (midipulse tick) const { automutex locker(m_mutex); return m_triggers.get_state(tick); } bool sequence::transpose_trigger (midipulse tick, int transposition) { automutex locker(m_mutex); bool result = m_triggers.transpose(tick, transposition); if (result) modify(false); /* no easy way to undo this */ return result; } /** * Returns a copy of the triggers for this sequence. This function is * basically a threadsafe version of sequence::triggerlist(). * * \return * Returns of copy of m_triggers.triggerlist(). */ triggers::container sequence::get_triggers () const { automutex locker(m_mutex); return triggerlist(); } /** * Checks the list of triggers against the given tick. If any * trigger is found to bracket that tick, then true is returned, and * the trigger is marked as selected. * * \param tick * Provides the tick of interest. * * \return * Returns true if a trigger is found that brackets the given tick; * this is the return value of m_triggers.select(). */ bool sequence::select_trigger (midipulse tick) { automutex locker(m_mutex); return m_triggers.select(tick); } /** * Unselects the desired trigger. * * \param tick * Indicates the trigger to be unselected. */ bool sequence::unselect_trigger (midipulse tick) { automutex locker(m_mutex); return m_triggers.unselect(tick); } /** * Unselects all triggers. * * \return * Returns the m_triggers.unselect() return value. */ bool sequence::unselect_triggers () { automutex locker(m_mutex); return m_triggers.unselect(); } /** * Deletes the first selected trigger that is found. * * \return * Returns true if a trigger was found and deleted. */ bool sequence::delete_selected_triggers () { automutex locker(m_mutex); bool result = m_triggers.remove_selected(); if (result) modify(false); /* issue #90 flag change w/o notify */ return result; } /** * Copies and deletes the first selected trigger that is found. * * \return * Returns true if a trigger was found, copied, and deleted. */ bool sequence::cut_selected_triggers () { automutex locker(m_mutex); copy_selected_triggers(); /* locks itself (recursive) */ return m_triggers.remove_selected(); } /** * First, this function clears any unpasted middle-click tick setting. * Then it copies the first selected trigger that is found. */ bool sequence::copy_selected_triggers () { automutex locker(m_mutex); set_trigger_paste_tick(c_no_paste_trigger); m_triggers.copy_selected(); return true; } /** * If there is a copied trigger, then this function grabs it from the trigger * clipboard and adds it. * * Why isn't this protected by a mutex? We will enable this if anything bad * happens, such as a deadlock, or corruption, that we can prove happens * here. * * \param paste_tick * A new parameter that provides the tick for pasting, or * c_no_paste_trigger (-1) if there is none. */ bool sequence::paste_trigger (midipulse paste_tick) { automutex locker(m_mutex); m_triggers.paste(paste_tick); return true; } /** * Provides a helper function simplify and speed up performer :: * reset_sequences(). In Live mode, the user controls playback, while in * Song mode, the performance/song editor controls playback. This function * used to be called "reset()". * * \param song_mode * Set to true if song mode is in force. This setting corresponds to * sequence::playback::song. False (the default) corresponds to * sequence::playback::live. */ void sequence::stop (bool songmode) { bool state = armed(); off_playing_notes(); zero_markers(); /* sets the "last-tick" value */ if (recording()) /* ca 2023-04-25 */ (void) verify_and_link(); set_armed(songmode ? false : state); m_next_boundary = 0; } /** * A pause version of stop(). It still includes the note-shutoff capability * to prevent notes from lingering. Note that we do not call set_arm(false); * it disarms the sequence, which we do not want upon pausing. * * \param song_mode * Set to true if song mode is in force. This setting corresponds to * performer::playback::song. False (the default) corresponds to * performer::playback::live. */ void sequence::pause (bool song_mode) { bool state = armed(); off_playing_notes(); if (! song_mode) set_armed(state); if (recording()) /* ca 2023-04-25 */ (void) verify_and_link(); } /** * Sets the draw-trigger iterator to the beginning of the trigger list. * * \threadsafe */ void sequence::reset_draw_trigger_marker () { automutex locker(m_mutex); m_triggers.reset_draw_trigger_marker(); } /** * A new function provided so that we can find the minimum and maximum notes * with only one (not two) traversal of the event list. * * \todo * For efficency, we should calculate this only when the event set * changes, and save the results and return them if good. * * \threadsafe * * \param lowest * A reference parameter to return the note with the lowest value. * if there are no notes, then it is set to max_midi_value(), and * false is returned. * * \param highest * A reference parameter to return the note with the highest value. * if there are no notes, then it is set to 0, and false is returned. * * \return * If there are no notes or tempo events in the list, then false is * returned, and the results should be disregarded. If true is returned, * but there are only tempo events, then the low/high range is 0 to 127. */ bool sequence::minmax_notes (int & lowest, int & highest) // const { automutex locker(m_mutex); bool result = false; int low = int(max_midi_value()); int high = -1; for (auto & er : m_events) { if (er.is_strict_note()) { if (er.get_note() < low) { low = er.get_note(); result = true; } else if (er.get_note() > high) { high = er.get_note(); result = true; } } else if (er.is_tempo()) { midibyte notebyte = tempo_to_note_value(er.tempo()); if (notebyte < low) low = notebyte; else if (notebyte > high) high = notebyte; result = true; } } lowest = low; highest = high; return result; } /** * Each call to seqdata() fills the passed references with a events * elements, and returns true. When it has no more events, returns a * false. * * Note that, before the first call to draw a sequence, the sequence::begin() * function must be called. * * \param [out] niout * Provides a pointer destination for a structure hold all of the values * for a note. Saves a lot of stack pushes. The note_info class is * nested in the sequence class, which is a friend. * * \param evi * A caller-provided iterator. Thus, it won't interfere with other * callers. * * \return * Returns a sequence::draw value: linked, note_on, note_off, or finish. * Note that the new value sequence::draw::tempo could be returned, as * well. Note that a return of draw::finish indicates to exit a drawing * loop. */ sequence::draw sequence::get_next_note ( note_info & niout, event::buffer::const_iterator & evi ) const { automutex locker(m_mutex); while (evi != m_events.cend()) { draw status = get_note_info(niout, evi); if (status != draw::none) return status; /* must ++evi after call */ ++evi; } return draw::finish; } /** * Copies important information for drawing a note event. * * \param evi * The current event iterator value. It is not checked, and is not * iterated after getting the data. */ sequence::draw sequence::get_note_info ( note_info & niout, event::buffer::const_iterator & evi ) const { const event & drawevent = eventlist::cdref(evi); bool isnoteon = drawevent.is_note_on(); bool islinked = drawevent.is_linked(); niout.ni_tick_finish = 0; niout.ni_tick_start = drawevent.timestamp(); niout.ni_note = drawevent.get_note(); /* ie. d0() */ niout.ni_selected = drawevent.is_selected(); niout.ni_velocity = drawevent.note_velocity(); if (isnoteon) { if (islinked) { niout.ni_tick_finish = drawevent.link()->timestamp(); return draw::linked; } else return draw::note_on; } else if (drawevent.is_note_off() && ! islinked) { return draw::note_off; } else if (drawevent.is_tempo()) { midibpm bpm = drawevent.tempo(); midibyte notebyte = tempo_to_note_value(bpm); niout.ni_note = int(notebyte); niout.ni_velocity = int(bpm + 0.5); niout.ni_non_note = true; /* * Hmmmm, must check if tempo events ever have a link. No, they * don't but they might need one to properly draw tempo changes!!! * We might just draw tempo as a circle for simplicity. */ if (islinked) niout.ni_tick_finish = drawevent.link()->timestamp(); else niout.ni_tick_finish = get_length(); /* * Tempo needs to be retained. This is good only for drawing a * horizontal tempo line; we need a way to return both a starting * tempo and ending tempo. Return the latter in velocity? */ return draw::tempo; } else if (drawevent.is_program_change()) { niout.ni_tick_finish = niout.ni_tick_start; niout.ni_non_note = true; return draw::program; } else if (drawevent.is_controller()) { niout.ni_non_note = true; return draw::controller; } else if (drawevent.is_pitchbend()) { niout.ni_non_note = true; return draw::pitchbend; } return draw::none; } /** * Checks for non-terminated notes. * * \return * Returns true if there is at least one non-terminated linked note in * the interval. */ bool sequence::reset_interval ( midipulse t0, midipulse t1, event::buffer::const_iterator & it0, event::buffer::const_iterator & it1 ) const { bool result = false; bool got_beginning = false; it0 = m_events.cbegin(); it1 = m_events.cend(); for (auto iter = cbegin(); ! cend(iter); ++iter) { midipulse t = iter->timestamp(); if (t >= t0) { if (! got_beginning) { it0 = iter; /* side-effect */ got_beginning = true; } if (iter->is_linked()) { event::buffer::const_iterator ev = iter->link(); if (ev->timestamp() >= t1) { result = true; // What about terminating iterator ?? break; } } } else if (t >= t1) { it1 = iter; /* side-effect */ break; } } return result; } /** * Get the next event in the event list. Then set the status and control * character parameters using that event. This function requires that * sequence::cbegin() be called to reset to the beginning of the events list. * * \param status * Provides a pointer to the MIDI status byte to be set, as a way to * retrieve the event. * * \param cc * The return pointer for the control value. * * \param [out] evi * An iterator return value for the next event found. The caller might * want to check if it is a Tempo event. Do not use this iterator if * false is returned! For consistency with get_next_event_match(), * we rely on the caller to increment this pointer for the next call. * * \return * Returns true if the data is useable, and false if there are no more * events. */ bool sequence::get_next_event ( midibyte & status, midibyte & cc, event::buffer::const_iterator & evi ) { automutex locker(m_mutex); bool result = evi != m_events.end(); if (result) { midibyte d1; /* will be ignored */ const event & ev = eventlist::cdref(evi); status = ev.get_status(); ev.get_data(cc, d1); } return result; } /** * This function makes the caller responsible for providing and maintaining * the iterator, so that there are no conflicting operations on * m_draw_iterator from seqdata, seqevent, seqroll, and perfroll. * * This function returns the whole event, rather than filling in a bunch of * parameters. In addition, it allows Meta events to be found. Gets * the next event in the event list that matches the given status and control * character. Then set the rest of the parameters parameters using that * event. If the status is the new value EVENT_ANY, then any event will be * obtained. * * Note the usage of event::is_desired(status, cc); either we have a control * change with the right CC or it's a different type of event. * * \param status * The type of event to be obtained. The special value EVENT_ANY can be * provided so that no event statuses are filtered. * * \param cc * The continuous controller value that might be desired. * * \param [out] evi * An iterator return value for the next event found. The caller might * want to check if it is a Tempo event. Do not use this iterator if * false is returned! The caller must increment it for the next call, just * as for get_next_event(). * * \return * Returns true if the current event was one of the desired ones, or was * a Tempo or Time Signature event (the Meta events whose drawing in qseqdata * supported). In this case, the caller must increment the * iterator. */ bool sequence::get_next_event_match ( midibyte status, midibyte cc, event::buffer::const_iterator & evi ) { automutex locker(m_mutex); bool ismeta = event::is_meta_msg(status); while (evi != m_events.end()) { const event & drawevent = eventlist::cdref(evi); bool ok = drawevent.match_status(status); if (ok && ismeta) { if (! event::is_meta_text_msg(status)) /* do all text the same */ ok = drawevent.channel() == cc; /* avoids redundant check */ if (ok) return true; /* must ++evi after call */ } else { if (! ok) ok = status == EVENT_ANY; if (ok) { midibyte d0; drawevent.get_data(d0); ok = event::is_desired_cc_or_not_cc(status, cc, d0); if (ok) return true; /* must ++evi after call */ } } ++evi; /* keep going here */ } return false; } /** * Similar to get_next_event_match(), but specific to Meta events and can * be restricted to a limited range of time. * * \param metamsg * Provides the type of Meta message. In Seq66, this value is stored * in the "channel" member. A special case is EVENT_META_TEXT_EVENT, * which covers a number of different textual meta messages. * * \param [out] evi * An iterator return value for the next event found. The caller might * want to check it. Do not use this iterator if false is returned! The * caller must increment it for the next call, just as for * get_next_event() or get_next_event_match(). * * \param start * Provides the starting point for the search. Events before this tick * are ignored. The default value is 0, the beginning of the pattern. * * \param range * Provides how far to look for a time signature. The default is * c_null_midipulse, which means just detect the first Meta event no * matter how deep into the pattern. Another useful value is the snap() * value from the seqedit. In that case the event time must be within * \a start + \a range ticks. * * \return * Returns true if the Meta event was detected before the range limit was * reached. */ bool sequence::get_next_meta_match ( midibyte metamsg, event::buffer::const_iterator & evi, midipulse start, midipulse range ) { automutex locker(m_mutex); if (range != c_null_midipulse) range += start; while (evi != m_events.end()) { const event & drawevent = eventlist::cdref(evi); if (drawevent.is_meta()) { bool match = metamsg == EVENT_META_TEXT_EVENT ? event::is_meta_text_msg(drawevent.channel()) : drawevent.channel() == metamsg ; if (match) { midipulse tick = evi->timestamp(); if (tick >= start) { if (tick < range || range == c_null_midipulse) return true; } } } ++evi; /* keep going here */ } return false; } /** * Get the next trigger in the trigger list, and set the parameters based * on that trigger. */ bool sequence::next_trigger (trigger & trig) { trig = m_triggers.next(); return trig.is_valid(); } /** * \setter m_last_tick * This function used to be called "set_orig_tick()", now renamed to * match up with get_last_tick(). * * \threadsafe */ void sequence::set_last_tick (midipulse tick) { automutex locker(m_mutex); if (is_null_midipulse(tick)) tick = m_length; m_last_tick = tick; } /** * Get the performer's current tick so that setting position by a click works * properly. */ midipulse sequence::get_tick () const { if (expanded_recording()) { return perf()->get_tick(); } else { if (get_length() > 0) return perf()->get_tick() % get_length(); else return perf()->get_tick(); } } /** * Returns the last tick played, and is used by the editor's idle function. * If m_length is 0, this function returns m_last_tick - m_trigger_offset, to * avoid an arithmetic exception. Should we return 0 instead? * * Note that seqroll calls this function to help get the location of the * progress bar. What does perfedit do? */ midipulse sequence::get_last_tick () const { return get_length() > 0 ? (m_last_tick + get_length() - m_trigger_offset) % get_length() : m_last_tick - m_trigger_offset ; } /** * Sets the MIDI buss/port number to dump MIDI data to. When first called, * there is generally no performer yet, so all that it done is to set the * nominal buss. Later, set_parent() is called and here we then determine * the true system MIDI bus in use by this sequence. * * \threadsafe * * \param nominalbus * The MIDI buss to set as the buss number for this sequence. Also * called the "MIDI port" number. This number is not necessarily the * true system bus number. We no longer check that nominalbus != * m_nominal_bus. Saves some trouble. * * \param user_change * If true (the default value is false), the user has decided to change * this value, and we might need to modify the performer's dirty flag, so * that the user gets prompted for a change. This is a response to * GitHub issue #47, where buss changes do not cause a prompt to save the * sequence. */ bool sequence::set_midi_bus (bussbyte nominalbus, bool user_change) { automutex locker(m_mutex); bool result = is_valid_buss(nominalbus); if (result) { m_nominal_bus = nominalbus; /* log the putative buss */ if (is_nullptr(perf())) /* "parent" is not yet set */ { m_true_bus = null_buss(); /* an invalid value */ } else { off_playing_notes(); /* off notes except initial */ m_true_bus = perf()->true_output_bus(nominalbus); if (is_null_buss(m_true_bus)) m_true_bus = nominalbus; /* buss no longer exists */ if (user_change) modify(); /* no easy way to undo this */ #if defined USE_THIS_CALL /* see sequence::modify() */ notify_change(user_change); /* better than set_dirty() */ #endif set_dirty(); /* for display updating */ } } return result; } /** * Similar to set_midi_bus(), but supports the new and optional input * buss. */ bool sequence::set_midi_in_bus (bussbyte nominalbus, bool user_change) { automutex locker(m_mutex); bool result = is_valid_buss(nominalbus); if (result) { m_nominal_in_bus = nominalbus; /* log the putative buss */ if (is_nullptr(perf())) /* "parent" is not yet set */ { m_true_in_bus = null_buss(); /* an invalid value */ } else { m_true_in_bus = perf()->true_input_bus(nominalbus); if (is_null_buss(m_true_in_bus)) m_true_in_bus = nominalbus; /* named buss no longer exists */ if (user_change) modify(); /* no easy way to undo this */ #if defined USE_THIS_CALL /* see sequence::modify() */ notify_change(user_change); /* more reliable than set dirty */ #endif set_dirty(); /* this is for display updating */ } } return result; } /** * Sets the length (m_length) and adjusts triggers for it, if desired. * This function is called in qseqeditframe64, when the user changes * beats/bar or the sequence length in measures. This function is also called * when reading a MIDI file. * * There's an issue, though. If the application is compiled to use the * original std::list container for MIDI events, that implementation sorts * the container after every event insertion. If the application is compiled * to used the std::map container (to speed up the reading of large MIDI * files *greatly*), sorting happens automatically. But, if we use the * original std::list implementation, but leave the sorting until later (to * speed up the reading of large MIDI files *greatly*), then the * verify_and_link() call that happens with every new event happens before * the events are sorted, and the result is elongated notes showing up in the * pattern slot in the main window. Therefore, we need a way to skip the * verification when reading a MIDI file, and do the verification only after * all events are read. * * That function calculates the length in ticks: * \verbatim L = M x B x 4 x P / W L == length (ticks or pulses) M == number of measures B == beats per measure P == pulses per quarter-note W == beat width in beats per measure \endverbatim * * For our "b4uacuse" MIDI file, M can be about 100 measures, B is 4, * P can be 192 (but we want to support higher values), and W is 4. * So L = 100 * 4 * 4 * 192 / 4 = 76800 ticks. Seems small. * * Issue #88: * * If the time signature is something like 4/16, then the pattern length * will be much less than PPQN * 4. This affects the set_parent() * function. * * \threadsafe * * \param len * The length value to be set. If it is smaller than ppqn/4, then * it is set to that value, unless it is zero, in which case m_length is * used and does not change. It also sets the length value for the * sequence's triggers. * * \param adjust_triggers * If true, m_triggers.adjust_offsets_to_length() is called. The value * defaults to true. * * \param verify * This new parameter defaults to true. If true, verify_and_link() is * called. Otherwise, it is not, and the caller should call this * function with the default value after reading all the events. * * \return * Returns true if the length value actually changed. */ bool sequence::set_length (midipulse len, bool adjust_triggers, bool verify) { automutex locker(m_mutex); bool result = len != m_length; if (result) { bool was_playing = armed(); /* was it armed? */ set_armed(false); /* mute the pattern */ if (len > 0) { if (len < midipulse(m_ppqn / 4)) len = midipulse(m_ppqn / 4); m_length = len; result = true; } else len = get_length(); m_events.set_length(len); m_triggers.set_length(len); /* must precede adjustment */ if (adjust_triggers) m_triggers.adjust_offsets_to_length(len); if (verify) (void) verify_and_link(); if (was_playing) /* start up and refresh */ set_armed(true); } return result; } /** * Sets the sequence length based on the first four parameters. There's an * implicit "adjust-triggers = true" parameter used in this function. Please * note that there is an overload that takes only a measure number and uses * the current beats/bar, PPQN, and beat-width values of this sequence. The * calculate_unit_measure() function is called, but won't change any values * just because the length (number of measures) changed. * * \warning * The measures calculation is somewhat useless if the BPM (beats/minute) * varies throughout the song. * * \param bpb * Provides the beats per bar (measure). If 0, the current value is used. * * \param ppqn * Provides the pulses-per-quarter-note to apply to the length * application. If 0, the current value is used. * * \param bw * Provides the beatwidth (typically 4) from the time signature. If 0, the * current value is used. * * \param measures * Provides the number of measures the sequence should cover, obtained * from the user-interface. Defaults to 0, which means we calculate the * current measures. * * \param user_change * If true, this is a modification, not an initialization. The default * is false. * * \return * Returns true if the modification was initiated by the user. */ bool sequence::apply_length ( int bpb, int ppq, int bw, int measures, bool user_change ) { bool result = false; bool reset_L_R_markers = false; if (bpb == 0) { bpb = get_beats_per_bar(); } else { if (bpb != get_beats_per_bar()) /* ca 2024-12-10 */ reset_L_R_markers = true; set_beats_per_bar(bpb, user_change); } if (ppq == 0) ppq = int(get_ppqn()); else change_ppqn(ppq); /* rarely changed; rescales if */ if (bw == 0) { bw = get_beat_width(); } else { if (bw != get_beat_width()) reset_L_R_markers = true; set_beat_width(bw, user_change); } if (measures == 0) { (void) unit_measure(true); /* reset the unit-measure */ measures = get_measures(0); /* calculate the current bars */ result = set_length(seq66::measures_to_ticks(bpb, ppq, bw, measures)); } else result = set_measures(measures, user_change); /* and set_length() */ if (result) (void) unit_measure(true); /* for progress and redrawing */ if (reset_L_R_markers) { if (not_nullptr(perf())) { perf()->set_left_tick(0); /* move L marker to the start */ perf()->set_right_tick(0); /* move R marker to measure end */ } } return result; } /** * Extends the length of the sequence. Calls set_length() with the new * length and its default parameters. Currently used only when consolidating * patterns into an SMF 0 track. * * \param len * The new length of the sequence. * * \return * Returns the new number of measures. */ bool sequence::extend_length () { automutex locker(m_mutex); midipulse len = m_events.get_max_timestamp(); bool result = len > get_length(); if (len > get_length()) { int measures = int(double(len) / unit_measure(true) + 0.5); /* TIME */ len = m_unit_measure * measures; result = set_length(len, false, false); /* no trig adjust or verify */ } return result; } /** * Doubles the length of the pattern. This is always a modification. */ bool sequence::double_length () { automutex locker(m_mutex); int m = get_measures(); bool result = m > 0; if (result) { m *= 2; result = apply_length(m); if (result) modify(); /* have pending changes */ } return result; } /** * Notifies the parent performer's subscribers that the sequence has * changed in some way not based on a trigger or action, and is hence a * modify action. [The default change value is "yes" for performer :: * notify_sequence_change().] * * \param userchange * Indicates if the change was requested by a user or done in the * "normal" course of operations. The default is true. */ void sequence::notify_change (bool userchange) { if (not_nullptr(perf())) { performer::change mod = userchange ? performer::change::yes : performer::change::no ; perf()->notify_sequence_change(seq_number(), mod); } } /** * Notifies the parent performer's subscribers that the sequence has * changed state based on a trigger or action. This will not cause a modify * action. */ void sequence::notify_trigger () { if (not_nullptr(perf())) perf()->notify_trigger_change(seq_number(), performer::change::no); } /** * Sets the playing state of this sequence. When playing, and the sequencer * is running, notes get dumped to the ALSA buffers. * * If we're turning play on, we now (2021-04-27 issue #49) turn song-mute * off, so that the pattern will not get turned off when playback starts. * This covers the case where the user enables and then disables a mute * group, which sets song-mute to true on all sequences. * * \param p * Provides the playing status to set. True means to turn on the * playing, false means to turn it off, and turn off any notes still * playing. */ bool sequence::set_armed (bool p) { automutex locker(m_mutex); bool result = p != armed(); if (result) { armed(p); if (p) set_song_mute(false); /* see banner notes */ else off_playing_notes(); /* * This call is meant to allow the grid slot and the pattern-editor * armed statuses to stay in sync. However, if colours.midi is opened * and a pattern is opened, then b4uacuse.mid is selected (as * a recent file) and a pattern is opened, then we go back to * colours.midi, a segfault occurs due to the pattern editor * being bogus. What to do??? * * notify_change(false); */ set_dirty(); m_queued = m_one_shot = false; perf()->announce_pattern(seq_number()); /* for issue #89 */ #if defined SEQ66_PLATFORM_DEBUG_TMI printf("seq %d: playing %s\n", int(seq_number()), p ? "on" : "off"); #endif } /* * Let's move these above so that announce_pattern() behaves properly. * Whenever playing state changes, we are unqueued. Not yet sure * about one-shot. * * m_queued = false; * m_one_shot = false; */ return result; } /** * This sets the status of basic recording, with no other wrinkles such as * quantization. * * This function sets m_notes_on to 0, only if the recording status has * changed. It is called by set_recording(). We probably need to explicitly * turn off all playing notes; not sure yet. * * Except if already Thru and trying to turn recording (input) off, set input * on here no matter what, because even if m_thru, input could have been * replaced in another sequence. * * \param recordon * Provides the desired status to set recording. * * \param toggle * If true, ignore the first parameter and toggle the flag. The default * value is false. */ /** * This function sets only the status of recording, regardless of alteration * type. And, if recording get turned off, the alteration type is set * to alteration::none. * * Issue #129: * * The mastermidibus::set_sequence_input() call ignores turning on * alterations when an inputing sequence has already been selected. * Fixed in mastermidibase. */ bool sequence::set_recording (toggler flag) { automutex locker(m_mutex); bool recordon = m_recording; if (flag == toggler::flip) recordon = ! recordon; else recordon = flag == toggler::on; bool result = master_bus()->set_sequence_input(recordon, this); if (result) { channel_match(false); m_recording = recordon; m_notes_on = 0; /* reset the step-edit note counter */ m_last_tick = 0; if (recordon) { if (! perf()->record_by_buss() && perf()->record_by_channel()) channel_match(true); } else m_record_alteration = alteration::none; set_dirty(); notify_trigger(); } return result; } /** * Added some wrinkles from qseqeditframe64. If recording is on, and the * flag is toggler::off, we want to disable basic recording only if the * alteration is alteration::none. Otherwise, we want to disable only * the alteration option, leaving basic recording still active. * * - If toggler::on. Set the appropriate alteration and pass the flag * to the basic set_recording() function above. * - If toggler::off. * - Set alteration::none. The next step also does that. * - If the specified alteration was none, pass the flag as above. * - If toggler::flip. See the code :-D */ bool sequence::set_recording (alteration q, toggler flag) { automutex locker(m_mutex); bool result = true; if (flag == toggler::on) { m_record_alteration = q; result = set_recording(toggler::on); } else if (flag == toggler::off) { m_record_alteration = alteration::none; /* * Don't change the status of recording just because * quantization/alteration is being turned off. * * if (q == alteration::none) // turn off alteration * result = set_recording(toggler::off); */ set_dirty(); /* * notify_trigger(); */ notify_change(false); } else /* toggler::flip */ { if (q == alteration::none) /* plain basic recording */ { result = set_recording(toggler::flip); } else { if (q == m_record_alteration) m_record_alteration = alteration::none; else m_record_alteration = q; result = set_recording(toggler::flip); } } return result; } bool sequence::set_recording_style (recordstyle rs) { automutex locker(m_mutex); bool result = rs != recordstyle::max; if (result) { m_recording_style = rs; if (rs == recordstyle::overwrite) loop_reset(true); /* on overwrite, always reset the sequence */ /* * Why do this? * * notify_trigger(); // tricky! // */ notify_change(false); } return result; } /** * Sets the state of MIDI Thru. * * \param thru_active * Provides the desired status to set the through state. * * \param toggle * If true, ignore the first parameter and toggle the flag. The default * value is false. */ bool sequence::set_thru (bool thruon, bool toggle) { automutex locker(m_mutex); if (toggle) thruon = ! m_thru; bool result = thruon != m_thru; if (result) { /* * Except if already recording and trying to turn Thru (hence input) * off, set input to here no matter what, because even in m_recording, * input could have been replaced in another sequence. * LET's try putting in the original conditional. */ if (! m_recording) result = master_bus()->set_sequence_input(thruon, this); if (result) m_thru = thruon; } return result; } void sequence::snap (int st) { automutex locker(m_mutex); m_snap_tick = midipulse(st); m_events.zero_len_correction(snap() / 2); } void sequence::step_edit_note_length (int len) { automutex locker(m_mutex); m_step_edit_note_length = midipulse(len); } void sequence::loop_reset (bool reset) { automutex locker(m_mutex); m_loop_reset = reset; } /** * Sets the sequence name member, m_name. This is the name shown in the top * of a mainwnd pattern slot. * * We now try to include the length of the sequences in measures at the end * of the name, and limit the length of the entire string. As noted in the * printing of sequence::get_name() in mainwnd, this length is 13 characters. */ void sequence::set_name (const std::string & name) { if (name.empty()) m_name = sm_default_name; else m_name = name; /* legacy behavior */ set_dirty(); } /** * Gets the title of the pattern, to show in the pattern slot. This function * differs from name, which just returns the value of m_name. Here, we also * include the length of the sequences in measures at the end of the name, * and limit the length of the entire string. As noted in the printing of * sequence::get_name() in mainwnd, this length is 13 characters. * * \return * Returns the name of the sequence, with the length in measures of the * pattern wedged in at the end, if non-zero. */ std::string sequence::title () const { int measures = calculate_measures(); bool showmeasures = true; if (measures > 0 && showmeasures) /* do we have bars to show? */ { char mtemp[16]; /* holds measures as string */ char fulltemp[32]; /* seq name + measures */ std::memset(fulltemp, ' ', sizeof fulltemp); snprintf(mtemp, sizeof mtemp, " %d", measures); for (int i = 0; i < int(m_name.size()); ++i) { if (i <= (14 - 1)) /* max size fitting in slot */ fulltemp[i] = m_name[i]; /* add sequence name/title */ else break; } int mlen = int(strlen(mtemp)); /* no. of chars in measures */ int offset = 14 - mlen; /* we're allowed 14 chars */ for (int i = 0; i < mlen; ++i) fulltemp[i + offset] = mtemp[i]; fulltemp[14] = 0; /* guarantee C string term. */ return std::string(fulltemp); } else return m_name; } /** * Sets the m_midi_channel number, which is the output channel for this * sequence. * * No longer true: * * If the channel number provides equates to the null channel, * this function does not change the channel number, but merely sets the * m_free_channel flag. * * \threadsafe * * \param ch * The MIDI channel to set as the output channel number for this * sequence. This value can range from 0 to 15, but c_midichannel_null * equal to 0x80 means we are just setting the "no-channel" status. * * \param user_change * If true (the default value is false), the user has decided to change * this value, and we might need to modify the performer's dirty flag, so * that the user gets prompted for a change, This is a response to * GitHub issue #47, where channel changes do not cause a prompt to save * the sequence. */ bool sequence::set_midi_channel (midibyte ch, bool user_change) { automutex locker(m_mutex); bool result = ch != m_midi_channel; if (result) result = is_valid_channel(ch); /* 0 to 15 or null_channel() */ if (result) { off_playing_notes(); m_free_channel = is_null_channel(ch); m_midi_channel = ch; /* if (! m_free_channel) */ if (user_change) modify(); /* no easy way to undo this */ set_dirty(); /* this is for display updating */ } return result; } /** * Translates the 0 to 15 channel to "1" to "16". The "F" indicates "no * channel" in the pattern slot grid. */ std::string sequence::channel_string () const { return m_free_channel ? std::string("F") : std::to_string(m_midi_channel + 1) ; } /** * Modifies all the channel events in the pattern. This also * changes the pattern to use the "Free" channel. */ bool sequence::set_channels (int channel) { bool result = channel != c_midichannel_null; if (result) { result = m_events.set_channels(channel); if (result) { m_midi_channel = c_midichannel_null; m_free_channel = true; } } return result; } /** * Constructs a list of the currently-held events. We do it by brute force, * not by std::sstream. * * \threadunsafe */ std::string sequence::to_string () const { midibyte channel = seq_midi_channel(); std::string chanstring = is_null_channel(channel) ? "null" : std::to_string(int(channel) + 1) ; std::string result = "Pattern "; result += std::to_string(seq_number()); result += " '"; result += name(); result += "'\n"; result += "Channel "; result += chanstring; result += ", Bus "; result += std::to_string(seq_midi_bus()); result += "\n Transposeable: "; result += bool_to_string(transposable()); result += "\n Length (ticks): "; result += std::to_string(get_length()); result += "Events:\n"; result += m_events.to_string(); return result; } /** * Takes an event that this sequence is holding, and places it on the MIDI * buss. This function does not bother checking if m_master_bus is a null * pointer. * * Note that the call to midi_channel() yields the event channel if * free_channel() is true. Otherwise the global pattern channel is true. * * \param ev * The event to put on the buss. * * \threadsafe */ void sequence::put_event_on_bus (const event & ev) { midibyte note = ev.get_note(); bool skip = false; if (ev.is_note_on()) { ++m_playing_notes[note]; } else if (ev.is_note_off()) { if (m_playing_notes[note] == 0) skip = true; else --m_playing_notes[note]; } if (! skip) { event evout; evout.prep_for_send(perf()->get_tick(), ev); /* issue #100 */ master_bus()->play_and_flush(m_true_bus, &evout, midi_channel(ev)); } } /** * Sends a note-off event for all active notes. This function does not * bother checking if m_master_bus is a null pointer. * * \threadsafe */ void sequence::off_playing_notes () { automutex locker(m_mutex); int channel = free_channel() ? 0 : seq_midi_channel() ; event e(0, EVENT_NOTE_OFF, channel, 0, 0); for (int x = 0; x < c_notes_count; ++x) { while (m_playing_notes[x] > 0) { e.set_data(x); master_bus()->play(m_true_bus, &e, channel); --m_playing_notes[x]; } } if (not_nullptr(master_bus())) master_bus()->flush(); } /** * Transposes notes by the given steps, in accordance with the given * scale. If the scale value is 0, this is "no scale", which is the * chromatic scale, where all 12 notes, including sharps and flats, are * part of the scale. * * Also, we've moved external calls to push_undo() into this function. * The caller shouldn't have to do that. * * \note * We noticed (ca 2016-06-10) that MIDI aftertouch events need to be * transposed, but are not being transposed here. Assuming they are * selectable (another question!), the test for note-on and note-off is not * sufficient, and so has been replaced by a call to event::is_note(). * * \param steps * The number of steps to transpose the notes. * * \param scale * The scale to make the notes adhere to while transposing. If the scale * is 0, it is straight transposition. If greater than 0, then the * transposition is harmonic, and based on the "up" or "down" settings in * the scales module for that scale. */ bool sequence::transpose_notes (int steps, int scale, int key) { automutex locker(m_mutex); const int * transposetable; bool result = false; m_events_undo.push(m_events); /* push_undo(), no lock */ if (steps < 0) { transposetable = scales_down(scale, key); /* 0 = chromatic scale */ steps *= -1; } else transposetable = scales_up(scale, key); /* 0 = chromatic scale */ for (auto & er : m_events) { if (er.is_selected_note()) /* transposable event? */ { int note = er.get_note(); bool off_scale = false; if (transposetable[note % c_octave_size] == 0) { off_scale = true; note -= 1; } for (int x = 0; x < steps; ++x) note += transposetable[note % c_octave_size]; if (off_scale) note += 1; er.set_note(note); result = true; } } if (result) modify(); return result; } #if defined SEQ66_SEQ32_SHIFT_SUPPORT /** * We need to look into this function. */ void sequence::shift_notes (midipulse ticks) { automutex locker(m_mutex); if (get_length() > 0) { m_events_undo.push(m_events); /* push_undo(), no lock */ for (auto & er : m_events) { if (er.is_selected_note()) /* shiftable event? */ { midipulse timestamp = er.timestamp() + ticks; if (timestamp < 0L) /* wraparound */ timestamp = get_length() - ((-timestamp) % get_length()); else timestamp %= get_length(); er.set_timestamp(timestamp); result = true; } } if (result) { m_events.sort(); set_dirty(); /* seqedit to update */ } } } #endif // SEQ66_SEQ32_SHIFT_SUPPORT /** * Applies the transpose value held by the master MIDI buss object, if * non-zero, and if the sequence is set to be transposable. */ void sequence::apply_song_transpose () { int transpose = transposable() ? perf()->get_transpose() : 0 ; if (transpose != 0) { automutex locker(m_mutex); m_events_undo.push(m_events); /* push_undo(), no lock */ for (auto & er : m_events) { if (er.is_note()) /* also aftertouch */ er.transpose_note(transpose); } set_dirty(); modify(); } } /** * \setter m_transposable * Changing this flag modifies the sequence and performance. Note that * when a sequence is being read from a MIDI file, it will not yet have a * parent, so we have to check for that before setting the performer modify * flag. * * \param flag * The value to set. * * \param user_change * If true (default is false), then call the change a modification. * This change can happen at load time, which is not a modification. */ void sequence::set_transposable (bool flag, bool user_change) { automutex locker(m_mutex); bool modded = flag != m_transposable && user_change; m_transposable = flag; if (modded) modify(); } /** * Quantizes the currently-selected set of events that match the type of * event specified. This function first marks the selected events. Then it * grabs the matching events, puts them into a list of events to be quantized * and quantizes them against the snap ticks. Linked events (which are * always Note On or Note Off) are adjusted as well, with Note Offs that wrap * around being adjust to be just at the end of the pattern. This function * them removes the marked event from the sequence, and merges the quantized * events into the pattern's event container. Finally, the modified event * list is verified and linked. * * \param status * Indicates the type of event to be quantized. * * \param cc * The desired control-change to count, if the event is a control-change. * * \param snap_tick * Provides the maximum amount to move the events. Actually, events are * moved to the previous or next snap_tick value depend on whether they * are halfway to the next one or not. * * \param divide * A rough indicator of the amount of quantization. The only values used * in the application are either 1 ("quantize") or 2 ("tighten"). The * latter value reduces the amount of change slightly. This value is * tested for 0, and the function just returns if that is the case. */ bool sequence::quantize_events (midibyte status, midibyte cc, int divide) { automutex locker(m_mutex); if (divide == 0) return false; bool result = m_events.quantize_events(status, cc, snap(), divide); if (result) set_dirty(); return result; } bool sequence::quantize_notes (int divide) { automutex locker(m_mutex); if (divide == 0) return false; bool result = m_events.quantize_notes(snap(), divide); if (result) set_dirty(); return result; } /** * We set the beats and width to 0 to use the current values. */ bool sequence::change_ppqn (int p) { automutex locker(m_mutex); bool result = p != m_ppqn; if (result) result = ppqn_in_range(p); if (result) { result = m_events.rescale(p, m_ppqn); /* new & old PPQNs */ if (result) { m_length = rescale_tick(m_length, p, m_ppqn); m_ppqn = p; result = apply_length(0, 0, 0); /* use new PPQN */ m_triggers.change_ppqn(p); } } return result; } /** * A new convenience function. See the sequence::quantize_events() function * for more information. This function just does locking and a push-undo * before calling that function. * * Note that only selected events are subject to quantization. * * \param status * The kind of event to quantize, such as Note On, or the event type * selected in the pattern editor's data pane. * * \param cc * The control-change value to quantize, again as selected in the pattern * editor's data pane. For Note Ons, this value should be set to 0. * * \param divide * Provides a division value, usually either 1 ("quantize") or 2 * ("tighten"). * * \return * Returns true if the events were quantized. */ bool sequence::push_quantize (midibyte status, midibyte cc, int divide) { automutex locker(m_mutex); m_events_undo.push(m_events); return quantize_events(status, cc, divide); } bool sequence::push_quantize_notes (int divide) { automutex locker(m_mutex); m_events_undo.push(m_events); return quantize_notes(divide); } bool sequence::push_jitter_notes (int range) { automutex locker(m_mutex); m_events_undo.push(m_events); if (range == (-1)) range = usr().jitter_range(snap()); return jitter_notes(range); } /** * A member function to dump a summary of events stored in the event-list of * a sequence. Later, use to_string(). */ void sequence::show_events () const { printf ( "sequence #%d '%s': channel %d, events %d\n", seq_number(), name().c_str(), seq_midi_channel(), event_count() ); for (auto iter = cbegin(); ! cend(iter); ++iter) { const event & er = eventlist::cdref(iter); std::string evdump = er.to_string(); printf("%s", evdump.c_str()); } } /** * Copies an external container of events into the current container, * effectively replacing all of its events. Compare this function to the * remove_all() function. Copying the container is a lot of work, but fast. * Also note that we have to recalculate the length of the sequence. * Another option, if we have a new sequence length value (in pulses) * would be to call sequence::set_length(len, adjust_triggers). We * need to make sure the length is rounded up to the next quarter note. * Actually, should make it a full measure size! Or do we always want to * preserve the pattern length no matter how many trailing events are deleted? * * \threadsafe * Note that we had to consolidate the replacement of all the events in * the container in order to prevent the "Save to Sequence" button in the * eventedit object from causing the application to segfault. It would * segfault when the mainwnd timer callback would fire, causing updates * to the sequence's slot pixmap, which would then try to access deleted * events. Part of the issue was that note links were dropped when * copying the events, so now we call verify_and_link() to hopefully * reconstitute the links. * * \param newevents * Provides the container of MIDI events that will completely replace the * current container. Normally this container is supplied by the event * editor, via the eventslots class. */ bool sequence::copy_events (const eventlist & newevents) { automutex locker(m_mutex); bool result = false; m_events.clear(); m_events = newevents; if (m_events.empty()) { m_events.unmodify(); /* * ca 2021-02-03 Not sure we want to change the length at all, let * alone set it to 0. No pattern ever has a length of 0. * * m_length = 0; */ } else { midipulse len = m_events.get_max_timestamp(); bool change_length = false; if (len < get_ppqn()) { double qn_per_beat = 4.0 / get_beat_width(); int qnnum = int(get_beats_per_bar() * qn_per_beat); len = qnnum * get_ppqn(); change_length = true; } else if (len < m_length) /* trimmed trailing note(s) */ { // leave the length as it was } else if (len > m_length) { change_length = true; /* calc. measures & length */ } if (change_length) set_length(len); /* m_length = len */ (void) verify_and_link(); /* function uses m_length */ result = true; } modify(); return result; } /** * Sets the "parent" of this sequence, so that it can get some extra * information about the performance. Remember that m_parent is not at all * owned by the sequence. We just don't want to do all the work necessary to * make it a reference, at this time. * * Add the buss override, if specified. We can't set it until after * assigning the master MIDI buss, otherwise we get a segfault. * * Issue #88: * * If the time signature is something like 4/16, then the beat length * will be much less than PPQN. It will be the quarter note size / 4. * Also check out pulses_to_midi_measures(). * * \param p * A pointer to the parent, assigned only if not already assigned. */ void sequence::set_parent (performer * p) { if (not_nullptr(p)) { int bpb = get_beats_per_bar(); int bw = get_beat_width(); if (bpb == 0) bpb = p->get_beats_per_bar(); if (bw == 0) bw = p->get_beat_width(); midipulse ppnote = 4 * get_ppqn() / bw; /* get_beat_width(); */ midipulse barlength = ppnote * bpb; /* get_beats_per_bar(); */ bussbyte buss_override = usr().midi_buss_override(); m_parent = p; /* perf() is the accessor */ set_master_midi_bus(p->master_bus()); sort_events(); /* sort the events now */ set_length(); /* final verify_and_link() */ empty_coloring(); /* yellow color if no events */ if (get_length() < barlength) /* pad sequence to a measure */ set_length(barlength, false); (void) set_midi_in_bus(m_nominal_in_bus); if (is_null_buss(buss_override)) (void) set_midi_bus(m_nominal_bus); else (void) set_midi_bus(buss_override); set_beats_per_bar(bpb); set_beat_width(bw); unmodify(); /* for issue #90 */ } } /** * Why don't we see this in kepler34? We do, in the MidiPerformance::play() * function. We refactored this, Chris. Remember? :-D * * \param tick * Provides the current active pulse position, the tick/pulse from which * to start playing. * * \param playbackmode * If true, we are in Song mode. Otherwise, Live mode. * * \param resumenoteons * Indicates if we are to resume Note Ons. Used by performer::play(). */ void sequence::play_queue (midipulse tick, bool playbackmode, bool resumenoteons) { if (check_queued_tick(tick)) { play(get_queued_tick() - 1, playbackmode, resumenoteons); (void) toggle_playing(tick, resumenoteons); if (! perf()->is_solo()) { automation::action a = automation::action::off; automation::ctrlstatus cs = automation::ctrlstatus::queue; (void) perf()->set_ctrl_status(a, cs); } } if (check_one_shot_tick(tick)) { play(one_shot_tick() - 1, playbackmode, resumenoteons); (void) toggle_playing(tick, resumenoteons); (void) toggle_queued(); /* queue it to mute it again after one play */ (void) perf()->set_ctrl_status ( automation::action::off, automation::ctrlstatus::oneshot ); } if (is_metro_seq()) { live_play(tick); } else { play(tick, playbackmode, resumenoteons); } } /** * Actually, useful mainly for the user-interface, this function calculates * the size of the left and right handles of a note. * * \param start * The starting tick of the note event. * * \param finish * The ending tick of the note event. * * \return * Returns 16 or one-third of the note length. This value is scaled * according to PPQN if different from 192. */ midipulse sequence::handle_size (midipulse start, midipulse finish) { midipulse result = c_handlesize * m_ppqn / usr().base_ppqn(); midipulse notelength = finish - start; if (notelength < result / 3) result = notelength / 3; return result; } /** * Toggles the m_one_shot flag, sets m_off_from_snap to true, and adjusts * m_one_shot_tick according to m_last_tick and m_length. */ bool sequence::toggle_one_shot () { automutex locker(m_mutex); set_dirty_mp(); m_one_shot = ! m_one_shot; m_one_shot_tick = m_last_tick - mod_last_tick() + get_length(); perf()->announce_pattern(seq_number()); /* for issue #89 */ off_from_snap(true); return m_one_shot; } /** * Sets the dirty flag, sets m_one_shot to false, and m_off_from_snap to * true. This function remains unused here and in Kepler34. Instead, see * the set_armed() function above. */ void sequence::off_one_shot () { automutex locker(m_mutex); set_dirty_mp(); m_one_shot = false; off_from_snap(true); perf()->announce_pattern(seq_number()); /* for issue #89 */ } /** * Starts the growing of the sequence for Song recording. This process * starts by adding a chunk of c_song_record_incr ticks to the * trigger, which allows the rest of the threads to notice the change. * * \question * Do we need to call set_dirty_mp() here? * * \param tick * Provides the current tick, which helps set the recorded block's * boundaries, and is copied into m_song_record_tick. * * \param snap * If true, trigger recording will snap. Defaults to the preferred * state, true. Note that the performer actually does the snapping on * behalf of all patterns. */ void sequence::song_recording_start (midipulse tick, bool snap) { song_recording(true); song_recording_snap(snap); if (snap) (void) perf()->calculate_snap(tick); /* issue #44 redux */ song_record_tick(tick); /* snapped or not */ add_trigger(tick, c_song_record_incr); } /** * Stops the growing of the sequence for Song recording. If we have been * recording, we snap the end of the trigger segment to the next whole * sequence interval. * * However, for issue #44, we'd like to have the trigger stop at the snap * point for the actual tick at which muting turns on. * * In Kepler34, these were the grow_trigger() call parameters: * * - Song-recording start tick. * - Current tick, "tick". * - Length: len = seq_length() - (tick % seq_length()). * * That length snaps the end of the trigger to the next whole sequence * interval. * * \question * Do we need to call set_dirty_mp() here? * * \param tick * Provides the current tick, which helps set the recorded block's * boundaries. It is the tick at which the current trigger ends. */ void sequence::song_recording_stop (midipulse tick) { (void) perf()->calculate_snap(tick); /* issue #44 redux */ grow_trigger(song_record_tick(), tick, 1); if (song_recording_snap()) off_from_snap(true); m_song_playback_block = m_song_recording = false; } /** * If we're playing a recorded pattern (one that has Song-mode triggers), and * the user pauses playing in the middle of some notes, upon restart we want * to replay the Note Ons that are before the current tick and before the * Note On's linked Note Off. * \verbatim t tick t+length ---------------------------------------------------------------- | : | | A | on XXXXXXXXXXXXXXX off | on XXXXXXXXXXXXXXX off | | : | | B | on XXXXX off : | on XXXXX off | | : | | C | off XXXXXXXXXXX on : | off XXXXXXXXXXX on | | : | | D | XXXXXXX off on XXXXXXXXXX| XXXXXXX off on XXXXXXXXXX| | : | | E | XXXXXXX off : on XXX| XXXXXXX off on XXX| | : | | ---------------------------------------------------------------- Wn Wn+1 \endverbatim * * where Wn is the current window of the pattern length (demarcated by times * t to t+length-1), and tick is the tick at which the user paused playback. * Let T = tick % length. Assuming all notes are linked properly, we have * the following cases: * * A: on < T, off > T, so emit the Note On again. * B: on < T, off < T, no note events to emit. * C: same disposition as B. * D: on < T and > off, so emit the Note On again. * E: on > tick, emit the Note On normally. * * Currently, cases D and E are not handled because we have disabled handling * note wrap-around for now. See eventlist::link_new() and the * SEQ66_USE_STAZED_LINK_NEW_EXTENSION macro. * * We think this explanation in the Kepler34 code is incorrect: * * "If the Note-On event is after the Note-Off event, the pattern wraps * around, so that we play it now to resume." * * One question is where is best to do the locking of put_event_on_bus(). In * retrospect, probably better to do it just once, instead of for each event. * * \param tick * The current tick-time, in MIDI pulses. */ void sequence::resume_note_ons (midipulse tick) { automutex locker(m_mutex); /* better here? */ if (get_length() > 0) { for (auto & ei : m_events) { if (ei.is_note_on_linked()) /* note on linked */ { midipulse on = ei.timestamp(); /* see banner notes */ midipulse off = ei.link()->timestamp(); midipulse rem = tick % get_length(); if (on < rem && (off > rem || on > off)) put_event_on_bus(ei); } } } } /** * Makes a calculation for expanded recording, used in seqedit and qseqroll. * This is the way Seq32 does it now, and it seems to work for Seq66. * * \return * Returns true if we are recording, expanded-record is enabled, * and we're far enough along in the current length to move to the next * "time window". */ bool sequence::expand_recording () const { bool result = false; if (expanding()) { midipulse tstamp = m_last_tick; if (tstamp >= expand_threshold()) { #if defined SEQ66_PLATFORM_DEBUG_TMI printf ( "tick %ld >= length %ld - measure %ld / 4\n", tstamp, get_length(), unit_measure() ); #endif result = true; } } return result; } /* * Static member function. */ recordstyle sequence::loop_record_style (int ri) { recordstyle result = recordstyle::merge; int min = usr().pattern_record_code(result); int max = usr().pattern_record_code(recordstyle::max); if (ri > min && ri < max) result = static_cast(ri); return result; } /** * Code to help user-interface callers. Used in qseqeditframe64. */ bool sequence::update_recording (int index) { recordstyle rectype = loop_record_style(index); bool result = rectype != recordstyle::max; if (result) { switch (rectype) { case recordstyle::merge: case recordstyle::overwrite: case recordstyle::expand: case recordstyle::oneshot: break; case recordstyle::oneshot_reset: /* * This might be surprising. Use the new grid slot * menu popup entry "Clear events" to do this. * * clear_events(); */ m_last_tick = 0; set_recording(toggler::on); break; default: result = false; break; } if (result) result = set_recording_style(rectype); } return result; } /** * Implements the actions brought forth from the Tools (hammer) button. * * Note that the push_undo() calls push all of the current events (in * sequence::m_events) onto the stack (as a single entry). * * \todo * Move this code into eventlist (without the redraw call). */ void sequence::handle_edit_action (eventlist::edit action, int var) { switch (action) { case eventlist::edit::select_all_notes: select_all_notes(); break; case eventlist::edit::select_inverse_notes: select_all_notes(true); break; case eventlist::edit::select_all_events: select_events(m_status, m_cc); break; case eventlist::edit::select_inverse_events: select_events(m_status, m_cc, true); break; case eventlist::edit::randomize_events: (void) randomize(m_status, var); break; case eventlist::edit::quantize_notes: /* * sequence::quantize_events() is used in recording as well, so we do * not want to incorporate sequence::push_undo() into it. So we make * a new function to do that. */ push_quantize(EVENT_NOTE_ON, 0, 1); break; case eventlist::edit::quantize_events: push_quantize(m_status, m_cc, 1); break; case eventlist::edit::tighten_notes: push_quantize(EVENT_NOTE_ON, 0, 2); break; case eventlist::edit::tighten_events: push_quantize(m_status, m_cc, 2); break; case eventlist::edit::transpose_notes: /* regular transpose */ transpose_notes(var, 0); set_dirty(); /* updates perfedit */ break; case eventlist::edit::transpose_harmonic: /* harmonic transpose */ transpose_notes(var, musical_scale()); set_dirty(); /* updates perfedit */ break; default: break; } } /** * Exportability ensures that the sequence pointer is valid. This function * adds all triggered events. * * For each trigger in the sequence, add events to the list below; fill * one-by-one in order, creating a single long sequence. * * Do we want to add this here? * * Then set a single trigger for the big sequence: start at zero, end at * last trigger end with snap. We're going to reference (not copy) the * triggers now. * * The we adjust the sequence length to snap to the nearest measure past the * end. We fill the MIDI container with trigger "events", and then the * container's bytes are written. * * tick_end() isn't quite a trigger length, off by 1. Subtracting * tick_start() can really screw it up. */ bool sequence::flatten (sequence & destseq, bool maketrigger) { bool result = is_exportable(); if (result) { midipulse last_ts = 0; const auto & trigs = get_triggers(); for (auto & t : trigs) last_ts = flatten_trigger(destseq, t, last_ts); const trigger & ender = trigs.back(); midipulse seqend = ender.tick_end(); if (maketrigger) { midipulse measticks = measures_to_ticks(); /* 1 measure length */ if (measticks > 0) { midipulse remainder = seqend % measticks; if (remainder != (measticks - 1)) seqend += measticks - remainder - 1; result = destseq.add_trigger(0, seqend); } } int m = destseq.get_measures(seqend); result = destseq.set_measures(m, true); /* a user change */ } return result; } /** * Fills in sequence events based on the trigger and events in the sequence * associated with this snippets. * * Derived from midi_vector_base::song_fill_seq_event(). * * This calculation needs investigation. The number of times the pattern is * played is given by how many pattern lengths fit in the trigger length. * But the commented calculation adds to the value of 1 already assigned. * And what about triggers that are somehow of 0 size? Let's try a different * calculation, currently the same. * * int times_played = 1; * times_played += (trig.tick_end() - trig.tick_start()) / len; * * \param destseq * The destination for the flattened data. * * \param trig * The current trigger to be processed. * * \param prev_timestamp * The time-stamp of the previous event. * * \return * The next time-stamp value is returned. */ midipulse sequence::flatten_trigger ( sequence & destseq, const trigger & trig, midipulse prev_timestamp ) { midipulse len = get_length(); midipulse trig_offset = trig.offset() % len; midipulse start_offset = trig.tick_start() % len; midipulse time_offset = trig.tick_start() + trig_offset - start_offset; int times_played = 1 + (trig.length() - 1) / len; if (trig_offset > start_offset) /* offset len too far */ time_offset -= len; int note_is_used[c_notes_count]; for (int i = 0; i < c_notes_count; ++i) note_is_used[i] = 0; /* initialize to off */ for (int p = 0; p <= times_played; ++p, time_offset += len) { for (auto e : events()) /* use a copy of event */ { midipulse timestamp = e.timestamp() + time_offset; if (timestamp >= trig.tick_start()) /* at/after trigger */ { if (e.is_note()) /* includes aftertouch */ { midibyte note = e.get_note(); if (trig.transposed()) e.transpose_note(trig.transpose()); if (e.is_note_on()) { if (timestamp <= trig.tick_end()) ++note_is_used[note]; /* count the note */ else continue; /* skip */ } else if (e.is_note_off()) { if (note_is_used[note] > 0) { --note_is_used[note]; /* turn off the note */ if (timestamp > trig.tick_end()) timestamp = trig.tick_end(); } else continue; /* if no Note On, skip */ } } } else continue; /* before trigger, skip */ if (timestamp >= trig.tick_end()) /* event past trigger */ { if (! e.is_note()) /* (also aftertouch) */ continue; /* drop the event */ } prev_timestamp = timestamp; e.set_timestamp(timestamp); /* * This function locks the mutex, appends the event, and, if * a note-off, sorts, verifies and links, which trims added notes * since we've not yet increased the sequence's length value. * So we do the minimum here. * * destseq.add_event(e); */ bool ok = destseq.events().append(e); if (! ok) break; } } return prev_timestamp; } } // namespace seq66 /* * sequence.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/setmapper.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file setmapper.cpp * * This module declares a class to manage a number of screensets. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-02-12 * \updates 2025-05-21 * \license GNU GPLv2 or above * * Implements three classes: seq, screenset, and setmapper, which replace a * number of data items and functions in the seq66::performer class. * * Together, these classes emulate the way Sequencer64 worked, with 32 sets * of 32 patterns, and some extra data buffers to supplement the information * within each sequence object. The sequence number is still the same, from * 0 on up (e.g. to 1023). The performer object can access sequences by * number, as before, but it no longer has to make calculations for picking * sets and mute-groups, and it can let the setmanager do the looping in a * lot of cases. * * Create a new sequence from a file: * * -# midifile::finalize_sequence(p, seqref, seqno, screenset). * This function calculates the preferred seq number from the number * in the MIDI file and the desired destination screenset. * -# performer::add_sequence(seqptr, prefseqnum). We need to keep the * seq and set numbers separate and pass them along. * -# performer::install_sequence(seqptr, seqno). Ditto. * -# setmapper::install_sequence(seqptr, seqno, setnum) * -# screenset::install_sequence(seqptr, seqno). Let the sequence * the original seqno, and let seq hold the set and modified * seqno. * * Create a new sequence from the GUI: * * -# perform::new_sequence(seqno). Again, the seqno is based on the * old style of numbering, obtained by mouse location and set * selection. * * Mute groups: * * Mute-group handling in Sequencer64 was perhaps needlessly complex, with * a number of similarly named functions in the performer class. The * functionality has been offloaded to setmapper and the mutegroups class. * The process should be simple: * * -# Enter learn-mode, then obtain the number of the desired mute-group * from the keyboard or a MIDI control. * -# Notify all subscribers. * -# Copy the current armed/muted statuses of the playing screen into * the desired mute-group. * -# Exit learn-mode and notify all subscribers. * -# TO BE CONTINUED... */ #include /* std::cout */ #include "cfg/settings.hpp" /* seq66::rc() */ #include "play/mutegroups.hpp" /* seq66::mutegroups class */ #include "play/setmapper.hpp" /* seq66::setmapper class */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /** * Creates a manager for all of the sets in a tune, and also the mutegroups. * * After creation, screenset 0 is created and set as the play-screen. * * \param mgs * Provides the existing mutegroup to be managed. * * \param sets * Provides the maximum number of sets to be supported and managed. * * \param rows * Provides the number of rows (virtual rows) in each set and mutegroup. * We could use the mutegroups sizes, perhaps. * * \param columns * Provides the number of columns (virtual columns) in each set and * mutegroup. */ setmapper::setmapper ( setmaster & mc, mutegroups & mgs, int rows, int columns ) : m_mute_groups (mgs), m_set_size (rows * columns), m_set_master (mc), /* master() accessor */ m_sequence_count (0), m_sequence_max ( m_set_size * seq::limit() / m_set_size ), m_sequence_high (seq::unassigned()), m_edit_sequence (seq::unassigned()), m_set_clipboard (seq::unassigned(), rows, columns), m_playscreen (seq::unassigned()), m_playscreen_pointer (nullptr), m_tracks_mute_state (m_set_size, false) { (void) reset(); } /** * Resets back to the constructor set. This means we have one set, the empty * play-screen, plus a "dummy" set. */ bool setmapper::reset () { clear(); bool result = master().reset(); /* clear and create initial set */ if (result) result = set_playscreen(0); return result; } /** * Given the raw sequence number, returns the calculated set number and the * offset of the sequence in the set. * * \param seqno * The raw sequence number. Normally, this value can range from 0 to * 1023, or whatever the maximum is based on set size and number of sets. * * \param [out] offset * Holds the calculated offset. It will always be clamped from 0 to * m_set_size. * * \return * Returns the calculated set number. It is clamped to a valid value. */ screenset::number setmapper::seq_set (seq::number seqno, int & offset) const { screenset::number result = clamp(seqno / m_set_size); offset = seqno % m_set_size; return result; } bool setmapper::fill_play_set (playset & p, bool clearit) { return play_screen()->fill_play_set(p, clearit); } bool setmapper::add_to_play_set (playset & p, sequence * s) { seq::number seqno = s->seq_number(); screenset & sset = screen(seqno); /* tricky !!! */ bool result = sset.usable(); if (result) result = sset.add_to_play_set(p, seqno); return result; } bool setmapper::add_to_play_set (playset & p, screenset & s) { return s.fill_play_set(p, false); } bool setmapper::add_all_sets_to_play_set (playset & p) { bool result = true; for (auto & sset : sets()) /* screenset reference */ { if (! sset.second.fill_play_set(p, false)) result = false; } return result; } bool setmapper::copy_screenset (screenset::number srcset, screenset::number destset) { const screenset & src = master().screen(srcset); screenset & dest = master().screen(destset); bool result = src.usable() && dest.usable(); if (result) { result = dest.copy_patterns(src); if (result) recount_sequences(); } return result; } bool setmapper::save_screenset (screenset::number srcset) { const screenset & src = master().screen(srcset); bool result = src.usable(); if (result) { screenset & dest = m_set_clipboard; dest.change_set_number(0); /* make a guarantee */ result = dest.copy_patterns(src); /* also sets the name */ if (result) { // anything to do? } } return result; } bool setmapper::paste_screenset (screenset::number destset) { const screenset & src = m_set_clipboard; bool result = src.usable(); if (result) { screenset & dest = master().screen(destset); result = dest.copy_patterns(src); if (result) recount_sequences(); } return result; } /** * Re-evaluates the number of sequences and the maximum sequence number. * This is needed when we bypass the normal add-sequence function when * pasting a screenset. */ void setmapper::recount_sequences () { m_sequence_count = m_sequence_high = 0; for (auto & sset : sets()) /* screenset reference */ { int count = sset.second.active_count(); /* side-effect high seq */ int high = sset.second.sequence_high(); m_sequence_count += count; if (high > m_sequence_high) m_sequence_high = high; } } /** * Get an existing screenset, or return the dummy (unusable) screenset. */ const screenset & setmapper::screen (seq::number seqno) const { screenset::number setno = seq_set(seqno); return master().screen(setno); } /** * Look up the screen to be used, given the sequence number. If the screen * doesn't exist, and is a legal screenset number, then create it. */ screenset & setmapper::screen (seq::number seqno) { screenset::number setno = seq_set(seqno); screenset & desired_screen = master().screen(setno); if (desired_screen.usable()) { return desired_screen; } else if (master().is_screenset_valid(setno)) { if (seqno < seq::limit()) { auto newset = add_set(setno); return newset->second; } else return dummy_screenset(); } else return dummy_screenset(); } /** * Adds the sequence pointer to the matching screenset. The caller should * have already made its initial setup of the sequence, and then should * relinquish ownership of the pointer. * * If the screenset does not already exist, it is created and added to the * list of screensets the setmapper manages. Then the sequence is added. * * \param s * Provides the sequencer pointer, which is rejected if null. * * \param seqno * The desired sequence number, which is a linear value ranging from 0 to * a number determined by the active set size multiplied by the number of * sets. It is merely a starting point, and will be incremented until an * unused sequence number is found. The actual value is returned if the * function result is true. * * \return * Returns true if the sequence pointer was able to be added to the * screenset. Null pointers to sequence are not added. False can be * returned if the sequence number already exists in the screenset. */ bool setmapper::add_sequence (sequence * s, seq::number & seqno) { bool result = false; if (not_nullptr(s)) { screenset & sset = screen(seqno); /* tricky !!! */ while (! result) { result = sset.usable(); if (result) result = sset.add(s, seqno); if (! result) { ++seqno; if (seqno == m_sequence_max) break; } } if (result) { seq::number n = seqno + 1; ++m_sequence_count; if (n > m_sequence_high) m_sequence_high = n; /* no way to back out, tho */ } } return result; } bool setmapper::any_modified_sequences () const { bool result = false; for (const auto & sset : sets()) /* screenset reference */ { if (sset.second.any_modified_sequences()) { result = true; break; } } return result; } void setmapper::unmodify_all_sequences () { for (auto & sset : sets()) /* screenset reference */ sset.second.unmodify_all_sequences(); } /** * \param seqno * If not seq::unassigned(), this pattern is to be soloed. */ void setmapper::off_sequences (seq::number seqno) { for (auto & sset : sets()) { if (sset.second.active()) sset.second.off_sequences(seqno); } } /** * Dirties up a sequence. * * \param seqno * Either a track number or seq::all() (the default value). */ void setmapper::set_dirty (seq::number seqno) { if (seqno == seq::all()) { for (auto & sset : sets()) /* screenset reference */ sset.second.set_dirty(); } else { screenset::number setno = seq_set(seqno); auto setiterator = sets().find(setno); if (setiterator != sets().end()) setiterator->second.set_dirty(seqno); } } /** * Toggles a sequence. * * \param seqno * Either a track number or seq::all() (the default value). */ void setmapper::toggle (seq::number seqno) { if (seqno == seq::all()) { for (auto & sset : sets()) /* screenset reference */ sset.second.toggle(); } else { screenset::number setno = seq_set(seqno); auto setiterator = sets().find(setno); if (setiterator != sets().end()) setiterator->second.toggle(seqno); } } /** * Toggles the mutes status of all playing (currently unmuted) tracks in the * current set of active patterns/sequences on all screen-sets. Covers * tracks from 0 to m_sequence_max. The statuses are preserved for * restoration. * * If no sequences are armed, then turn them all on, as a convenience to the * user. * * Note that this function should operate only in Live mode; it is too * confusing to use in Song mode. */ void setmapper::toggle_playing_tracks () { if (armed()) /* any seqs armed? */ { if (m_armed_saved) { m_armed_saved = false; apply_armed_statuses(); } else m_armed_saved = learn_armed_statuses(); } else mute(); } /** * Toggles the song-mute of a sequence. * * \param seqno * Either a track number or seq::all() (the default value). */ void setmapper::toggle_song_mute (seq::number seqno) { if (seqno == seq::all()) { for (auto & sset : sets()) /* screenset reference */ sset.second.toggle_song_mute(); } else { screenset::number setno = seq_set(seqno); auto setiterator = sets().find(setno); if (setiterator != sets().end()) setiterator->second.toggle_song_mute(seqno); } } /** * This plays all sets at once. Could be a useful feature, but the very * large b4uacuse-stress MIDI file reveals a lot of crackling in Yoshimi * playback. Compare it to the plain play() function. */ void setmapper::play_all_sets ( midipulse tick, sequence::playback mode, bool resumenoteons ) { for (auto & sset : sets()) sset.second.play(tick, mode, resumenoteons); } /** * Installs the sequence/track/loop. The performer class does a lot of the * work first, and also does the modification check, so it is not done here. * However, if the sequence requires a new set to be created, we do it here. * * \param s * Provides a pointer to the sequence to be installed. * * \param seqno * Provides the number of the sequence, which also determines what set it * it is in. The actual value is returned if the function result is * true. * * \return * Returns true if the sequence was added successfully. */ bool setmapper::install_sequence (sequence * s, seq::number & seqno) { bool result = true; screenset::number setno = seq_set(seqno); auto setiterator = sets().find(setno); if (setiterator == sets().end()) { auto setp = add_set(setno); result = setp != sets().end(); } if (result) result = add_sequence(s, seqno); return result; } /** * Removes a sequence from the tune, based on its sequence number. It will * not be removed if it is in editing. * * \param seqno * Provides the sequence number, used to look up any existing sequences. * All sequences have a unique number ranging from 0 to 1023. Also, * please note that the setmapper does lookups, including of screensets, * using this range of numbers, rather than set number. * * \return * Returns true if the sequence did exist and was removed successfully. * If true, the caller should decrement its sequence count. */ bool setmapper::remove_sequence (seq::number seqno) { screenset & sset = screen(seqno); /* tricky */ bool result = ! sset.usable(); /* doesn't exist, we're golden */ if (! result) { result = sset.remove(seqno); /* it exists, remove it! */ #if defined USE_OLD_CODE if (result) { if (m_sequence_count > 1) /* allow for the dummy sequence */ --m_sequence_count; } #else recount_sequences(); /* ca 2025-05-20 */ #endif } return result; } /** * Learns the current armed status, and also toggles them. * * \return * Returns true if an armed status was found. */ bool setmapper::learn_armed_statuses () { bool result = false; for (auto & sset : sets()) /* screenset ref */ { bool ok = sset.second.learn_armed_statuses(); if (ok) result = true; } return result; } /** * Applies transpose to a sequence. * * \param seqno * Either a track number or seq::all() (the default value). */ void setmapper::apply_song_transpose (seq::number seqno) { if (seqno == seq::all()) { for (auto & sset : sets()) /* screenset reference */ sset.second.apply_song_transpose(); } else { screenset::number setno = seq_set(seqno); auto setiterator = sets().find(setno); if (setiterator != sets().end()) setiterator->second.apply_song_transpose(seqno); } } midipulse setmapper::max_timestamp () const { midipulse result = 0; for (const auto & sset : sets()) { midipulse t = sset.second.max_timestamp(); if (t > result) result = t; } return result; } midipulse setmapper::max_extent () const { midipulse result = 0; for (const auto & sset : sets()) { midipulse t = sset.second.max_extent(); if (t > result) result = t; } return result; } /* * ------------------------------------------------------------------------- * Triggers * ------------------------------------------------------------------------- */ /** * Counts all the triggers in all the sequences in all the sets. */ int setmapper::trigger_count () const { int result = 0; for (const auto & sset : sets()) { result += sset.second.trigger_count(); } return result; } /** * Locates the largest trigger value among the active sequences. * * \return * Returns the highest trigger value, or zero. It is not clear why * this function doesn't return a "no trigger found" value. Is there * always at least one trigger, at 0? */ midipulse setmapper::max_trigger () const { midipulse result = 0; for (const auto & sset : sets()) { midipulse t = sset.second.max_trigger(); if (t > result) result = t; } return result; } /** * Selected the triggers in the range of the timestamp/sequence box. * * \param seqlow * Provides the low track to be selected. * * \param seqhigh * Provides the high track to be selected. If not in the same set, * nothing is done. We need a way to report that. * * \param tick_start * Provides the low end of the box. * * \param tick_finish * Provides the high end of the box. */ void setmapper::select_triggers_in_range ( seq::number seqlow, seq::number seqhigh, midipulse tick_start, midipulse tick_finish ) { screenset::number setlow = seq_set(seqlow); screenset::number sethigh = seq_set(seqhigh); if (setlow == sethigh) { auto setiterator = sets().find(setlow); if (setiterator != sets().end()) { setiterator->second.select_triggers_in_range /* in screenset */ ( seqlow, seqhigh, tick_start, tick_finish ); } } } /** * Deselects the triggers in a sequence. * * \param seqno * Either a track number or seq::all() (the default value). */ void setmapper::unselect_triggers (seq::number seqno) { if (seqno == seq::all()) { for (auto & sset : sets()) /* screenset reference */ sset.second.unselect_triggers(); } else { screenset::number setno = seq_set(seqno); auto setiterator = sets().find(setno); if (setiterator != sets().end()) setiterator->second.unselect_triggers(seqno); } } /** * If the left tick is less than the right tick, then, for each sequence * that is active, its triggers are moved by the difference between the * right and left in the specified direction. * * \param lefftick * Provides the low of the box. * * \param righttick * Provides the high end of the box. * * \param direction * The direction of the trigger move. False is leftward, and true is * rightward. * * \param seqno * Either a track number or seq::all() (the default value). * * \return * Returns true if a change was made. */ bool setmapper::move_triggers ( midipulse lefttick, midipulse righttick, bool direction, seq::number seqno ) { bool result = false; if (righttick > lefttick) { midipulse distance = righttick - lefttick; if (seqno == seq::all()) { for (auto & sset : sets()) /* screenset reference */ { if (sset.second.move_triggers(lefttick, distance, direction)) result = true; } } else { screenset::number setno = seq_set(seqno); auto setiterator = sets().find(setno); if (setiterator != sets().end()) { result = setiterator->second.move_triggers ( lefttick, distance, direction, seqno ); } } } return result; } /** * If the left tick is less than the right tick, then, for each sequence that * is active, its triggers are copied, offset by the difference between the * right and left. This copies the triggers between the L marker and R * marker to the R marker. * * \param seqno * Either a track number or seq::all() (the default value). */ void setmapper::copy_triggers ( midipulse lefttick, midipulse righttick, seq::number seqno ) { if (righttick > lefttick) { midipulse distance = righttick - lefttick; if (seqno == seq::all()) { for (auto & sset : sets()) /* screenset reference */ sset.second.copy_triggers(lefttick, distance); } else { screenset::number setno = seq_set(seqno); auto setiterator = sets().find(setno); if (setiterator != sets().end()) setiterator->second.copy_triggers(lefttick, distance, seqno); } } } /* * ------------------------------------------------------------------------- * Play-screen * ------------------------------------------------------------------------- */ /** * If the desired play-screen exists, unmark the current play-screen and mark * the new one. * * If there was a existing screen-set \a setno, just mark it (again). * Otherwise, if the set number is valid, then create a new screenset and set * it as the play-screen. * * We no longer check for a change in m_playscreen, because doing so leads to * a segfault... bad set? * * \param setno * Provides the desired set number. This ranges from 0 to 2047, though * generally the number of sets is 32 or lower. There is a screenset * #2048 that always exists in order to provide an inactive/dummy * screenset. We decided to go by the setmaster's limit rather than * screenset::limit() [2048]. * * \return * Returns true if the play-screen was able to be set. */ bool setmapper::set_playscreen (screenset::number setno) { bool result = setno >= 0 && setno < master().screenset_max(); if (result) { auto sset = sets().find(setno); result = false; if (sset != sets().end()) { auto oldset = sets().find(m_playscreen); if (oldset != sets().end()) oldset->second.is_playscreen(false); m_playscreen = setno; sset->second.is_playscreen(true); result = true; } else { auto setp = add_set(setno); if (setp != sets().end()) { set_playscreen(setno); setp->second.is_playscreen(true); result = true; } } if (! result) m_playscreen = 0; /* use the always-present set 0 */ m_playscreen_pointer = &sets().at(m_playscreen); } return result; } /** * Sets the screen-set that is active, based on the value of m_screenset. * This function is called when one of the snapshot keys is pressed. * * For each value up to m_seqs_in_set (32), the index of the current sequence * in the current screen-set is obtained. If the sequence * is active and the sequence actually exists, it is processed; null * sequences are no longer flagged as an error, they are just ignored. * * Modifies the playscreen, stores the current playing-status of each sequence, * and calls turns on unmuted tracks in the current screen-set. * Basically, this function retrieves and saves the playing status of the * sequences in the current play-screen, sets the play-screen to the current * screen-set, and then mutes the previous play-screen. It is called via the * c_midi_control_play_ss value or via the set-playing-screen-set keystroke. */ bool setmapper::set_playing_screenset (screenset::number setno) { bool result = set_playscreen(setno); if (result) { result = play_screen()->learn_bits(m_tracks_mute_state); if (result && rc().is_setsmode_normal()) mute_group_tracks(); } return result; } /* * ------------------------------------------------------------------------- * Mutes * ------------------------------------------------------------------------- */ /** * Applies a mute group to the current play-screen. */ bool setmapper::apply_mutes (mutegroup::number group) { midibooleans bits; bool result = mutes().apply(group, bits); if (result) result = play_screen()->apply_bits(bits); return result; } /** * Applies a mute group to the current play-screen. */ bool setmapper::unapply_mutes (mutegroup::number group) { midibooleans bits; bool result = mutes().unapply(group, bits); if (result) result = play_screen()->apply_bits(bits); return result; } /** * Toggles a mute group to the current play-screen. The mutegroups::toggle() * function determines if the specified mute-group is active. If not, then * the armable patterns in the mute-group are turned on. * * Otherwise, all the bits are cleared, so that the whole set (which includes * patterns in the mute group and patterns that the user may have * subsequently armed individually) is zero. An alternative is to disarm * only the patterns in the mute group, leaving the other ones armed. */ bool setmapper::toggle_mutes (mutegroup::number group) { midibooleans bits; bool result = mutes().toggle(group, bits); if (result) result = play_screen()->apply_bits(bits); return result; } bool setmapper::toggle_active_mutes (mutegroup::number group) { midibooleans armedbits; bool result = play_screen()->learn_bits(armedbits); if (result) { result = mutes().toggle_active(group, armedbits); if (result) result = play_screen()->apply_bits(armedbits); } return result; } /** * Sets the statuses of a mute group to the sequence statuses of the * current play-screen. * * If in group-learn mode, this function gets the playing statuses * of all of the sequences in the current play-screen, and copies them into * the desired mute-group. Then, no matter what, it makes the desired * mute-group the selected mute-group. * * \param learnmode * Set to true if we are learning a mute-group. * * \param group * The number of the mute-group to add or to update. * */ bool setmapper::learn_mutes (bool learnmode, mutegroup::number group) { bool result = learnmode; if (result) { auto mgiterator = mutes().list().find(clamp_group(group)); bool update = mgiterator != mutes().list().end(); midibooleans bits; result = play_screen()->learn_bits(bits); if (result) { if (update) { result = mgiterator->second.set(bits); } else { #if defined USE_OLD_CODE mutegroup mg(group); /* default rows, columns */ result = mg.set(bits); if (result) result = mutes().add(group, mg); #else result = mutes().update(group, bits); #endif } if (result) { mutes().group_selected(group); mutes().group_learn(true); m_tracks_mute_state = bits; /* save playscreen vector */ } } } return result; } /** * Turn the playing of a sequence on or off. Used for the implementation of * sequence_playing_on() and sequence_playing_off(). * * Kepler34's version seems slightly different, may need more study. * * \param seqno * The sequence number to be affected. * * \param on * True if the sequence is to be turned on, false if it is to be turned * off. * * \param qinprogress * Indicates if queuing is in progress. This status is obtained from the * performer. */ void setmapper::sequence_playing_change ( seq::number seqno, bool on, bool qinprogress ) { int offset; screenset::number setno = seq_set(seqno, offset); auto setiterator = sets().find(setno); if (setiterator != sets().end()) { setiterator->second.sequence_playing_change(seqno, on, qinprogress); if (setiterator->second.is_playscreen()) m_tracks_mute_state[offset] = on; } } /** * A play-screen-specific version of sequence_playing_change(). */ void setmapper::sequence_playscreen_change ( seq::number seqno, bool on, bool qinprogress ) { int offset; screenset::number setno = seq_set(seqno, offset); if (setno == play_screen()->set_number()) { play_screen()->sequence_playing_change(seqno, on, qinprogress); m_tracks_mute_state[offset] = on; } } /** * Also note that screenset::is_seq_in_edit() could be used, but setmapper * currently has no reference the the screenset class. Yet, see the armed() * function just below! */ bool setmapper::is_seq_in_edit (seq::number seqno) const { seq::pointer sp = loop(seqno); bool result = bool(sp); if (result) result = sp->seq_in_edit(); return result; } /** * Returns true if even one sequence in one screenset is armed. */ bool setmapper::armed () const { for (auto & sset : sets()) /* screenset reference */ { if (sset.second.armed()) return true; } return false; } /** * If group_mode() is true, then this function operates. It loops through * every screen-set. In each screen-set, it acts on each active sequence. * If the active sequence is in the current "in-view" screen-set (m_screenset * as opposed to m_playscreen, and its m_track_mute_state[] is true, then the * sequence is turned on, otherwise it is turned off. The result is that the * in-view screen-set is activated as per the mute states, while all other * screen-sets are muted. */ void setmapper::mute_group_tracks () { if (group_mode()) { for (auto & sset : sets()) { bool pscreen = sset.second.is_playscreen(); int seqoffset = sset.second.offset(); for (int s = 0; s < m_set_size; ++s) { int seqno = seqoffset + s; if (is_seq_active(seqno)) /* semi-redundant check */ { bool on = pscreen && m_tracks_mute_state[s]; sequence_playing_change(seqno, on); } } } } } /** * Select a mute group and then mutes the track in the group. Called in * perform and in mainwnd. * * When in group-learn mode, for active sequences, the mute-group settings * are set based on the playing status of each sequence. Then the mute-group * is stored in m_tracks_mute_state[], which holds states for only the number * of sequences in a set. * * Compare to select_group_mute(); its main difference is that it will at * least copy the states even if not in group-learn mode. And, if in * group-learn mode, it will grab the playing states of the sequences before * copying them. * * This function is used only once, in select_and_mute_group(). It used to * be called just select_mute_group(), but that's too easy to confuse with * select_group_mute(). * * \param mutegroup * Provides the mute-group to select. * * \param group * Provides the group number for the group to be muted. */ void setmapper::select_and_mute_group (mutegroup::number group) { learn_mutes(mutes().is_group_learn(), group); mute_group_tracks(); } std::string setmapper::name (screenset::number setno) const { std::string result = dummy_screenset().name(); if (sets().find(setno) != sets().end()) { const auto & s = sets().at(setno); result = s.name(); } return result; } bool setmapper::name (screenset::number setno, const std::string & nm) { bool result = sets().find(setno) != sets().end(); if (result) { auto & s = sets().at(setno); s.name(nm); } return result; } void setmapper::show (bool showseqs) const { std::cout << master().sets_to_string(showseqs); } } // namespace seq66 /* * setmapper.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/setmaster.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file setmaster.cpp * * This module declares a class to manage a number of screensets. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-08-10 * \updates 2023-11-13 * \license GNU GPLv2 or above * * Implements setmaster. The difference between the setmaster and setmapper * is that setmaster provides a constant number of sets (4 x 8) used for the * managerment of sets, while setmapper translates between the performer and * the playing (current) screenset. */ #include /* std::cout */ #include /* std::ostringstream */ #include "cfg/settings.hpp" /* seq66::usr() */ #include "play/setmaster.hpp" /* seq66::setmaster class */ /* * This namespace is not documented because it screws up the document * processing done by Doxygen. */ namespace seq66 { /** * Creates a manager for all of the sets in a tune, at set level. It also * provides access to the container of sets and to the currently-selected set, * called the "play-screen". * * After creation, screenset 0 is created and set as the play-screen. */ setmaster::setmaster (int setrows, int setcolumns) : m_screenset_rows (setrows), m_screenset_columns (setcolumns), m_rows (c_rows), /* constant */ m_columns (c_columns), /* constant */ m_swap_coordinates (usr().swap_coordinates()), m_set_count (m_rows * m_columns), m_highest_set (-1), m_container () /* #, screenset map */ { if (! reset()) error_message("setmaster", "reset() failed()"); } /** * Resets back to the constructor set. This means we have one set, the empty * play-screen, plus a "dummy" set. */ bool setmaster::reset () { bool result = false; clear(); for (int i = 0; i < Size(); ++i) { auto setp = add_set(screenset::number(i)); result = setp != m_container.end(); if (! result) break; } if (result) { auto setp = add_set(screenset::limit()); /* create the dummy set */ result = setp != m_container.end(); } return result; } /** * Returns the set number for the given row and column. Remember that the * layout of sets matches that of sequences. See the top banner of the * setmaster.cpp file. This function is useful for picking the correct set in * the qsetmaster button array. * * Compare to screenset::grid_to_seq(). * * Set Layout: * * Like the sequences in the main (live) window, the set numbers are * transposed, so that the set number increments vertically, not horizontally: * \verbatim 0 4 8 12 16 20 24 28 1 5 9 13 17 21 25 29 2 6 10 14 18 22 26 30 3 7 11 15 19 23 27 31 \endverbatim * * This layout can also be "swapped" so that increasing pattern numbers go from * left to right instead of from to bottom. * * However, this grid never changes. There's a strong dependency on the number * of keys we can devote to this function (32) and the 4-rows by 8-columns * heritage of Seq24. * * \param row * Provides the desired row, clamped to a legal value. * * \param row * Provides the desired row, clamped to a legal value. * * \return * Returns the calculated set number, which will range from 0 to * (m_rows * m_columns) - 1 = m_set_count. If out of range, * set 0 is returned. */ screenset::number setmaster::grid_to_set (int row, int column) const { if (row < 0 || row >= m_rows || column < 0 || column >= m_columns) return 0; else if (swap_coordinates()) return column + m_columns * row; else return row + m_rows * column; } /** * Compare to screenset::index_to_grid(). Fixes found while fixing * issue #87. */ bool setmaster::index_to_grid (screenset::number setno, int & row, int & column) { bool result = is_screenset_valid(setno); if (result) { if (swap_coordinates()) { row = setno / m_columns; // not m_screenset_columns column = setno % m_columns; // not m_screenset_columns } else { row = setno % m_rows; // not m_screenset_rows column = setno / m_rows; // not m_screenset_rows } } return result; } /** * Creates and adds an "empty" (i.e. no activated sequences) screenset to * the container. * * This code revealed that, when items (screenset, seq, etc.) are to be * stored in containers, it is best to not have a const data member. These * make the assignment operator or constructor ill-formed, so that the * compiler deletes these functions! Then, calling std::make_pair() results * in very mysterious error messages. * * If remove any existing set at the given set number, and add a new one. * * Auto types: * * - std::pair * - std::pair */ setmaster::container::iterator setmaster::add_set (screenset::number setno) { (void) m_container.erase(setno); /* remove the existing item */ screenset newset(setno, m_screenset_rows, m_screenset_columns); auto setpair = std::make_pair(setno, newset); auto resultpair = m_container.insert(setpair); if (resultpair.second) { if (setno > m_highest_set && setno != screenset::limit()) m_highest_set = setno; } return resultpair.first; } /** * Removes a set, replacing it with an empty screenset. * This basically just clears the set, and should (must check) remove * it's sequences. */ bool setmaster::remove_set (screenset::number setno) { auto item = find_by_value(setno); bool result = item != m_container.end(); if (result) { (void) m_container.erase(item); screenset newset(setno, m_screenset_rows, m_screenset_columns); auto setpair = std::make_pair(setno, newset); auto resultpair = m_container.insert(setpair); result = resultpair.second; } return result; } /** * Clears a set in place, effectively deleting it. */ bool setmaster::clear_set (screenset::number setno) { auto item = find_by_value(setno); bool result = item != m_container.end(); if (result) { item->second.clear(); (void) item->second.name(""); } return result; } /** * Returns a reference to a screen, if found. Otherise, a reference to an * unusable dummy is returned. */ screenset & setmaster::screen (screenset::number setno) { auto sp = m_container.find(setno); /* look it up in the map */ return sp != m_container.end() ? sp->second : dummy_screenset(); } const screenset & setmaster::screen (screenset::number setno) const { auto sp = m_container.find(setno); /* look it up in the map */ return sp != m_container.end() ? sp->second : dummy_screenset(); } int setmaster::screenset_active_count () const { int result = 0; for (auto & sset : m_container) /* screenset reference */ { if (sset.second.active_count() > 0) ++result; } return result; } bool setmaster::any_in_edit () const { bool result = false; for (const auto & sset : m_container) { if (sset.second.any_in_edit()) { result = true; break; } } return result; } /** * Given a set number, counts through the container until it finds the matching * set number. We have to brute-force the lookup because there may be * unoccupied set-slots in between occupied set-slots. */ int setmaster::screenset_index (screenset::number setno) const { int result = int(seq::unassigned()); int index = 0; for (const auto & sset : m_container) { if (sset.second.set_number() == setno) { result = index; break; } ++index; } return result; } /** * For each screenset that exists, execute a set-handler function. */ bool setmaster::exec_set_function (screenset::sethandler s) { bool result = false; screenset::number index = 0; for (auto & sset : m_container) /* screenset reference */ { if (sset.second.usable()) { result = sset.second.exec_set_function(s, index++); if (! result) break; } } return result; } /** * Runs a set-handler and a slot-handler for each set. */ bool setmaster::exec_set_function (screenset::sethandler s, screenset::slothandler p) { bool result = false; for (auto & sset : m_container) /* screenset reference */ { if (sset.second.usable()) { result = sset.second.exec_set_function(s, p); if (! result) break; } } return result; } /** * Runs only a slot-handler for each slot (pattern) in each set. */ bool setmaster::exec_set_function (screenset::slothandler p) { bool result = false; for (auto & sset : m_container) /* screenset reference */ { if (sset.second.usable()) { result = sset.second.exec_slot_function(p); if (! result) break; } } return result; } /** * Does a brute-force lookup of the given set number, obtained by screenset * :: set_number(). We must use the long form of the for loop here, as far * as we can tell. */ setmaster::container::iterator setmaster::find_by_value (screenset::number setno) { return m_container.find(setno); } /** * \tricky * For use in the qsetmaster set-list, we need to look up the iterator to * a set by value, not by key, because after the first swap there is no * longer a correspondence between the key and the actual set-number * value. * * Also, the old method would lead to a core dump when the * qsmainwnd::on_set_change() function would get called. Valgrind was * no help in figuring this out. * * The new method changes the pair of sets in place, and seems to work. */ #if defined USE_OLD_CODE bool setmaster::swap_sets (screenset::number set0, screenset::number set1) { auto item0 = find_by_value(set0); auto item1 = find_by_value(set1); bool result = item0 != m_container.end() && item1 != m_container.end(); if (result) { screenset copy0{item0->second}; screenset copy1{item1->second}; copy0.change_set_number(set1); /* also changes seq #s */ copy1.change_set_number(set0); /* also changes seq #s */ (void) m_container.erase(item0); (void) m_container.erase(item1); auto setpair = std::make_pair(set1, copy0); auto resultpair = m_container.insert(setpair); result = resultpair.second; if (result) { setpair = std::make_pair(set0, copy1); resultpair = m_container.insert(setpair); result = resultpair.second; } } return result; } #else bool setmaster::swap_sets (screenset::number set0, screenset::number set1) { screenset & copy0 = screen(set0); screenset & copy1 = screen(set1); bool result = copy0.usable() && copy1.usable(); if (result) { screenset tempsrc = screen(set0); copy0.change_set_number(set1); /* also changes seq #s */ copy1.change_set_number(set0); /* also changes seq #s */ copy0.copy_patterns(copy1); copy1.copy_patterns(tempsrc); } return result; } #endif std::string setmaster::set_to_string (screenset::number setno) const { const screenset & sc = screen(setno); return sc.to_string(); } std::string setmaster::sets_to_string (bool showseqs, int limit) const { std::ostringstream result; result << "Sets" << (showseqs ? " and Sequences:" : ":") << std::endl; int counter = 0; for (auto & s : m_container) { int keyvalue = int(s.first); if (keyvalue < screenset::limit()) { result << " Key " << int(s.first) << ": "; if (s.second.usable()) result << s.second.to_string(showseqs, limit); else result << std::endl; } if (++counter == limit) break; } return result.str(); } void setmaster::show (bool showseqs, int limit) const { std::cout << sets_to_string(showseqs, limit); } } // namespace seq66 /* * setmaster.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/play/songsummary.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file songsummary.cpp * * This module declares/defines a class for writing summary information about * a MIDI file. * * \library seq66 application * \author Chris Ahlstrom * \date 2021-01-22 * \updates 2025-10-16 * \license GNU GPLv2 or above * */ #include /* std::ifstream and std::ofstream */ #include /* std::hex etc. */ #include /* std::map<> template */ #include "cfg/settings.hpp" /* seq66::rc() and choose_ppqn() */ #include "midi/midi_vector_base.hpp" /* seq66::c_notes, other tags */ #include "play/performer.hpp" /* must precede songsummary.hpp ! */ #include "play/sequence.hpp" /* seq66::sequence */ #include "play/songsummary.hpp" /* seq66::songsummary */ #include "util/strfunctions.hpp" /* seq66::bool_to_string() */ namespace seq66 { std::map s_tag_names_container { { c_midibus, "Track output buss" }, { c_midichannel, "Track output channel" }, { c_midiclocks, "Track clocking" }, { c_triggers, "Old triggers" }, { c_notes, "Set notes" }, { c_timesig, "Track time signature" }, { c_bpmtag, "Main beats/minute" }, { c_triggers_ex, "Track trigger data" }, { c_mutegroups, "Song mute group data" }, { c_gap_A, "Gap A" }, { c_gap_B, "Gap B" }, { c_gap_C, "Gap C" }, { c_gap_D, "Gap D" }, { c_gap_E, "Gap E" }, { c_gap_F, "Gap F" }, { c_midictrl, "MIDI control" }, { c_musickey, "Track key" }, { c_musicscale, "Track scale" }, { c_backsequence, "Track background sequence" }, { c_transpose, "Track transposability" }, { c_perf_bp_mes, "Perfedit beats/measure" }, { c_perf_bw, "Perfedit beat-width" }, { c_tempo_map, "Reserve seq32 tempo map" }, { c_midiinbus, "Track input bus" }, { c_musicchord, "Track chord" }, { c_tempo_track, "Alternate tempo track number" }, { c_seq_color, "Color" }, { c_seq_edit_mode, "Normal/drum edit mode, not saved/used" }, { c_seq_loopcount, "N-repeat for pattern" }, { c_reserved_3, "Reserved 3" }, { c_reserved_4, "Reserved 4" }, { c_trig_transpose, "Transposable trigger" } }; /** * Principal constructor. * * \param name * Provides the name of the MIDI file to be read or written. */ songsummary::songsummary (const std::string & name) : m_name (name) { // no other code needed } /** * A rote destructor. */ songsummary::~songsummary () { // empty body } /** * Write the whole MIDI data and Seq24 information out to a text file. * * \param p * Provides the object that will contain and manage the entire * performance. * * \param doseqspec * If true (the default, then the Seq66-specific SeqSpec sections * are written to the file. If false, we want to export the tracks as a * basic MIDI sequence (which is not the same as exporting a Song, with * triggers, as a MIDI sequence). * * \return * Returns true if the write operations succeeded. */ bool songsummary::write (performer & p, bool doseqspec) { std::ofstream file(name(), std::ios::out | std::ios::trunc); bool result = file.is_open(); if (result) { result = write_header(file, p); } if (result) { for (int track = 0; track < p.sequence_high(); ++track) { if (p.is_seq_active(track)) { seq::pointer s = p.get_sequence(track); if (s) { result = write_sequence(file, s); if (! result) break; } } } } if (result && doseqspec) { result = write_proprietary_track(file, p); if (! result) { file_error("SeqSpec write failed", name()); } } return result; } bool songsummary::write_sequence (std::ofstream & file, seq::pointer s) { int triggercount = s->trigger_count(); file << "Sequence #" << s->seq_number() << " '" << s->name() << "'\n" << " Input port #: " << int(s->seq_midi_in_bus()) << "-->" << int(s->true_in_bus()) << "\n" << " Output port #: " << int(s->seq_midi_bus()) << "-->" << int(s->true_bus()) << "\n" << " Channel: " << int(s->seq_midi_channel()) << "\n" << " Beats: " << s->get_beats_per_bar() << "/" << s->get_beat_width() << "\n" << " Length (ticks): " << long(s->get_length()) << "\n" << "Events;triggers: " << s->event_count() << "; " << triggercount << "\n" << " Transposable: " << bool_to_string(s->transposable()) << "\n" << " Key and scale: " << int(s->musical_key()) << "; " << int(s->musical_scale()) << "\n" ; if (s->color() >= 0) { #if defined SEQ66_COLORS_NOT_REQUIRING_A_GUI PaletteColor pc = PaletteColor(s->color()); std::string colorname = get_color_name(pc); file << " Color: " << s->color() << " " << colorname << "\n"; #else file << " Color: " << s->color() << "\n"; #endif } /* * The format of c_triggers_ex: 0x24240008, followed by a length value * of 4 + triggercount * 12. Each trigger has three 4-byte values: * trigger-on, trigger-off, and trigger-offset. The c_trig_transpose * (0x24240020) tag adds a byte value for trigger transposition. */ if (triggercount > 0) { file << s->trigger_listing() << std::endl; } return true; } /** * For each groups in the mute-groups, * * mutegroups has rows and columns for each group, but doesn't have a way to * iterate through all the groups. */ void songsummary::write_mute_groups ( std::ofstream & file, const performer & p ) { bool got_mutes = false; const mutegroups & mutes = p.mutes(); for (const auto & stz : mutes.list()) { int groupnumber = stz.first; const mutegroup & m = stz.second; bool ok = m.any(); if (ok) { midibooleans mutebits = m.get(); ok = mutebits.size() > 0; if (ok) { int count = 0; got_mutes = true; file << "Mute group #" << std::setw(2) << groupnumber << ": "; for (auto mutestatus : mutebits) { file << (bool(mutestatus) ? "1" : "0"); if (++count % 8 == 0) file << " "; } file << " \"" << m.name() << "\"" << std::endl; } else file << "Mute group #" << groupnumber << " error" << std::endl; } else { file << "Mute group #" << groupnumber << " empty" << std::endl; } } if (! got_mutes) file << "All mute-groups are of size 0" << std::endl; } bool songsummary::write_header (std::ofstream & file, const performer & p) { int numtracks = 0; for (int i = 0; i < p.sequence_high(); ++i) { if (p.is_seq_active(i)) ++numtracks; /* count number of active tracks */ } bool result = numtracks > 0; if (result) { file << "File name: " << name() << "\n" << "No. of sets: " << p.screenset_count() << "\n" << "No. of tracks: " << numtracks << "\n" << "MIDI format: " << 1 << "\n" << "PPQN: " << p.ppqn() << "\n" ; } else { file << "File name: " << name() << "\n" << "No. of tracks: " << 0 << "! Aborting!\n" ; } return result; } /** * Writes a "proprietary" (SeqSpec) Seq24 header item. * * The new format writes 0x00 0xFF 0x7F len 0x242400xx; the first 0x00 is the * delta time. * * In the new format, the 0x24 is a kind of "manufacturer ID". At * http://www.midi.org/techspecs/manid.php we see that most manufacturer IDs * start with 0x00, and are thus three bytes long, or start with codes at * 0x40 and above. Similary, this site shows that no manufacturer uses 0x24: * * http://sequence15.blogspot.com/2008/12/midi-manufacturer-ids.html * * \warning * Currently, the manufacturer ID is not handled; it is part of the * data, which can be misleading in programs that analyze MIDI files. * * \param control_tag * Determines the type of sequencer-specific section to be written. * It should be one of the value in the globals module, such as * c_midibus or c_mutegroups. * * \param value * This value appears in the output, and its meaning depends on the * control tag. */ void songsummary::write_prop_header ( std::ofstream & file, midilong control_tag, int value ) { std::string ctagname = "Unknown"; std::map::const_iterator ci = s_tag_names_container.find(control_tag); if (ci != s_tag_names_container.end()) ctagname = ci->second; file << "0xFF 0x7F " << std::hex << control_tag << std::dec << " " << ctagname << " = " << value << "\n" ; } void songsummary::write_set_names ( std::ofstream & file, const performer & p ) { int setcount = p.screenset_count(); file << "Screen-set Notes:" << "\n"; write_prop_header(file, c_notes, setcount); for (int s = 0; s < setcount; ++s) { const std::string & note = p.set_name(s); file << " Set #" < /* std::sort(), std::merge() */ #include "cfg/settings.hpp" /* seq66::rc() settings access */ #include "midi/midi_vector_base.hpp" /* c_triggers_ex, c_trig_transpose */ #include "play/sequence.hpp" /* the "parent" of the triggers */ #include "play/triggers.hpp" /* seq66::triggers helper class */ #include "util/strfunctions.hpp" /* seq66::bool_to_string() */ namespace seq66 { /** * The default constructor creates an invalid trigger. */ trigger::trigger () : m_tick_start (c_midipulse_max), /* start (LONG_MAX) > end is invalid */ m_tick_end (0), m_offset (0), m_transpose (0), m_selected (false) { // No code needed } trigger::trigger ( midipulse tick, midipulse len, midipulse offset, midibyte tpose ) : m_tick_start (tick), m_tick_end (tick + len - 1), m_offset (offset), m_transpose (0), /* set in constructor body */ m_selected (false) { transpose_byte(tpose); /* convert byte to scaled int */ } void trigger::rescale (int newppqn, int oldppqn) { m_tick_start = rescale_tick(m_tick_start, newppqn, oldppqn); m_tick_end = rescale_tick(m_tick_end, newppqn, oldppqn); m_offset = rescale_tick(m_offset, newppqn, oldppqn); } std::string trigger::to_string () const { std::string result = "trigger: "; result += std::to_string(tick_start()); result += " to "; result += std::to_string(tick_end()); result += " offset "; result += std::to_string(offset()); result += " transpose by "; result += std::to_string(transpose()); return result; } /** * Gets the total number of bytes needed to store all the triggers in the * container. Non-transposed triggers can save a byte, and also be backward * compatible to Seq24. However, we're currently stuck with one size or the * other for all the triggers in one pattern/sequence, and so this is a * static function for now. */ int trigger::datasize (midilong seqspec) { if (seqspec == c_trig_transpose) /* adds a "transpose" byte */ return 3 * 4 + 1; else if (seqspec == c_triggers_ex) /* seq24's c_triggers_new */ return 3 * 4; else if (seqspec == c_triggers) /* not seen in the wild */ return 2 * 4; else return 0; } /** * Principal constructor. * * \param parent * The triggers object often needs to tell its parent sequence object * what to do (such as stop playing). */ triggers::triggers (sequence & parent) : m_parent (parent), m_triggers (), m_number_selected (0), m_clipboard (), m_undo_stack (), m_redo_stack (), m_draw_iterator (), m_trigger_copied (false), m_paste_tick (c_no_paste_trigger), // stazed m_ppqn (0), m_length (0) { // Empty body } /** * Principal assignment operator. Follows the stock rules for such an * operator, but does a little more then just assign member values. * * \param rhs * Provides the "right-hand side" of the assignment operation. * * \return * Returns a reference to self, for use in concatenated assignment * operations. */ triggers & triggers::operator = (const triggers & rhs) { if (this != &rhs) { /* * Reference member: m_parent = rhs.m_parent; */ m_triggers = rhs.m_triggers; m_clipboard = rhs.m_clipboard; m_undo_stack = rhs.m_undo_stack; m_redo_stack = rhs.m_redo_stack; m_draw_iterator = rhs.m_draw_iterator; m_trigger_copied = rhs.m_trigger_copied; m_ppqn = rhs.m_ppqn; m_length = rhs.m_length; } return *this; } bool triggers::rescale (int newppqn, int oldppqn) { bool result = oldppqn > 0; if (result) { for (auto & t : m_triggers) t.rescale(newppqn, oldppqn); set_length(rescale_tick(m_length, newppqn, oldppqn)); } return result; } /** * Gets the total number of bytes needed to store all the triggers in the * container. Non-transposed triggers can save a byte, and also be backward * compatible to Seq24. However, we're currently stuck with one size or the * other. */ int triggers::datasize (midilong seqspec) const { return count() * trigger::datasize(seqspec); } bool triggers::any_transposed () const { bool result = false; for (auto & t : m_triggers) { if (t.transposed()) { result = true; break; } } return result; } bool triggers::change_ppqn (int p) { bool result = p > 0; if (result) { bool result = rescale(p, m_ppqn); /* new & old PPQN */ if (result) set_ppqn(p); } return result; } /** * Pushes the list-trigger into the trigger undo-list, then flags each * item in the undo-list as unselected. */ void triggers::push_undo () { m_undo_stack.push(m_triggers); for (auto & t : m_undo_stack.top()) unselect(t, false); /* do not count this unselection */ } /** * If the trigger undo-list has any items, the list-trigger is pushed * into the redo list, the top of the undo-list is coped into the * list-trigger, and then pops from the undo-list. */ void triggers::pop_undo () { if (m_undo_stack.size() > 0) { m_redo_stack.push(m_triggers); m_triggers = m_undo_stack.top(); m_undo_stack.pop(); } } /** * If the trigger redo-list has any items, the list-trigger is pushed * into the undo list, the top of the redo-list is coped into the * list-trigger, and then pops from the redo-list. */ void triggers::pop_redo () { if (m_redo_stack.size() > 0) { m_undo_stack.push(m_triggers); m_triggers = m_redo_stack.top(); m_redo_stack.pop(); } } /** * If playback-mode (song mode) is in force, that is, if using in-triggers * and on/off triggers, this function handles that kind of playback. * This is a new function for sequence :: play() to call. * * The for-loop goes through all the triggers, determining if there are * trigger start/end values before the \a end_tick. If so, then the trigger * state is set to true (start only within the tick range) or false (end is * within the tick range), and the trigger tick is set to start or end. The * first start or end trigger that is past the end tick cause the search to * end. * * ------------------------------------- * tick_start | | tick_end * ------------------------------------- * start_tick || start_tick || * end_tick || end_tick * * Song recording: * * If we've reached a new chunk of drawn sequences in the song data, and * we're not recording, unset the block on this sequence's events. * * If the trigger state has changed, then the start/end ticks are passed back * to the sequence, and the trigger offset is adjusted. * * If we have reached a new chunk of drawn patterns in the song data, and we * are not recording, then trigger unsets the playback block on this * pattern's events. The song_playback_block() function deals with live-play * recording of triggers. * * \param start_tick * Provides the starting tick value, and returns the modified value as a * side-effect. * * \param end_tick * Provides the ending tick value, and returns the modified value as a * side-effect. * * \param resumenoteons * Indicates what to do with notes when song-recording. * * \return * Returns true if we're through playing the frame (trigger turning off), * and the caller should stop the playback. * * \sideeffect * - start_tick and end_tick as noted above * - sequence::song_playback_block() [in] * - sequence::set_armed() and armed() [in] * - sequence::resume_note_ons() * - sequence::set_trigger_offset(); * - sequence::last_tick(); [in] */ bool triggers::play ( midipulse & start_tick, midipulse & end_tick, int & transpose, bool resumenoteons ) { bool result = false; /* turns off after frame play */ bool trigger_state = false; midipulse tick = start_tick; /* saved for later */ midipulse trigger_offset = 0; midipulse trigger_tick = 0; int tp = 0; transpose = 0; for (auto & t : m_triggers) { /* * See the song_playback_block() function note in the banner. */ if (t.at_trigger_transition(start_tick, end_tick)) m_parent.song_playback_block(false); midipulse trigstart = t.tick_start(); midipulse trigend = t.tick_end(); midipulse trigoffset = t.offset(); if (trigstart <= end_tick) /* trigger in range... */ { trigger_state = true; trigger_tick = trigstart; trigger_offset = trigoffset; tp = t.transpose(); } if (trigend <= end_tick) /* ... but ends early */ { trigger_state = false; trigger_tick = trigend; trigger_offset = trigoffset; } if (trigstart > end_tick || trigend > end_tick) break; } /* * Had triggers in the slice, not equal to current state. Therefore, it * is time to change the sequence trigger state. We only change state if * we are not improvising (i.e. playing "Live"). */ bool ok = trigger_state != m_parent.armed(); if (ok) ok = ! m_parent.song_playback_block(); if (ok) { if (trigger_state) /* turning on */ { if (trigger_tick < m_parent.last_tick()) start_tick = m_parent.last_tick(); /* side-effect */ else start_tick = trigger_tick; /* side-effect */ m_parent.set_armed(true); /* side-effect */ /* * If triggered between a Note On and a Note Off, then play it. * Fixed for issue #5. */ if (resumenoteons) m_parent.resume_note_ons(tick); } else { end_tick = trigger_tick; /* on, turning off */ result = true; /* done, ditto */ } } else { // Add probe code here } bool offplay = m_triggers.empty() && m_parent.armed(); if (offplay) offplay = ! m_parent.song_playback_block(); if (offplay) m_parent.set_armed(false); /* stop playing */ else transpose = tp; /* side-effect */ m_parent.set_trigger_offset(trigger_offset); return result; } /** * Adjusts the given offset by mod'ing it with m_length and adding * m_length if needed, and returning the result. * * \param offset * Provides the offset, mod'ed against m_length, used to adjust the * offset. * * \return * Returns the new offset. However, if m_length is 0, no change is made, * and the original offset is returned. */ midipulse triggers::adjust_offset (midipulse offset) { if (m_length > 0) { offset %= m_length; if (offset < 0) offset += m_length; } return offset; } /** * Adds a trigger. * * \param tick * Provides the tick (pulse) time at which the trigger goes on. * * \param len * Provides the length of the trigger. This value is actually calculated * from the "on" value minus the "off" value read from the MIDI file. * * \param offset * This value specifies the offset of the trigger. It is a feature of * the c_triggers_ex that c_triggers doesn't have. It is the third * value in the trigger specification of the Seq66 MIDI file. The default * value is 0. * * \param transpose * If the even newer tag value c_trig_transpose is used, it add this value, * which is 0x00 for no transposition, and 0x40 is the base value ("zero") * for transposition. * * \param fixoffset * If true, the offset parameter is modified by adjust_offset() first. * We think that basically makes sure it is positive. The default is true. */ void triggers::add ( midipulse tick, midipulse len, midipulse offset, midibyte transpose, bool fixoffset ) { if (tick >= 0 && len >= 0) { midipulse adjusted_offset = fixoffset ? adjust_offset(offset) : offset; trigger t(tick, len, adjusted_offset, transpose); for (auto ti = m_triggers.begin(); ti != m_triggers.end(); /* ++ti */) { midipulse tickstart = ti->tick_start(); midipulse tickend = ti->tick_end(); if (tickstart >= t.tick_start() && tickend <= t.tick_end()) { unselect(*ti); /* adjust selection count */ ti = m_triggers.erase(ti); /* inside new one? erase. */ continue; /* skip the ++ti */ } else if (tickend >= t.tick_end() && tickstart <= t.tick_end()) { ti->tick_start(t.tick_end() + 1); /* event's end inside? */ } else if (tickend >= t.tick_start() && tickstart <= t.tick_start()) { ti->tick_end(t.tick_start() - 1); /* last start, new end */ } ++ti; /* tricky code */ } m_triggers.push_back(t); sort(); } } /** * This function examines each trigger in the trigger list. If the given * position is between the current trigger's tick-start and tick-end * values, the these values are copied to the start and end parameters, * respectively, and then we exit. * * \param position * The position to examine. * * \param start * The destination for the starting tick (m_tick_start) of the * matching trigger. * * \param ender * The destination for the ending tick (m_tick_end) of the * matching trigger. * * \return * Returns true if a trigger was found whose start/end ticks * contained the position. Otherwise, false is returned, and the * start and end return parameters should not be used. */ bool triggers::intersect (midipulse position, midipulse & start, midipulse & ender) { for (auto & ti : m_triggers) { if (ti.tick_start() <= position && position <= ti.tick_end()) { start = ti.tick_start(); /* return by reference */ ender = ti.tick_end(); /* ditto */ return true; } } return false; } bool triggers::intersect (midipulse position) { for (auto & ti : m_triggers) { if (ti.tick_start() <= position && position <= ti.tick_end()) return true; } return false; } /** * Grows a trigger. This function looks for the first trigger where * the tickfrom parameter is between the trigger's tick-start and tick-end * values. If found then the trigger's start is moved back to tickto, if * necessary, or the trigger's end is moved to tickto plus the length * parameter, if necessary. * * Then this new trigger is added, and the function breaks from the search * loop. * * \param tickfrom * The desired from-value back which to expand the trigger, if necessary. * * \param tickto * The desired to-value towards which to expand the trigger, if necessary. * * \param len * The additional length to append to tickto for the check. */ bool triggers::grow_trigger (midipulse tickfrom, midipulse tickto, midipulse len) { bool result = false; for (auto & t : m_triggers) { midipulse start = t.tick_start(); midipulse ender = t.tick_end(); if (start <= tickfrom && tickfrom <= ender) { midipulse calcend = tickto + len - 1; if (tickto < start) start = tickto; if (calcend > ender) { #if defined SEQ66_PLATFORM_DEBUG_TMI printf("Growing trigger from %ld to %ld (%ld), length %ld\n", long(tickfrom), long(tickto), long(calcend), long(len)); #endif ender = calcend; } add(start, ender - start + 1, t.offset()); result = true; break; } } return result; } /** * Deletes the first trigger that brackets the given tick from the * trigger-list. * * \param tick * Provides the tick to be examined. */ bool triggers::remove (midipulse tick) { bool result = false; for (auto i = m_triggers.begin(); i != m_triggers.end(); ++i) { if (i->tick_start() <= tick && tick <= i->tick_end()) { unselect(*i); /* adjust selection count */ m_triggers.erase(i); result = true; break; } } return result; } void triggers::sort () { std::sort(m_triggers.begin(), m_triggers.end()); } /** * Splits a single trigger into two triggers. The * original trigger ends 1 tick before the splittick parameter, * and the new trigger starts at splittick and ends where the original * trigger ended. * * \param trig * Provides the original trigger, and also holds the changes made to * that trigger as it is shortened, as a side-effect. * * \param splittick * The position just after where the original trigger will be * truncated, and the new trigger begins. */ bool triggers::split (trigger & trig, midipulse splittick) { midipulse new_tick_end = trig.tick_end(); midipulse new_tick_start = splittick; midipulse len = new_tick_end - new_tick_start; bool result = len > 1; trig.tick_end(splittick - 1); if (result) add(new_tick_start, len + 1, trig.offset()); return result; } /** * Splits the first trigger that brackets the splittick parameter. This is * the first trigger where splittick is greater than L and less than R. * * \param splittick * Provides the tick that must be bracketed for the split to be made. * * \return * Returns true if a split was able to be made. */ bool triggers::split (midipulse splittick, trigger::splitpoint splittype) { bool result = false; for (auto & t : m_triggers) { if (t.tick_start() <= splittick && splittick <= t.tick_end()) { midipulse tick = splittick; /* snap or exact */ midipulse offset = 0; if (splittype == trigger::splitpoint::middle) { tick = (t.tick_end() - t.tick_start() + 1) / 2; offset = t.tick_start(); } result = split(t, tick + offset); break; } } return result; } /** * Adjusts trigger offsets to the length specified for all triggers, and undo * triggers. * * \param newlength * Provides the length to which to adjust the offsets. */ void triggers::adjust_offsets_to_length (midipulse newlength) { for (auto & t : m_triggers) { t.offset(adjust_offset(t.offset())); t.offset(m_length - t.offset()); /* flip */ midipulse inverse_offset = m_length - (t.tick_start() % m_length); midipulse local_offset = (inverse_offset - t.offset()); local_offset %= m_length; midipulse inverse_offset_new = newlength - (t.tick_start() % newlength); midipulse new_offset = inverse_offset_new - local_offset; /** COMMON CODE? **/ t.offset(new_offset % newlength); t.offset(newlength - t.offset()); } } /** * Copies triggers to a point distant from a given tick. * * \param starttick * The current location of the triggers. * * \param distance * The distance away from the current location to which to copy the * triggers. */ void triggers::copy (midipulse starttick, midipulse distance) { midipulse from_start_tick = starttick + distance; midipulse from_end_tick = from_start_tick + distance - 1; move(starttick, distance, true); for (auto & t : m_triggers) { midipulse tickstart = t.tick_start(); if (tickstart >= from_start_tick && tickstart <= from_end_tick) { midipulse tickend = t.tick_end(); trigger xt; xt.offset(t.offset()); xt.tick_start(tickstart - distance); if (tickend <= from_end_tick) xt.tick_end(tickend - distance); else if (tickend > from_end_tick) xt.tick_end(from_start_tick - 1); xt.increment_offset(m_length - (distance % m_length)); xt.offset(t.offset() % m_length); if (xt.offset() < 0) xt.increment_offset(m_length); m_triggers.push_back(xt); } } sort(); /* m_triggers.sort() */ } /** * Find the first trigger that brackets the given tick from the * trigger-list. Then return a reference to the trigger. * * \param tick * Provides the "x" location of the drop-click in the song-editor piano * roll, in units of pulses. * * \return * Returns a reference to the trigger, if found. Otherwise, a * reference to a default trigger is returned. Use the is_valid() * function before doing something with the trigger. */ const trigger & triggers::find_trigger (midipulse tick) const { static trigger s_dummy; for (auto i = m_triggers.begin(); i != m_triggers.end(); ++i) { if (i->tick_start() <= tick && tick <= i->tick_end()) return *i; } return s_dummy; } const trigger & triggers::find_trigger_by_index (int index) const { static trigger s_dummy; if (count() > index) { int counter = 0; for (auto i = m_triggers.begin(); i != m_triggers.end(); ++i) { if (counter++ == index) return *i; } } return s_dummy; } /** * Moves triggers in the trigger-list. There's no way to optimize this by * saving tick values, as they are potentially modified at each step. * \verbatim tick_start() tick_end() ----------------------- ----------------------- | | | | | x | | | | | | | ----------------------- ----------------------- starttick \endverbatim * * \param starttick * The current location of the triggers. * * \param distance * The distance away from the current location to which to move the * triggers. * * \param direction * If true, the triggers are moved forward. If false, the triggers are * moved backward. * * \param single * If true (the default), then only the selected trigger will move. * Other wise, that trigger and all subsequent triggers will be moved. * The definition of "subsequent" depends on the direction parameter. */ bool triggers::move ( midipulse starttick, midipulse distance, bool direction, bool single ) { bool result = (starttick + distance) > 0; if (result) { int counter = 0; for (auto & t : m_triggers) /* ++counter */ { if (t.tick_start() >= starttick) { if (direction) /* forward */ { midipulse stopper = (-1); const trigger & tnext = find_trigger_by_index(counter + 1); if (tnext.is_valid()) stopper = tnext.tick_start(); midipulse added_end = t.tick_end() + distance; result = stopper == (-1) || added_end < stopper; if (result) { midipulse added = t.tick_start() + distance; t.tick_start(added); t.tick_end(added_end); added = (t.offset() + distance) % m_length; t.offset(added); t.offset(adjust_offset(t.offset())); } if (single) break; } else /* backward */ { midipulse stopper = (-1); const trigger & tprev = find_trigger_by_index(counter - 1); if (tprev.is_valid()) stopper = tprev.tick_end(); midipulse deducted_start = t.tick_start() - distance; result = stopper == (-1) || deducted_start > stopper; if (result) result = deducted_start >= 0; if (result) { midipulse deducted_end = t.tick_end() - distance; result = true; t.tick_start(deducted_start); t.tick_end(deducted_end); deducted_end = (m_length - (distance % m_length)) % m_length; t.offset(deducted_end); t.offset(adjust_offset(t.offset())); } if (single) break; } } ++counter; } } return result; } /** * The following code was removed from the move() function. Kept around in * case it matters; it is a feature from Seq24. It causes a trigger moved * by a key to get split. */ void triggers::move_split ( midipulse starttick, midipulse distance, bool direction ) { midipulse endtick = starttick + distance; for (auto i = m_triggers.begin(); i != m_triggers.end(); ++i) { if (i->tick_start() < starttick && starttick < i->tick_end()) { if (direction) /* forward */ split(*i, starttick); else /* backward */ split(*i, endtick); } if (i->tick_start() < starttick && starttick < i->tick_end()) { if (direction) /* forward */ split(*i, starttick); else /* backward */ i->tick_end(starttick - 1); } if ( i->tick_start() >= starttick && i->tick_end() <= endtick && ! direction ) { unselect(*i); /* adjust sel count */ m_triggers.erase(i); i = m_triggers.begin(); /* A BETTER WAY? */ } if (i->tick_start() < endtick && endtick < i->tick_end()) { if (! direction) /* forward */ i->tick_start(endtick); } } move(starttick, distance, direction, false /* ? */); } /** * Gets the selected trigger's start tick. We guess this ends up selecting * only one trigger, otherwise only the last selected one would effectively * set the result. * * \return * Returns the tick_start() value of the last-selected trigger. If no * triggers are selected, then midipulse(-1) is returned. */ midipulse triggers::get_selected_start () { midipulse result = midipulse(-1); for (auto & t : m_triggers) { if (t.selected()) result = t.tick_start(); } return result; } /** * Gets the selected trigger's end tick. * * \return * Returns the tick_end() value of the last-selected trigger. If no * triggers are selected, then midipulse(-1) is returned. */ midipulse triggers::get_selected_end () { midipulse result = midipulse(-1); for (auto & t : m_triggers) { if (t.selected()) result = t.tick_end(); } return result; } /** * Moves selected triggers as per the given parameters. * \verbatim mintick][0 1][maxtick 2 \endverbatim * * The \a which parameter has three possible values: * * -# If we are moving 0 (triggers::grow::start), use first as offset. * -# If we are moving the 1 (triggers::grow::end), use the last as the offset. * -# If we are moving both, 2 (triggers::grow::move), use first as offset. * * \param tick * The tick at which the trigger starts. * * \param fixoffset * Set to true if the offset is to be adjusted. * * \param which * Selects which movement will be done, as discussed above. * See the values of the triggers::grow type. * * \return * Returns true if there was room to move. Otherwise, false is returned. * We need this feature to support keystoke movement of a selected * trigger in the perfroll window, and keep it from continually * incrementing when there can be no more movement. This causes moving * the other direction to be delayed while the accumulating movement * counter is used up. However, right now we can't rely on this result, * and ignore it. There may be no way around this minor issue. */ bool triggers::move_selected (midipulse tick, bool fixoffset, grow which) { bool result = true; midipulse mintick = 0; midipulse maxtick = 0x7ffffff; /* 0x7fffffff ? */ auto s = m_triggers.begin(); for (auto i = m_triggers.begin(); i != m_triggers.end(); ++i) { if (i->selected()) { s = i; if (++i != m_triggers.end()) maxtick = i->tick_start() - 1; midipulse deltatick = 0; if (which == triggers::grow::end) { midipulse ppqn_start = s->tick_start() + (m_ppqn / 8); deltatick = tick - s->tick_end(); if (deltatick > 0 && tick > maxtick) deltatick = maxtick - s->tick_end(); if (deltatick < 0 && (deltatick + s->tick_end() <= ppqn_start)) deltatick = ppqn_start - s->tick_end(); } else if (which == triggers::grow::start) { midipulse ppqn_end = s->tick_end() - (m_ppqn / 8); deltatick = tick - s->tick_start(); if (deltatick < 0 && tick < mintick) deltatick = mintick - s->tick_start(); if (deltatick > 0 && (deltatick + s->tick_start() >= ppqn_end)) deltatick = ppqn_end - s->tick_start(); } else if (which == triggers::grow::move) { deltatick = tick - s->tick_start(); if (deltatick < 0 && tick < mintick) deltatick = mintick - s->tick_start(); if (deltatick > 0 && (deltatick + s->tick_end()) > maxtick) deltatick = maxtick - s->tick_end(); } /* * This code must be executed, even if deltatick == 0! * And setting result = deltatick == 0 causes some weirdness * in selection movement with the arrow keys in the perfroll. */ if (which == triggers::grow::start || which == triggers::grow::move) s->increment_tick_start(deltatick); if (which == triggers::grow::end || which == triggers::grow::move) s->increment_tick_end(deltatick); if (fixoffset) { s->increment_offset(deltatick); s->offset(adjust_offset(s->offset())); } break; } else mintick = i->tick_end() + 1; } return result; } void triggers::offset_selected (midipulse tick, grow editmode) { for (auto & t : m_triggers) { if (t.selected()) { if ( editmode == triggers::grow::start || editmode == triggers::grow::move ) { t.increment_tick_start(tick); } if ( editmode == triggers::grow::end || editmode == triggers::grow::move ) t.increment_tick_end(tick); if (editmode == triggers::grow::move) t.increment_offset(tick); } } } /** * Get the ending value of the last trigger in the trigger-list. * * \return * Returns the tick-end for the last trigger, if available. Otherwise, 0 * is returned. */ midipulse triggers::get_maximum () const { midipulse result = 0; if (! m_triggers.empty()) result = m_triggers.back().tick_end(); return result; } /** * Checks the list of triggers against the given tick. If any * trigger is found to bracket that tick, then true is returned. * * \param tick * Provides the tick of interest. * * \return * Returns true if a trigger is found that brackets the given tick. */ bool triggers::get_state (midipulse tick) const { bool result = false; for (const auto & t : m_triggers) { if (t.tick_start() <= tick && tick <= t.tick_end()) { result = true; break; } } return result; } bool triggers::transpose (midipulse tick, int transposition) { bool result = false; for (auto & t : m_triggers) { if (t.tick_start() <= tick && tick <= t.tick_end()) { result = transposition != t.transpose(); if (result) t.transpose(transposition); break; } } return result; } /** * Selects the desired trigger. Checks the list of triggers against the given * tick. If any trigger is found to bracket that tick, then true is returned, * and the trigger is marked as selected. * * \param tick * Provides the tick of interest. * * \return * Returns true if a trigger is found that brackets the given tick. */ bool triggers::select (midipulse tick) { bool result = false; for (auto & t : m_triggers) { if (t.tick_start() <= tick && tick <= t.tick_end()) { select(t); result = true; } } return result; } /** * Unselects the desired trigger. Checks the list of triggers against the * given tick. If any trigger is found to bracket that tick, then true is * returned, and the trigger is marked as unselected. * * \param tick * Provides the tick of interest. * * \return * Returns true if a trigger is found that brackets the given tick. */ bool triggers::unselect (midipulse tick) { bool result = false; for (auto & t : m_triggers) { if (t.tick_start() <= tick && tick <= t.tick_end()) { unselect(t); result = true; } } return result; } /** * Unselects all triggers for the sequence. * * \return * Always returns false. */ bool triggers::unselect () { for (auto & t : m_triggers) unselect(t); return false; } /** * Deletes the first selected trigger that is found. * * \return * Returns true if a trigger was removed. */ bool triggers::remove_selected () { bool result = false; for (auto i = m_triggers.begin(); i != m_triggers.end(); ++i) { if (i->selected()) { unselect(*i); /* this adjusts the selection count */ m_triggers.erase(i); result = true; break; } } return result; } /** * Copies the first selected trigger that is found. */ void triggers::copy_selected () { for (auto & t : m_triggers) { if (t.selected()) { m_clipboard = t; m_trigger_copied = true; break; } } } /** * If there is a copied trigger, then this function grabs it from the trigger * clipboard and adds it. It pastes at the copy end. or at the paste-tick, * if supplied. * * \param paste_tick * Provides the optional tick at which to paste the trigger. If not * set to c_no_paste_trigger, this value is used to adjust the paste * offset. */ void triggers::paste (midipulse paste_tick) { if (m_trigger_copied) { midipulse len = m_clipboard.tick_end() - m_clipboard.tick_start() + 1; if (paste_tick == c_no_paste_trigger) { add(m_clipboard.tick_end() + 1, len, m_clipboard.offset() + len); m_clipboard.tick_start(m_clipboard.tick_end() + 1); m_clipboard.tick_end(m_clipboard.tick_start() + len - 1); midipulse offset = m_clipboard.offset() + len; m_clipboard.offset(adjust_offset(offset)); } else { /* * Set the +/- distance to paste the tick, from the start. */ long offset = paste_tick - m_clipboard.tick_start(); add(paste_tick, len, m_clipboard.offset() + offset); m_clipboard.tick_start(paste_tick); m_clipboard.tick_end(m_clipboard.tick_start() + len - 1); m_clipboard.increment_offset(offset); m_clipboard.offset(adjust_offset(m_clipboard.offset())); set_trigger_paste_tick(c_no_paste_trigger); /* reset */ } m_trigger_copied = false; } } /** * Get the next trigger in the trigger list. * * \return * Returns the next trigger. If there is none, a default trigger object * is returned. */ trigger triggers::next () { trigger result; if (! cend(m_draw_iterator)) { result = *m_draw_iterator; ++m_draw_iterator; } return result; } /** * Selects the given trigger and increments the count of selected triggers if * appropriate. Don't confuse this function with select(midipulse). * * \param t * Provides a reference to the desired trigger. * * \param count * If true, count the selection. This can only be done in normal * triggers, not triggers in the undo container. */ void triggers::select (trigger & t, bool count) { if (! t.selected()) { t.selected(true); if (count) ++m_number_selected; } } /** * Unselects the given trigger and decrements the count of selected triggers if * appropriate. Don't confuse this function with unselect(midipulse). * * \param t * Provides a reference to the desired trigger. * * \param count * If true, uncount the selection. This can only be done in normal * triggers, not triggers in the undo container. */ void triggers::unselect (trigger & t, bool count) { if (t.selected()) { t.selected(false); if (count) { if (m_number_selected > 0) { --m_number_selected; } else { warnprint("trigger unselect yields count error"); } } } } /** * Prints a list of the currently-held triggers. * * \param seqname * A tag name to accompany the print-out, for the human to read. */ void triggers::print (const std::string & seqname) const { printf ( "sequence '%s' triggers (%d selected):\n", seqname.c_str(), number_selected() ); for (const auto & t : m_triggers) { printf ( " tick_start = %ld; tick_end = %ld; offset = %ld; selected = %s\n", long(t.tick_start()), long(t.tick_end()), long(t.offset()), bool_to_string(t.selected()).c_str() ); } } std::string triggers::to_string () const { std::string result = std::to_string(count()); result += " triggers:\n"; for (const auto & t : m_triggers) { result += " "; result += t.to_string(); result += "\n"; } return result; } } // namespace seq66 /* * triggers.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/seq66_features.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file seq66_features.cpp * * This module adds some functions that reveal the features compiled into * the Seq66 application. * * \library seq66 application * \author Chris Ahlstrom * \date 2017-03-12 * \updates 2026-04-15 * \license GNU GPLv2 or above * * The first part of this file defines a couple of global structure * instances, followed by the global variables that these structures * completely replace. */ #include /* std::ostringstream */ #include "seq66-config.h" /* automake-generated or for qmake */ #include "seq66_features.hpp" /* feature macros, seq66 namespace */ #if defined SEQ66_PLATFORM_UNIX #include /* C::isatty(3) */ #endif #if defined SEQ66_PLATFORM_WINDOWS #include /* C::_isatty() for Windows */ #endif namespace seq66 { /** * Hard-wired replacements for build macros. Also modifiable at run-time * via the "set" functions */ #if defined SEQ66_PLATFORM_WINDOWS static std::string s_app_build_os = "Windows 10"; /* FIXME */ static std::string s_app_build_issue = "Microsoft Windows"; #endif #if defined SEQ66_PLATFORM_MACOSX static std::string s_app_build_os = "MacOSX"; /* FIXME */ static std::string s_app_build_issue = "Apple MacOSX"; #endif #if defined SEQ66_PLATFORM_UNIX /* Linux? */ static std::string s_app_build_os = SEQ66_APP_BUILD_OS; #if defined SEQ66_PLATFORM_FREEBSD static std::string s_app_build_issue = "FreeBSD"; /* FIXME */ #else static std::string s_app_build_issue = SEQ66_APP_BUILD_ISSUE; #endif #endif static std::string s_pane_focus; static std::string s_alsa_version; static std::string s_jack_version; static std::string s_qt_version; static std::string s_app_engine = SEQ66_APP_ENGINE; static std::string s_app_name = SEQ66_APP_NAME; static std::string s_app_path; static std::string s_app_type = SEQ66_APP_TYPE; static bool s_app_cli = false; static std::string s_apptag = SEQ66_APP_NAME " " SEQ66_VERSION; static std::string s_arg_0 = ""; static std::string s_client_name = SEQ66_CLIENT_NAME; /* can change */ static std::string s_client_name_short = SEQ66_CLIENT_NAME; static std::string s_client_name_tag = "[" SEQ66_CLIENT_NAME "]"; static std::string s_icon_name = SEQ66_ICON_NAME; /* unchanging */ static std::string s_package_name = SEQ66_PACKAGE_NAME; static std::string s_session_tag = "Session"; static std::string s_api_version = SEQ66_API_VERSION; static std::string s_version = SEQ66_VERSION; static std::string s_versiontext = SEQ66_APP_NAME " " SEQ66_VERSION " " SEQ66_GIT_VERSION " " SEQ66_VERSION_DATE_SHORT "\n"; /** * Sets the focus for the current action. This is an arbitrary string * that Help / Keystrokes can use. */ void set_pane_focus (const std::string & s) { s_pane_focus = s; } /** * Sets version strings. Meant to be called where the engine or API is used. * Otherwise, empty. */ void set_alsa_version (const std::string & v) { s_alsa_version = v; } void set_jack_version (const std::string & v) { s_jack_version = v; } void set_qt_version (const std::string & v) { s_qt_version = v; } /** * Sets the current name of the application. */ void set_app_name (const std::string & aname) { s_app_name = aname; } /** * Sets the path to the application. Most useful on Windows. */ void set_app_path (const std::string & apath) { s_app_path = apath; } /** * Sets the current type of the application. */ void set_app_type (const std::string & atype) { s_app_type = atype; } void set_app_cli (bool iscli) { s_app_cli = iscli; } void set_app_engine (const std::string & aengine) { s_app_engine = aengine; } void set_app_build_os (const std::string & abuild_os) { s_app_build_os = abuild_os; } void set_app_build_issue (const std::string & abuild_issue) { s_app_build_issue = abuild_issue; } void set_arg_0 (const std::string & arg) { s_arg_0 = arg; } void set_client_name (const std::string & cname) { s_client_name = cname; /* current base name of the client port */ s_client_name_short = cname; /* without any "random" session portion */ auto pos = cname.find_first_of("./:"); /* common session delimiters */ if (pos != std::string::npos) s_client_name_short = cname.substr(0, pos); s_client_name_tag = "["; s_client_name_tag += s_client_name_short; s_client_name_tag += "]"; } void set_package_name (const std::string & pname) { s_package_name = pname; } /** * Returns the focus string. */ const std::string & seq_pane_focus () { return s_pane_focus; } /** * Returns the name of the application. We could continue to use the macro * SEQ66_APP_NAME, but we might eventually want to make this name * configurable. Done! */ const std::string & seq_app_name () { return s_app_name; } const std::string & seq_app_path () { return s_app_path; } const std::string & seq_app_type () { return s_app_type; } bool seq_app_cli () { return s_app_cli; } const std::string & seq_default_logfile_name () { static std::string s_logfile_base_name = seq_app_name(); static bool s_initialized = false; if (! s_initialized) { s_logfile_base_name += ".log"; s_initialized = true; } return s_logfile_base_name; } const std::string & seq_app_engine () { return s_app_engine; } const std::string & seq_app_build_os () { return s_app_build_os; } /** * We saw "Ubuntu" when built on our new Arch Linux laptop. How? The * configure.ac file uses * * AC_DEFINE_UNQUOTED(APP_BUILD_ISSUE, * "[m4_normalize(esyscmd([cat /etc/issue.net]))]", "Distro of build") * * And this gets locked into the configure script (somehow). * * So now we recommend that main() call set_app_build_issue() to the * correct value. */ const std::string & seq_app_build_issue () { return s_app_build_issue; } const std::string & seq_arg_0 () { return s_arg_0; } /** * Returns the name of the client for the application. It starts as the * macro SEQ66_CLIENT_NAME ("seq66"), but this name is now configurable. * When session management is active, the session-manager's client ID, or * something derived from it, is copied to this variable. */ const std::string & seq_client_name () { return s_client_name; } const std::string & seq_client_short () { return s_client_name_short; } const std::string & seq_config_name () { static std::string s_config_name = SEQ66_CONFIG_NAME; return s_config_name; } const std::string & seq_config_dir_name () { static std::string s_config_dir_name = SEQ66_CONFIG_DIR_NAME; return s_config_dir_name; } const std::string & seq_icon_name () { return s_icon_name; } /** * This function checks to see if stdin, stdout, or stderro are attached to a * console device, versus being redirected to a file. * * stdout is a FILE pointer giving the standard output stream. * Use stdout for functions such as fprintf(), fputs(), etc. * * STDOUT_FILENO is an integer file descriptor (actually, the integer 1). * One might use it for the write() system call. * * The relation between the two is STDOUT_FILENO == fileno(stdout) * * \param fd * Provides the file descriptor, one of STDIN_FILE = 0, STDOUT_FILENO = * 1, and STDERR_FILENO = 2. These values are defined in unistd.h * or in seq66_features.h (probably already defined in some Windoze * header. * * \return * Returns true if the file-descriptor is not redirected to a file. */ bool is_a_tty (int fd) { bool result = true; int rc; #if defined SEQ66_PLATFORM_WINDOWS int fileno; switch (fd) { case STDIN_FILENO: fileno = _fileno(stdin); break; case STDOUT_FILENO: fileno = _fileno(stdout); break; case STDERR_FILENO: fileno = _fileno(stderr); break; default: fileno = (-1); break; } rc = (fileno >= 0) ? _isatty(fileno) : 999 ; if (rc != 0) /* fd refers to a terminal */ rc = 1; /* to match Linux isatty() */ #else rc = isatty(fd); #endif if (rc == 0) { if (rc == EBADF) { printf ( "[%s] File descriptor %d is invalid\n", seq_client_name().c_str(), rc ); } else result = rc == 1; /* fd refers to a terminal */ } return result; } /** * Text color codes ('*' indicates the color is used below): * * - 30 = black * * - 31 = red * * - 32 = green * * - 33 = yellow * * - 34 = blue * * - 35 = magenta * * - 36 = cyan * - 37 = white */ std::string seq_client_tag (msglevel el) { if (el == msglevel::none) { return s_client_name_tag; } else { static const char * s_level_colors [] = { "\033[0m", /* goes back to normal console color */ "\033[1;32m", /* info message green */ "\033[1;33m", /* warning message is yellow */ "\033[1;31m", /* error message is red */ "\033[1;34m", /* status message is blue */ "\033[1;36m", /* session message is cyan */ "\033[1;30m" /* debug message is black */ }; std::string result = "["; int index = static_cast(el); bool iserror = el == msglevel::error || el == msglevel::warn || el == msglevel::debug; bool showcolor = is_a_tty(iserror ? STDERR_FILENO : STDOUT_FILENO); if (showcolor) result += s_level_colors[index]; result += s_client_name_short; if (showcolor) result += s_level_colors[0]; result += "]"; return result; } } /** * Returns the name of the package for the application. This is the name of * the product ("Seq66") no matter what executable has been generated. */ const std::string & seq_package_name () { return s_package_name; } std::string session_tag (const std::string & refinement) { std::string result = s_session_tag; if (! refinement.empty()) { result += " "; result += refinement; } return result; } /** * Returns the version information of the application. */ const std::string & seq_api_version () { return s_api_version; } const std::string & seq_api_subdirectory () { static bool s_uninitialized = true; static std::string s_subdirectory; if (s_uninitialized) { s_uninitialized = false; s_subdirectory = SEQ66_CONFIG_DIR_NAME; /* constant */ s_subdirectory += "-"; s_subdirectory += seq_api_version(); } return s_subdirectory; } const std::string & seq_version () { return s_version; } /** * Sets up the "hardwired" version text for Seq66. This value * ultimately comes from the configure.ac script (for now). It holds, * among other things, the hand-crafted date in that file. */ const std::string & seq_version_text () { return s_versiontext; } const std::string & seq_app_tag () { return s_apptag; } /** * This section of variables provide static information about the options * enabled or disabled during the build. */ #if defined SEQ66_PLATFORM_32_BIT const static std::string s_bitness = "32-bit"; #else const static std::string s_bitness = "64-bit"; #endif /** * Generates a string describing the features of the build. * * \return * Returns an ordered, human-readable string enumerating the built-in * features of this application. */ std::string seq_build_details () { std::ostringstream result; #if defined SEQ66_PLATFORM_DEBUG std::string buildmode = "Debug"; #else std::string buildmode = "Release"; #endif result << "Built " << __DATE__ << " " << __TIME__ "\n" << "C++ version " << std::to_string(__cplusplus) << "\n" #if defined __clang__ << "Clang C++ " << __clang_version__ << "\n" #else #if defined SEQ66_PLATFORM_GNU << "GNU C++ " << __GNUC__ << "." << __GNUC_MINOR__ << "." << __GNUC_PATCHLEVEL__ << "\n" #endif #endif << "Executable: " << seq_app_name() << " (" << seq_app_path() << ")\n" << "Interface: " << seq_app_type() << "\n" << "Engine: " << seq_app_engine() << "\n" ; result << "Package: " << seq_package_name() << "\n" << "Client: " << seq_client_name() << "\n" ; result << "Build OS: " << seq_app_build_os() << "\n" << "Build Type: " << s_bitness << " " << buildmode << "\n" ; #if defined SEQ66_PLATFORM_UNIX result << "Build Distro: " << seq_app_build_issue() << "\n"; #endif if (! s_qt_version.empty()) result << "GUI: Qt v. " << s_qt_version << "\n"; if (! s_alsa_version.empty()) result << "ALSA v. " << s_alsa_version << "\n"; #if defined SEQ66_PORTMIDI_SUPPORT result << "PortMIDI\n"; #endif #if defined SEQ66_JACK_SUPPORT result << "JACK v. " << s_jack_version << " Transport and MIDI\n" #if defined SEQ66_JACK_SESSION << "JACK Session\n" #endif ; #endif result #if defined SEQ66_NSM_SUPPORT << "NSM (Non/New Session Manager)\n" #endif #if defined SEQ66_SHOW_FEATURES_TMI << "\n" "Chord generator, LFO, trigger transpose, tap BPM, song recording " "pattern coloring, pause, save time-sig/tempo, " "event editor, follow-progress, play-lists, mute-groups.\n" #endif << "\n" "Some options can be enabled via ./configure," " seq66_features.h, or build-specific seq66-config.h files in" " include/qt/* for qmake portmidi and rtmidi builds. Also see" " INSTALL." << std::endl ; return result.str(); } } // namespace seq66 /* * seq66_features.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/sessions/clinsmanager.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file clinsmanager.cpp * * This module declares/defines the main module for the Non Session Manager * control of seq66cli and qseq66. * * \library clinsmanager application * \author Chris Ahlstrom * \date 2020-08-31 * \updates 2025-04-16 * \license GNU GPLv2 or above * * This object also works if there is no session manager in the build. It * handles non-session startup as well. */ #include "cfg/cmdlineopts.hpp" /* command-line functions */ #include "cfg/settings.hpp" /* seq66::usr() and seq66::rc() */ #include "os/daemonize.hpp" /* seq66::session_setup(), _close() */ #include "os/timing.hpp" /* seq66::millisleep() */ #include "sessions/clinsmanager.hpp" /* seq66::clinsmanager class */ #include "util/filefunctions.hpp" /* seq66::pathname_concatenate() */ #include "util/strfunctions.hpp" /* seq66::contains() */ #if defined SEQ66_NSM_SUPPORT #include "nsm/nsmmessagesex.hpp" /* seq66::nsm access functions */ #endif namespace seq66 { /** * This function attempts to get the ACTUAL operating system on which * a clinsmanager application is built. See the comment for * set/seq_app_bulld_issue() function(s) in the seq66_features module. * * We could also read the /etc/os-release file and parse that sucker. * Maybe later :-) */ static void get_and_set_build_issue () { #if defined SEQ66_PLATFORM_WINDOWS /* * Windows version helper functions are a mess! */ std::string buildtext = "Windows 64-bit"; /* no 32-bit support yet */ #else std::string buildtext = "Unknown"; /* fallback for failure */ std::string temp = file_read_string("/etc/issue"); if (temp.empty()) temp = file_read_string("/etc/issue.net"); if (! temp.empty()) { auto spos = temp.find_first_of("\\"); if (spos != std::string::npos) temp = temp.substr(0, spos - 1); buildtext = temp; } #endif set_app_build_issue(buildtext); } /** * Note that this object is created before there is any chance to get the * configuration, because the smanager base class is what gets the * configuration, well after this constructor. */ clinsmanager::clinsmanager (const std::string & caps) : smanager (caps), #if defined SEQ66_NSM_SUPPORT m_nsm_client (), #endif m_nsm_active (false), m_poll_period_ms (3 * usr().window_redraw_rate()) /* in qsmainwnd */ { get_and_set_build_issue(); } /** * Detects if the 'usr' file defines usage of NSM and a valid URL for the * nsmd daemon, or, if not, is there an NSM URL in the environment. Also, if * there is an NSM URL in the environment, it overrides the one specified in * the 'usr' file. * * Also sanity-checks the URL: "osc.udp://hostname.domain:PORT#" * * \param [out] url * Holds the URL that was found. Use it only if this function returns * true. * * \return * Returns true if a usable NSM URL was found and nsmd was found to be * running. */ bool clinsmanager::detect_session (std::string & url) { bool result = false; url.clear(); #if defined SEQ66_NSM_SUPPORT std::string tenturl = nsm::get_url(); /* a tentative URL */ session_message("Checking for NSM_URL"); if (! tenturl.empty()) { result = true; url = tenturl; } if (! result) /* not in NSM's env. */ { result = usr().want_nsm_session(); /* user wants NSM usage */ if (result) { tenturl = usr().session_url(); /* try 'usr' file's URL */ if (tenturl.empty()) /* configured NSM URL? */ { result = false; } else { result = contains(tenturl, "osc.udp://"); /* sanity check */ if (result) { session_message("NSM URL", tenturl); url = tenturl; /* configured NSM URL */ } else tenturl.clear(); } } } if (result) file_message("NSM URL", tenturl); /* comforting message */ #endif return result; } /** * This function first determines if the user wants an NSM session. If so, * it determines if it can get a valid NSM_URL environment variable. If not, * that may simply be due to nsmd running in different console window or as a * daemon. In that cause, it checks if there was a non-empty * "[user-session]" "url" value. This is useful in trouble-shooting, for * example. Finally, just in case the user has set up the "usr" file for * running NSM, but hasn't started non-session-manager or nsmd itself, we use * the new pid_exists() function to make sure that nsmd is indeed running. Ay! * * If all is well, a new nsmclient is created, and an announce/open handshake * starts. This function is called before create_window(). */ bool clinsmanager::create_session (int argc, char * argv []) { #if defined SEQ66_NSM_SUPPORT std::string url; bool ok = detect_session(url); /* side-effect */ if (! ok) { if (usr().want_nsm_session()) /* want to debug NSM? */ { nsm_active(true); /* class flag */ usr().in_nsm_session(true); /* global flag */ rc().config_subdirectory("config"); } return true; } if (ok) { std::string nsmfile = "dummy/file"; std::string nsmext = nsm::default_ext(); rc().config_subdirectory("config"); /* appended to NSM path */ m_nsm_client.reset(create_nsmclient(*this, url, nsmfile, nsmext)); bool result = bool(m_nsm_client); if (result) { /* * Use the same name as provided when opening the JACK client. */ std::string appname = seq_client_name(); /* "seq66", -l labl */ std::string exename = seq_arg_0(); /* "qseq66", etc. */ result = m_nsm_client->announce(appname, exename, capabilities()); if (result) { std::string note = "Announced "; note += appname; note += " "; note += exename; note += " "; note += capabilities(); status_message(note); } else file_error("Create session", "failed to announce"); } else file_error("Create session", "failed to make client"); if (url == "testing") result = true; nsm_active(result); /* class flag */ usr().in_nsm_session(result); /* global flag */ (void) smanager::create_session(argc, argv); return result; } else { return smanager::create_session(argc, argv); } #else return smanager::create_session(argc, argv); #endif } /** * Somewhat of the inverse of create_session(). */ bool clinsmanager::close_session (std::string & msg, bool ok) { #if defined SEQ66_NSM_SUPPORT if (usr().in_nsm_session()) { warnprint("Closing NSM session"); nsm_active(false); /* class flag */ usr().in_nsm_session(false); /* global flag */ if (m_nsm_client) /* double check */ m_nsm_client->close_session(); /* * Freezes in the lo_server_thread_stop() call: m_nsm_client.reset(); */ } #endif return smanager::close_session(msg, ok); } /** * Saves the active MIDI file, and then calls the base-class version of * save_session(). */ bool clinsmanager::save_session (std::string & msg, bool ok) { bool result = not_nullptr(perf()); if (ok) msg.clear(); if (result) { result = smanager::save_session(msg, ok); if (result) { /* * Only show the message if not running under a session * manager. This is because the message-box will hang the * application until the user clicks OK. */ if (! nsm_active()) show_message(session_tag(), msg); } else show_error(session_tag(), msg); } return result; } /** * This function is useful in the command-line version of the application. * For the Qt version, see the qt5nsmanager class, which runs the Qt exec() * function.. */ bool clinsmanager::run () { bool result = false; session_setup(); while (! session_close()) { result = true; if (session_save()) { std::string msg; result = save_session(msg, true); if (! result) { file_error(msg, "CLI"); } } millisleep(m_poll_period_ms); } return true; } /** * Creates a session path specified by the Non Session Manager. This * function is meant to be called after receiving the /nsm/client/open * message. * * A sample session path: * * /home/ahlstrom/NSM Sessions/QSeq66 Installed/seq66.nYMVC * * The NSM daemon creates the directory for this project after dropping the * client ID (seq66.nYMVC). We append the client ID and create this * directory, followng the lead of Non-Mixer and Qtractor. * * Note: At this point, the performer [perf()] does not yet exist, but * the 'rc', 'usr', 'ctrl', and 'mutes' files have been parsed. * * The first thing to do is grab the current (and default) configuration * directory (in the user's HOME area). We may need it, in order to find the * original files to recreate. Next we see if the configuration has already * been created, using the "rc" file as the test case. The normal base-name * (e.g. "qseq66") is always used in an NSM session. We will read/write the * configuration from the NSM path. We assume (for now) that the "midi" * directory was also created. * * \param argc * The command-line argument count. * * \param argv * The command-line argument list. * * \param path * The base configuration path. For NSM usage, this will be a directory * for the project in the NSM session directory created by nsmd. The * sub-directories "config" and "midi" will be created for use by NSM. * For normal usage, this directory will be the standard home directory * or the one specified by the --home option. */ bool clinsmanager::create_project ( int argc, char * argv [], const std::string & path ) { bool result = ! path.empty(); if (result) { std::string cfgpath; std::string midipath; /* * Appends "midi" and "config" (with NSM) */ result = make_path_names(path, cfgpath, midipath); if (result) result = create_configuration(argc, argv, path, cfgpath, midipath); } #if defined SEQ66_NSM_SUPPORT if (m_nsm_client) (void) m_nsm_client->open_reply(result); /* issue #28 */ #endif return result; } void clinsmanager::session_manager_name (const std::string & mgrname) { smanager::session_manager_name(mgrname); if (! mgrname.empty()) file_message(session_tag("manager"), mgrname); } void clinsmanager::session_manager_path (const std::string & pathname) { smanager::session_manager_path(pathname); if (! pathname.empty()) file_message(session_tag("path"), pathname); } void clinsmanager::session_display_name (const std::string & dispname) { smanager::session_display_name(dispname); if (! dispname.empty()) file_message(session_tag("name"), dispname); } void clinsmanager::session_client_id (const std::string & clid) { smanager::session_client_id(clid); if (! clid.empty()) file_message(session_tag("client ID"), clid); } /** * Shows the collected messages in the console, and recommends the user * exit and check the configuration. */ void clinsmanager::show_error ( const std::string & tag, const std::string & msg ) const { if (msg.empty()) { #if defined SEQ66_PORTMIDI_SUPPORT if (Pm_error_present()) { std::string pmerrmsg = std::string(Pm_error_message()); append_error_message(pmerrmsg); } #endif std::string msg = error_message(); msg += "Please exit and fix Seq66 configuration."; show_message(tag, msg); } else { append_error_message(msg); show_message(tag, msg); } } } // namespace seq66 /* * clinsmanager.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/sessions/smanager.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file smanager.cpp * * This module declares/defines a module for managing a generic seq66 * session. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-03-22 * \updates 2025-04-29 * \license GNU GPLv2 or above * * Note that this module is part of the libseq66 library, not the libsessions * library. That is because it provides functionality that is useful even if * session support is not enabled. * * The process: * * -# Call main_settings(argc, argv). It sets defaults, does some parsing * of command-line options and files. It saves the MIDI file-name, if * provided. * -# Call create_performer(). This does not launch the performer. Save * the unique-pointer. * -# Call open_playlist(). It will open it, if specified and possible. e -# Call open_note_mapper(). It will open it, if specified and possible. * -# If the MIDI file-name is set, open it via a call to open_midi_file(). * -# If a user-interface is needed, create a unique-pointer to it, then * show it. This will remove any previous pointer. The function is * virtual, create_window(). */ #include /* std::strlen() */ #include "seq66_features.hpp" /* set_app_name() */ #include "cfg/cmdlineopts.hpp" /* static command-line functions */ #include "cfg/midicontrolfile.hpp" /* seq66::midicontrolfile functions */ #include "cfg/notemapfile.hpp" /* seq66::notemapfile functions */ #include "cfg/patchesfile.hpp" /* seq66::patchesfile functions */ #include "cfg/playlistfile.hpp" /* seq66::playlistfile functions */ #include "cfg/rcfile.hpp" /* seq66::rcfile functions */ #include "cfg/sessionfile.hpp" /* seq66::sessionfile */ #include "cfg/settings.hpp" /* seq66::usr() and seq66::rc() */ #include "midi/midifile.hpp" /* seq66::write_midi_file() */ #include "play/performer.hpp" /* seq66::performer */ #include "play/playlist.hpp" /* seq66::playlist class */ #include "os/daemonize.hpp" /* seq66::reroute_stdio(), etc. */ #include "os/shellexecute.hpp" /* seq66::copy_directory_recursive()*/ #include "sessions/smanager.hpp" /* seq66::smanager() */ #include "util/filefunctions.hpp" /* seq66::file_readable() etc. */ #if defined SEQ66_PORTMIDI_SUPPORT #include "portmidi.h" /* Pm_error_present() */ #endif namespace seq66 { /** * Port error message. */ static std::string s_port_error_msg { "Check MIDI Clock & MIDI Input tabs for unavailable/missing ports. " "Make sure the MIDI tune is not using such ports. " "Click 'Remap and restart' or change the global output ports " "for the tune." }; static std::string s_port_update_msg { "There are more real ports than mapped ports. " "Click 'Remap and restart' to recreate the maps or edit " "them in the 'rc' file." }; /** * Does the usual construction. It also calls set_defaults() from the * settings.cpp module in order to guarantee that we have rc() and usr() * available. See that function for more information. * * create_performer() has to wait until after the calls to * main_settings(), create_session(), create_project(), */ smanager::smanager (const std::string & caps) : m_perf_pointer (), /* perf() accessor */ m_capabilities (caps), m_session_manager_name ("None"), m_session_manager_path ("None"), m_session_display_name ("None"), m_session_client_id ("None"), m_midi_filename (), m_is_help (false), m_last_dirty_status (false), m_rerouted (false), m_extant_errmsg (), m_extant_msg_active (false) { set_configuration_defaults(); } /** * We found that on a Debian developer laptop, this destructor took a couple * of seconds to call get_deleter(). Works fine on our Ubuntu developer * laptop. Weird. Actually might have been a side-effect of installing a * KxStudio PPA. */ smanager::~smanager () { if (! is_help()) session_message("Exiting session manager"); } /** * This function also checks to make sure the log file does not get too large * (about a megabyte). */ bool smanager::reroute_to_log (const std::string & filepath) const { static const size_t s_limit = 1048576; size_t sz = file_size(filepath); if (sz > s_limit) { (void) file_delete(filepath); session_message("Log file deleted", filepath); } session_message("Rerouting console messages", filepath); return reroute_stdio(filepath); } /** * A static function to be called in main() (that is, early in the life * of the application), so that this information can be changed later from * the command-line. Helps to regularize the handling of the GUI versus * CLI versions of the application. * * Note that ./configure sets the client name to seq66. But it is probably * better to distinguish qseq66 from seq66cli. */ void smanager::app_info (const std::string arg0, bool is_cli) { set_app_name(SEQ66_APP_NAME); /* set at ./configure time */ set_app_path(arg0); /* log for future usage */ set_arg_0(arg0); /* issue #131 */ if (is_cli) { /* * See the CLI main() routine instead. * * seq66::usr().app_is_headless(true); // conflated with cli */ set_app_cli(true); /* the default is false */ set_app_type(SEQ66_APP_TYPE); /* e.g. "qt5" vs "cli" */ set_client_name("seq66cli"); /* change from configure */ rc().set_config_files("seq66cli"); /* set 'rc', 'usr' names */ } } /** * The first thing is to set the various settings defaults, and then read * the 'usr' and 'rc' configuration files, in that order. The last thing * is to override any other settings via the command-line parameters. * * NSM: * * This function can detect the parent process ("nsmd") under Linux. * Under Windows, we don't really support NSM, and so the * seq66::get_parent_process_name() function in the daemonize.cpp module * returns a name of "None". * * \param argc * The number of command-line parameters, including the name of the * application as parameter 0. * * \param argv * The array of pointers to the command-line parameters. * * \return * Returns true if this code worked properly and was not a request for * help/version information. */ bool smanager::main_settings (int argc, char * argv []) { static const std::string s_nsm_name{"nsmd"}; bool result = true; /* false --> EXIT_FAILURE */ std::string parentname = get_parent_process_name(); bool in_nsm = contains(parentname, s_nsm_name); /* this is tentative! */ /* * Call app_info() above in main() instead of this. * * if (seq_app_cli()) * { * set_app_name("seq66cli"); * set_app_type("cli"); * set_client_name("seq66cli"); * rc().set_config_files("seq66cli"); * } * else * set_app_name(SEQ66_APP_NAME); * * set_arg_0(argv[0]); */ /** * Set up objects specific to the GUI for the performer. Parse command-line * options to see if they affect what gets read from the 'rc' or 'usr' * configuration files. They will be parsed again later so that they can * still override whatever other settings were made via the configuration * files. However, we currently have a issue where the mastermidibus * created by the performer object gets the default PPQN value, because the * "user" configuration file has not been read at that point. See the * performer::launch() function. */ if (in_nsm) { session_message("Parent process", parentname); } else { bool ishelp = cmdlineopts::help_check(argc, argv); if (ishelp) { /* * ca 2023-12-13 Why do this? */ (void) cmdlineopts::parse_command_line_options(argc, argv); is_help(true); result = false; } else { int rcode = cmdlineopts::parse_command_line_options(argc, argv); result = rcode != (-1); if (result) { #if ! defined SEQ66_PLATFORM_WINDOWS if (usr().want_nsm_session()) { in_nsm = true; session_manager_name("Simulated NSM"); session_manager_path(rc().home_config_directory()); } #endif } else is_help(true); /* a hack to avoid create_window() */ } if (result) { int optionindex = (-1); bool sessionmodified = false; if (rc().alt_session()) /* issue #131 */ { /* * Check for a session, either defined by the environment * variable "SEQ66_SESSION_TAG" or by the "--session-tag tag" * option. The latter can override the first. * * The name 'sessions.rc' is a bit more accurate. */ std::string sessionfilename = rc().make_config_filespec("sessions.rc"); if (file_readable(sessionfilename)) { std::string sesstag = rc().session_tag(); sessionfile sf(sessionfilename, sesstag, rc()); sessionmodified = sf.parse(); if (! sessionmodified) { /* * The session tag is not found. It seems best * to just bug out. The GUI will still appear in * order to show the error message. */ std::string msg = "Session tag ["; msg += rc().session_tag(); msg += "] in "; msg += sessionfilename; msg += " not found.\nExit and try again."; append_error_message(msg); return false; } } } else { std::string sesstag = cmdlineopts::env_session_tag(); if (! sesstag.empty()) rc().session_tag(sesstag); } /* * If "-o log=file.ext" occurred, handle it early on in startup. */ if (! sessionmodified) (void) cmdlineopts::parse_log_option(argc, argv); /* * If parsing fails, report it and disable usage of the * application and saving bad garbage out when exiting. Still * must launch, otherwise a segfault occurs via dependencies in * the qsmainwnd. */ if (! in_nsm) { std::string errmessage; /* just in case! */ result = cmdlineopts::parse_options_files(errmessage); if (result) { if (argc > 1) { optionindex = cmdlineopts::parse_command_line_options ( argc, argv ); result = optionindex >= 0; } } else { errprint(errmessage); append_error_message(errmessage); /* raises the message */ } } if (result) { /* * The 'usr' file might not specify a log-file. Check again * here. But do we really want to not do this for a verbose * CLI run? * * bool uselog = ! (seq_app_cli() && rc().verbose()) && * usr().option_use_logfile(); */ (void) cmdlineopts::parse_o_options(argc, argv); bool uselog = usr().option_use_logfile(); /* * The user migh specify -o options that are also set up in * the 'usr' file; the command line must take precedence. The * "log" option is processed early in the startup sequence. * These same settings are made in the cmdlineopts module. */ if (uselog) { std::string logfile = usr().option_logfile(); if (logfile.empty()) logfile = "/dev/null"; /* Windows Mingw ok? */ m_rerouted = reroute_to_log(logfile); } m_midi_filename.clear(); if (optionindex > 0 && optionindex < argc) /* MIDI filename? */ { std::string fname = argv[optionindex]; std::string errmsg; if (file_readable(fname)) { std::string path; /* not used here */ std::string basename; m_midi_filename = fname; if (filename_split(fname, path, basename)) { rc().midi_filename(basename); rc().playlist_active(false); } } else { char temp[512]; (void) snprintf ( temp, sizeof temp, "MIDI file not readable: '%s'", fname.c_str() ); append_error_message(temp); /* raises the message */ m_midi_filename.clear(); } } } } } return result; } /** * This function is currently meant to be called by the owner of this * smanager. This call must occur before creating the application main * window. * * Otherwise, seq66 will not register with NSM (if enabled) in a timely * fashion. Also, we always have to launch, even if an error occurred, to * avoid a segfault and show at least a minimal message. * * NSM: * * The NSM API requires that applications MUST NOT register their JACK client * until receiving an NSM "open" message. So, in the main() function of the * application, we make the smanager calls in this order: * * -# main_settings(). Gets the normal Seq66 configuration items from * the "rc", "usr", "mutes", "ctrl", and "playlist" files. * -# create_session(). Sets up the session and does the NSM "announce" * handshake protocol. We ignore the return code so that Seq66 can * run even if NSM is not available. This call also receives the * "open" response, which provides the NSM path, display name, and * the client ID. * -# create_project(). This function firsts makes sure that the client * directory [$HOME/NSM Sessions/Session/seq66.nXYZZ] exists. * It then gets the session configuration. Do we want to * reload a complete set of configuration files from this directory, * or just a session-specific subset from an "nsm" file ??? * Most of this work is in the non-GUI-specific clinsmanager * derived class. * -# create_performer(). This sets up the ports and launches the * threads. * -# open_playist(). If applicable. Probably better as part of the * session. * -# open_midi_file(). If applicable. Probably better as part of the * session. * -# create_window(). This creates the main window, and the Sessions * tab can be hidden if not needed. Menu entries can also be * adjusted for session support; see qsmainwnd. * -# run(). The program runs until the user or NSM kills it. This is * called from the main() function of the application. * -# close_session(). Should tell NSM that it is bowing out of the * session. Done normally in main(), but here if a serious error * occurs. * * \return * Returns false if the performer wasn't able to be created and launched. * Other failures, such as not getting good settings, might be ignored. */ bool smanager::create_performer () { bool result = false; int ppqn = choose_ppqn(); int rows = usr().mainwnd_rows(); int cols = usr().mainwnd_cols(); pointer p(new (std::nothrow) performer(ppqn, rows, cols)); result = bool(p); if (result) { m_perf_pointer = std::move(p); /* change the ownership */ (void) perf()->get_settings(rc(), usr()); result = perf()->launch(ppqn); // std::string perfmsgs; if (result) { // Anything to do? } else { errprint("performer launch failed"); } } else { errprint("performer creation failed"); } return result; } /** * Code moved from rcfile to here while researching issue #89. * * \return * Returns true if the 'ctrl' file was able to be opened. * If not active, we don't bother to report the error. */ bool smanager::open_midi_control_file () { std::string fullpath = rc().midi_control_filespec(); bool result = ! fullpath.empty(); if (result) { result = read_midi_control_file(fullpath, rc()); if (rc().midi_control_active() && ! result) append_error_message("Read failed", fullpath); } return result; } /** * Opens a playlist, if specified. It is opened and read if there is an * empty or non-empty play-list filename, even if specified to be inactive. * An empty filename results in a basically empty, but ultimately populable, * playlist. Saves a lot of pointer checks. * * \return * Returns true if a playlist was specified and successfully opened, or * if there is no playlist called for. In other words, true is returned * if there is no error. */ bool smanager::open_playlist () { bool result = not_nullptr(perf()); if (result) { std::string playlistname = rc().playlist_filespec(); result = perf()->open_playlist(playlistname); if (result) { result = perf()->open_current_song(); } else { if (rc().playlist_active()) { std::string msg = "Playlist open failed: '"; msg += playlistname; msg += "'"; append_error_message(msg); } result = true; /* avoid early exit */ } } else { append_error_message("Open playlist: no performer"); } return result; } bool smanager::open_note_mapper () { bool result = not_nullptr(perf()); if (result) { std::string notemapname = rc().notemap_filespec(); if (! notemapname.empty()) { result = perf()->open_note_mapper(notemapname); if (! result) result = true; /* avoid early exit */ } } else { append_error_message("Open note-mapper: no performer"); } return result; } /** * We don't need the performer for this action, because the patches * list affects only the user interface, not playback/recording. */ bool smanager::open_patch_file () { bool result = false; std::string patchesname = rc().patches_filespec(); if (rc().patches_active() && ! patchesname.empty()) { result = open_patches(patchesname); /* * Hmmmmmmmmmmmm */ if (! result) result = true; /* avoid early exit */ } return result; } /** * Encapsulates opening the MIDI file, if specified (on the command-line). * * \return * Returns the name of the MIDI file, if successful. Otherwise, an empty * string is returned. */ std::string smanager::open_midi_file (const std::string & fname) { std::string result = fname; bool ok = file_readable(result); midi_filename(""); /* side-effect */ if (ok) { std::string errmsg; ok = perf()->read_midi_file(result, errmsg); if (ok) { std::string infomsg = "PPQN set to "; infomsg += std::to_string(perf()->ppqn()); info_message(infomsg); (void) perf()->apply_session_mutes(); /* * Redundant: file_message("Open", result); */ midi_filename(result); /* side-effect */ rc().playlist_active(false); /* disable it */ } else append_error_message(errmsg); } else append_error_message("MIDI unreadable", result); return result; } /** * The clinsmanager::create_session() function supercedes this one, but calls * it. */ bool smanager::create_session (int /*argc*/, char * /*argv*/ []) { session_setup(); /* daemonize: set basic signal handlers */ return true; } /** * Closes the session, with an option to handle errors in the session. * * Note that we do not save if in a session, as we rely on the session * manager to tell this application to save before forcing this application * to end. * * \param [out] msg * Provides a place to store any error message for the caller to use. * * \param ok * Indicates if an error occurred, or not. The default is true, which * indicates "no problem". * * \return * Returns the ok parameter if false, otherwise, the result of finishing * up is returned. */ bool smanager::close_session (std::string & msg, bool ok) { bool result = not_nullptr(perf()); if (result) { result = perf()->finish(); /* tear down performer */ perf()->put_settings(rc(), usr()); /* copy latest settings */ if (result) (void) save_session(msg, result); } result = ok; (void) session_close(); /* daemonize signals exit */ return result; } /** * This function saves the following files (so far): * * - *.rc * - *.ctrl (via the 'rc' file) * - *.mutes (via the 'rc' file) * - *.usr * - *.drums * * The clinsmanager::save_session() function saves the MIDI file to the * session, if applicable. That function also clears the message parameter * before the saving starts. * * \param [out] msg * Provides a place to store any error message for the caller to use. * * \param ok * Indicates if an error occurred, or not. The default is true, which * indicates "no problem". */ bool smanager::save_session (std::string & msg, bool ok) { bool result = not_nullptr(perf()); if (result) { if (ok) /* code from clinsmanager here */ { if (perf()->modified()) /* i.e. MIDI file is modified */ { std::string filename = rc().midi_filename(); if (filename.empty()) { /* Don't need this: msg = "MIDI file-name empty"; */ } else { bool is_wrk = file_extension_match(filename, ".wrk"); if (is_wrk) filename = file_extension_set(filename, ".midi"); result = write_midi_file(*perf(), filename, msg); if (result) msg = result ? "Saved: " : "Not able to save: " ; msg += filename; } } } if (result && ok) { bool save = rc().auto_options_save(); if (save) { session_message("Save", "Options"); if (! cmdlineopts::write_options_files()) msg = "Config writes failed"; } /* * Not able to save at exit: 'palette', 'patches', 'qss'. * The first two can be saved in Edit / Preferences / * Session, which dumps the read-in values or the internal * values. */ if (rc().auto_ctrl_save()) { std::string mcfname = rc().midi_control_filespec(); session_message("Save", "Controls"); result = write_midi_control_file(mcfname, rc()); } if (rc().auto_mutes_save()) { session_message("Save", "Mutes"); result = perf()->save_mutegroups(); // add msg return? } if (rc().auto_playlist_save()) { session_message("Save", "Playlist"); result = perf()->save_playlist(); // add msg return? } if (rc().auto_drums_save()) { session_message("Save", "Note-mapper"); result = perf()->save_note_mapper(); // add msg return? } } else { result = false; if (! is_help()) { (void) cmdlineopts::write_options_files("erroneous"); if (error_active()) { errprint(error_message()); msg = error_message(); } } } } else { msg = "no performer!"; } return result; } /** * This function is overridden in qt5nsmanager to actually create the * user-interface. */ bool smanager::create_window () { return true; } /** * Sets the error flag and appends to the error message, which are both mutable * so that we can safely call this function under any circumstances. * * \param message * Provides the message to be set. If empty, the message-active flag and * the message are both cleared. * * \param data * Optional additional data for the message. */ void smanager::append_error_message ( const std::string & msg, const std::string & data ) const { if (msg.empty()) { m_extant_errmsg.clear(); m_extant_msg_active = false; } else { std::string fullmessage = msg; if (! data.empty()) { fullmessage += ": '"; fullmessage += data; fullmessage += "'"; } m_extant_msg_active = true; if (! m_extant_errmsg.empty()) m_extant_errmsg += "\n"; m_extant_errmsg += fullmessage; } } /* * Having this here after creating the main window may cause issue * #100, where ladish doesn't see seq66's ports in time. * * perf()->launch(usr().midi_ppqn()); * * We also check for any "fatal" PortMidi errors, so we can display * them. But we still want to keep going, in order to at least * generate the log-files and configuration files to * C:/Users/me/AppData/Local/seq66 or ~/.config/seq66. */ void smanager::show_message (const std::string & tag, const std::string & msg) const { std::string fullmsg = tag + ": " + msg; seq66::info_message(fullmsg); /* checks for "debug" and adds "[]" */ } void smanager::show_error (const std::string & tag, const std::string & msg) const { std::string fullmsg = tag + ": " + msg; seq66::error_message(msg); } /** * Checks for an internal (e.g. PortMidi) error, storing the message if * applicable. * * ca 2023-09-24 * For portmidi, the internal_error_check() might not find an error, * while a port-map error does exist. So we add a check of the latter here. * * \param [out] errmsg * Provides a destination for the PortMidi error. It is cleared if * there is no error. * * \return * Returns true if there is an error. In this case, the caller should * show the error message. */ bool smanager::internal_error_check (std::string & errmsg) const { std::string pmerrmsg; errmsg.clear(); #if defined SEQ66_PORTMIDI_SUPPORT /* * We should eventually get this code into the midibus arena for PortMidi * and for RtMidi support. */ bool result = bool(Pm_error_present()); if (result) { const char * perr = Pm_error_message(); if (not_nullptr(perr) && std::strlen(perr) > 0) pmerrmsg = std::string(perr); } else { result = perf()->port_map_error(); /* ca 2023-09-24 */ } #else bool result = internal_error_pending(); #endif if (result) { if (pmerrmsg.empty()) pmerrmsg = perf()->error_messages(); else pmerrmsg += s_port_error_msg; append_error_message(pmerrmsg); errmsg = pmerrmsg; } return result; } /** * Shouldn't we be using the 'usr' log-file name??? */ void smanager::error_handling () { std::string errmsg; bool internal_error = internal_error_check(errmsg); bool session_error = error_active(); std::string path = seq66::rc().config_filespec(seq_default_logfile_name()); #if defined SEQ66_PORTMIDI_SUPPORT const char * pmerrmsg = pm_log_buffer(); /* guaranteed to be valid */ errmsg += "\n"; errmsg += std::string(pmerrmsg); #endif if (internal_error) { show_error("Error.", errmsg); } else if (session_error) { errmsg += error_message(); show_error("Session error.", errmsg); } (void) seq66::file_append_log(path, errmsg); } /** * Refactored so that the basic NSM session can be set up before launch(), as * per NSM rules. * * The following call detects a session, creates an nsmclient, sends an NSM * announce message, waits for the response, uses it to set the session * information. What we really see: * * nsmclient::announce() Send announcement, wait for response * Gets manager path!!! * nsmclient::open() Sets manager path * * We run() the window, get the exit status, and close the session in the * main() function of the application. * * Call sequence summary: * * - main_settings() * - create_session() * - create_project() * - create_performer() * - open_playlist() * - open_note_mapper() * - open_midi_file() if specified on command-line; otherwise * - Open most-recent file if that option is enabled: * Get full path to the most recently-opened or imported file. What * if smanager::open_midi_file() has already been called via the * command-line? Then skip this step. * - create_window() * - run(), done in main() * - close_session(), done in main() */ bool smanager::create (int argc, char * argv []) { bool result = main_settings(argc, argv); if (result) { bool ok = create_session(argc, argv); /* path, client ID, etc */ if (ok) { /* * Need to test this change under NSM and Windows! * * rc().session_directory(); */ std::string homedir = manager_path(); /* session manager path */ if (homedir == "None") homedir = rc().home_config_directory(); session_message("Session manager path", homedir); (void) create_project(argc, argv, homedir); } if (ok) (void) open_midi_control_file(); /* * We don't want to return a false result, otherwise seq66 will * exit mysteriously, without showing any message. So we use an * internal flag. Found while investigating issue #110. Done for * everything to line 875. We also have to call open_playlist() * to avoid a segfault later. */ ok = create_performer(); if (ok) { std::string fname = midi_filename(); if (fname.empty()) { if (rc().load_most_recent()) { std::string midifname = rc().recent_file(0, false); if (! midifname.empty()) { /* * We don't want to necessarily save the 'rc' file * at exit just because we're loading the most * recent file. */ bool autorcsave = rc().auto_rc_save(); (void) open_midi_file(midifname); rc().auto_rc_save(autorcsave); } } } else (void) open_midi_file(fname); } ok = open_playlist(); if (ok) ok = open_note_mapper(); if (ok) ok = open_patch_file(); #if defined USE_CRIPPLED_RUN /* * This code is currently disabled because too many null pointers * end up being found, causing crashes. For now we rely on * console error messages. */ else /* ca 2023-04-15 */ { std::string msg; (void) create_window(); error_handling(); (void) create_session(); (void) run(); (void) close_session(msg, false); } #endif if (result) { if (perf()->error_pending()) { std::string errmsgs = perf()->error_messages(); if (errmsgs.empty()) errmsgs = "Empty error message!"; append_error_message(errmsgs); } result = create_window(); if (result) { if (perf()->new_ports_available()) show_message("Session note.", s_port_update_msg); else error_handling(); } else { std::string msg; /* maybe errmsg? */ result = close_session(msg, false); session_message("Window creation error", msg); } } if (! is_help()) cmdlineopts::show_locale(); } else { if (! is_help()) { std::string msg; (void) create_performer(); (void) create_window(); error_handling(); (void) create_session(); (void) run(); (void) close_session(msg, false); } } return result; } bool smanager::create_configuration ( int argc, char * argv [], const std::string & mainpath, const std::string & cfgfilepath, const std::string & midifilepath ) { bool result = ! cfgfilepath.empty(); if (result) { std::string rcbase = rc().config_filename(); rc().midi_filepath(midifilepath); /* do this first */ rc().full_config_directory(cfgfilepath); /* set session dir. */ std::string rcpath = rc().home_config_directory(); std::string rcfile = filename_concatenate(rcpath, rcbase); bool already_created = file_exists(rcfile); if (already_created) { session_message("File exists", rcfile); /* comforting */ result = read_configuration(argc, argv, cfgfilepath, midifilepath); if (result) { if (usr().in_nsm_session()) { rc().auto_rc_save(true); } else { /* * In some cases we might require rc to be rewritten. */ bool r = rc().auto_rc_save(); /* 'rc' save? */ bool u = rc().auto_usr_save(); /* --user-save? */ rc().set_save_list(false); /* clear them all */ rc().auto_rc_save(r); /* restore it */ rc().auto_usr_save(u); /* restore it */ } } } else { if (! m_rerouted) { usr().option_logfile(seq_default_logfile_name()); m_rerouted = reroute_to_log(usr().option_logfile()); } result = make_directory_path(mainpath); if (result) { session_message("Main path", mainpath); result = make_directory_path(cfgfilepath); } if (result && ! midifilepath.empty()) { result = make_directory_path(midifilepath); if (result) session_message("MIDI path", midifilepath); } /* * When creating the configuration files, we do not want to * automatically set auto_rc_save(), so we unset it here and * use the new flag. The first_run_in_progress() flag is * a one-time flag during the whole lifecycle of Seq66 * runs. */ rc().set_save_list(true); /* save all configs */ rc().auto_rc_save(false); /* except this one */ rc().first_run_in_progress(true); /* save at exit */ if (usr().in_nsm_session()) { usr().session_visibility(false); /* new session=hide */ rc().load_most_recent(true); /* issue #41 */ rc().jack_auto_connect(false); /* issue #48 */ } /* * The options files and other files are written at exit. * We might provide a menu command to write them at any time. */ #if defined WRITE_OPTIONS_FILES_AT_STARTUP if (result) { file_message("Saving session configuration", cfgfilepath); result = cmdlineopts::write_options_files(); if (result) result = create_playlist(cfgfilepath, midifilepath); if (result) result = create_notemap(cfgfilepath); } #endif } } return result; } bool smanager::create_playlist ( const std::string & cfgfilepath, const std::string & midifilepath ) { bool result = true; std::string srcplayfile = rc().playlist_filename(); if (srcplayfile.empty()) srcplayfile = "empty.playlist"; /* * The following calls splits the srcplayfile into a path and basename, * then appends the basename to the cfgfilepath. */ std::string dstplayfile = file_path_set(srcplayfile, cfgfilepath); if (! rc().playlist_active()) { warnprint("Playlist inactive, saving anyway"); } if (dstplayfile.empty()) { file_error("Playlist file", "none"); } else { std::string s("Temp"); performer * p(nullptr); std::shared_ptr plp; plp.reset(new (std::nothrow) playlist(p, s, false)); result = bool(plp); if (result) { std::string homepath = rc().home_config_directory(); srcplayfile = file_path_set(srcplayfile, homepath); (void) save_playlist(*plp, srcplayfile, dstplayfile); if (! midifilepath.empty()) { (void) copy_playlist_songs ( *plp, srcplayfile, midifilepath ); } /* * The first is where MIDI files are stored in the session, and * the second is where they are stored for the play-list. * Currently, the same directory. */ rc().midi_filepath(midifilepath); rc().midi_base_directory(midifilepath); } } return result; } bool smanager::create_notemap (const std::string & cfgfilepath) { bool result = true; std::string srcnotefile = rc().notemap_filename(); if (srcnotefile.empty()) srcnotefile = "empty.drums"; std::string dstnotefile = file_path_set(srcnotefile, cfgfilepath); if (! rc().notemap_active()) { warnprint("Note-map not active, saving anyway"); } std::string destination = rc().notemap_filename(); if (destination.empty()) { warnprint("Note-map file name empty"); } else { std::string homepath = rc().home_config_directory(); std::shared_ptr nmp; nmp.reset(new (std::nothrow) notemapper()); result = bool(nmp); file_message("Note-mapper save", destination); srcnotefile = file_path_set(srcnotefile, homepath); (void) copy_notemapper(*nmp, srcnotefile, destination); } return result; } bool smanager::read_configuration ( int argc, char * argv [], const std::string & cfgfilepath, const std::string & midifilepath ) { rc().full_config_directory(cfgfilepath); /* set NSM dir */ rc().midi_filepath(midifilepath); /* set MIDI dir */ if (! midifilepath.empty()) { file_message("MIDI path", rc().midi_filepath()); file_message("MIDI file", rc().midi_filename()); } std::string errmessage; bool result = cmdlineopts::parse_options_files(errmessage); if (result) { /* * Perhaps at some point, the "rc"/"usr" options might affect NSM * usage. In the meantime, we still need command-line options, if * present, to override the file-specified options. One big example * is the --buss override. The smanager::main_settings() function is * called way before create_project(); */ if (argc > 1) { int rcode = cmdlineopts::parse_command_line_options(argc, argv); result = rcode != (-1); if (result) (void) cmdlineopts::parse_o_options(argc, argv); else is_help(true); /* a hack to avoid create_window() */ } } else { file_error(errmessage, rc().config_filespec()); } return result; } bool smanager::make_path_names ( const std::string & path, std::string & outcfgpath, std::string & outmidipath, const std::string & midisubdir ) { bool result = ! path.empty(); if (result) { std::string cfgpath = normalize_path(path); std::string midipath = cfgpath; if (! midisubdir.empty()) midipath = pathname_concatenate(cfgpath, midisubdir); outcfgpath = cfgpath; outmidipath = midipath; } return result; } /** * Imports configuration files from another directory into the current * "home" directory. Implements File / Import / Project Configuration. * * Function for the main window to call. It deletes the existing * configuration, copies the source configuration, then calls * reset_configuration_items(). * * When this function succeeds, we need to signal a session-reload and the * make settings as done in qsmainwnd::import_project(). * * We don't really need to care if NSM is active here, do we? * * \param sourcepath * Provides the name of the source directory. * * \param sourcebase * Provides the basename (e.g. "qseq66.rc") of the source * configuration file. * * \return * Returns true if the import succeeds. One source of failure * is specifying the current "home" directory as the source * path. The configuration to import must come from a different * subdirectory. */ bool smanager::import_into_session ( const std::string & sourcepath, const std::string & sourcebase /* e.g. qseq66.rc */ ) { bool result = ! sourcepath.empty() && ! sourcebase.empty(); if (result) { std::string destdir = rc().home_config_directory(); std::string destbase = rc().config_filename(); result = destdir != sourcepath; if (result) { std::string cfgpath; std::string midipath; session_message("Source", sourcepath + sourcebase); session_message("Destination", destdir); result = make_path_names(destdir, cfgpath, midipath); if (result) { /* * Deletes configuration files listed in rc(). * This does not cover MIDI files, such as those in a * "midi" directory or in a playlist. */ result = delete_configuration(cfgpath, destbase); if (result) { #if defined SEQ66_COPY_PROJECT_BY_RC result = copy_configuration(sourcepath, sourcebase, cfgpath); #else result = copy_directory_recursive(sourcepath, cfgpath); #endif if (result) { result = reset_configuration_items ( destdir, sourcebase, cfgpath, midipath ); } } } } } return result; } /** * Copies the relevant files in the "home" configuration to another directory. * * How do we want this to work? * * - Append (and create if necessary) a subdirectory? * - "config" for NSM usage. * - No additional directory name otherwise. * - The destbase name (stripped of extension) * - Force the user to have already created the full desired path? * No, we can make the directory as needed. * * Idea: * * - First save these: * - rc().set_config_directory() * - rc().set_config_files() * - Then change them. * - Make sure the destination path exists, creating it if necessary. * - Call some of the code present in save_session(). * - Restore the saved values. * * \param destpath * Provides the full destination path. * * \param destbase * Provides the basename to use (e.g. "qseq66"). */ bool smanager::export_session_configuration ( const std::string & destpath, const std::string & destbase ) { bool result = not_nullptr(perf()); if (result) { std::string sourcedir = rc().home_config_directory(); std::string sourcebase = rc().config_filename(); result = ! destpath.empty() && ! destbase.empty(); if (result) { result = sourcedir != destpath; if (result) file_message("Export destination", destpath); else file_error("Export destination = source", destpath); } if (result) { std::string srcpalette = rc().palette_filespec(); std::string srcqss = rc().style_sheet_filespec(); rc().home_config_directory(destpath); rc().config_filename(destbase); result = make_directory_path(destpath); if (result) result = cmdlineopts::alt_write_rc_file(destbase); if (result) { result = cmdlineopts::alt_write_usr_file(destbase); if (result) { std::string mcfname = rc().midi_control_filespec(); result = write_midi_control_file(mcfname, rc()); if (result) result = perf()->save_mutegroups(); if (result) result = perf()->save_playlist(); if (result) result = perf()->save_note_mapper(); if (result) { std::string destpalette = rc().palette_filespec(); std::string destqss = rc().style_sheet_filespec(); file_message("Copy palette", destpalette); result = file_copy(srcpalette, destpalette); if (result) { file_message("Copy qss", destqss); result = file_copy(srcqss, destqss); } #if defined SEQ66_HANDLE_FILE_WILDCARDS if (result) { std::string wildcard = filename_concatenate ( sourcedir, "*.png" ); tokenization filelist; bool ok = get_wildcards(wildcard, filelist); if (ok) { file_message("Copy PNG images", "*.png"); result = file_list_copy(destpath, filelist); if (! result) file_error("PNG export failed", destpath); } } #endif } } if (! result) file_error("usr export failed", destpath); } else file_error("rc export failed", destpath); rc().home_config_directory(sourcedir); rc().config_filename(sourcebase); } } else { file_error("no performer!", "TODO"); } return result; } /** * This function is like create_configuration(), but the source is not the * standard configuration-file directory, but a directory chosen by the user. * This function should be called only for importing a configuration into an * NSM session directory. Called by import_into_session() above. * * \param sourcepath * The source path is the path to the configuration files chosen for * import by the user. Often it is the usual ~/.config/seq66 directory. * * \param sourcebase * This is the actual configuration file chosen by the user. It can be * any file qseq66.*, only the first part of the name (e.g. "qseq66") is * used. * * \param cfgfilepath * This is the configuration directory used in this NSM session. It is * often something like "~/NSM Sessions/MySession/seq66.nLTQC/config. * This is the destination for the imported configuration files. * * \param midifilepath * This is the MIDI directory used in this NSM session. It is often * something like "~/NSM Sessions/MySession/seq66.nLTQC/midi. This is * the destination for the imported MIDI files (due to being part of an * imported play-list. * * \return * Returns true if the import succeeded. */ bool smanager::reset_configuration_items ( const std::string & sourcepath, const std::string & sourcebase, const std::string & cfgfilepath, const std::string & midifilepath ) { bool result = ! sourcepath.empty() && ! sourcebase.empty(); if (result) { std::string rcbase = file_extension_set(sourcebase, ".rc"); std::string rcfile = filename_concatenate(sourcepath, rcbase); result = file_exists(rcfile); /* a valid source */ if (result) { std::string usrbase = file_extension_set(sourcebase, ".usr"); std::string usrfile = filename_concatenate(sourcepath, usrbase); file_message("File exists", rcfile); /* comforting */ rc().config_filename(rcfile); rc().user_filename(usrfile); std::string errmessage; result = cmdlineopts::parse_rc_file(rcfile, errmessage); if (result) result = cmdlineopts::parse_usr_file(usrfile, errmessage); /* * Don't change this. */ if (result) { if (usr().in_nsm_session()) rc().load_most_recent(true); /* issue #41 */ } } if (result) { std::string srcplayfile = rc().playlist_filename(); std::string srcnotefile = rc().notemap_filename(); if (srcplayfile.empty()) srcplayfile = "empty.playlist"; if (srcnotefile.empty()) srcnotefile = "empty.drums"; file_message("Saving imported configuration", cfgfilepath); rc().auto_usr_save(true); result = cmdlineopts::write_options_files(); if (result) result = create_playlist(cfgfilepath, midifilepath); if (result) result = create_notemap(cfgfilepath); } } return result; } } // namespace seq66 /* * smanager.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/util/automutex.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file automutex.cpp * * This module declares/defines the base class for mutexes. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2019-03-16 * \license GNU GPLv2 or above * * Seq66 needs a recursive mutex and a condition-variable for sequencer * operations. This module defines the recursive mutex needed. */ #include "util/automutex.hpp" namespace seq66 { /* * Currently, the header defines all the code needed for this class. */ } // namespace seq66 /* * automutex.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/util/basic_macros.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file basic_macros.cpp * * This module defines some informative functions that are actually * better off as functions. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-10 * \updates 2023-04-03 * \license GNU GPLv2 or above * * One of the big new feature of some of these functions is writing the name of * the application in color before each message that is put out. */ #include /* defines the assert() macro */ #include /* std::strlen(3) */ #include /* see "man stdarg(3)" */ #include #include "util/basic_macros.hpp" /* basic macros-cum-functions */ #if defined SEQ66_PLATFORM_UNIX #include /* C::write(2) */ #define S_WRITE write /* POSIX write(2) */ #endif #if defined SEQ66_PLATFORM_WINDOWS /* Microsoft platform */ #include /* C::_write() */ #if defined SEQ66_PLATFORM_MSVC /* Microsoft compiler vs MingW */ #endif #define S_WRITE _write /* Microsoft's write() */ #endif namespace seq66 { /** * Functions to remove dependencies on the "cfg" modules. Could eventually * replace rcsettings::verbose() and investigate(). */ static bool s_is_verbose = false; static bool s_is_investigate = false; void set_verbose (bool flag) { s_is_verbose = flag; } bool verbose () { return s_is_verbose; } void set_investigate (bool flag) { s_is_investigate = flag; } bool investigate () { return s_is_investigate; } /** * Provides a way to still get the benefits of assert() output in release * mode, without aborting the application. * * \todo * This can slow down client code slightly, and it would be good to * reduce the impact. * * \param ptr * Provides the pointer to be tested. * * \param context * Provides context for the message. Usually the __func__ macro is * the best option for this parameter. * * \return * Returns true in release mode, if the pointer was not null. In * debug mode, will always return true, but the assert() will abort * the application anyway. */ #if defined SEQ66_PLATFORM_DEBUG bool not_nullptr_assert (void * ptr, const std::string & context) { bool result = true; int flag = int(not_nullptr(ptr)); if (! flag) { std::cerr << seq_client_tag(msglevel::error) << " null pointer in context " << context << std::endl; result = false; } #if defined SEQ66_PLATFORM_GNU_TMI /* does not work in Mingw */ int errornumber = flag ? 0 : 1 ; assert_perror(errornumber); #else assert(flag); #endif return result; } #endif // SEQ66_PLATFORM_DEBUG /** * Provided for convenience and for avoid those annoying warnings about * "declared with attribute warn_unused_result [-Wunused-result]". */ static void write_msg (int fd, const char * msg, size_t count) { if (S_WRITE(fd, msg, count) == (-1)) { /* * Generally should fail only if interrupted by a signal-handler * before any bytes are written. See the man-page for write(2). */ } } /** * Meant for use in signal handlers. For the colors, hardwired here, see * s_level_colors in the seq66_features.cpp modules. The "seq66" tag is * black (no error) or red (error). The text is black. The character count * is programmer supplied (see the comments). */ static const char * s_start = "[\033[1;30mseq66\033[0m] \033[1;30m"; // 26 static const char * s_error = "[\033[1;31mseq66\033[0m] \033[1;30m"; // 26 static const char * s_eol = "\033[0m\n"; // 5 static const char * s_label = "[seq66] "; // 8 static const char * s_nl = "\n"; // 1 void async_safe_strprint (const char * msg, bool colorit) { if (not_nullptr(msg)) { size_t count = std::strlen(msg); if (count > 0) { if (is_a_tty(STDOUT_FILENO) && colorit) { write_msg(STDOUT_FILENO, s_start, 26); write_msg(STDOUT_FILENO, msg, count); write_msg(STDOUT_FILENO, s_eol, 5); } else { write_msg(STDOUT_FILENO, s_label, 8); write_msg(STDOUT_FILENO, msg, count); write_msg(STDOUT_FILENO, s_nl, 1); } } } } void async_safe_errprint (const char * msg, bool colorit) { if (not_nullptr(msg)) { size_t count = std::strlen(msg); if (count > 0) { if (is_a_tty(STDERR_FILENO) && colorit) { write_msg(STDERR_FILENO, s_error, 26); write_msg(STDERR_FILENO, msg, count); write_msg(STDERR_FILENO, s_eol, 5); } else { write_msg(STDERR_FILENO, s_label, 8); write_msg(STDERR_FILENO, msg, count); write_msg(STDERR_FILENO, s_nl, 1); } } } } /** * This function assumes the programmer knows what she's doing. The pointer * should be good and the buffer should be 24 characters. After getting the * digits, the count is the number of digits, which is 1 at a minimum. * * \param destination * Provides a 24-byte buffer to hold the resulting string. Assumed to be * valid and at least that large, for speed. * * \param number * The unsigned value to convert to an ASCII null-terminated string. * * \param spacebefore * If true (the default), then output a space first. This helps in * printing a number of values rapidly in a row. */ void async_safe_utoa (char * destination, unsigned number, bool spacebefore) { const unsigned ascii_base = unsigned('0'); char reversed[c_async_safe_utoa_size]; int count = 0; do { unsigned remainder = number % 10; reversed[count++] = char(remainder) + ascii_base; number /= 10; } while (number != 0); int index = 0; int limit = count; if (spacebefore) { destination[index++] = ' '; ++limit; } for ( ; index < limit; ++index) { --count; destination[index] = reversed[count]; } destination[index] = 0; /* append the string terminator */ } /** * Common-code for console informationational messages. Adds markers and a * newline. * * \param msg * The message to print, sans the newline. * * \param data * Additional information about the message. Optional. * * \return * Returns true, so that the caller can show the message and return the * status at the same time. */ void info_message (const std::string & msg, const std::string & data) { if (verbose()) { std::cout << seq_client_tag(msglevel::info) << " " << msg; if (! data.empty()) std::cout << ": " << data; if (! msg.empty()) std::cout << std::endl; } } void status_message (const std::string & msg, const std::string & data) { std::cout << seq_client_tag(msglevel::status) << " " << msg; if (! data.empty()) std::cout << ": " << data; if (! msg.empty()) std::cout << std::endl; } void session_message (const std::string & msg, const std::string & data) { std::cout << seq_client_tag(msglevel::session) << " " << msg; if (! data.empty()) std::cout << ": " << data; if (! msg.empty()) std::cout << std::endl; } /** * Common-code for console warning messages. Adds markers and a newline. * * \param msg * The message to print, sans the newline. * * \param data * Additional information about the message. Optional. * * \return * Returns true, so that the caller can show the message and return the * status at the same time. */ void warn_message (const std::string & msg, const std::string & data) { std::cerr << seq_client_tag(msglevel::warn) << " " << msg; if (! data.empty()) std::cerr << ": " << data; if (! msg.empty()) std::cerr << std::endl; } /** * Common-code for error messages. Adds markers, and returns false. * * \param msg * The message to print, sans the newline. * * \param data * Additional information about the message. Optional. * * \return * Returns false for convenience/brevity in setting function return * values. */ bool error_message (const std::string & msg, const std::string & data) { std::cerr << seq_client_tag(msglevel::error) << " " << msg; if (! data.empty()) std::cerr << ": " << data; if (! msg.empty()) std::cerr << std::endl; return false; } /** * More sneaky escape sequences for coloring. */ static const char * s_black = "\033[1;30m"; static const char * s_normal = "\033[0m"; /** * Common-code for debug messages. Adds markers, and returns false. * * \param msg * The message to print, sans the newline. * * \param data * Additional information about the error. Optional. * * \return * Returns true. The return value here is rarely used, if at all. */ void debug_message (const std::string & msg, const std::string & data) { if (investigate()) { std::cerr << seq_client_tag(msglevel::debug) << " "; if (is_a_tty(STDERR_FILENO)) std::cerr << s_black; std::cerr << msg; if (! data.empty()) std::cerr << ": " << data; if (! msg.empty()) { if (is_a_tty(STDERR_FILENO)) std::cerr << s_normal << std::endl; else std::cerr << std::endl; } } } /** * Common-code for error messages involving file issues, a very common use * case in error-reporting. Adds markers, and returns false. * * \param tag * The message to print, sans the newline. * * \param path * The name of the file to be shown. * * \return * Returns false for convenience/brevity in setting function return * values. */ bool file_error (const std::string & tag, const std::string & path) { std::cerr << seq_client_tag(msglevel::error) << " " << tag << ": '" << path << "'" << std::endl; return false; } /** * Shows a path-name (or other C++ string) as an info message. This output * is not contingent upon debugging or verbosity. * * \param tag * Provides the text to precede the name of the path. * * \param path * Provides the path-name to print. This message can be something other * than a path-name, by the way. */ void file_message (const std::string & tag, const std::string & path) { std::cout << seq_client_tag(msglevel::status) << " " << tag << ": '" << path << "'" << std::endl; } /** * This function just prints a colored tag to the proper output based on * message level. */ void print_client_tag (msglevel el) { std::string tag = seq_client_tag(el); bool iserror = el == msglevel::error || el == msglevel::warn || el == msglevel::debug; tag += " "; if (iserror) std::cerr << tag; else std::cout << tag; } /** * Takes a format string and a variable-argument list and returns the * formatted string. * * Although currently a public function, its usage is meant to be internal * for the msgprintf() function. See that function's description. * * C++11 is required, due to the use of va_copy(). * * \param fmt * Provides the printf() format string. * * \param args * Provides the variable-argument list. * * \return * Returns the formatted string. If an error occurs, the string is * empty. */ static std::string formatted (const std::string & fmt, va_list args) { std::string result; va_list args_copy; /* Step 2 */ va_copy(args_copy, args); const char * const szfmt = fmt.c_str(); int ilen = std::vsnprintf(NULL, 0, szfmt, args_copy); va_end(args_copy); if (ilen > 0) { std::vector dest(ilen + 1); /* Step 3 */ std::vsnprintf(dest.data(), dest.size(), szfmt, args); result = std::string(dest.data(), dest.size() - 1); } va_end(args); return result; } /** * Shows a boolean value as a "true/false" info message. * * \param tag * Provides the text to precede the boolean value. * * \param flag * Provides the boolean value to print. */ void boolprint (const std::string & tag, bool flag) { std::string fmt = tag + " %s"; msgprintf(msglevel::info, fmt, flag ? "true" : "false"); } /** * Shows a boolean value as an "on/off" info message. * * \param tag * Provides the text to precede the boolean value. * * \param flag * Provides the boolean value to print. */ void toggleprint (const std::string & tag, bool flag) { std::string fmt = tag + " %s"; msgprintf(msglevel::info, fmt, flag ? "on" : "off"); } /** * Formats a variable list of input and returns the formatted string. * Not the fastest function ever, but useful. Adapted from the last sample * in: * * https://stackoverflow.com/questions/19009094/ * c-variable-arguments-with-stdstring-only * * -# Initialize the usage of a variable-argument array. * -# Acquire the destination size from a copy of the variable-argument * array; mock the formatting with vsnprintf() and a null destination to * find out how many characters will be constructed. The copy is * necessary to avoid messing up the original variable-argument array. * -# Return a formatted string without needing memory management, and * without assuming any compiler/platform-specific behavior. * * \param lev * Indicates the desired message level: info, warn, or error. * * \param fmt * Indicates the desired format for the message. Use "%s" for strings. * * \param ... * Provides the printf() parameters for the format string. Please note * that C++ strings cannot be used directly... std::string::c_str() must * be used. */ void msgprintf (msglevel lev, std::string fmt, ...) { if (! fmt.empty()) { /* * cppcheck: Using reference 'fmt' as parameter for va_start() results * in undefined behaviour. Also claims we need to add a va_end(), so we * did, below, on 2019-04-21. */ va_list args; /* Step 1 */ va_start(args, fmt); std::string output = formatted(fmt, args); /* Steps 2 & 3 */ switch (lev) { case msglevel::none: std::cout << seq_client_tag(lev) << " " << output << std::endl; break; case msglevel::info: if (verbose()) std::cout << seq_client_tag(lev) << " " << output << std::endl; break; case msglevel::status: case msglevel::session: std::cout << seq_client_tag(lev) << " " << output << std::endl; break; case msglevel::warn: case msglevel::error: case msglevel::debug: std::cerr << seq_client_tag(lev) << " " << output << std::endl; break; } va_end(args); /* 2019-04-21 */ } } /** * Acts like msgprintf(), but returns the result as a string, and doesn't * bother with "info level" and whether we're debugging or not. * * \param fmt * Indicates the desired format for the message. Use "%s" for strings. * * \param ... * Provides the printf() parameters for the format string. Please note * that C++ strings cannot be used directly... std::string::c_str() must * be used. */ std::string msgsnprintf (std::string fmt, ...) { std::string result; if (! fmt.empty()) { va_list args; va_start(args, fmt); result = formatted(fmt, args); va_end(args); } return result; } } // namespace seq66 /* * basic_macros.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/util/condition.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file condition.cpp * * This module declares/defines the base class for mutexes. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2024-01-16 * \license GNU GPLv2 or above * * 2019-04-21 Reverted to commit 5b125f71 to stop GUI deadlock :-( * * Seq66 needs a mutex and a condition-variable for sequencer operations. * We tried to convert from pthreads to std::condition_variable, but it caused * issues such as freezing the user interfaces. For now, even though we don't * have to hide it, we using the "pimpl" paradigm to use a pthreads * implementation. See the Doxygen documentation for more information. */ #include /* std::make_unique() */ #include #include "util/condition.hpp" namespace seq66 { /* * -------------------------------------------------------------------------- * condition::impl nested class for pthreads * -------------------------------------------------------------------------- */ class condition::impl { private: using variable = pthread_cond_t; /** * We need a global condition-variable so that it can coordinate threads * in different objects. */ static variable sm_cond; /** * Provides a class-specific way to wait for a condition to change. It * is normally set to the static variable sm_cond. */ variable & m_cond; /** * Access to the outer class's recmutex. */ recmutex & m_rec_mutex; public: impl (recmutex & rm) : m_cond (sm_cond), m_rec_mutex (rm) { // no code } /** * Signals the condition variable. */ void signal () { pthread_cond_signal(&m_cond); } /** * Waits for the condition variable. If we use std::condition_variable, * we would need to provide a non-recursive mutex for locking. This * somehow freezes some things. A battle we will fight another day. */ void wait () { pthread_cond_wait(&m_cond, &(m_rec_mutex.native_locker())); } /** * Nanoseconds: convert milliseconds to microsecond */ void wait (int ms) { struct timespec w; w.tv_sec = long(ms / 1000); w.tv_nsec = long((ms * 1000) % 1000000) * 1000; pthread_cond_timedwait(&m_cond, &(m_rec_mutex.native_locker()), &w); } }; // class mutex::impl for pthreads /** * Define the static condition variable used by all mutex locks. */ condition::impl::variable condition::impl::sm_cond; } // namespace seq66 namespace seq66 { /* * -------------------------------------------------------------------------- * condition class p_imple wrapper * -------------------------------------------------------------------------- */ /** * Initialize the condition variable with the global variable. */ condition::condition () : m_mutex_lock (), p_imple (std::make_unique(m_mutex_lock)) { // Empty body } condition::condition (const condition & /* rhs */) : m_mutex_lock (), p_imple (std::make_unique(m_mutex_lock)) { // Empty body } condition & condition::operator = (const condition & rhs) { if (this != & rhs) { m_mutex_lock = rhs.m_mutex_lock; p_imple.reset(std::make_unique(m_mutex_lock).get()); } return *this; } condition::~condition () { // No code needed at this time, we think } void condition::signal () { p_imple->signal(); } void condition::wait () { p_imple->wait(); } void condition::wait (int ms) { p_imple->wait(ms); } /* * -------------------------------------------------------------------------- * A C++-only implmenation * -------------------------------------------------------------------------- * * How does the synchronisation work? The program has two threads. They use a * wait function and a signal function, respectively. The signal() function * in one thread notifies, using the condition variable, that it is done with * the preparation for work. While holding the lock, the other thread waits * for its notification: using the condition variable, the lock mutex, and * the predicate function. * * The sender and receiver both need a lock. In the case of the sender, a * std::lock_guard is sufficient, because it calls to lock and unlock only * once. In the case of the receiver, a std::unique_lock is necessary * because it usually frequently locks and unlocks its mutex. * * See: https://www.modernescpp.com/index.php/ * c-core-guidelines-be-aware-of-the-traps-of-condition-variables */ synchronizer::synchronizer () : m_helper_mutex (), m_condition_var () { // no other code } bool synchronizer::wait () { std::unique_lock locker(m_helper_mutex); m_condition_var.wait(locker, [this]{ return predicate(); }); return predicate(); } void synchronizer::signal () { std::lock_guard locker(m_helper_mutex); m_condition_var.notify_one(); } } // namespace seq66 /* * condition.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/util/filefunctions.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file filefunctions.cpp * * Provides the implementations for safe replacements for the various C * file functions. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-11-20 * \updates 2025-01-27 * \version $Revision$ * * We basically include only the functions we need for Seq66, not * much more than that. These functions are adapted from our xpc_basic * project. * * In addition, internally we want to handle all file-specifications in * UNIX format, even if the name includes Window's constructs such as "C:". * So the normalize_path() function (which converts both ways) is used * heavily. For OS calls, we want to use the native format. */ #include /* std::replace() function */ #include /* std::toupper() function */ #include /* realpath(3) or _fullpath() */ #include /* std::strlen(), strerror_r() etc. */ #include /* std::strftime() */ #include #include "cfg/settings.hpp" /* seq66::doc_folder_list() */ #include "util/basic_macros.hpp" /* support and platform macros */ #include "util/filefunctions.hpp" /* free functions in seq66 n'space */ #include "util/strfunctions.hpp" /* free functions in seq66 n'space */ #if defined SEQ66_HANDLE_FILE_WILDCARDS #include /* ::glob() to get wildcards */ #endif /** * All file-specifications internal to Seq66 and in its configuration files * use the UNIX path separator ("/"), no matter what the operating system. * Also, select the HOME or LOCALAPPDATA environment variables depending on * whether building for Windows or not. LOCALAPPDATA points to the root of * the Windows user's configuration directory, AppData/Local. * * APPDATA C:\Users\username\AppData\Roaming * LOCALAPPDATA C:\Users\username\AppData\Local * HOMEDRIVE C: * HOMEPATH \Users\username * */ #if defined SEQ66_PLATFORM_WINDOWS #define SEQ66_PATH_SLASH "\\" #define SEQ66_PATH_SLASH_CHAR '\\' #define SEQ66_ENV_HOMEDRIVE "HOMEDRIVE" #define SEQ66_ENV_HOMEPATH "HOMEPATH" #define SEQ66_ENV_CONFIG "LOCALAPPDATA" #else #define SEQ66_PATH_SLASH "/" #define SEQ66_PATH_SLASH_CHAR '/' #define SEQ66_ENV_HOME "HOME" #define SEQ66_ENV_CONFIG ".config" #endif #define SEQ66_PATH_SLASHES "/\\" /* * More legacy configuration macros. */ #if SEQ66_HAVE_LIMITS_H #include /* PATH_MAX */ #endif #if defined SEQ66_PLATFORM_WINDOWS /* Microsoft platform */ #include /* file-name info and getcwd() */ #include /* _access_s() */ #include /* _SH_DENYNO */ #if defined SEQ66_PLATFORM_MSVC /* Microsoft compiler vs MingW */ #define F_OK 0x00 /* existence */ #define X_OK 0x01 /* executable, not useful w/Windows */ #define W_OK 0x02 /* writability */ #define R_OK 0x04 /* readability */ #define S_ACCESS _access_s /* Microsoft's safe access() */ #define S_CHDIR _chdir /* Microsoft's chdir() */ #define S_CLOSE _close /* Microsoft's close() */ #define S_FOPEN fopen_s /* Microsoft's safe fopen() */ #define S_GETCWD _getcwd /* Microsoft's getcwd() */ #define S_LSEEK _lseek /* Microsoft's lseek() */ #define S_MKDIR _mkdir /* Microsoft's mkdir() */ #define S_OPEN _sopen_s /* Microsoft's safe open() */ #define S_RMDIR _rmdir /* Microsoft's rmdir() */ #define S_STAT _stat /* Microsoft's stat function */ #define S_UNLINK _unlink /* Microsoft file deletion function */ #define S_MAX_FNAME _MAX_FNAME /* Microsoft's filename size (260!) */ #define S_MAX_PATH _MAX_PATH /* Microsoft's path size (260!) */ using stat_t = struct _stat; #else /* not SEQ66_PLATFORM_MSVC: Mingw */ #define S_ACCESS _access_s /* Microsoft's safe access() */ #define S_CHDIR chdir #define S_CLOSE _close /* Microsoft's close() */ #define S_FOPEN fopen_s /* Microsoft's safe fopen() */ #define S_GETCWD _getcwd /* Microsoft's getcwd() */ #define S_LSEEK _lseek /* Microsoft's lseek() */ #define S_MKDIR mkdir #define S_OPEN _sopen_s /* Microsoft's safe open() */ #define S_RMDIR rmdir #define S_STAT stat #define S_UNLINK _unlink /* Microsoft file deletion function */ #define S_MAX_FNAME _MAX_FNAME /* Microsoft's filename size (260!) */ #define S_MAX_PATH _MAX_PATH /* Microsoft's path size (260!) */ using stat_t = struct stat; #endif /* SEQ66_PLATFORM_MSVC */ #else /* non-Microsoft stuff follows */ #include #define S_ACCESS access /* ISO/POSIX/BSD unsafe access() */ #define S_CHDIR chdir /* ISO/POSIX/BSD chdir() */ #define S_CLOSE close /* ISO/POSIX/BSD close() */ #define S_FOPEN fopen /* ISO/POSIX/BSD safe sprintf() */ #define S_GETCWD getcwd /* ISO/POSIX/BSD getcwd() */ #define S_LSEEK lseek /* ISO/POSIX/BSD lseek() */ #define S_MKDIR mkdir /* ISO/POSIX/BSD mkdir() */ #define S_OPEN open /* ISO/POSIX/BSD safe open() */ #define S_RMDIR rmdir /* ISO/POSIX/BSD rmdir() */ #define S_STAT stat /* ISO/POSIX/BSD stat function */ #define S_UNLINK unlink /* ISO etc. file deletion function */ #define S_MAX_FNAME NAME_MAX /* ISO/POSIX/BSD stat function */ #define S_MAX_PATH PATH_MAX /* POSIX path size */ using stat_t = struct stat; #endif /* SEQ66_PLATFORM_WINDOWS */ /** * A macro for the error code when a file does not exist. */ #define ERRNO_FILE_DOESNT_EXIST 2 /* true in Windows and Linux */ /** * A more comprehensible name for the type of errno. */ using errno_t = int; namespace seq66 { /* * Almost equivalent to SEQ66_PLATFORM_WINDOWS. */ #if defined SEQ66_PLATFORM_MING_OR_WINDOWS /** * An internal function to assist other copying functions. All parameters * are assumed to have been guaranteed valid by the caller. * * \param destination * The pointer to the area to receive the copy. * * \param destsize * The maximum number of characters to be copied, plus one. The space * needed for the terminating null character must be included in this * count. * * \param source * The null-terminated string to be copied. * * \param sourcelimit * An optional number of characters to which to limit the source string. * It defaults to 0. The caller does not need to guarantee that it is * not longer than the actual length of the source; the copying stops at * the null character. * * - If 0, the limit is set to the length of the source. * - If greater than the actual length of the source, the limit is set * to the length of the source. * * \return * Returns 'true' if the parameters were valid, so that the whole source * string could be copied (without truncation). Even if the parameters * were invalid, the destination still can be used safely -- it might be * empty or truncated, though. */ static bool s_stringcopy ( char * destination, size_t destsize, const char * source, size_t sourcelimit = 0 ) { bool result = false; size_t length = std::strlen(source); /* inefficient */ *destination = 0; /* empty out destination */ if (sourcelimit > length || sourcelimit == 0) sourcelimit = length; if (sourcelimit <= INT_MAX) /* just a sanity check */ { /* * This code checks the length of the source string, and decides * if it can copy the whole string, or has to limit the string and * enforce the null terminator. */ #if defined SEQ66_PLATFORM_MSVC int rcode; size_t count = _TRUNCATE; /* overloaded parameter!! */ if (sourcelimit < destsize) /* truncation impossible */ { destsize = sourcelimit + 1; count = sourcelimit; } rcode = strncpy_s /* guaranteed null byte */ ( destination, destsize, source, count ); if (rcode == 0) result = true; else warn_message("stringcopy truncation"); #else if (sourcelimit < destsize) /* truncation impossible */ { destsize = sourcelimit + 1; result = true; } else /* truncation definite */ warn_message("stringcopy truncation"); (void) std::strncpy(destination, source, destsize); destination[destsize-1] = 0; #endif } return result; } #endif // defined SEQ66_PLATFORM_MING_OR_WINDOWS /** * A static function to make safer copies of a system error message than does * strerror(). Replaces strerror() [Borland], strerror_s() [Microsoft], and * strerror_r() [GNU]. The differences in the three functions are accounted * for in this implementation. * * \win32 * The Windows version returns a 0 (success) when the errnum value is such * that it yields an "Unknown error"! In Microsoft's crtdefs.h, errno_t is * an integer. The values in Microsoft's errno.h go up to only a couple * hundred. Nobody defines an ELAST value, so we will here, for now. * * \param errnum * The errno variable to be translated to a message. * * \return * Returns an empty string if no error occurred in the function. */ static std::string string_errno (errno_t errnum) { std::string result; char dest[1024]; /* static allocation for now only */ bool ok = true; /* start optimistically */ dest[0] = 0; /* make it an empty string to start */ #if defined SEQ66_PLATFORM_MING_OR_WINDOWS #define ELAST (EWOULDBLOCK + 1) /* Can change at vendor whim! */ if (errnum != 0) { int rcode = strerror_s(dest, sizeof dest, errnum); ok = (errnum >= 0) && (errnum < ELAST) ? (rcode == 0) : false ; } else ok = s_stringcopy(dest, sizeof dest, "Possible truncation"); #endif #if defined SEQ66_PLATFORM_XSI int rcode = strerror_r(int(errnum), dest, sizeof dest); ok = rcode == 0; #elif defined SEQ66_PLATFORM_GNU /* * This code gets compiled in Qt/Mingw on Windows. */ char * msg = strerror(int(errnum)); (void) strncpy(dest, msg, sizeof dest - 1); #else const char * msg = strerror(errnum); (void) std::strncpy(dest, msg, sizeof dest - 1); #endif if (ok) result = std::string(dest); else result = std::string("string_errno() internal error"); return result; } /** * Checks for a file error, reporting it to the error-log if there is one. * * \private * This function is visible and used only in this module, and assumes * that the pointer parameters have been checked by the caller. * * \param filename * Provides the name of the file to be handled. * * \param mode * Provides the mode of usage for the file, which can be one of the * file-opening mode, or the number of the function in which the failure * occurred. * * \param errnum * Provides the error code. If 0, there is no error to report. * * \return * Returns the status of the \a errnum variable. */ static bool s_file_error ( const std::string & filename, const std::string & mode, int errnum ) { bool result = errnum == 0; if (! result) { std::string temp = string_errno(errnum); temp += " (mode/function " + mode + ")"; file_error(temp, filename); } return result; } /** * Checks a file for the desired access modes. The following modes are * defined, and can be OR'd: * \verbatim POSIX Value Windows Meaning F_OK 0 0x00 Existence X_OK 1 N/A Executable W_OK 2 0x04 Writable R_OK 4 0x02 Readable \endverbatim * * \win32 * Windows does not provide a mode to check for executability. * * \param filename * Provides the name of the file to be checked. * * \param mode * Provides the mode of the file to check for. This value should be in * the cross-platform set of file-mode's for the various versions of the * fopen() function. * * \return * Returns 'true' if the requested modes are all supported for the file. */ bool file_access (const std::string & filename, int mode) { bool result = file_name_good(filename); if (result) { #if defined SEQ66_PLATFORM_MSVC /** * Passing in X_OK here on Windows 7 yields a debug assertion! For * now, we just have to return false if that value is part of the mode * mask. */ if (mode & X_OK) { errprint("cannot test X_OK (executable bit) on Windows"); result = false; } else { int errnum = S_ACCESS(filename.c_str(), mode); result = errnum == 0; } #else int errnum = S_ACCESS(filename.c_str(), mode); result = errnum == 0; #endif } return result; } /** * Checks a file for existence. * * \param filename * Provides the name of the file to be checked. * * \return * Returns 'true' if the file exists. * */ bool file_exists (const std::string & filename) { return file_access(filename, F_OK); } /** * Checks a file for readability. * * \param filename * Provides the name of the file to be checked. * * \return * Returns 'true' if the file is readable. * */ bool file_readable (const std::string & filename) { return file_access(filename, R_OK); } /** * Checks a file for writability. * * \param filename * Provides the name of the file to be checked. * * \return * Returns 'true' if the file is writable. * */ bool file_writable (const std::string & filename) { return file_access(filename, W_OK); } /** * Checks a file for readability and writability. An even stronger test than * file_exists. At present, we see no need to distinguish read and write * permissions. We assume the file is fully accessible only if the file has * both permissions. * * This can be surprising if one wants only to read a file, and the file is * read-only. * * \param filename * Provides the name of the file to be checked. * * \return * Returns 'true' if the file is readable and writable. */ bool file_read_writable (const std::string & filename) { return file_access(filename, R_OK|W_OK); } /** * Checks a file for the ability to be executed. * * \param filename * Provides the name of the file to be checked. * * \return * Returns 'true' if the file exists. */ bool file_executable (const std::string & filename) { bool result = file_name_good(filename); if (result) { stat_t statusbuf; int statresult = S_STAT(filename.c_str(), &statusbuf); if (statresult == 0) /* a good file handle? */ { #if defined SEQ66_PLATFORM_MSVC result = (statusbuf.st_mode & _S_IEXEC) != 0; #else result = ( ((statusbuf.st_mode & S_IXUSR) != 0) || ((statusbuf.st_mode & S_IXGRP) != 0) || ((statusbuf.st_mode & S_IXOTH) != 0) ); #endif } else result = false; } return result; } /** * Checks a file to see if it is a directory. This function is also used in * the function of the same name in fileutilities.cpp. * * \param filename * Provides the name of the directory to be checked. * * \return * Returns 'true' if the file is a directory. */ bool file_is_directory (const std::string & filename) { bool result = file_name_good(filename) && file_exists(filename); if (result) { stat_t statusbuf; int statresult = S_STAT(filename.c_str(), &statusbuf); if (statresult == 0) // a good file handle? { #if defined SEQ66_PLATFORM_MSVC result = (statusbuf.st_mode & _S_IFDIR) != 0; #else result = (statusbuf.st_mode & S_IFDIR) != 0; #endif } else result = false; } return result; } /** * Determines the size of a file. An alternative, but apparently less * reliable method is the following, which apparently needs to be put in a * try-catch block. * * std::ifstream file(m_name, std::ios::in | std::ios::binary | std::ios::ate); * bool result = file.is_open(); * (void) file.seekg(0, file.end); // seek to the file's end * m_file_size = file.tellg(); // get the end offset * * Another option is to #include and call * std::uintmax_t file_size (const std::filesystem::path& p), which * throws (but there is a noexcept version as well). * * \param filename * The name of the file. If it is a directory, this function will fail. * * \return * Returns the size of the file or 0 if the file is of size 0 or is * not a file. */ size_t file_size (const std::string & filename) { size_t result = 0; if (file_name_good(filename)) { stat_t statusbuf; int statresult = S_STAT(filename.c_str(), &statusbuf); if (statresult == 0) // a good file handle? result = statusbuf.st_size; } return result; } /** * Verifies that a file-name pointer is legal. The following checks are * made: * * - The file-name is not empty. * - The file-name is not one of the following: * - "stdout" * - "stdin" * - "stderr" * * Does not replace any function, but is a helper function that is worthwhile * to expose publicly. * * \param fname * Provides the file name to be verified. * * \return * Returns 'true' if the file-name is valid. */ bool file_name_good (const std::string & fname) { bool result = ! fname.empty(); if (result) { result = fname != "stdout" && fname != "stdin" && fname != "stderr"; if (! result) file_message("file-name invalid", fname); } return result; } /** * Verifies that a file-mode pointer is legal. The following checks of the * file-mode string are made: * * - The mode is not empty. * - The mode is one of the following: * - r, w, a * - r+, w+, a+ * - rb, wb, ab * - rb+, wb+, ab+ * - r+b, w+b, a+b * - rt, wt, at * - rt+, wt+, at+ * - r+t, w+t, a+t * In other words, non-standard extensions are not allowed. * * Does not replace any function, but is a helper function that is worthwhile * to expose publicly. * * \param mode * Provides the file-opening mode string to be verified. * * \return * Returns 'true' if the file-name is valid. * */ bool file_mode_good (const std::string & mode) { bool result = ! mode.empty(); if (result) { /* * Accept only valid characters here. Note that this test will * reject empty strings as well. */ result = mode[0] == 'r' || mode[0] == 'w' || mode[0] == 'a'; if (result) { if (mode[1] != 0) { result = mode[1] == '+' || mode[1] == 'b' || mode[1] == 't'; if (result) { if (mode[2] != 0) { result = mode[2] == '+' || mode[2] == 'b' || mode[2] == 't'; if (result) result = mode[3] == 0; } } } } if (! result) file_message("file-mode invalid", mode); } return result; } /** * Opens a file in the desired operating mode. It replaces fopen() or * fopen_s() [Microsoft]. * * \note * Both the Microsoft and GNU "fopen" functions provide an errno-style * error code when the function fails. The Microsoft function fopen_s() * returns the error-code directly. The fopen(3) function returns a null * pointer, and sets the value of 'errno' to indicate the error. * * \param filename * Provides the name of the file to be opened. * * \param mode * Provides the mode of the file. See the file_mode_good() function. * This value should be in the cross-platform set of file-mode's for the * various versions of the fopen() function. * * \return * Returns the file pointer if the function succeeded. Otherwise, a null * pointer is returned. The caller must check this value before * proceeding to use the file-handle. */ std::FILE * file_open (const std::string & filename, const std::string & mode) { std::FILE * filehandle = nullptr; if (file_name_good(filename) && ! mode.empty()) { #if defined SEQ66_PLATFORM_WINDOWS /* MSVC undefined in Qt on Windows */ int errnum = (int) S_FOPEN(&filehandle, filename.c_str(), mode.c_str()); if (errnum != 0) filehandle = nullptr; #else int errnum = 0; filehandle = S_FOPEN(filename.c_str(), mode.c_str()); if (is_nullptr(filehandle)) errnum = errno; #endif (void) s_file_error(filename, mode, errnum); } return filehandle; } /** * Opens a file for binary reading. This function calls * file_open(filename, "rb"). It replaces fopen(). * * \param filename * Provides the name of the file to be opened. * * \return * Returns the file pointer if the function succeeded. Otherwise, a null * pointer is returned. The caller must check this value before * proceeding to use the file-handle. */ std::FILE * file_open_for_read (const std::string & filename) { std::FILE * filehandle = nullptr; if (file_readable(filename)) filehandle = file_open(filename, "rb"); /* open for reading only */ return filehandle; } /** * Recreates a file for binary writing. This function calls * file_open(filename, "wb"). Replaces fopen(). * * \note * One might expect that this function should fail if the file already * exists. Indeed, that would be a nice function to have. However, this * function supports legacy code, and we can't modify what it does at * this time. * * \param filename * Provides the name of the file to be opened. * * \return * Returns the file pointer if the function succeeded. Otherwise, a null * pointer is returned. The caller must check this value before * proceeding to use the file-handle. */ std::FILE * file_create_for_write (const std::string & filename) { return file_open(filename, "wb"); } /** * Gets the current date/time. * * \return * Returns the current date and time as a string. */ std::string current_date_time () { static char s_temp[64]; static const char * const s_format = "%Y-%m-%d %H:%M:%S"; time_t t; std::memset(s_temp, 0, sizeof s_temp); time(&t); struct tm * tm = localtime(&t); std::strftime(s_temp, sizeof s_temp - 1, s_format, tm); return std::string(s_temp); } /** * Appends a string to file. If it does not exist, it is appended to. */ bool file_write_string (const std::string & filename, const std::string & text) { std::FILE * fptr = file_open(filename, "a"); /* "w": now we append */ bool result = not_nullptr(fptr); if (result) { std::string fulltext = filename; fulltext += "\n"; fulltext += current_date_time(); fulltext += "\n"; fulltext += text; fulltext += "\n"; size_t len = fulltext.length(); size_t rc = fwrite(fulltext.c_str(), sizeof(char), len, fptr); if (rc < len) { file_error("Write failed", filename); result = false; } (void) file_close(fptr, filename); } return result; } /** * Reads a file into a string. */ std::string file_read_string (const std::string & file) { std::string result; bool ok = file_name_good(file); if (ok) { std::FILE * input = file_open_for_read(file); if (not_nullptr(input)) { int ci; while ((ci = fgetc(input)) != EOF) { result += char(ci); } (void) file_close(input, file); } } return result; } /** * Closes a file opened by the file_open() functions. Replaces fclose(). * * \param filehandle * Provides the file-handle to be closed. * * \param filename * Provides the name of the file to be closed. Used only for error * reporting. If you don't care about it, pass in an empty string, * which is the default value. * * \return * Returns 'true' if the close operation succeeded. */ bool file_close (std::FILE * filehandle, const std::string & filename) { bool result = not_nullptr_assert(filehandle, "file_close() null handle"); if (result) { int rcode = fclose(filehandle); result = s_file_error(filename, __func__, rcode); } else { /* * if (! filename.empty()) * errprintex(filename, "File"); */ } return result; } bool file_delete (const std::string & filespec) { bool result = ! filespec.empty(); if (result) { int rc = unlink(filespec.c_str()); result = rc != (-1); if (! result) file_error("Delete failed", filespec); } return result; } /** * Copies a file to a file with a different name. The file names can be * absolute, or they can be relative. This function will refuse to copy a * file onto itself. * * (This check is simply a check for string-equivalence of the names; the * check does not try to figure out if the paths resolve to the same * file-name and path, for now. The get_full_path() function will work only * if the file already exists.) * * \param oldfile * The full path to the source file. Includes the path and the base-name * of the file. * * \param newfile * The full path to the destination file. If there is no base file-name * (e.g. "file.ext") then the base file-name of \a oldfile will be * appended. * * \todo * Consider fflush() to flush the user-space buffers provided by the C * library, and then fsync() to have the kernel flush the file to disk, * followed by fsync() on the directory itself to finish the process. * See the Linux man pages [fflush(3), sync(2), and fsync(2)]. * * \return * Returns 'true' if all of the parameters were valid, and all of the * operations succeeded. Returns 'false' otherwise. */ bool file_copy ( const std::string & oldfile, const std::string & newfile ) { bool result = file_name_good(oldfile) && file_name_good(newfile); if (result) { std::string destfilespec = newfile; std::string destpath; std::string destbase; result = filename_split(newfile, destpath, destbase); if (result) { if (destbase.empty()) { std::string sourcebase = filename_base(oldfile); destfilespec = filename_concatenate(destpath, sourcebase); } } /* * The destination file-specification, if the file does not already * exist, will result in a warning in get_full_path() and a * seeming empty string to compare against. */ bool ok = get_full_path(oldfile) != get_full_path(destfilespec); if (result && ok) { std::FILE * input = file_open_for_read(oldfile); if (not_nullptr(input)) { bool okinput = false; bool okoutput = false; std::FILE * output = file_create_for_write(destfilespec); if (not_nullptr(output)) { int ci; while ((ci = fgetc(input)) != EOF) { int co = fputc(ci, output); if (co == EOF) break; } okoutput = file_close(output, destfilespec); } okinput = file_close(input, oldfile); result = okinput && okoutput; } } } return result; } /** * Copies a file to a given directory. Unlike file_copy(), it does not * split components. It's just a simple copy operation. * * \param sourcefile * Provides either a file-name in the application's current working * directory, or a full file specification to an existing file. * * \param path * Provides the destination directory, which is verified to exist. * * \return * Returns true if the file copying succeeded. */ bool file_copy_to_path ( const std::string & sourcefile, const std::string & path ) { bool result = file_exists(sourcefile) && file_is_directory(path); if (result) { std::FILE * input = file_open_for_read(sourcefile); if (not_nullptr(input)) { bool okinput = false; bool okoutput = false; std::string unusedpath; std::string filebase; bool ok = filename_split(sourcefile, unusedpath, filebase); if (ok) { std::string destfilespec = filename_concatenate ( path, filebase ); std::FILE * output = file_create_for_write(destfilespec); if (not_nullptr(output)) { int ci; while ((ci = fgetc(input)) != EOF) { int co = fputc(ci, output); if (co == EOF) break; } okoutput = file_close(output, destfilespec); } okinput = file_close(input, sourcefile); result = okinput && okoutput; } } } return result; } /** * Appends a character buffer to a file in the configuration directory. * Useful for dumping error information, such as under PortMidi in Windows. * Very similar to file_write_string()! * * \param filename * Provides the full path to the file plus the file-name and * file-extension. One way to create such a path is using the * rcsettings::config_filespec() function. * * \param data * Provides the string data to write, already formatted and ready to go. * * \return * Returns true if all steps in the process succeeded. */ bool file_append_log ( const std::string & filename, const std::string & data ) { std::string text = trim(data); if (text.empty()) { return true; /* no need to open */ } else { std::FILE * fp = file_open(filename, "a"); /* open for appending */ bool result = not_nullptr(fp); if (result) { std::string log = "\n"; log += current_date_time(); log += "\n"; log += text.data(); log += "\n\n"; size_t rc = fwrite(log.data(), sizeof(char), log.size(), fp); if (rc < log.size()) { file_error("Write failed", filename); result = false; } (void) file_close(fp, filename); } return result; } } /** * Check if the file-name has a "/", or, in Windows, "\" or ":". The latter * case covers names like "C:filename.ext". * * Obviously, this function otherwise assumes a well-formed file * specification. * * \param filename * The name of the file, to be tested. * * \return * Returns true if any directory characters are found. */ bool name_has_path (const std::string & filename) { auto pos = filename.find_first_of("/"); bool result = pos != std::string::npos; #if defined SEQ66_PLATFORM_WINDOWS if (! result) { pos = filename.find_first_of("\\"); result = pos != std::string::npos; if (! result) { pos = filename.find_first_of(":"); result = pos != std::string::npos; } } #endif return result; } /** * Detects if the filename contains a root path, rather than a relative * path. Using the circumflex (for /home/user) is treated as a root path. * \verbatim ~/dir/dir2 ... User's HOME directory. /dir/dir2 ... System directory. \dir\dir2 ... Windows root directory on current drive. C:[\dir-or-file...] Windows root directory on specific drive. \\server\volume... Windows Universal Naming Convention (UNC). \endverbatim * * Also, using the circumflex (for /home/user) is treated as a root path. */ bool name_has_root_path (const std::string & filename) { auto pos = filename.find_first_of("~/"); /* "~" == "/home/usr" */ bool result = pos != std::string::npos; #if defined SEQ66_PLATFORM_WINDOWS if (! result) { pos = filename.find_first_of("\\"); /* UNC/current drive root */ result = pos != std::string::npos; } #endif if (result) result = pos == 0; /* path starts with "/~\ */ #if defined SEQ66_PLATFORM_WINDOWS if (! result) { pos = filename.find_first_of(":"); /* C: */ result = pos != std::string::npos; if (result) result = std::isalpha(filename[0]) && pos == 1; } #endif return result; } /** * Detect a legimate file extension in a file-name. Tricky! * * - .apprc The file-name is the extension. * - app.rc File-name extension present. * - app.local.rc File-name extension present. * - ~/.config/seq66/ No file-name extension. * - ~/.config/seq66/filename No file-name extension. * - ~/.config/seq66/filename.ext File-name extension present. * - ~/.config/seq66/file.name.ext File-name extension present. * * \param filename * Provides the file-name or full path specification. It is assumed to * be normalized to UNIX format by normalize_path(). * * \return * Returns true or false as per the list above. */ bool name_has_extension (const std::string & filename) { auto spos = filename.find_last_of("/"); if (spos == std::string::npos) spos = 0; auto ppos = filename.find_first_of(".", spos); bool result = ppos != std::string::npos; return result; } /** * A function to ensure that the ~/.config/seq66 directory exists. This * function is actually a little more general than that, but it is not * sufficiently general, in general, General. Consider using * make_directory_path(), defined elsewhere in this module. This one is now * static. * * \param pathname * Provides the name of the path to create. The parent directory of the * final directory must already exist. * * \return * Returns true if the path-name exists. */ #if defined SEQ66_PLATFORM_GNU #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif static bool make_directory (const std::string & pathname) { bool result = file_name_good(pathname); if (result) { static struct stat st = { #if defined SEQ66_PLATFORM_CLANG #if defined SEQ66_PLATFORM_FREEBSD /* __clang_major__<17 ? */ 0, 0, 0, 0, 0, 0, 0, 0, 0, { 0, 0 } /* Clang/FreeBSD */ #else 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* and more for Linux! */ #endif #else 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* and more for Linux! */ #endif }; if (S_STAT(pathname.c_str(), &st) == -1) { #if defined SEQ66_PLATFORM_WINDOWS int rcode = S_MKDIR(pathname.c_str()); #else int rcode = S_MKDIR(pathname.c_str(), 0755); /* rwxr-xr-x */ #endif result = rcode == 0; if (! result) file_error("mkdir() failed", pathname); } } return result; } /** * Creates a directory based on its full directory path. This function is a * turbo version of the mkdir() and _mkdir() functions. It ensures that the * whole sequences of directories in a path-name are created, if they don't * exist already. In addition, the path-name parameter is massaged in * whatever way is necessary, such as removing any terminating backslash. * For now, since at least _mkdir() can handle either the '/' or the '\', we * don't worry about converting to the '\' for DOS-like stuff. In fact, for * the Seq66 project, all paths are UNIX-style internally, and calling the * normalize_path() function guarantees that. The input pathname is in the * form of: * \verbatim /dir0/dir1/dir2/.../dirn/ C:/dir0/dir1/dir2/.../dirn/ \endverbatim * * The code works by temporarily converting each '/' character in sequence to * a null, and seeing if the resulting entity (drive or directory) exists. * If not, the attempt is made to create it. If it succeeds, the null is * converted back to a '/', and the next subdirectory is worked on, until all * are done. For absolute UNIX or Windows paths, we do not remove the first * slash. * * \bug * This uses C calls. * * Replaces: * * - _mkdir() [Microsoft] * - mkdir() [GNU]. * * \param directory_name * Provides the name of the directory to create. * * \return * Returns true if the create operation succeeded. It also returns true * if the directory already exists. */ bool make_directory_path (const std::string & directory_name) { bool result = file_name_good(directory_name); std::string dirname = os_normalize_path(directory_name); /* ca 2023-05-11 */ if (result) { if (file_exists(dirname)) /* directory already exists */ return true; } if (result) { result = dirname.length() < S_MAX_PATH; if (! result) file_error("Path too long", dirname); } if (result) { char currdir[S_MAX_PATH]; bool more = true; int slash = '/'; char * nextptr; /* just what it says! */ (void) std::strncpy(currdir, dirname.c_str(), sizeof currdir - 1); char * endptr = &currdir[0]; /* start at the beginning */ char * ending = strchr(endptr, '\0'); do { nextptr = strchr(endptr, slash); /* find next slash */ if (is_nullptr(nextptr)) /* if still not found ... */ { more = false; /* ...this is last subdir */ if (endptr < ending) /* ...if not yet at end */ nextptr = ending; /* ...this will be the end */ } else { if (nextptr == endptr) { ++endptr; continue; } } if (not_nullptr(nextptr)) { *nextptr = 0; /* make null terminator */ if (! file_exists(currdir)) /* subdirectory exists? */ { if (! make_directory(std::string(currdir))) { more = result = false; break; } } if (more) { *nextptr = char(slash); /* restore the slash */ endptr = nextptr + 1; /* point just past slash */ } } } while (more); } return result; } /** * Simply removes the initial path slash. Meant for playlist usage, and is very * simplistic at this time. */ std::string make_path_relative (const std::string & path) { std::string result = path; auto spos = result.find_first_of(SEQ66_PATH_SLASHES); if (spos == 0) result = result.substr(1); return result; } /** * Deletes a directory based on its directory name. This function first makes * sure that the name is valid, using the file_name_good() function. * * Replaces: * * - _rmdir() [Microsoft] * - rmdir() [GNU]. Note, however, that file_delete() can be called and * will work with both files and directories. * * \param filename * Provides the name of the file to be deleted. * * \return * Returns true if the delete operation succeeded. It also returns true * if the directory did not exist in the first place. */ bool delete_directory (const std::string & filename) { bool result = file_name_good(filename) && file_exists(filename); if (result) { int rcode = S_RMDIR(filename.c_str()); if (rcode == (-1)) result = s_file_error(filename, __func__, errno); } return result; } /** * Provides the path name of the current working directory. This function is * a wrapper for getcwd() and other such functions. It obtains the current * working directory in the application. * * \return * The pointer to the string containg the name of the current directory. * This name is the full path name for the directory. If an error * occurs, then an empty string is returned. */ std::string get_current_directory () { std::string result; char temp[PATH_MAX]; char * cwd = S_GETCWD(temp, PATH_MAX); /* get current directory */ if (not_nullptr(cwd)) { size_t len = strlen(cwd); if (len > 0) { result = cwd; } else { errprint("empty current directory name"); } } else { errprint("current directory unavailable"); } return result; } /** * Given a path, relative or not, this function returns the full path. * It uses the Linux function realpath(3), which returns the canonicalized * absolute path-name. For Windows, the function _fullpath() is used. * * TODO: make sure both Win/Lin calls get the result freed after use. * * \param path * Provides the path, which may be relative. * * \return * Returns the full path. If a problem occurs, the result is empty. */ std::string get_full_path (const std::string & path) { std::string result; /* default empty result */ if (file_name_good(path)) { char * resolved_path; #if defined SEQ66_PLATFORM_WINDOWS /* _MSVC not defined in Qt */ resolved_path = _fullpath(NULL, path.c_str(), S_MAX_PATH); #else resolved_path = realpath(path.c_str(), NULL); #endif if (not_NULL(resolved_path)) { result = resolved_path; free(resolved_path); } else { /* * In Linux we could call string_errno(errno ). */ #if defined SEQ66_PLATFORM_POSIX_API errno_t errnum = errno; std::string errmsg = "Warning: "; errmsg += string_errno(errnum); file_message(errmsg, path); #else file_message("realpath() error", path); #endif } } return result; } /** * Returns the UNIX path separator, no matter the platform. * See os_path_slash() below. */ char path_slash () { return '/'; } /** * Returns the default path separator. Based on the operating system: a * backslash for Windows and (laughing) CPM and DOS, and a forward slash * for UNIXen. */ char os_path_slash () { return SEQ66_PATH_SLASH_CHAR; } /** * Saves some code and improves readability. */ std::string unix_normalize_path (const std::string & path) { return normalize_path(path, true, true); } /** * Makes sure that the path-name is a UNIX path, separated by forward slashes * (the solidus), or a Windows path, separated by back slashes. It also * converts "~" to the user's HOME or LOCALAPPDATA directory. * * \param path * Provides the path, which should be a full path-name. * * \param to_unix * Defaults to true, which converts "\" to "/". False converts in the * opposite direction. * * \param terminate * If true, tack on a final separator, if necessary. Defaults to false. * Do not set to true if you know the path ends with a file-name. * * \return * The possibly modified path is returned. If path is not valid (e.g. * the name of a console file handle), then an empty string is returned. */ std::string normalize_path (const std::string & path, bool to_unix, bool terminate) { std::string result; if (file_name_good(path)) { result = path; auto circumpos = result.find_first_of("~"); if (circumpos != std::string::npos) { result.replace(circumpos, 1, user_home()); } if (to_unix) { auto pos = path.find_first_of("\\"); if (pos != std::string::npos) std::replace(result.begin(), result.end(), '\\', '/'); if (terminate && result.back() != '/') result += "/"; } else { auto pos = path.find_first_of("/"); if (pos != std::string::npos) std::replace(result.begin(), result.end(), '/', '\\'); if (terminate && result.back() != '\\') result += "\\"; } } return result; } /** * Shortens a file-specification to make sure it is no longer than the * provided length value. This is done by removing character in the middle, * if necessary, and replacing them with an ellipse. * * This function operates by first trying to find the /home * directory. If found, it strips off /home/username and * replace it with the Linux ~ replacement for the * $HOME environment variable. This function assumes that the * "username" portion must exist, and that there's no goofy stuff * like double-slashes in the path. * * \param fpath * The file specification, including the full path to the file, and the * name of the file. * * \param leng * Provides the length to which to limit the string. * * \return * Returns the fpath parameter, possibly shortened to fit within the * desired length. */ std::string shorten_file_spec (const std::string & fpath, int leng) { std::string home = user_home(); std::string newhome = "~"; std::string newpath = fpath; if (contains(fpath, home)) newpath = newpath.replace(0, home.length(), newhome); std::size_t pathsize = newpath.size(); if (pathsize <= std::size_t(leng)) { return newpath; } else { std::string ellipse("..."); std::size_t halflength = (std::size_t(leng) - ellipse.size()) / 2 - 1; std::string result = newpath.substr(0, halflength); std::string lastpart = newpath.substr(pathsize - halflength - 1); result = result + ellipse + lastpart; return result; } } /** * Normalize as per the OS for which this module was built. * * \param path * Provides the path, which should be a full path-name. * * \param terminate * If true, tack on a final separator, if necessary. Defaults to false. * Do not set to true if you know the path ends with a file-name. * * \return * The possibly modified path is returned. */ std::string os_normalize_path (const std::string & path, bool terminate) { #if defined SEQ66_PLATFORM_UNIX bool to_unix = true; #else bool to_unix = false; #endif return normalize_path(path, to_unix, terminate); } /** * Makes sure the path is using the proper separators, and that a separator * appears at the end. The path is trimmed and normalized, but not * terminated. Compare to the clean_path() function. * * \param file * The file-name to fix. Assumed to be a file name. * * \param tounix * Use UNIX conventions for the path separator/terminator. Defaults to * true. * * \return * Returns the possibly-modified file-name. */ std::string clean_file (const std::string & file, bool to_unix) { std::string result = file; (void) trim(result, SEQ66_TRIM_CHARS_QUOTES); return normalize_path(result, to_unix, false); /* no added slash */ } /** * Makes sure the path is using the proper separators, and that a separator * appears at the end. The path is trimmed, normalized, and then properly * terminated. Compare to the clean_file() function. * * \param path * The path-name to fix. Assumed to be a directory name. * * \param tounix * Use UNIX conventions for the path separator/terminator. Defaults to * true. * * \return * Returns the possibly-modified path. */ std::string clean_path (const std::string & path, bool to_unix) { std::string result = path; (void) trim(result, SEQ66_TRIM_CHARS_QUOTES); return normalize_path(result, to_unix, true); /* an added slash */ } std::string append_file ( const std::string & path, const std::string & filename, bool to_unix ) { std::string result = path; if (! result.empty()) { (void) rtrim(result, SEQ66_TRIM_CHARS_PATHS); result += to_unix ? path_slash() : os_path_slash(); } result += filename; return normalize_path(result, to_unix, false); } /** * Concatenates paths. Used by playlist processing only at present. * Compare this function to pathname_concatenate(). * * The paths are first trimmed of white-space. The beginning path retains any * path characters (forward or backward slash it has at the start and end of the * path. If it doesn't have one at the end, one is added. * * The second path is stripped of any slash it has at the beginning, converting * it to a relative path. An ending slash is added, if necessary. */ std::string append_path ( const std::string & path, const std::string & pathname, bool to_unix ) { std::string result = path; std::string pn = pathname; char slash = to_unix ? path_slash() : os_path_slash() ; if (! result.empty()) { (void) trim(result); /* whitespace out */ auto spos = result.find_last_of(SEQ66_PATH_SLASHES); auto endindex = result.length() - 1; if (spos == std::string::npos || spos != endindex) result += slash; } if (! pn.empty()) { (void) trim(pn); (void) ltrim(pn, SEQ66_TRIM_CHARS_PATHS); auto spos = pn.find_last_of(SEQ66_PATH_SLASHES); auto endindex = pn.length() - 1; if (spos == std::string::npos || spos != endindex) pn += slash; } result += pn; return normalize_path(result, to_unix, true); } /** * Cleans up the path to make sure it is either valid or empty, and then * appends the base file-name (generally in the format "base" or * "base.extension") to it. * * This function works solely using UNIX conventions. * * If desired, it can be converted to Windows conventions using * os_normalize_path(). */ std::string filename_concatenate (const std::string & path, const std::string & filebase) { std::string result = clean_path(path); /* also adds end slash */ std::string base = filename_base(filebase); /* strip existing path */ result += base; return result; } std::string filename_concatenate ( const std::string & path, const std::string & base, const std::string & ext ) { std::string result = filename_concatenate(path, base); result = file_extension_set(result, ext); return result; } /** * This function concatenates two paths robustly, if not efficiently. * Compare this function to append_path(). * * \param path0 * The main path, which can be a root path or a relative path. It is * modified only to add a terminating slash, if necessary. * * \param path1 * The second path, which can only be a relative path (or a base * filename such as "x.ext"). If it starts with a slash, that is * removed. * * \return * Returns the two paths: path0 + "/" + path1 + "/", where the slashes * are added only if not already present. */ std::string pathname_concatenate (const std::string & path0, const std::string & path1) { std::string result = clean_path(path0); std::string cleanpath1 = clean_path(path1); if (cleanpath1[0] == '/') cleanpath1 = cleanpath1.erase(0, 1); result += cleanpath1; return result; } /** * This function is a kind of inverse of filename_concatenate(). Note that * it does not split out the file-extension. Also note that the results are * in UNIX convention. And there isn't any error checking for empty input or * results. * * \param fullpath * The putative full file-specification, including path and base-name. * Assumed to be trimmed of white-space on both ends. * * \param [out] path * The destination for the path portion of the full path. * * \param [out] filebase * The destination for the base-name (xxxx.yyy) portion of the full path. * * \return * Returns true if there was a path in the filename, as indicated by the * presence of a "/". False is returned otherwise, and the \a fullpath * argument is copied to the \a filebase destination parameter and the \a * path argument is cleared. */ bool filename_split ( const std::string & fullpath, std::string & path, std::string & filebase ) { std::string temp = normalize_path(fullpath); auto spos = temp.find_last_of("/"); bool result = spos != std::string::npos; path.clear(); filebase.clear(); if (result) { auto pos = spos + 1; path = temp.substr(0, pos); /* include slash */ filebase = temp.substr(pos, temp.length() - pos); } else filebase = fullpath; return result; } /** * This function also splits out the file extension. */ bool filename_split_ext ( const std::string & fullpath, std::string & path, /* can end up empty */ std::string & filebare, /* will not have extension */ std::string & ext /* can end up empty */ ) { std::string filebase; /* not filebare :-) */ bool result = filename_split(fullpath, path, filebase); bool ok = ! filebase.empty(); ext.clear(); if (ok) { auto hpos = filebase.find_first_of("."); auto ppos = filebase.find_last_of("."); if (hpos == ppos && hpos == 0) /* one dot at start */ { filebare = filebase; } else { if (ppos != std::string::npos) { filebare = filebase.substr(0, ppos); ext = filebase.substr(ppos, filebase.length() - ppos); } else filebare = filebase; } } return result; } /** * Replaces the path in a full file specification. This function is useful * in doing a "save-as" operation to a different destination. * * \param fullpath * The full file specification to be modified. * * \param newpath * Provides the replacement path. * * \return * Returns the modified file specification. */ std::string file_path_set (const std::string & fullpath, const std::string & newpath) { std::string path; std::string filebase; (void) filename_split(fullpath, path, filebase); return filename_concatenate(newpath, filebase); } /** * Replaces the file-name in a full file specification. This function is useful * in doing a "save-as" operation. * * \param fullpath * The full file specification to be modified. * * \param newfile * Provides the replacement file name. * * \return * Returns the modified file specification. */ std::string file_base_set (const std::string & fullpath, const std::string & newbase) { std::string path; std::string filebase; (void) filename_split(fullpath, path, filebase); return filename_concatenate(path, newbase); } /** * Uses filename_split to extract only the path in of the file * specification. * * \param fullpath * The full file-specification from which to extract. * * \return * Returns the path, if any. */ std::string filename_path (const std::string & fullpath) { std::string result; std::string base; (void) filename_split(fullpath, result, base); return result; } /** * Uses filename_split to extract only the base part of the file * specification ("xxxx.yyy"). * * \param fullpath * The full file-specification from which to extract. * * \param noext * If set to true (the default is false), then the extenstion (".yyy") is * also stripped. */ std::string filename_base (const std::string & fullpath, bool noext) { std::string result; std::string path; (void) filename_split(fullpath, path, result); if (noext) { auto dpos = result.find_last_of("."); bool ok = dpos != std::string::npos; if (ok) result = result.substr(0, dpos); } return result; } /** * Gets a file extension, defined simply as the text after the last period * in the path. * * We could use libmagic to determine the actual file type, but that is * probably overkill for our purposes. * * \param path * Provides the file name or the full path to the file. * * \return * Returns the file extension without the period. If there was no * period, then an empty string is returned. */ std::string file_extension (const std::string & path) { std::string result; auto ppos = path.find_last_of("."); if (ppos != std::string::npos) { auto len = path.length() - 2; result = path.substr(ppos + 1, len); } return result; } /** * This function makes sure that the file-extension of the given path is set * to the given extension parameter. Styles that need to be handled: * * - name or name.ext * - /dir0/dir1/name[.ext] * - /home/user/.config/seq66/qseq66[.ext] * - /dir0/dir1/dir2.xyz/name * - /dir0/dir1/name/ * * \param path * Provides the path-name, which can have an extension, or not. It can * also be a simple base filename, with no path. * * \param ext * Provides the desired extension. It must include the period, as in * ".ctrl". If this parameter is empty, and an extension exists, it is * stripped off. The default value is empty. * * \return * Returns a copy of the augmented (or extension-stripped) string. */ std::string file_extension_set (const std::string & path, const std::string & ext) { std::string result; if (! path.empty()) { std::string pathspec; std::string filebare; std::string extdummy; bool ok = filename_split_ext(path, pathspec, filebare, extdummy); if (ok) result += pathspec; result += filebare; result += ext; } return result; } /** * Sees if file-extensions match, case-insensitively. * * \param path * Provides the file name or the full path to the file. * * \param target * Provides the file extension to match against, with or without the * period. The period is removed before the check. * * \return * Returns true if the file extensions match. */ bool file_extension_match (const std::string & path, const std::string & target) { std::string tar = target; std::string ext = file_extension(path); /* path ext without period */ if (tar[0] == '.') { auto len = tar.length() - 1; tar = tar.substr(1, len); } return strcasecompare(ext, tar); } /** * Sets the current directory for the application. A wrapper replacement for * chdir() or _chdir(). It sets the current working directory in the * application. This function is necessary in order to make sure the current * working directory is a safe place to work. * * \return * Returns true if the path name is good, and the chdir() call succeeded. * Otherwise, false is returned. * * \param path * The full or relative name of the directory. */ bool set_current_directory (const std::string & path) { bool result = false; if (! path.empty()) { int rcode = S_CHDIR(path.c_str()); result = is_posix_success(rcode); if (! result) file_error("chdir() failed", path); } return result; } /** * An alternative on Linux to using either /proc/self/exe or argv[0] is using * the information passed by the ELF interpreter, made available by glibc. * The getauxval() function is a glibc extension; check so that it doesn't * return NULL (indicating that the ELF interpreter hasn't provided the * AT_EXECFN parameter). This is never actually a problem on Linux. */ std::string executable_full_path () { std::string result; #if defined PLATFORM_GLIBC /* TO DO!!!! */ const char * p = (const char *) getauxval(AT_EXECFN); if (not_nullptr(p)) { result = p; } #endif return result; } /** * Gets the user's $hOME (Linux) or $LOCALAPPDAT (Windows) directory from the * current environment. * * getenv("HOME"): * * - Linux returns "/home/ahlstrom". Append "/.config/seq66". * - Windows returns "\Users\ahlstrom". A better value than HOMEPATH * is LOCALAPPDATA, which gives us most of what we want: * "C:\Users\ahlstrom\AppData\Local", and then we append simply * "seq66". However, this inconsistency is annoying. So now * we provide separate functions for home versus the standard * configuration directory for a Windows or Linux user. * * \param appfolder * If not empty (the default) then the parameter is appended to the * path that is returned. * * \return * Returns the value of $HOME, such as "/home/user" or "C:\Users\user". * Notice the lack of a terminating path-slash. If std::getenv() fails, * an empty string is returned. The value is normalized to use the * UNIX path separator. */ std::string user_home (const std::string & appfolder) { std::string result; #if defined SEQ66_PLATFORM_WINDOWS char * env = std::getenv(SEQ66_ENV_HOMEDRIVE); if (not_nullptr(env)) { char * env2 = std::getenv(SEQ66_ENV_HOMEPATH); if (not_nullptr(env2)) { result += env; /* "C:" */ result += env2; /* "\Users\username" */ } } #else char * env = std::getenv(SEQ66_ENV_HOME); if (not_nullptr(env)) result = std::string(env); /* "/home/username" */ #endif if (result.empty()) { file_error("std::getenv() failed", "HOME"); } else { result = normalize_path(result); if (! appfolder.empty()) result = filename_concatenate(result, appfolder); } return result; } /** * Similar to user_home(), but tacks on the standard configuration directory * for Linux or Windows. Again, there is no slash at the end. * * \param appfolder * If not empty (the default) then the parameter is appended to the * path that is returned. * * \return * Returns the value of $HOME, such as "/home/user" or * "C:\Users\user\AppData\Local". Notice the lack of a terminating * path-slash. If std::getenv() fails, an empty string is returned. The * value is normalized to use the UNIX path separator. */ std::string user_config (const std::string & appfolder) { std::string result; #if defined SEQ66_PLATFORM_WINDOWS char * env = std::getenv(SEQ66_ENV_CONFIG); if (not_nullptr(env)) { result = env; /* C:\Users\username\AppData\Local */ result = normalize_path(result); } #else result = user_home(); if (! result.empty()) result = filename_concatenate(result, SEQ66_ENV_CONFIG); #endif if (result.empty()) file_error("std::getenv() failed", "CONFIG"); else if (! appfolder.empty()) result = filename_concatenate(result, appfolder); return result; } /** * Strips off the "HOME" parts of the user config directory for use * as the default "session" directory. For Linux, what is effectively * stripped for Linux is "/home/username/" which yields ".config". * For Windows it is "C:/Users/username/", which yields "AppData/Local". * * \param appfolder * If not empty, this value is concatenated to the result. * * \return * Returns the relative location of the "config" portion of the user's * home directory. */ std::string user_session (const std::string & appfolder) { #if defined SEQ66_PLATFORM_WINDOWS std::string result = user_config(); if (! result.empty()) { auto spos0 = result.find_first_of("/"); if (spos0 != std::string::npos) { auto spos1= result.find_first_of("/", spos0 + 1); if (spos1 != std::string::npos) { auto spos2= result.find_first_of("/", spos1 + 1); if (spos2 != std::string::npos) { result = result.substr(spos2 + 1); if (! appfolder.empty()) result = filename_concatenate(result, appfolder); } } } } #else std::string result = ".config"; if (! appfolder.empty()) result = filename_concatenate(result, appfolder); #endif return result; } /** * Given a list of potential directories, try to find a given file in them. * * \param dirlist * A list of directories to search. * * \param filename * The base name ("name.ext") of the file to find. * * \return * Returns the full file-specification to get the file, if found. * Otherwise, it returns an empty string. */ std::string find_file ( const tokenization & dirlist, const std::string & filename ) { std::string result; if (dirlist.size() > 0 && ! filename.empty()) { for (const auto & folder : dirlist) { if (folder.empty()) { break; } else { std::string fullspec = filename_concatenate(folder, filename); if (file_exists(fullspec)) { result = fullspec; break; } } } } return result; } #if defined SEQ66_HANDLE_FILE_WILDCARDS /** * This function uses glob(3) to obtain a list of all the files that * match the wild-path. * * \param wildpath * Provides a wild-card to search for. A simple one like "*.png" * will look for all PNG files in the current working directory * for the application. One with a path (for example, * "~/.config/seq66/ *.png") will search in that directory. * * \param [inout] filelist * Provides a string-vector for storing the results. * * \param append * Normally false, this value causes the file-list to be cleared * first. Use true to accumulate wildcard matches in the list. * * \return * Returns true if matches were found. */ bool get_wildcards ( const std::string & wildpath, tokenization & filelist, bool append ) { bool result = ! wildpath.empty(); if (result) { int flags = GLOB_ERR; #if defined SEQ66_PLATFORM_LINUX flags |= GLOB_TILDE; #endif glob_t g; int rc = glob(wildpath.c_str(), flags, nullptr, &g); if (rc != 0) { result = false; } else { if (! append) filelist.clear(); for (std::size_t i = 0; i < g.gl_pathc; ++i) filelist.push_back(g.gl_pathv[i]); } globfree(&g); } return result; } #endif // defined SEQ66_HANDLE_FILE_WILDCARDS /** * Copies a list of files to a directory, which must exist. */ bool file_list_copy ( const std::string & destpath, const tokenization & filelist ) { int count = 0; bool ok = file_exists(destpath); if (ok) { for (auto & f : filelist) { ok = file_copy_to_path(f, destpath); if (ok) ++count; else break; } } return count == int(filelist.size()); } #if defined SEQ66_USE_PRIMITIVE_WILDCARD_MATCH /** * Partial replacement for glob(3), meant to be used with Windows ultimately. */ static int chmatch (const std::string & target, const std::string & pat); /* * Wildcard matcher. * * https://comp.lang.c.narkive.com/ZxNTzMdM/globing-on-windows-in-c-c-language * * Post by Vipin * * Following code works well on UNIX but there is no or * equivalent on windows in my opinion. * * Please share the code if anybody has written already. This took quite * a bit of getting right. The code was reviewed on comp.lang.c a * couple of months ago. * * Notes: * * - ? = match any character * - * = match zero or more characters * - [?], [*] are escapes for these characters, * - [abc] matches a, b or c. * - [A-Z] [0-9] [*-x], match range. * - [[] - match '['. * - [][abc] match ], [, a, b or c * * \param target * The target string to try to match. * * \param pattern * The pattern string to match. * * \return * 1 if match, 0 if not. */ bool wildcard_match (const std::string & target, const std::string & pattern) { std::string::size_type targetpos = 0; std::string::size_type patternpos = 0; int gobble; do { std::string tar = target.substr(targetpos); std::string pat = pattern.substr(patternpos); gobble = chmatch(tar, pat); patternpos += gobble; ++targetpos; } while (gobble > 0); if (target[targetpos] == 0 && pattern[patternpos] == 0) { return true; } else if (pattern[patternpos] == '*') { while (pattern[patternpos + 1] == '*') ++patternpos; if (pattern[patternpos + 1] == 0) return 1; while (target[targetpos] != 0) { std::string tar = target.substr(targetpos); std::string pat = pattern.substr(patternpos + 1); if (quickglob(tar, pat)) return true; ++targetpos; } } return false; } /** * Match a character. * * Kenneth Brody at the URL above. * * Notes: * * means that a * in pat will return zero * * \param target * The target string * * \param pat * The pattern string. * * Returns * The number of pat character matched. */ static int chmatch (const std::string & target, const std::string & pat) { /* * Treat close bracket following open bracket as a character. */ std::string::size_type patpos = 0; std::string::size_type ender = pat.find_first_of("]"); if (pat[patpos] == '[' && ender != std::string::npos) /* not_npos(ender) */ { if (ender == (patpos + 1)) { /* * Make "[]" with no close mismatch all. */ ender = pat.find_first_of("]", 2); if (ender == std::string::npos) /* is_npos(ender) */ return 0; } /* * Allow [A-Z] and similar syntax. */ bool fourback = (ender - patpos) == 4; bool isrange = pat[2] == '-'; bool valid = pat[1] <= pat[3]; if (fourback && isrange && valid) { if (target[0] >= pat[1] && target[0] <= pat[3]) return 5; else return 0; } /* * Search for the character list contained within brackets */ std::string::size_type pos = pat.find_first_of(target[0], 1); if (pos != std::string::npos && pos < ender) return ender + 1; else return 0; } if (pat[0] == '?' && target[0] != 0) return 1; if (pat[0] == '*') return 0; if (target[0] == 0 || pat[0] == 0) return 0; if (target[0] == pat[0]) return 1; return 0; } #endif // defined SEQ66_USE_PRIMITIVE_WILDCARD_MATCH } // namespace seq66 /* * filefunctions.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/util/named_bools.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file named_bools.cpp * * This module declares/defines items for an abstract representation of the * color of a sequence or panel item. Colors are, of course, part of using a * GUI, but here we are not tied to a GUI. * * \library seq66 application * \author Chris Ahlstrom * \date 2021-09-13 * \updates 2021-09-13 * \license GNU GPLv2 or above * * This module is inspired by MidiPerformance::getSequenceColor() in * Kepler34. */ #include "util/named_bools.hpp" /* seq66::named_bools class */ namespace seq66 { /* * All code is in the header file. */ } /* * named_bools.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/util/palette.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file palette.cpp * * This module declares/defines items for an abstract representation of the * color of a sequence or panel item. Colors are, of course, part of using a * GUI, but here we are not tied to a GUI. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-02-20 * \updates 2019-01-22 * \license GNU GPLv2 or above * * This module is inspired by MidiPerformance::getSequenceColor() in * Kepler34. */ #include "util/palette.hpp" /* seq66::palette template class */ namespace seq66 { /* * All code is in the template header file. */ } /* * palette.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/util/recmutex.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file recmutex.cpp * * This module declares/defines the base class for mutexes. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2024-01-16 * \license GNU GPLv2 or above * * Seq66 needs a mutex for sequencer operations. We have finally, after a * week of monkeying around with std::condition_variable, which requires * std::mutex, decided to stick with the old pthreads implementation for now. */ #include "util/recmutex.hpp" /* seq66::recmutex */ /** * FreeBSD pthreads is different from the others. * * Using pthread_mutex_init() and pthread_mutex_destroy() seems to hang * up automutex usage. The mutex gets destroyed in the first thread in * which it goes out of scope.` */ #if defined SEQ66_PLATFORM_FREEBSD /* TODO */ #else #define SEQ66_USE_MUTEX_INITIALIZER #if defined SEQ66_PLATFORM_MING_OR_WINDOWS /** * Using the init()/destroy() paradigm with an automutex seems to hang * things up, so we define this macro to do it the old way. * * The MingW compiler (at least on Windows) and the Microsoft Visual Studio * compiler do not support the pthread PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP * macro. * * Currently, we do not use the CRITICAL_SECTION or alternate threads API. * Kept here for reference */ #define MUTEX_INITIALIZER PTHREAD_RECURSIVE_MUTEX_INITIALIZER #else #define MUTEX_INITIALIZER PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP #endif #endif // FreeBSD namespace seq66 { /** * Define the static recursive mutex and its condition variable. */ recmutex::native recmutex::sm_global_mutex; #if defined USE_GLOBAL_MUTEX /** * We need a static flag to guarantee that the static native mutex object * is initialized. */ void recmutex::init_global_mutex () { static bool s_global_mutex_initialized = false; if (! s_global_mutex_initialized) { s_global_mutex_initialized = true; recmutex::sm_global_mutex = MUTEX_INITIALIZER; } } #endif /** * Constructor for recmutex. */ recmutex::recmutex () : #if defined SEQ66_PLATFORM_FREEBSD m_mutex_attributes (), /* uninit'd pthread_mutexattr_t */ #endif m_mutex_lock () /* uninitialized pthread_mutex_t */ { #if defined USE_GLOBAL_MUTEX init_global_mutex(); /* might not need global mutex, tho */ #endif init(); /* m_mutex_lock = MUTEX_INITIALIZER */ } /** * Copy constructor. It doesn't make any sense to copy a mutex (and, in fact, * std::mutex is Noncopyable and Nonmoveable. * * However, there are objects that use a mutex and should still be copyable. * So rather than defining a default copy constructor or deleting it, we * make one that creates a new mutex. We do not need to call the static * function init_global_mutex(). We could call it, but nothing would happen. */ recmutex::recmutex (const recmutex & /* rhs */ ) : m_mutex_lock () { init(); } /** * Similarly, we want to support the principal assignment operator. */ recmutex & recmutex::operator = (const recmutex & rhs) { if (this != & rhs) { #if defined USE_GLOBAL_MUTEX init_global_mutex(); #endif init(); } return *this; } recmutex::~recmutex () { destroy(); } /** * Locks the recmutex. */ void recmutex::lock () const { (void) pthread_mutex_lock(&m_mutex_lock); } /** * Unlocks the recmutex. */ void recmutex::unlock () const { (void) pthread_mutex_unlock(&m_mutex_lock); } /** * FreeBSD prthreads is different from the others. */ #if defined SEQ66_PLATFORM_FREEBSD void recmutex::init () { int rc = pthread_mutexattr_init(&m_mutex_attributes); if (rc == 0) { rc = pthread_mutexattr_settype ( &m_mutex_attributes, PTHREAD_MUTEX_RECURSIVE ); if (rc == 0) { rc = pthread_mutex_init(&m_mutex_lock, &m_mutex_attributes); } } if (rc != 0) { /* TODO */ } } void recmutex::destroy () { int rc = pthread_mutex_unlock(&m_mutex_lock); if (rc == 0) { rc = pthread_mutex_destroy(&m_mutex_lock); if (rc == 0) rc = pthread_mutexattr_destroy(&m_mutex_attributes); } if (rc != 0) { /* TODO */ } } #else /** * Initializes the recmutex. * * From https://pages.cs.wisc.edu/~remzi/OSTEP/ * "Operating Systems: Three Easy Pieces": * * With POSIX threads, there are two ways to initialize locks. One way to do * this is to use PTHREAD_MUTEX_INITIALIZER, as follows: pthread_mutex_t lock * = PTHREAD_MUTEX_INITIALIZER; * * Doing so sets the lock to the default values and thus makes the lock * usable. The dynamic way to do it (i.e., at run time) is to make a call to * pthread_mutex_init() as noted below. * * The first argument to this routine is the address of the lock itself, * whereas the second is an optional set of attributes. Read more about the * attributes yourself; passing NULL in simply uses the defaults. Either way * works, but we usually use the dynamic (latter) method. */ void recmutex::init () { #if defined SEQ66_USE_MUTEX_INITIALIZER m_mutex_lock = MUTEX_INITIALIZER; #else int rc = pthread_mutex_init(&m_mutex_lock, NULL); if (rc != 0) { // what to do? } #endif } void recmutex::destroy () { #if ! defined SEQ66_USE_MUTEX_INITIALIZER pthread_mutex_unlock(&m_mutex_lock); #endif } #endif // FreeBSD } // namespace seq66 /* * recmutex.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/util/rect.cpp ================================================ /* * This file is part of seq66. * * seq24 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. * * seq24 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 seq24; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file rect.cpp * * This module declares/defines the concrete class for a rectangle. * * \library seq66 application * \author Seq24 team; modifications by Chris Ahlstrom * \date 2017-09-16 * \updates 2020-11-25 * \license GNU GPLv2 or above * */ #include "util/rect.hpp" /* seq66::rect */ namespace seq66 { /** * \defaultctor */ rect::rect () : m_x (0), /* also known as x0() */ m_y (0), /* also known as y0() */ m_width (0), /* used to calculate x1() */ m_height (0) /* used to calculate y1() */ { // Empty body } /** * Principal constructor. * * \param x * The x coordinate. * * \param y * The y coordinate. * * \param width * The width value. * * \param height * The width value. */ rect::rect (int x, int y, int width, int height) : m_x (x), m_y (y), m_width (width), m_height (height) { // Empty body } /** * Gets the rectangle values for primitive callers that don't store them as * a rectangle. * * \param [out] x0 * The destination x coordinate. * * \param [out] y0 * The destination y coordinate. * * \param [out] width * The destination width value. * * \param [out] height * The destination width value. */ void rect::get (int & x, int & y, int & width, int & height) const { x = m_x; y = m_y; width = m_width; height = m_height; } void rect::get_coordinates (int & x0, int & y0, int & x1, int & y1) const { x0 = m_x; y0 = m_y; x1 = x0 + m_width; y1 = y0 + m_height; } /** * Sets all of the members of the rectangle directly. * * \param x * The x coordinate. * * \param y * The y coordinate. * * \param width * The width value. * * \param height * The width value. */ void rect::set (int x, int y, int width, int height) { m_x = x; m_y = y; m_width = width; m_height = height; } void rect::set_coordinates (int x0, int y0, int x1, int y1) { m_x = x0; m_y = y0; m_width = x1 - x0; m_height = y1 - y0; } /** * Converts rectangle corner coordinates to a rect object, which includes * width and height. * * \param x0 * The x value of the first corner. * * \param y0 * The y value of the first corner. * * \param x1 * The x value of the second corner. * * \param y1 * The y value of the second corner. * * \param [out] r * The destination for the coordinate, width, and height. */ void rect::xy_to_rect (int x0, int y0, int x1, int y1, rect & r) { if (x0 < x1) { r.m_x = x0; r.m_width = x1 - x0; } else { r.m_x = x1; r.m_width = x0 - x1; } if (y0 < y1) { r.m_y = y0; r.m_height = y1 - y0; } else { r.m_y = y1; r.m_height = y0 - y1; } } /** * Converts rectangle corner coordinates to a starting coordinate, plus a * width and height. This function checks the mins / maxes, and then fills * in the x, y, width, and height values. It picks the lowest x and y * coordinate to use as the corner coordinate, so that the width and height * are always positive. * * \param x0 * The x value of the first corner. * * \param y0 * The y value of the first corner. * * \param x1 * The x value of the second corner. * * \param y1 * The y value of the second corner. * * \param [out] x * The destination for the x value in pixels. * * \param [out] y * The destination for the y value in pixels. * * \param [out] w * The destination for the rectangle width in pixels. * * \param [out] h * The destination for the rectangle height value in pixels. */ void rect::xy_to_rect_get ( int x0, int y0, int x1, int y1, int & x, int & y, int & w, int & h ) { if (x0 < x1) { x = x0; w = x1 - x0; } else { x = x1; w = x0 - x1; } if (y0 < y1) { y = y0; h = y1 - y0; } else { y = y1; h = y0 - y1; } } } // namespace seq66 /* * rect.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/util/ring_buffer.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file ring_buffer.cpp * * This module defines our own ringbuffer that support objects, not just * characters. * * \library seq66 application * \author Chris Ahlstrom * \date 2022-09-19 * \updates 2023-12-08 * \license GNU GPLv2 or above * * A lock-free ring buffer. * * (C) Copyright 2000 Paul Davis * (C) Copyright 2003 Rohan Drape * (C) Copyright 2002 Fred Gleason * * $Id: ringbuffer.h,v 1.1 2007/12/19 20:22:23 fredg Exp $ * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Library General Public License version 2 as * published by the Free Software Foundation. * * 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., * 675 Mass Ave, Cambridge, MA 02139, USA. * * Adapted from code by Paul Davis and Rohan Drape in * 'example-clients/ringbuffer.ch' in the Jack Audio Connection Kit. * * https://accu.org/journals/overload/10/50/goodliffe_389/ * * https://codereview.stackexchange.com/questions/208293/ * ring-buffer-implementation-in-c14 * * The ring_buffer is a FIFO (first-in/first-out), where one adds data to the * back of the buffer and consumes data from the front. * \verbatim Start 0-1-2-3-4-5-6.--------buffer --------------------32> End B U F F E R R I N G _ ---------------------------------------------------- wrap-around --> | data | w r i t e s p a c e | data... | ---- | ---------------------------------------------------- | | ^ ^ next item ^ | | | | goes here | | | | | | | ^ front() back() front() v | "head" "tail" "head" later | | | -------------------------------<------------------------------- \endverbatim * * - At start, tail = 0, head = 0. Unlike the original implementation, * we don't set head to 1, in order to support an empty buffer. * - New data is added at the back of the circular buffer via * push_back(). This increments the tail, adding to the number of * elements in the ring_buffer. The first item pushed goes to * slot 0, not slot 1, in this implementation. * - The buffer starts at the front, and one reads from there. * This decrements the head. * - At the end of the array, we wrap around to the start. * * This implementation: * * - Encodes whole objects, not characters. * - Provides insertion to the back of the container and direct * access to the back. * - Provides access to the front of the container to get that * object. * - Provides a front() function to inspect the object. If worried * about the usability of the result, then use the read() function * and test the result for a value greater than 0. * - Provides a pop_front() to remove the front object. * - Currently does not handle TYPE = char as strings, just single * characters. * * Recommendations for TYPE: * * - Provide a boolean or integer that indicates the retrieved item * is not usable. For example, a count() of -1 or a boolean called * usable(). The ring_buffer template does not enforce this. */ #include /* std::string */ #include "util/ring_buffer.hpp" #if defined SEQ66_PLATFORM_DEBUG #include #endif namespace seq66 { #if defined SEQ66_PLATFORM_DEBUG class ring_test { public: using cref = /* const */ ring_buffer::reference; private: int m_test_counter; std::string m_test_text; public: ring_test (int counter = (-1), const std::string & text = ""); ring_test (const ring_test & rhs) = default; ring_test & operator = (const ring_test & rhs) = default; ~ring_test () = default; void increment () { ++m_test_counter; } int test_counter () const { return m_test_counter; } void set_test_text (const std::string & t) { m_test_text = t; } const std::string & test_text () const { return m_test_text; } std::string to_string (); }; // class ring_test static void show_message (const std::string & msg) { std::cout << msg << std::endl; } static void show_error (const std::string & msg) { std::cerr << msg << std::endl; } static bool item_test (ring_test::cref item, const std::string & tag, int counter) { std::string values = item.to_string(); int c = item.test_counter(); std::cout << "Item test " << tag << " " << values << std::endl; bool result = c == counter; if (! result) std::cerr << "'" << tag << "' test failed" << std::endl; return result; } ring_test::ring_test (int counter, const std::string & text) : m_test_counter (counter), m_test_text (text) { // No code } std::string ring_test::to_string () { std::string result = "counter " + std::to_string(test_counter()) + "; "; result += "text '" + test_text() + "'."; return result; } bool run_ring_test () { bool result = true; ring_test rt_a(1, "rt_a"); ring_test rt_b(2, "rt_b"); ring_test rt_c(3, "rt_c"); ring_test rt_d(4, "rt_d"); ring_test rt_e(5, "rt_e"); ring_test rt_f(6, "rt_f"); ring_test rt_g(7, "rt_g"); ring_test rt_h(8, "rt_h"); ring_test rt_i(9, "rt_i"); ring_test rt_j(10, "rt_j"); /* * Smoke test */ ring_buffer rb(7); /* should become 8 (power of 2) */ std::size_t sz = rb.write(rt_a); if (sz != 1) { show_error("ring_buffer::write() failed"); result = false; } else { ring_test rt; sz = rb.read(rt); if (sz > 0) { show_error("ring_buffer::read() failed"); result = false; } else { std::string ss = rt.to_string(); std::cout << "Read test object '" << ss << "'" << std::endl; if (rb.count() > 0) { show_error("read() failed to pop the object"); result = false; } } } /* * Full buffer test. Ultimately 10 items entered. Only 8 should remain. * Then we pop all items in the ring_buffer and show them. * (Should compare counters at some point.) */ if (result) { rb.clear(); if (! rb.empty()) { show_error("ring_buffer not empty"); result = false; } } if (result) { std::size_t space = rb.read_space(); if (space == 0) { rb.push_back(rt_a); rb.push_back(rt_b); rb.push_back(rt_c); rb.push_back(rt_d); rb.push_back(rt_e); rb.push_back(rt_f); rb.push_back(rt_g); rb.push_back(rt_h); space = rb.read_space(); if (rb.count() != 8 || space != 8) { show_error("ring_buffer count mismatch"); result = false; } if (result) { space = rb.write_space(); if (space > 0) { show_error("write space > 0"); result = false; } } } else { show_error("empty read-space error"); result = false; } } if (result) { /* * Here, rt_a and rt_b should be dropped. */ rb.push_back(rt_i); rb.push_back(rt_j); std::size_t rspace = rb.read_space(); std::size_t wspace = rb.write_space(); if (rb.count() != 8 || rspace != 8 || wspace != 0) { show_error("objects not overwritten"); result = false; } if (rb.dropped() != 2) { show_error("unexpected number of dropped items"); result = false; } if (result) { int imax = rb.count(); /* can't use count() in loop */ for (int i = 0; i < imax; ++i) { ring_test::cref item = rb.front(); std::string values = item.to_string(); printf("[%d] %s\n", i, values.c_str()); if (item.test_counter() != (i + 3)) result = false; rb.pop_front(); } if (! result) show_message("Item-counter mismatch detected"); if (rb.empty()) { show_message("Should see rt_c through rt_j values"); } else { show_error("ringbuffer still has items!"); result = false; } } } /* * Alternative push/pops with access via front(). Note that back() * goes back one step from the tail in order to (hopefully) get a valid * object. */ if (result) { rb.clear(); ring_test::cref fitem = rb.front(); ring_test::cref bitem = rb.back(); result = item_test(fitem, "front", (-1)); /* usability test */ result = item_test(bitem, "back", (-1)); /* usability test */ rb.push_back(rt_a); /* front (1) */ rb.push_back(rt_b); rb.push_back(rt_c); rb.push_back(rt_d); /* back (4) */ result = rb.count() == 4; if (result) { ring_test::cref frontitem = rb.front(); result = item_test(frontitem, "[front]", 1); if (result) { ring_test::cref backitem = rb.back(); result = item_test(backitem, "[back] ", 4); } if (result) { rb.pop_front(); } else show_error("First front call failed"); } else show_error("Bad ring_buffer count"); } /* * End of tests. */ return result; } #endif } // namespace seq66 /* * ring_buffer.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libseq66/src/util/strfunctions.cpp ================================================ /* * This file is part of seq66. * * seq24 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. * * seq24 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 seq24; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file strfunctions.cpp * * Provides the implementations for safe replacements for the various C * file functions. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-24 * \updates 2025-02-20 * \version $Revision$ * * We basically include only the functions we need for Seq66, not * much more than that. These functions are adapted from our xpc_basic * project. */ #include /* std::find_if() function */ #include /* std::toupper() function */ #include /* INT_MAX and its ilk */ #include /* std::floor(), std::pow() */ #include /* std::memcmp() function */ #include /* std::invalid_argument */ #include "util/strfunctions.hpp" /* free functions in seq66 n'space */ #if defined SEQ66_PLATFORM_WINDOWS #include /* ::MultiByteToWideChar() */ #endif namespace seq66 { /** * Returns double quotes as a string. */ const std::string & double_quotes () { static const std::string s_double_quotes = std::string("\"\""); return s_double_quotes; } /** * Returns "" as a string for external callers. */ std::string empty_string () { return double_quotes(); } /** * Provides a way to work with a visible empty string (e.g. in a * configuration file). This function returns true if the string really is * empty, or just contains two double quotes (""). * See the add_quotes() and double_quotes() functions. */ bool is_empty_string (const std::string & item) { return item.empty() || item == double_quotes(); } const std::string & questionable_string () { static const std::string s_question_mark = std::string("?"); return s_question_mark; } bool is_questionable_string (const std::string & item) { return item == questionable_string(); } bool is_missing_string (const std::string & item) { return is_empty_string(item) || is_questionable_string(item); } /** * Indicates if one string can be found within another. Doesn't force the * caller to use size_type. */ bool contains (const std::string & original, const std::string & target) { auto pos = original.find(target); return pos != std::string::npos; } /** * Strips the comments from a string. The algorithm moves to the "#" * character, backs up to the first non-space before that character, and * removes all characters after that character. We also want to be able to * have "#" in quotes be preserved. * * \param item * Provides the string to be comment-stripped. * * \return * Returns the string data from the first non-space to the last * non-space before the hash-tag. */ std::string strip_comments (const std::string & item) { std::string result = item; auto hashpos = result.find_first_of("#"); auto qpos = result.find_first_of("\"'"); if (qpos != std::string::npos) { char quotechar[2] = { 'x', 0 }; quotechar[0] = result[qpos]; auto qpos2 = result.find_first_of(quotechar, qpos + 1); if (qpos2 != std::string::npos) { if (hashpos > qpos2) result = result.substr(0, hashpos); } else result = result.substr(0, hashpos); } else { if (hashpos != std::string::npos) result = result.substr(0, hashpos); } result = trim(result, SEQ66_TRIM_CHARS); return result; } /** * Gets the next quoted string. */ std::string next_quoted_string (const std::string & source, std::string::size_type pos) { std::string result; auto lpos = source.find_first_of(double_quotes(), pos); if (lpos != std::string::npos) { auto rpos = source.find_first_of(double_quotes(), lpos + 1); if (rpos != std::string::npos) { size_t len = size_t(rpos - lpos - 1); if (len > 0) result = source.substr(lpos + 1, len); } } return result; } /** * Gets the next bracket string. It looks for sqaure brackets instead of * double quotes, and it trims space at each end of the string. */ std::string next_bracketed_string ( const std::string & source, std::string::size_type pos ) { std::string result; auto lpos = source.find_first_of("[", pos); if (lpos != std::string::npos) { auto rpos = source.find_first_of("]", lpos + 1); if (rpos != std::string::npos) { size_t len = size_t(rpos - lpos - 1); if (len > 0) { result = trim(source.substr(lpos + 1, len)); } } } return result; } /** * Strips single- or double-quotes from a string. Meant mainly for removing * quotes around a file-name, so it works only if the first character is a * quote, and the last character is a quote. * * \param item * The string to be massaged. * * \return * The string without quotes. If it didn't have any, the string * should be unchanged. */ std::string strip_quotes (const std::string & item) { std::string result; if (! item.empty()) { result = item; auto fpos = result.find_first_of("\""); if (fpos == 0) { auto lpos = result.find_last_of("\""); auto end_index = result.length() - 1; if (lpos != std::string::npos && lpos == end_index) result = result.substr(1, end_index - 1); } else { fpos = result.find_first_of("'"); if (fpos == 0) { auto lpos = result.find_last_of("'"); auto end_index = result.length() - 1; if (lpos != std::string::npos && lpos == end_index) result = result.substr(1, end_index - 1); } } } return result; } /** * Wraps a string in double-quotes. It works only if the first character is * not a quote, and the last character is not a quote. * * \param item * The string to be massaged. * * \return * The string with double-quotes. If it already had them, the string * should be unchanged. If the string was empty, a string consisting of * two double-quotes is returned. */ std::string add_quotes (const std::string & item) { std::string result = item; if (result.empty()) { result = double_quotes(); } else { bool quoted = false; auto pos0 = result.find_first_of("\""); auto pos1 = result.find_last_of("\""); if (pos0 != std::string::npos && pos1 != std::string::npos) { if (pos1 != pos0) quoted = pos0 == 0 && pos1 == result.length() - 1; } if (! quoted) result = "\"" + item + "\""; } return result; } /** * Compares two strings for equality up to the given length. Unlike * std::string::compare(), it returns only a boolean. Meant to be a simpler * alternative, and analogous to strncmp(). The comparison function was * std::string::compare(), but it's semantics don't quite work here. * * We want to see if the "comparing" string matches the "compared" string up * to n characters or to the full length of the compared string. * * \param a * Provides the "compared" string. False is returned if it is empty. It * is the "target", the string whose contents we want to match. It is * generally the shorter of the two strings, but no promises. * * \param b * Provides the "comparing" string. False is returned if it is empty. It * is the "source" string, the string whose contents we want to determine * if it matches the "target" string. * * \param n * Provides the number of characters in the "comparing" string (parameter * \a a) that must match. If equal to 0 (the default value), then the * minimum of the lengths of a and b is used. * * \return * Returns true if the strings compare identically for the first \a n * characters, but see \a n above. Returns false otherwise, including * when n > a.length(). */ bool strncompare (const std::string & a, const std::string & b, size_t n) { bool result = ! a.empty() && ! b.empty(); if (result) { if (n == 0) n = std::min(a.length(), b.length()); if (n <= a.length() && b.length() >= n) { #if defined USE_SLOW_CODE for (size_t i = 0; i < n; ++i) { if (a[i] != b[i]) { result = false; break; } } #else result = std::memcmp(a.data(), b.data(), n) == 0; #endif } else result = false; } return result; } /** * Performs a case-insensitive comparison of two characters. * * \return * Returns true if the characters match via the std::toupper() call. */ static inline bool casecompare (char a, char b) { return ( std::toupper(static_cast(a)) == std::toupper(static_cast(b)) ); } /** * Performs a case-insensitive comparison of two strings. * * \return * Returns true if the strings match via the casecompare() call. The * strings must be the same length. */ bool strcasecompare (const std::string & a, const std::string & b) { return ( (a.size() == b.size()) && std::equal(a.begin(), a.end(), b.begin(), casecompare) ); } /* * There are implementations of these functions using the std::find_if() * algorithm, but the implementations here seem simpler and more flexible. */ /** * Left-trims a set of characters from the string. * * \param str * The prospective string to be trimmed. It must be a reference, and * must be non-const. * * \param chars * The set of characters to be trimmed. Defaults to SEQ66_TRIM_CHARS * (" \t\n\v\f\r"). Another macro available is SEQ66_TRIM_CHARS_QUOTES * (" \t\n\v\f\r\"'") which adds the double- and single-quote * characters. * * \return * Returns a reference to the string that was left-trimmed. */ std::string & ltrim (std::string & str, const std::string & chars) { str.erase(0, str.find_first_not_of(chars)); return str; } /** * Right-trims a set of characters from the string. Similar to the ltrim() * function. * * \param str * The prospective string to be trimmed. It must be a reference, and * must be non-const. * * \param chars * The set of characters to be trimmed. * * \return * Returns a reference to the string that was right-trimmed. */ std::string & rtrim (std::string & str, const std::string & chars) { str.erase(str.find_last_not_of(chars) + 1); return str; } /** * Left- and right-trims a set of characters from the string. Similar to the * ltrim() and rtrim() functions combined. * * \param str * The prospective string to be trimmed. It must be a reference, and * must be non-const. * * \param chars * The set of characters to be trimmed. The default is SEQ66_TRIM_CHARS, * which is white-space characters. * * \return * Returns a reference to the string that was left-right-trimmed. */ std::string trim (const std::string & str, const std::string & chars) { std::string result = str; (void) ltrim(rtrim(result, chars), chars); return result; } /** * Replaces the first n occurrences of a sub-string with the specified string. * * \param source * Provides the original string to be modified. * * \param target * Provides the sub-string to be replaced. * * \param replacement * Provides the replacement for the substring. * * \param n * Indicates how many occurrences to replace. The default value is -1, * which means to replace all of them. * * \return * Returns the modified copy of the string, with the replacement made. * If the target string was not found in the source, the source string * should be unchanged. */ std::string string_replace ( const std::string & source, const std::string & target, const std::string & replacement, int n ) { std::string result = source; auto targetsize = target.size(); auto targetloc = result.find(target); while (targetloc != std::string::npos) { (void) result.replace(targetloc, targetsize, replacement); targetloc = result.find(target); if (n > 0) { --n; if (n == 0) break; } } return result; } #if defined USE_HAS_DIGIT /* we now use try/catch */ static bool has_digit (const std::string & s, bool floating = false) { bool result = false; if (! s.empty()) { int count = 0; for (const auto c : s) { if (c == '-' || c == '+') { if (count++ == 0) continue; /* skip leading sign character */ else break; /* end the search */ } if (floating && c == '.') { if (count++ == 0) continue; /* skip leading decimal point */ else break; /* end the search */ } if (std::isdigit(c)) { result = true; break; } else if (std::isspace(c)) { count = 0; continue; } else break; } } return result; } #endif /** * A useful function for the midi_bytes-to-from-string functions below. * * \param c * Provides a hexadecimal digit. This must be a number or a lower-case * hex digit. * * \return * Returns the value of the hexadecimal digit. If not a hex digit, * then (-1) is returned. */ int hex_digit (char c) { static std::string s_hex_digits = "0123456789abcdef"; int result = (-1); auto pos = s_hex_digits.find_first_of(c); if (pos != std::string::npos) result = int(pos); return result; } /** * This pair of functions work to make sure that all ASCII characters in * the string are restricted to the range of 0 to 127. This is done by * encoding characters of value 128 to 255 in the format "\xx" where "xx" is * two hexadecimal digits. For simplicity the x's are always lower-case. * They are also zero-padded to two bytes. * * \param s * Provides the string to be "midi-ized". * * \param limit * Provides the maximum number of characters allowed in the converted * string. Defaults to 0, which means "no limit", i.e. a very large * value. */ std::string string_to_midi_bytes (const std::string & s, size_t limit) { int maximum = limit == 0 ? INT_MAX : int(limit) ; std::string result; for (const auto c : s) { unsigned char b = (unsigned char) (c); /* if bigger than 127 */ if (b > 127) { if (maximum >= 3) { char tmp[4]; int count = snprintf(tmp, sizeof tmp, "\\%02x", b); maximum -= count; result += tmp; } else break; } else { result.push_back(c); if (--maximum == 0) break; } } return result; } std::string midi_bytes_to_string (const std::string & s) { auto bslashpos = s.find_first_of("\\"); if (bslashpos != std::string::npos) { std::string result; bool slashed = false; /* * int sum = 0; */ int hexcount = 0; for (const auto c : s) { if (slashed) { int value = hex_digit(c); if (value >= 0) { ++hexcount; if (hexcount == 1) { /* * sum += value * 16; */ } else if (hexcount == 2) { result.push_back(c); slashed = false; hexcount = 0; } } else { result.push_back(c); slashed = false; hexcount = 0; } } else { if (c == '\\') slashed = true; else result.push_back(c); } } return result; } else return s; } /** * Converts a string value to a boolean. Note that an empty string returns * the default value, which is false if the caller does not supply a default * parameter. * * \param s * The string representing the value. "1", "true", "on", and "yes" are * true, all other values are false. Capitalized versions checked. * * \param defalt * The desired default for an empty string. The default \a defalt value * is false. */ bool string_to_bool (const std::string & s, bool defalt) { return s.empty() ? defalt : ( s == "1" || s == "true" || s == "on" || s == "yes" ); } /** * Attempt to convert a string to a pair of integers, based on the * presence of a pair delimiter. * * \param s * The string to parse. * * \param [out] v1 * The first value of the pair. * * \param [out] v2 * The second value of the pair. * * \param delimiter * Specifies the pair delimiter. There is no default value, but "/", * ",", ":", or even " " might be common values. * * \return * Returns true if the delimiter was found, and there are two numbers * to convert. */ bool string_to_int_pair ( const std::string & s, int & v1, int & v2, const std::string & delimiter ) { bool result = s.find_first_of(delimiter) != std::string::npos; if (result) { tokenization numbers = tokenize(s, delimiter); result = numbers.size() == 2; if (result) { result = std::isdigit(numbers[0][0]) && std::isdigit(numbers[1][0]); if (result) { v1 = string_to_int(numbers[0]); v2 = string_to_int(numbers[1]); } } } return result; } bool string_to_time_signature (const std::string & s, int & beats, int & width) { return string_to_int_pair(s, beats, width, "/"); } std::string time_signature_string (int beats, int width) { std::string result; if (beats > 0 && width > 0) /* could also test for <= 32 */ { char temp[32]; (void) snprintf(temp, sizeof temp, "%d/%d", beats, width); result = temp; } return result; } /** * Converts a string to a double value. This function bypasses characters * until it finds a digit (whether part of the number or a "0x" construct), * and then converts it. The strtol() function is used with a base of 0 so * that decimal, hexadecimal, and octal values can all be parsed. * * This function is the base implementation for string_to_int() as well. * * \param s * Provides the string to convert to a double value. Integers, numbers * with a decimal point, and simple, but strictly formatted, fractions * (e.g. "1/4") are supported. * * \param defalt * The desired default for an empty string. The default \a defalt value * is 0.0. * * \param rounding * If not 0 (the default), then round the value to the given number of * decimal places. This prevents values with messy extra digits past the * decimal. * * \return * Returns the double value represented by the string. * If the string is empty or has no digits, then the defalt * parameter is returned. */ double string_to_double (const std::string & s, double defalt, int rounding) { double result = defalt; if (! s.empty()) { try { int beats, width; bool is_time_sig = string_to_time_signature(s, beats, width); if (is_time_sig) { double numerator = double(beats); double denominator = double(width); result = numerator / denominator; } else result = std::stod(s, nullptr); if (rounding > 0) { double power = std::pow(10.0, rounding); result = std::floor(result * power) / power; } } catch (std::invalid_argument const &) { // no code } } return result; } /** * Checks a string to see if it could be a floating point value. This status * requires nothing but digits plus a comma or a decimal point. The latter * are necessary, otherwise the string is an integer. We trim the left/right * spaces; any space in between invalidates the check. This is not a robust * check, requiring some smarts on the caller. */ bool is_floating_string (const std::string & value) { bool result = false; std::string trimmed = trim(value); if (trimmed.find_first_of(" ") == std::string::npos) { /* * Find the first non-digit character. Then check if a non-digit * character was found. */ auto it = std::find_if ( value.begin(), value.end(), [] (char c) { return ! std::isdigit(c); } ); if (it != value.end()) { if (*it == ',' || *it == '.') result = true; } } return result; } /** * Converts a double value to a string with an optional precision. * * \param value * The value to be converted. * * \param precision * The number of digits of precision. Note that this is not the * number of digits after the decimal point, but the number of * significant digits. If set to 0, no precision is used. */ std::string double_to_string (double value, int precision) { char temp[32]; if (precision == 0) (void) snprintf(temp, sizeof temp, "%g", value); else (void) snprintf(temp, sizeof temp, "%.*g", precision, value); return std::string(temp); } float string_to_float (const std::string & s, float defalt, int rounding) { return float(string_to_double(s, double(defalt), rounding)); } /** * Converts a string to a signed long value. This function bypasses * characters until it finds a digit (whether part of the number or a "0x" * construct), and then converts it. The strtol() function is used with a * base of 0 so that decimal, hexadecimal, and octal values can all be * parsed. * * This function is the base implementation for string_to_int() as well. * * \param s * Provides the string to convert to a signed long integer. * * \param defalt * The desired default for an empty string. The default \a defalt value * is 0. * * \return * Returns the signed long integer value represented by the string. * If the string is empty or has no digits, then the default value is * returned. */ long string_to_long (const std::string & s, long defalt) { long result = defalt; if (! s.empty()) { try { result = std::stol(s, nullptr, 0); } catch (std::invalid_argument const &) { // no code } } return result; } std::string long_to_string (long value) { char temp[32]; (void) snprintf(temp, sizeof temp, "%ld", value); return std::string(temp); } /** * Converts a string to an unsigned long integer. */ unsigned long string_to_unsigned_long (const std::string & s, unsigned long defalt) { double result = defalt; try { result = std::stoul(s, nullptr, 0); } catch (std::invalid_argument const &) { // no code } return result; } /** * Converts a string to an unsigned integer. */ unsigned string_to_unsigned (const std::string & s, unsigned defalt) { return unsigned(string_to_unsigned_long(s, (unsigned long)(defalt))); } /** * Converts a string to an integer. Simply calls string_to_long(). * * \param s * Provides the string to convert to an integer. * * \param defalt * Provides the fallback value, itself defaulting to 0. * * \return * Returns the integer value represented by the string. */ int string_to_int (const std::string & s, int defalt) { return int(string_to_long(s, long(defalt))); } std::string int_to_string (int value) { char temp[32]; (void) snprintf(temp, sizeof temp, "%d", value); return std::string(temp); } /** * Tests that a string is not empty and has non-space characters. Provides * essentially the opposite test that string_is_void() provides. The * definition of white-space is provided by the std::isspace() * function/macro. * * \param s * The string pointer to check for emptiness. * * \return * Returns true if the pointer is valid, the string has a non-zero * length, and is not just white-space. */ bool string_not_void (const std::string & s) { bool result = false; if (! s.empty()) { for (int i = 0; i < int(s.length()); ++i) { if (! std::isspace(s[i])) { result = true; break; } } } return result; } /** * Tests that a string is empty or has only white-space characters. Meant to * have essentially the opposite result of string_not_void(). The meaning of * empty is special here, as it refers to a string being useless as a token: * * - The string is of zero length. * - The string has only white-space characters in it, where the * isspace() macro provides the definition of white-space. * * \param s * The string pointer to check for emptiness. * * \return * Returns true if the string has a zero length, or is only * white-space. */ bool string_is_void (const std::string & s) { bool result = s.empty(); if (! result) result = ! string_not_void(s); return result; } /** * Compares two strings for a form of semantic equality, for the purposes of * editable_event(), for example. The strings_match() function returns true * if the comparison items are identical, without case-sensitivity in * character content up to the length of the secondary string. This allows * abbreviations to match. (And, in scanning routines, the first match is * immediately accepted.) * * \param target * The primary string in the comparison. This is the target string, the * one we hope to match. It is assumed to be non-empty, and the * result is false if it is empty. * * \param x * The secondary string in the comparison. It must be no longer than the * target string, or the match is false. * * \return * Returns true if both strings are are identical in characters, up to * the length of the secondary string, with the case of the characters * being insignificant. Otherwise, false is returned. */ bool strings_match (const std::string & target, const std::string & x) { bool result = ! target.empty(); if (result) { result = x.length() <= target.length(); if (result) { for (int i = 0; i < int(x.length()); ++i) { if (std::tolower(x[i]) != std::tolower(target[i])) { result = false; break; } } } } return result; } /** * This function is strings_match(), with the added feature that it skips * leading digits and white-space before doing the match. */ bool strings_match_ex (const std::string & target, const std::string & x) { bool result = ! target.empty(); if (result) { static const std::string s_skip_set { "(!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" /* ispunct */ "0123456789" /* digits */ " \t\r\n\v\f" /* white */ }; std::string::size_type pos = x.find_first_not_of(s_skip_set); std::string trimmed_x = x.substr(pos); result = ! trimmed_x.empty(); if (result) result = strings_match(target, trimmed_x); } return result; } /** * Returns the source string with all characters converted to lowercase. */ std::string tolower (const std::string & source) { std::string result; for (auto c : source) { char c2 = std::tolower(c); result += c2; } return result; } /** * Returns the source string with all characters converted to uppercase. */ std::string toupper (const std::string & source) { std::string result; for (auto c : source) { char c2 = std::toupper(c); result += c2; } return result; } /** * Returns the source string with the first character converted to uppercase. */ std::string capitalize (const std::string & source) { std::string result; int count = 0; for (auto c : source) { char c2 = count++ == 0 ? std::toupper(c) : c ; result += c2; } return result; } /** * Easy conversion from boolean to string, "true" or "false". */ std::string bool_to_string (bool x) { static const std::string s_true { "true" }; static const std::string s_false { "false" }; return x ? s_true : s_false ; } /** * Easy conversion from boolean to character, "T" or "F". */ char bool_to_char (bool x) { static char s_true { 'T' }; static char s_false { 'F' }; return x ? s_true : s_false ; } std::string pointer_to_string (void * ptr) { /* * long long int value = reinterpret_cast; */ char temp[32]; snprintf(temp, sizeof temp, "0x%p", ptr); return std::string(temp); } /** * Tokenizes a substanza, defined as the text between square brackets, * including the square brackets. * * Other code uses sscanf() to extract data from within "[ ]". See * midicontrolfile::parse_midi_control_out() for an example. * * \param tokens * Provides a vector into which to push the tokens. The destination for * all of the strings found in parsing. * * \param source * The substanza to be parsed and tokenized. * * \param bleft * The position in the source at which to start the parsing. The default * value is 0. * * \param brackets * Provides the starting and ending characters for the token in a two * character string. The default is empty, in which case "[]" is * implied. * * \return * Returns the number of tokens pushed (i.e. the final size of the tokens * vector). If it returns 0, there are no tokens, and the destination * vector has been cleared. If it returns 2, then the two tokens are "[" * and "]"... the substanza is empty of data. */ int tokenize_stanzas ( tokenization & tokens, const std::string & source, std::string::size_type bleft, const std::string & brackets ) { static std::string s_delims = SEQ66_TRIM_CHARS; /* isspace()? */ std::string BL = "["; std::string BR = "]"; char CBR = ']'; if (brackets.size() >= 2) { BL = brackets[0]; BR = brackets[1]; CBR = brackets[1]; } tokens.clear(); bleft = source.find_first_of(BL, bleft); if (bleft != std::string::npos) { auto bright = source.find_first_of(BR, bleft + 1); if (bright != std::string::npos && bright > bleft) { tokens.push_back(BL); ++bleft; if (std::isspace(source[bleft])) bleft = source.find_first_not_of(s_delims, bleft); if (source[bleft] != CBR) { for (;;) { auto last = source.find_first_of(s_delims, bleft); if (last == std::string::npos) { if (bright > bleft) { tokens.push_back ( source.substr(bleft, bright - bleft) ); } break; } else { tokens.push_back(source.substr(bleft, last - bleft)); bleft = source.find_first_not_of(s_delims, last); } } } tokens.push_back(BR); } } return int(tokens.size()); } /** * Tokenizes a string containing a pair of numbers separated by either spaces * or an 'x'. Useful in grabbing dimensions. Handles integers or basic * floats. It assumes only a single delimiter between each token: * * - "1.0x2.0" * - "1.0 2.0" * * No matter what the delimiter, spaces are trimmed from each token. * * \param source * Provides the string to be parsed into tokens. * * \param delimiters * The character(s) separating the tokens. Defaults to a Space character. * * \return * Returns the number of tokens converted in a string vector. */ tokenization tokenize ( const std::string & source, const std::string & delimiters ) { tokenization result; std::size_t previous = source.find_first_not_of(delimiters); while (previous != std::string::npos) { std::size_t current = source.find_first_of(delimiters, previous); if (current == std::string::npos) { std::string temp = trim(source.substr(previous)); result.push_back(temp); break; } else { std::string temp = trim(source.substr(previous, current-previous)); result.push_back(temp); previous = source.find_first_not_of(delimiters, current); } } return result; } /** * This function makes values in quotes into a single token, and it uses the * space and tab to delimit tokens. Otherwise it is like tokenize() above. * It handles only double quotes, which should match. We don't want to watch * out for apostrophes. The quotes are stripped. */ tokenization tokenize_quoted (const std::string & source) { tokenization result; tokenization temp = tokenize(source); if (! temp.empty()) { bool quotes = false; std::string quoted; for (const auto & token : temp) { if (token.front() == '"') { if (token.back() == '"') /* single-word quote */ { if (token.size() > 1) { quoted = token.substr(1, token.length() - 2); if (! quoted.empty()) result.push_back(quoted); } else /* isolated end quote */ result.push_back(quoted); } else { quotes = true; quoted = token.substr(1); } } else { if (token.back() == '"') /* end of quoted string */ { if (quotes) { quotes = false; quoted += " "; quoted += token.substr(0, token.length() - 1); if (! quoted.empty()) { result.push_back(quoted); quoted.clear(); /* in case more quotes */ quotes = false; } } } else { if (quotes) /* append the token */ { quoted += " "; quoted += token; } else result.push_back(token); } } } } return result; } /** * Simplifies a string by tokenizing it based on spaces, and dropping tokens * that have some special characters and don't start with a letter, then * reassembling the remaining tokens with spaces in between. */ std::string simplify (const std::string & source) { std::string result; tokenization tokens = tokenize(source); if (tokens.empty()) { result = source; } else { static std::string s_special = "[:]()"; bool first_one = false; for (const auto & t : tokens) { bool ok = std::isalpha(t[0]); if (! ok) ok = t.find_first_of(s_special) == std::string::npos; if (ok) { if (first_one) result += " "; result += t; first_one = true; } } } return result; } /** * * This is a simplistic string-conversion function; it requires that * the source string be ASCII-encoded. * * Windows: * * -# Handle the trivial case of an empty string. * -# Determine the required length of the new string. * -# Construct a new string of required length. * -# Convert the old string to the new string. * * Linux: * * Just do a simple assign. * */ std::wstring widen_string (const std::string & source) { if (source.empty()) return std::wstring(); /* trivial case of empty string */ #if defined SEQ66_PLATFORM_WINDOWS size_t required_length = ::MultiByteToWideChar ( CP_UTF8, 0, source.c_str(), int(source.length()), 0, 0 ); std::wstring result(required_length, L'\0'); ::MultiByteToWideChar ( CP_UTF8, 0, source.c_str(), int(source.length()), &result[0], int(result.length()) ); return result; #else std::wstring result; result.assign(source.begin(), source.end()); return result; #endif } /** * Takes a string and adds line breaks to make it fit full words within * a margin. Any existing line breaks are treated like spaces. * * This function can optionally add a comment character at the beginning * of each line, followed by a space. * * As with most of our string functions, this one isn't built for speed or * space-saving. * * Method: * * -# Tokenize the string using SEQ66_WHITE_CHARS = " \t\r\n\v\f" as * the delimiters. * * \param source * Provides the string to be wrapped. * * \param margin * Provides the wrap margin, beyond which no character will appear. * Words that hit that limit are belayed to the next line. The default * margin is 80. * * \param commentchar * If non-zero (which is the default), then this character is prepended * to each line. A common value is "#". * * \return * Returns the word-wrapped string. */ std::string word_wrap (const std::string & source, size_t margin, char commentchar) { std::string result; if (! source.empty()) { std::string commenting{" "}; size_t linelen = 0; tokenization words = tokenize(source, SEQ66_WHITE_CHARS); commenting[0] = commentchar; for (auto w : words) { bool room = (linelen + w.length()) < margin; if (linelen == 0 || ! room) { if (commentchar != 0) w = commenting + w; if (! room) result += "\n"; result += w; linelen = w.length(); } else { w = " " + w; result += w; linelen += w.length(); } } if (linelen > 0) result += "\n"; } return result; } /** * This function is meant for --help. It allows the display of long * help text for an option by splitting the description line and indenting * any subsequent lines that are necessary. * * As an example, consider this line from another application: * * -U, --jack-session uuid Set UUID for JACK session; turns on session * management. Use 'on' to enable it and let JACK * set the UUID. * * \param source * Provides a string defining a command-line option (for example). In * the example above, this is the text at the right. * * \param leftmargin * Provides the margin for lines after the first (if necessary). * This provides the hanging indent. * * \param rightmargin * Provides the maximum length of the lines that are generated. * * \return * Returns the reformatted string. */ std::string hanging_word_wrap ( const std::string & source, size_t leftmargin, size_t rightmargin ) { std::string result; if (! source.empty()) { /* * int line = 0; // the first line // */ size_t linelen = leftmargin; std::string padding(leftmargin, ' '); tokenization words = tokenize(source, SEQ66_WHITE_CHARS); for (auto w : words) { bool room = (linelen + w.length()) < rightmargin; if (! room) { result += "\n"; result += padding; linelen = leftmargin; /* * ++line; */ } w = " " + w; result += w; linelen += w.length(); } } return result; } } // namespace seq66 /* * strfunctions.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libsessions/Makefile.am ================================================ #***************************************************************************** # Makefile.am (libsessions) #----------------------------------------------------------------------------- ## # \file Makefile.am # \library libsessions # \author Chris Ahlstrom # \date 2020-03-08 # \updates 2020-03-08 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This file is a makefile for the libsessions library project. This # makefile provides the skeleton needed to build the libsessions project # directory using GNU autotools. # #----------------------------------------------------------------------------- #***************************************************************************** # Packing targets. #----------------------------------------------------------------------------- # # Always use Automake in foreign mode (adding foreign to # AUTOMAKE_OPTIONS in Makefile.am). Otherwise, it requires too many # boilerplate files from the GNU coding standards that aren't useful to # us. # #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 subdir-objects MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #***************************************************************************** # EXTRA_DIST #----------------------------------------------------------------------------- EXTRA_DIST = #***************************************************************************** # SUBDIRS #----------------------------------------------------------------------------- SUBDIRS = include src #***************************************************************************** # DIST_SUBDIRS #----------------------------------------------------------------------------- # # DIST_SUBDIRS is used by targets that need to recurse into /all/ # directories, even those which have been conditionally left out of the # build. # # Precisely, DIST_SUBDIRS is used by: # # - make dist # - make distclean # - make maintainer-clean. # # All other recursive targets use SUBDIRS. # #----------------------------------------------------------------------------- DIST_SUBDIRS = $(SUBDIRS) #***************************************************************************** # all-local #----------------------------------------------------------------------------- all-local: @echo "Top source-directory 'top_srcdir' is $(top_srcdir)" @echo "* * * * * All libsessions build items completed * * * * *" #***************************************************************************** # Makefile.am (libsessions) #----------------------------------------------------------------------------- # vim: ts=3 sw=3 noet ft=automake #----------------------------------------------------------------------------- ================================================ FILE: libsessions/include/Makefile.am ================================================ #****************************************************************************** # Makefile.am (libseq66) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library libseq66 library # \author Chris Ahlstrom # \date 2020-03-08 # \update 2021-09-03 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the seq66 C/C++ # library. # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 subdir-objects MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) CLEANFILES = *.gc* MOSTLYCLEANFILES = *~ #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = *.h *.hpp #****************************************************************************** # Items from configure.ac #------------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ GIT_VERSION = @GIT_VERSION@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ prefix = @prefix@ includedir = @seq66includedir@ libdir = @seq66libdir@ datadir = @datadir@ datarootdir = @datarootdir@ seq66includedir = @seq66includedir@ seq66libdir = @seq66libdir@ #****************************************************************************** # Compiler and linker flags #------------------------------------------------------------------------------ # # $(GTKMM_CFLAGS) # #------------------------------------------------------------------------------ AM_CXXFLAGS = \ -I../include \ -I$(top_srcdir)/include \ $(JACK_CFLAGS) \ -DSEQ66_GIT_VERSION=\"$(git_info)\" \ -Wall $(MM_WFLAGS) #****************************************************************************** # Source files #---------------------------------------------------------------------------- # # Not added: nsm.h, nsmdummy.h. # #---------------------------------------------------------------------------- nobase_include_HEADERS = \ nsm/nsmbase.hpp \ nsm/nsmclient.hpp \ nsm/nsmmessagesex.hpp \ nsm/nsmserver.hpp #****************************************************************************** # uninstall-hook #------------------------------------------------------------------------------ uninstall-hook: @echo "Note: you may want to remove $(pkgincludedir) manually" #****************************************************************************** # Makefile.am (libseq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: libsessions/include/nsm/nsmbase.hpp ================================================ #if ! defined SEQ66_NSMBASE_HPP #define SEQ66_NSMBASE_HPP /** * \file nsmbase.hpp * * This module provides a reimplementation of the nsm.h header file as a * class. * * \library seq66 * \author Chris Ahlstrom and other authors; see documentation * \date 2020-03-01 * \updates 2024-03-12 * \version $Revision$ * \license GNU GPL v2 or above * * Upcoming support for the Non Session Manager. */ #include /* std::atomic */ #include /* std::vector */ #include "seq66_features.hpp" /* feature (SUPPORT) macros */ #include "util/basic_macros.hpp" /* is_nullptr() & not_nullptr() */ #include "nsm/nsmmessagesex.hpp" /* seq66::nsm::tag */ #if defined SEQ66_LIBLO_SUPPORT #include /* library for the OSC protocol */ #else #error Support for liblo required for this class, install liblo-dev #endif #define NSM_API_VERSION_MAJOR 1 #define NSM_API_VERSION_MINOR 0 namespace seq66 { namespace nsm { /** * Provides reply codes matching those of NSM> */ enum class error { ok = 0, general = -1, incompatible_api = -2, blacklisted = -3, launch_failed = -4, no_such_file = -5, no_session_open = -6, unsaved_changes = -7, not_now = -8, bad_project = -9, create_failed = -10, session_locked = -11, /* see nsmd.C in the Non project */ operation_pending = -12, /* see nsmd.C in the Non project */ save_failed = -99 /* doesn't exist in the Non project */ }; /* * External helper functions in the nsm namespace. */ extern std::string reply_string (error errorcode); extern std::string get_url (); extern void incoming_msg ( const std::string & cbname, const std::string & message, const std::string & pattern, bool iserror = false ); extern void outgoing_msg ( const std::string & message, const std::string & pattern, const std::string & data = "sent" ); extern tokenization convert_lo_args ( const std::string & pattern, int argc, lo_arg ** argv ); } // namespace nsm /** * nsmbase is an NSM OSC server/client base class. */ class nsmbase { friend class clinsmanager; private: static std::string sm_nsm_default_ext; /** * Provides a reference (a void pointer) to an OSC service. See * /usr/include/lo/lo_types.h. */ lo_address m_lo_address; /** * Provides a reference (a void pointer) to a thread "containing" * an OSC server. See /usr/include/lo/lo_types.h. */ lo_server_thread m_lo_server_thread; /** * Provides a reference (a void pointer) to an object representing an * an OSC server. See /usr/include/lo/lo_types.h. */ lo_server m_lo_server; /** * This item is mutable because it can be falsified if the server and * address are found to be null. It is turned on when we receive the * information about the session (including path to the session). */ mutable std::atomic m_active; bool m_dirty; int m_dirty_count; std::string m_manager; std::string m_capabilities; std::string m_path_name; std::string m_display_name; std::string m_client_id; std::string m_nsm_file; std::string m_nsm_ext; std::string m_nsm_url; public: nsmbase ( const std::string & nsmurl, const std::string & nsmfile = "", const std::string & nsmext = "" ); virtual ~nsmbase (); public: bool is_active() const // session activation accessor { return m_active; } bool is_a_client (const nsmbase * p) { return not_nullptr(p) && p->is_active(); } bool not_a_client (const nsmbase * p) { return is_nullptr(p) || ! p->is_active(); } // Session manager accessors. const std::string & manager () const { return m_manager; } const std::string & capabilities () const { return m_capabilities; } // Session client accessors. const std::string & path_name () const { return m_path_name; } const std::string & display_name () const { return m_display_name; } const std::string & client_id () const { return m_client_id; } const std::string & nsm_file () const { return m_nsm_file; } const std::string & nsm_ext () const { return m_nsm_ext; } const std::string & nsm_url () const { return m_nsm_url; } bool dirty () const { return m_dirty; } void dirty (bool isdirty); /* session managers call this one */ protected: void path_name (const std::string & s) { m_path_name = s; } void display_name (const std::string & s) { m_display_name = s; } void client_id (const std::string & s) { m_client_id = s; } void is_active (bool f) { m_active = f; } void manager (const std::string & s) { m_manager = s; } void capabilities (const std::string & s) { m_capabilities = s; } protected: bool msg_check (int timeoutms = 0); /* milliseconds */ bool lo_is_valid () const; void nsm_debug (const std::string & tag); void add_client_method (nsm::tag t, lo_method_handler h); #if defined SEQ66_NSM_ADD_SERVER_METHOD void add_server_method (nsm::tag t, lo_method_handler h); #endif bool send_announcement ( const std::string & appname, const std::string & exename, const std::string & capabilities ); void start_thread (); void stop_thread (); void update_dirty_count (bool flag = true); // Session client reply methods bool open_reply ( nsm::error errorcode = nsm::error::ok, const std::string & msg = "No info" ); bool save_reply ( nsm::error errorcode = nsm::error::ok, const std::string & msg = "No info" ); bool send_nsm_reply ( const std::string & path, nsm::error errorcode, const std::string & msg ); bool send ( const std::string & message, const std::string & pattern ); bool send_from_client (nsm::tag t); bool send_from_client ( nsm::tag t, const std::string & s1, const std::string & s2, const std::string & s3 = "" ); void open_reply (bool loaded) { open_reply(loaded ? nsm::error::ok : nsm::error::general); if (loaded) m_dirty = false; } void save_reply (bool saved) { save_reply(saved ? nsm::error::ok : nsm::error::general); if (saved) m_dirty = false; } public: // virtual methods for callbacks in nsmbase virtual void nsm_reply /* generic replies */ ( const std::string & message, const std::string & pattern ); virtual void error (int errcode, const std::string & mesg); protected: // virtual methods virtual bool progress (float percent); virtual bool is_dirty (); virtual bool is_clean (); virtual bool message (int priority, const std::string & mesg); virtual bool initialize (); /* * Used by the free-function OSC callbacks, and there are too many to make * as friends. */ public: virtual void announce_reply ( const std::string & mesg, const std::string & manager, const std::string & capabilities ) = 0; virtual void open ( const std::string & path_name, const std::string & display_name, const std::string & client_id ) = 0; virtual void save () = 0; virtual void label (const std::string & label) = 0; virtual void loaded () = 0; virtual void show (const std::string & path) = 0; virtual void hide (const std::string & path) = 0; virtual void broadcast ( const std::string & message, const std::string & pattern, const tokenization & argv ) = 0; virtual bool announce ( const std::string & app_name, const std::string & exe_name, const std::string & capabilities ) = 0; protected: /* * Prospective caller helpers a la qtractorMainForm. */ virtual bool open_session (); virtual bool save_session (); virtual bool close_session (); private: int send_from ( const std::string & message, const std::string & pattern, const std::string & s1 = "", const std::string & s2 = "", const std::string & s3 = "" ); /* * Other message args not covered: * * - int, string * - string, string, string, int, int, int */ }; // class nsmbase } // namespace seq66 #endif // SEQ66_NSMBASE_HPP /* * nsmbase.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libsessions/include/nsm/nsmclient.hpp ================================================ #if ! defined SEQ66_NSMCLIENT_HPP #define SEQ66_NSMCLIENT_HPP /** * \file nsmclient.hpp * * This module provides macros for generating simple messages, MIDI * parameters, and more. * * \library seq66 * \author Chris Ahlstrom and other authors; see documentation * \date 2020-03-01 * \updates 2021-12-07 * \version $Revision$ * \license GNU GPL v2 or above * * Upcoming support for the Non Session Manager. */ #include "nsm/nsmbase.hpp" /* seq66::nsmbase base class */ namespace seq66 { class smanager; /** * nsmclient is an NSM OSC client agent. */ class nsmclient : public nsmbase { public: /** * Enumeration to represent the "capabilities" supported by the Non * Session Manager (NSM). * * \var none * Indicates no capabilities; provided for completeness or * error-checking. * * \var cswitch * The client is capable of responding to multiple open messages * without restarting. The string for this value is "switch", but that * is a reserved word in C/C++. * * \var dirty * The client knows when it has unsaved changes. * * \var progress * The client can send progress updates during time-consuming * operations. * * \var message * The client can send textual status updates. * * \var optional_gui * The client has a optional GUI. */ enum class caps { none, cswitch, /* ":switch:" */ dirty, progress, message, optional_gui /* ":optional-gui:" */ }; protected: /** * Provides the session manager object helping this client do session * management. No Qt code. */ smanager & m_session_manager; private: std::atomic m_hidden; public: nsmclient ( smanager & sessionmanager, const std::string & nsm_url, const std::string & nsm_file = "", const std::string & nsm_ext = "" ); virtual ~nsmclient (); void send_visibility (bool isshown); bool hidden () const { return m_hidden; } public: // session client method overrides virtual bool initialize () override; virtual void announce_reply ( const std::string & mesg, const std::string & manager, const std::string & capabilities ) override; virtual void open ( const std::string & path_name, const std::string & display_name, const std::string & client_id ) override; virtual void save () override; virtual void loaded () override; virtual void label (const std::string & label) override; virtual void show (const std::string & path) override; virtual void hide (const std::string & path) override; virtual void broadcast ( const std::string & message, const std::string & pattern, const tokenization & argv ) override; virtual bool announce ( const std::string & app_name, const std::string & exe_name, const std::string & capabilities ) override; public: // Other virtual functions virtual bool open_session () override; // helper a la qtractorMainForm virtual void session_manager_name (const std::string & mgrname); virtual void session_manager_path (const std::string & pathname); virtual void session_display_name (const std::string & dispname); virtual void session_client_id (const std::string & clid); protected: void hidden (bool flag) { m_hidden = flag; } /* * signals: // Session client callbacks. void active (bool is_active); void open (); void save (); void loaded (); void show (); void hide (); * */ }; // class nsmclient /* * External helper functions. */ extern nsmclient * create_nsmclient ( smanager & sessionmanager, const std::string & nsmurl, const std::string & nsmfile, const std::string & nsmext ); } // namespace seq66 #endif // SEQ66_NSMCLIENT_HPP /* * nsmclient.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libsessions/include/nsm/nsmmessagesex.hpp ================================================ #if ! defined SEQ66_NSMMESSAGESEX_HPP #define SEQ66_NSMMESSAGESEX_HPP /** * \file nsmmessagesex.hpp * * This module provides a kind of repository of all the possible OSC/NSM * messages. * * \library seq66 * \author Chris Ahlstrom * \date 2020-08-20 * \updates 2020-09-01 * \version $Revision$ * \license GNU GPL v2 or above * * Upcoming support for the Non Session Manager. This module provides a list * of OSC paths (messages) for various purposes, as a way to keep track of * them all and use them propertly. */ #include /* std::map dictionary class */ #include /* std::string class */ namespace seq66 { /* * The seq66::nsm namespace is a place to squirrel away concepts that are * not defined in the other nsmbase classes. */ namespace nsm { /** * The seq66::nsm::tag enumeration is used in the lookup of the long * strings that are sent and received by NSM. We can use these tags to * look up both the long name and the OSC formatting string to be used in * a message. */ enum class tag { null, // client, all items null abort, // server add, // server addstrip, // non announce, // gui, gui/server, server arguments, // proxy broadcast, // server clean, // client clienterror, // proxy close, // server connect, // signal configfile, // proxy created, // signal dirty, // client, gui/client disconnect, // signal duplicate, // server error, // used by many executable, // proxy generic, // signal hello, // signal hidden, // client hide, // client, gui/client kill, // proxy label, // client, gui/client, proxy list, // server, session, signal loaded, // client message, // client, gui/client, gui/server name, // gui/session, session newcs, // gui/client, server open, // client, server optional, // gui/client oscreply, // osc, non ping, // used by manu progress, // client, gui/client quit, // server remove, // gui/client removed, // signal renamed, // signal reply, // used by many, signal has no args replyex, // another variation resume, // gui/client root, // gui/session save, // client, gui/client, server savesignal, // proxy session, // gui/session show, // client, gui/client shown, // client start, // proxy status, // gui/client stop, // gui/client stopsignal, // prox stripbynumber, // non switchc, // gui/client update, // proxy visible // gui/client }; /** * This type holds the long OSC string for the message, and the data * pattern string that describes the data being sent. */ using messagepair = struct { std::string msg_text; std::string msg_pattern; }; /** * A lookup map for tags and message pairs. */ using lookup = std::map; /* * Free functions for table lookup. */ extern bool client_msg (tag t, std::string & message, std::string & pattern); extern bool gui_client_msg (tag t, std::string & message, std::string & pattern); extern bool gui_session_msg (tag t, std::string & message, std::string & pattern); extern bool proxy_msg (tag t, std::string & message, std::string & pattern); extern bool server_msg (tag t, std::string & message, std::string & pattern); extern bool misc_msg (tag t, std::string & message, std::string & pattern); /* * Free functions for inverse table lookup. */ extern tag client_tag ( const std::string & message, const std::string & pattern = "X" ); extern tag server_tag ( const std::string & message, const std::string & pattern = "X" ); /* * More free functions. */ extern const std::string & default_ext (); extern const std::string & dirty_msg (bool isdirty); extern const std::string & visible_msg (bool isvisible); extern const std::string & url (); extern bool is_announce (const std::string & s = ""); } // namespace nsm } // namespace seq66 #endif // SEQ66_NSMMESSAGESEX_HPP /* * nsmmessagesex.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libsessions/include/nsm/nsmserver.hpp ================================================ #if ! defined SEQ66_NSMSERVER_HPP #define SEQ66_NSMSERVER_HPP /** * \file nsmserver.hpp * * This module provides macros for generating simple messages, MIDI * parameters, and more. * * \library seq66 * \author Chris Ahlstrom and other authors; see documentation * \date 2020-03-11 * \updates 2020-08-20 * \version $Revision$ * \license GNU GPL v2 or above * * Upcoming support for the Non Session Manager. */ #include "nsm/nsmbase.hpp" /* seq66::nsmbase base class */ namespace seq66 { /** * nsmbase is an NSM OSC server/client base class. */ class nsmserver : public nsmbase { public: /** * * \var none * Indicates no capabilities; provided for completeness or * error-checking. * * \var server_control * The server provides client-to-server control. * * \var broadcast * The server responds to the "/nsm/server/broadcast" message. * * \var optional_gui * The server responds to "optional-gui" messages. If this * capability is not present, then clients with "optional-gui" must * always keep themselves visible. */ enum class caps { none, server_control, broadcast, optional_gui }; /** * These command values can indicate the pending operation. */ enum class command { none, quit, kill, save, open, start, close, duplicate, cnew }; private: static std::string sm_nsm_default_ext; public: nsmserver (const std::string & nsmurl); virtual ~nsmserver () { // no code } }; // class nsmserver /* * External helper functions. */ extern std::unique_ptr create_nsmserver (); } // namespace seq66 #endif // SEQ66_NSMSERVER_HPP /* * nsmserver.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libsessions/libsessions.pro ================================================ #****************************************************************************** # libsessions.pro (qpseq66) #------------------------------------------------------------------------------ ## # \file libsessions.pro # \library qseq66 application # \author Chris Ahlstrom # \date 2020-03-24 # \update 2020-09-03 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Important: # # This project file is designed only for Qt 5 (and above?). However, # on a 32-bit Linux system with an old version of Qt (5.3), the build # fails due to std::unique_ptr not being defined, even with the c++11 # flag added. # #------------------------------------------------------------------------------ message($$_PRO_FILE_PWD_) TEMPLATE = lib CONFIG += staticlib config_prl qtc_runnable c++14 TARGET = sessions # These are needed to set up seq66_platform_macros: CONFIG(debug, debug|release) { DEFINES += DEBUG } else { DEFINES += NDEBUG } contains (CONFIG, rtmidi) { MIDILIB = rtmidi DEFINES += "SEQ66_MIDILIB=rtmidi" DEFINES += "SEQ66_RTMIDI_SUPPORT=1" } else { MIDILIB = portmidi DEFINES += "SEQ66_MIDILIB=portmidi" DEFINES += "SEQ66_PORTMIDI_SUPPORT=1" } contains (CONFIG, rtmidi) { HEADERS += include/nsm/nsmbase.hpp \ include/nsm/nsmclient.hpp \ include/nsm/nsmmessagesex.hpp SOURCES += src/nsm/nsmbase.cpp \ src/nsm/nsmclient.cpp \ src/nsm/nsmmessagesex.cpp \ } INCLUDEPATH = ../include/qt/rtmidi \ ../libseq66/include \ ../seq_portmidi/include \ ../seq_rtmidi/include \ include #****************************************************************************** # libsessions.pro (qpseq66) #------------------------------------------------------------------------------ # vim: ts=4 sw=4 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: libsessions/src/Makefile.am ================================================ #****************************************************************************** # Makefile.am (libsessions/src) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library libsessions library # \author Chris Ahlstrom # \date 2020-03-08 # \update 2022-05-19 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the libsessions C/C++ # library. # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 subdir-objects MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #****************************************************************************** # CLEANFILES #------------------------------------------------------------------------------ CLEANFILES = *.gc* MOSTLYCLEANFILES = *~ #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = #****************************************************************************** # Items from configure.ac #------------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ GIT_VERSION = @GIT_VERSION@ SEQ66_API_MAJOR = @SEQ66_API_MAJOR@ SEQ66_API_MINOR = @SEQ66_API_MINOR@ SEQ66_API_PATCH = @SEQ66_API_PATCH@ SEQ66_API_VERSION = @SEQ66_API_VERSION@ SEQ66_LT_CURRENT = @SEQ66_LT_CURRENT@ SEQ66_LT_REVISION = @SEQ66_LT_REVISION@ SEQ66_LT_AGE = @SEQ66_LT_AGE@ SEQ66_LIBTOOL_VERSION = @SEQ66_LIBTOOL_VERSION@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ prefix = @prefix@ includedir = @seq66includedir@ libdir = @seq66libdir@ datadir = @datadir@ datarootdir = @datarootdir@ seq66includedir = @seq66includedir@ seq66libdir = @seq66libdir@ #****************************************************************************** # localedir #------------------------------------------------------------------------------ # # 'localedir' is the normal system directory for installed localization # files. # #------------------------------------------------------------------------------ localedir = $(datadir)/locale DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ #***************************************************************************** # libtool #----------------------------------------------------------------------------- version = $(SEQ66_LIBTOOL_VERSION) #***************************************************************************** # git_version #----------------------------------------------------------------------------- # git_version = $(shell git describe --abbrev=7 --always --tags) #----------------------------------------------------------------------------- git_version = $(shell git describe --tags --long) git_branch =$(shell git branch | grep -e ^*) git_info = "$(git_version) $(git_branch)" #****************************************************************************** # Compiler and linker flags # # $(GTKMM_CFLAGS) # # -I$(top_srcdir)/seq_gtkmm2/include # # Unfortunately, we need to add the platform-specific include directories # because we include the performer module in some modules, and it includes # the platform-specific stuff. # #------------------------------------------------------------------------------ AM_CXXFLAGS = \ -I../include \ -I$(top_srcdir)/include \ -I$(top_srcdir)/libseq66/include \ -I$(top_srcdir)/libsessions/include \ -I$(top_srcdir)/seq_rtmidi/include \ -I$(top_srcdir)/seq_portmidi/include \ $(ALSA_CFLAGS) \ $(JACK_CFLAGS) \ -DSEQ66_GIT_VERSION=\"$(git_info)\" #****************************************************************************** # The library to build, a libtool-based library #------------------------------------------------------------------------------ lib_LTLIBRARIES = libsessions.la #****************************************************************************** # Source files #---------------------------------------------------------------------------- libsessions_la_SOURCES = \ nsm/nsmbase.cpp \ nsm/nsmclient.cpp \ nsm/nsmmessagesex.cpp \ nsm/nsmserver.cpp libsessions_la_LDFLAGS = -version-info $(version) libsessions_la_LIBADD = $(ALSA_LIBS) $(JACK_LIBS) #****************************************************************************** # uninstall-hook #------------------------------------------------------------------------------ # # We'd like to remove /usr/local/include/seq66-1.0 if it is # empty. However, we don't have a good way to do it yet. # #------------------------------------------------------------------------------ uninstall-hook: @echo "Note: you may want to remove $(libdir) manually" #****************************************************************************** # Makefile.am (libsessions/src) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: libsessions/src/nsm/nsmbase.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file nsmbase.cpp * * This module defines some informative functions that are actually * better off as functions. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-03-07 * \updates 2024-11-07 * \license GNU GPLv2 or above * * nsmbase is an Non Session Manager (NSM) OSC client helper. The NSM API * comprises a simple Open Sound Control (OSC) based protocol. * * The Non project contains a daemon, nsmd, which is an implementation of the * server side of the NSM API. nsmd is controlled by the non-session-manager * GUI. The same server-side API can also be implemented by other session * managers (such as LADISH). The only dependency for client implementations * is liblo (the OSC library) and code based on the nsm.h header file. * * Find out if NSM_URL is defined in the host environment, and create the nsm * object on the heap if so. The create_nsm() should be used; it returns a * "unique pointer". (We may need to provide specific factory functions for * Qt, Gtkmm, and command-line versions of the application.) * * NSM_URL: * * The NSM_URL environment variable is used to inform clients of how to * reach the nsmd daemon, which can be started as follows: * * nsmd [--osc-port portnum] [--session-root path] [--detach] * * In the following setting, 18440 is the 'portnum'. 127.0.0.1 is the * local host (the computer on which all NSM-related apps are running. * * NSM_URL=osc.udp://127.0.0.1:18440/ * NSM_URL=osc.udp://mlsleno:15325/ (on developer laptop) * * Note that, if running an nsm_proxy client, this variable may need to be * passed on the command-line (in typical bash fashion). * * Also see the file contrib/non/nsmopen.sh for examples, and "oscsend * --help". * * Warning: * * If the non-session-manager user-interface comes up completely * disabled, this means that OSC cannot find the host. If this occurs, * make sure that the real host name is appended to the "localhost" entry * in the /etc/hosts file for at least one of the loopback interfaces: * * - 127.0.0.1 localhost myhostname * - 127.0.1.1 myhostname.domainname * * Notes for the future: * * There's the general NSM osc protocol which allows basic session * management like adding programs, listing the current sessions and * starting one. This would be sufficient for cadence. * * Detection of NSM is by checking validity of NSM_URL environment variable * and getting a response from it so a simple "/nsm/session/list" to * NSM_URL would be sufficient to both populate the list and detect whether * it's running * * nsm-proxy integration, however, might be more difficult due to nsmd only * providing details of launching programs to the non-session-manager gui. * * Also nsmd will only bind to a single GUI, so this should be the * frontend. (An alternative would be to modify nsmd to support multiple * GUIs running at the same time.) * * New tool that implements the /nsm/gui/xxx OSC endpoint to receive more * details about the session which allow it to give a view of the current * session. * * CLANG and LO_TT_IMMEDIATE: * * Warning: compound literals are a C99-specific feature [-Wc99-extensions] * This is due to using, in lo_osc_types.h: * * #define LO_TT_IMMEDIATE ((lo_timetag){0U,1U}) * * versus "lo_timetag lo_get_tt_immediate();" as LO_TT_IMMEDIATE */ #if defined __clang__ #include "lo/lo_osc_types.h" static lo_timetag s_lo_timetag = { 0, 1 }; #define LO_TT_IMMEDIATE_2 s_lo_timetag #else #define LO_TT_IMMEDIATE_2 LO_TT_IMMEDIATE #endif #undef SHOW_CLIENT_DATA_TYPE /* for development purposes only */ #if defined SHOW_CLIENT_DATA_TYPE #include /* std::cout */ #endif #include /* std::strlen() */ #include /* provides the pid_t typedef */ #include /* C getpid() */ #include "cfg/settings.hpp" /* rc() and usr() functions */ #include "util/basic_macros.hpp" /* not_nullptr(), warnprint(), etc. */ #include "nsm/nsmbase.hpp" /* seq66::nsmbase class */ #include "nsm/nsmmessagesex.hpp" /* seq66::nsm new message functions */ #define NSM_API_VERSION_MAJOR 1 #define NSM_API_VERSION_MINOR 0 namespace seq66 { /** * A handler for the /reply message. */ static int osc_nsm_reply ( const char * path, const char * types, lo_arg ** /* argv */, int /* argc */, lo_message /* msg */, void * user_data ) { nsmbase * pnsmc = static_cast(user_data); if (is_nullptr(pnsmc)) return -1; nsm::incoming_msg("Basic Reply", path, types); pnsmc->nsm_reply(path, types); return 0; } /** * This set of functions supplies some OSC (liblo) callback methods for the * nsmclient to use. * * Note that lo_arg (/usr/include/lo/lo_osc_types.h) is a union that covers * various integer types, floats, doubles, chars of various kinds, 4-byte * MIDI packets, lo_timetag, and a "blob" structure (size + data). * * The lo_message type is a low-level object messages passed via OSC. It is * a void pointer. * * Finally, note the "sis" check for types. The type characters are defined * in the OSC header lo_osc_types.h, and there are values for float ('f'), * string ('s'), 32-bit integers ('i'), and more. These character tag * messages and specify the arguments of lo_send(). */ static int osc_nsm_error ( const char * path, const char * types, lo_arg ** argv, int /* argc */, lo_message /* msg */, void * user_data ) { nsmbase * pnsmc = static_cast(user_data); if (is_nullptr(pnsmc)) return -1; nsm::incoming_msg("Error", path, types); pnsmc->error(int(argv[1]->i), &argv[2]->s); return 0; } /* * osc_stop_signal() : stop_signal() virtual function * osc_start() : start() virtual function * osc_kill() : kill() virtual function * osc_update() : update() virtual function */ /** * This constructor should (currently) not be called unless the NSM URL was * found to be good. */ nsmbase::nsmbase ( const std::string & nsmurl, const std::string & nsmfile, const std::string & nsmext ) : m_lo_address (nullptr), m_lo_server_thread (nullptr), m_lo_server (nullptr), m_active (false), /* an atomic boolean value */ m_dirty (false), m_dirty_count (0), m_manager (), m_capabilities (), m_path_name (), m_display_name (), m_client_id (), m_nsm_file (nsmfile), m_nsm_ext (nsmext), m_nsm_url (nsmurl) { // No code needed } /** * Stops and frees the server thread, and frees the OSC address. Note that * the server itself does not need to be freed. (We will see what valgrind * says about that later, if possible.) */ nsmbase::~nsmbase () { stop_thread(); if (m_lo_address) lo_address_free(m_lo_address); } void nsmbase::start_thread () { if (not_nullptr(m_lo_server_thread)) { int rcode = lo_server_thread_start(m_lo_server_thread); if (rcode == 0) /* successful? */ session_message("OSC server thread started"); else error_message("OSC server thread start failed"); } } void nsmbase::stop_thread () { if (not_nullptr(m_lo_server_thread)) { lo_server_thread_free(m_lo_server_thread); m_lo_server_thread = nullptr; } else { if (not_nullptr(m_lo_server)) { lo_server_free(m_lo_server); m_lo_server = nullptr; } } } /** * Gets the server address from NSM_URL, creates the server and server * thread, and adds the basic /error and /reply handlers. (The rest are * added by the nsmclient class.) */ bool nsmbase::initialize () { m_lo_address = lo_address_new_from_url(nsm_url().c_str()); bool result = not_nullptr(m_lo_address); if (result) { const int proto = lo_address_get_protocol(m_lo_address); std::string ps = "Unknown"; switch (proto) { case LO_UDP: ps = "UDP"; break; case LO_TCP: ps = "TCP"; break; case LO_UNIX: ps = "UNIX"; break; } ps += " OSC protocol"; session_message(ps); m_lo_server_thread = lo_server_thread_new_with_proto(NULL, proto, NULL); result = not_nullptr(m_lo_server_thread); if (result) { m_lo_server = lo_server_thread_get_server(m_lo_server_thread); result = not_nullptr(m_lo_server); if (result) { add_client_method(nsm::tag::error, osc_nsm_error); add_client_method(nsm::tag::reply, osc_nsm_reply); /* * See nsmclient. * add_client_method(nsm::tag::replyex, osc_nsm_announce_reply); * add_client_method(nsm::tag::hide, osc_nsm_hide); * add_client_method(nsm::tag::label, osc_nsm_label); * add_client_method(nsm::tag::loaded, osc_nsm_session_loaded); * add_client_method(nsm::tag::null, osc_nsm_broadcast); * add_client_method(nsm::tag::open, osc_nsm_open); * add_client_method(nsm::tag::save, osc_nsm_save); * add_client_method(nsm::tag::show, osc_nsm_show); * * The most-derived class should call this, we think. * lo_server_thread_start(m_lo_server_thread); */ } else error_message("OSC bad server"); } else error_message("OSC bad server thread"); } else error_message("OSC bad server address"); return result; } /** * Checks to be sure that the server and the address are usable (i.e. not * null). */ bool nsmbase::lo_is_valid () const { bool result = not_nullptr_2(m_lo_address, m_lo_server); if (! result) error_message("Null OSC address or server"); return result; } /** * Wait for the next message. Useful after sending the /nsm/server/announce * message. We added a brief sleep delay because sometimes we seem to miss a * message at startup. * * Note that lo_server_wait() waits for the given timeout, then returns 1 if * a message is waiting. * * \param timeoutms * Indicates how long to wait for a server message, in milliseconds. * Defaults to 100 ms. * * \return * Returns true if a message was received within 2 seconds, roughly. */ bool nsmbase::msg_check (int timeoutms) { bool result = false; if (timeoutms > 0) { /* * This cause issues when NSM responds quickly: microsleep(100); */ if (lo_server_wait(m_lo_server, timeoutms)) { result = true; session_message("NSM waiting for reply..."); while (lo_server_recv_noblock(m_lo_server, 0)) { /* do nothing, handle the message(s) */ } } if (! result) error_message("NSM no reply!"); } return result; } /* * Session client methods. */ /** * Sends one of these messages: * * - /nsm/client/is_clean * - /nsm/client/is_dirty message * * It sets the m_dirty flag accordingly. */ void nsmbase::dirty (bool isdirty) { if (lo_is_valid()) { const char * path = nsm::dirty_msg(isdirty).c_str(); (void) send(path, ""); m_dirty = isdirty; } } /** * Optionally increments, or clears, the dirty-count. If active, then the * dirty-count is checked in order to set the dirty-flag. */ void nsmbase::update_dirty_count (bool updatedirt) { if (updatedirt) ++m_dirty_count; else m_dirty_count = 0; if (is_active()) { if (! m_dirty && updatedirt) m_dirty = true; else if (m_dirty && ! updatedirt) m_dirty = false; } } /** * Call this function to send a progress indication to the session manager. * The message looks like this: * * /nsm/client/progress f:percent * * No error handling here, as the data is informational. * * \param percent * The indication of progress, ranging from 0.0 to 100.0. */ bool nsmbase::progress (float percent) { bool result = lo_is_valid(); if (result) { std::string message; std::string pattern; bool ok = nsm::client_msg(nsm::tag::progress, message, pattern); if (ok) { (void) lo_send_from /* "/nsm/client/progress" "f" */ ( m_lo_address, m_lo_server, LO_TT_IMMEDIATE_2, message.c_str(), pattern.c_str(), percent ); nsm::outgoing_msg(message, pattern, std::to_string(percent)); } } return result; } /** * Send out the indication of dirtiness status: * * /nsm/client/is_dirty */ bool nsmbase::is_dirty () { bool result = lo_is_valid(); if (result) { std::string message; std::string pattern; bool ok = nsm::client_msg(nsm::tag::dirty, message, pattern); if (ok) { int rcode = send_from(message, pattern); ok = rcode == 0; } } return result; } /** * Send out the indication of cleanliness status: * * /nsm/client/is_clean */ bool nsmbase::is_clean () { bool result = lo_is_valid(); if (result) { std::string message; std::string pattern; result = nsm::client_msg(nsm::tag::clean, message, pattern); if (result) result = send(message, pattern); } return result; } /** * Sends a message: * * /nsm/client/message i:priority s:message * * where the priority ranges from 0 (least important) to 3 (most important). */ bool nsmbase::message (int priority, const std::string & mesg) { bool result = lo_is_valid(); if (result) { std::string message; std::string pattern; bool ok = nsm::client_msg(nsm::tag::message, message, pattern); if (ok) { lo_send_from /* "/nsm/client/message" "is" */ ( m_lo_address, m_lo_server, LO_TT_IMMEDIATE_2, message.c_str(), pattern.c_str(), priority, mesg.c_str() ); std::string text = "priority " + std::to_string(priority) + "; msg '" + mesg + "'"; nsm::outgoing_msg(message, pattern, text); } } return result; } /* * Session client reply methods. If the reply code is not error::ok, then the * reply will be an error reply. */ bool nsmbase::open_reply (nsm::error errorcode, const std::string & msg) { return send_nsm_reply("/nsm/client/open", errorcode, msg); } bool nsmbase::save_reply (nsm::error errorcode, const std::string & msg) { return send_nsm_reply("/nsm/client/save", errorcode, msg); } /** * Sends a reply or error message, useful for the following message: * * - /nsm/client/open * - /nsm/client/save * * The message sent is one of the following where "path" is either of the * above messages, in double-quotes: * * - /reply "path" s:message * - /error "path" i:errorcode s:message * * The "path" sent depends on the reply code provided. */ bool nsmbase::send_nsm_reply ( const std::string & path, nsm::error errorcode, const std::string & msg ) { bool result = lo_is_valid(); if (result) { int rc = (-1); std::string pattern; std::string message; std::string replytype; std::string replymsg = reply_string(errorcode); replymsg += ": "; replymsg += msg; if (errorcode == nsm::error::ok) { if (client_msg(nsm::tag::reply, message, pattern)) rc = send_from(message, pattern, path, replymsg); replytype = "reply"; } else { if (client_msg(nsm::tag::error, message, pattern)) { rc = lo_send_from ( m_lo_address, m_lo_server, LO_TT_IMMEDIATE_2, message.c_str(), pattern.c_str(), path.c_str(), static_cast(errorcode), replymsg.c_str() ); } replytype = "error"; } result = rc != (-1); std::string text = path + " " + replytype + " " + replymsg; if (! result) text += "; FAILED"; nsm::outgoing_msg(message, pattern, text); } return result; } /** * Sends the following message: * * /nsm/server/announce s:appname s:capabilities s:exename * i:apimajor i:apiminor i:pid * * See nsmclient::announce() for more discussion. */ bool nsmbase::send_announcement ( const std::string & appname, /* actually a package name, "seq66" */ const std::string & exename, /* comes from argv[0] */ const std::string & capabilities /* e.g. ":switch:dirty:" */ ) { std::string message; std::string pattern; bool result = lo_is_valid(); if (result) result = nsm::server_msg(nsm::tag::announce, message, pattern); if (result) { const char * packagename = appname.c_str(); const char * app = exename.c_str(); const char * caps = capabilities.c_str(); int pid = int(getpid()); int rc = lo_send_from /* "/nsm/server/announce" "sssiii" */ ( m_lo_address, m_lo_server, LO_TT_IMMEDIATE_2, message.c_str(), pattern.c_str(), packagename, caps, app, NSM_API_VERSION_MAJOR, NSM_API_VERSION_MINOR, pid ); result = rc != (-1); std::string text = "sent package " + appname + "; app " + exename + "; capabilities " + capabilities; if (! result) text += ", but it FAILED"; nsm::outgoing_msg(message, pattern, text); } return result; } /* * Generic server reply. Not sure we ever get this one. */ void nsmbase:: nsm_reply (const std::string & message, const std::string & pattern) { nsm::outgoing_msg(message, pattern, "Server Reply"); } /* * Handles the base server error: * * /error i:errcode s:errormessage */ void nsmbase::error (int errcode, const std::string & errmesg) { is_active(false); m_manager.clear(); m_capabilities.clear(); m_path_name.clear(); m_display_name.clear(); m_client_id.clear(); std::string ecm = reply_string(static_cast(errcode)); nsm::incoming_msg("Error Values", errmesg, ecm, true); } void nsmbase::nsm_debug (const std::string & tag) { if (tag.empty()) nsm::outgoing_msg(m_path_name, m_client_id, m_display_name); else nsm::outgoing_msg(m_path_name, m_client_id, tag); } /* * Prospective caller helpers a la qtractorMainForm. */ /** * After calling this function and checking the return value, the caller * should close out any "open" items and set up a new "session". After that, * we call open_reply() with the boolean result of the new session setup. */ bool nsmbase::open_session () { bool result = is_active(); if (result) { m_dirty_count = 0; m_dirty = false; m_nsm_file.clear(); } return result; } /** * Send close message, quit, abort? */ bool nsmbase::close_session () { bool result = is_active(); if (result) { m_dirty_count = 0; } return result; } /** * The caller should call this function, then check the result before saving * the "session". * * We probably need to return a reply-code instead of a boolean. */ bool nsmbase::save_session () { bool result = is_active(); if (result) { m_dirty_count = 0; m_dirty = false; /* * Done by caller: m_nsm_file.clear() ??? */ } return result; } /* * lo_method // void * to a new server method * lo_server_add_method * ( * lo_server st, // void * created by lo_server_new() * const char * path, * const char * typespec, * lo_method_handler h, // see below * const void * userdata * ) * * lo_method // void * to a new server method * lo_server_thread_add_method * ( * lo_server_thread st, // void * made by lo_server_thread_new() * const char * path, // OSC path string or NULL * const char * typespec, // OSC parameters format * lo_method_handler h, // see below * const void * userdata // data for the method handler * ) * * int (* lo_method_handler) * ( * const char * path, * const char * typespec, * lo_arg ** argv, int argc, // a union of many types * lo_message msg, // void * created by lo_message_new() * void * user_data * ) */ /** * Adds an OSC method handler function to both the server and the server * thread. The server addition is to be able to send to the OSC server, and * the server thread addition is to be able to receive replies from the * OSC server. Weird. */ void nsmbase::add_client_method (nsm::tag t, lo_method_handler h) { std::string message; std::string pattern; #if defined SHOW_CLIENT_DATA_TYPE const std::type_info & ti = typeid(this); std::cout << "Client type = " << ti.name(); #endif if (client_msg(t, message, pattern)) { if (t == nsm::tag::null) { const char * nul = NULL; (void) lo_server_thread_add_method ( m_lo_server_thread, nul, nul, h, this ); nsm::outgoing_msg("OSC", "", "Broadcast method added"); } else { const char * m = message.c_str(); const char * p = pattern.c_str(); (void) lo_server_thread_add_method ( m_lo_server_thread, m, p, h, this ); nsm::outgoing_msg(message, pattern, "Client method added"); } } } #if defined SEQ66_NSM_ADD_SERVER_METHOD void nsmbase::add_server_method (nsm::tag t, lo_method_handler h) { std::string message; std::string pattern; if (server_msg(t, message, pattern)) { const char * m = message.c_str(); const char * p = pattern.c_str(); /* * All of a sudden in Arch Linux this call fails to build; it still * builds in an older Ubuntu. This code was obviously wrong, * but is never called. * * (void) lo_server_add_method(m_lo_server_thread, m, p, h, this); */ (void) lo_server_add_method(m_lo_server, m, p, h, this); nsm::outgoing_msg(message, pattern, "Server method added"); } } #endif bool nsmbase::send ( const std::string & message, const std::string & pattern ) { int rcode = send_from(message.c_str(), pattern.c_str()); bool result = rcode != (-1); if (result) nsm::outgoing_msg(message, pattern, "Sent"); else nsm::outgoing_msg(message, pattern, "Send FAILURE"); return result; } bool nsmbase::send_from_client (nsm::tag t) { std::string message; std::string pattern; bool result = nsm::client_msg(t, message, pattern); if (result) result = send(message, pattern); return result; } bool nsmbase::send_from_client ( nsm::tag t, const std::string & s1, const std::string & s2, const std::string & s3 ) { std::string message; std::string pattern; bool result = nsm::client_msg(t, message, pattern); if (result) { int rcode = send_from(message, pattern, s1, s2, s3); result = rcode != (-1); } return result; } /** * A wrapper function for easier trouble-shooting. The last three arguments are * optional. */ int nsmbase::send_from ( const std::string & message, const std::string & pattern, const std::string & s1, const std::string & s2, const std::string & s3 ) { int result = (-1); if (s1.empty()) { result = lo_send_from ( m_lo_address, m_lo_server, LO_TT_IMMEDIATE_2, message.c_str(), pattern.c_str() ); } else { if (s2.empty()) { result = lo_send_from ( m_lo_address, m_lo_server, LO_TT_IMMEDIATE_2, message.c_str(), pattern.c_str(), s1.c_str() ); } else { if (s3.empty()) { result = lo_send_from ( m_lo_address, m_lo_server, LO_TT_IMMEDIATE_2, message.c_str(), pattern.c_str(), s1.c_str(), s2.c_str() ); } else { result = lo_send_from ( m_lo_address, m_lo_server, LO_TT_IMMEDIATE_2, message.c_str(), pattern.c_str(), s1.c_str(), s2.c_str(), s3.c_str() ); } } } if (result != (-1)) { std::string msg = "OSC message sent " + message + pattern; session_message(msg); } else { std::string msg = "OSC message FAILURE " + message + pattern; error_message(msg); } return result; } /* * This namespace makes for easier-to-read code. */ namespace nsm { /** * A free function to provide a string for a reply code in the nsm::error * enumeration class. * \verbatim ok: OK. general: General error. This is a very common error value. incompatible_api: Incompatible API version. blacklisted: The client has been blacklisted. launch_failed: Launch failed. no_such_file: No such file. no_session_open: No session open. unsaved_changes: Unsaved changes would be lost. not_now: The operation cannot be completed at this time. bad_project: An existing project file was found to be corrupt. create_failed: Create failed. A new project could not be created. session_locked: Session is locked. operation_pending: An operation is pending. save_failed: An non-existent (heh heh) error code. \endverbatim * * The NSM API documentation claims there is an "ERR_SAVE_FAILED" code, * meaning "The project could not be saved", which needs to be sent as a * response when appropriate. However, this code does not exist. Other * codes are private to the nsmd.C module, and are exposed in our * implementation of the API. */ std::string reply_string (nsm::error errorcode) { std::string result; switch (errorcode) { case nsm::error::ok: result = "Acknowledged"; break; case nsm::error::general: result = "General error"; break; case nsm::error::incompatible_api: result = "Incompatible API"; break; case nsm::error::blacklisted: result = "Blacklisted"; break; case nsm::error::launch_failed: result = "Launch failed"; break; case nsm::error::no_such_file: result = "No such file"; break; case nsm::error::no_session_open: result = "No session open"; break; case nsm::error::unsaved_changes: result = "Unsaved changes"; break; case nsm::error::not_now: result = "Not now"; break; case nsm::error::bad_project: result = "Bad project"; break; case nsm::error::create_failed: result = "Create failed"; break; case nsm::error::session_locked: result = "Session locked"; break; case nsm::error::operation_pending: result = "Operation Pending"; break; case nsm::error::save_failed: result = "Save failed."; break; default: result = "Unknown reply"; break; } return result; } /** * See if there is session-manager "present" on the host computer. */ static std::string get_session_url (const std::string & env_value) { std::string result; #if defined _GNU_SOURCE char * url = secure_getenv(env_value.c_str()); #else char * url = std::getenv(env_value.c_str()); #endif if (not_nullptr(url) && strlen(url) > 0) result = std::string(url); return result; } /** * See if there is NSM "present" on the host computer. A static value is * included that, if not empty, will be used, for troubleshooting and * testing. To use it, check out the value of NSM_URL, put it here, and * rebuild. This feature is meant to make it easier to debug, but will it * work? */ std::string get_url () { static std::string s_debug_url = ""; std::string url = nsm::url(); std::string result = s_debug_url.empty() ? get_session_url(url) : s_debug_url ; bool active = ! result.empty(); usr().in_nsm_session(active); if (active) { std::string msg = "NSM URL " + result; session_message(msg); } return result; } void incoming_msg ( const std::string & cbname, const std::string & message, const std::string & pattern, bool iserror ) { if (rc().investigate() || iserror) { std::string text = msgsnprintf ( "%s<--NSM: %s [%s]", cbname.c_str(), message.c_str(), pattern.c_str() ); (void) session_message(text); } } void outgoing_msg ( const std::string & message, const std::string & pattern, const std::string & data ) { std::string text = msgsnprintf ( "%s-->[%s] %s", message.c_str(), pattern.c_str(), data.c_str() ); session_message(text); } tokenization convert_lo_args (const std::string & pattern, int argc, lo_arg ** argv) { tokenization result; if (argc > 0) { for (int i = 0; i < argc; ++i) { std::string temp; char patc = pattern[i]; switch (patc) { case 's': temp = argv[i]->s; /* &argv[i] */ break; case 'i': temp = std::to_string(argv[i]->i); break; case 'f': temp = std::to_string(argv[i]->f); break; default: temp = "unhandled format type: "; temp += patc; break; } result.push_back(temp); } } return result; } } // namespace nsm } // namespace seq66 /* * nsmbase.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libsessions/src/nsm/nsmclient.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file nsmclient.cpp * * This module defines some informative functions that are actually * better off as functions. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-03-01 * \updates 2021-11-29 * \license GNU GPLv2 or above * * nsmclient is an Non Session Manager (NSM) OSC client agent. The NSM API * comprises a simple Open Sound Control (OSC) based protocol. * * The Non project contains a daemon, nsmd, which is an implementation of the * server side of the NSM API. nsmd is controlled by the non-session-manager * GUI. The same server-side API can also be implemented by other session * managers (such as LADISH). The only dependency for client implementations * is liblo (the OSC library) and the nsm.h header file. * * Session Manager Startup: * * To start the Non Session Manager and the GUI: * * $ non-session-manager [ -- --session-root path ] & * * The default path is "$HOME/NSM Sessions". Inside this directory there * will ultimately exist one directory per session, each with the name of * the session as given by the user. Inside this session directory is a * file called "session.nsm". Inside this file is a list of session * clients in the following format: * * appname:exename:nXXXX (example: qseq66:qseq66:nYUSM) * * where XXXX is a set of four random uppercase ASCII letters. The * string "nXXXX" is used to look up clients. * * At startup, the environment variable NSM_URL is added to the * environment. It's format is described below. Commands are handled as * per the "Commands handling by the server" section at the top of the * nsmmessageex.cpp module. A new session (e.g. "MySession") can be added * using the "New" button. It will be stored in "$HOME / New Sessions / * MySession / session.nsm", which starts out empty. * * Once running, an executable can be added as a client. However, the * executable must be in the path. The command "/nsm/server/add" will * fail with the error "Absolute paths are not permitted. Clients must be * in $PATH". Once added properly, NSM spawns the executable, and the * executable inherits the environment (i.e. NSM_URL). * * After closing/saving the session, the session.nsm file contains * only a line such as "qseq66:qseq66:nMTRJ", as noted above. * * Once qseq66 is part of the session, clicking on the session name will * launch qseq66. * * Process: * * -# Find out if NSM_URL is defined in the host environment, and * create the nsmclient object on the heap if so. The * create_nsmclient() should be used; it returns a "unique pointer". * (We may need to provide specific factory functions for Qt, Gtkmm, * and command-line versions of the application.) * -# If NSM_URL is valid and reachable, call the nsmbase::announce() * function. * -# Connect up callbacks (e.g. signals in Qt) for the following events: * - Open NSM session. The caller should first see if this nsmclient * is active. If so, close the session, which checks the * dirty-count in order to ask the user if changes need to be * saved. * - Save NSM session. * - Show NSM session. * - Hide NSM session. * -# Applications must not register JACK clients until receiving an * open message, which provides a unique client name prefix suitable * for passing to JACK. * -# Call nsmclient::announce(APP_TITLE, ":switch:dirty:optional-gui:") * if using a GUI. * * NSM_URL: * * The NSM_URL environment variable is used to inform clients of how to * reach the nsmd daemon, which can be started as follows: * * nsmd [--osc-port portnum] [--session-root path] [--detach] * * In the following setting, 18440 is the 'portnum'. 127.0.0.1 is the * local host (the computer on which all NSM-related apps are running. * * NSM_URL=osc.udp://127.0.0.1:18440/ * NSM_URL=osc.udp://mlsleno:15325/ (on developer laptop) * NSM_URL=osc.udp://mlsasus.mls:12325/ (on another laptop) * * Note that, if running an nsm_proxy client, this variable may need to be * passed on the command-line (in typical bash fashion). * * Also see the file contrib/non/nsmopen.sh for examples, and "oscsend * --help". * * New session: * * TODO * * Detecting NSM session actions: * * In a Qt-based application, we can provide an extended NSM client that * will respond to signals propagated by the "emit" operator. In a * command-line application, we can set flags that are detected in a * polling loop and cause actions to occur. In Gtkmm applications, we * can set callbacks to be executed. It would be nice to use the same * (callback?) system for all of them. * * Shutdown: * * When an NSM client shuts down, NSM detects its PID and detects if the * client aborted or stopped normally. A normal stopped occurs when the * client receives a KILL or QUIT command. If a QUIT command, the server * sends: * * "/nsm/gui/client/status" + Client-ID + "removed" status * * Otherwise, it sends two messages: * * "/nsm/gui/client/label" + Client-ID + optional error message * "/nsm/gui/client/status" + Client-ID + "stopped" * * Notes for the future: * * There's the general NSM osc protocol which allows basic session * management like adding programs, listing the current sessions and * starting one. This would be sufficient for cadence. * * Detection of NSM is by checking validity of NSM_URL environment variable * and getting a response from it so a simple "/nsm/session/list" to * NSM_URL would be sufficient to both populate the list and detect whether * it's running * * nsm-proxy integration, however, might be more difficult due to nsmd only * providing details of launching programs to the non-session-manager gui. * * Also nsmd will only bind to a single GUI, so this should be the * frontend. (An alternative would be to modify nsmd to support multiple * GUIs running at the same time.) * * New tool that implements the /nsm/gui/xxx OSC endpoint to receive more * details about the session which allow it to give a view of the current * session. * * INVESTIGATE the NSM replacement, RaySession. */ #include "util/basic_macros.hpp" /* not_nullptr() macro */ #include "nsm/nsmclient.hpp" /* seq66::nsmclient class */ #include "nsm/nsmmessagesex.hpp" /* seq66::nsm message functions */ #include "sessions/smanager.hpp" /* seq66::smanager virtuals */ namespace seq66 { /** * The typical type signature of this callback is "ssss". */ static int osc_nsm_announce_reply ( const char * path, const char * types, lo_arg ** argv, int /* argc */, lo_message /* msg */, void * user_data ) { nsmbase * pnsmc = static_cast(user_data); if (is_nullptr(pnsmc)) return -1; if (nsm::is_announce(&argv[0]->s)) { nsm::incoming_msg("Announce Reply", path, types); pnsmc->announce_reply(&argv[1]->s, &argv[2]->s, &argv[3]->s); } else nsm::incoming_msg("Basic Reply", path, types); return 0; } static int osc_nsm_open ( const char * path, const char * types, lo_arg ** argv, int /* argc */, lo_message /* msg */, void * user_data ) { nsmbase * pnsmc = static_cast(user_data); if (is_nullptr(pnsmc)) return -1; nsm::incoming_msg("Open", path, types); pnsmc->open(&argv[0]->s, &argv[1]->s, &argv[2]->s); return 0; } static int osc_nsm_save ( const char * path, const char * types, lo_arg ** /* argv */, int /* argc */, lo_message /* msg */, void * user_data ) { nsmbase * pnsmc = static_cast(user_data); if (is_nullptr(pnsmc)) return -1; nsm::incoming_msg("Save", path, types); pnsmc->save(); /* a virtual function */ return 0; } static int osc_nsm_session_loaded ( const char * path, const char * types, lo_arg ** /* argv */, int /* argc */, lo_message /* msg */, void * user_data ) { nsmbase * pnsmc = static_cast(user_data); if (is_nullptr(pnsmc)) return -1; nsm::incoming_msg("Session Loaded", path, types); pnsmc->loaded(); return 0; } static int osc_nsm_label ( const char * path, const char * types, lo_arg ** argv, int /* argc */, lo_message /* msg */, void * user_data ) { nsmbase * pnsmc = static_cast(user_data); if (is_nullptr(pnsmc)) return -1; nsm::incoming_msg("Label", path, types); pnsmc->label(std::string(&argv[0]->s)); /* a virtual function */ return 0; } /** * This function could also be called osc_show_gui(). See the nsm-proxy code. */ static int osc_nsm_show ( const char * path, const char * types, lo_arg ** /* argv */, int /* argc */, lo_message /* msg */, void * user_data ) { nsmbase * pnsmc = static_cast(user_data); if (is_nullptr(pnsmc)) return -1; nsm::incoming_msg("Show", path, types); pnsmc->show(path); /* a virtual function */ return 0; } /** * This function could also be called osc_hide_gui(). See the nsm-proxy * code. * * Warning: * * The agordejo version of "non-session-manager" never calls this * function. It calls osc_nsm_show()! */ static int osc_nsm_hide ( const char * path, const char * types, lo_arg ** /* argv */, int /* argc */, lo_message /* msg */, void * user_data ) { nsmbase * pnsmc = static_cast(user_data); if (pnsmc == NULL) return -1; nsm::incoming_msg("Hide", path, types); pnsmc->hide(path); return 0; } static int osc_nsm_broadcast ( const char * path, const char * types, lo_arg ** argv, int argc, lo_message /*msg*/, void * user_data ) { nsmbase * pnsmc = static_cast(user_data); if (pnsmc == NULL) return -1; tokenization arguments = nsm::convert_lo_args(types, argc, argv); nsm::incoming_msg("Broadcast", path, types); pnsmc->broadcast(path, types, arguments); return 0; } /* ------------------------------------------------------------------------ */ /** * Principal constructor. */ nsmclient::nsmclient ( smanager & sm, const std::string & nsmurl, const std::string & nsmfile, const std::string & nsmext ) : nsmbase (nsmurl, nsmfile, nsmext), m_session_manager (sm) { // no code so far } nsmclient::~nsmclient () { // no code so far } bool nsmclient::initialize () { bool result = nsmbase::initialize(); if (result) { add_client_method(nsm::tag::replyex, osc_nsm_announce_reply); add_client_method(nsm::tag::open, osc_nsm_open); add_client_method(nsm::tag::save, osc_nsm_save); add_client_method(nsm::tag::loaded, osc_nsm_session_loaded); add_client_method(nsm::tag::label, osc_nsm_label); add_client_method(nsm::tag::show, osc_nsm_show); add_client_method(nsm::tag::hide, osc_nsm_hide); add_client_method(nsm::tag::null, osc_nsm_broadcast); start_thread(); } return result; } /* * Server announce reply handler for the following message: * * /nsm/server/announce s:application_name s:capabilities * s:executable_name i:api_ver sion_major i:api_version_minor i:pid * * This message is sent by the server after the client sends its announcement * [see the nsmclient :: announce() and nsmbase :: send_announcement() * functions]. * * Here, we can indicate that the session is connected and active, and what * session manager is in force. However, we do not want to activate this * client until the session settings are made in the open() function below. */ void nsmclient::announce_reply ( const std::string & mesg, const std::string & mgr, const std::string & caps ) { capabilities(caps); session_manager_name(mgr); nsm::incoming_msg("Announce Reply Values", mgr, caps + " " + mesg); } /** * Client open callback for the following message: * * /nsm/client/open s:path_to_instance_specific_project * s:display_name s:client_id * * Format examples: * * - Display name: "JackSession" (user's chosen session name in NSM) * - Client ID: "seq66.nUKIE" * - Path: "/home/user/NSM Sessions/JackSession/seq66.nUKIE" * * Compare to the "open" code in nsm-proxy. See nsmclient::announce() for * more discussion. Note that this function is where we can be active and * possible copy the application configuration to the session path. */ void nsmclient::open ( const std::string & pathname, const std::string & displayname, const std::string & clientid ) { session_manager_path(pathname); session_display_name(displayname); session_client_id(clientid); set_client_name(clientid); /* set "seq66.nUKIE" as client ID */ nsm::incoming_msg("Open Values", pathname, clientid + " " + displayname); is_active(true); } /* * Client save callback for the following message: * * /nsm/client/save * * Note that, though documented, code nsm :: reply :: save_failed does not * exist, so we use nsm :: reply :: general for now. See nsmbase :: * save_reply. */ void nsmclient::save () { if (save_session()) /* assumes that all is okay */ { std::string msg; bool saved = m_session_manager.save_session(msg); nsm::error r = saved ? nsm::error::ok : nsm::error::general ; (void) save_reply(r, msg); } } /** * Handles the following message from the server: * * /nsm/client/session_is_loaded */ void nsmclient::loaded () { nsm_debug("Session loaded message from server"); } /** * Handles the following message from the server: * * /nsm/client/label s:label */ void nsmclient::label (const std::string & label) { std::string tag("Label from server: "); tag += label; nsm_debug(tag); // no code } /** * Client show optional GUI. The GUI manager class must provide this * functionality. The message from the server is: * * /nsm/client/show_optional_gui */ void nsmclient::show (const std::string & path) { std::string msg = "show " + path; nsm_debug(msg); hidden(false); /* * Misread API.mu... there is no response necessary here. * * send_from_client(nsm::tag::reply, path, "Show OK"); */ } /* * Client hide optional GUI. The message from the server is: * * /nsm/client/hide_optional_gui */ void nsmclient::hide (const std::string & path) { std::string msg = "hide " + path; nsm_debug(msg); hidden(true); /* * Misread API.mu... there is no response necessary here. * * send_from_client(nsm::tag::reply, path, "Hide OK"); */ } void nsmclient::send_visibility (bool isshown) { nsm::tag status = isshown ? nsm::tag::shown : nsm::tag::hidden ; hidden(! isshown); send_from_client(status); } /** * Receives a broadcast and figures out what to do with it. Sort of. The * broadcast message seems to have no path and no data types. Keep an eye on * this one. * * The following function is for *sending* broadcasts, not yet implemented: * * nsmbase::broadcast (const std::string & path, lo_message msg) */ void nsmclient::broadcast ( const std::string & /*message*/, const std::string & /*pattern*/, const tokenization & argv ) { if (lo_is_valid()) { int argc = int(argv.size()); for (int i = 0; i < argc; ++i) msgprintf(msglevel::info, " [%d] %s", i, argv[i].c_str()); } } /** * Provides a client-announce function. * * If NSM_URL is valid and reachable, call this function to send the * following "sssiii" message to the provided address as soon as ready to * respond to the /nsm/client/open event. api_version_major and * api_version_minor must be the two parts of the version number of the NSM * API. If registering JACK clients, application_name must be passed to * jack_client_open. capabilities is a string containing a list of the * capabilities the client possesses, e.g. ":dirty:switch:progress:". * executable_name must be the executable name that launched the program (e.g * argv[0]). * * We wait on the reply from NSM, which is flagged by an atomic boolean in * the open() function. * \verbatim /nsm/server/announce s:application_name s:capabilities s:executable_name i:api_version_major i:api_version_minor i:pid \endverbatim * * \param appname * Provides the "nick-name" for the application. The seq66_features.cpp * function seq_client_name() is used to get this name, which starts out * as "seq66". The seq_client_name() is later modified in the open() * callback function, and ends up like "seq66.nUKIE". * * \param exename * Comes from argv[0]. For example, it has the value "qseq66" in the * graphical application. * * \param capabilities * Provides the main session features the application supports. */ bool nsmclient::announce ( const std::string & appname, /* actually a package name, "Seq66" */ const std::string & exename, /* comes from argv[0] */ const std::string & capabilities /* e.g. ":switch:dirty:" */ ) { bool result = send_announcement(appname, exename, capabilities); if (result) { int count = 12; while (! is_active()) /* this is an atomic boolean check */ { (void) msg_check(1000); if (--count == 0) { errprint("Timed out waiting for NSM"); result = false; break; } } } return result; } /* * Prospective caller helpers a la qtractorMainForm. */ /** * After calling this function and checking the return value, * the caller should close out any "open" items and set up a new "session". * * After that, call open_reply() with the boolean result of the new session * setup. */ bool nsmclient::open_session () { bool result = nsmbase::open_session(); if (result) { // what else? } return result; } /* * Virtual functions to pass status to the smanager-derived class. */ void nsmclient::session_manager_name (const std::string & mgrname) { manager(mgrname); m_session_manager.session_manager_name(mgrname); } void nsmclient::session_manager_path (const std::string & pathname) { path_name(pathname); m_session_manager.session_manager_path(pathname); } void nsmclient::session_display_name (const std::string & dispname) { display_name(dispname); m_session_manager.session_display_name(dispname); } void nsmclient::session_client_id (const std::string & clid) { client_id(clid); m_session_manager.session_client_id(clid); } /** * Provides a factory function to create an nsmclient, and then to call its * virtual initialization function (so that we don't have to call it in the * constructor). * * Note that this bare pointer returned should be assigned immediately to a * smart pointer, such as std::unique_ptr<>. See * seq_qt5/src/qt5nsmanager.cpp for an example. * * \param sm * Provides a reference to the existing session manager. * * \param nsmurl * Provides either the value of NSM_URL or the value that might be * defined in the "usr" file. * * \param nsmfile * Need to revisit this and figure out what it is. The name of the nsm * file? Currently created by nsmd, it is always "session.nsm". * * \param nsmext * The NSM file extension, "nsm". * * \return * Returns the pointer to the nsmclient. Assign it to a smart pointer * immediately! */ nsmclient * create_nsmclient ( smanager & sm, const std::string & nsmurl, const std::string & nsmfile, const std::string & nsmext ) { nsmclient * result = nullptr; if (! nsmurl.empty()) { result = new (std::nothrow) nsmclient(sm, nsmurl, nsmfile, nsmext); if (not_nullptr(result)) (void) result->initialize(); } return result; } } // namespace seq66 /* * nsmclient.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libsessions/src/nsm/nsmmessagesex.cpp ================================================ /** * \file nsmmessagesex.cpp * * This module provides a kind of repository of all the possible OSC/NSM * messages. * * \library seq66 * \author Chris Ahlstrom * \date 2020-08-21 * \updates 2020-08-28 * \version $Revision$ * \license GNU GPL v2 or above * * Upcoming support for the Non Session Manager. Defines a number of free * functions in the seq66::nsm namespace. * * Commands handling by the server: * * -# add. Adds a client process. Sends either an "/error" + path * message, or a "/reply" + path + OK + "Launched" message. ("New" * doesn't send OK!) * -# announce. The client sends an "announce" message. If there is no * session, "/error" + path + errcode + message is sent. * Incompatible API versions are detected. * -# save. Commands all clients to save. Sends either an "/error" * message or a "/reply" + path + "Saved" message. * -# duplicate. Duplicates a session. Sends an "/error" or a * "/nsm/gui/session/session" message plus a "/reply" + path + * "Duplicated" message. * -# new. Commands all clients to save, and then creates a new session. * Sends an "/error" or a "/nsm/gui/session/session" message plus a * "/reply" + path + "Session created" message. * -# list. Lists sessions. Sends an empty "/reply", then an * "/nsm/server/list" message with an empty message. * -# open. Opens a session. Sends an "/error" message or a "/reply" + * path + "Loaded" message. * -# quit. Closes the session. Sends "/nsm/gui/session/name" plus an * empty session name. * -# abort. If a session is open and there is no operation pending (in * which cases an "/error" is sent), then the session is quit as * above. * -# close. Similar to "abort", except that all clients are first * commanded to save. * -# broadcast. The server sends out a command to all clients. * -# progress. Sends "/nsm/gui/client/progress" + Client-ID + * progress. * -# is_dirty. A client sends "/nsm/client/is_dirty" and the server * sends out "/nsm/gui/client/dirty" + Client-ID + dirty. * -# is_clean. A client sends "/nsm/client/is_clean" and the server * sends out "/nsm/gui/client/dirty" + Client-ID + 0. * -# gui_is_hidden. The client sends "/nsm/client/gui_is_hidden" and * the server sends "/nsm/gui/client/gui_visible" + Client-ID + 0. * -# gui_is_shown. The client sends "/nsm/client/gui_is_shown" and * the server sends "/nsm/gui/client/gui_visible" + Client-ID + 1. * -# message. The client sends "/nsm/client/message" + Client_ID + * integer + string, and the server forwards this information to all * clients via an "/nsm/gui/client/message". * -# label. The client sends an "/nsm/client/label" message, and the * server sends out an "/nsm/gui/client/label" message. * -# error. The client sends an "/error" message ("sis" parameters), * and the server sends out "/nsm/gui/client/status" + Client-ID + * status. MORE TO COME. * -# reply. The client sends a "/reply" message ("ssss" parameters), * and the server sends out "/nsm/gui/client/status" + Client-ID + * status. MORE TO COME. * -# stop. A GUI operation. * -# remove. A GUI operation. * -# resume. A GUI operation. * -# client_save. A GUI operation. * -# client_show_optional_gui. A GUI operation. * -# client_hide_optional_gui. A GUI operation. * -# gui_announce. * -# ping. * -# null. */ #include "nsm/nsmmessagesex.hpp" /* seq66::nsm::tag, etc. */ namespace seq66 { /* * The seq66::nsm namespace is a place to squirrel away concepts that are * not defined in the other nsmbase classes. */ namespace nsm { /** * This map of message/pattern pairs provides all the messages and patterns * used in the "/nsm/client/xxxxx" series of messages, including client * variations of "/error" and "/reply". */ static lookup s_client_msgs = { { tag::null, { "", "" } }, { tag::clean, { "/nsm/client/is_clean", "" } }, { tag::dirty, { "/nsm/client/is_dirty", "" } }, { tag::error, { "/error", "sis" } }, { tag::hidden, { "/nsm/client/gui_is_hidden", "" } }, { tag::hide, { "/nsm/client/hide_optional_gui", "" } }, { tag::label, { "/nsm/client/label", "s" } }, { tag::loaded, { "/nsm/client/session_is_loaded", "" } }, { tag::message, { "/nsm/client/message", "is" } }, { tag::open, { "/nsm/client/open", "sss" } }, { tag::progress, { "/nsm/client/progress", "f" } }, { tag::reply, { "/reply", "ss" } }, { tag::replyex, { "/reply", "ssss" } }, { tag::save, { "/nsm/client/save", "" } }, { tag::show, { "/nsm/client/show_optional_gui", "" } }, { tag::shown, { "/nsm/client/gui_is_shown", "" } } }; static lookup s_gui_client_msgs = { { tag::announce, { "/nsm/gui/gui_announce", "s" } }, { tag::dirty, { "/nsm/gui/client/dirty", "si" } }, { tag::hide, { "/nsm/gui/client/hide_optional_gui", "s" } }, { tag::label, { "/nsm/gui/client/label", "ss" } }, { tag::message, { "/nsm/gui/client/message", "s" } }, { tag::newcs, { "/nsm/gui/client/new", "ss" } }, { tag::optional, { "/nsm/gui/client/has_optional_gui", "s" } }, { tag::progress, { "/nsm/gui/client/progress", "sf" } }, { tag::remove, { "/nsm/gui/client/remove", "s" } }, { tag::resume, { "/nsm/gui/client/resume", "s" } }, { tag::save, { "/nsm/gui/client/save", "s" } }, { tag::show, { "/nsm/gui/client/show_optional_gui", "s" } }, { tag::status, { "/nsm/gui/client/status", "ss" } }, { tag::stop, { "/nsm/gui/client/stop", "s" } }, { tag::switchc, { "/nsm/gui/client/switch", "ss" } }, { tag::visible, { "/nsm/gui/client/gui_visible", "si" } } }; static lookup s_gui_session_msgs = { { tag::announce, { "/nsm/gui/server_announce", "s" } }, { tag::message, { "/nsm/gui/server/message", "s" } }, { tag::name, { "/nsm/gui/session/name", "ss" } }, { tag::root, { "/nsm/gui/session/root", "s" } }, { tag::session, { "/nsm/gui/session/session", "s" } } }; static lookup s_proxy_msgs = { { tag::arguments, { "/nsm/proxy/arguments", "s" } }, { tag::clienterror, { "/nsm/proxy/client_error", "s" } }, { tag::configfile, { "/nsm/proxy/config_file", "s" } }, { tag::executable, { "/nsm/proxy/executable", "s" } }, { tag::kill, { "/nsm/proxy/kill", "" } }, { tag::label, { "/nsm/proxy/label", "s" } }, { tag::savesignal, { "/nsm/proxy/save_signal", "i" } }, { tag::start, { "/nsm/proxy/start", "sss" } }, { tag::stopsignal, { "/nsm/proxy/stop_signal", "i" } }, { tag::update, { "/nsm/proxy/update", "" } } }; static lookup s_server_msgs = { { tag::abort, { "/nsm/server/abort", "" } }, { tag::add, { "/nsm/server/add", "s" } }, { tag::announce, { "/nsm/server/announce", "sssiii" } }, { tag::broadcast, { "/nsm/server/broadcast", "" } }, { tag::close, { "/nsm/server/close", "" } }, { tag::duplicate, { "/nsm/server/duplicate", "s" } }, { tag::list, { "/nsm/server/list", "" } }, { tag::newcs, { "/nsm/server/new", "s" } }, { tag::open, { "/nsm/server/open", "s" } }, { tag::quit, { "/nsm/server/quit", "" } }, { tag::save, { "/nsm/server/save", "" } } }; static lookup s_misc_msgs = { { tag::error, { "/error", "sis" } }, { tag::list, { "/nsm/session/list", "?" } }, { tag::name, { "/nsm/session/name", "ss" } }, { tag::ping, { "/osc/ping", "" } }, { tag::reply, { "/reply", "ssss" } } }; /** * Used in creating an OSC server endpoint. */ static lookup s_signal_msgs = { { tag::connect, { "/signal/connect", "ss" } }, { tag::created, { "/signal/created", "ss" } }, { tag::disconnect, { "/signal/disconnect", "ss" } }, { tag::generic, { "", "" } }, { tag::hello, { "/signal/hello", "ss" } }, { tag::list, { "/signal/list", "" } }, { tag::removed, { "/signal/removed", "ss" } }, { tag::renamed, { "/signal/renamed", "ss" } }, { tag::reply, { "/reply", "" } } }; /** * Used by NSM itself. */ static lookup s_non_msgs = { { tag::addstrip, { "/non/mixer/add_strip", "" } }, { tag::hello, { "/non/hello", "ssss" } }, { tag::oscreply, { "", "" } }, { tag::stripbynumber, { "", "" } } }; /** * Generic lookup function for a lookup table. * * \param table * Provides the particular table (e.g. client versus server) to be looked * up. * * \param t * Provides the "tag" enumeration value to be used to look up the desired * message and its message pattern. * * \param [out] message * The destination for the message text that was found. * * \param [out] pattern * The destination for the pattern text that was found. * * \return * Returns true if the tag \a t was found. If false is returned, do not * use the message and pattern. */ static bool nsm_lookup ( const lookup & table, tag t, std::string & message, std::string & pattern ) { bool result = false; auto lci = table.find(t); /* lookup::const_iterator */ if (lci != table.end()) { result = true; message = lci->second.msg_text; pattern = lci->second.msg_pattern; } return result; } /* * The rest of these free functions provide easy lookup of the various messages * and their patterns. */ bool client_msg (tag t, std::string & message, std::string & pattern) { return nsm_lookup(s_client_msgs, t, message, pattern); } bool gui_client_msg (tag t, std::string & message, std::string & pattern) { return nsm_lookup(s_gui_client_msgs, t, message, pattern); } bool gui_session_msg (tag t, std::string & message, std::string & pattern) { return nsm_lookup(s_gui_session_msgs, t, message, pattern); } bool proxy_msg (tag t, std::string & message, std::string & pattern) { return nsm_lookup(s_proxy_msgs, t, message, pattern); } bool server_msg (tag t, std::string & message, std::string & pattern) { return nsm_lookup(s_server_msgs, t, message, pattern); } bool misc_msg (tag t, std::string & message, std::string & pattern) { return nsm_lookup(s_misc_msgs, t, message, pattern); } /** * Inverse lookup. Given the message-path name, returns the code. * * Not sure if we need to check the pattern as well. */ tag nsm_lookup_tag ( const lookup & table, const std::string & message, const std::string & pattern = "X" ) { tag result = tag::null; for (auto lci = table.begin(); lci != table.end(); ++lci) { bool match = lci->second.msg_text == message; if (match) { if (pattern != "X") match = lci->second.msg_pattern == pattern; if (match) { result = lci->first; break; } } } return result; } /* * The rest of these free functions provide easy lookup of the various tags * from the given message. */ tag client_tag (const std::string & message, const std::string & pattern) { return nsm_lookup_tag(s_client_msgs, message, pattern); } tag server_tag (const std::string & message, const std::string & pattern) { return nsm_lookup_tag(s_server_msgs, message, pattern); } /* * Additional helpful functions. */ const std::string & default_ext () { static const std::string sm_default_ext("nsm"); return sm_default_ext; } const std::string & dirty_msg (bool isdirty) { tag t = isdirty ? tag::dirty : tag::clean ; return s_client_msgs[t].msg_text; } const std::string & visible_msg (bool isvisible) { tag t = isvisible ? tag::shown : tag::hidden ; return s_client_msgs[t].msg_text; } const std::string & url () { static const std::string sm_url_var("NSM_URL"); return sm_url_var; } bool is_announce (const std::string & s) { return s == s_server_msgs[tag::announce].msg_text; } } // namespace nsm } // namespace seq66 /* * nsmmessagesex.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: libsessions/src/nsm/nsmserver.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file nsmserver.cpp * * This module could serve as an alternative to nsmd (Non Session Manager * daemon) someday. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-03-11 * \updates 2020-03-11 * \license GNU GPLv2 or above * */ #include /* std::unique_ptr<> */ #include "nsm/nsmserver.hpp" /* seq66::nsmserver class */ namespace seq66 { /** * Principal constructor */ nsmserver::nsmserver (const std::string & nsmurl) : nsmbase (nsmurl) { // } std::unique_ptr create_nsmserver () { std::unique_ptr result; std::string url = nsm::get_url(); if (! url.empty()) { // result.reset(new nsmserver(url)); } return result; } } // namespace seq66 /* * nsmserver.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: licenses/LICENSE.FDL ================================================ GNU Free Documentation License Version 1.3, 3 November 2008 Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 0. PREAMBLE The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others. This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference. 1. APPLICABILITY AND DEFINITIONS This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law. A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language. A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none. The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words. A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque". Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only. The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text. The "publisher" means any person or entity that distributes copies of the Document to the public. A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the Document means that it remains a section "Entitled XYZ" according to this definition. The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License. 2. VERBATIM COPYING You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies. 3. COPYING IN QUANTITY If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document. 4. MODIFICATIONS You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version: A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement. C. State on the Title page the name of the publisher of the Modified Version, as the publisher. D. Preserve all the copyright notices of the Document. E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices. F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below. G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice. H. Include an unaltered copy of this License. I. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence. J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. K. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. M. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version. N. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section. O. Preserve any Warranty Disclaimers. If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles. You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties--for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version. 5. COMBINING DOCUMENTS You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled "History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all sections Entitled "Endorsements". 6. COLLECTIONS OF DOCUMENTS You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document. 7. AGGREGATION WITH INDEPENDENT WORKS A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate. 8. TRANSLATION Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail. If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title. 9. TERMINATION You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License. However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material does not give you any rights to use it. 10. FUTURE REVISIONS OF THIS LICENSE The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See https://www.gnu.org/licenses/. Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Document. 11. RELICENSING "Massive Multiauthor Collaboration Site" (or "MMC Site") means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A "Massive Multiauthor Collaboration" (or "MMC") contained in the site means any set of copyrightable works thus published on the MMC site. "CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization. "Incorporate" means to publish or republish a Document, in whole or in part, as part of another Document. An MMC is "eligible for relicensing" if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008. The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing. ADDENDUM: How to use this License for your documents To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the "with...Texts." line with this: with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation. If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software. ================================================ FILE: licenses/LICENSE.GPL ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. 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 3 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, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program 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, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU 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. But first, please read . ================================================ FILE: licenses/LICENSE.LGPL ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: m4/Makefile.am ================================================ #******************************************************************************* # Makefile.am (m4) #------------------------------------------------------------------------------- ## # \file Makefile.am # \library seq66 # \author Chris Ahlstrom # \date 2018-11-11 # \update 2022-01-04 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the seq66 # library's main 'm4' directory. This makefile provides mostly just a # way to make sure the m4 files are included in the 'dist' target. # #------------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile PACKAGE = @PACKAGE@ VERSION = @VERSION@ #******************************************************************************* # EXTRA_DIST #------------------------------------------------------------------------------- EXTRA_DIST = alsa.m4 \ ax_cxx_compile_stdcxx.m4 \ ax_cxx_compile_stdcxx_11.m4 \ ax_have_qt.m4 \ ax_have_qt_ex.m4 \ ax_have_qt_min.m4 \ ax_prefix_config_h.m4 \ ax_prog_flex.m4 \ ax_pthread.m4 \ ax_require_defined.m4 \ gcc-version.m4 \ inttypes.m4 \ isc-posix.m4 \ libtool.m4 \ ltoptions.m4 \ ltsugar.m4 \ ltversion.m4 \ mm-common.m4 \ mm-warnings.m4 \ pkg.m4 \ seq64_mingw_dll.m4 \ threadlib.m4 \ win32msc.m4 \ xpc_debug.m4 \ xpc_doxygen.m4 \ xpc_errorlog.m4 \ xpc_mingw.m4 \ xpc_mingw.m4 \ xpc_nullptr.m4 \ xpc_thisptr.m4 #****************************************************************************** # Makefile.am (m4) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: m4/alsa.m4 ================================================ dnl Configure Paths for Alsa dnl Some modifications by Richard Boulton dnl Christopher Lansdown dnl Jaroslav Kysela dnl Last modification: $Id: alsa.m4,v 1.24 2004/09/15 18:48:07 tiwai Exp $ dnl dnl AM_PATH_ALSA([MINIMUM-VERSION [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) dnl Test for libasound, and define ALSA_CFLAGS, ALSA_LIBS and dnl ALSA_TOPOLOGY_LIBS as appropriate. dnl dnl enables arguments --with-alsa-prefix= dnl --with-alsa-inc-prefix= dnl --disable-alsatest dnl dnl For backwards compatibility, if ACTION_IF_NOT_FOUND is not specified, dnl and the alsa libraries are not found, a fatal AC_MSG_ERROR() will result. dnl AC_DEFUN([AM_PATH_ALSA], [dnl Save the original CFLAGS, LDFLAGS, and LIBS alsa_save_CFLAGS="$CFLAGS" alsa_save_LDFLAGS="$LDFLAGS" alsa_save_LIBS="$LIBS" alsa_found=yes alsa_topology_found=no dnl dnl Get the cflags and libraries for alsa dnl AC_ARG_WITH(alsa-prefix, AS_HELP_STRING([--with-alsa-prefix=PFX], [Prefix where Alsa library is installed(optional)]), [alsa_prefix="$withval"], [alsa_prefix=""]) AC_ARG_WITH(alsa-inc-prefix, AS_HELP_STRING([--with-alsa-inc-prefix=PFX], [Prefix where include libraries are (optional)]), [alsa_inc_prefix="$withval"], [alsa_inc_prefix=""]) AC_ARG_ENABLE(alsa-topology, AS_HELP_STRING([--enable-alsatopology], [Force to use the Alsa topology library]), [enable_atopology="$enableval"], [enable_atopology=no]) AC_ARG_ENABLE(alsatest, AS_HELP_STRING([--disable-alsatest], [Do not try to compile and run a test Alsa program]), [enable_alsatest="$enableval"], [enable_alsatest=yes]) dnl Add any special include directories AC_MSG_CHECKING(for ALSA CFLAGS) if test "$alsa_inc_prefix" != "" ; then ALSA_CFLAGS="$ALSA_CFLAGS -I$alsa_inc_prefix" CFLAGS="$CFLAGS -I$alsa_inc_prefix" fi AC_MSG_RESULT($ALSA_CFLAGS) AC_CHECK_LIB(c, dlopen, LIBDL="", [AC_CHECK_LIB(dl, dlopen, LIBDL="-ldl")]) dnl add any special lib dirs AC_MSG_CHECKING(for ALSA LDFLAGS) if test "$alsa_prefix" != "" ; then ALSA_LIBS="$ALSA_LIBS -L$alsa_prefix" LDFLAGS="$LDFLAGS $ALSA_LIBS" fi dnl add the alsa library ALSA_LIBS="$ALSA_LIBS -lasound -lm $LIBDL -lpthread" LIBS="$ALSA_LIBS $LIBS" AC_MSG_RESULT($ALSA_LIBS) dnl Check for a working version of libasound that is of the right version. if test "x$enable_alsatest" = "xyes"; then AC_MSG_CHECKING([required libasound headers version]) min_alsa_version=ifelse([$1], , 0.1.1, $1) no_alsa="" alsa_min_major_version=`echo $min_alsa_version | \ sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` alsa_min_minor_version=`echo $min_alsa_version | \ sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` alsa_min_micro_version=`echo $min_alsa_version | \ sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` AC_MSG_RESULT($alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version) AC_LANG_PUSH([C]) AC_MSG_CHECKING([for libasound headers version >= $alsa_min_major_version.$alsa_min_minor_version.$alsa_min_micro_version ($min_alsa_version)]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ /* ensure backward compatibility */ #if !defined(SND_LIB_MAJOR) && defined(SOUNDLIB_VERSION_MAJOR) #define SND_LIB_MAJOR SOUNDLIB_VERSION_MAJOR #endif #if !defined(SND_LIB_MINOR) && defined(SOUNDLIB_VERSION_MINOR) #define SND_LIB_MINOR SOUNDLIB_VERSION_MINOR #endif #if !defined(SND_LIB_SUBMINOR) && defined(SOUNDLIB_VERSION_SUBMINOR) #define SND_LIB_SUBMINOR SOUNDLIB_VERSION_SUBMINOR #endif # if(SND_LIB_MAJOR > $alsa_min_major_version) exit(0); # else # if(SND_LIB_MAJOR < $alsa_min_major_version) # error not present # endif # if(SND_LIB_MINOR > $alsa_min_minor_version) exit(0); # else # if(SND_LIB_MINOR < $alsa_min_minor_version) # error not present # endif # if(SND_LIB_SUBMINOR < $alsa_min_micro_version) # error not present # endif # endif # endif exit(0); ]])], [AC_MSG_RESULT(found.)], [AC_MSG_RESULT(not present.) ifelse([$3], , [AC_MSG_ERROR(Sufficiently new version of libasound not found.)]) alsa_found=no] ) AC_LANG_POP([C]) AC_LANG_PUSH([C]) AC_MSG_CHECKING([for libatopology (sound headers version > 1.1.9)]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #include ]], [[ /* ensure backward compatibility */ #if !defined(SND_LIB_VERSION) #define SND_LIB_VERSION 0 #endif #if SND_LIB_VERSION > 0x00010109 exit(0); #else # error not present #endif exit(0); ]])], [AC_MSG_RESULT(yes) enable_atopology="yes"], [AC_MSG_RESULT(no)] ) AC_LANG_POP([C]) fi dnl Now that we know that we have the right version, let's see if we have the library and not just the headers. if test "x$enable_alsatest" = "xyes"; then AC_CHECK_LIB([asound], [snd_ctl_open],, [ifelse([$3], , [AC_MSG_ERROR(No linkable libasound was found.)]) alsa_found=no] ) if test "x$enable_atopology" = "xyes"; then alsa_topology_found=yes alsa_save_LIBS2="$LIBS" AC_CHECK_LIB([atopology], [snd_tplg_new],, [ifelse([$3], , [AC_MSG_ERROR(No linkable libatopology was found.)]) alsa_topology_found=no, ] ) LIBS="$alsa_save_LIBS2" fi else if test "x$enable_atopology" = "xyes"; then alsa_topology_found=yes fi fi if test "x$alsa_found" = "xyes" ; then ifelse([$2], , :, [$2]) LIBS=`echo $LIBS | sed 's/-lasound//g'` LIBS=`echo $LIBS | sed 's/ //'` LIBS="-lasound $LIBS" fi if test "x$alsa_found" = "xno" ; then ifelse([$3], , :, [$3]) CFLAGS="$alsa_save_CFLAGS" LDFLAGS="$alsa_save_LDFLAGS" LIBS="$alsa_save_LIBS" ALSA_CFLAGS="" ALSA_LIBS="" ALSA_TOPOLOGY_LIBS="" fi dnl add the alsa topology library; must be at the end AC_MSG_CHECKING(for ALSA topology LDFLAGS) if test "x$alsa_topology_found" = "xyes"; then ALSA_TOPOLOGY_LIBS="$ALSA_TOPOLOGY_LIBS -latopology" fi AC_MSG_RESULT($ALSA_TOPOLOGY_LIBS) dnl That should be it. Now just export out symbols: AC_SUBST(ALSA_CFLAGS) AC_SUBST(ALSA_LIBS) AC_SUBST(ALSA_TOPOLOGY_LIBS) ]) ================================================ FILE: m4/ax_cxx_compile_stdcxx.m4 ================================================ # =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html # =========================================================================== # # SYNOPSIS # # AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) # # DESCRIPTION # # Check for baseline language coverage in the compiler for the specified # version of the C++ standard. If necessary, add switches to CXX and # CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) # or '14' (for the C++14 standard). # # The second argument, if specified, indicates whether you insist on an # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. # -std=c++11). If neither is specified, you get whatever works, with # preference for an extended mode. # # The third argument, if specified 'mandatory' or if left unspecified, # indicates that baseline support for the specified C++ standard is # required and that the macro should error out if no mode with that # support is found. If specified 'optional', then configuration proceeds # regardless, after defining HAVE_CXX${VERSION} if and only if a # supporting mode is found. # # LICENSE # # Copyright (c) 2008 Benjamin Kosnik # Copyright (c) 2012 Zack Weinberg # Copyright (c) 2013 Roy Stogner # Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov # Copyright (c) 2015 Paul Norman # Copyright (c) 2015 Moritz Klammler # Copyright (c) 2016, 2018 Krzesimir Nowak # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 10 dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro dnl (serial version number 13). AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], [$1], [14], [ax_cxx_compile_alternatives="14 1y"], [$1], [17], [ax_cxx_compile_alternatives="17 1z"], [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$2], [], [], [$2], [ext], [], [$2], [noext], [], [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], [$3], [optional], [ax_cxx_compile_cxx$1_required=false], [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) AC_LANG_PUSH([C++])dnl ac_success=no m4_if([$2], [noext], [], [dnl if test x$ac_success = xno; then for alternative in ${ax_cxx_compile_alternatives}; do switch="-std=gnu++${alternative}" cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, [ac_save_CXX="$CXX" CXX="$CXX $switch" AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [eval $cachevar=yes], [eval $cachevar=no]) CXX="$ac_save_CXX"]) if eval test x\$$cachevar = xyes; then CXX="$CXX $switch" if test -n "$CXXCPP" ; then CXXCPP="$CXXCPP $switch" fi ac_success=yes break fi done fi]) m4_if([$2], [ext], [], [dnl if test x$ac_success = xno; then dnl HP's aCC needs +std=c++11 according to: dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf dnl Cray's crayCC needs "-h std=c++11" for alternative in ${ax_cxx_compile_alternatives}; do for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, [ac_save_CXX="$CXX" CXX="$CXX $switch" AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [eval $cachevar=yes], [eval $cachevar=no]) CXX="$ac_save_CXX"]) if eval test x\$$cachevar = xyes; then CXX="$CXX $switch" if test -n "$CXXCPP" ; then CXXCPP="$CXXCPP $switch" fi ac_success=yes break fi done if test x$ac_success = xyes; then break fi done fi]) AC_LANG_POP([C++]) if test x$ax_cxx_compile_cxx$1_required = xtrue; then if test x$ac_success = xno; then AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) fi fi if test x$ac_success = xno; then HAVE_CXX$1=0 AC_MSG_NOTICE([No compiler with C++$1 support was found]) else HAVE_CXX$1=1 AC_DEFINE(HAVE_CXX$1,1, [define if the compiler supports basic C++$1 syntax]) fi AC_SUBST(HAVE_CXX$1) ]) dnl Test body for checking C++11 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 ) dnl Test body for checking C++14 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 ) m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 ) dnl Tests for new features in C++11 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ // If the compiler admits that it is not ready for C++11, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201103L #error "This is not a C++11 compiler" #else namespace cxx11 { namespace test_static_assert { template struct check { static_assert(sizeof(int) <= sizeof(T), "not big enough"); }; } namespace test_final_override { struct Base { virtual void f() {} }; struct Derived : public Base { virtual void f() override {} }; } namespace test_double_right_angle_brackets { template < typename T > struct check {}; typedef check single_type; typedef check> double_type; typedef check>> triple_type; typedef check>>> quadruple_type; } namespace test_decltype { int f() { int a = 1; decltype(a) b = 2; return a + b; } } namespace test_type_deduction { template < typename T1, typename T2 > struct is_same { static const bool value = false; }; template < typename T > struct is_same { static const bool value = true; }; template < typename T1, typename T2 > auto add(T1 a1, T2 a2) -> decltype(a1 + a2) { return a1 + a2; } int test(const int c, volatile int v) { static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == false, ""); auto ac = c; auto av = v; auto sumi = ac + av + 'x'; auto sumf = ac + av + 1.0; static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == true, ""); return (sumf > 0.0) ? sumi : add(c, v); } } namespace test_noexcept { int f() { return 0; } int g() noexcept { return 0; } static_assert(noexcept(f()) == false, ""); static_assert(noexcept(g()) == true, ""); } namespace test_constexpr { template < typename CharT > unsigned long constexpr strlen_c_r(const CharT *const s, const unsigned long acc) noexcept { return *s ? strlen_c_r(s + 1, acc + 1) : acc; } template < typename CharT > unsigned long constexpr strlen_c(const CharT *const s) noexcept { return strlen_c_r(s, 0UL); } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("1") == 1UL, ""); static_assert(strlen_c("example") == 7UL, ""); static_assert(strlen_c("another\0example") == 7UL, ""); } namespace test_rvalue_references { template < int N > struct answer { static constexpr int value = N; }; answer<1> f(int&) { return answer<1>(); } answer<2> f(const int&) { return answer<2>(); } answer<3> f(int&&) { return answer<3>(); } void test() { int i = 0; const int c = 0; static_assert(decltype(f(i))::value == 1, ""); static_assert(decltype(f(c))::value == 2, ""); static_assert(decltype(f(0))::value == 3, ""); } } namespace test_uniform_initialization { struct test { static const int zero {}; static const int one {1}; }; static_assert(test::zero == 0, ""); static_assert(test::one == 1, ""); } namespace test_lambdas { void test1() { auto lambda1 = [](){}; auto lambda2 = lambda1; lambda1(); lambda2(); } int test2() { auto a = [](int i, int j){ return i + j; }(1, 2); auto b = []() -> int { return '0'; }(); auto c = [=](){ return a + b; }(); auto d = [&](){ return c; }(); auto e = [a, &b](int x) mutable { const auto identity = [](int y){ return y; }; for (auto i = 0; i < a; ++i) a += b--; return x + identity(a + b); }(0); return a + b + c + d + e; } int test3() { const auto nullary = [](){ return 0; }; const auto unary = [](int x){ return x; }; using nullary_t = decltype(nullary); using unary_t = decltype(unary); const auto higher1st = [](nullary_t f){ return f(); }; const auto higher2nd = [unary](nullary_t f1){ return [unary, f1](unary_t f2){ return f2(unary(f1())); }; }; return higher1st(nullary) + higher2nd(nullary)(unary); } } namespace test_variadic_templates { template struct sum; template struct sum { static constexpr auto value = N0 + sum::value; }; template <> struct sum<> { static constexpr auto value = 0; }; static_assert(sum<>::value == 0, ""); static_assert(sum<1>::value == 1, ""); static_assert(sum<23>::value == 23, ""); static_assert(sum<1, 2>::value == 3, ""); static_assert(sum<5, 5, 11>::value == 21, ""); static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); } // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function // because of this. namespace test_template_alias_sfinae { struct foo {}; template using member = typename T::member_type; template void func(...) {} template void func(member*) {} void test(); void test() { func(0); } } } // namespace cxx11 #endif // __cplusplus >= 201103L ]]) dnl Tests for new features in C++14 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ // If the compiler admits that it is not ready for C++14, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201402L #error "This is not a C++14 compiler" #else namespace cxx14 { namespace test_polymorphic_lambdas { int test() { const auto lambda = [](auto&&... args){ const auto istiny = [](auto x){ return (sizeof(x) == 1UL) ? 1 : 0; }; const int aretiny[] = { istiny(args)... }; return aretiny[0]; }; return lambda(1, 1L, 1.0f, '1'); } } namespace test_binary_literals { constexpr auto ivii = 0b0000000000101010; static_assert(ivii == 42, "wrong value"); } namespace test_generalized_constexpr { template < typename CharT > constexpr unsigned long strlen_c(const CharT *const s) noexcept { auto length = 0UL; for (auto p = s; *p; ++p) ++length; return length; } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("x") == 1UL, ""); static_assert(strlen_c("test") == 4UL, ""); static_assert(strlen_c("another\0test") == 7UL, ""); } namespace test_lambda_init_capture { int test() { auto x = 0; const auto lambda1 = [a = x](int b){ return a + b; }; const auto lambda2 = [a = lambda1(x)](){ return a; }; return lambda2(); } } namespace test_digit_separators { constexpr auto ten_million = 100'000'000; static_assert(ten_million == 100000000, ""); } namespace test_return_type_deduction { auto f(int& x) { return x; } decltype(auto) g(int& x) { return x; } template < typename T1, typename T2 > struct is_same { static constexpr auto value = false; }; template < typename T > struct is_same { static constexpr auto value = true; }; int test() { auto x = 0; static_assert(is_same::value, ""); static_assert(is_same::value, ""); return x; } } } // namespace cxx14 #endif // __cplusplus >= 201402L ]]) dnl Tests for new features in C++17 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ // If the compiler admits that it is not ready for C++17, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201703L #error "This is not a C++17 compiler" #else #include #include #include namespace cxx17 { namespace test_constexpr_lambdas { constexpr int foo = [](){return 42;}(); } namespace test::nested_namespace::definitions { } namespace test_fold_expression { template int multiply(Args... args) { return (args * ... * 1); } template bool all(Args... args) { return (args && ...); } } namespace test_extended_static_assert { static_assert (true); } namespace test_auto_brace_init_list { auto foo = {5}; auto bar {5}; static_assert(std::is_same, decltype(foo)>::value); static_assert(std::is_same::value); } namespace test_typename_in_template_template_parameter { template typename X> struct D; } namespace test_fallthrough_nodiscard_maybe_unused_attributes { int f1() { return 42; } [[nodiscard]] int f2() { [[maybe_unused]] auto unused = f1(); switch (f1()) { case 17: f1(); [[fallthrough]]; case 42: f1(); } return f1(); } } namespace test_extended_aggregate_initialization { struct base1 { int b1, b2 = 42; }; struct base2 { base2() { b3 = 42; } int b3; }; struct derived : base1, base2 { int d; }; derived d1 {{1, 2}, {}, 4}; // full initialization derived d2 {{}, {}, 4}; // value-initialized bases } namespace test_general_range_based_for_loop { struct iter { int i; int& operator* () { return i; } const int& operator* () const { return i; } iter& operator++() { ++i; return *this; } }; struct sentinel { int i; }; bool operator== (const iter& i, const sentinel& s) { return i.i == s.i; } bool operator!= (const iter& i, const sentinel& s) { return !(i == s); } struct range { iter begin() const { return {0}; } sentinel end() const { return {5}; } }; void f() { range r {}; for (auto i : r) { [[maybe_unused]] auto v = i; } } } namespace test_lambda_capture_asterisk_this_by_value { struct t { int i; int foo() { return [*this]() { return i; }(); } }; } namespace test_enum_class_construction { enum class byte : unsigned char {}; byte foo {42}; } namespace test_constexpr_if { template int f () { if constexpr(cond) { return 13; } else { return 42; } } } namespace test_selection_statement_with_initializer { int f() { return 13; } int f2() { if (auto i = f(); i > 0) { return 3; } switch (auto i = f(); i + 4) { case 17: return 2; default: return 1; } } } namespace test_template_argument_deduction_for_class_templates { template struct pair { pair (T1 p1, T2 p2) : m1 {p1}, m2 {p2} {} T1 m1; T2 m2; }; void f() { [[maybe_unused]] auto p = pair{13, 42u}; } } namespace test_non_type_auto_template_parameters { template struct B {}; B<5> b1; B<'a'> b2; } namespace test_structured_bindings { int arr[2] = { 1, 2 }; std::pair pr = { 1, 2 }; auto f1() -> int(&)[2] { return arr; } auto f2() -> std::pair& { return pr; } struct S { int x1 : 2; volatile double y1; }; S f3() { return {}; } auto [ x1, y1 ] = f1(); auto& [ xr1, yr1 ] = f1(); auto [ x2, y2 ] = f2(); auto& [ xr2, yr2 ] = f2(); const auto [ x3, y3 ] = f3(); } namespace test_exception_spec_type_system { struct Good {}; struct Bad {}; void g1() noexcept; void g2(); template Bad f(T*, T*); template Good f(T1*, T2*); static_assert (std::is_same_v); } namespace test_inline_variables { template void f(T) {} template inline T g(T) { return T{}; } template<> inline void f<>(int) {} template<> int g<>(int) { return 5; } } } // namespace cxx17 #endif // __cplusplus < 201703L ]]) ================================================ FILE: m4/ax_cxx_compile_stdcxx_11.m4 ================================================ # ============================================================================= # https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html # ============================================================================= # # SYNOPSIS # # AX_CXX_COMPILE_STDCXX_11([ext|noext], [mandatory|optional]) # # DESCRIPTION # # Check for baseline language coverage in the compiler for the C++11 # standard; if necessary, add switches to CXX and CXXCPP to enable # support. # # This macro is a convenience alias for calling the AX_CXX_COMPILE_STDCXX # macro with the version set to C++11. The two optional arguments are # forwarded literally as the second and third argument respectively. # Please see the documentation for the AX_CXX_COMPILE_STDCXX macro for # more information. If you want to use this macro, you also need to # download the ax_cxx_compile_stdcxx.m4 file. # # LICENSE # # Copyright (c) 2008 Benjamin Kosnik # Copyright (c) 2012 Zack Weinberg # Copyright (c) 2013 Roy Stogner # Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov # Copyright (c) 2015 Paul Norman # Copyright (c) 2015 Moritz Klammler # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 18 AX_REQUIRE_DEFINED([AX_CXX_COMPILE_STDCXX]) AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [AX_CXX_COMPILE_STDCXX([11], [$1], [$2])]) ================================================ FILE: m4/ax_have_qt.m4 ================================================ # =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_have_qt.html # =========================================================================== # # SYNOPSIS # # AX_HAVE_QT # # DESCRIPTION # # Searches $PATH and queries qmake for Qt include files, libraries and Qt # binary utilities. The macro only supports Qt5 or later. # # The following shell variable is set to either "yes" or "no": # # have_qt # # Additionally, the following variables are exported: # # QT_CXXFLAGS # QT_LIBS # QT_MOC # QT_UIC # QT_RCC # QT_LRELEASE # QT_LUPDATE # QT_DIR # QMAKE # # which respectively contain an "-I" flag pointing to the Qt include # directory, link flags necessary to link with Qt and X, the full path to # the meta object compiler and the user interface compiler both, and # finally the variable QTDIR as Qt likes to see it defined. # # Example lines for Makefile.in: # # CXXFLAGS = @QT_CXXFLAGS@ # MOC = @QT_MOC@ # # After the variables have been set, a trial compile and link is performed # to check the correct functioning of the meta object compiler. This test # may fail when the different detected elements stem from different # releases of the Qt framework. In that case, an error message is emitted # and configure stops. # # No common variables such as $LIBS or $CFLAGS are polluted. # # LICENSE # # Copyright (c) 2008 Bastiaan Veelo # Copyright (c) 2014 Alex Henrie # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 25 AU_ALIAS([BNV_HAVE_QT], [AX_HAVE_QT]) AC_DEFUN([AX_HAVE_QT], [ AC_REQUIRE([AC_PROG_CXX]) AC_REQUIRE([AC_PATH_X]) AC_REQUIRE([AC_PATH_XTRA]) # openSUSE leap 15.3 installs qmake-qt5, not qmake, for example. # Store the full name (like qmake-qt5) into QMAKE # and the specifier (like -qt5 or empty) into am_have_qt_qmexe_suff. AC_ARG_VAR([QMAKE],"Qt make tool") AC_CHECK_TOOLS([QMAKE],[qmake qmake-qt6 qmake-qt5],[false]) AC_MSG_CHECKING(for Qt) am_have_qt_qmexe_suff=`echo $QMAKE | sed 's,^.*qmake,,'` # If we have Qt5 or later in the path, we're golden ver=`$QMAKE --version | grep -o "Qt version ."` if test "$ver" ">" "Qt version 4"; then have_qt=yes # This pro file dumps qmake's variables, but it only works on Qt 5 or later am_have_qt_dir=`mktemp -d` am_have_qt_pro="$am_have_qt_dir/test.pro" am_have_qt_stash="$am_have_qt_dir/.qmake.stash" am_have_qt_makefile="$am_have_qt_dir/Makefile" # http://qt-project.org/doc/qt-5/qmake-variable-reference.html#qt cat > $am_have_qt_pro << EOF win32 { CONFIG -= debug_and_release CONFIG += release } qtHaveModule(axcontainer): QT += axcontainer qtHaveModule(axserver): QT += axserver qtHaveModule(concurrent): QT += concurrent qtHaveModule(core): QT += core qtHaveModule(dbus): QT += dbus qtHaveModule(declarative): QT += declarative qtHaveModule(designer): QT += designer qtHaveModule(gui): QT += gui qtHaveModule(help): QT += help qtHaveModule(multimedia): QT += multimedia qtHaveModule(multimediawidgets): QT += multimediawidgets qtHaveModule(network): QT += network qtHaveModule(opengl): QT += opengl qtHaveModule(printsupport): QT += printsupport qtHaveModule(qml): QT += qml qtHaveModule(qmltest): QT += qmltest qtHaveModule(x11extras): QT += x11extras qtHaveModule(script): QT += script qtHaveModule(scripttools): QT += scripttools qtHaveModule(sensors): QT += sensors qtHaveModule(serialport): QT += serialport qtHaveModule(sql): QT += sql qtHaveModule(svg): QT += svg qtHaveModule(testlib): QT += testlib qtHaveModule(uitools): QT += uitools qtHaveModule(webkit): QT += webkit qtHaveModule(webkitwidgets): QT += webkitwidgets qtHaveModule(xml): QT += xml qtHaveModule(xmlpatterns): QT += xmlpatterns percent.target = % percent.commands = @echo -n "\$(\$(@))\ " QMAKE_EXTRA_TARGETS += percent EOF $QMAKE $am_have_qt_pro -o $am_have_qt_makefile QT_CXXFLAGS=`cd $am_have_qt_dir; make -s -f $am_have_qt_makefile CXXFLAGS INCPATH` QT_LIBS=`cd $am_have_qt_dir; make -s -f $am_have_qt_makefile LIBS` rm $am_have_qt_pro $am_have_qt_stash $am_have_qt_makefile rmdir $am_have_qt_dir # Look for specific tools in $PATH QT_MOC=`which moc$am_have_qt_qmexe_suff` QT_UIC=`which uic$am_have_qt_qmexe_suff` QT_RCC=`which rcc$am_have_qt_qmexe_suff` QT_LRELEASE=`which lrelease$am_have_qt_qmexe_suff` QT_LUPDATE=`which lupdate$am_have_qt_qmexe_suff` # Get Qt version from qmake QT_DIR=`$QMAKE --version | grep -o -E /.+` # All variables are defined, report the result AC_MSG_RESULT([$have_qt: QT_CXXFLAGS=$QT_CXXFLAGS QT_DIR=$QT_DIR QT_LIBS=$QT_LIBS QT_UIC=$QT_UIC QT_MOC=$QT_MOC QT_RCC=$QT_RCC QT_LRELEASE=$QT_LRELEASE QT_LUPDATE=$QT_LUPDATE]) else # Qt was not found have_qt=no QT_CXXFLAGS= QT_DIR= QT_LIBS= QT_UIC= QT_MOC= QT_RCC= QT_LRELEASE= QT_LUPDATE= AC_MSG_RESULT($have_qt) fi AC_SUBST(QT_CXXFLAGS) AC_SUBST(QT_DIR) AC_SUBST(QT_LIBS) AC_SUBST(QT_UIC) AC_SUBST(QT_MOC) AC_SUBST(QT_RCC) AC_SUBST(QT_LRELEASE) AC_SUBST(QT_LUPDATE) AC_SUBST(QMAKE) #### Being paranoid: if test x"$have_qt" = xyes; then AC_MSG_CHECKING(correct functioning of Qt installation) AC_CACHE_VAL(ax_cv_qt_test_result, [ cat > ax_qt_test.h << EOF #include class Test : public QObject { Q_OBJECT public: Test() {} ~Test() {} public slots: void receive() {} signals: void send(); }; EOF cat > ax_qt_main.$ac_ext << EOF #include "ax_qt_test.h" #include int main( int argc, char **argv ) { QApplication app( argc, argv ); Test t; QObject::connect( &t, SIGNAL(send()), &t, SLOT(receive()) ); } EOF ax_cv_qt_test_result="failure" ax_try_1="$QT_MOC ax_qt_test.h -o moc_ax_qt_test.$ac_ext >/dev/null 2>/dev/null" AC_TRY_EVAL(ax_try_1) if test x"$ac_status" != x0; then echo "$ax_err_1" >&AS_MESSAGE_LOG_FD echo "configure: could not run $QT_MOC on:" >&AS_MESSAGE_LOG_FD cat ax_qt_test.h >&AS_MESSAGE_LOG_FD else ax_try_2="$CXX $QT_CXXFLAGS -c $CXXFLAGS -o moc_ax_qt_test.o moc_ax_qt_test.$ac_ext >/dev/null 2>/dev/null" AC_TRY_EVAL(ax_try_2) if test x"$ac_status" != x0; then echo "$ax_err_2" >&AS_MESSAGE_LOG_FD echo "configure: could not compile:" >&AS_MESSAGE_LOG_FD cat moc_ax_qt_test.$ac_ext >&AS_MESSAGE_LOG_FD else ax_try_3="$CXX $QT_CXXFLAGS -c $CXXFLAGS -o ax_qt_main.o ax_qt_main.$ac_ext >/dev/null 2>/dev/null" AC_TRY_EVAL(ax_try_3) if test x"$ac_status" != x0; then echo "$ax_err_3" >&AS_MESSAGE_LOG_FD echo "configure: could not compile:" >&AS_MESSAGE_LOG_FD cat ax_qt_main.$ac_ext >&AS_MESSAGE_LOG_FD else ax_try_4="$CXX -o ax_qt_main ax_qt_main.o moc_ax_qt_test.o $QT_LIBS $LIBS >/dev/null 2>/dev/null" AC_TRY_EVAL(ax_try_4) if test x"$ac_status" != x0; then echo "$ax_err_4" >&AS_MESSAGE_LOG_FD else ax_cv_qt_test_result="success" fi fi fi fi ])dnl AC_CACHE_VAL ax_cv_qt_test_result AC_MSG_RESULT([$ax_cv_qt_test_result]) if test x"$ax_cv_qt_test_result" = "xfailure"; then AC_MSG_ERROR([Failed to find matching components of a complete Qt installation. Try using more options, see ./configure --help.]) fi rm -f ax_qt_test.h moc_ax_qt_test.$ac_ext moc_ax_qt_test.o \ ax_qt_main.$ac_ext ax_qt_main.o ax_qt_main fi ]) ================================================ FILE: m4/ax_have_qt_clang.m4 ================================================ # =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_have_qt_clang.html # =========================================================================== # # SYNOPSIS # # AX_HAVE_QT_CLANG # # DESCRIPTION # # Modified by C. Ahlstrom 2023-12-08 to remove the paranoid testing # of Qt when using Clang-16. # # A modification of ax_have_qt_min.m4. # # =========================================================================== #serial 19 AU_ALIAS([BNV_HAVE_QT], [AX_HAVE_QT_CLANG]) AC_DEFUN([AX_HAVE_QT_CLANG], [ AC_REQUIRE([AC_PROG_CXX]) AC_REQUIRE([AC_PATH_X]) AC_REQUIRE([AC_PATH_XTRA]) AC_MSG_CHECKING(for Qt) # From serial 19 to handle issue #54. # # openSUSE leap 15.3 installs qmake-qt5, not qmake, for example. # Store the full name (like qmake-qt5) into am_have_qt_qmexe # and the specifier (like -qt5 or empty) into am_have_qt_qmexe_suff. AC_CHECK_PROGS(am_have_qt_qmexe,qmake qmake-qt6 qmake-qt5,[]) am_have_qt_qmexe_suff=`echo $am_have_qt_qmexe | cut -b 6-` # If we have Qt5 or later in the path, we're golden ver=`$am_have_qt_qmexe --version | grep -o "Qt version ."` if test "$ver" ">" "Qt version 4"; then have_qt=yes # This pro file dumps qmake's variables, but it only works on Qt 5 or later am_have_qt_dir=`mktemp -d` am_have_qt_pro="$am_have_qt_dir/test.pro" am_have_qt_makefile="$am_have_qt_dir/Makefile" # http://qt-project.org/doc/qt-5/qmake-variable-reference.html#qt cat > $am_have_qt_pro << EOF win32 { CONFIG -= debug_and_release CONFIG += release } qtHaveModule(core): QT += core qtHaveModule(gui): QT += gui qtHaveModule(widgets): QT += widgets percent.target = % percent.commands = @echo -n "\$(\$(@))\ " QMAKE_EXTRA_TARGETS += percent EOF $am_have_qt_qmexe $am_have_qt_pro -o $am_have_qt_makefile echo "QT TEST Makefile = $am_have_qt_makefile" # Get Qt version from qmake QT_CXXFLAGS=`cd $am_have_qt_dir; make -s -f $am_have_qt_makefile CXXFLAGS INCPATH` QT_LIBS=`cd $am_have_qt_dir; make -s -f $am_have_qt_makefile LIBS` rm $am_have_qt_pro $am_have_qt_makefile rmdir $am_have_qt_dir # Look for specific tools in $PATH QT_MOC=`which moc$am_have_qt_qmexe_suff` QT_UIC=`which uic$am_have_qt_qmexe_suff` QT_RCC=`which rcc$am_have_qt_qmexe_suff` QT_LRELEASE=`which lrelease$am_have_qt_qmexe_suff` QT_LUPDATE=`which lupdate$am_have_qt_qmexe_suff` # Get Qt version from qmake QT_DIR=`$am_have_qt_qmexe --version | grep -o -E /.+` # All variables are defined, report the result AC_MSG_RESULT([$have_qt: QT_CXXFLAGS=$QT_CXXFLAGS QT_DIR=$QT_DIR QT_LIBS=$QT_LIBS QT_UIC=$QT_UIC QT_MOC=$QT_MOC QT_RCC=$QT_RCC QT_LRELEASE=$QT_LRELEASE QT_LUPDATE=$QT_LUPDATE]) else # Qt was not found have_qt=no QT_CXXFLAGS= QT_DIR= QT_LIBS= QT_UIC= QT_MOC= QT_RCC= QT_LRELEASE= QT_LUPDATE= AC_MSG_RESULT($have_qt) AC_MSG_ERROR(qmake/Qt not found... is any qmake on your system?) fi AC_SUBST(QT_CXXFLAGS) AC_SUBST(QT_DIR) AC_SUBST(QT_LIBS) AC_SUBST(QT_UIC) AC_SUBST(QT_MOC) AC_SUBST(QT_RCC) AC_SUBST(QT_LRELEASE) AC_SUBST(QT_LUPDATE) ]) ================================================ FILE: m4/ax_have_qt_ex.m4 ================================================ # =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_have_qt.html extended # =========================================================================== # # SYNOPSIS # # AX_HAVE_QT_EX # # DESCRIPTION # # Extended by C. Ahlstrom 2018-03-08 to support rcc. # Modified by C. Ahlstrom 2019-11-29 to change the macro name to avoid # conflicts and to remove (experimentally) the problematic uitools library. # Modified by C. Ahlstrom 2020-07-24 to be in accord with the INSTALL file. # # Searches $PATH and queries qmake for Qt include files, libraries and Qt # binary utilities. The macro only supports Qt5 or later. # # The following shell variable is set to either "yes" or "no": # # have_qt # # Additionally, the following variables are exported: # # QT_CXXFLAGS # QT_LIBS # QT_MOC # QT_RCC (new with this version) # QT_UIC # QT_LRELEASE # QT_LUPDATE # QT_DIR # # which respectively contain an "-I" flag pointing to the Qt include # directory, link flags necessary to link with Qt and X, the full path to # the meta object compiler and the user interface compiler both, and # finally the variable QTDIR as Qt likes to see it defined. # # Example lines for Makefile.in: # # CXXFLAGS = @QT_CXXFLAGS@ # MOC = @QT_MOC@ # # After the variables have been set, a trial compile and link is performed # to check the correct functioning of the meta object compiler. This test # may fail when the different detected elements stem from different # releases of the Qt framework. In that case, an error message is emitted # and configure stops. # # No common variables such as $LIBS or $CFLAGS are polluted. # # LICENSE # # Copyright (c) 2008 Bastiaan Veelo # Copyright (c) 2014 Alex Henrie # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 15 AU_ALIAS([BNV_HAVE_QT], [AX_HAVE_QT_EX]) AC_DEFUN([AX_HAVE_QT_EX], [ AC_REQUIRE([AC_PROG_CXX]) AC_REQUIRE([AC_PATH_X]) AC_REQUIRE([AC_PATH_XTRA]) AC_MSG_CHECKING(for Qt) # If we have Qt5 or later in the path, we're golden ver=`qmake --version | grep -o "Qt version ."` if test "$ver" "==" "Qt version 5"; then have_qt=yes # This pro file dumps qmake's variables, but it only works on Qt 5 or later am_have_qt_pro=`mktemp` am_have_qt_makefile=`mktemp` # http://qt-project.org/doc/qt-5/qmake-variable-reference.html#qt cat > $am_have_qt_pro << EOF qtHaveModule(concurrent): QT += concurrent qtHaveModule(core): QT += core qtHaveModule(dbus): QT += dbus qtHaveModule(gui): QT += gui qtHaveModule(widgets): QT += widgets qtHaveModule(network): QT += network qtHaveModule(opengl): QT += opengl qtHaveModule(printsupport): QT += printsupport qtHaveModule(qml): QT += qml qtHaveModule(x11extras): QT += x11extras qtHaveModule(sql): QT += sql qtHaveModule(testlib): QT += testlib qtHaveModule(xml): QT += xml percent.target = % percent.commands = @echo -n "\$(\$(@))\ " QMAKE_EXTRA_TARGETS += percent EOF qmake $am_have_qt_pro -o $am_have_qt_makefile QT_CXXFLAGS=`make -s -f $am_have_qt_makefile CXXFLAGS INCPATH` QT_LIBS=`make -s -f $am_have_qt_makefile LIBS` rm $am_have_qt_pro $am_have_qt_makefile # Look for specific tools in $PATH QT_MOC=`which moc` QT_RCC=`which rcc` QT_UIC=`which uic` QT_LRELEASE=`which lrelease` QT_LUPDATE=`which lupdate` # Get Qt version from qmake QT_DIR=`qmake --version | grep -o -E /.+` # All variables are defined, report the result AC_MSG_RESULT([$have_qt: QT_CXXFLAGS=$QT_CXXFLAGS QT_DIR=$QT_DIR QT_LIBS=$QT_LIBS QT_MOC=$QT_MOC QT_RCC=$QT_RCC QT_UIC=$QT_UIC QT_LRELEASE=$QT_LRELEASE QT_LUPDATE=$QT_LUPDATE]) else # Qt was not found have_qt=no QT_CXXFLAGS= QT_DIR= QT_LIBS= QT_MOC= QT_RCC= QT_UIC= QT_LRELEASE= QT_LUPDATE= AC_MSG_RESULT($have_qt) fi AC_SUBST(QT_CXXFLAGS) AC_SUBST(QT_DIR) AC_SUBST(QT_LIBS) AC_SUBST(QT_MOC) AC_SUBST(QT_RCC) AC_SUBST(QT_UIC) AC_SUBST(QT_LRELEASE) AC_SUBST(QT_LUPDATE) #### Being paranoid: if test x"$have_qt" = xyes; then AC_MSG_CHECKING(correct functioning of Qt installation) AC_CACHE_VAL(ax_cv_qt_test_result, [ cat > ax_qt_test.h << EOF #include class Test : public QObject { Q_OBJECT public: Test() {} ~Test() {} public slots: void receive() {} signals: void send(); }; EOF cat > ax_qt_main.$ac_ext << EOF #include "ax_qt_test.h" #include int main( int argc, char **argv ) { QApplication app( argc, argv ); Test t; QObject::connect( &t, SIGNAL(send()), &t, SLOT(receive()) ); } EOF ax_cv_qt_test_result="failure" ax_try_1="$QT_MOC ax_qt_test.h -o moc_ax_qt_test.$ac_ext >/dev/null 2>/dev/null" AC_TRY_EVAL(ax_try_1) if test x"$ac_status" != x0; then echo "$ax_err_1" >&AS_MESSAGE_LOG_FD echo "configure: could not run $QT_MOC on:" >&AS_MESSAGE_LOG_FD cat ax_qt_test.h >&AS_MESSAGE_LOG_FD else ax_try_2="$CXX $QT_CXXFLAGS -c $CXXFLAGS -o moc_ax_qt_test.o moc_ax_qt_test.$ac_ext >/dev/null 2>/dev/null" AC_TRY_EVAL(ax_try_2) if test x"$ac_status" != x0; then echo "$ax_err_2" >&AS_MESSAGE_LOG_FD echo "configure: could not compile:" >&AS_MESSAGE_LOG_FD cat moc_ax_qt_test.$ac_ext >&AS_MESSAGE_LOG_FD else ax_try_3="$CXX $QT_CXXFLAGS -c $CXXFLAGS -o ax_qt_main.o ax_qt_main.$ac_ext >/dev/null 2>/dev/null" AC_TRY_EVAL(ax_try_3) if test x"$ac_status" != x0; then echo "$ax_err_3" >&AS_MESSAGE_LOG_FD echo "configure: could not compile:" >&AS_MESSAGE_LOG_FD cat ax_qt_main.$ac_ext >&AS_MESSAGE_LOG_FD else ax_try_4="$CXX -o ax_qt_main ax_qt_main.o moc_ax_qt_test.o $QT_LIBS $LIBS >/dev/null 2>/dev/null" AC_TRY_EVAL(ax_try_4) if test x"$ac_status" != x0; then echo "$ax_err_4" >&AS_MESSAGE_LOG_FD else ax_cv_qt_test_result="success" fi fi fi fi ])dnl AC_CACHE_VAL ax_cv_qt_test_result AC_MSG_RESULT([$ax_cv_qt_test_result]) if test x"$ax_cv_qt_test_result" = "xfailure"; then AC_MSG_ERROR([Failed to find matching components of a complete Qt installation. Try using more options, see ./configure --help.]) fi rm -f ax_qt_test.h moc_ax_qt_test.$ac_ext moc_ax_qt_test.o \ ax_qt_main.$ac_ext ax_qt_main.o ax_qt_main fi ]) ================================================ FILE: m4/ax_have_qt_min.m4 ================================================ # =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_have_qt_min.html # =========================================================================== # # SYNOPSIS # # AX_HAVE_QT_MIN # # DESCRIPTION # # Extended by C. Ahlstrom 2018-03-08 to support rcc. # # Modified by C. Ahlstrom 2019-11-29 to change the macro name to avoid # conflicts and to remove the problematic uitools library test. # # Modified by C. Ahlstrom 2020-04-01 to pare down to the minimum modules # needed for Seq66: core, gui, and widgets. # # Modified by C. Ahlstrom 2021-07-06 to emit an error message when qmake # cannot be found. # # Modified by C. Ahlstrom 2022-08-22 to incorporate Qt detection code from # ax_have_qt.m4 serial 19. This work is for issue #54. # # Searches $PATH and queries qmake for Qt include files, libraries and Qt # binary utilities. The macro only supports Qt5 or later. # # The following shell variable is set to either "yes" or "no": # # have_qt # # Additionally, the following variables are exported: # # QT_CXXFLAGS # QT_LIBS # QT_MOC # QT_UIC # QT_RCC (new with this ax_have_qt.m4 serial 19) # QT_LRELEASE # QT_LUPDATE # QT_DIR # # which respectively contain an "-I" flag pointing to the Qt include # directory, link flags necessary to link with Qt and X, the full path to # the meta object compiler and the user interface compiler both, and # finally the variable QTDIR as Qt likes to see it defined. # # Example lines for Makefile.in: # # CXXFLAGS = @QT_CXXFLAGS@ # MOC = @QT_MOC@ # # After the variables have been set, a trial compile and link is performed # to check the correct functioning of the meta object compiler. This test # may fail when the different detected elements stem from different # releases of the Qt framework. In that case, an error message is emitted # and configure stops. # # No common variables such as $LIBS or $CFLAGS are polluted. # # LICENSE # # Copyright (c) 2008 Bastiaan Veelo # Copyright (c) 2014 Alex Henrie # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 19 AU_ALIAS([BNV_HAVE_QT], [AX_HAVE_QT_MIN]) AC_DEFUN([AX_HAVE_QT_MIN], [ AC_REQUIRE([AC_PROG_CXX]) AC_REQUIRE([AC_PATH_X]) AC_REQUIRE([AC_PATH_XTRA]) AC_MSG_CHECKING(for Qt) # From serial 19 to handle issue #54. # # openSUSE leap 15.3 installs qmake-qt5, not qmake, for example. # Store the full name (like qmake-qt5) into am_have_qt_qmexe # and the specifier (like -qt5 or empty) into am_have_qt_qmexe_suff. AC_CHECK_PROGS(am_have_qt_qmexe,qmake qmake-qt6 qmake-qt5,[]) am_have_qt_qmexe_suff=`echo $am_have_qt_qmexe | cut -b 6-` # If we have Qt5 or later in the path, we're golden ver=`$am_have_qt_qmexe --version | grep -o "Qt version ."` if test "$ver" ">" "Qt version 4"; then have_qt=yes # This pro file dumps qmake's variables, but it only works on Qt 5 or later am_have_qt_dir=`mktemp -d` am_have_qt_pro="$am_have_qt_dir/test.pro" am_have_qt_makefile="$am_have_qt_dir/Makefile" # http://qt-project.org/doc/qt-5/qmake-variable-reference.html#qt cat > $am_have_qt_pro << EOF win32 { CONFIG -= debug_and_release CONFIG += release } qtHaveModule(core): QT += core qtHaveModule(gui): QT += gui qtHaveModule(widgets): QT += widgets percent.target = % percent.commands = @echo -n "\$(\$(@))\ " QMAKE_EXTRA_TARGETS += percent EOF $am_have_qt_qmexe $am_have_qt_pro -o $am_have_qt_makefile # Get Qt version from qmake QT_CXXFLAGS=`cd $am_have_qt_dir; make -s -f $am_have_qt_makefile CXXFLAGS INCPATH` QT_LIBS=`cd $am_have_qt_dir; make -s -f $am_have_qt_makefile LIBS` rm $am_have_qt_pro $am_have_qt_makefile rmdir $am_have_qt_dir # Look for specific tools in $PATH QT_MOC=`which moc$am_have_qt_qmexe_suff` QT_UIC=`which uic$am_have_qt_qmexe_suff` QT_RCC=`which rcc$am_have_qt_qmexe_suff` QT_LRELEASE=`which lrelease$am_have_qt_qmexe_suff` QT_LUPDATE=`which lupdate$am_have_qt_qmexe_suff` # Get Qt version from qmake QT_DIR=`$am_have_qt_qmexe --version | grep -o -E /.+` # All variables are defined, report the result AC_MSG_RESULT([$have_qt: QT_CXXFLAGS=$QT_CXXFLAGS QT_DIR=$QT_DIR QT_LIBS=$QT_LIBS QT_UIC=$QT_UIC QT_MOC=$QT_MOC QT_RCC=$QT_RCC QT_LRELEASE=$QT_LRELEASE QT_LUPDATE=$QT_LUPDATE]) else # Qt was not found have_qt=no QT_CXXFLAGS= QT_DIR= QT_LIBS= QT_UIC= QT_MOC= QT_RCC= QT_LRELEASE= QT_LUPDATE= AC_MSG_RESULT($have_qt) AC_MSG_ERROR(qmake/Qt not found... is any qmake on your system?) fi AC_SUBST(QT_CXXFLAGS) AC_SUBST(QT_DIR) AC_SUBST(QT_LIBS) AC_SUBST(QT_UIC) AC_SUBST(QT_MOC) AC_SUBST(QT_RCC) AC_SUBST(QT_LRELEASE) AC_SUBST(QT_LUPDATE) #### Being paranoid: if test x"$have_qt" = xyes; then AC_MSG_CHECKING(correct functioning of Qt installation) AC_CACHE_VAL(ax_cv_qt_test_result, [ cat > ax_qt_test.h << EOF #include class Test : public QObject { Q_OBJECT public: Test() {} ~Test() {} public slots: void receive() {} signals: void send(); }; EOF cat > ax_qt_main.$ac_ext << EOF #include "ax_qt_test.h" #include int main( int argc, char **argv ) { QApplication app( argc, argv ); Test t; QObject::connect( &t, SIGNAL(send()), &t, SLOT(receive()) ); } EOF ax_cv_qt_test_result="failure" ax_try_1="$QT_MOC ax_qt_test.h -o moc_ax_qt_test.$ac_ext >/dev/null 2>/dev/null" AC_TRY_EVAL(ax_try_1) if test x"$ac_status" != x0; then echo "$ax_err_1" >&AS_MESSAGE_LOG_FD echo "configure: could not run $QT_MOC on:" >&AS_MESSAGE_LOG_FD cat ax_qt_test.h >&AS_MESSAGE_LOG_FD else ax_try_2="$CXX $QT_CXXFLAGS -c $CXXFLAGS -o moc_ax_qt_test.o moc_ax_qt_test.$ac_ext >/dev/null 2>/dev/null" AC_TRY_EVAL(ax_try_2) if test x"$ac_status" != x0; then echo "$ax_err_2" >&AS_MESSAGE_LOG_FD echo "configure: could not compile:" >&AS_MESSAGE_LOG_FD cat moc_ax_qt_test.$ac_ext >&AS_MESSAGE_LOG_FD else ax_try_3="$CXX $QT_CXXFLAGS -c $CXXFLAGS -o ax_qt_main.o ax_qt_main.$ac_ext >/dev/null 2>/dev/null" AC_TRY_EVAL(ax_try_3) if test x"$ac_status" != x0; then echo "$ax_err_3" >&AS_MESSAGE_LOG_FD echo "configure: could not compile:" >&AS_MESSAGE_LOG_FD cat ax_qt_main.$ac_ext >&AS_MESSAGE_LOG_FD else ax_try_4="$CXX -o ax_qt_main ax_qt_main.o moc_ax_qt_test.o $QT_LIBS $LIBS >/dev/null 2>/dev/null" AC_TRY_EVAL(ax_try_4) if test x"$ac_status" != x0; then echo "$ax_err_4" >&AS_MESSAGE_LOG_FD else ax_cv_qt_test_result="success" fi fi fi fi ])dnl AC_CACHE_VAL ax_cv_qt_test_result AC_MSG_RESULT([$ax_cv_qt_test_result]) if test x"$ax_cv_qt_test_result" = "xfailure"; then AC_MSG_ERROR([Failed to find matching components of a complete Qt installation. Try using more options, see ./configure --help.]) fi rm -f ax_qt_test.h moc_ax_qt_test.$ac_ext moc_ax_qt_test.o \ ax_qt_main.$ac_ext ax_qt_main.o ax_qt_main fi ]) ================================================ FILE: m4/ax_prefix_config_h.m4 ================================================ # =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_prefix_config_h.html # =========================================================================== # # SYNOPSIS # # AX_PREFIX_CONFIG_H [(OUTPUT-HEADER [,PREFIX [,ORIG-HEADER]])] # # DESCRIPTION # # Generate an installable config.h. # # A package should not normally install its config.h as a system header, # but if it must, this macro can be used to avoid namespace pollution by # making a copy of config.h with a prefix added to all the macro names. # # Each "#define SOMEDEF" line of the configuration header has the given # prefix added, in the same case as the first character of the macro name. # # Defaults: # # OUTPUT-HEADER = $PACKAGE-config.h # PREFIX = $PACKAGE # ORIG-HEADER, from AM_CONFIG_HEADER(config.h) # # Your configure.ac script should contain both macros in this order. # # Example: # # AC_INIT(config.h.in) # config.h.in as created by "autoheader" # AM_INIT_AUTOMAKE(testpkg, 0.1.1) # makes #undef VERSION and PACKAGE # AM_CONFIG_HEADER(config.h) # prep config.h from config.h.in # AX_PREFIX_CONFIG_H(mylib/_config.h) # prep mylib/_config.h from it.. # AC_MEMORY_H # makes "#undef NEED_MEMORY_H" # AC_C_CONST_H # makes "#undef const" # AC_OUTPUT(Makefile) # creates the "config.h" now # # and also mylib/_config.h # # If the argument to AX_PREFIX_CONFIG_H would have been omitted then the # default output file would have been called simply "testpkg-config.h", # but even under the name "mylib/_config.h" it contains prefix-defines # like # # #ifndef TESTPKG_VERSION # #define TESTPKG_VERSION "0.1.1" # #endif # #ifndef TESTPKG_NEED_MEMORY_H # #define TESTPKG_NEED_MEMORY_H 1 # #endif # #ifndef _testpkg_const # #define _testpkg_const _const # #endif # # and this "mylib/_config.h" can be installed along with other header # files, which is most convenient when creating a shared library (that has # some headers) whose functionality depends on features detected at # compile-time. No need to invent some "mylib-confdefs.h.in" manually. # # Note that some AC_DEFINEs that end up in the config.h file are actually # self-referential - e.g. AC_C_INLINE, AC_C_CONST, and the AC_TYPE_OFF_T # say that they "will define inline|const|off_t if the system does not do # it by itself". You might want to clean up about these - consider an # extra mylib/conf.h that reads something like: # # #include # #ifndef _testpkg_const # #define _testpkg_const const # #endif # # and then start using _testpkg_const in the header files. That is also a # good thing to differentiate whether some library-user has starting to # take up with a different compiler, so perhaps it could read something # like this: # # #ifdef _MSC_VER # #include # #else # #include # #endif # #ifndef _testpkg_const # #define _testpkg_const const # #endif # # LICENSE # # Copyright (c) 2014 Reuben Thomas # Copyright (c) 2008 Guido U. Draheim # Copyright (c) 2008 Marten Svantesson # Copyright (c) 2008 Gerald Point # # 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 3 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, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 16 AC_DEFUN([AX_PREFIX_CONFIG_H],[dnl AC_PREREQ([2.62]) AC_BEFORE([AC_CONFIG_HEADERS],[$0])dnl AC_CONFIG_COMMANDS(m4_default([$1], [$PACKAGE-config.h]),[dnl AS_VAR_PUSHDEF([_OUT],[ac_prefix_conf_OUT])dnl AS_VAR_PUSHDEF([_DEF],[ac_prefix_conf_DEF])dnl AS_VAR_PUSHDEF([_PKG],[ac_prefix_conf_PKG])dnl AS_VAR_PUSHDEF([_LOW],[ac_prefix_conf_LOW])dnl AS_VAR_PUSHDEF([_UPP],[ac_prefix_conf_UPP])dnl AS_VAR_PUSHDEF([_INP],[ac_prefix_conf_INP])dnl m4_pushdef([_script],[conftest.prefix])dnl m4_pushdef([_symbol],[m4_cr_Letters[]m4_cr_digits[]_])dnl _OUT=`echo m4_default([$1], [$PACKAGE-config.h])` _DEF=`echo _$_OUT | sed -e "y:m4_cr_letters:m4_cr_LETTERS[]:" -e "s/@<:@^m4_cr_Letters@:>@/_/g"` _PKG=`echo m4_default([$2], [$PACKAGE])` _LOW=`echo _$_PKG | sed -e "y:m4_cr_LETTERS-:m4_cr_letters[]_:"` _UPP=`echo $_PKG | sed -e "y:m4_cr_letters-:m4_cr_LETTERS[]_:" -e "/^@<:@m4_cr_digits@:>@/s/^/_/"` _INP=`echo "$3" | sed -e 's/ *//'` if test ".$_INP" = "."; then for ac_file in : $CONFIG_HEADERS; do test "_$ac_file" = _: && continue case "$ac_file" in *.h) _INP=$ac_file ;; *) esac test ".$_INP" != "." && break done fi if test ".$_INP" = "."; then case "$_OUT" in */*) _INP=`basename "$_OUT"` ;; *-*) _INP=`echo "$_OUT" | sed -e "s/@<:@_symbol@:>@*-//"` ;; *) _INP=config.h ;; esac fi if test -z "$_PKG" ; then AC_MSG_ERROR([no prefix for _PREFIX_PKG_CONFIG_H]) else if test ! -f "$_INP" ; then if test -f "$srcdir/$_INP" ; then _INP="$srcdir/$_INP" fi fi AC_MSG_NOTICE(creating $_OUT - prefix $_UPP for $_INP defines) if test -f $_INP ; then AS_ECHO(["s/^@%:@undef *\\(@<:@m4_cr_LETTERS[]_@:>@\\)/@%:@undef $_UPP""_\\1/"]) > _script AS_ECHO(["s/^@%:@undef *\\(@<:@m4_cr_letters@:>@\\)/@%:@undef $_LOW""_\\1/"]) >> _script AS_ECHO(["s/^@%:@def[]ine *\\(@<:@m4_cr_LETTERS[]_@:>@@<:@_symbol@:>@*\\)\\(.*\\)/@%:@ifndef $_UPP""_\\1\\"]) >> _script AS_ECHO(["@%:@def[]ine $_UPP""_\\1\\2\\"]) >> _script AS_ECHO(["@%:@endif/"]) >> _script AS_ECHO(["s/^@%:@def[]ine *\\(@<:@m4_cr_letters@:>@@<:@_symbol@:>@*\\)\\(.*\\)/@%:@ifndef $_LOW""_\\1\\"]) >> _script AS_ECHO(["@%:@define $_LOW""_\\1\\2\\"]) >> _script AS_ECHO(["@%:@endif/"]) >> _script # now executing _script on _DEF input to create _OUT output file echo "@%:@ifndef $_DEF" >$tmp/pconfig.h echo "@%:@def[]ine $_DEF 1" >>$tmp/pconfig.h echo ' ' >>$tmp/pconfig.h echo /'*' $_OUT. Generated automatically at end of configure. '*'/ >>$tmp/pconfig.h sed -f _script $_INP >>$tmp/pconfig.h echo ' ' >>$tmp/pconfig.h echo '/* once:' $_DEF '*/' >>$tmp/pconfig.h echo "@%:@endif" >>$tmp/pconfig.h if cmp -s $_OUT $tmp/pconfig.h 2>/dev/null; then AC_MSG_NOTICE([$_OUT is unchanged]) else ac_dir=`AS_DIRNAME(["$_OUT"])` AS_MKDIR_P(["$ac_dir"]) rm -f "$_OUT" mv $tmp/pconfig.h "$_OUT" fi else AC_MSG_ERROR([input file $_INP does not exist - skip generating $_OUT]) fi rm -f conftest.* fi m4_popdef([_symbol])dnl m4_popdef([_script])dnl AS_VAR_POPDEF([_INP])dnl AS_VAR_POPDEF([_UPP])dnl AS_VAR_POPDEF([_LOW])dnl AS_VAR_POPDEF([_PKG])dnl AS_VAR_POPDEF([_DEF])dnl AS_VAR_POPDEF([_OUT])dnl ],[PACKAGE="$PACKAGE"])]) ================================================ FILE: m4/ax_prog_flex.m4 ================================================ # =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_prog_flex.html # =========================================================================== # # SYNOPSIS # # AX_PROG_FLEX(ACTION-IF-TRUE,ACTION-IF-FALSE) # # DESCRIPTION # # Check whether flex is the scanner generator. Run ACTION-IF-TRUE if # successful, ACTION-IF-FALSE otherwise # # LICENSE # # Copyright (c) 2009 Francesco Salvestrini # Copyright (c) 2010 Diego Elio Petteno` # # 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, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 9 AC_DEFUN([AX_PROG_FLEX], [ AC_REQUIRE([AM_PROG_LEX]) AC_REQUIRE([AC_PROG_EGREP]) AC_CACHE_CHECK([if flex is the lexer generator],[ax_cv_prog_flex],[ AS_IF([$LEX --version 2>/dev/null | $EGREP -q '^flex '], [ax_cv_prog_flex=yes], [ax_cv_prog_flex=no]) ]) AS_IF([test "$ax_cv_prog_flex" = "yes"], m4_ifnblank([$1], [[$1]]), m4_ifnblank([$2], [[$2]]) ) ]) ================================================ FILE: m4/ax_pthread.m4 ================================================ # =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_pthread.html # =========================================================================== # # SYNOPSIS # # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) # # DESCRIPTION # # This macro figures out how to build C programs using POSIX threads. It # sets the PTHREAD_LIBS output variable to the threads library and linker # flags, and the PTHREAD_CFLAGS output variable to any special C compiler # flags that are needed. (The user can also force certain compiler # flags/libs to be tested by setting these environment variables.) # # Also sets PTHREAD_CC and PTHREAD_CXX to any special C compiler that is # needed for multi-threaded programs (defaults to the value of CC # respectively CXX otherwise). (This is necessary on e.g. AIX to use the # special cc_r/CC_r compiler alias.) # # NOTE: You are assumed to not only compile your program with these flags, # but also to link with them as well. For example, you might link with # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # $PTHREAD_CXX $CXXFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # # If you are only building threaded programs, you may wish to use these # variables in your default LIBS, CFLAGS, and CC: # # LIBS="$PTHREAD_LIBS $LIBS" # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS" # CC="$PTHREAD_CC" # CXX="$PTHREAD_CXX" # # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant # has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to # that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). # # Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the # PTHREAD_PRIO_INHERIT symbol is defined when compiling with # PTHREAD_CFLAGS. # # ACTION-IF-FOUND is a list of shell commands to run if a threads library # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it # is not found. If ACTION-IF-FOUND is not specified, the default action # will define HAVE_PTHREAD. # # Please let the authors know if this macro fails on any platform, or if # you have any other suggestions or comments. This macro was based on work # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by # Alejandro Forero Cuervo to the autoconf macro repository. We are also # grateful for the helpful feedback of numerous users. # # Updated for Autoconf 2.68 by Daniel Richard G. # # LICENSE # # Copyright (c) 2008 Steven G. Johnson # Copyright (c) 2011 Daniel Richard G. # Copyright (c) 2019 Marc Stevens # # 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 3 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, see . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 31 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_REQUIRE([AC_PROG_CC]) AC_REQUIRE([AC_PROG_SED]) AC_LANG_PUSH([C]) ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on Tru64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then ax_pthread_save_CC="$CC" ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) AS_IF([test "x$PTHREAD_CXX" != "x"], [CXX="$PTHREAD_CXX"]) CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) AC_MSG_RESULT([$ax_pthread_ok]) if test "x$ax_pthread_ok" = "xno"; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi CC="$ax_pthread_save_CC" CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items with a "," contain both # C compiler flags (before ",") and linker flags (after ","). Other items # starting with a "-" are C compiler flags, and remaining items are # library names, except for "none" which indicates that we try without # any flags at all, and "pthread-config" which is a program returning # the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 # (Note: HP C rejects this with "bad form for `-t' option") # -pthreads: Solaris/gcc (Note: HP C also rejects) # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads and # -D_REENTRANT too), HP C (must be checked before -lpthread, which # is present but should not be used directly; and before -mthreads, # because the compiler interprets this as "-mt" + "-hreads") # -mthreads: Mingw32/gcc, Lynx/gcc # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case $host_os in freebsd*) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) ax_pthread_flags="-kthread lthread $ax_pthread_flags" ;; hpux*) # From the cc(1) man page: "[-mt] Sets various -D flags to enable # multi-threading and also sets -lpthread." ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" ;; openedition*) # IBM z/OS requires a feature-test macro to be defined in order to # enable POSIX threads at all, so give the user a hint if this is # not set. (We don't define these ourselves, as they can affect # other portions of the system API in unpredictable ways.) AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], [ # if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) AX_PTHREAD_ZOS_MISSING # endif ], [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) ;; solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (N.B.: The stubs are missing # pthread_cleanup_push, or rather a function called by this macro, # so we could check for that, but who knows whether they'll stub # that too in a future libc.) So we'll check first for the # standard Solaris way of linking pthreads (-mt -lpthread). ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" ;; esac # Are we compiling with Clang? AC_CACHE_CHECK([whether $CC is Clang], [ax_cv_PTHREAD_CLANG], [ax_cv_PTHREAD_CLANG=no # Note that Autoconf sets GCC=yes for Clang as well as GCC if test "x$GCC" = "xyes"; then AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ # if defined(__clang__) && defined(__llvm__) AX_PTHREAD_CC_IS_CLANG # endif ], [ax_cv_PTHREAD_CLANG=yes]) fi ]) ax_pthread_clang="$ax_cv_PTHREAD_CLANG" # GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) # Note that for GCC and Clang -pthread generally implies -lpthread, # except when -nostdlib is passed. # This is problematic using libtool to build C++ shared libraries with pthread: # [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 # [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 # [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 # To solve this, first try -pthread together with -lpthread for GCC AS_IF([test "x$GCC" = "xyes"], [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"]) # Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first AS_IF([test "x$ax_pthread_clang" = "xyes"], [ax_pthread_flags="-pthread,-lpthread -pthread"]) # The presence of a feature test macro requesting re-entrant function # definitions is, on some systems, a strong hint that pthreads support is # correctly enabled case $host_os in darwin* | hpux* | linux* | osf* | solaris*) ax_pthread_check_macro="_REENTRANT" ;; aix*) ax_pthread_check_macro="_THREAD_SAFE" ;; *) ax_pthread_check_macro="--" ;; esac AS_IF([test "x$ax_pthread_check_macro" = "x--"], [ax_pthread_check_cond=0], [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) if test "x$ax_pthread_ok" = "xno"; then for ax_pthread_try_flag in $ax_pthread_flags; do case $ax_pthread_try_flag in none) AC_MSG_CHECKING([whether pthreads work without any flags]) ;; *,*) PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"]) ;; -*) AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) PTHREAD_CFLAGS="$ax_pthread_try_flag" ;; pthread-config) AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) PTHREAD_LIBS="-l$ax_pthread_try_flag" ;; esac ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. AC_LINK_IFELSE([AC_LANG_PROGRAM([#include # if $ax_pthread_check_cond # error "$ax_pthread_check_macro must be defined" # endif static void *some_global = NULL; static void routine(void *a) { /* To avoid any unused-parameter or unused-but-set-parameter warning. */ some_global = a; } static void *start_routine(void *a) { return a; }], [pthread_t th; pthread_attr_t attr; pthread_create(&th, 0, start_routine, 0); pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_cleanup_pop(0) /* ; */])], [ax_pthread_ok=yes], []) CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" AC_MSG_RESULT([$ax_pthread_ok]) AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Clang needs special handling, because older versions handle the -pthread # option in a rather... idiosyncratic way if test "x$ax_pthread_clang" = "xyes"; then # Clang takes -pthread; it has never supported any other flag # (Note 1: This will need to be revisited if a system that Clang # supports has POSIX threads in a separate library. This tends not # to be the way of modern systems, but it's conceivable.) # (Note 2: On some systems, notably Darwin, -pthread is not needed # to get POSIX threads support; the API is always present and # active. We could reasonably leave PTHREAD_CFLAGS empty. But # -pthread does define _REENTRANT, and while the Darwin headers # ignore this macro, third-party headers might not.) # However, older versions of Clang make a point of warning the user # that, in an invocation where only linking and no compilation is # taking place, the -pthread option has no effect ("argument unused # during compilation"). They expect -pthread to be passed in only # when source code is being compiled. # # Problem is, this is at odds with the way Automake and most other # C build frameworks function, which is that the same flags used in # compilation (CFLAGS) are also used in linking. Many systems # supported by AX_PTHREAD require exactly this for POSIX threads # support, and in fact it is often not straightforward to specify a # flag that is used only in the compilation phase and not in # linking. Such a scenario is extremely rare in practice. # # Even though use of the -pthread flag in linking would only print # a warning, this can be a nuisance for well-run software projects # that build with -Werror. So if the active version of Clang has # this misfeature, we search for an option to squash it. AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown # Create an alternate version of $ac_link that compiles and # links in two steps (.c -> .o, .o -> exe) instead of one # (.c -> exe), because the warning occurs only in the second # step ax_pthread_save_ac_link="$ac_link" ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' ax_pthread_link_step=`AS_ECHO(["$ac_link"]) | sed "$ax_pthread_sed"` ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" ax_pthread_save_CFLAGS="$CFLAGS" for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" ac_link="$ax_pthread_save_ac_link" AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], [ac_link="$ax_pthread_2step_ac_link" AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], [break]) ]) done ac_link="$ax_pthread_save_ac_link" CFLAGS="$ax_pthread_save_CFLAGS" AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" ]) case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in no | unknown) ;; *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; esac fi # $ax_pthread_clang = yes # Various other checks: if test "x$ax_pthread_ok" = "xyes"; then ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. AC_CACHE_CHECK([for joinable pthread attribute], [ax_cv_PTHREAD_JOINABLE_ATTR], [ax_cv_PTHREAD_JOINABLE_ATTR=unknown for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [int attr = $ax_pthread_attr; return attr /* ; */])], [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], []) done ]) AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ test "x$ax_pthread_joinable_attr_defined" != "xyes"], [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$ax_cv_PTHREAD_JOINABLE_ATTR], [Define to necessary symbol if this constant uses a non-standard name on your system.]) ax_pthread_joinable_attr_defined=yes ]) AC_CACHE_CHECK([whether more special flags are required for pthreads], [ax_cv_PTHREAD_SPECIAL_FLAGS], [ax_cv_PTHREAD_SPECIAL_FLAGS=no case $host_os in solaris*) ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" ;; esac ]) AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ test "x$ax_pthread_special_flags_added" != "xyes"], [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" ax_pthread_special_flags_added=yes]) AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], [ax_cv_PTHREAD_PRIO_INHERIT], [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[int i = PTHREAD_PRIO_INHERIT; return i;]])], [ax_cv_PTHREAD_PRIO_INHERIT=yes], [ax_cv_PTHREAD_PRIO_INHERIT=no]) ]) AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ test "x$ax_pthread_prio_inherit_defined" != "xyes"], [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) ax_pthread_prio_inherit_defined=yes ]) CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" # More AIX lossage: compile with *_r variant if test "x$GCC" != "xyes"; then case $host_os in aix*) AS_CASE(["x/$CC"], [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], [#handle absolute path differently from PATH based program lookup AS_CASE(["x$CC"], [x/*], [ AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"]) AS_IF([test "x${CXX}" != "x"], [AS_IF([AS_EXECUTABLE_P([${CXX}_r])],[PTHREAD_CXX="${CXX}_r"])]) ], [ AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC]) AS_IF([test "x${CXX}" != "x"], [AC_CHECK_PROGS([PTHREAD_CXX],[${CXX}_r],[$CXX])]) ] ) ]) ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" AC_SUBST([PTHREAD_LIBS]) AC_SUBST([PTHREAD_CFLAGS]) AC_SUBST([PTHREAD_CC]) AC_SUBST([PTHREAD_CXX]) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test "x$ax_pthread_ok" = "xyes"; then ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) : else ax_pthread_ok=no $2 fi AC_LANG_POP ])dnl AX_PTHREAD ================================================ FILE: m4/ax_require_defined.m4 ================================================ # =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_require_defined.html # =========================================================================== # # SYNOPSIS # # AX_REQUIRE_DEFINED(MACRO) # # DESCRIPTION # # AX_REQUIRE_DEFINED is a simple helper for making sure other macros have # been defined and thus are available for use. This avoids random issues # where a macro isn't expanded. Instead the configure script emits a # non-fatal: # # ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found # # It's like AC_REQUIRE except it doesn't expand the required macro. # # Here's an example: # # AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG]) # # LICENSE # # Copyright (c) 2014 Mike Frysinger # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 2 AC_DEFUN([AX_REQUIRE_DEFINED], [dnl m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])]) ])dnl AX_REQUIRE_DEFINED ================================================ FILE: m4/gcc-version.m4 ================================================ dnl *************************************************************************** dnl gcc-version.m4 -- Chris Ahlstrom dnl --------------------------------------------------------------------------- dnl dnl \file gcc-version.m4 dnl \library xpc_suite dnl \author Chris Ahlstrom dnl \date 2008-03-04 dnl \updates 2015-10-07 dnl \version $Revision$ dnl \license $XPC_SUITE_GPL_LICENSE$ dnl dnl Found somewhere, and modified with explanations. We've added two more dnl variables that get returned, too: dnl dnl gcc_version_major dnl gcc_version_minor dnl dnl \references dnl - http://www.gnu.org/software/automake/manual/ dnl autoconf/Changequote-is-Evil.html dnl - http://www.shlomifish.org/lecture/Autotools/slides/changequote.html dnl dnl We fixed this macro to add the space in each changequote call. dnl dnl \usage dnl AC_GCC_VERSION(TOPSRCDIR) dnl dnl where TOPSRCDIR is the top-level source directory. dnl dnl Set up the variables: dnl dnl gcc_version_trigger: pathname of gcc's version.c, if available dnl dnl This comes out as "/gcc/version.c", which seems to mean that dnl version.c is not available. But, if we pass, to ./configure, the dnl --with-gcc-version-trigger argument, then it comes out simply as dnl "yes". Huh? dnl dnl gcc_version_full: full gcc version string dnl dnl This comes out as: 4.1.2 20061115 (prerelease) (Debian 4.1.1-21) dnl dnl gcc_version: the first "word" in $gcc_version_full dnl dnl This comes out as: 4.1.2 dnl dnl If we knew the compiler was gcc, we could use the -dumpversion dnl argument to obtain this value. dnl dnl --------------------------------------------------------------------------- AC_DEFUN([AC_GCC_VERSION], [ changequote(, )dnl if test "${with_gcc_version_trigger+set}" = set; then gcc_version_trigger=$with_gcc_version_trigger else gcc_version_trigger=$1/gcc/version.c fi if test -f "${gcc_version_trigger}"; then gcc_version_full=`grep version_string "${gcc_version_trigger}" | sed -e 's/.*"\([^"]*\)".*/\1/'` else gcc_version_full=`$CC -v 2>&1 | sed -n 's/^gcc version //p'` fi gcc_version=`echo ${gcc_version_full} | sed -e 's/\([^ ]*\) .*/\1/'` gcc_version_major=`$CC -v 2>&1 | sed -n 's/^gcc version \(.\).*/\1/p'` gcc_version_minor=`gcc -v 2>&1 | sed -n 's/^gcc version [0-9]\.\(.\).*/\1/p'` changequote([, ])dnl AC_SUBST(gcc_version_trigger) AC_SUBST(gcc_version_full) AC_SUBST(gcc_version) ])dnl dnl gcc-version.m4 dnl dnl vim: ts=3 sw=3 et ft=config ================================================ FILE: m4/inttypes.m4 ================================================ # inttypes.m4 serial 1 (gettext-0.11.4) dnl Copyright (C) 1997-2002 Free Software Foundation, Inc. dnl This file is free software, distributed under the terms of the GNU dnl General Public License. As a special exception to the GNU General dnl Public License, this file may be distributed as part of a program dnl that contains a configuration script generated by Autoconf, under dnl the same distribution terms as the rest of that program. dnl From Paul Eggert. # Define HAVE_INTTYPES_H if exists and doesn't clash with # . AC_DEFUN([gt_HEADER_INTTYPES_H], [ AC_CACHE_CHECK([for inttypes.h], gt_cv_header_inttypes_h, [ AC_TRY_COMPILE( [#include #include ], [], gt_cv_header_inttypes_h=yes, gt_cv_header_inttypes_h=no) ]) if test $gt_cv_header_inttypes_h = yes; then AC_DEFINE_UNQUOTED(HAVE_INTTYPES_H, 1, [Define if exists and doesn't clash with .]) fi ]) ================================================ FILE: m4/isc-posix.m4 ================================================ #serial 1 # This test replaces the one in autoconf. # Currently this macro should have the same name as the autoconf macro # because gettext's gettext.m4 (distributed in the automake package) # still uses it. Otherwise, the use in gettext.m4 makes autoheader # give these diagnostics: # configure.in:556: AC_TRY_COMPILE was called before AC_ISC_POSIX # configure.in:556: AC_TRY_RUN was called before AC_ISC_POSIX undefine([AC_ISC_POSIX]) AC_DEFUN([AC_ISC_POSIX], [ dnl This test replaces the obsolescent AC_ISC_POSIX kludge. AC_CHECK_LIB(cposix, strerror, [LIBS="$LIBS -lcposix"]) ] ) ================================================ FILE: m4/mm-common.m4 ================================================ ## Copyright (c) 2009 Openismus GmbH ## ## macros/mm-common.m4. Generated from mm-common.m4.in by configure. ## ## mm-common 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. ## ## mm-common 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 mm-common. If not, see . #serial 20090814 ## _MM_PRE_INIT ## ## Private pre-initialization macro for use with AC_REQUIRE(). For now, ## all it does is register a forbidden token pattern with autom4te, so ## that unexpanded macro calls in the output can be caught. ## AC_DEFUN([_MM_PRE_INIT], [m4_pattern_forbid([^_?MM_])]) ## _MM_PREREQ(this-package, this-version, min-version, user-package) ## m4_define([_MM_PREREQ], [dnl m4_if(m4_quote(m4_version_compare([$2], [$3])), [-1], [m4_fatal([$4 requires $1 $3 (version $2 is installed)])])[]dnl ]) ## MM_PREREQ(min-version) ## ## Require at least mm-common to be installed, otherwise ## abort with a fatal error message. The version is checked statically ## at the time the configure script is generated. ## AC_DEFUN([MM_PREREQ], [dnl m4_assert([$# >= 1])[]dnl AC_REQUIRE([_MM_PRE_INIT])[]dnl _MM_PREREQ([mm-common], [0.9.8], [$1], m4_defn([AC_PACKAGE_NAME]))[]dnl ]) ================================================ FILE: m4/mm-warnings.m4 ================================================ ## Copyright (c) 2009 Openismus GmbH ## ## This file is part of mm-common. ## ## mm-common 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. ## ## mm-common 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 mm-common. If not, see . #serial 20091103 ## _MM_ARG_ENABLE_WARNINGS_OPTION ## ## Implementation helper macro of MM_ARG_ENABLE_WARNINGS(). Pulled in ## through AC_REQUIRE() so that it is only expanded once. ## m4_define([_MM_ARG_ENABLE_WARNINGS_OPTION], [dnl AC_PROVIDE([$0])[]dnl AC_ARG_ENABLE([warnings], [AS_HELP_STRING([[--enable-warnings[=min|max|fatal|no]]], [set compiler pedantry level [default=min]])], [mm_enable_warnings=$enableval], [mm_enable_warnings=min])[]dnl ]) ## MM_ARG_ENABLE_WARNINGS(variable, min-flags, max-flags, [deprecation-prefixes]) ## ## Provide the --enable-warnings configure argument, set to "min" by default. ## and should be space-separated lists of compiler ## warning flags to use with --enable-warnings=min or --enable-warnings=max, ## respectively. Warning level "fatal" is the same as "max" but in addition ## enables -Werror mode. ## ## If not empty, should be a list of module prefixes ## which is expanded to -D_DISABLE_DEPRECATED flags if fatal warnings ## are enabled, too. ## ## For instance, your configure.ac file might use the macro like this: ## ## MM_ARG_ENABLE_WARNINGS([EXAMPLE_WFLAGS], ## [-Wall], ## [-pedantic -Wall -Wextra], ## [G PANGO ATK GDK GDK_PIXBUF GTK]) ## ## Your Makefile.am could then contain a line such as this: ## ## AM_CFLAGS = $(EXAMPLE_WFLAGS) ## ## In order to determine the warning options to use with the C++ compiler, ## call AC_LANG([C++]) first to change the current language. If different ## output variables are used, it is also fine to call MM_ARG_ENABLE_WARNINGS ## repeatedly, once for each language setting. ## ## You may force people to fix warnings when creating release tarballs by ## adding this line to your Makefile.am: ## ## DISTCHECK_CONFIGURE_FLAGS = --enable-warnings=fatal ## AC_DEFUN([MM_ARG_ENABLE_WARNINGS], [dnl m4_assert([$# >= 3])[]dnl AC_REQUIRE([_MM_PRE_INIT])[]dnl AC_REQUIRE([_MM_ARG_ENABLE_WARNINGS_OPTION])[]dnl dnl AS_CASE([$ac_compile], [[*'$CXXFLAGS '*]], [mm_lang='C++' mm_cc=$CXX mm_conftest="conftest.[$]{ac_ext-cc}"], [[*'$CFLAGS '*]], [mm_lang=C mm_cc=$CC mm_conftest="conftest.[$]{ac_ext-c}"], [AC_MSG_ERROR([[current language is neither C nor C++]])]) dnl AC_MSG_CHECKING([which $mm_lang compiler warning flags to use]) m4_ifval([$4], [mm_deprecation_flags= ])mm_tested_flags= dnl AS_CASE([$mm_enable_warnings], [no], [mm_warning_flags=], [max], [mm_warning_flags="$3"], [fatal], [mm_warning_flags="$3 -Werror"[]m4_ifval([$4], [ for mm_prefix in $4 do mm_deprecation_flags="$mm_deprecation_flags-D[$]{mm_prefix}_DISABLE_DEPRECATED " done])], [mm_warning_flags="$2"]) dnl AS_IF([test "x$mm_warning_flags" != x], [ # Keep in mind that the dummy source must be devoid of any # problems that might cause diagnostics. AC_LANG_CONFTEST([AC_LANG_SOURCE([[ int main(int argc, char** argv) { return (argv != 0) ? argc : 0; } ]])]) for mm_flag in $mm_warning_flags do # Test whether the compiler accepts the flag. Look at standard output, # since GCC only shows a warning message if an option is not supported. mm_cc_out=`$mm_cc $mm_tested_flags $mm_flag -c "$mm_conftest" 2>&1 || echo failed` rm -f "conftest.[$]{OBJEXT-o}" AS_IF([test "x$mm_cc_out" = x], [AS_IF([test "x$mm_tested_flags" = x], [mm_tested_flags=$mm_flag], [mm_tested_flags="$mm_tested_flags $mm_flag"])], [cat <<_MMEOF >&AS_MESSAGE_LOG_FD $mm_cc: $mm_cc_out _MMEOF ]) done rm -f "$mm_conftest" ]) mm_all_flags=m4_ifval([$4], [$mm_deprecation_flags])$mm_tested_flags AC_SUBST([$1], [$mm_all_flags]) dnl test "x$mm_all_flags" != x || mm_all_flags=none AC_MSG_RESULT([$mm_all_flags])[]dnl ]) ================================================ FILE: m4/pkg.m4 ================================================ # pkg.m4 - Macros to locate and use pkg-config. -*- Autoconf -*- # serial 13 (pkgconf) dnl Copyright © 2004 Scott James Remnant . dnl Copyright © 2012-2015 Dan Nicholson dnl dnl This program is free software; you can redistribute it and/or modify dnl it under the terms of the GNU General Public License as published by dnl the Free Software Foundation; either version 2 of the License, or dnl (at your option) any later version. dnl dnl This program is distributed in the hope that it will be useful, but dnl WITHOUT ANY WARRANTY; without even the implied warranty of dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU dnl General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License dnl along with this program; if not, see . dnl dnl As a special exception to the GNU General Public License, if you dnl distribute this file as part of a program that contains a dnl configuration script generated by Autoconf, you may include it under dnl the same distribution terms that you use for the rest of that dnl program. dnl PKG_PREREQ(MIN-VERSION) dnl ----------------------- dnl Since: 0.29 dnl dnl Verify that the version of the pkg-config macros are at least dnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's dnl installed version of pkg-config, this checks the developer's version dnl of pkg.m4 when generating configure. dnl dnl To ensure that this macro is defined, also add: dnl m4_ifndef([PKG_PREREQ], dnl [m4_fatal([must install pkg-config 0.29 or later before running autoconf/autogen])]) dnl dnl See the "Since" comment for each macro you use to see what version dnl of the macros you require. m4_defun([PKG_PREREQ], [m4_define([PKG_MACROS_VERSION], [0.29.2]) m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1, [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])]) ])dnl PKG_PREREQ dnl PKG_PROG_PKG_CONFIG([MIN-VERSION], [ACTION-IF-NOT-FOUND]) dnl --------------------------------------------------------- dnl Since: 0.16 dnl dnl Search for the pkg-config tool and set the PKG_CONFIG variable to dnl first found in the path. Checks that the version of pkg-config found dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is dnl used since that's the first version where most current features of dnl pkg-config existed. dnl dnl If pkg-config is not found or older than specified, it will result dnl in an empty PKG_CONFIG variable. To avoid widespread issues with dnl scripts not checking it, ACTION-IF-NOT-FOUND defaults to aborting. dnl You can specify [PKG_CONFIG=false] as an action instead, which would dnl result in pkg-config tests failing, but no bogus error messages. AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) fi if test -n "$PKG_CONFIG"; then _pkg_min_version=m4_default([$1], [0.9.0]) AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) PKG_CONFIG="" fi fi if test -z "$PKG_CONFIG"; then m4_default([$2], [AC_MSG_ERROR([pkg-config not found])]) fi[]dnl ])dnl PKG_PROG_PKG_CONFIG dnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ------------------------------------------------------------------- dnl Since: 0.18 dnl dnl Check to see whether a particular set of modules exists. Similar to dnl PKG_CHECK_MODULES(), but does not set variables or print errors. dnl dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) dnl only at the first occurrence in configure.ac, so if the first place dnl it's called might be skipped (such as if it is within an "if", you dnl have to call PKG_CHECK_EXISTS manually AC_DEFUN([PKG_CHECK_EXISTS], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl if test -n "$PKG_CONFIG" && \ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then m4_default([$2], [:]) m4_ifvaln([$3], [else $3])dnl fi]) dnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) dnl --------------------------------------------- dnl Internal wrapper calling pkg-config via PKG_CONFIG and setting dnl pkg_failed based on the result. m4_define([_PKG_CONFIG], [if test -n "$$1"; then pkg_cv_[]$1="$$1" elif test -n "$PKG_CONFIG"; then PKG_CHECK_EXISTS([$3], [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes ], [pkg_failed=yes]) else pkg_failed=untried fi[]dnl ])dnl _PKG_CONFIG dnl _PKG_SHORT_ERRORS_SUPPORTED dnl --------------------------- dnl Internal check to see if pkg-config supports short errors. AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi[]dnl ])dnl _PKG_SHORT_ERRORS_SUPPORTED dnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], dnl [ACTION-IF-NOT-FOUND]) dnl -------------------------------------------------------------- dnl Since: 0.4.0 dnl dnl Note that if there is a possibility the first call to dnl PKG_CHECK_MODULES might not happen, you should be sure to include an dnl explicit call to PKG_PROG_PKG_CONFIG in your configure.ac AC_DEFUN([PKG_CHECK_MODULES], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl pkg_failed=no AC_MSG_CHECKING([for $2]) _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) _PKG_CONFIG([$1][_LIBS], [libs], [$2]) m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS and $1[]_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details.]) if test $pkg_failed = yes; then AC_MSG_RESULT([no]) _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` else $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD m4_default([$4], [AC_MSG_ERROR( [Package requirements ($2) were not met: $$1_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. _PKG_TEXT])[]dnl ]) elif test $pkg_failed = untried; then AC_MSG_RESULT([no]) m4_default([$4], [AC_MSG_FAILURE( [The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. _PKG_TEXT To get pkg-config, see .])[]dnl ]) else $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS $1[]_LIBS=$pkg_cv_[]$1[]_LIBS AC_MSG_RESULT([yes]) $3 fi[]dnl ])dnl PKG_CHECK_MODULES dnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], dnl [ACTION-IF-NOT-FOUND]) dnl --------------------------------------------------------------------- dnl Since: 0.29 dnl dnl Checks for existence of MODULES and gathers its build flags with dnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags dnl and VARIABLE-PREFIX_LIBS from --libs. dnl dnl Note that if there is a possibility the first call to dnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to dnl include an explicit call to PKG_PROG_PKG_CONFIG in your dnl configure.ac. AC_DEFUN([PKG_CHECK_MODULES_STATIC], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl _save_PKG_CONFIG=$PKG_CONFIG PKG_CONFIG="$PKG_CONFIG --static" PKG_CHECK_MODULES($@) PKG_CONFIG=$_save_PKG_CONFIG[]dnl ])dnl PKG_CHECK_MODULES_STATIC dnl PKG_INSTALLDIR([DIRECTORY]) dnl ------------------------- dnl Since: 0.27 dnl dnl Substitutes the variable pkgconfigdir as the location where a module dnl should install pkg-config .pc files. By default the directory is dnl $libdir/pkgconfig, but the default can be changed by passing dnl DIRECTORY. The user can override through the --with-pkgconfigdir dnl parameter. AC_DEFUN([PKG_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([pkgconfigdir], [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, [with_pkgconfigdir=]pkg_default) AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ])dnl PKG_INSTALLDIR dnl PKG_NOARCH_INSTALLDIR([DIRECTORY]) dnl -------------------------------- dnl Since: 0.27 dnl dnl Substitutes the variable noarch_pkgconfigdir as the location where a dnl module should install arch-independent pkg-config .pc files. By dnl default the directory is $datadir/pkgconfig, but the default can be dnl changed by passing DIRECTORY. The user can override through the dnl --with-noarch-pkgconfigdir parameter. AC_DEFUN([PKG_NOARCH_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([noarch-pkgconfigdir], [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, [with_noarch_pkgconfigdir=]pkg_default) AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ])dnl PKG_NOARCH_INSTALLDIR dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ------------------------------------------- dnl Since: 0.28 dnl dnl Retrieves the value of the pkg-config variable for the given module. AC_DEFUN([PKG_CHECK_VAR], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl _PKG_CONFIG([$1], [variable="][$3]["], [$2]) AS_VAR_COPY([$1], [pkg_cv_][$1]) AS_VAR_IF([$1], [""], [$5], [$4])dnl ])dnl PKG_CHECK_VAR dnl PKG_WITH_MODULES(VARIABLE-PREFIX, MODULES, dnl [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND], dnl [DESCRIPTION], [DEFAULT]) dnl ------------------------------------------ dnl dnl Prepare a "--with-" configure option using the lowercase dnl [VARIABLE-PREFIX] name, merging the behaviour of AC_ARG_WITH and dnl PKG_CHECK_MODULES in a single macro. AC_DEFUN([PKG_WITH_MODULES], [ m4_pushdef([with_arg], m4_tolower([$1])) m4_pushdef([description], [m4_default([$5], [build with ]with_arg[ support])]) m4_pushdef([def_arg], [m4_default([$6], [auto])]) m4_pushdef([def_action_if_found], [AS_TR_SH([with_]with_arg)=yes]) m4_pushdef([def_action_if_not_found], [AS_TR_SH([with_]with_arg)=no]) m4_case(def_arg, [yes],[m4_pushdef([with_without], [--without-]with_arg)], [m4_pushdef([with_without],[--with-]with_arg)]) AC_ARG_WITH(with_arg, AS_HELP_STRING(with_without, description[ @<:@default=]def_arg[@:>@]),, [AS_TR_SH([with_]with_arg)=def_arg]) AS_CASE([$AS_TR_SH([with_]with_arg)], [yes],[PKG_CHECK_MODULES([$1],[$2],$3,$4)], [auto],[PKG_CHECK_MODULES([$1],[$2], [m4_n([def_action_if_found]) $3], [m4_n([def_action_if_not_found]) $4])]) m4_popdef([with_arg]) m4_popdef([description]) m4_popdef([def_arg]) ])dnl PKG_WITH_MODULES dnl PKG_HAVE_WITH_MODULES(VARIABLE-PREFIX, MODULES, dnl [DESCRIPTION], [DEFAULT]) dnl ----------------------------------------------- dnl dnl Convenience macro to trigger AM_CONDITIONAL after PKG_WITH_MODULES dnl check._[VARIABLE-PREFIX] is exported as make variable. AC_DEFUN([PKG_HAVE_WITH_MODULES], [ PKG_WITH_MODULES([$1],[$2],,,[$3],[$4]) AM_CONDITIONAL([HAVE_][$1], [test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"]) ])dnl PKG_HAVE_WITH_MODULES dnl PKG_HAVE_DEFINE_WITH_MODULES(VARIABLE-PREFIX, MODULES, dnl [DESCRIPTION], [DEFAULT]) dnl ------------------------------------------------------ dnl dnl Convenience macro to run AM_CONDITIONAL and AC_DEFINE after dnl PKG_WITH_MODULES check. HAVE_[VARIABLE-PREFIX] is exported as make dnl and preprocessor variable. AC_DEFUN([PKG_HAVE_DEFINE_WITH_MODULES], [ PKG_HAVE_WITH_MODULES([$1],[$2],[$3],[$4]) AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"], [AC_DEFINE([HAVE_][$1], 1, [Enable ]m4_tolower([$1])[ support])]) ])dnl PKG_HAVE_DEFINE_WITH_MODULES ================================================ FILE: m4/seq64_mingw_dll.m4 ================================================ dnl Version: MPL 1.1 / GPLv3+ / LGPLv3+ dnl dnl The contents of this file are subject to the Mozilla Public License Version dnl 1.1 (the "License"); you may not use this file except in compliance with dnl the License or as specified alternatively below. You may obtain a copy of dnl the License at http://www.mozilla.org/MPL/ dnl dnl Software distributed under the License is distributed on an "AS IS" basis, dnl WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License dnl for the specific language governing rights and limitations under the dnl License. dnl dnl Major Contributor(s): dnl Copyright (C) 2012 Red Hat, Inc., David Tardon dnl (initial developer) dnl dnl All Rights Reserved. dnl dnl For minor contributions see the git repository. dnl dnl Alternatively, the contents of this file may be used under the terms of dnl either the GNU General Public License Version 3 or later (the "GPLv3+"), or dnl the GNU Lesser General Public License Version 3 or later (the "LGPLv3+"), dnl in which case the provisions of the GPLv3+ or the LGPLv3+ are applicable dnl instead of those above. dnl dnl SEQ66_MINGW_CHECK_DLL dnl ( dnl variable-infix, dll-name-stem, [action-if-found], [action-if-not-found] dnl ) dnl dnl Checks for presence of dll dll-name-stem . Sets variable dnl MINGW_variable-infix_DLL if found, issues an error otherwise. dnl dnl It recognizes these dll patterns (x, y match any character, but they dnl are supposed to be numerals): dnl dnl * name-x.dll dnl * name-xy.dll dnl * name-x.y.dll dnl * name.dll dnl dnl Example: dnl dnl SEQ66_MINGW_CHECK_DLL([EXPAT], [libexpat]) dnl might result in MINGW_EXPAT_DLL=libexpat-1.dll being set. dnl dnl uses CC, WITH_MINGW dnl dnl Tweaked by Chris Ahlstrom for use in the Seq66 project. dnl Changed libo to SEQ66. dnl dnl ------------------------------------------------------------------------- AC_DEFUN([SEQ66_MINGW_CHECK_DLL], [AC_ARG_VAR([MINGW_][$1][_DLL],[output variable containing the found dll name])dnl if test -n "$WITH_MINGW"; then dnl TODO move this to configure: there is no need to call $CC more than once _seq66_mingw_dlldir=`$CC -print-sysroot`/mingw/bin _seq66_mingw_dllname= AC_MSG_CHECKING([for $2 dll]) dnl try one- or two-numbered version _seq66_mingw_try_dll([$2][-?.dll]) if test "$_seq66_mingw_dllname" = ""; then _seq66_mingw_try_dll([$2][-??.dll]) fi dnl maybe the version contains a dot (e.g., libdb) if test "$_seq66_mingw_dllname" = ""; then _seq66_mingw_try_dll([$2][-?.?.dll]) fi dnl maybe it is not versioned if test "$_seq66_mingw_dllname" = ""; then _seq66_mingw_try_dll([$2][.dll]) fi if test "$_seq66_mingw_dllname" = ""; then AC_MSG_RESULT([no]) m4_default([$4],[AC_MSG_ERROR([no dll found for $2])]) else AC_MSG_RESULT([$_seq66_mingw_dllname]) [MINGW_][$1][_DLL]="$_seq66_mingw_dllname" m4_default([$3],[]) fi fi[]dnl ]) # seq66_MINGW_CHECK_DLL # SEQ66_MINGW_TRY_DLL(variable-infix,dll-name-stem) # # Checks for presence of dll dll-name-stem . Sets variable # MINGW_variable-infix_DLL if found, does nothing otherwise. # # See SEQ66_MINGW_CHECK_DLL for further info. # # uses CC, WITH_MINGW # ------------------------------------------------ # AC_DEFUN([SEQ66_MINGW_TRY_DLL], [dnl shortcut: do not test for already found dlls if test -z "$[MINGW_][$1][_DLL]"; then SEQ66_MINGW_CHECK_DLL([$1],[$2],[[]],[[]]) fi[]dnl ]) # SEQ66_MINGW_TRY_DLL # _seq66_mingw_try_dll(dll-name,dll-dir) m4_define([_seq66_mingw_try_dll], [_seq66_mingw_trying_dll=`ls "[$_seq66_mingw_dlldir]"/[$1] 2>/dev/null` if test -f "$_seq66_mingw_trying_dll"; then _seq66_mingw_dllname=`basename "$_seq66_mingw_trying_dll"` fi[]dnl ]) # _seq66_mingw_try_dll dnl vim:set shiftwidth=4 softtabstop=4 expandtab: ================================================ FILE: m4/threadlib.m4 ================================================ # threadlib.m4 serial 5 (gettext-0.18) dnl Copyright (C) 2005-2010 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl From Bruno Haible. dnl gl_THREADLIB dnl ------------ dnl Tests for a multithreading library to be used. dnl Defines at most one of the macros USE_POSIX_THREADS, USE_SOLARIS_THREADS, dnl USE_PTH_THREADS, USE_WIN32_THREADS dnl Sets the variables LIBTHREAD and LTLIBTHREAD to the linker options for use dnl in a Makefile (LIBTHREAD for use without libtool, LTLIBTHREAD for use with dnl libtool). dnl Sets the variables LIBMULTITHREAD and LTLIBMULTITHREAD similarly, for dnl programs that really need multithread functionality. The difference dnl between LIBTHREAD and LIBMULTITHREAD is that on platforms supporting weak dnl symbols, typically LIBTHREAD="" whereas LIBMULTITHREAD="-lpthread". dnl Adds to CPPFLAGS the flag -D_REENTRANT or -D_THREAD_SAFE if needed for dnl multithread-safe programs. AC_DEFUN([gl_THREADLIB_EARLY], [ AC_REQUIRE([gl_THREADLIB_EARLY_BODY]) ]) dnl The guts of gl_THREADLIB_EARLY. Needs to be expanded only once. AC_DEFUN([gl_THREADLIB_EARLY_BODY], [ dnl Ordering constraints: This macro modifies CPPFLAGS in a way that dnl influences the result of the autoconf tests that test for *_unlocked dnl declarations, on AIX 5 at least. Therefore it must come early. AC_BEFORE([$0], [gl_FUNC_GLIBC_UNLOCKED_IO])dnl AC_BEFORE([$0], [gl_ARGP])dnl AC_REQUIRE([AC_CANONICAL_HOST]) dnl _GNU_SOURCE is needed for pthread_rwlock_t on glibc systems. dnl AC_USE_SYSTEM_EXTENSIONS was introduced in autoconf 2.60 and obsoletes dnl AC_GNU_SOURCE. m4_ifdef([AC_USE_SYSTEM_EXTENSIONS], [AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])], [AC_REQUIRE([AC_GNU_SOURCE])]) dnl Check for multithreading. m4_divert_text([DEFAULTS], [gl_use_threads_default=]) AC_ARG_ENABLE([threads], AC_HELP_STRING([--enable-threads={posix|solaris|pth|win32}], [specify multithreading API]) AC_HELP_STRING([--disable-threads], [build without multithread safety]), [gl_use_threads=$enableval], [if test -n "$gl_use_threads_default"; then gl_use_threads="$gl_use_threads_default" else changequote(,)dnl case "$host_os" in dnl Disable multithreading by default on OSF/1, because it interferes dnl with fork()/exec(): When msgexec is linked with -lpthread, its dnl child process gets an endless segmentation fault inside execvp(). dnl Disable multithreading by default on Cygwin 1.5.x, because it has dnl bugs that lead to endless loops or crashes. See dnl . osf*) gl_use_threads=no ;; cygwin*) case `uname -r` in 1.[0-5].*) gl_use_threads=no ;; *) gl_use_threads=yes ;; esac ;; *) gl_use_threads=yes ;; esac changequote([,])dnl fi ]) if test "$gl_use_threads" = yes || test "$gl_use_threads" = posix; then # For using : case "$host_os" in osf*) # On OSF/1, the compiler needs the flag -D_REENTRANT so that it # groks . cc also understands the flag -pthread, but # we don't use it because 1. gcc-2.95 doesn't understand -pthread, # 2. putting a flag into CPPFLAGS that has an effect on the linker # causes the AC_TRY_LINK test below to succeed unexpectedly, # leading to wrong values of LIBTHREAD and LTLIBTHREAD. CPPFLAGS="$CPPFLAGS -D_REENTRANT" ;; esac # Some systems optimize for single-threaded programs by default, and # need special flags to disable these optimizations. For example, the # definition of 'errno' in . case "$host_os" in aix* | freebsd*) CPPFLAGS="$CPPFLAGS -D_THREAD_SAFE" ;; solaris*) CPPFLAGS="$CPPFLAGS -D_REENTRANT" ;; esac fi ]) dnl The guts of gl_THREADLIB. Needs to be expanded only once. AC_DEFUN([gl_THREADLIB_BODY], [ AC_REQUIRE([gl_THREADLIB_EARLY_BODY]) gl_threads_api=none LIBTHREAD= LTLIBTHREAD= LIBMULTITHREAD= LTLIBMULTITHREAD= if test "$gl_use_threads" != no; then dnl Check whether the compiler and linker support weak declarations. AC_CACHE_CHECK([whether imported symbols can be declared weak], [gl_cv_have_weak], [gl_cv_have_weak=no dnl First, test whether the compiler accepts it syntactically. AC_TRY_LINK([extern void xyzzy (); #pragma weak xyzzy], [xyzzy();], [gl_cv_have_weak=maybe]) if test $gl_cv_have_weak = maybe; then dnl Second, test whether it actually works. On Cygwin 1.7.2, with dnl gcc 4.3, symbols declared weak always evaluate to the address 0. AC_TRY_RUN([ #include #pragma weak fputs int main () { return (fputs == NULL); }], [gl_cv_have_weak=yes], [gl_cv_have_weak=no], [dnl When cross-compiling, assume that only ELF platforms support dnl weak symbols. AC_EGREP_CPP([Extensible Linking Format], [#ifdef __ELF__ Extensible Linking Format #endif ], [gl_cv_have_weak="guessing yes"], [gl_cv_have_weak="guessing no"]) ]) fi ]) if test "$gl_use_threads" = yes || test "$gl_use_threads" = posix; then # On OSF/1, the compiler needs the flag -pthread or -D_REENTRANT so that # it groks . It's added above, in gl_THREADLIB_EARLY_BODY. AC_CHECK_HEADER([pthread.h], [gl_have_pthread_h=yes], [gl_have_pthread_h=no]) if test "$gl_have_pthread_h" = yes; then # Other possible tests: # -lpthreads (FSU threads, PCthreads) # -lgthreads gl_have_pthread= # Test whether both pthread_mutex_lock and pthread_mutexattr_init exist # in libc. IRIX 6.5 has the first one in both libc and libpthread, but # the second one only in libpthread, and lock.c needs it. AC_TRY_LINK([#include ], [pthread_mutex_lock((pthread_mutex_t*)0); pthread_mutexattr_init((pthread_mutexattr_t*)0);], [gl_have_pthread=yes]) # Test for libpthread by looking for pthread_kill. (Not pthread_self, # since it is defined as a macro on OSF/1.) if test -n "$gl_have_pthread"; then # The program links fine without libpthread. But it may actually # need to link with libpthread in order to create multiple threads. AC_CHECK_LIB([pthread], [pthread_kill], [LIBMULTITHREAD=-lpthread LTLIBMULTITHREAD=-lpthread # On Solaris and HP-UX, most pthread functions exist also in libc. # Therefore pthread_in_use() needs to actually try to create a # thread: pthread_create from libc will fail, whereas # pthread_create will actually create a thread. case "$host_os" in solaris* | hpux*) AC_DEFINE([PTHREAD_IN_USE_DETECTION_HARD], [1], [Define if the pthread_in_use() detection is hard.]) esac ]) else # Some library is needed. Try libpthread and libc_r. AC_CHECK_LIB([pthread], [pthread_kill], [gl_have_pthread=yes LIBTHREAD=-lpthread LTLIBTHREAD=-lpthread LIBMULTITHREAD=-lpthread LTLIBMULTITHREAD=-lpthread]) if test -z "$gl_have_pthread"; then # For FreeBSD 4. AC_CHECK_LIB([c_r], [pthread_kill], [gl_have_pthread=yes LIBTHREAD=-lc_r LTLIBTHREAD=-lc_r LIBMULTITHREAD=-lc_r LTLIBMULTITHREAD=-lc_r]) fi fi if test -n "$gl_have_pthread"; then gl_threads_api=posix AC_DEFINE([USE_POSIX_THREADS], [1], [Define if the POSIX multithreading library can be used.]) if test -n "$LIBMULTITHREAD" || test -n "$LTLIBMULTITHREAD"; then if case "$gl_cv_have_weak" in *yes) true;; *) false;; esac; then AC_DEFINE([USE_POSIX_THREADS_WEAK], [1], [Define if references to the POSIX multithreading library should be made weak.]) LIBTHREAD= LTLIBTHREAD= fi fi fi fi fi if test -z "$gl_have_pthread"; then if test "$gl_use_threads" = yes || test "$gl_use_threads" = solaris; then gl_have_solaristhread= gl_save_LIBS="$LIBS" LIBS="$LIBS -lthread" AC_TRY_LINK([#include #include ], [thr_self();], [gl_have_solaristhread=yes]) LIBS="$gl_save_LIBS" if test -n "$gl_have_solaristhread"; then gl_threads_api=solaris LIBTHREAD=-lthread LTLIBTHREAD=-lthread LIBMULTITHREAD="$LIBTHREAD" LTLIBMULTITHREAD="$LTLIBTHREAD" AC_DEFINE([USE_SOLARIS_THREADS], [1], [Define if the old Solaris multithreading library can be used.]) if case "$gl_cv_have_weak" in *yes) true;; *) false;; esac; then AC_DEFINE([USE_SOLARIS_THREADS_WEAK], [1], [Define if references to the old Solaris multithreading library should be made weak.]) LIBTHREAD= LTLIBTHREAD= fi fi fi fi if test "$gl_use_threads" = pth; then gl_save_CPPFLAGS="$CPPFLAGS" AC_LIB_LINKFLAGS([pth]) gl_have_pth= gl_save_LIBS="$LIBS" LIBS="$LIBS -lpth" AC_TRY_LINK([#include ], [pth_self();], [gl_have_pth=yes]) LIBS="$gl_save_LIBS" if test -n "$gl_have_pth"; then gl_threads_api=pth LIBTHREAD="$LIBPTH" LTLIBTHREAD="$LTLIBPTH" LIBMULTITHREAD="$LIBTHREAD" LTLIBMULTITHREAD="$LTLIBTHREAD" AC_DEFINE([USE_PTH_THREADS], [1], [Define if the GNU Pth multithreading library can be used.]) if test -n "$LIBMULTITHREAD" || test -n "$LTLIBMULTITHREAD"; then if case "$gl_cv_have_weak" in *yes) true;; *) false;; esac; then AC_DEFINE([USE_PTH_THREADS_WEAK], [1], [Define if references to the GNU Pth multithreading library should be made weak.]) LIBTHREAD= LTLIBTHREAD= fi fi else CPPFLAGS="$gl_save_CPPFLAGS" fi fi if test -z "$gl_have_pthread"; then if test "$gl_use_threads" = yes || test "$gl_use_threads" = win32; then if { case "$host_os" in mingw*) true;; *) false;; esac }; then gl_threads_api=win32 AC_DEFINE([USE_WIN32_THREADS], [1], [Define if the Win32 multithreading API can be used.]) fi fi fi fi AC_MSG_CHECKING([for multithread API to use]) AC_MSG_RESULT([$gl_threads_api]) AC_SUBST([LIBTHREAD]) AC_SUBST([LTLIBTHREAD]) AC_SUBST([LIBMULTITHREAD]) AC_SUBST([LTLIBMULTITHREAD]) ]) AC_DEFUN([gl_THREADLIB], [ AC_REQUIRE([gl_THREADLIB_EARLY]) AC_REQUIRE([gl_THREADLIB_BODY]) ]) dnl gl_DISABLE_THREADS dnl ------------------ dnl Sets the gl_THREADLIB default so that threads are not used by default. dnl The user can still override it at installation time, by using the dnl configure option '--enable-threads'. AC_DEFUN([gl_DISABLE_THREADS], [ m4_divert_text([INIT_PREPARE], [gl_use_threads_default=no]) ]) dnl Survey of platforms: dnl dnl Platform Available Compiler Supports test-lock dnl flavours option weak result dnl --------------- --------- --------- -------- --------- dnl Linux 2.4/glibc posix -lpthread Y OK dnl dnl GNU Hurd/glibc posix dnl dnl FreeBSD 5.3 posix -lc_r Y dnl posix -lkse ? Y dnl posix -lpthread ? Y dnl posix -lthr Y dnl dnl FreeBSD 5.2 posix -lc_r Y dnl posix -lkse Y dnl posix -lthr Y dnl dnl FreeBSD 4.0,4.10 posix -lc_r Y OK dnl dnl NetBSD 1.6 -- dnl dnl OpenBSD 3.4 posix -lpthread Y OK dnl dnl MacOS X 10.[123] posix -lpthread Y OK dnl dnl Solaris 7,8,9 posix -lpthread Y Sol 7,8: 0.0; Sol 9: OK dnl solaris -lthread Y Sol 7,8: 0.0; Sol 9: OK dnl dnl HP-UX 11 posix -lpthread N (cc) OK dnl Y (gcc) dnl dnl IRIX 6.5 posix -lpthread Y 0.5 dnl dnl AIX 4.3,5.1 posix -lpthread N AIX 4: 0.5; AIX 5: OK dnl dnl OSF/1 4.0,5.1 posix -pthread (cc) N OK dnl -lpthread (gcc) Y dnl dnl Cygwin posix -lpthread Y OK dnl dnl Any of the above pth -lpth 0.0 dnl dnl Mingw win32 N OK dnl dnl BeOS 5 -- dnl dnl The test-lock result shows what happens if in test-lock.c EXPLICIT_YIELD is dnl turned off: dnl OK if all three tests terminate OK, dnl 0.5 if the first test terminates OK but the second one loops endlessly, dnl 0.0 if the first test already loops endlessly. ================================================ FILE: m4/win32msc.m4 ================================================ dnl Check for M$-Compiler (C) 2000-2001 Fritz Elfert dnl dnl This program is free software; you can redistribute it and/or modify dnl it under the terms of the GNU General Public License as published by dnl the Free Software Foundation; either version 2 of the License, or dnl (at your option) any later version. dnl dnl This program is distributed in the hope that it will be useful, dnl but WITHOUT ANY WARRANTY; without even the implied warranty of dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the dnl GNU General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License dnl along with this program; if not, write to the Free Software dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. dnl dnl As a special exception to the GNU General Public License, if you dnl distribute this file as part of a program that contains a configuration dnl script generated by Autoconf, you may include it under the same dnl distribution terms that you use for the rest of that program. AC_DEFUN([NP_FIND_DOSENV],[ save_IFS="$IFS" $3=no IFS="," ENVVAR=`echo $$2 | tr ';' ','` for i in $ENVVAR ; do f=`cygpath -u "$i"` if test -r "$f/$1" ; then $3="$f" break fi done IFS="$save_IFS" ]) AC_DEFUN([NP_PROG_MSC],[ AC_ARG_WITH(wininc, [ --with-wininc=DIR define windows include directory], [win32inc="$withval"],[win32inc=no] ) AC_ARG_WITH(winlib, [ --with-winlib=DIR define windows lib directory], [win32lib="$withval"],[win32lib=no] ) AC_CACHE_CHECK(for MSC environment, np_cv_prog_msc, [ np_cv_prog_msc=no cat > conftest.c <&1` case "$RET" in conftest.c?) np_cv_prog_msc=yes ;; *) ;; esac ]) if test "x$np_cv_prog_msc" = "xyes" ; then if test "$win32inc" = "no" ; then AC_MSG_CHECKING(for Win32 include dir) NP_FIND_DOSENV(ras.h,INCLUDE,win32inc) AC_MSG_RESULT("$win32inc") fi if test "$win32inc" = "no" ; then AC_MSG_ERROR([Unable to get Win32 include dir from environment.]) AC_MSG_ERROR([use --with-wininc=DIR to set it manually]) fi if test "$win32lib" = "no" ; then AC_MSG_CHECKING(for Win32 lib dir) NP_FIND_DOSENV(wsock32.lib,LIB,win32lib) AC_MSG_RESULT("$win32lib") fi if test "$win32lib" = "no" ; then AC_MSG_ERROR([Unable to get Win32 lib dir from environment.]) AC_MSG_ERROR([use --with-winlib=DIR to set it manually]) fi CC="cl -nologo -GX -D__WIN32__ -D_MT -D_WIN32_WINNT=0x0500 -I$win32inc" CXX="cl -nologo -GX -D__WIN32__ -D_MT -D_WIN32_WINNT=0x0500 -I$win32inc" LD="link /libpath:$win32lib" AS=masm fi AM_CONDITIONAL(MSWIN32, test $np_cv_prog_msc = yes) ]) ================================================ FILE: m4/xpc_debug.m4 ================================================ dnl *************************************************************************** dnl xpc_debug.m4 dnl --------------------------------------------------------------------------- dnl dnl \file xpc_debug.m4 dnl \library xpc_suite subproject dnl \author Chris Ahlstrom dnl \date 2008-03-04 dnl \updates 2022-01-22 dnl \version $Revision$ dnl \license $XPC_SUITE_GPL_LICENSE$ dnl dnl Tests whether the user wants debugging, test coverage support, or dnl profiling. dnl dnl --enable-debug dnl --enable-calls (sets a flag and enables debug) dnl --enable-coverage dnl --enable-profile dnl dnl Set up for debugging. This macro is used to get the arguments supplied dnl to the configure script (./configure --enable-debug) dnl dnl It defines the symbol DBGFLAGS, which you should add to the COMMONFLAGS dnl for the compiler call. Optimization is turned off, and the symbols dnl DEBUG, _DEBUG, and NWIN32 are defined. Actually, we now do not define dnl any macros related to Windows in this file. It's for debugging, not OS dnl detection! dnl dnl In addition, the WARNINGS setting is changed to make sure all warnings dnl are shown. dnl dnl In addition, the debugging is turned on via the -ggdb flag, instead dnl of the -g flag, to see if there is any advantage. We're using the gdb dnl debugger. If you don't use it, change "-ggdb" to "-g". dnl dnl Also, for convenience, this macro adds additional debugging symbols to dnl supplement DEBUG: _DEBUG. dnl dnl The main variable that results from this script is DBGFLAGS. dnl dnl Also defined are DOCOVERAGE, COVFLAGS, DOPROFILE, PROFLAGS, DODEBUG, dnl and DOCALLS but normally we don't need them. dnl dnl 2022-05-11: Removed the DOCALLS/CALLFLAGS option and the c/h files, too dnl impractical. dnl dnl --------------------------------------------------------------------------- AC_DEFUN([AC_XPC_DEBUGGING], [ COVFLAGS="" PROFLAGS="" PROLDFLAGS="" OPTFLAGS="" DBGFLAGS="" MORFLAGS="" if test -n "$GCC"; then AC_MSG_CHECKING([whether to enable gcov coverage tests]) AC_ARG_ENABLE(coverage, [ --enable-coverage=(no/yes) Turn on a test-coverage build (default=no)], [ case "${enableval}" in yes) coverage=yes ;; no) coverage=no ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-coverage) ;; esac ], [ coverage=no ]) AM_CONDITIONAL(DOCOVERAGE, test x$coverage = xyes) if test "x$coverage" = "xyes" ; then COVFLAGS="-fprofile-arcs -ftest-coverage" OPTFLAGS="-O0" AC_MSG_RESULT(yes) else AC_MSG_RESULT(no) fi fi AC_SUBST([COVFLAGS]) AC_DEFINE_UNQUOTED([COVFLAGS], [$COVFLAGS], [Define COVFLAGS=-fprofile-arcs -ftest-coverage if coverage support is wanted.]) if test -n "$GCC"; then PROFLAGS= AC_MSG_CHECKING([whether to enable gprof profiling]) AC_ARG_ENABLE(profile, [ --enable-profile=(no/yes/gprof/prof) Turn on profiling builds (default=no, yes=gprof)], [ case "${enableval}" in yes) profile=yes ;; no) profile=no ;; prof) profile=prof ;; gprof) profile=gprof ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-profile) ;; esac ], [ profile=no ]) AM_CONDITIONAL(DOPROFILE, test x$profile = xyes) if test "x$profile" = "xyes" ; then PROFLAGS="-pg" PROLDFLAGS="-pg" OPTFLAGS="-O0" DBGFLAGS="-g" AC_MSG_RESULT(yes) elif test "x$profile" = "xprof" ; then PROFLAGS="-p" PROLDFLAGS="-p" OPTFLAGS="-O0" DBGFLAGS="-g" AC_MSG_RESULT(prof) elif test "x$profile" = "xgprof" ; then PROFLAGS="-pg" PROLDFLAGS="-pg" OPTFLAGS="-O0" DBGFLAGS="-g" AC_MSG_RESULT(prof) else DBGFLAGS="" AC_MSG_RESULT(no) fi fi AC_SUBST([PROFLAGS]) AC_DEFINE_UNQUOTED([PROFLAGS], [$PROFLAGS], [Define PROFLAGS=-pg (gprof) or -p (prof) if profile support is wanted.]) dnl Handle the --enable-debug option. First set the DBGFLAGS value, in case dnl the coverage or profile options were processed above. dnl dnl DBGFLAGS="$DBGFLAGS -ggdb -O0 -DDEBUG -D_DEBUG -fno-inline" MORFLAGS="-DDEBUG -D_DEBUG -fno-inline" if test -n "$GCC"; then DBGFLAGS="$COVFLAGS $PROFLAGS" AC_MSG_CHECKING([whether to enable gdb debugging]) AC_ARG_ENABLE(debug, [ --enable-debug=(no/yes/db/gdb) Turn on debug builds (default=no, yes=gdb)], [ case "${enableval}" in yes) debug=yes ;; no) debug=no ;; gdb) debug=gdb ;; db) debug=db ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-debug) ;; esac ], [ debug=no ]) AM_CONDITIONAL(DODEBUG, test x$debug != xno) if test "x$debug" = "xyes" ; then OPTFLAGS="-O0" DBGFLAGS="-g $OPTFLAGS $MORFLAGS" AC_MSG_RESULT(yes) elif test "x$debug" = "xdb" ; then OPTFLAGS="-O0" DBGFLAGS="-g $OPTFLAGS $MORFLAGS" AC_MSG_RESULT(yes) elif test "x$debug" = "xgdb" ; then OPTFLAGS="-O0" DBGFLAGS="-ggdb $OPTFLAGS $MORFLAGS" AC_MSG_RESULT(yes) else if test "x$OPTFLAGS" = "x" ; then OPTFLAGS="-O3" DBGFLAGS="" fi AC_MSG_RESULT(no) fi AC_MSG_CHECKING([whether to enable debug and function instrumentation]) AC_ARG_ENABLE(calls, [ --enable-debug=(no/yes) Turn on call instrumentation (default=no)], [ case "${enableval}" in yes) calls=yes ;; no) calls=no ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-calls) ;; esac ], [ calls=no ]) fi AC_SUBST([DBGFLAGS]) AC_DEFINE_UNQUOTED([DBGFLAGS], [$DBGFLAGS], [Define DBGFLAGS=-g -O0 -DDEBUG -fno-inline if debug support is wanted.]) ]) dnl xpc_debug.m4 dnl dnl vim: ts=3 sw=3 et ft=config ================================================ FILE: m4/xpc_doxygen.m4 ================================================ dnl *************************************************************************** dnl xpc_doxygen.m4 dnl --------------------------------------------------------------------------- dnl dnl \file xpc_doxygen.m4 dnl \library xpc_suite subproject dnl \author Chris Ahlstrom dnl \date 2009-04-03 dnl \updates 2015-10-07 dnl \version $Revision$ dnl \license $XPC_SUITE_GPL_LICENSE$ dnl dnl Tests that the system has Doxygen installed. dnl dnl We could have used AC_CHECK_PROG instead. dnl dnl Note that the autoconf-archive package contains a very complex m4 dnl macro for producing a variety of output documentation formats via dnl Doxygen. The file containing the macro is dnl dnl /usr/share/aclocal/ax_prog_doxygen.m4 dnl dnl We may master that macro someday! dnl dnl --------------------------------------------------------------------------- AC_DEFUN([AC_PROG_DOXYGEN], [ AC_CHECK_TOOL(DOXYGEN, doxygen,) if test "$DOXYGEN" = ""; then echo "WARNING: Doxygen (http://www.stack.nl/~dimitri/doxygen) is not"; echo " installed. You will not be able to create the documentation."; fi; ]) dnl xpc_doxygen.m4 dnl dnl vim: ts=3 sw=3 et ft=config ================================================ FILE: m4/xpc_errorlog.m4 ================================================ dnl *************************************************************************** dnl xpc_errorlog.m4 dnl --------------------------------------------------------------------------- dnl dnl \file xpc_errorlog.m4 dnl \library xpc_suite subproject dnl \author Chris Ahlstrom dnl \date 2008-03-04 dnl \updates 2017-08-31 dnl \version $Revision$ dnl \license $XPC_SUITE_GPL_LICENSE$ dnl dnl Tests whether the user wants error-logging. dnl dnl Set up for inactive errorlogging functions using the switch dnl --disable-errorlog, while the default is an implicit dnl --enable-errorlog. dnl dnl Enabling this feature enables any internationalization lookups and dnl and error-logging support; disabling it speeds up the code (one would dnl assume). dnl dnl \todo dnl It also disables the is_nullptr() family of macros, so that null dnl pointers are not as thoroughly checked for as without this option. dnl dnl It defines the symbol XPC_NO_ERRORLOG, which is added to the CFLAGS dnl for the compiler calls. Also, the "-Wno-extra" flag is set, to nullify dnl the previous occurrence of "-Wextra". We tried to use "-Wno-empty-body", dnl since disabling the error-logging functions causes a lot of these dnl warnings, but that flag caused a fatal error, contrary to this document: dnl dnl http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html dnl dnl --------------------------------------------------------------------------- AC_DEFUN([AC_XPC_ERRORLOG], [ NOERRLOG= if test -n "$GCC"; then AC_MSG_CHECKING(whether to enable error-logging functions) AC_ARG_ENABLE(errorlog, [ --enable-errorlog=(no/yes) Turn on error logging (default=yes)], [ case "${enableval}" in yes) errorlog=yes ;; no) errorlog=no ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-errorlog) ;; esac ], [ errorlog=yes ]) AM_CONDITIONAL(DONOERRLOG, test x$errorlog = xyes) NOERRLOG="" if test "x$errorlog" = "xno" ; then NOERRLOG="-DXPC_NO_ERRORLOG" WARNINGS="$WARNINGS -Wno-unused -Wno-extra" AC_MSG_RESULT(no) else AC_MSG_RESULT(yes) fi fi AC_SUBST([NOERRLOG]) AC_DEFINE_UNQUOTED([NOERRLOG], [$NOERRLOG], [Set NOERRLOG=-DXPC_NO_ERRORLOG if the user wants to disable error-logging.]) ]) dnl xpc_errorlog.m4 dnl dnl vim: ts=3 sw=3 et ft=config ================================================ FILE: m4/xpc_mingw.m4 ================================================ dnl *************************************************************************** dnl xpc_mingw.m4 dnl --------------------------------------------------------------------------- dnl dnl \file xpc_mingw.m4 dnl \library XPC dnl \author Chris Ahlstrom dnl \date 2008-03-04 dnl \update 2017-09-01 dnl \version $Revision$ dnl \license $XPC_SUITE_GPL_LICENSE$ dnl dnl Sets up for using MingW. dnl dnl --------------------------------------------------------------------------- AC_DEFUN([AC_XPC_MINGW], [ AC_MSG_CHECKING([whether to enable MingW32/64 for target system]) AC_ARG_ENABLE(mingw, [ --enable-mingw=(no/w32/w64) Turn on MingW32/64 support (default=no)], [ case "${enableval}" in w32) mingw="yes" ;; w64) mingw="yes" ;; no) mingw="no" ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-mingw) ;; esac ], [ mingw=no ]) CYGWIN= MINGW32= case $host_os in *cygwin*) CYGWIN=1 ; mingw="no" ;; *mingw*) MINGW32=1 ;; esac AR="ar" # why??? mingwflavor="none" if test "x$MINGW" = "xyes" ; then # Fix for the debian distribution of mingw. Note that we can assume # the other executables are found as well. Actually, let's comment this # old stuff out. # # if test -x "/usr/i586-mingw32msvc/bin/ar" ; then # AR="/usr/i586-mingw32msvc/bin/ar" # AS="/usr/i586-mingw32msvc/bin/as" # DLLTOOL="/usr/i586-mingw32msvc/bin/dlltool" # LD="/usr/i586-mingw32msvc/bin/ld" # LDBFD="/usr/i586-mingw32msvc/bin/ld.bfd" # NM="/usr/i586-mingw32msvc/bin/nm" # OBJCOPY="/usr/i586-mingw32msvc/bin/objcopy" # OBJDUMP="/usr/i586-mingw32msvc/bin/objdump" # RANLIB="/usr/i586-mingw32msvc/bin/ranlib" # READELF="/usr/i586-mingw32msvc/bin/readelf" # STRIP="/usr/i586-mingw32msvc/bin/strip" # mingw="yes" # mingflavor="w32" # fi # # Fix for the gentoo distribution of mingw. Gentoo as of 2012 sets # some these up as links to files in /usr/libexec/gcc/i686-pc-mingw32, as # in i686-pc-mingw32-ar -> /usr/libexec/gcc/i686-pc-mingw32/ar. # # In both platforms, other executables may be available, # but we don't set them up here right now: # # gprof gcov gfortran cpp pkg-config fix-root emerge windres # windmc strings size readelf ld.bfd gprof # elfedit c++filt addr2line # # AR="/opt/xmingw/bin/i386-mingw32msvc-ar" if test -x "/usr/i686-w64-mingw32/bin/ar" ; then AR="/usr/i686-w64-mingw32/bin/ar" AS="/usr/i686-w64-mingw32/bin/as" DLLTOOL="/usr/i686-w64-mingw32/bin/dlltool" LD="/usr/i686-w64-mingw32/bin/ld" LDBFD="/usr/i686-w64-mingw32/bin/ld.bfd" NM="/usr/i686-w64-mingw32/bin/nm" OBJCOPY="/usr/i686-w64-mingw32/bin/objcopy" OBJDUMP="/usr/i686-w64-mingw32/bin/objdump" RANLIB="/usr/i686-w64-mingw32/bin/ranlib" READELF="/usr/i686-w64-mingw32/bin/readelf" STRIP="/usr/i686-w64-mingw32/bin/strip" mingflavor="w32" fi if test -x "/usr/x86_64-w64-mingw32/bin/ar" ; then AR="/usr/x86_64-w64-mingw32/bin/ar" AS="/usr/x86_64-w64-mingw32/bin/as" DLLTOOL="/usr/x86_64-w64-mingw32/bin/dlltool" LD="/usr/x86_64-w64-mingw32/bin/ld" LDBFD="/usr/x86_64-w64-mingw32/bin/ld.bfd" NM="/usr/x86_64-w64-mingw32/bin/nm" OBJCOPY="/usr/x86_64-w64-mingw32/bin/objcopy" OBJDUMP="/usr/x86_64-w64-mingw32/bin/objdump" RANLIB="/usr/x86_64-w64-mingw32/bin/ranlib" READELF="/usr/x86_64-w64-mingw32/bin/readelf" STRIP="/usr/x86_64-w64-mingw32/bin/strip" mingflavor="w64" fi dnl What about mingflavor? fi if test "x$mingw" = "xno" ; then AC_MSG_RESULT(no) else if test "x$mingflavor" != "xnone" ; then AC_MSG_RESULT(yes) else AC_MSG_RESULT(no) AC_MSG_ERROR(MingW support not found for --enable-mingw) fi fi AC_SUBST(AR) dnl Checks for system services if test "x${CYGWIN}" = "xyes" ; then AC_DEFINE([CYGWIN], [1], [Define on cygwin]) AC_MSG_RESULT(cygwin) else if test "x${MINGW}" = "xyes" ; then AC_DEFINE([MINGW], [1], [Define on Mingw]) WIN32=1 export WIN32 AC_DEFINE([WIN32], [1], [Define on windows]) LIBS="$LIBS -lws2_32 -lgdi32" AC_MSG_RESULT([mingw]) else LINUX=1 export LINUX AC_DEFINE([LINUX], [1], [Define if not on cygwin or mingw]) AC_MSG_RESULT() fi fi ]) dnl xpc_mingw.m4 dnl dnl vim: ts=3 sw=3 et ft=config ================================================ FILE: m4/xpc_nullptr.m4 ================================================ dnl *************************************************************************** dnl xpc_nullptr.m4 dnl --------------------------------------------------------------------------- dnl dnl \file xpc_nullptr.m4 dnl \library xpc_suite subproject dnl \author Chris Ahlstrom dnl \date 2008-03-04 dnl \updates 2015-10-07 dnl \version $Revision$ dnl \license $XPC_SUITE_GPL_LICENSE$ dnl dnl Tests whether the user wants null-pointer checking, where the dnl is_nullptr() family of macros check the macro argument against dnl "nullptr" (a.k.a. "NULL). dnl dnl Set up for inactive null-pointer checking using the switch dnl --disable-nullptr, while the default is an implicit dnl --enable-nullptr. dnl dnl Disabling it speeds up the code (one would assume). dnl dnl It defines the symbol XPC_NO_NULLPTR, which is added to the CFLAGS dnl for the compiler calls. Also, the "-Wno-xxxxa" flag is set, to nullify dnl warning about .... dnl dnl --------------------------------------------------------------------------- AC_DEFUN([AC_XPC_NULLPTR], [ NONULLPTR= if test -n "$GCC"; then AC_MSG_CHECKING(whether to enable null-pointer checking) AC_ARG_ENABLE(errorlog, [ --enable-nullptr=(no/yes) Turn on error logging (default=yes)], [ case "${enableval}" in yes) nullptr=yes ;; no) nullptr=no ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-nullptr) ;; esac ], [ nullptr=yes ]) AM_CONDITIONAL(DONONULLPTR, test x$nullptr = xyes) NONULLPTR="" if test "x$nullptr" = "xno" ; then NONULLPTR="-DXPC_NO_NULLPTR" WARNINGS="$WARNINGS -Wno-unused -Wno-extra" AC_MSG_RESULT(yes) else AC_MSG_RESULT(no) fi fi AC_SUBST([NONULLPTR]) AC_DEFINE_UNQUOTED([NONULLPTR], [$NONULLPTR], [Set NONULLPTR=-DXPC_NO_NULLPTR if the user wants to disable null-pointer checking.]) ]) dnl xpc_nullptr.m4 dnl dnl vim: ts=3 sw=3 et ft=config ================================================ FILE: m4/xpc_thisptr.m4 ================================================ dnl *************************************************************************** dnl xpc_thisptr.m4 dnl --------------------------------------------------------------------------- dnl dnl \file xpc_thisptr.m4 dnl \library xpc_suite subproject dnl \author Chris Ahlstrom dnl \date 2008-03-04 dnl \updates 2015-10-07 dnl \version $Revision$ dnl \license $XPC_SUITE_GPL_LICENSE$ dnl dnl Tests whether the user wants to disable checking of C "this" pointers. dnl dnl This macro is used to get the arguments supplied dnl to the configure script (./configure --enable-thisptr) dnl dnl It defines the symbol XPC_NO_THISPTR, which is added to the CFLAGS for dnl the compiler call. dnl dnl --------------------------------------------------------------------------- AC_DEFUN([AC_XPC_THISPTR], [ NOTHISPTR= if test -n "$GCC"; then AC_MSG_CHECKING(whether to enable this-pointer checks) AC_ARG_ENABLE(thisptr, [ --enable-thisptr=(no/yes) Turn on this-pointer checks (default=yes)], [ case "${enableval}" in yes) thisptr=yes ;; no) thisptr=no ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-thisptr) ;; esac ], [ thisptr=yes ]) AM_CONDITIONAL(DONOTHISPTR, test x$thisptr = xyes) NOTHISPTR="" if test "x$thisptr" = "xno" ; then NOTHISPTR="-DXPC_NO_THISPTR" AC_MSG_RESULT(yes) else AC_MSG_RESULT(no) fi fi AC_SUBST([NOTHISPTR]) AC_DEFINE_UNQUOTED([NOTHISPTR], [$NOTHISPTR], [Set NOTHISPTR=-DXPC_NO_THISPTR if the user wants to disable this-checking.]) ]) dnl xpc_thisptr.m4 dnl dnl vim: ts=3 sw=3 et ft=config ================================================ FILE: man/Makefile.am ================================================ #******************************************************************************* # Makefile.am (man) #------------------------------------------------------------------------------- ## # \file Makefile.am # \library seq66 # \author Chris Ahlstrom # \date 2015-09-10 # \update 2019-05-04 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for seq66's main 'man' # directory. # #------------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile PACKAGE = @PACKAGE@ VERSION = @VERSION@ #******************************************************************************* # EXTRA_DIST #------------------------------------------------------------------------------- EXTRA_DIST = #******************************************************************************* # dist #------------------------------------------------------------------------------- dist_man_MANS = seq66.1 seq66cli.1 sequencer66.1 #****************************************************************************** # Makefile.am (man) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: man/seq66.1 ================================================ .TH Seq66 "December 2023" "Version 0.99.11" "Seq66 Manual Page" .SH NAME Seq66 (qseq66) - Real-time live MIDI loop sequencer refactored from Sequencer64/Seq24 .SH SYNOPSIS .B qseq66 [\fIOPTIONS\fP] [\fIfilename\fP] .BR .BR .B qpseq66 (Windows) [\fIOPTIONS\fP] [\fIfilename\fP] .BR .BR .B seq66cli [\fIOPTIONS\fP] [\fIfilename\fP] .BR filename is the name of a MIDI file to be loaded at startup. .SH DESCRIPTION .PP \fISequencer66\fP ("Seq66") is a real-time MIDI sequencer. It provides a nice interface for editing and playing MIDI 'loops'. It uses Qt 5 for the user interface. It is functionally very similar to Sequencer64 and Seq24, but more flexible and faster. The two executables are qseq66 and a command-line/daemon called seq66cli. The Windows version uses a variant of PortMidi, and is qpseq66. .SH OPTIONS Seq66 accepts the following options, plus an optional name of a MIDI file. Note that many of the options are "sticky". That is, they will be saved to the "rc" configuration file. "User" options can also be saved, if desired, via the "--user-save" option. .TP 8 .B \-h, \-\-help Display a list of all commandline options. .TP 8 .B \-V, \-\-version Display the program version. .TP 8 .B \-v, \-\-verbose Adds more output to the console, for troubleshooting. This option is not saved to the "rc" configuration file. .TP 8 .B \-H, \-\-home dir Set the directory that holds the configuration files. It is always relative to the user's HOME directory: The default is $HOME/.config/seq66. .TP 8 .B \-X, \-\-playlist filename Load the given play-list from the HOME directory. The conventional name for this file is, for example, "live-sets.playlist". This file provides one or more play-list entries, each providing a list of one or more songs. Once loaded, the user can use the four arrow keys to move between play-lists and the songs in each play-list. The play-list entries are also controllable via MIDI control values set in the "rc" file. See the Seq66 manual for more information. Note that, once set, this option is, by default, saved in the "rc" file. .TP 8 .B \-b b, \-\-bus b Supports modifying the buss number on all tracks when a MIDI file is read. All tracks are loaded with this buss-number override. This feature is useful for testing, making it easy to map the MIDI file onto the system's current hardware/software synthesizer setup. .TP 8 .B \-B b, \-\-buss b The same as the --bus option, just for oldsters to use. .TP 8 .B \-q p, \-\-ppqn p Supports modifying the PPQN value of Seq66, which defaults to a value of 192. This setting should allow MIDI files to play back at the proper speed, and be written with the new PPQN value. The support values for PPQN (p) are 0 and 32 to 1920. The value of 0 is special. It indicates that the Seq66 internal PPQN should be set to the value that is present in the currently-loaded MIDI file. .TP 8 .B \-l, \-\-client-name Changes the default label for the client MIDI ports, which is "seq66". It can be changed to any value, though better if it includes "seq66". The Non Session Manager will override this label with its own label (e.g, "seq66.nXYZX"), however. .TP 8 .B \-n, \-\-nsm Enable .BR NSM support (if included in the build options). This option provides support for qseq66 as a client of the Non Session Manager. This support is basically finished, but please report any issues. Note that .BR LASH is not supported, but JACK session is supported. .TP 8 .B \-T, \-\-no-nsm Disable NSM support (if included in the build options). This option overrides the user-session option 'session', useful to temporarily run in "typical" usage. .TP 8 .B \-U, \-\-jack-session UUID | "on" Enable usage of JACK session management. The user should use "on", which enables JACK session management; the process will provide its own UUID when session management starts. .TP 8 .B \-\-file \fI\fP Load the specified MIDI file on startup. This option does not exist. Instead, specify the file itself as the last command-line argument. .TP 8 .B \-m, \-\-manual-ports Seq66 won't attach (auto-connect) ALSA/JACK MIDI ports. Instead, it will create is own set of 4 virtual input and 8 virtual output busses/ports. To change this number, use the "-o virtual" option. The user can then connect them manually (e.g. in qjackctl for JACK usage). Not supported in the PortMIDI build. .TP 8 .B \-a, \-\-auto-ports Seq66 will attach (auto-connect) to existing ALSA/JACK MIDI ports. Useful for overriding the qseq66.rc configuration file when set up to use manual (virtual) ports. .TP 8 .B \-r, \-\-reveal-ports Do not use the 'user' definition for port names, show the actual port names reported by ALSA/JACK. .TP 8 .B \-R, \-\-hide-ports Use the 'user' definition for port names (useful for overriding a configuration). .TP 8 .B \-A, \-\-alsa Seq66 will use ALSA MIDI ports. This option is useful to override the configuration file, if it was set up for JACK. This is a sticky option. .TP 8 .B \-s, \-\-show-midi Dumps incoming MIDI to screen. .TP 8 .B \-k, \-\-show-keys Prints pressed key value. .TP 8 .B \-K, \-\-inverse Changes the sequence editor and performance/song editor colors to an inverse-color mode. This mode can be considered a "night mode". .TP 8 .B \-p, \-\-priority Runs higher priority with a FIFO scheduler. This option needs root access to succeed. .TP 8 .B \-P, \-\-pass-sysex Passes any incoming SYSEX messages to all outputs. Not yet fully implemented. .TP 8 .B \-d, \-\-record-by-channel Divert MIDI input by cannel into the sequences that are configured for each channel. .TP 8 .B \-D, \-\-legacy-record Record all MIDI into the active sequence. The following options will not be shown by --help if the application is not compiled for JACK support. .TP 8 .B \-j, \-\-jack-transport Seq66 will sync to JACK transport, as "slave". Note that JACK transport is separate from native JACK MIDI support. .TP 8 .B \-g, \-\-no-jack-transport Seq66 will not use JACK transport. This setting is needed if one had earlier enabled JACK transport, but no longer wants it. .TP 8 .B \-J, \-\-jack-master Seq66 will try to be the JACK master. This is a sticky option. .TP 8 .B \-C, \-\-jack-master-cond JACK master will fail if there is already a JACK master. .TP 8 .B \-t, \-\-jack-midi Use native JACK MIDI. This is a separate option from JACK Transport, and is the default. If JACK is not running, the application falls back to ALSA. .TP 8 .B \-N, \-\-no-jack-midi Use ALSA MIDI, even with JACK Transport. See the -A option. .TP 8 .B \-W, \-\-jack-connect Auto-connect to JACK ports discovered on the system. This is the standing default. .TP 8 .B \-w, \-\-no-jack-connect Do not auto-connect to JACK ports. This option is useful to let a session manager take care of the JACK connections. Note that one cannot disable autoconnection if using the ALSA or portmidi engines. .TP 8 .B \-M, \-\-jack-start-mode \fImode\fP In ALSA or JACK, the following play modes are available: "live", "song", or "auto". Auto is now the default. Live mode means that the musician controls the unmuting of patterns on the main window. Song mode means that the song layout in the "Song" window controls the playback. Auto means to use Song mode if the song contains performance triggers. .TP 8 .B \-0, \-\-smf-0 Normally, SMF 0 (single-track MIDI) files are split into separate tracks when read into Seq66. This 'usr' option preserve the file as an SMF 0 file. .TP 8 .B \-u, \-\-user-save Save the "user" configuration file after exiting. Normally, it is saved only if it does not exist, so as not to make certain command-line "user" options (such as --bus) permanent. (Perhaps the same should be true of the "rc" configuration options.) .TP 8 .B \-f, \-\-rc filename Use a different "rc" configuration file. It must be a file in the user's $HOME/.config/seq66 directory or the directory specified by the --home option. The '.rc' extension is added if no extension is present in the filename. .TP 8 .B \-F, \-\-usr filename Use a different "usr" configuration file. It must be a file in the user's $HOME/.config/seq66 directory or the directory specified by the --home option. The '.usr' extension is added if no extension is present in the filename. .TP 8 .B \-c, \-\-config basename Use a different configuration file base name for the 'rc' and 'usr' files. For example, one can specify a full configuration for "testing", for "jack", or for "alsa". .TP 8 .B \-S, \-\-session-tag tag Look into sessions.rc in the "home" configuration directory to load an alternate "session". This file specifies an alternate "home" configuration directory, an alternate 'rc' file-name, an alternate MIDI client ID, and an alternate log file. This option is useful for testing or for changing the setup. It can also be set by exporting the setting SEQ_SESSION_TAG="tagname" with the desired tag-name. .TP 8 .B \-L, \-\-locale localename Set a different locale for running seq66. If the current locale uses the comma as a decimal point, then try --locale en_US.UTF-8 (for example). .TP 8 .B \-o, \-\-option opvalue Provides additional options, including the no-GUI version of Seq66. Here are the opvalues supported: daemonize Fork the command-line application to background. The base configuration files are "seq66cli.rc", etc. This feature currently does not work, needs to fork with the proper settings still. For now, create a keyboard or desktop shortcut for a seq66cli command. no-daemonize Makes the command-line application not fork. log=filename Redirect console output to a log file in the configuration directory. sets=RxC Modifies the rows and columns in a set from the default of 4x8. Supported values of R are 4 to 8, and C can range from 8 to 12. If not 4x8, seq66 is in 'variset' mode. Affects mute groups, too. scale=x Scales the main window size, from 0.5 to 3.0. A value of 0.75 is useful when using "-o wid=2x2 -o sets=8x8", though the pattern labelling is mildly distorted. mutes=value Saving of mute-groups: 'mutes', 'midi', or 'both'. 'mutes' saves to a separate file, 'midi' saves the mutes in the MIDI file. virtual=o,i Set up the --manual-ports option, using 'o' output ports and 'i' input ports. .SH FILES \fB$HOME\fP/.config/qseq66.rc stores the main configuration settings for Seq66. If it does not exist, it will be generated when Seq66 exits. If it does exist, it will be rewritten with the current configuration of Seq66. If running under .BR NSM , the configuration is stored in the Non Session Manager directory created for that session. Many, or most, of the command-line options are "sticky", in that they will be written to the configuration file. This configuration file also specifies other configuration files to be used. \fB$HOME\fP/.config/qseq66.usr stores the MIDI-configuration settings and some of the user-interface settings for Seq66. If it does not exist, it will be generated with a minimal configuration when Seq66 exits. If it does exist, it will not be rewritten with the current configuration of Seq66 except when the user-save option is given, or when particular items are changed in the 'Preferences' dialog. \fB$HOME\fP/.config/qseq66.ctrl contains the keystroke-control and MIDI-control specification for operating Seq66 from the keyboard and via MIDI commands. It also specifies MIDI commands to show the status of commands, patterns, and mute-groups on "launch-pad" devices. \fB$HOME\fP/.config/qseq66.mutes contains the setting for mute-groups, which specify collections of unmuted patterns to be played at the touch of a keystroke or by a MIDI command configured in the 'ctrl' file. \fB$HOME\fP/.config/qseq66.drums contains settings which can be used to modify drum tracks recorded on legacy MIDI equipment to play on modern General MIDI equipment. The conversions can be reversed as well. \fB$HOME\fP/.config/qseq66.playlist contains one or more play-lists. Each play-list is a group of songs. The user can cycle through the play-lists and the songs using the arrow keys or MIDI commands configured in the 'ctrl' file. \fB$HOME\fP/.config/qseq66.palette contains all of the variable colors for tracks, foreground, background, etc. If present, it overrides the default palette colors. \fB$HOME\fP/.config/qseq66.qss is an optional Qt style-sheet. If present, it is loaded and can override most elements of the user-interface. It can be specified in the 'usr' file. Many sample configuration files are provided in the 'data/linux' and the 'data/samples' installed directories. See the Seq66 user manual for details. .SH BUGS Seq66 has them. See .UR https://github.com/ahlstromcj/seq66/issues for the reported bugs. We take pride in hiding a few more :-D. .SH SUGGESTIONS AND BUG REPORTS Any bugs found should be reported to the upstream author and/or package maintainer. See the link in the previous section. .SH HOMEPAGE .UR https://github.com/ahlstromcj/seq66/ .SH OTHER INFO --ppqn works and should be close to bug-free. If a MIDI file is re-saved, the new PPQN is also saved to the MIDI file. Note that some options shown above may have been disabled in the Linux distro's build configuration. The current Seq66 project homepage is a simple git repository at the https://github.com/ahlstromcj/seq66.git URL. Comprehensive instructions are provided as a PDF manual in the same project. The old Seq24 project homepage is at , and the new one is at . It is released under the GNU GPL license. Seq66 is also released under the GNU GPL license. .SH SEE ALSO There are no man-pages yet for the configuration files. However, when Seq66 is first run, these files are saved in $HOME/.config/seq66, and they are fairly self-documenting. Also see the Seq66 PDF user's manual in the 'doc' directory for even more information. It is very comprehensive and is indexed. .SH AUTHOR Seq66 was written by Chris Ahlstrom , with contributions from Tim Deagan , Daniel Appelt , 0rel, layk, and many others. Seq24 was originally written by Rob C. Buse and the Seq24 team at LaunchPad. This manual page was written by Dana Olson with additions from Guido Scholz and Chris Ahlstrom . ================================================ FILE: man/seq66cli.1 ================================================ .TH Seq66cli "November 2023" "Version 0.99.10" "Seq66 Command-Line Manual Page" .SH NAME Seq66 - Real-time live MIDI sequencer, command-line daemon .SH SYNOPSIS .B seq66cli [\fIOPTIONS\fP] [\fIFILENAME\fP] .SH DESCRIPTION .PP \fIseq66cli\fP is the command-line (no GUI) native JACK version of Seq66. Please see "man seq66" for full information on this application. This application is known by the names "Seq66", "Sequencer66", and, depending on the operating environment or build, "qseq66" (Linux) or "qpseq66.exe" (Windows). .SH SEE ALSO See the seq66 man page for command-line options. Use the "--help" option for a comprehensive list. See the Seq66 PDF user's manual in the 'doc' directory for even more information. It is very comprehensive and is indexed. ================================================ FILE: man/sequencer66.1 ================================================ .TH Sequencer66 "November 2023" "Version 0.99.10" "Seq66 Alternate Manual Page" .SH NAME Sequencer66 - Real-time live MIDI sequencer refactored from Seq24, Native JACK .SH SYNOPSIS .B qseq66 [\fIOPTIONS\fP] [\fIFILENAME\fP] .B seq66cli [\fIOPTIONS\fP] [\fIFILENAME\fP] .B qpseq66 [\fIOPTIONS\fP] [\fIFILENAME\fP] on Windows. .SH DESCRIPTION .PP \fISequencer66\fP is the comprehensively updated version of Sequencer64. See "man sequencer66" for full information on this application. .SH SEE ALSO See the Seq66 man page ("man seq66") for command-line options. Use the "--help" option for a comprehensive list. See the Seq66 PDF user's manual in the 'doc' directory for even more information. It is very comprehensive and is indexed. ================================================ FILE: nsis/README ================================================ NSIS Notes Chris Ahlstrom 2021-12-11 to 2025-02-02 This directory contains files that allow Linux and Windows users to create an NSIS installer for Seq66. The latest version adds application shortcuts. Also, if NSIS is installed on Windows, it is used in preference to copying the build products to a Linux partition and using "makensis" there. The Windows version of Seq66 is a 64-application, and the Linux "makensis" program creates a 32-bit installer. One can also install the latest NSIS on Windows, add the path to "makensis.exe" to the PATH variable, and the build_release_package.bat script will detect and use the Windows version of NSIS as of Seq66 version 0.99.5. Here are some notes for creating an installer for a 64-bit version of Seq66. The process involves modifications to the build-release DOS batch file and the NSIS files in this directory. Source: https://www.bojankomazec.com/2011/10/nsis-installer-for-64-bit-windows.html "Here are some tips for creating (32-bit) NSIS installer which installs 64-bit application on 64-bit Windows." Since NSIS is currently a 32-bit program, it would redirect installations by default to "C:\Program Files (x86)" and uses, from the Windows Registry, only "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node". We have updated that to install to "C:\Program Files", since Seq66 can be built only as a 64-bit application on our current development system. NOTES: - Currently we force the creation of a 64-bit Seq66, but still create a 32-bit Seq66 installer. - The following material is for reference only at this time. Read the release batch file for the most accurate information. ======================================================== The "x64" plug-in provides a macro, "RunningX64", which returns true if the installer is running on 64-bit Windows under WoW64 emulation. It can be obtained from: "http://nsis.sourceforge.net/Include/x64.nsh". Once detecting 64-bit host, we need to: - Set "C:\Program Files" as the install directory, using $PROGRAMFILES64 instead of $PROGRAMFILES. - Enable access to the 64-bit Registry with the SetRegView NSIS command. Setup.nsi: !include x64.nsh ; Set the initial value for $INSTDIR. InstallDir "$PROGRAMFILES\${MY_COMPANY}\${MY_APP}" . . . ${If} ${RunningX64} DetailPrint "Installer running on 64-bit host" ; Disable Registry redirection to enable access to th e64-bit ; portion of the Registry. SetRegView 64 ; Change the install directory. StrCpy $INSTDIR "$PROGRAMFILES64\${MY_COMPANY}\${MY_APP}" ${EndIf} I think that is all we need. The steps that follow require building code with Visual Studio, and we do not need it. ======================================================== To detect whether some 64-bit process is running, use the FindProcDLL plug-in (there are couple of versions available but I found only this one - FindProcDLL_mod_by_hnedka.7z - working for me; please have a look at this forum thread). Download this archived file, unpack it and copy FindProcDLL.dll to your ..\NSIS\Plugins directory. ${If} ${RunningX64} FindProcDLL::FindProc "Some64BitProcess.exe" ${If} $R0 == 1 DetailPrint "FindProcDLL::FindProc() returned 1 (process is running)" ${ElseIf} $R0 == 0 DetailPrint "FindProcDLL::FindProc() returned 0 (process is not running)" ${Else} DetailPrint "FindProcDLL::FindProc() returned unexpected value" ${Endif} ${Else} ... "NSIS Unicode FindProc / KillProc plug-in", 'FindProc Unicode-source.zip': https://sourceforge.net/projects/findkillprocuni/files/ FindProcDLL_mod_by_hnedka.7z can be found at: http://forums.winamp.com/attachment.php?attachmentid=48888&d=1307099823 Additional notes: If you need to install either a 32-bit or 64-bit file, you can do this: ${If} ${RunningX64} File "/oname=MyFile.exe" "files\MyFile64.exe" ${Else} File "files\MyFile.exe" ${EndIf} If you need to register a DLL, you can do this: ${If} ${RunningX64} ExecWait 'regsvr32.exe /s "$INSTDIR\MyDLL.dll"' ${Else} RegDLL "$INSTDIR\MyDLL.dll" ${EndIf} (Or use Library.nsh) # vim: sw=4 ts=4 wm=8 et ft=sh ================================================ FILE: nsis/Seq66Constants.nsh ================================================ ;--------------------------------------------------------------------------- ; ; File: Seq66Constants.nsh ; Author: Chris Ahlstrom ; Date: 2018-05-26 ; Updated: 2026-05-01 ; Version: 0.99.24 ; ; Provides constants commonly used by the installer for Seq66 for ; Windows. ; ; Note that "PRODUCT_NAME" determines the name of the directory in ; C:/Program Files, where the application is installed. ; ;--------------------------------------------------------------------------- ;============================================================================ ; Product Registry keys. ;============================================================================ !define COMPANY_NAME "Seq66" !define PRODUCT_NAME "Seq66" !define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\qpseq66.exe" !define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" !define PRODUCT_UNINST_ROOT_KEY "HKLM" !define EXE_NAME "qpseq66.exe" ;============================================================================ ; Informational settings ;============================================================================ !define VER_MAIN_PURPOSE "Seq66 for Windows" !define VER_NUMBER "0.99" !define VER_REVISION "24" !define VER_VARIANT "Windows" !define PRODUCT_VERSION "${VER_NUMBER} ${VER_VARIANT} (rev ${VER_REVISION})" !define PRODUCT_PUBLISHER "C. Ahlstrom (ahlstromcj@gmail.com)" !define PRODUCT_WEB_SITE "https://github.com/ahlstromcj/seq66/" ;============================================================================ ; The type of build to make. Uncomment the one desired. WIN64 preferred. ; Actually WIN32 might not be a tenable option anymore. Working on getting ; it to work (it works inside QtCreator). ;============================================================================ !define WIN64 !define WINBITS "64" ; We currently cannot get a 32-bit build to work outside of Qtcreator. ; ; !define WIN32 ; !define WINBITS "32" ;============================================================================ ; Directory to place the installer. It's in seq66/release. ;============================================================================ !define EXE_DIRECTORY "..\release" ; vim: ts=4 sw=4 wm=3 et ft=nsis ================================================ FILE: nsis/Seq66Setup.nsi ================================================ ;--------------------------------------------------------------------------- ; ; File: Seq66Setup.nsi ; Author: Chris Ahlstrom ; Date: 2018-05-26 ; Updated: 2026-05-01 ; Version: 0.99.24 ; ; Usage of this Windows build script: ; ; - See the build_release_package.bat file for full details. ; - Obtain and install the NSIS 2.46 (or above) installer from ; http://nsis.sourceforge.net/Download, or preferably install it from ; your Linux repository via apt. It can also be installed on ; Windows, and the build script can detect if it is available on ; the PATH. ; - In Windows, Check out the latest branch project from Git. Or ; make a source package using the handy "pack" script on Linux, ; and copy the source package to your Windows system, and unpack it ; there. ; - In the project directory, on the command-line, run the following ; command to build the Release version of Seq66 using qmake and make, ; and to create a 7-Zip "release" package that can be unpacked in ; the root "seq66" directory. ; C:\Projects\seq66\build_release_package.bat ; - The resulting package is something like the file ; "qpseq66-release-package-0.95.1.7z", found in ; ../seq66/seq66-release/Seq66qt5. ; - Then run NSIS: ; - Windows: ; - Click on "Compile NSI scripts". ; - Click File / Load Script. ; - Navigate to the "nsis" directory and select ; "Seq66Setup_V0.95.nsi". The script will take a few minutes ; to build. The output goes to ".... (TBD)" ; - You can run that executable, or you can instead click the ; "Test Installer" button in the NSIS window. ; - When you get to the "Choose Install Location" window, you can ; use "C" and test the installation. ; - Or, as in Linux, the makensis command can be used if ; available. ; - Linux: The program that creates Windows installers on Linux is ; 'makensis'. ; - The actual build is done on Windows. ; - Change to the "seq66/nsis" directory. ; - Run "makensis Seq66Setup.nsi". ; - After creation, The installer package is at ; "seq66/release/seq66_setup_x64-0.99.5.exe" or similar. ; - Select the defaults and let the installer do its thing. ; - To uninstall the application, use Settings / ; Control Panel / Add and Remove Programs. The application is ; Seq66, and the executable is qpseq66.exe. ; ; References: ; ; - http://nsis.sourceforge.net/Download ; - http://www.atomicmpc.com.au/Feature/24263, ; tutorial-create-a-nsis-install-script.aspx/2 ; ;--------------------------------------------------------------------------- ;--------------------------------------------------------------------------- ; MUI.nsh provides GUI features as expected for an installer. ; Sections.nsh provides support for sections and section groups. ; Seq66Constants.nsh contains names and version numbers. ;--------------------------------------------------------------------------- Unicode True !include MUI.nsh !include MUI2.nsh !include Sections.nsh !include Seq66Constants.nsh !define MUI_ICON "..\resources\icons\route66.ico" !define MUI_HEADERIMAGE !define MUI_HEADERIMAGE_BITMAP "..\resources\icons\route66.bmp" !define MUI_HEADERIMAGE_RIGHT ;--------------------------------------------------------------------------- ; We want: ; ; - The description at the bottom. ; - A welcome page. ; - A license page. ; - A components page. ; - A directory page to allow changing the installation location of ; Seq66. ; - An install-files page. ; - A finish page. ; - An uninstaller page. ; - An abort-warning prompt. ; ;--------------------------------------------------------------------------- !define MUI_COMPONENTSPAGE_SMALLDESC !insertmacro MUI_PAGE_WELCOME !define MUI_LICENSEPAGE_CHECKBOX !insertmacro MUI_PAGE_LICENSE "..\data\license.text" !insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES ;--------------------------------------------------------------------------- ; Tentative code to install a desktop icon. Disabls the readme prompt and ; uses it to prompt for installing a desktop icon ; ; Function finishpageaction ; CreateShortcut "$DESKTOP\qpseq66.lnk" "$iNSTDIR\qpseq66.exe" ; FunctionEnd ; ; !define MUI_FINISHPAGE_SHOWREADME "" ; !define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED ; !define MUI_FINISHPAGE_SHOWREADME_TEXT "Create Desktop Shortcut" ; !define MUI_FINISHPAGE_SHOWREADME_FUNCTION finishpageaction ; ;---------------------------- ; ; Uninstall: ; ; Removes pins using the shortcut. ; Deletes the shortcut. ; Refreshes the desktop. ; ; !macro customInstall WinShell::UninstShortcut "$desktopLink" ; ; Delete "$desktopLink" ; System::Call 'shell32::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)' ; ; !macroend ; ;--------------------------------------------------------------------------- !define MUI_FINISHPAGE_SHOWREADME "..\data\readme.text" !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_INSTFILES !define MUI_ABORTWARNING !insertmacro MUI_LANGUAGE "English" ;--------------------------------------------------------------------------- ; Here we set a non-silent install. ; ; SilentInstall silent ; ;--------------------------------------------------------------------------- SilentInstall normal Name "${PRODUCT_NAME} ${PRODUCT_VERSION} ${WINBITS}-bit" BrandingText "${PRODUCT_NAME} ${PRODUCT_VERSION} NSIS-based Installer" OutFile "${EXE_DIRECTORY}\seq66_setup_x${WINBITS}-${VER_NUMBER}.${VER_REVISION}.exe" RequestExecutionLevel admin InstallDirRegKey HKLM "${PRODUCT_DIR_REGKEY}" "" ShowInstDetails show ShowUnInstDetails show InstallDir "$PROGRAMFILES${WINBITS}\${PRODUCT_NAME}" ;--------------------------------------------------------------------------- ; The actual installer sections ;--------------------------------------------------------------------------- Section "Application" SEC_APPLIC SetOutPath "$INSTDIR" SetOverwrite on File "..\release\qpseq66.exe" SectionEnd SectionGroup "Qt5 Support" SEC_QT5 Section "Mingw DLLs" SEC_MINGW SetOutPath "$INSTDIR" SetOverwrite on File "..\release\D3Dcompiler_47.dll" File "..\release\lib*.dll" File "..\release\opengl*.dll" SectionEnd Section "Qt5 Main DLLs" SEC_QTDLLS SetOutPath "$INSTDIR" SetOverwrite on File "..\release\Qt*.dll" SectionEnd Section "Qt5 Icon Engine" SEC_QTICON SetOutPath "$INSTDIR\iconengines" SetOverwrite on File /r "..\release\iconengines\*.*" SectionEnd Section "Qt5 Imaging" SEC_QTIMG SetOutPath "$INSTDIR\imageformats" SetOverwrite on File /r "..\release\imageformats\*.*" SectionEnd Section "Qt5 Platform Support" SEC_QTPLAT SetOutPath "$INSTDIR\platforms" SetOverwrite on File /r "..\release\platforms\*.*" SectionEnd Section "Qt5 Style Engine" SEC_QTSTYLE SetOutPath "$INSTDIR\styles" SetOverwrite on File /r "..\release\styles\*.*" SectionEnd Section "Qt5 Translations" SEC_QTTRANS SetOutPath "$INSTDIR\translations" SetOverwrite on File /r "..\release\translations\*.*" SectionEnd SectionGroupEnd Section "Licensing and Sample Files" SEC_LIC SetOutPath "$INSTDIR\data" SetOverwrite on File /r "..\release\data\*" SectionEnd Section "Documentation" SEC_DOC SetOutPath "$INSTDIR\doc" SetOverwrite on File /r "..\release\data\share\doc\*.pdf" File /r "..\release\data\share\doc\*.ods" File /r "..\release\data\share\doc\tutorial\*.*" File /r "..\release\data\share\doc\info\*.*" SectionEnd ;-------------------------------------------------------------------------- ; Section "Registry Entries" ; ; Seq66 is completely configured via qpseq66.rc and qpseq66.usr ; in the user-directory C:/Users/username/AppData/Local/seq66. ; ;-------------------------------------------------------------------------- ; Section "Registry Entries" ; SectionEnd ;-------------------------------------------------------------------------- ; Post ;-------------------------------------------------------------------------- ; ; In this section, uninstallation Registry keys are added. ; ; We are not sure if they are needed. ; ; https://nsis.sourceforge.io/ ; A_simple_installer_with_start_menu_shortcut_and_uninstaller ; ;-------------------------------------------------------------------------- Section -Post SetRegView ${WINBITS} ; EXE_NAME is just a constant that is defined in the script, you can just use ; ; CreateShortcut "$smprograms\my app\my shortcut.lnk" ; "c:\path\to\application.exe" "" "c:\path\to\application.exe" 0 ; ; It will create a shortcut to the application executable. CreateDirectory '$SMPROGRAMS\${COMPANY_NAME}\${PRODUCT_NAME}' CreateShortCut '$SMPROGRAMS\${COMPANY_NAME}\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk' \ '$INSTDIR\${EXE_NAME}' "" '$INSTDIR\${EXE_NAME}' 0 CreateShortCut '$SMPROGRAMS\${COMPANY_NAME}\${PRODUCT_NAME}\Uninstall ${PRODUCT_NAME}.lnk' \ '$INSTDIR\uninst.exe' "" '$INSTDIR\uninst.exe' 0 WriteUninstaller "$INSTDIR\uninst.exe" WriteRegStr HKLM "${PRODUCT_DIR_REGKEY}" "" "$INSTDIR\qpseq66.exe" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\qpseq66.exe" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}" WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}" SectionEnd ;-------------------------------------------------------------------------- ; Section Descriptions ;-------------------------------------------------------------------------- !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_DESCRIPTION_TEXT ${SEC_APPLIC} "Application ${WINBITS}-bit executable." !insertmacro MUI_DESCRIPTION_TEXT ${SEC_QT5} "Qt 5 DLLs." !insertmacro MUI_DESCRIPTION_TEXT ${SEC_MINGW} "MingW DLLs." !insertmacro MUI_DESCRIPTION_TEXT ${SEC_QTDLLS} "Qt 5 DLLs." !insertmacro MUI_DESCRIPTION_TEXT ${SEC_QTICON} "Qt 5 icon-engine DLLs." !insertmacro MUI_DESCRIPTION_TEXT ${SEC_QTIMG} "Qt 5 image-format DLLs." !insertmacro MUI_DESCRIPTION_TEXT ${SEC_QTPLAT} "Qt 5 platform DLLs." !insertmacro MUI_DESCRIPTION_TEXT ${SEC_QTSTYLE} "Qt 5 style DLLs." !insertmacro MUI_DESCRIPTION_TEXT ${SEC_QTTRANS} "Qt 5 translation files." !insertmacro MUI_DESCRIPTION_TEXT ${SEC_LIC} "Licenses and sample files." !insertmacro MUI_FUNCTION_DESCRIPTION_END ;-------------------------------------------------------------------------- ; Uninstallation operations ;-------------------------------------------------------------------------- Function un.onUninstSuccess HideWindow MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) was successfully removed from your computer." FunctionEnd Function un.onInit MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Are you sure you want to completely remove $(^Name) and all of its components?" IDYES +2 Abort FunctionEnd ; The Uninstall section is layed out as much as possible to match the ; sections listed above. ; ; Set up to call a few batch files Section Uninstall ExpandEnvStrings $0 %COMSPEC% Delete "$INSTDIR\uninst.exe" RMDir /r "$INSTDIR" DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" DeleteRegKey HKLM "${PRODUCT_DIR_REGKEY}" SectionEnd ; vim: ts=4 sw=4 wm=3 et ft=nsis ================================================ FILE: nsis/build_release_package.bat ================================================ @echo off :: ************************************************************************** :: Seq66 Windows Build-Release Package :: -------------------------------------------------------------------------- :: :: \file build_release_package.bat :: \library Seq66 for Windows :: \author Chris Ahlstrom :: \date 2018-05-26 :: \update 2026-05-01 :: \license $XPC_SUITE_GPL_LICENSE$ :: :: This script sets up and creates a release build of Seq66 for :: Windows and creates a 7-Zip package that can be unpacked in the root :: of the project, ready to be made into an NSIS installer either in Linux :: or in Windows. :: :: If NSIS is installed and the PATH leads to the makensis.exe program, :: then a Seq66 NSIS installer will be created. Otherwise, that is a :: separate manual step described below in steps 7 to 9. :: :: Also see: :: :: https://stackoverflow.com/questions/18553125/ :: how-qtcreator-is-able-to-avoid-the-console-window-when-building-a-windows-applic :: :: Requirements: :: :: 0. Decide whether to a 32-bit or a 64-bit build. Modify the :: environment variables and steps below to accommodate your choice. :: We might support only 64-bit builds at some point. And in fact, :: 32-bit and old OS's like Windows XP may not be supported. :: :: https://forum.qt.io/topic/73292/ :: the-last-qt-version-that-supported-windows-xp/5 :: :: Warning: Currently a 32-bit build is not possible on our current :: setup. :: :: 1. Runs in Windows only. :: 2. Requires QtCreator to be installed, and configured to provide :: the 32/64-bit Mingw tools, including mingw32-make.exe (there is :: no mingw64-make.exe, but the mingw810_32 and mingw810_64 :: directories both have a binary by the name), and qmake.exe. :: The PATH must include the path to both executables. See "Path :: Additions" below. We have not tried the Microsoft C++ compiler :: yet. Any takers? :: 3. Requires 7-Zip to be installed and accessible from the DOS :: command-line, as 7z.exe. :: :: Path Additions: :: :: QTVERSION=5.15.2 :: :: 1. C:\Qt\Qt5.15.2\mingw81_32\bin (or 64-bit) :: 2. C:\Qt\Tools\mingw810_32\bin (ditto) :: 3. C:\Program Files (x86)\NSIS\Bin (if NSIS installed on Win) :: 4. C:\Program Files\Git\mingw64\bin (GNU commands like "xz") :: 5. C:\Program Files\Git\usr\bin (has only git and shells) :: :: Depending on the versions some things will be different. :: :: a. For earlier versions of Qt, might need to remove "Qt" from the :: "Qt5.12.9" subdirectories. :: b. Might also need to change the "81" version number in "mingw81_32" :: in the paths. :: c. Can also change to 64-bit: "mingw81_64". In this case warnings or :: errors might be exposed in the Windows PortMidi C files, though :: we check both builds. :: :: Note: the default is now PROJECT_BITS = 64. Support for 32-bits :: in Qt does not seem to be viable anymore without a laborious :: rebuild of Qt 5.12+ from source code. :: :: Build Instructions: :: :: Note that steps 6 through 10 can be performed on Linux with the :: "packages" script. On my Windows build machine, the source is placed :: in C:\Users\chris\Documents\Home\seq66. :: :: 1. Before running this script, modify the environment variables below :: in this batch file for your specific setup, including :: PROJECT_VERSION, PROJECT_DRIVE, PROJECT_BITS, PROJECT_BASE, :: and NSIS_PLATFORM (either "Linux" or "Windows"). :: 2. Also edit seq66/nsis/Seq66Constants.nsh to specify the current :: date and Seq66 version number. The macros to modify are: :: VER_NUMBER (e.g. "0.90") and VER_REVISION (e.g. "1", as in "0.90.1"). :: 3. Run "./bootstrap --full-clean" to get rid of generated files. :: Then run the "pack" script to create an xz tarball, such as :: "seq66-master-2021-04-17-pack.tar.xz. :: 4. Copy this file to the desired directory on Windows, and use :: a program like 7zip to extract it. It takes two passes of :: "7-Zip / Extract Here", one to extract the "xz" file, and one to :: extract the "tar" file. :: 5. a. In Windows Explorer, make sure there is no existing Qt Creator :: build or shadow directory/configuration, especially a Debug :: configuration. :: b. In Windows Explorer, just double-click on this batch file in its :: location in the "nsis" directory, and watch the build run in a :: DOS window. :: c. Alternatively, create a "shadow" directory at the same level :: as "seq66", change to it in a DOS console, and run :: "..\seq66\nsis\build_release_package.bat". :: d. Or cd to the seq66\nsis directory and try :: "build_release_package.bat > build.log 2>&1" :: This redirection of output is optional. A make.log is :: created anyway. :: 6. The result is a file such as "qpseq66-release-package-x64-0.90.1.7z". :: It is found in seq66/../seq66-release-32/Seq66qt5. Also, a :: log file is made in seq66/make.log, :: which can be checked for build warnings and errors. If you cannot :: find these files, search for 'seq66-release-32'. :: 7. In Linux, one can copy this 7z file to the root 'seq66' directory. :: However, if NSIS is installed in Windows, the NSIS executable :: package is built by the script in seq66/release. Otherwise, :: If the directory 'seq66/release' exists, remove the 'release' :: directory as shown in the next step. :: 8. Use 7zip to extract this file; it will unpack the contents into :: the directory called 'release' ('seq66/release'), which contains :: qpseq66.exe, DLL files, data, etc. Then move the 7z file out of :: the way, for example to the directory about the seq66 directory. :: Here are the commands ("$" is the command-line prompt character): :: :: seq66 $ rm -rf release/ # careful! :: seq66 $ 7z x qpseq66-release-package-x64-0.90.2.7z :: seq66 $ mv qpseq66-release-package-x64-0.90.2.7z .. :: :: 9. Change to the seq66/nsis directory and run: :: :: seq66/nsis $ makensis Seq66Setup.nsi :: :: However, if NSIS is installed in Windows, the NSIS executable :: package is built by the script and resides in seq66/release. :: 10. The installer is seq66/release/seq66_setup_0.90.1.exe, and it is :: in the 'release' directory. Move it out of this directory to a :: safe place for transport. For example, assuming the current :: directory is 'release'. One of these can be run: :: :: seq66/release $ mv seq66_setup_0.90.1.exe \ :: ../../sequencer64-packages/seq66/0.90 :: seq66/release $ mv seq66_setup_0.90.1.exe\ :: ../../seq66/packages/... TO DO !!! :: :: Note: setup files are now part of the GitHub releases of Seq66. :: 11. Make a portable Zip package: :: :: $ mv release/ qpseq66 :: $ zip -u -r qpseq66-portable-0.90.1-0.zip qpseq66/ :: $ mv qpseq66-portable-0.90.1-0.zip ../seq66-packages/latest :: :: 13. Change to the 'seq66' directory to make a standard Linux :: source/configure tarball for a version built using bootstrap :: (to generate the "configure" script): :: :: $ ./pack --release rtmidi 0.90.1 :: $ mv ../seq66-master-rtmidi-0.90.1.tar.xz \ :: ../seq66-packages/latest :: :: where "rtmidi" can be replaced with whatever the current build :: is, such as "cli" or "portmidi" or "qt". :: :: This batch file completely removes the old Windows seq66-release-64 or :: 32 directory and re-does everything. :: :: See the set of variable immediately below. :: :: PROJECT_BASE is the directory that is the immediate parent of the seq66 :: directory. Adjust this value for your setup. :: :: Mingw: :: :: set PROJECT_BASE=\home\chris\Home\git :: :: NSIS_PLATFORM defaults to "Windows", but the presence of the makensis :: program on Windows is tested; if missing, then the value is "Linux". :: ::--------------------------------------------------------------------------- set PROJECT_VERSION=0.99.24 set PROJECT_DATE=2026-05-01 set PROJECT_DRIVE=C: :: Set the bits of the project, either 64 or 32. Also define WIN64 versus :: WIN32 and set WINBItS to "32" in Seq66Constants.nsh. :: :: set PROJECT_BITS=32 // probably no longer supportable by Qt :: set PROJECT_BITS=64 set PROJECT_BITS=64 set QTVERSION=5.15.2 set MINGVERSION=mingw81 set TOOLVERSION=mingw810 set QTPATH=C:\Qt\%QTVERSION% set QMAKE=%QTPATH%\%MINGVERSION%_%PROJECT_BITS%\bin\qmake.exe set QMAKEOPTS=-makefile -recursive :: Old: :: set MAKEPATH=C:\Qt\Qt%QTVERSION%\Tools\%TOOLVERSION%_%PROJECT_BITS%\bin set MAKEPATH=C:\Qt\Tools\%TOOLVERSION%_%PROJECT_BITS%\bin set MINGMAKE=%MAKEPATH%\mingw32-make :: This is where the seq66 and the shadow build directories reside. Adjust :: them for your setup. set PROJECT_BASE=\Users\%USERNAME%\Documents\git set NSIS_PLATFORM=Windows :: PROJECT_REL_ROOT is relative to the Qt shadow build directory created :: in PROJECT_BASE. set PROJECT_NAME=seq66 set PROJECT_TREE=%PROJECT_DRIVE%%PROJECT_BASE%\%PROJECT_NAME% set PROJECT_REL_ROOT=..\seq66 set PROJECT_PRO=seq66.pro set PROJECT_7ZIP=qpseq66-release-package-x%PROJECT_BITS%-%PROJECT_VERSION%.7z set SHADOW_DIR=..\seq66-release-%PROJECT_BITS% set APP_DIR=Seq66qt5 set LOG=..\seq66\make.log set RELEASE_DIR=%APP_DIR%\release set AUX_DIR=data set DOC_DIR=data\share\doc set TUTORIAL_DIR=data\share\doc\tutorial set INFO_DIR=data\share\doc\info :: The quotes are required here. Do we want to add "qtquickcompiler"? set CONFIG_SET="CONFIG += release WIN%PROJECT_BITS%" :: Set the current drive (normally C:). Then change to the user's git :: directory + seq66. We now put the make.log file there instead of the :: shadow directory. %PROJECT_DRIVE% cd %PROJECT_BASE%\%PROJECT_NAME% echo Starting the build for this project directory: > make.log 2>&1 cd >> make.log 2>&1 del /S /Q %SHADOW_DIR%\*.* > NUL echo Recreating Qt shadow directory %SHADOW_DIR% ... >> make.log 2>&1 rmdir %SHADOW_DIR% mkdir %SHADOW_DIR% :: Make sure the supplementary batch files are in the shadow directory. cd %SHADOW_DIR% set > environment.log cd >> %LOG% 2>&1 :: TODO: add "-spec win32-g++" for 32 bit and ??? for 64-bit echo %QMAKE% %QMAKEOPTS% %CONFIG_SET% %PROJECT_REL_ROOT%\%PROJECT_PRO% >> %LOG% 2>&1 %QMAKE% %QMAKEOPTS% %CONFIG_SET% %PROJECT_REL_ROOT%\%PROJECT_PRO% >> %LOG% 2>&1 echo %MINGMAKE% with output to make.log >> %LOG% 2>&1 %MINGMAKE% >> %LOG% 2>&1 :: if %ERRORLEVEL% NEQ 0 goto builderror if ERRORLEVEL 1 goto builderror echo Compiling and linking succeeded >> %LOG% 2>&1 if %PROJECT_BITS% == 64 goto windep64 echo Running brute-force windeploy ... >> %LOG% 2>&1 call ..\seq66\nsis\winddeploybruteforce %QTVERSION% %MINGVERSION%_32 %RELEASEDIR% goto makerels :: windeployqt Seq66qt5\release :windep64 echo Creating deployment area via windeployqt %RELEASE_DIR% >> %LOG% 2>&1 windeployqt %RELEASE_DIR% :makerels echo Creating data directories in %RELEASE_DIR% >> %LOG% 2>&1 echo mkdir %RELEASE_DIR%\%AUX_DIR% echo mkdir %RELEASE_DIR%\%DOC_DIR% echo mkdir %RELEASE_DIR%\%TUTORIAL_DIR% echo mkdir %RELEASE_DIR%\%INFO_DIR% echo copy %PROJECT_REL_ROOT%\%DOC_DIR%\*.pdf %RELEASE_DIR%\%DOC_DIR% echo copy %PROJECT_REL_ROOT%\%DOC_DIR%\*.ods %RELEASE_DIR%\%DOC_DIR% echo xcopy %PROJECT_REL_ROOT%\%DOC_DIR%\tutorial\*.* %RELEASE_DIR%\%TUTORIAL_DIR% /f /s /e /y /i echo xcopy %PROJECT_REL_ROOT%\%DOC_DIR%\info\*.* %RELEASE_DIR%\%INFO_DIR% /f /s /e /y /i mkdir %RELEASE_DIR%\%AUX_DIR% mkdir %RELEASE_DIR%\%AUX_DIR%\linux mkdir %RELEASE_DIR%\%AUX_DIR%\midi mkdir %RELEASE_DIR%\%AUX_DIR%\pixmaps mkdir %RELEASE_DIR%\%AUX_DIR%\samples mkdir %RELEASE_DIR%\%AUX_DIR%\win mkdir %RELEASE_DIR%\%AUX_DIR%\wrk mkdir %RELEASE_DIR%\%DOC_DIR% mkdir %RELEASE_DIR%\%TUTORIAL_DIR% mkdir %RELEASE_DIR%\%INFO_DIR% copy %PROJECT_REL_ROOT%\%AUX_DIR%\license.* %RELEASE_DIR%\%AUX_DIR% copy %PROJECT_REL_ROOT%\%AUX_DIR%\readme.* %RELEASE_DIR%\%AUX_DIR% copy %PROJECT_REL_ROOT%\%AUX_DIR%\linux\*.* %RELEASE_DIR%\%AUX_DIR%\linux xcopy %PROJECT_REL_ROOT%\%AUX_DIR%\midi\*.* %RELEASE_DIR%\%AUX_DIR%\midi /f /s /e /y /i copy %PROJECT_REL_ROOT%\%AUX_DIR%\pixmaps\*.* %RELEASE_DIR%\%AUX_DIR%\pixmaps copy %PROJECT_REL_ROOT%\%AUX_DIR%\samples\*.* %RELEASE_DIR%\%AUX_DIR%\samples copy %PROJECT_REL_ROOT%\%AUX_DIR%\win\*.* %RELEASE_DIR%\%AUX_DIR%\win copy %PROJECT_REL_ROOT%\%AUX_DIR%\wrk\*.* %RELEASE_DIR%\%AUX_DIR%\wrk copy %PROJECT_REL_ROOT%\%DOC_DIR%\README %RELEASE_DIR%\%DOC_DIR% copy %PROJECT_REL_ROOT%\%DOC_DIR%\*.pdf %RELEASE_DIR%\%DOC_DIR% copy %PROJECT_REL_ROOT%\%DOC_DIR%\*.ods %RELEASE_DIR%\%DOC_DIR% xcopy %PROJECT_REL_ROOT%\%DOC_DIR%\tutorial\*.* %RELEASE_DIR%\%TUTORIAL_DIR% /f /s /e /y /i xcopy %PROJECT_REL_ROOT%\%DOC_DIR%\info\*.* %RELEASE_DIR%\%INFO_DIR% /f /s /e /y /i :: This section takes the generated build and data files and packs them :: up into a 7-zip archive. This archive should be copied to the root :: directory (seq66) and extracted (the contents go into the release :: directory. :: :: Then, in Linux, "cd" to the "nsis" directory and run :: :: makensis Seq66Setup.nsi :: :: pushd Seq66qt5 :: 7z a -r qppseq66-nsis-ready-package-DATE.7z release\* echo The build DLLs and EXE are in %SHADOW_DIR%\%RELEASE_DIR% >> %LOG% 2>&1 pushd %APP_DIR% echo Making 7zip package in %APP_DIR%... >> %LOG% 2>&1 cd echo 7z a -r %PROJECT_7ZIP% release\* >> %LOG% 2>&1 del *.o 7z a -r %PROJECT_7ZIP% release\* popd :: Test for a properly set up NSIS on Windows. echo Testing for Windows NSIS/makensis ... >> %LOG% 2>&1 makensis /version if ERRORLEVEL 0 goto nsisexists echo NSIS "makensis" for Windows not found, skipping it... >> %LOG% 2>&1 set NSIS_PLATFORM=Linux :: Here we are seq66-release-64 (or 32). :nsisexists if NOT %NSIS_PLATFORM% == Windows goto skipnsis echo Copying the 7zip package to the project tree... >> %LOG% 2>&1 del /S /Q %PROJECT_TREE%\release\*.* > NUL copy %APP_DIR%\%PROJECT_7ZIP% %PROJECT_TREE% cd %PROJECT_TREE% echo Unpacking the 7zip package, %PROJECT_7ZIP% ... >> %LOG% 2>&1 7z x %PROJECT_7ZIP% echo Building a Windows installer using NSIS... >> %LOG% 2>&1 cd %PROJECT_TREE% pushd nsis makensis Seq66Setup.nsi echo If makensis succeeded, the installer is located in >> %LOG% 2>&1 echo %PROJECT_TREE%\release, named like "seq66_setup_VERSION.exe". >> %LOG% 2>&1 popd goto done :builderror echo %MINGMAKE% failed, aborting! Check %LOG% for errors. >> %LOG% 2>&1 goto ender :skipnsis echo The NSIS builder is not installed on this Windows computer. >> %LOG% 2>&1 echo In Linux, copy the %PROJECT_7ZIP% file to project root ("seq66") >> %LOG% 2>&1 echo and extract that file. The contents go into the "release" directory. >> %LOG% 2>&1 echo Change to the "nsis" directory and run "makensis Seq66Setup.nsi". >> %LOG% 2>&1 :done echo Build products are in %RELEASE_DIR%. >> %LOG% 2>&1 :ender :: vim: ts=4 sw=4 ft=dosbatch fileformat=dos ================================================ FILE: nsis/x64.nsh ================================================ ;--------------------------------------------------------------------------- ; ; File: x64.nsh ; Author: Chris Ahlstrom ; Date: 2021-12-12 ; Updated: 2023-05-08 ; Version: 0.99.5 ; ; NSIS, presently a 32-bit program, redirects installations to default ; "C:\Program Files (x86)" and uses the Windows Registry key ; "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node". ; ; The "x64" plug-in provides a macro, "RunningX64", that returns true when ; the installer runs on 64-bit Windows under WoW64 emulation. It can be ; obtained from: "http://nsis.sourceforge.net/Include/x64.nsh". ; ; This version has comments added and perhaps some tweaks. It has a few ; simple macros to handle installations on x64 machines. ; ; - RunningX64 checks if the installer is running on a 64-bit OS. ; - IsWow64 checks if the installer is 32-bit, running on a 64-bit OS. ; - IsNative*** checks the OS native CPU architecture. ; - DisableX64FSRedirection disables file system redirection. ; - EnableX64FSRedirection enables file system redirection. ; ; Note that this script requires (we think) NSIS 3.0 and above. ; ; Usages: ; ; ${If} ${RunningX64} ; MessageBox MB_OK "Running on 64-bit Windows" ; ${EndIf} ; ; ${If} ${IsNativeAMD64} ; ; Install AMD64 64-bit driver/library ; ${ElseIf} ${IsNativeARM64} ; ; Install ARM64 64-bit driver/library ; ${ElseIf} ${IsNativeIA32} ; ; Install i386 32-bit driver/library ; ${Else} ; Abort "Unsupported CPU architecture!" ; ${EndIf} ; ; ${If} ${IsNativeAMD64} ; File "amd64\myapp.exe" ; ${ElseIf} ${IsNativeIA32} ; ${OrIf} ${IsWow64} ; File "x86\myapp.exe" ; ${Else} ; Abort "Unsupported CPU architecture!" ; ${EndIf} ; ; SetOutPath $SYSDIR ; ${DisableX64FSRedirection} ; File something.bin # extracts to C:\Windows\System32 ; ${EnableX64FSRedirection} ; File something.bin # extracts to C:\Windows\SysWOW64 ; ;--------------------------------------------------------------------------- !ifndef ___X64__NSH___ !define ___X64__NSH___ !include LogicLib.nsh ; /usr/share/nsis/Include/LogicLib.nsh ;--------------------------------------------------------------------------- ; IsWow64() ; ; 1. [Win10.1511+] 0 if not WOW64 ; 2. [WinXP+] FALSE for a 32-bit application on ARM64! ; ;--------------------------------------------------------------------------- !define IsWow64 `"" IsWow64 ""` !macro _IsWow64 _a _b _t _f !insertmacro _LOGICLIB_TEMP System::Call kernel32::GetCurrentProcess()p.s System::Call kernel32::IsWow64Process2(ps,*i0s,*i) ; Note 1 Push | System::Call kernel32::IsWow64Process(p-1,*i0s) ; Note 2 System::Int64Op Pop $_LOGICLIB_TEMP !insertmacro _!= $_LOGICLIB_TEMP 0 `${_t}` `${_f}` !macroend ;--------------------------------------------------------------------------- ; RunningX64() ;--------------------------------------------------------------------------- !define RunningX64 `"" RunningX64 ""` !macro _RunningX64 _a _b _t _f !if ${NSIS_PTR_SIZE} > 4 !insertmacro LogicLib_JumpToBranch `${_t}` `${_f}` !else !insertmacro _IsWow64 `${_a}` `${_b}` `${_t}` `${_f}` !endif !macroend ;--------------------------------------------------------------------------- ; GetNativeMachineArchitecture() ; ; 1. Always IMAGE_FILE_MACHINE_I386 on Win9x. ; ;--------------------------------------------------------------------------- !define GetNativeMachineArchitecture "!insertmacro GetNativeMachineArchitecture " !macro GetNativeMachineArchitecture outvar !define GetNativeMachineArchitecture_lbl lbl_GNMA_${__COUNTER__} System::Call kernel32::GetCurrentProcess()p.s System::Call kernel32::IsWow64Process2(ps,*i,*i0s) Pop ${outvar} IntCmp ${outvar} 0 "" ${GetNativeMachineArchitecture_lbl}_done ${GetNativeMachineArchitecture_lbl}_done !if "${NSIS_PTR_SIZE}" <= 4 !if "${NSIS_CHAR_SIZE}" <= 1 System::Call 'USER32::CharNextW(w"")p.s' Pop ${outvar} IntPtrCmpU ${outvar} 0 "" ${GetNativeMachineArchitecture_lbl}_oldnt ${GetNativeMachineArchitecture_lbl}_oldnt StrCpy ${outvar} 332 ; Note 1 Goto ${GetNativeMachineArchitecture_lbl}_done ${GetNativeMachineArchitecture_lbl}_oldnt: !endif !endif System::Call '*0x7FFE002E(&i2.s)' Pop ${outvar} ${GetNativeMachineArchitecture_lbl}_done: !undef GetNativeMachineArchitecture_lbl !macroend ;--------------------------------------------------------------------------- ; _IsNativeMachineArchitecture() ;--------------------------------------------------------------------------- !macro _IsNativeMachineArchitecture _ignore _arc _t _f !insertmacro _LOGICLIB_TEMP ${GetNativeMachineArchitecture} $_LOGICLIB_TEMP !insertmacro _= $_LOGICLIB_TEMP ${_arc} `${_t}` `${_f}` !macroend !define IsNativeMachineArchitecture `"" IsNativeMachineArchitecture ` !define IsNativeIA32 '${IsNativeMachineArchitecture} 332' ; Intel x86 !define IsNativeAMD64 '${IsNativeMachineArchitecture} 34404' ; x86-64/x64 !define IsNativeARM64 '${IsNativeMachineArchitecture} 43620' ; ARM 64 ;--------------------------------------------------------------------------- ; DisableX64FSRedirection() ;--------------------------------------------------------------------------- !define DisableX64FSRedirection "!insertmacro DisableX64FSRedirection" !macro DisableX64FSRedirection System::Call kernel32::Wow64EnableWow64FsRedirection(i0) !macroend ;--------------------------------------------------------------------------- ; EnableX64FSRedirection() ;--------------------------------------------------------------------------- !define EnableX64FSRedirection "!insertmacro EnableX64FSRedirection" !macro EnableX64FSRedirection System::Call kernel32::Wow64EnableWow64FsRedirection(i1) !macroend !endif # !___X64__NSH___ ; vim: ts=4 sw=4 wm=3 et ft=nsis ================================================ FILE: pack ================================================ #!/bin/bash # #****************************************************************************** # pack (Seq66) #------------------------------------------------------------------------------ ## # \file pack # \library Seq66 # \author Chris Ahlstrom # \date 2015-09-10 # \update 2020-11-25 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Packs up the current project directory, ignoring some of the derived # junk and the version-control infrastructure. # # Run "./pack --help" for more information. # # Allows some artifacts generated by bootstrap to be included, so that a # conventional "GNU configure" tarfile can be generated. If you do # not want that, do a "./bootstrap --full-clean" first. # # Please note that making a build from the resulting tarball will not include # git version information, which is a good thing for release tarballs, which # just need the date and version number. # #----------------------------------------------------------------------------- CURRENTDATE=$(date +%Y-%m-%d) CURRENTDIR=$(pwd) WORKINGDIR=${CURRENTDIR##/*/} # strip all but last part of path PACKAGENAME="bogus" TAGSTRING="pack" DODRYRUN="no" DOCLEAN="no" DORELEASE="no" DODIFFS="no" BRANCH=`git symbolic-ref -q HEAD 2> /dev/null` if [ $? == 0 ] ; then ISGITBRANCH="yes" GITBRANCH=${BRANCH##*/} else ISGITBRANCH="no" GITBRANCH="" fi if [ "$1" == "--help" ] || [ "$1" == "help" ] ; then cat << E_O_F pack 0.9 2020-11-25 Usage: ./pack [--dry-run] [--clean] [--release package] [--diffs] [tag] 'tag' is information to include in the name of the tarball; it replaces the current date. This script packs the contents of the project directory into a tarball with the name of the directory and other information as part of the filename. This tarball can be suitable for unpacking, then running ./configure, and building the default (currently rtmidi) build of Seq66 (currently the seq66 executable). To create such a tarball, see the instructions below. This script packs the entire current directory ('$WORKINGDIR') into a file named like the following (using no tag as an example): $WORKINGDIR-master-$TAGSTRING-my-code.tar.xz $WORKINGDIR-$TAGSTRING-my-code.tar.xz If the --diffs option is specified, "seqpack" is used in lieu of the current working directory. Obviously, this option will work only in a git repository. It's a convenience for the developers. For source-code releases, one first checks out the desired git branch, then bootstraps the desired package (e.g. 'rtmidi'), then builds the code (optional, but recommended), and then runs a command like the following: ./pack --release rtmidi 0.94.6 The resulting tarball can be distributed to users who just want to do the beautiful "./configure ; make ; make install" mantra. Excluded from the tarball are derived products. Version-control-system directories are ignored. This script detects the presence of a git branch, and incorporates the branch name into the name of the tarball. Finally, it packs up 'configure', Makefile.in files, and other necessary files. Do './bootstrap --full-clean' first, or './pack --clean', if you don't want these files and want to start from scratch and build something difference from the default build. E_O_F else while [ "$1" != "" ] ; do case "$1" in --dry-run) DODRYRUN="yes" ;; --clean) DOCLEAN="yes" ;; --no-clean) DOCLEAN="no" ;; --release) shift DORELEASE="yes" PACKAGENAME="$1" ;; --diffs) DODIFFS="yes" ;; *) TAGSTRING="$1" ;; esac shift done if [ "$ISGITBRANCH" == "yes" ] ; then if [ "$DORELEASE" == "yes" ] ; then TARBALLNAME="$WORKINGDIR-$GITBRANCH-$PACKAGENAME-$TAGSTRING.tar.xz" make clean elif [ "$DODIFFS" == "yes" ] ; then TARBALLNAME="seqpack-$GITBRANCH-$CURRENTDATE-$TAGSTRING.tar.xz" else TARBALLNAME="$WORKINGDIR-$GITBRANCH-$CURRENTDATE-$TAGSTRING.tar.xz" fi else if [ "$DODIFFS" == "yes" ] ; then echo "The --diffs option requires a git repository, aborting!" exit 1 fi if [ "$DORELEASE" == "yes" ] ; then TARBALLNAME="$WORKINGDIR-$PACKAGENAME-$TAGSTRING.tar.xz" else TARBALLNAME="$WORKINGDIR-$CURRENTDATE-$TAGSTRING.tar.xz" fi fi echo "The tar-ball to be generated is '../$TARBALLNAME'" if [ "$DODRYRUN" == "yes" ] ; then if [ "$DOCLEAN" == "yes" ] ; then echo "Seq66 will be cleaned by 'bootstrap --full-clean'." fi if [ "$DODIFFS" == "yes" ] ; then echo "Will do 'tar cJf $TARBALLNAME \$(git diff --name-only)'" else echo "Will do 'tar cJf $TARBALLNAME $WORKINGDIR'" fi exit 1 fi if [ "$DOCLEAN" == "yes" ] ; then echo "Cleaning the project..." ./bootstrap --full-clean fi if [ "$DODIFFS" == "yes" ] ; then tar cJf $TARBALLNAME $(git diff --name-only) else # Others that mostly, but not always, are not needed, and so aren't excluded # at this time: # # Makefile.in # Linux executable files # --exclude="aux-files" # --exclude="Makefile" (currently the ones in the "doc" directory # --exclude="latex" # # Removed the slash after WORKINGDIR, and moved it to the end, which # now allows tar to not include .git in the archive, saving a lot of time # and space. pushd .. if [ -d $WORKINGDIR ] ; then tar cJf $TARBALLNAME \ --exclude-vcs \ --exclude=".git" \ --exclude="Seq66/seq66" \ --exclude="Seq66qt5/qseq66" \ --exclude="Seq66cli/seq66cli" \ --exclude="config.h" \ --exclude="config.status" \ --exclude="libtool" \ --exclude="Debug" \ --exclude="Release" \ --exclude="debug" \ --exclude="release" \ --exclude="autom4te.cache" \ --exclude="core" \ --exclude="moc" \ --exclude="obj" \ --exclude="html" \ --exclude="ipch" \ --exclude="*stamp*" \ --exclude="tests" \ --exclude="out.*" \ --exclude=".deps" \ --exclude=".exes" \ --exclude=".libs" \ --exclude="*.a" \ --exclude="*.BAK" \ --exclude="*.bak" \ --exclude="*.bpi" \ --exclude="*.bz" \ --exclude="*.bz2" \ --exclude="*.deps" \ --exclude="*.gz" \ --exclude="*.o" \ --exclude="*.lo" \ --exclude="*.la" \ --exclude="*.lib" \ --exclude="*.log" \ --exclude="*.obj" \ --exclude="*.pch" \ --exclude="*.pdb" \ --exclude="*.Po" \ --exclude="*.sdf" \ --exclude="*.so" \ --exclude="*.stash" \ --exclude="*.swp" \ --exclude=".*.swp" \ --exclude="*.t" \ --exclude="*.tds" \ --exclude="*.tmp" \ --exclude="*.user" \ --exclude="*.xz" \ --exclude="*.testsettings" \ $WORKINGDIR echo else echo "? Working directory $WORKINGDIR does not exist." echo " Are you running pack from the proper directory?" echo " That is what you must do. See './pack --help'." fi popd fi fi #****************************************************************************** # pack (Seq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: packages ================================================ #!/bin/bash # #****************************************************************************** # packages (Seq66) #------------------------------------------------------------------------------ ## # \file packages # \library Seq66 # \author Chris Ahlstrom # \date 2018-09-29 # \update 2021-04-17 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Packs up the current project directory, makes an NSIS package installer. # Written solely to automate some tedious stuff needed for an official # Seq66 release. # #----------------------------------------------------------------------------- RELEASEMAJOR="0.93" RELEASEMINOR="0" RELEASENUMBER="$RELEASEMAJOR.$RELEASEMINOR" REVNUMBER="0" DODRYRUN="no" DORELEASE="no" if [ "$1" == "--help" ] || [ "$1" == "help" ] ; then cat << E_O_F packages 0.6 2021-04-17 Usage: ./packages [--dry-run] [--release [0.93.0] ] This script packs the contents of the current directory into the following packages: E_O_F else while [ "$1" != "" ] ; do case "$1" in --dry-run) DODRYRUN="yes" ;; --clean) DOCLEAN="yes" ;; --no-clean) DOCLEAN="no" ;; --release) shift DORELEASE="yes" if [ "$1" != "" ] ; then RELEASENUMBER="$1" fi ;; *) ;; esac shift done fi PACKAGENAME="qpseq66-release-package-$RELEASENUMBER.7z" # INSTALLERNAME="seq66_setup_$RELEASENUMBER-$REVNUMBER.exe" # ZIPNAME="qpseq66-portable-$RELEASENUMBER-$REVNUMBER.zip" INSTALLERNAME="seq66_setup_$RELEASENUMBER.exe" ZIPNAME="qpseq66-portable-$RELEASENUMBER.zip" TARNAME="seq66-master-rtmidi-$RELEASENUMBER.tar.xz" if [ "$DODRYRUN" == "yes" ] ; then echo "DRY RUN, release = '$RELEASENUMBER'...." if [ -f configure ] ; then echo " " echo "Making a standard Linux source/configure tarball..." echo "$ ./pack --release rtmidi $RELEASENUMBER" echo "$ mv ../$TARNAME ../seq66-packages/latest" else echo " " echo "WARNING: 'configure' does not exist, cannot make a standard Linux" echo " source tarball! Aborting." exit 1 fi echo "Extracting Windows deployment package..." echo "$ 7z x -y $PACKAGENAME" # echo "$ mv $PACKAGENAME .." echo "$ pushd nsis" echo "Building NSIS installer for release $RELEASENUMBER..." echo "$ makensis Seq66Setup.nsi" echo "$ popd" echo "$ mkdir -p qpseq66" echo "$ cp -r release/* qpseq66" echo "$ rm -rf release" echo "$ cp qpseq66/$INSTALLERNAME ../seq66-packages/latest" echo "Copying qpseq66/$INSTALLERNAME to ../seq66-packages/latest..." echo "Making a portable Zip package..." echo "$ zip -q -u -r $ZIPNAME qpseq66/" echo "Copying qpseq66/$ZIPNAME to ../seq66-packages/latest..." echo "$ cp $ZIPNAME ../seq66-packages/latest" echo "$ ls -lrt ../seq66-packages/latest" else if [ -f configure ] ; then echo " " echo "Making a standard Linux source/configure tarball..." ./pack --release rtmidi $RELEASENUMBER mv ../$TARNAME ../seq66-packages/latest else echo " " echo "WARNING: 'configure' does not exist, cannot make a standard Linux" echo " source tarball! Aborting." exit 1 fi echo "Extracting Windows deployment package..." 7z x -y $PACKAGENAME # mv $PACKAGENAME .. pushd nsis echo "Building NSIS installer for release $RELEASENUMBER..." makensis Seq66Setup.nsi popd mkdir -p qpseq66 cp -r release/* qpseq66 rm -rf release/ echo "Copying qpseq66/$INSTALLERNAME to ../seq66-packages/latest..." cp qpseq66/$INSTALLERNAME ../seq66-packages/latest echo "Making a portable Zip package..." zip -q -u -r $ZIPNAME qpseq66/ echo "Copying qpseq66/$ZIPNAME to ../seq66-packages/latest..." cp $ZIPNAME ../seq66-packages/latest ls -lrt ../seq66-packages/latest fi #****************************************************************************** # packages (Seq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 et ft=sh #------------------------------------------------------------------------------ ================================================ FILE: resources/icons/route66.xpm ================================================ /* XPM */ static const char * route66[] = { /* columns rows colors chars-per-pixel */ "64 64 99 2 ", " c black", ". c #020202", "X c gray2", "o c #060606", "O c #00030B", "+ c gray5", "@ c gray8", "# c #161616", "$ c #1D1D1D", "% c #252525", "& c gray15", "* c #282828", "= c #2A2A2A", "- c gray18", "; c #323232", ": c #353535", "> c gray22", ", c gray24", "< c gray26", "1 c gray27", "2 c #494949", "3 c #4B4B4B", "4 c gray30", "5 c #555555", "6 c #5A5A5A", "7 c gray36", "8 c gray39", "9 c #656565", "0 c gray40", "q c #686868", "w c #6A6A6A", "e c #6C6C6C", "r c gray44", "t c #717171", "y c #767676", "u c #777777", "i c gray49", "p c #808080", "a c #838383", "s c #888888", "d c gray55", "f c #8E8E8E", "g c #909090", "h c gray57", "j c #939393", "k c gray58", "l c #989898", "z c gray60", "x c #9A9A9A", "c c gray61", "v c #A0A0A0", "b c #A9A9A9", "n c #ACACAC", "m c #B1B1B1", "M c #B2B2B2", "N c gray74", "B c #C0C0C0", "V c #C1C1C1", "C c gray77", "Z c #C5C5C5", "A c #C8C8C8", "S c #CACACA", "D c gray80", "F c gray81", "G c gray82", "H c gray83", "J c #D5D5D5", "K c gray84", "L c #D7D7D7", "P c #D8D8D8", "I c gainsboro", "U c #DDDDDD", "Y c gray87", "T c #DFDFDF", "R c #E2E2E2", "E c #E4E4E4", "W c gray90", "Q c #E6E6E6", "! c #E9E9E9", "~ c #EAEAEA", "^ c gray92", "/ c #ECECEC", "( c gray93", ") c #EEEEEE", "_ c #EFEFEF", "` c gray94", "' c #F1F1F1", "] c gray95", "[ c #F4F4F4", "{ c gray97", "} c #F8F8F8", "| c #F9F9F9", " . c gray98", ".. c #FBFBFB", "X. c gray99", "o. c #FDFDFD", "O. c #FEFEFE", "+. c white", "@. c None", /* pixels */ "@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.", "@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.", "@.@.@.@.@.@.@.@.@.@.@.$ @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@. @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@. @.@.@.@.@.@.@.@.@.@.", "@.@.@.@.@.@.@.@.@.@. @.@.@.@.@.@.@.@.@.@.@.@. @.@.@.@.@.@.@.@.@.@.@.@. @.@.@.@.@.@.@.@.@.", "@.@.@.@.@.@.@.@.@. @.@.@.@.@.@ +.+. @.@.@.@.@. +. @.@.@.@.@.@.@.@.", "@.@.@.@.@.@.@.@. +.+.+.+. +.+.+.+.+.+.+.+.. +.+.+.+.+.$ @.@.@.@.@.@.@.", "@.@.@.@.@.@.@. +.+.+.+.+.+.+.+.+. +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. +.+.+.+.+.+.+.+.+.@ @.@.@.@.@.", "@.@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. @.@.@.@.", "@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+. +.+.+. +.+.+. +.+.+.+.+.+.+.+.+.+. @.@.@.", "@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+. O O O O O +.+.+.O O O O O O +.+.+.O O O O O O O +.+.+.+.+.+.+.+.+.+.+. @.@.", "@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+. O O O O O +.+.+.O O O O O O +.+.+.O O O O O O O +.+.+.+.+.+.+.+.+.+.+.+. @.", "@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+. O O +.+.+.+.+.+.+.+.O +.+.+.+.+.+.+.O O +.+.O O +.+.+.+.+.+.+.+.+.+.+.+. @.", "@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+. O O +.+.+.+.+.+.+.+.O +.+.+.+.+.+.+.O O +.+.O O +.+.+.+.+.+.+.+.+.+.+.+. @.@.", "@.@.@. * +.+.+.+.+.+.+.+.+.+.+.+.+. O O O O O O +.+.+.O O O O O +.+.+.+.O O +.+.O O +.+.+.+.+.+.+.+.+.+.+.+. @.@.", "@.@.@.@. ..+.+.+.+.+.+.+.+.+.+.+.+. O O O O O O +.+.+.O O O O O +.+.+.+.O O +.+.O O +.+.+.+.+.+.+.+.+.+.+. @.@.@.", "@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.O O +.+.+.O O +.+.+. .+.+.+.O O +.+.O O +.+.+.+.+.+.+.+.+.+... @.@.@.", "@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.O O +.+.+.O O +.+.+.+.+.+.+.O O +.{ O O +.+.+.+.+.+.+.+.+.+. @.@.@.@.", "@.@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+. O O O O O O +.+.+.O O O O O +.+.+.O O O O O O O +.+.+.+.+.+.+.+.+. @.@.@.@.", "@.@.@.@.@.@.% +.+.+.+.+.+.+.+.+.+. O O O O O O +.+.+.O O O O O O +.+.+.O O O O O O O +.+.+.+.+.+.+. @.@.@.@.@.", "@.@.@.@.@.@.@. +.+.+.+.+.+.+.+.+.+. +.+. . +.+.+. O +.+.+.+.+.+.+. @.@.@.@.@.", "@.@.@.@.@.@.@.o +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. +.+.+.+.+.+.+. @.@.@.@.@.", "@.@.@.@.@.@.@.@. +.+.+. @.@.@.@.@.@.", "@.@.@.@.@.@.@.@. O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O @.@.@.@.@.@.", "@.@.@.@.@.@.@.@. @.@.@.@.@.@.", "@.@.@.@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. @.@.@.@.@.@.", "@.@.@.@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. @.@.@.@.@.@.", "@.@.@.@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. @.@.@.@.@.@.", "@.@.@.@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. @.@.@.@.@.@.", "@.@.@.@.@.@.@. +.+.+.+.+.+.+.+.+.o.G z 0 > ; 7 g A .+.+.+.+.+.+.+.+.+.o.G z 0 > ; 7 g A .+.+.+.+.+.+.+.+. @.@.@.@.@.@.", "@.@.@.@.@.@.@. ] +.+.+.+.+.+.+.+.Q t 6 E +.+.+.+.+.+.+.Q t 6 E +.+.+.+.+.+.+. @.@.@.@.@.@.", "@.@.@.@.@.@. +.+.+.+.+.+.+.+.E , < ) +.+.+.+.+.E , < ) +.+.+.+.+.+.+. @.@.@.@.@.", "@.@.@.@.@.@. +.+.+.+.+.+.+.+.} 5 : V ! R c k +.+.+.+.} 5 : V ! R c k +.+.+.+.+.+.+. @.@.@.@.@.", "@.@.@.@.@. +.+.+.+.+.+.+.+.M + U +.+.+.+.s # 8 { +.+.+.M + U +.+.+.+.s # 8 { +.+.+.+.+.+.+. @.@.@.@.", "@.@.@.@.@. +.+.+.+.+.+.+.+.X.1 x +.+.+.+.+.( I ) o.+.+.+.+.X.1 x +.+.+.+.+.( I ) o.+.+.+.+.+.+.+.+.+. @.@.@.", "@.@.@.@. +.+.+.+.+.+.+.+.+.L K +.+.+.+.+.+.+.+.+.+.+.+.+.L K +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. @.@.@.", "@.@.@.@. +.+.+.+.+.+.+.+.+.M [ +.+.+.+.+.+.+.+.+.+.+.+.+.M [ +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. @.@.", "@.@.@. +.+.+.+.+.+.+.+.+.+.f ; +._ m r , > e b ~ +.+.+.+.+.f ; +._ m r , > e b ~ +.+.+.+.+.+.+.+.+.+.+.+. @.@.", "@.@.@. +.+.+.+.+.+.+.+.+.+.9 5 H : + N +.+.+.+.9 5 H : + N +.+.+.+.+.+.+.+.+.+.+. @.", "@.@.@. +.+.+.+.+.+.+.+.+.+.4 ; & C +.+.+.4 ; & C +.+.+.+.+.+.+.+.+.+.+. @.", "@.@.o +.+.+.+.+.+.+.+.+.+.+.= y L / U d - [ +.+.= y L / U d - [ +.+.+.+.+.+.+.+.+.[ @.", "@.@. +.+.+.+.+.+.+.+.+.+.+.= 6 | +.+.+.o.i B +.+.= 6 | +.+.+.o.i B +.+.+.+.+.+.+.+.+.+. @.", "@. +.+.+.+.+.+.+.+.+.+.+.4 C +.+.+.+.+.P j +.+.4 C +.+.+.+.+.P j +.+.+.+.+.+.+.+.+.+. ", "@. +.+.+.+.+.+.+.+.+.+.+.w Y +.+.+.+.+.' y +.+.w Y +.+.+.+.+.' y +.+.+.+.+.+.+.+.+.+. ", "@. +.+.+.+.+.+.+.+.+.+.+.l Y +.+.+.+.+... e +.+.l Y +.+.+.+.+... e +.+.+.+.+.+.+.+.+.+. ", "@. +.+.+.+.+.+.+.+.+.+.+.N D +.+.+.+.+.` a +.+.N D +.+.+.+.+.` a +.+.+.+.+.+.+.+.+.+. ", "@. +.+.+.+.+.+.+.+.+.+.+.^ g +.+.+.+.+.J n +.+.^ g +.+.+.+.+.J n +.+.+.+.+.+.+.+.+.+. ", "@.@. +.+.+.+.+.+.+.+.+.+.+.+.p E +.+.+.O.i I +.+.+.p E +.+.+.O.i I +.+.+.+.+.+.+.+.+.[ @.", "@.@. +.+.+.+.+.+.+.+.+.+.+.+.W # 2 S ^ T h i +.+.+.+.W # 2 S ^ T h i +.+.+.+.+.+.+.+.+.+.+. @.", "@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.C 3 ) +.+.+.+.+.C 3 ) +.+.+.+.+.+.+.+.+.+. @.", "@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.F 2 u ) +.+.+.+.+.+.+.F 2 u ) +.+.+.+.+.+.+.+.+.+.+. @.@.", "@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.{ Z d 7 - , q v L O.+.+.+.+.+.+.+.+.+.{ Z d 7 - , q v L O.+.+.+.+.+.+.+.+.+.+.+. @.@.", "@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. @.@.@.", "@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. @.@.@.@.", "@.@.@.@.@.@. ] +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.X @.@.@.@.@.", "@.@.@.@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. X @.@.@.@.@.@.", "@.@.@.@.@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. @.@.@.@.@.@.@.", "@.@.@.@.@.@.@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+. @.@.@.@.@.@.@.@.@.@.", "@.@.@.@.@.@.@.@.@.@.@.@.@.@. +.+.+.+.+.+.+.+.+.+.+.+.+. @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.", "@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@. X +.+.+.+.+.+.+.+. @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.", "@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@. +.[ +.+.+. @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.", "@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.. @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.", "@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@. @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.", "@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.", "@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@." }; ================================================ FILE: resources/pixmaps/Makefile.am ================================================ #****************************************************************************** # Makefile.am (seq66) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library seq66 application # \author Chris Ahlstrom # \date 2015-09-11 # \update 2025-10-15 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an packaging makefile for the seq66 # pixmaps. # #------------------------------------------------------------------------------ EXTRA_DIST = \ bus.xpm \ chord3-inv.xpm \ chord3.xpm \ chord.xpm \ collapse.xpm \ copy.xpm \ del.xpm \ down.xpm \ drum.xpm \ expandgrid.xpm \ expand.xpm \ exp_rec_on.xpm \ filter_off.xpm \ filter_on.xpm \ finger.xpm \ follow.xpm \ fruity.xpm \ hide.xpm \ ins.xpm \ jack_black.xpm \ jack.xpm \ key.xpm \ learn2.xpm \ learn.xpm \ length_red.xpm \ length_short_inv.xpm \ length_short.xpm \ length.xpm \ live_mode.xpm \ logo.xpm \ loop.xpm \ menu_empty_inv.xpm \ menu_empty.xpm \ menu_full_inv.xpm \ menu_full.xpm \ menu.xpm \ metro_on.xpm \ metro.xpm \ midi.xpm \ muting.xpm \ note_length_inv.xpm \ note_length.xpm \ n_rec_on.xpm \ panic2.xpm \ panic.xpm \ pause.xpm \ perfedit.xpm \ play2.xpm \ play_on.xpm \ play.xpm \ q_rec_on.xpm \ q_rec.xpm \ qseq66.png \ quantize_inv.xpm \ quantize.xpm \ rec_ex_buss.xpm \ rec_ex_channel.xpm \ rec_ex_normal.xpm \ rec_on.xpm \ rec.xpm \ redo.xpm \ right.xpm \ route66rwb-32x32.xpm \ route66rwb-64x64.xpm \ route66.xpm \ scale.xpm \ seq66_32.xpm \ seq66.xpm \ seq-editor.xpm \ sequences.xpm \ seq.xpm \ show_bars_off.xpm \ show_bars_on.xpm \ show.xpm \ snap.xpm \ song-editor.xpm \ song_mode.xpm \ song_rec_no_snap.xpm \ song_rec_off.xpm \ song_rec_on.xpm \ song_record.xpm \ song_rec.xpm \ song-snap.xpm \ stop.xpm \ thru_on.xpm \ thru.xpm \ tools.xpm \ transport_follow.xpm \ transpose.xpm \ t_rec_on.xpm \ tux.xpm \ up_inv.xpm \ up.xpm \ undo.xpm \ zoom_in.xpm \ zoom_out.xpm \ zoom.xpm #------------------------------------------------------------------------------ # Quick-n-dirty install #------------------------------------------------------------------------------ pixmapdir = $(datadir)/pixmaps pixmap_DATA = route66rwb-32x32.xpm route66rwb-64x64.xpm route66.xpm qseq66.png #***************************************************************************** # Makefile.am (seq66) #----------------------------------------------------------------------------- # vim: ts=3 sw=3 noet ft=automake #----------------------------------------------------------------------------- ================================================ FILE: resources/pixmaps/bus.xpm ================================================ /* XPM */ static const char * bus_xpm[] = { "36 14 4 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #939393", " ", " ..... ..... ..... ", " ..+.+.. ..+.+.. ..+.+.. ", " .+++++. .+++++. .+++++. ", " .@+++@. .@+++@. .@+++@. ", " .+++++. .+++++. .+++++. ", " ..+@+.. ..+@+.. ..+@+.. ", " ..... ..... ..... ", " . . . ", " . . . ", " . . . ", " . . . ..................... ", " ", " "}; ================================================ FILE: resources/pixmaps/chord.xpm ================================================ /* XPM */ static const char * chord_xpm [] = { "17 17 3 1 ", " c #None", ". c #000000", "+ c #9E9E9E", " +....+ ", " +. .+ ", "................ ", " +. .+ ", " +....+ ", " ", " +....+ ", " +. .+ ", "................ ", " +. .+ ", " +....+ ", " ", " +....+ ", " +. .+ ", "................ ", " +. .+ ", " +....+ "}; ================================================ FILE: resources/pixmaps/chord3-inv.xpm ================================================ /* XPM */ static const char * chord3_inv_xpm[] = { "17 15 3 1", " c None", ". c #000000", "+ c #FFFFFF", " .. ", " ... ", " ... . .. ", " .+++.. ", "......+++++......", " .+++.. ", " ... . ", " .+++.. ", "......+++++......", " .+++.. ", " ... . ", " .+++.. ", "......+++++......", " .+++. ", " ... "}; ================================================ FILE: resources/pixmaps/chord3.xpm ================================================ /* XPM */ static const char * chord3_xpm [] = { "17 17 3 1 ", " c #None", ". c #000000", "+ c #9E9E9E", " + ", " ++ ", " ++++++ + + ", " +. .++ + ", "...+........+... ", " +. .++ ", " ++++++ + ", " ++++++ + ", " +. .++ ", "...+........+... ", " +. .++ ", " ++++++ + ", " ++++++ + ", " +. .++ ", "...+........+... ", " +. .+ ", " ++++++ "}; ================================================ FILE: resources/pixmaps/collapse.xpm ================================================ /* XPM */ static const char * collapse_xpm[] = { "27 11 3 1", " c None", ". c #000000", "+ c #FFFFFF", " ", " ....... ....... ", " .+..... . .++++.. ", " .+..... .. .+...+. ", " .+..... ... .+...+. ", " .+..... ....... .++++.. ", " .+..... ... .+.+... ", " .+..... .. .+..+.. ", " .+++++. . .+...+. ", " ....... ....... ", " "}; ================================================ FILE: resources/pixmaps/copy.xpm ================================================ /* XPM */ static const char * copy_xpm[] = { "33 11 4 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #BDBEBD", " ................. ", " . . ", " ....... . ....... . ", " .+..... ... .++++.. ... ", " .+..... ..@.. .+...+. ..@.. ", " .+..... .@@@. .+...+. .@@@. ", " .+..... .@@@. .++++.. .@@@. ", " .+..... .@@@. .+.+... .@@@. ", " .+..... .@@@. .+..+.. .@@@. ", " .+++++. ..... .+...+. ..... ", " ....... ....... "}; ================================================ FILE: resources/pixmaps/del.xpm ================================================ /* XPM */ static const char * del_xpm[] = { "30 10 23 1", " c None", ". c #75FE00", "+ c #FFFF00", "@ c #0AFE00", "# c #1FFE00", "$ c #56FE00", "% c #0EFE00", "& c #D6FE00", "* c #78FE00", "= c #12FE00", "- c #F7FF00", "; c #B8FE00", "> c #4AFE00", ", c #97FE00", "' c #16FE00", ") c #B6FE00", "! c #4CFE00", "~ c #F5FF00", "{ c #D4FE00", "] c #87FE00", "^ c #09FE00", "/ c #22FE00", "( c #5DFE00", ".++++@# .++++++$ .+% ", ".+% &+* .+= .+% ", ".+% -+ .+= .+% ", ".+% ;+> .+= .+% ", ".+% ,+' .++++++ .+% ", ".+% ,+' .+= .+% ", ".+% )+! .+= .+% ", ".+% ~+ .+= .+% ", ".+% {+] .+= .+% ", ".++++^/ .++++++$ .++++++("}; ================================================ FILE: resources/pixmaps/down.xpm ================================================ /* XPM */ static const char * down_xpm[] = { "7 14 2 1", " c None", ". c #000000", " ", " ", " ", " ", " ... ", " ... ", " ... ", ".......", " ..... ", " ... ", " . ", " ", " ", " "}; ================================================ FILE: resources/pixmaps/drum.xpm ================================================ /* XPM */ static const char * drum_xpm[] = { "18 18 5 1", " c None", ". c #000000", "+ c #333333", "@ c #FFFFFF", "# c #4DE8EA", " . ", " ... ", " ... ", " +... ... ", " ...@@.....+ ..", " .@@@@..@@@@..... ", "..@@@@@@@@..... ", "..@@@@@@....@@. ", "#.@@@@@@@@@@@@. ", ".#..@@@@@@@@..# ", "..##........##. ", "...##########.. ", "...#.......#... ", "...#.......#... ", "+..#.......#... ", " +.#.......#..+ ", " .#.......#.. ", " ++++++ "}; ================================================ FILE: resources/pixmaps/exp_rec_on.xpm ================================================ /* XPM */ static const char * exp_rec_on_xpm[] = { "36 14 7 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #9E9E9E", "# c #FFBABA", "$ c #FF5454", "% c #B51E1E", " ...... ", " ...+..+... ", " ..+++@@+++.. . . ", " .++++++++++. .. .. ", "..++++++++++.. .#. .#. ", ".++++++++++++. ......#$. ......#$. ", ".+..++++++..+. .#####$$$. .#####$$$.", ".+@.++++++.@+. .$$$$$$$%. .$$$$$$$%.", ".++++++++++++. ......$%. ......$%. ", "..+..++++..+.. .%. .%. ", " .+@.+..+.@+. .. .. ", " ..+++..+++.. . . ", " ...++++... ", " ...... "}; ================================================ FILE: resources/pixmaps/expand.xpm ================================================ /* XPM */ static const char * expand_xpm[] = { "33 11 4 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #BDBEBD", " ................ ", " . . ", " ....... . ....... . ", " .+..... ... .++++.. . ", " .+..... ..@.. .+...+. ..... ", " .+..... .@@@. .+...+. ... ", " .+..... .@@@. .++++.. . ", " .+..... .@@@. .+.+... ", " .+..... .@@@. .+..+.. ", " .+++++. ..... .+...+. ", " ....... ....... "}; ================================================ FILE: resources/pixmaps/expandgrid.xpm ================================================ /* XPM */ static const char * expandgrid_xpm[] = { "33 11 3 1", " c None", ". c #000000", "+ c #FFFFFF", " ", " ................... +++ ", " .++.++.++.++.++.++. +.++ ", " ................... +++++++..++ ", " .++.++.++.++.++.++. +.........++", " ................... +..........+", " .++.++.++.++.++.++. +.........++", " ................... +++++++..++ ", " .++.++.++.++.++.++. +.++ ", " ................... +++ ", " "}; ================================================ FILE: resources/pixmaps/filter_off.xpm ================================================ /* XPM */ static const char * filter_off_xpm[] = { "12 20 2 1", " c None", ". c #000000", " ", " ... ", " . . ", " . . ", " . . ", " . . ", " . . ", " . . ", " . . ", " . . ", " . . ", " . . ", " ..... . ", " .. . ", " . . ", " . . ", " .. .. ", " ...... ", " ", " "}; ================================================ FILE: resources/pixmaps/filter_on.xpm ================================================ /* XPM */ static const char * filter_on_xpm[] = { "12 20 2 1", " c None", ". c #000000", " ", " ... ", " ... ", " ... ", " ... ", " ... ", " ... ", " ... ", " ... ", " ... ", " ... ", " ... ", " ....... ", " ........ ", " ........ ", " ........ ", " ........ ", " ...... ", " ", " "}; ================================================ FILE: resources/pixmaps/finger.xpm ================================================ /* XPM */ static const char * finger_xpm[] = { "16 18 12 1", " c None", ". c #FFFFFF", "+ c #F6F6F6", "@ c #000000", "# c #F7F7F7", "$ c #F8F8F8", "% c #F1F1F1", "& c #ECECEC", "* c #EDEDED", "= c #F3F3F3", "- c #F0F0F0", "; c #F4F4F4", " .+ ", " .@@+ ", " #@@+ ", " #@@+ ", " +@@+.. ", " +@@+@@$.. ", " +@@+@@.@@... ", " +@@+@@.@@.@@.", " ..%@@+@@.@@.@@+", ".@@&@@@@@.@@.@@+", ".@@*@@@@@@@@@@@+", ".@@.@@@@@@@@@@@+", "*@@@@@@@@@@@@@@+", "=@@@@@@@@@@@@@@+", "=@@@@@@@@@@@@@@+", "-@@@@@@@@@@@@@@;", "..@@@@@@@@@@@@..", " ;;;;;;;;;;;; "}; ================================================ FILE: resources/pixmaps/follow.xpm ================================================ /* XPM */ static const char * follow_xpm[] = { "18 18 5 1", " c None", ". c #000000", "+ c #D7E1E7", "@ c #96B0C0", "# c #586870", " ", " ", " ", " . .... ", " .. .... ", " .+. .. ", " .@+. .. ", " .......@@+. .. ", " .+++++@@@@+. .. ", " .@@@@@@@@@@+... ", " .######@@@#. .. ", " .......@@#. .. ", " .@#. .. ", " .#. .. ", " .. .. ", " . .. ", " .. ", " "}; ================================================ FILE: resources/pixmaps/fruity.xpm ================================================ /* XPM */ static const char * fruity_xpm[] = { "20 24 146 2", " c None", ". c #3B3B3B", "+ c #4E4E4E", "@ c #565656", "# c #626262", "$ c #141414", "% c #1E1E1E", "& c #1A1A1A", "* c #5E5E5E", "= c #4F4F4F", "- c #6F6F6F", "; c #8B8B8B", "> c #979797", ", c #848484", "' c #5B5B5B", ") c #727272", "! c #444444", "~ c #3C3C3C", "{ c #686868", "] c #8F8F8F", "^ c #A9A9A9", "/ c #C3C3C3", "( c #D6D6D6", "_ c #E8E8E8", ": c #E4E4E4", "< c #2B2B2B", "[ c #2E2E2E", "} c #4B4B4B", "| c #545454", "1 c #656565", "2 c #777777", "3 c #888888", "4 c #969696", "5 c #ADADAD", "6 c #B4B4B4", "7 c #D2D2D2", "8 c #7F7F7F", "9 c #717171", "0 c #696969", "a c #878787", "b c #838383", "c c #595959", "d c #797979", "e c #A8A8A8", "f c #C5C5C5", "g c #BDBDBD", "h c #BABABA", "i c #BEBEBE", "j c #B6B6B6", "k c #7C7C7C", "l c #424242", "m c #505050", "n c #3F3F3F", "o c #7D7D7D", "p c #B2B2B2", "q c #C1C1C1", "r c #6B6B6B", "s c #6D6D6D", "t c #949494", "u c #AAAAAA", "v c #C9C9C9", "w c #BCBCBC", "x c #999999", "y c #757575", "z c #515151", "A c #414141", "B c #454545", "C c #4A4A4A", "D c #818181", "E c #585858", "F c #767676", "G c #DADADA", "H c #F7F7F7", "I c #EDEDED", "J c #828282", "K c #222222", "L c #262626", "M c #3E3E3E", "N c #474747", "O c #787878", "P c #404040", "Q c #606060", "R c #E2E2E2", "S c #F6F6F6", "T c #F1F1F1", "U c #E0E0E0", "V c #B3B3B3", "W c #383838", "X c #4C4C4C", "Y c #6A6A6A", "Z c #464646", "` c #8A8A8A", " . c #EEEEEE", ".. c #F0F0F0", "+. c #E5E5E5", "@. c #6C6C6C", "#. c #373737", "$. c #646464", "%. c #535353", "&. c #EBEBEB", "*. c #EAEAEA", "=. c #C8C8C8", "-. c #8E8E8E", ";. c #3A3A3A", ">. c #8C8C8C", ",. c #8D8D8D", "'. c #9D9D9D", "). c #CCCCCC", "!. c #D8D8D8", "~. c #DFDFDF", "{. c #DEDEDE", "]. c #D9D9D9", "^. c #9E9E9E", "/. c #313131", "(. c #616161", "_. c #959595", ":. c #A5A5A5", "<. c #B9B9B9", "[. c #C7C7C7", "}. c #CFCFCF", "|. c #B8B8B8", "1. c #A3A3A3", "2. c #525252", "3. c #898989", "4. c #A7A7A7", "5. c #C0C0C0", "6. c #AEAEAE", "7. c #A0A0A0", "8. c #5D5D5D", "9. c #989898", "0. c #909090", "a. c #9B9B9B", "b. c #ABABAB", "c. c #929292", "d. c #3D3D3D", "e. c #363636", "f. c #939393", "g. c #919191", "h. c #868686", "i. c #272727", "j. c #575757", "k. c #2D2D2D", "l. c #9A9A9A", "m. c #7B7B7B", "n. c #9C9C9C", "o. c #484848", " ", " . + ", " @ # $ % & ", " * = - ; > , ' ", " ) ! ~ { ] ^ / ( _ : ] ", " < [ } * | 1 2 3 4 ^ 5 6 7 8 ", " + 1 9 0 a b c d e f g h i g j k ", " [ l m } n o p q r s t u v w x y z | ", " A B . C . 2 > w D E 1 F G H I v J K ", "L ! ~ n M } N ' O 2 + P Q R S T U V ", " l M W X J Y W l X Z M ` .....+.v @. ", " #.M P $.8 a d E %.' , ( _ &.*.: =.-. ", " ;.$.o a 3 >.,.'.h ).!.~.{.].q ^./.", " (.d ; 3 3 ; _.:.<.[.}.}.=.|.1.. ", " 2.) 3.-.3 a >.4 4.j 5./ i 6.7.M ", " 8.D t ] 3 3.] 9.4.p j 6 :.x M ", " ! Y ,._.] ` >.0.a.:.b.4.a.c.d.", " e.d f.4 c.,.-.c.9.^.a.g.3.~ ", " ! h.x x f.0.0.c.f.0.3.J i.", " j.; a.a.4 c.g.g.>.D @. ", " k.E -.a.l.> f.0.3 m.%. ", " } ` ^.n.4 0.a y Z ", " (.c.7.'.a X ", " W o.o.#. "}; ================================================ FILE: resources/pixmaps/hex_off.xpm ================================================ /* XPM */ static const char * hex_off_xpm[] = { "10 12 2 1", " c None", ". c #000000", ".... ....", ". .. .", " .. . ", " . . ", " . . ", " . . ", " . . ", " . . ", " . . . ", ". .. .", ". . . .", ".... ...."}; ================================================ FILE: resources/pixmaps/hex_on.xpm ================================================ /* XPM */ static const char * hex_on_xpm[] = { "10 12 45 1", " c None", ". c #313131", "+ c #000000", "@ c #262626", "# c #3C3C3C", "$ c #494949", "% c #515151", "& c #191919", "* c #5D5D5D", "= c #595959", "- c #141414", "; c #343434", "> c #404040", ", c #010101", "' c #525252", ") c #3D3D3D", "! c #5C5C5C", "~ c #0D0D0D", "{ c #363636", "] c #626262", "^ c #464646", "/ c #050505", "( c #565656", "_ c #393939", ": c #4E4E4E", "< c #4F4F4F", "[ c #101010", "} c #575757", "| c #5F5F5F", "1 c #303030", "2 c #0F0F0F", "3 c #4A4A4A", "4 c #656565", "5 c #5E5E5E", "6 c #020202", "7 c #3E3E3E", "8 c #4C4C4C", "9 c #505050", "0 c #0A0A0A", "a c #1B1B1B", "b c #111111", "c c #232323", "d c #373737", "e c #030303", "f c #5A5A5A", ".++@ #++$", "%.+&*=-+;#", " >+,')++$ ", " !~+{$+;] ", " ^+//+% ", " (_++:( ", " <-++[} ", " |1+.2+34 ", " 5,6#7++8 ", "90+a|}b+c<", "_++d #++$", ",+ef >++,"}; ================================================ FILE: resources/pixmaps/hide.xpm ================================================ /* XPM */ static const char * hide_xpm[] = { "16 16 2 1", " c None", ". c #000000", " ", " ", " ", " ", " ", " ", " ............ ", " . . ", " . . ", " ............ ", " ", " ", " ", " ", " ", " "}; ================================================ FILE: resources/pixmaps/ins.xpm ================================================ /* XPM */ static const char * ins_xpm[] = { "30 10 49 1", " c None", ". c #DDFF00", "+ c #FFFF00", "@ c #2CFE00", "# c #75FE00", "$ c #4BFE00", "% c #9DFE00", "& c #7FFE00", "* c #DBFE00", "= c #F2FE00", "- c #08FE00", "; c #11FE00", "> c #37FE00", ", c #55FE00", "' c #7BFE00", ") c #0AFE00", "! c #BCFE00", "~ c #1FFE00", "{ c #D6FE00", "] c #35FE00", "^ c #D1FF00", "/ c #86FE00", "( c #97FE00", "_ c #48FE00", ": c #85FE00", "< c #D9FF00", "[ c #0EFE00", "} c #B1FE00", "| c #93FE00", "1 c #FCFF00", "2 c #A3FE00", "3 c #A9FE00", "4 c #E2FE00", "5 c #01FE00", "6 c #A1FE00", "7 c #DEFE00", "8 c #BEFE00", "9 c #F3FF00", "0 c #4AFE00", "a c #F6FF00", "b c #92FE00", "c c #EFFF00", "d c #34FE00", "e c #A0FE00", "f c #39FE00", "g c #E9FF00", "h c #E6FE00", "i c #F3FE00", "j c #14FE00", " .++++@ #++$ %+& *=+-;> ", " ,++ #+++ %+& '+) !+ ", " ,++ #+~{] %+& ^+/ ( ", " ,++ #+_:+ %+& <+[ ", " ,++ #+_ }~ %+& |+++++@ ", " ,++ #+_ 1 2+& 34++++5 ", " ,++ #+_ 6-7+& 8++ ", " ,++ #+_ 9++& 0 a+ ", " ,++ #+_ b++& cd e+f ", " .++++@ #+_ g+& !hi++j "}; ================================================ FILE: resources/pixmaps/jack.xpm ================================================ /* XPM */ static const char * jack_xpm[] = { "39 12 211 2", " c None", ". c #FEAE00", "+ c #FFA400", "@ c #FFAC00", "# c #FDAC00", "$ c #875F00", "% c #1A1000", "& c #180D01", "* c #725100", "= c #FAB000", "- c #FDAA00", "; c #F5AB00", "> c #FFA500", ", c #FFB900", "' c #FFA200", ") c #F5A100", "! c #E09000", "~ c #A06700", "{ c #9D6B00", "] c #A36F00", "^ c #9C6A00", "/ c #0D0B00", "( c #0A0700", "_ c #2A1901", ": c #C58700", "< c #BF8300", "[ c #BB8300", "} c #B57600", "| c #B07300", "1 c #BA7A00", "2 c #EA9B00", "3 c #ECA400", "4 c #A96400", "5 c #C57700", "6 c #E29500", "7 c #F5A200", "8 c #FFA700", "9 c #FFA900", "0 c #F6A200", "a c #E29200", "b c #674400", "c c #6E4900", "d c #6C4800", "e c #3A2602", "f c #010103", "g c #080506", "h c #624000", "i c #7A5300", "j c #794F00", "k c #7C4E00", "l c #815700", "m c #794600", "n c #A16300", "o c #C78400", "p c #C58200", "q c #EEA400", "r c #8A4A01", "s c #702B02", "t c #A15A00", "u c #A56200", "v c #AC6900", "w c #AC6700", "x c #BE7A00", "y c #D78F00", "z c #F4A000", "A c #FAA600", "B c #EE9B00", "C c #DB8D00", "D c #1F1402", "E c #050505", "F c #120A05", "G c #965300", "H c #8B4F00", "I c #9C5E00", "J c #D68900", "K c #FFD100", "L c #512008", "M c #915100", "N c #BA7100", "O c #C17900", "P c #C17C00", "Q c #BB7200", "R c #B06D00", "S c #B57000", "T c #BC7200", "U c #CF8100", "V c #EE9A00", "W c #EB9500", "X c #AC7F00", "Y c #B18500", "Z c #AD8400", "` c #170F01", " . c #070702", ".. c #C59700", "+. c #C99C00", "@. c #C9A100", "#. c #CAA100", "$. c #CA9D00", "%. c #975D01", "&. c #934A00", "*. c #C98900", "=. c #DFA900", "-. c #D28600", ";. c #A26503", ">. c #5C3407", ",. c #865000", "'. c #9D5700", "). c #9F5500", "!. c #A05700", "~. c #9C5600", "{. c #954C00", "]. c #994A00", "^. c #A04C00", "/. c #AF5B00", "(. c #B56000", "_. c #D47D00", ":. c #EF9C00", "<. c #834E00", "[. c #875000", "}. c #8A5900", "|. c #130C01", "1. c #040200", "2. c #140B03", "3. c #935C00", "4. c #955D00", "5. c #976100", "6. c #945E00", "7. c #975F00", "8. c #9C5506", "9. c #BA6B00", "0. c #DA9700", "a. c #DA9500", "b. c #D28200", "c. c #A65F0D", "d. c #924D00", "e. c #C16700", "f. c #CA6B00", "g. c #C46700", "h. c #C16400", "i. c #BA5800", "j. c #A94C00", "k. c #9B4600", "l. c #9F4800", "m. c #A54C00", "n. c #D07B00", "o. c #EF9F01", "p. c #1E0F01", "q. c #000302", "r. c #0E0A04", "s. c #A56500", "t. c #F1CA00", "u. c #FFB000", "v. c #B77B01", "w. c #BF8100", "x. c #A05600", "y. c #B96400", "z. c #BE6D00", "A. c #C17A00", "B. c #C18200", "C. c #AC8A00", "D. c #865600", "E. c #9B5B00", "F. c #B87900", "G. c #201A02", "H. c #161003", "I. c #9E6903", "J. c #4D2D00", "K. c #704700", "L. c #D08B01", "M. c #995D00", "N. c #5B4500", "O. c #4B3500", "P. c #3B2100", "Q. c #2A1200", "R. c #210D00", "S. c #E8A000", "T. c #E5A000", "U. c #EBA000", "V. c #D49100", "W. c #160C01", "X. c #140B00", "Y. c #422901", "Z. c #F4AB00", "`. c #E9A600", " + c #E4A300", ".+ c #B98301", "++ c #6F4502", "@+ c #7D4B03", "#+ c #A16D00", "$+ c #915C00", "%+ c #422700", "&+ c #3A2700", "*+ c #200F00", "=+ c #1C0C00", "-+ c #271400", ";+ c #C08200", ">+ c #CC8700", ",+ c #CF8600", "'+ c #965A00", ")+ c #1A0E01", "!+ c #301A00", "~+ c #845100", "{+ c #A16400", "]+ c #623500", "^+ c #402100", "/+ c #2C1901", "(+ c #221006", "_+ c #1F0F01", ":+ c #180A01", "<+ c #180C00", "[+ c #261500", " ", " . + @ # $ % & * = @ - @ # $ % & * = ; > , ' + > ) ! ", " ~ { ] ^ / ( _ : < [ } ] ^ / ( _ : < | 1 2 3 4 5 6 7 8 9 0 a ", " b c d e f g h i j k l d e f g h i j m n o p q r s t u v w x y z A B C ", " - - - D E F - - - - - - D E F - - - - G H I J K L M N O P Q R S T U V W ", " X Y Z ` .& ..+.@.#.$.Z ` .& ..+.@.%.&.*.=.-.;.>.,.'.).!.~.{.].^./.(._.:. ", " <.[.}.|.1.2.3.4.5.6.7.}.|.1.2.3.4.5.8.9.0.a.b.c.K d.e.f.g.h.i.j.k.l.m.n.o. ", " - - - p.q.r.- - - - - - p.q.r.- - - s.1 - t.u.v.w.x.y.z.A.B.7 7 7 C.D.E. ", " F.F.F.F.G.H.F.F.F.F.F.F.F.G.H.F.F.F.- I.J.K.L.K M.7 7 7 7 N.O.P.Q.R. ", " S.T.U.V.W.X.Y.Z.3 `. +U.V.W.X.Y.Z.3 .+++ 7 @+#+$+%+&+*+=+-+ ", " ;+>+,+,+'+)+!+~+{+]+^+,+,+'+)+!+~+{+/+ (+_+:+<+[+ ", " "}; ================================================ FILE: resources/pixmaps/jack_black.xpm ================================================ /* XPM */ static const char * jack_black_xpm[] = { "39 12 176 2", " c None", ". c #050505", "+ c #000000", "@ c #010101", "# c #040404", "$ c #030303", "% c #020202", "& c #252525", "* c #1A1A1A", "= c #0F0F0F", "- c #0C0C0C", "; c #151515", "> c #AEAEAE", ", c #515151", "' c #424242", ") c #0A0A0A", "! c #1C1C1C", "~ c #060606", "{ c #131313", "] c #494949", "^ c #474747", "/ c #4D4D4D", "( c #414141", "_ c #434343", ": c #4B4B4B", "< c #585858", "[ c #363636", "} c #181818", "| c #0B0B0B", "1 c #101010", "2 c #323232", "3 c #080808", "4 c #222222", "5 c #202020", "6 c #525252", "7 c #121212", "8 c #C4C4C4", "9 c #E2E2E2", "0 c #8E8E8E", "a c #070707", "b c #3B3B3B", "c c #171717", "d c #595959", "e c #E5E5E5", "f c #DEDEDE", "g c #E6E6E6", "h c #D6D6D6", "i c #818181", "j c #3D3D3D", "k c #F1F1F1", "l c #7B7B7B", "m c #717171", "n c #B5B5B5", "o c #BFBFBF", "p c #C2C2C2", "q c #D5D5D5", "r c #DADADA", "s c #CECECE", "t c #CFCFCF", "u c #CDCDCD", "v c #2F2F2F", "w c #636363", "x c #E0E0E0", "y c #B8B8B8", "z c #5F5F5F", "A c #161616", "B c #545454", "C c #FDFDFD", "D c #E7E7E7", "E c #D9D9D9", "F c #E1E1E1", "G c #DDDDDD", "H c #FCFCFC", "I c #878787", "J c #686868", "K c #FEFEFE", "L c #8A8A8A", "M c #C0C0C0", "N c #EAEAEA", "O c #E9E9E9", "P c #ECECEC", "Q c #EBEBEB", "R c #E8E8E8", "S c #DCDCDC", "T c #E4E4E4", "U c #F2F2F2", "V c #333333", "W c #141414", "X c #FFFFFF", "Y c #ABABAB", "Z c #979797", "` c #4DE8EA", " . c #969696", ".. c #2D2D2D", "+. c #8C8C8C", "@. c #303030", "#. c #292929", "$. c #2E2E2E", "%. c #EDEDED", "&. c #A4A4A4", "*. c #A2A2A2", "=. c #464646", "-. c #808080", ";. c #0E0E0E", ">. c #9B9B9B", ",. c #4E4E4E", "'. c #C8C8C8", "). c #868686", "!. c #727272", "~. c #2B2B2B", "{. c #C9C9C9", "]. c #B7B7B7", "^. c #C1C1C1", "/. c #BDBDBD", "(. c #C5C5C5", "_. c #BEBEBE", ":. c #CACACA", "<. c #6E6E6E", "[. c #797979", "}. c #1B1B1B", "|. c #3F3F3F", "1. c #D8D8D8", "2. c #676767", "3. c #949494", "4. c #BCBCBC", "5. c #B9B9B9", "6. c #C7C7C7", "7. c #1F1F1F", "8. c #232323", "9. c #707070", "0. c #090909", "a. c #5B5B5B", "b. c #A7A7A7", "c. c #A0A0A0", "d. c #959595", "e. c #939393", "f. c #9A9A9A", "g. c #999999", "h. c #5A5A5A", "i. c #555555", "j. c #3A3A3A", "k. c #AFAFAF", "l. c #7F7F7F", "m. c #989898", "n. c #9F9F9F", "o. c #A1A1A1", "p. c #111111", "q. c #272727", "r. c #848484", "s. c #757575", "t. c #767676", "u. c #787878", "v. c #484848", "w. c #242424", "x. c #909090", "y. c #747474", "z. c #737373", "A. c #6F6F6F", "B. c #646464", "C. c #6C6C6C", "D. c #7A7A7A", "E. c #454545", "F. c #212121", "G. c #7E7E7E", "H. c #7C7C7C", "I. c #6A6A6A", "J. c #383838", "K. c #404040", "L. c #777777", "M. c #6D6D6D", "N. c #3E3E3E", "O. c #191919", "P. c #535353", "Q. c #626262", "R. c #606060", "S. c #0D0D0D", ". + + + . @ . + + # $ $ + + + + # + . % @ + & * = - + # @ + + ; > ", ", ' ) ! ~ { ] ^ / ( _ ] : < [ % } | 1 < 2 + + + + ~ 3 # ! 4 5 [ : 6 7 % [ 8 ", "9 0 a b c d e 9 f g 9 f h 9 i ~ j ) d k l 3 m > n o 8 p q r s t q u v + + w x ", "k y % z A B C D E 9 x F G H I + J | 6 K L + M N O P Q R S g 9 T Q U V W [ + 0 ", "X Y 3 Z b d ` ` ` ` ` ` ` ` 0 # ...6 X +.+ ` ` ` ` ` ` ` ` ` ` ` ` @.#. + $.", "%.&.c *.=.B ` ` ` ` ` ` ` ` -.;.>.5 B X m + ` ` ` ` ` ` ` ` ` ` ` ` 4 4 P ,.$ ", "'.).) !.~.=.{.o ].^./.(._.:.<.;.[.}.|.1.2.$ 3.o _.4.4.4.o 5._.o ^.6.7.8.e < % ", "&.9.0.a.7.[ b.c.d.>.e.f.g.&.h.a i.1 j.k.w + l.g.g.m.g.f.f.m.n.d.e.o.} 4 /.p.$ ", "l i.+ v * q.r.m s.t.u.s.!.r.v.+ v W w.x.' + a.y.y.z.9.<.z.u.!.m 9.A.{ q._ + B.", "[.d . ! ) 8.l <.<.C.<.!.9.D.E.+ & @ F.G.j.% z z.t.[.l H.I.[.l t.[.i } + + J.n ", "s.K.) A p.2 L.z.s.[.s.L.M.J N.a O.@ V I B + + + + ~ 3 # P.h.Q.R.z <.}.+ S.Z ", "% 1 + # + + a + + + + @ $ 0.a + . . + + + % + + + + + . + - + | u. "}; ================================================ FILE: resources/pixmaps/key.xpm ================================================ /* XPM */ static const char * key_xpm[] = { "13 14 3 1", " c None", ". c #000000", "+ c #F2D91A", " ..... ", " .+++. ", " .+... ", " .+++. ", " .+... ", " .+. ", " .+. ", " ...+... ", " .+++++. ", " .+...+. ", " .+. .+. ", " .+...+. ", " .+++++. ", " ....... "}; ================================================ FILE: resources/pixmaps/learn.xpm ================================================ /* XPM */ static const char * learn_xpm[] = { "21 10 2 1", " c None", ". c #000000", " ", " ", " ... ", " ... ", " ... ", " .............. ", " .............. ", " .............. ", " ", " "}; ================================================ FILE: resources/pixmaps/learn2.xpm ================================================ /* XPM */ static const char * learn2_xpm[] = { "21 10 2 1", " c None", ". c #000000", " ", " ", "..... ..............", "..... ..............", "..... ..............", "..... ..............", "..... ....", "..... ....", " ", " "}; ================================================ FILE: resources/pixmaps/length.xpm ================================================ /* XPM */ static const char * length_xpm[] = { "36 11 2 1", " c None", ". c #000000", " ................................ ", " ................................ ", " ", " ................ ", " ................ ", " ", " ........ ", " ........ ", " ", " .... ", " .... "}; ================================================ FILE: resources/pixmaps/length_red.xpm ================================================ /* XPM */ static const char * length_red_xpm[] = { "19 11 2 1", " c None", ". c #FF0000", "...................", "...................", " ", "................ ", "................ ", " ", "........ ", "........ ", " ", ".... ", ".... "}; ================================================ FILE: resources/pixmaps/length_short.xpm ================================================ /* XPM */ static const char * length_short_xpm[] = { "19 11 2 1", " c None", ". c #000000", "...................", "...................", " ", "................ ", "................ ", " ", "........ ", "........ ", " ", ".... ", ".... "}; ================================================ FILE: resources/pixmaps/length_short_inv.xpm ================================================ /* XPM */ static const char * length_short_inv_xpm[] = { "19 11 2 1", " c None", ". c #FFFFFF", "...................", "...................", " ", "................ ", "................ ", " ", "........ ", "........ ", " ", ".... ", ".... "}; ================================================ FILE: resources/pixmaps/live_mode.xpm ================================================ /* XPM */ static const char * live_mode_xpm[] = { "18 17 5 1", " c None", ". c #000000", "+ c #A5A5A5", "@ c #C6EF94", "# c #FFFFFF", "..................", ".++++++++++++++++.", ".++++++++++....++.", ".+++++++++..@@..+.", ".+++++++++.@@@@.+.", ".++++++++..@@@@.+.", ".+++++++.#..@@..+.", ".++++++.#......++.", ".+++++.#.....++++.", ".++++.#.....+++++.", ".+++.#....+++++++.", ".++.#....++++++++.", ".+.#....+++++++++.", ".+....+++++++++++.", ".++..++++++++++++.", ".++++++++++++++++.", ".................."}; ================================================ FILE: resources/pixmaps/logo.xpm ================================================ /* XPM */ static const char * route64rwb_32x32_xpm[] = { "32 32 465 2", " c None", ". c #373637", "+ c #000303", "@ c #0E1818", "# c #121111", "$ c #0A1111", "% c #191C1C", "& c #152020", "* c #414040", "= c #362626", "- c #CB1C1C", "; c #4B2323", "> c #000E0E", ", c #282D2D", "' c #000000", ") c #191F1F", "! c #000D0C", "~ c #392020", "{ c #B82929", "] c #F31A1A", "^ c #921313", "/ c #1D2222", "( c #091313", "_ c #0C1616", ": c #000708", "< c #2F2C2B", "[ c #911F20", "} c #E23434", "| c #181414", "1 c #252627", "2 c #431314", "3 c #FA1717", "4 c #F10D0D", "5 c #F60A0A", "6 c #FD2222", "7 c #C92323", "8 c #B23635", "9 c #B33232", "0 c #BF1C1C", "a c #FB1C1C", "b c #F80A0A", "c c #EA0606", "d c #EB0707", "e c #FB0505", "f c #F12E2E", "g c #BB1F1F", "h c #BB3F3F", "i c #C30E0F", "j c #F72D2C", "k c #F10C0C", "l c #ED2222", "m c #1B1818", "n c #4F4D4D", "o c #212425", "p c #641D1D", "q c #FA1010", "r c #F20D0D", "s c #CB3333", "t c #DDCDCD", "u c #DCC5C5", "v c #DACCCC", "w c #EE2D2D", "x c #CC1717", "y c #DFD0D0", "z c #DBCBCB", "A c #EF2424", "B c #CD3737", "C c #DECACA", "D c #DACBCC", "E c #ED3535", "F c #F00C0C", "G c #F31616", "H c #201213", "I c #2F2F2F", "J c #060909", "K c #983434", "L c #F70B0B", "M c #C93737", "N c #F6FFFF", "O c #F78989", "P c #F78F8F", "Q c #F22222", "R c #C51515", "S c #FFFFFF", "T c #F19595", "U c #F68E8E", "V c #F21D1D", "W c #CC3C3C", "X c #D78888", "Y c #F5F9F9", "Z c #F43E3E", "` c #F00D0D", " . c #F71111", ".. c #351919", "+. c #313030", "@. c #323030", "#. c #522525", "$. c #C93738", "%. c #F9FFFF", "&. c #D6A8A8", "*. c #D5ADAE", "=. c #ED2828", "-. c #D6ADAD", ";. c #D7ADAE", ">. c #EE2020", ",. c #CD3C3D", "'. c #C90F0F", "). c #F0F0F0", "!. c #F43F3F", "~. c #E91919", "{. c #171616", "]. c #0B1010", "^. c #B72525", "/. c #DD2121", "(. c #FB8B8B", "_. c #FA7D7D", ":. c #E9FCFC", "<. c #F33434", "[. c #F1BBBC", "}. c #F4BABA", "|. c #F12222", "1. c #C60202", "2. c #EFF0F0", "3. c #F60707", "4. c #632829", "5. c #2E2E2F", "6. c #180E0E", "7. c #FA0E0E", "8. c #D62727", "9. c #FEB4B4", "0. c #FBA7A7", "a. c #F0FFFF", "b. c #C41515", "c. c #F8AFAF", "d. c #FBA9A9", "e. c #F21F1F", "f. c #EDAEAE", "g. c #FAFFFF", "h. c #E81F1F", "i. c #0B1414", "j. c #182121", "k. c #AD1D1D", "l. c #F71010", "m. c #F61111", "n. c #D64142", "o. c #F6F7F7", "p. c #F2EDED", "q. c #F3F7F7", "r. c #F53535", "s. c #D52323", "t. c #F7FAFA", "u. c #F3F3F3", "v. c #F52B2B", "w. c #D84646", "x. c #F5F2F2", "y. c #F4EFEF", "z. c #F94444", "A. c #F90F0F", "B. c #842020", "C. c #141414", "D. c #170B0A", "E. c #422C1B", "F. c #412B1B", "G. c #412B1A", "H. c #442615", "I. c #431706", "J. c #431807", "K. c #412917", "L. c #432918", "M. c #421605", "N. c #431806", "O. c #412918", "P. c #432614", "Q. c #421806", "R. c #431A09", "S. c #43402F", "T. c #403B29", "U. c #422C1A", "V. c #412C1B", "W. c #422C1C", "X. c #090202", "Y. c #151421", "Z. c #2423E6", "`. c #2322E3", " + c #2222E3", ".+ c #2323E4", "++ c #1C1CDC", "@+ c #2221E3", "#+ c #1716EA", "$+ c #1313E8", "%+ c #1413E9", "&+ c #2221E4", "*+ c #3635EA", "=+ c #171618", "-+ c #2C2B13", ";+ c #2222F0", ">+ c #1D1DEF", ",+ c #1515EC", "'+ c #A7A7DF", ")+ c #CECDE0", "!+ c #1919ED", "~+ c #2826E3", "{+ c #DAD9C9", "]+ c #EBEBDC", "^+ c #CCCDB1", "/+ c #1819DA", "(+ c #3232F5", "_+ c #0A090C", ":+ c #1D1C29", "<+ c #1F1FF2", "[+ c #1616F0", "}+ c #9595D7", "|+ c #4D4CCD", "1+ c #1C1CF0", "2+ c #1617F2", "3+ c #C0BDDA", "4+ c #C8C8BE", "5+ c #0F0FD3", "6+ c #202023", "7+ c #141456", "8+ c #1E1EF4", "9+ c #1616F1", "0+ c #9292DB", "a+ c #9A9ADD", "b+ c #1414F0", "c+ c #1B1CEF", "d+ c #5F5DE3", "e+ c #F9F9F2", "f+ c #C7C8BE", "g+ c #2525FF", "h+ c #111014", "i+ c #181814", "j+ c #3030D2", "k+ c #1C1CEF", "l+ c #201FDB", "m+ c #F9F9F4", "n+ c #BBBBDF", "o+ c #1313F0", "p+ c #1B1AE4", "q+ c #EBEAEA", "r+ c #FFFFFE", "s+ c #1919F3", "t+ c #2C2E74", "u+ c #2E2E2E", "v+ c #202050", "w+ c #1919F9", "x+ c #B0AFE2", "y+ c #FEFDF9", "z+ c #2323E8", "A+ c #1717F0", "B+ c #1A1AF0", "C+ c #1414EF", "D+ c #ABA9DE", "E+ c #BBBCD5", "F+ c #2F2FF2", "G+ c #100F0B", "H+ c #050500", "I+ c #2D2DEF", "J+ c #1F1FE9", "K+ c #F2F2EB", "L+ c #CCCCE2", "M+ c #9E9EEE", "N+ c #A4A4EF", "O+ c #9090EB", "P+ c #1818E2", "Q+ c #1E1EEF", "R+ c #5B57D6", "S+ c #7174C9", "T+ c #AAA7F4", "U+ c #C7C8BF", "V+ c #1B1BEF", "W+ c #3838A2", "X+ c #2A2961", "Y+ c #1919F2", "Z+ c #5B5BE2", "`+ c #FEFEF5", " @ c #3333E0", ".@ c #1F1FEB", "+@ c #F1EFE2", "@@ c #FFFEFF", "#@ c #DCDDE7", "$@ c #0A0BE7", "%@ c #A9A8F1", "&@ c #1E1EF5", "*@ c #1A1A2D", "=@ c #3D3DD2", "-@ c #1919EF", ";@ c #B0B0EA", ">@ c #FFFFFD", ",@ c #E6E6F1", "'@ c #DDDDEC", ")@ c #FFFFF8", "!@ c #FEFEFF", "~@ c #F1F1EA", "{@ c #2222E8", "]@ c #1213F1", "^@ c #BEBBD6", "/@ c #373ADC", "(@ c #0C0CEE", "_@ c #A7A5F0", ":@ c #C4C4BB", "<@ c #0606D0", "[@ c #1A1AF1", "}@ c #34338B", "|@ c #1C1A16", "1@ c #181812", "2@ c #1D1DEE", "3@ c #1A1AF4", "4@ c #A9A9CD", "5@ c #4B4BE0", "6@ c #1B1BE8", "7@ c #1818E5", "8@ c #4242DD", "9@ c #EBEBEF", "0@ c #8281E1", "a@ c #413CD4", "b@ c #ECEDF2", "c@ c #ADADFC", "d@ c #ABABFA", "e@ c #E1E1F9", "f@ c #F5F5F0", "g@ c #ACACF3", "h@ c #6F71D8", "i@ c #1B1AF4", "j@ c #1C1CEE", "k@ c #2425BE", "l@ c #2B2B22", "m@ c #161629", "n@ c #1E1EF0", "o@ c #1414EE", "p@ c #DBDBFF", "q@ c #B0B1E2", "r@ c #1616F3", "s@ c #1314F0", "t@ c #C1BFF4", "u@ c #CCCCE6", "v@ c #4A45D2", "w@ c #878ACB", "x@ c #1E1DF7", "y@ c #3736D6", "z@ c #1A1A10", "A@ c #1C1D2C", "B@ c #AFAFD7", "C@ c #B8B8DB", "D@ c #0E0EF6", "E@ c #1010EE", "F@ c #C6C6ED", "G@ c #C6C6E6", "H@ c #1D19D4", "I@ c #FAFAFF", "J@ c #F2F2FE", "K@ c #F1F1FE", "L@ c #F3F3FE", "M@ c #8385CC", "N@ c #1D1DF7", "O@ c #2626C9", "P@ c #242419", "Q@ c #161608", "R@ c #2727ED", "S@ c #7878D2", "T@ c #8E8DDC", "U@ c #2626DB", "V@ c #2021D8", "W@ c #8F8FE3", "X@ c #FCFBF9", "Y@ c #6E6DD8", "Z@ c #1919F0", "`@ c #2424F0", " # c #2424EF", ".# c #1C1DF0", "+# c #ACA9F1", "@# c #CCCDC3", "## c #1818D6", "$# c #2020ED", "%# c #31329D", "&# c #2D2D29", "*# c #1D1C1E", "=# c #242479", "-# c #1B1BF1", ";# c #1E1EEC", "># c #E9E8E9", ",# c #C4C4E4", "'# c #1616EF", ")# c #0F10D3", "!# c #2121F6", "~# c #20202F", "{# c #090900", "]# c #2626C8", "^# c #1A1AF3", "/# c #1D1DF0", "(# c #2D2DE8", "_# c #C9C9ED", ":# c #FFFFFA", "<# c #F5F4EF", "[# c #9595E1", "}# c #1414EB", "|# c #ABA9F3", "1# c #C7C7BF", "2# c #0E0ED2", "3# c #1C1CF8", "4# c #2C2C81", "5# c #1E1E1B", "6# c #0F0F0C", "7# c #3D3CA0", "8# c #1919FE", "9# c #1E1EEE", "0# c #1717EC", "a# c #3333EC", "b# c #5E5EE2", "c# c #5F5FEB", "d# c #201FE2", "e# c #1919F1", "f# c #1818EF", "g# c #6967E1", "h# c #8888D3", "i# c #7F80C4", "j# c #1E1EE6", "k# c #2929FB", "l# c #2A2A63", "m# c #14150E", "n# c #1B1A19", "o# c #282826", "p# c #262597", "q# c #2E2ED0", "r# c #4747F1", "s# c #1B1BF6", "t# c #1D1DF5", "u# c #1C1CF2", "v# c #1C1CF1", "w# c #1D1DF4", "x# c #1C1CF5", "y# c #2C2CF5", "z# c #2423C5", "A# c #3131AA", "B# c #25256C", "C# c #141408", "D# c #2E2D2D", "E# c #161717", "F# c #27251B", "G# c #212006", "H# c #181725", "I# c #34336B", "J# c #2323B7", "K# c #2929F4", "L# c #1B1BF3", "M# c #1F1FF9", "N# c #2E2EDF", "O# c #292A81", "P# c #19193B", "Q# c #262615", "R# c #0C0C00", "S# c #222114", "T# c #201F13", "U# c #1A1A1B", "V# c #191978", "W# c #2D2DF4", "X# c #1818FD", "Y# c #2A2AC5", "Z# c #161646", "`# c #0D0E08", " $ c #282726", ".$ c #1D1D1B", "+$ c #0F0E06", "@$ c #1F1F26", "#$ c #12110E", " ", " . + @ # $ % & ", " * = - ; > , ' ' ) ! ~ { ] ^ / ( ' ' _ : < [ } | ", " 1 2 3 4 5 6 7 8 9 0 a b c d d e f g h i j e k k l m n ", " o p q 4 4 4 r s t u v w x y u z A B C u D E 4 4 4 F G H I ", " J K L 4 4 4 4 r M N O P Q R S T U V W S X Y Z 4 4 4 4 ` ...+.", " @.#.b 4 4 4 4 r $.%.&.*.=.R S -.;.>.,.S '.).!.4 4 4 4 4 ~.{. ", " ].^.k 4 4 4 4 /.(._.:.<.R S [.}.|.W S 1.2.!.4 4 4 4 3.4.5. ", " 6.7.4 4 4 r 8.9.0.a.<.b.S c.d.e.W S f.g.!.4 4 4 4 h.i. ", " j.k.l.m.m. .n.o.p.q.r.s.t.p.u.v.w.x.y.S z.m.m.m.A.B.C. ", " D.E.F.G.F.H.I.J.I.K.L.M.N.I.O.P.Q.R.S.T.E.U.V.W.X. ", " Y.Z.`.`. +`..+++ +`. +`.`.`.`. +`.@+#+$+%+&+`.*+=+ ", " -+;+>+>+>+,+'+)+!+>+>+>+>+>+>+>+>+~+{+]+^+/+>+(+_+ ", " :+<+>+>+[+}+S S |+1+>+>+>+>+>+>+2+3+S S 4+5+>+(+6+ ", " 7+8+>+9+0+S S a+b+>+>+>+>+>+>+c+d+e+S S f+5+>+g+h+ ", " i+j+k+>+l+m+S n+o+>+>+>+>+>+>+>+p+q+S r+S f+5+>+s+t+u+ ", " v+w+>+9+x+S y+z+A+B+>+>+>+>+>+C+D+S S E+S f+5+>+>+F+G+ ", " H+I+>+>+J+K+S L+M+N+O+P+Q+>+>+1+R+S S S+T+S U+5+>+>+V+W+# ", " X+Y+>+>+Z+S S S S S S `+ @>+>+.@+@@@#@$@%@S U+5+>+>+>+&@*@ ", " ' =@>+>+-@;@S S >@,@'@)@!@~@{@]@^@S S /@(@_@S :@<@A+>+>+[@}@|@", " 1@2@>+>+3@4@S S 5@6@7@8@9@S 0@a@S S b@c@d@e@S f@g@h@i@>+j@k@l@", " m@n@>+>+o@p@S q@r@>+>+s@t@S u@v@S S S S S S S S S w@x@>+>+y@z@", " A@n@>+>+s+B@S C@D@>+>+E@F@S G@H@I@J@J@J@K@I@S S L@M@N@>+2@O@P@", " Q@R@>+>+k+S@S >@T@U@V@W@X@S Y@Z@`@ # # #.#+#S @###$#>+>+B+%#&#", " *#=#-#>+>+;#>#S S S S S S ,#-#>+>+>+>+>+'#%@S f+)#>+>+>+!#~# ", " {#]#^#>+/#(#_#:#S S <#[#}#>+>+>+>+>+>+[+|#S 1#2#>+Q+3#4#5# ", " 6#7#8#9#>+0#a#b#c#d#e#>+>+>+>+>+>+>+f#g#h#i#j#e#k#l#m# ", " n#o#p#q#r#s#t#u#>+>+>+>+>+>+>+>+v#w#x#y#z#A#B#C#D# ", " E#F#' G#H#I#J#K#L#>+>+2@M#N#O#P#Q#R#S#' ", " T#U#V#W#X#Y#Z#`# $ ", " .$+$@$#$ ", " "}; ================================================ FILE: resources/pixmaps/loop.xpm ================================================ /* XPM */ static const char * loop_xpm[] = { "33 12 3 1", " c None", ". c #000000", "+ c #FFFFFF", " ", " . ", " ....... .. ....... ", " .+..... ..... .. .++++.. ", " .+..... . .. . .+...+. ", " .+..... . . . .+...+. ", " .+..... . . .++++.. ", " .+..... . . .+.+... ", " .+..... . . .+..+.. ", " .+++++. ......... .+...+. ", " ....... ....... ", " "}; ================================================ FILE: resources/pixmaps/menu.xpm ================================================ /* XPM */ static const char * menu_xpm[] = { "18 18 4 1", " c None", ". c #000000", "+ c #6C734B", "@ c #C6EF94", "..................", "..................", "..+@@@@@@@@@@@@@..", "..+@@@@@@@@@@@@@..", "..................", "..................", "..+@@@@@@@@@@@@@..", "..+@@@@@@@@@@@@@..", "..................", "..................", "..+@@@@@@@@@@@@@..", "..+@@@@@@@@@@@@@..", "..................", "..................", "..+@@@@@@@@@@@@@..", "..+@@@@@@@@@@@@@..", "..................", ".................."}; ================================================ FILE: resources/pixmaps/menu_empty.xpm ================================================ /* XPM */ static const char * menu_empty_xpm[] = { "10 10 2 1", " c None", ". c #000000", "..........", ". .", ". .", ". .", ". .", ". .", ". .", ". .", ". .", ".........."}; ================================================ FILE: resources/pixmaps/menu_empty_inv.xpm ================================================ /* XPM */ static const char * menu_empty_inv_xpm[] = { "10 10 2 1", " c None", ". c #FFFFFF", "..........", ". .", ". .", ". .", ". .", ". .", ". .", ". .", ". .", ".........."}; ================================================ FILE: resources/pixmaps/menu_full.xpm ================================================ /* XPM */ static const char * menu_full_xpm[] = { "10 10 2 1", " c None", ". c #000000", " ", " ........ ", " ........ ", " ........ ", " ........ ", " ........ ", " ........ ", " ........ ", " ........ ", " "}; ================================================ FILE: resources/pixmaps/menu_full_inv.xpm ================================================ /* XPM */ static const char * menu_full_inv_xpm[] = { "10 10 2 1", " c None", ". c #FFFFFF", " ", " ........ ", " ........ ", " ........ ", " ........ ", " ........ ", " ........ ", " ........ ", " ........ ", " "}; ================================================ FILE: resources/pixmaps/metro.xpm ================================================ /* XPM */ static const char * metro_xpm[] = { "40 48 160 2", " c None", ". c #989898", "+ c #000000", "@ c #676767", "# c #747474", "$ c #707070", "% c #606060", "& c #3F3F3F", "* c #F6F6F6", "= c #F7F7F7", "- c #D7D7D7", "; c #6B6B6B", "> c #666666", ", c #6A6A6A", "' c #626262", ") c #696969", "! c #050505", "~ c #F9F9F9", "{ c #ACACAC", "] c #FAFAFA", "^ c #F8F8F8", "/ c #FFFFFF", "( c #EAEAEA", "_ c #ECECEC", ": c #EFEFEF", "< c #3C3C3C", "[ c #F0F0F0", "} c #646464", "| c #AAAAAA", "1 c #FEFEFE", "2 c #E8E8E8", "3 c #F3F3F3", "4 c #D6D6D6", "5 c #AEAEAE", "6 c #FBFBFB", "7 c #E1E1E1", "8 c #CECECE", "9 c #E9E9E9", "0 c #1C1C1C", "a c #434343", "b c #E2E2E2", "c c #F5F5F5", "d c #C7C7C7", "e c #C6C6C6", "f c #3E3E3E", "g c #EEEEEE", "h c #797979", "i c #757575", "j c #E0E0E0", "k c #D9D9D9", "l c #EDEDED", "m c #D5D5D5", "n c #555555", "o c #292929", "p c #8C8C8C", "q c #FDFDFD", "r c #B5B5B5", "s c #F1F1F1", "t c #F4F4F4", "u c #B3B3B3", "v c #3D3D3D", "w c #181818", "x c #A2A2A2", "y c #8D8D8D", "z c #242424", "A c #616161", "B c #848484", "C c #404040", "D c #5A5A5A", "E c #BABABA", "F c #C2C2C2", "G c #DBDBDB", "H c #191919", "I c #727272", "J c #969696", "K c #F2F2F2", "L c #DCDCDC", "M c #575757", "N c #6D6D6D", "O c #E3E3E3", "P c #BDBDBD", "Q c #A9A9A9", "R c #343434", "S c #B0B0B0", "T c #8B8B8B", "U c #BBBBBB", "V c #BFBFBF", "W c #FCFCFC", "X c #878787", "Y c #0A0A0A", "Z c #D8D8D8", "` c #DEDEDE", " . c #C1C1C1", ".. c #767676", "+. c #2E2E2E", "@. c #949494", "#. c #4A4A4A", "$. c #9F9F9F", "%. c #5B5B5B", "&. c #4D4D4D", "*. c #B7B7B7", "=. c #B4B4B4", "-. c #A6A6A6", ";. c #D3D3D3", ">. c #CBCBCB", ",. c #929292", "'. c #7C7C7C", "). c #EBEBEB", "!. c #777777", "~. c #B8B8B8", "{. c #DDDDDD", "]. c #898989", "^. c #D4D4D4", "/. c #939393", "(. c #8A8A8A", "_. c #636363", ":. c #CFCFCF", "<. c #B6B6B6", "[. c #BCBCBC", "}. c #E4E4E4", "|. c #AFAFAF", "1. c #E5E5E5", "2. c #8E8E8E", "3. c #909090", "4. c #9A9A9A", "5. c #C0C0C0", "6. c #3B3B3B", "7. c #717171", "8. c #999999", "9. c #D1D1D1", "0. c #383838", "a. c #979797", "b. c #1D1D1D", "c. c #656565", "d. c #E6E6E6", "e. c #2A2A2A", "f. c #CDCDCD", "g. c #808080", "h. c #6F6F6F", "i. c #CACACA", "j. c #424242", "k. c #ABABAB", "l. c #484848", "m. c #B2B2B2", "n. c #202020", "o. c #454545", "p. c #858585", "q. c #C9C9C9", "r. c #272727", "s. c #9D9D9D", "t. c #9E9E9E", "u. c #828282", "v. c #888888", "w. c #868686", "x. c #505050", "y. c #030303", "z. c #0B0B0B", "A. c #040404", "B. c #131313", "C. c #090909", " . + + + + + + @ ", " # + + + + + + + + + $ ", " % & + * * * * * * = - + ", " + + ; > + , , ; ' ) , ! + ", " + + + ~ { + ] ^ / + ( _ + : + < ", " + [ } + ^ | + + + 1 + 2 3 + 4 + # ", " 5 + 6 + 7 ^ | + 8 9 1 + 2 ~ + . + 0 ", " a + b + = = | + c c 1 + 2 ^ + + / + 4 ", " + d e + = = | + f + 1 + 2 = g + g + h ", "i + j . + = = | + k l 1 + 2 = = + m + n ", "o p m + q + r = = | + s t 1 + 2 = = + u + v ", " w x y + _ + _ = = | + z + 1 + 2 = = + A g + ", " + B C D + E F + q = = | + 2 s 1 + 2 = = G + / + E ", " H + + + I + 9 J + ~ = = | + g K 1 + 2 = = ] + L + M ", " + N [ ^ O + P + 3 + Q = = = | + R + / + 2 = = 6 + S + + ", " T + O ^ ~ U + V + W + 4 = = = | + K c 1 + 2 = = = + X l + ", " y Y G ~ j + p + T Z + * = = = | + ` l 1 + 2 = = = .+ ~ + ", " ..+.+ + n + - @.+ W = = = | + #.+ 1 + 2 = = = [ + : + $. ", " %.&. + + * + *.^ = = = | + * = 1 + 2 = = = 1 + =.P + ", " -.+ + = + ;.= = = = | + >.2 1 + 2 = = = 6 ,.+ c + ", " h + + '._ + ).= = = = | + + + 1 + 2 = = = = d + = + ", " !.+ ~.r + W = = = = | + q ^ 1 + 2 = = = = {.+ c + -. ", " + ].+ J ] = = = = | + U b 1 + 2 = = = = t + ^./.+ ", " (.+ + + L = = = = = | + + + 1 + 2 = = = = 6 _.+ - + ", " + y L + e ~ = = = = | + / ] / + 2 = = = = ~ V + W + V ", " + 5 :.+ + - ^ = = = | + <.=.[.+ 2 = = = = = }.+ s + _. ", " + G + _.|.+ 1.= = = | + 2.T 3.+ 2 = = = = = [ + 2 4.+ ", " 8 + / + 5.W 3.+ K = = | + q ] / + 2 = = = = = = 6.+ F + ", " + N ).+ [ = * + 7.] = | + ] = 1 + 2 = = = = = ^ 8.+ s + ", " + =.9.0.= = = g + a.= | + ] = 1 + 2 = = = = = = 1.+ ] b.x ", " R 4 S c.= = = ^ d.+ E S + ] = 1 + 2 = = = = = = = + ` . e. ", " + f.D g.^.^.^.^.m S + h.+ - ^.G + i.^.^.^.^.^.^.^.j.k.{ l. ", " m.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + n. ", " o.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", " + L = = = = = = = = = = = = = = = = = = = = = = = = = = 6 + p. ", " + ).= = = = = = = = = = = = = = = = = = = = = = = = = = ^ d e. ", " + c = = = = = = = = = = = = = = = = = = = = = = = = = = = 2 + ", " T + ^ = = = = = = = = = = = = = = = = = = = = = = = = = = = : + ", " + q.^ = = = = = = = = = = = = = = = = = = = = = = = = = = = ^ r.r ", " + * = = = = = = = = = = = = = = = = = = = = = = = = = = = = ^ s.+ ", " 0.= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = {.+ ", " + ' = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + ", " + _.t.= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + u.", " + {.= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + v.", " + ' ] = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Z + ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", " w. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x.", " # y.z.A.B B.C.Y + "}; ================================================ FILE: resources/pixmaps/metro_on.xpm ================================================ /* XPM */ static const char * metro_on_xpm[] = { "40 48 148 2", " c None", ". c #34939B", "+ c #000000", "@ c #65C4CC", "# c #38A0A8", "$ c #3AA6AE", "% c #C3E8EB", "& c #F3FAFB", "* c #F2FAFB", "= c #205A5F", "- c #308990", "; c #030708", "> c #35979F", ", c #35969E", "' c #318C93", ") c #3699A1", "! c #339299", "~ c #1E565A", "{ c #E7F6F7", "] c #E3F4F6", "^ c #7DC0EF", "/ c #FFFFFF", "( c #F5FBFC", "_ c #F8FCFD", ": c #83CFD6", "< c #F6FCFC", "[ c #C2E7EB", "} c #FEFEFF", "| c #80CED5", "1 c #328F96", "2 c #E9F6F8", "3 c #0E282A", "4 c #DEF2F4", "5 c #B6E3E7", "6 c #F9FDFD", "7 c #86D1D7", "8 c #F0F9FA", "9 c #D4EEF1", "0 c #226065", "a c #3DADB6", "b c #E6F5F7", "c c #1F585D", "d c #AADEE3", "e c #ABDFE3", "f c #2B7980", "g c #C0E7EA", "h c #E4F5F6", "i c #C6E9EC", "j c #D1EDF0", "k c #3BA7B0", "l c #1F575C", "m c #8DD3D9", "n c #EFF9FA", "o c #EAF7F8", "p c #90D5DA", "q c #FCFEFE", "r c #53BDC6", "s c #153A3E", "t c #318A92", "u c #123336", "v c #54BEC6", "w c #74CAD1", "x c #0C2224", "y c #98D7DD", "z c #DDF2F4", "A c #A4DCE1", "B c #2D8087", "C c #205B60", "D c #47B8C2", "E c #2C7C83", "F c #CBEBEE", "G c #ECF8F9", "H c #62C3CB", "I c #39A3AB", "J c #0D2426", "K c #89D2D8", "L c #1A4A4E", "M c #7ECED4", "N c #EDF8F9", "O c #9CD9DE", "P c #379CA4", "Q c #4BBAC3", "R c #FBFDFE", "S c #9FDADF", "T c #51BDC5", "U c #A2DBE0", "V c #CEECEF", "W c #C5E9EC", "X c #050E0F", "Y c #6FC8CF", "Z c #256A6F", "` c #5FC2CA", " . c #174245", ".. c #3BA8B1", "+. c #8FD4DA", "@. c #93D6DB", "#. c #276E74", "$. c #2E8289", "%. c #5CC1C9", "&. c #B1E1E5", "*. c #7ACCD3", "=. c #3EB1BA", "-. c #95D6DC", ";. c #3CAAB3", ">. c #5DC1C9", ",. c #BFE6EA", "'. c #99D8DD", "). c #4EBBC4", "!. c #328D95", "~. c #50BCC5", "{. c #9BD9DE", "]. c #92D5DB", "^. c #B7E3E7", "/. c #68C5CD", "(. c #59BFC8", "_. c #56BEC7", ":. c #87D1D7", "<. c #C9EAED", "[. c #1E5459", "}. c #A1DBE0", "|. c #66C5CC", "1. c #39A1AA", "2. c #E1F4F5", "3. c #0F292C", "4. c #63C3CB", "5. c #1C5054", "6. c #BAE5E8", "7. c #153C3F", "8. c #339098", "9. c #24676C", "0. c #81CFD5", "a. c #215E63", "b. c #B0E1E5", "c. c #389EA7", "d. c #41B6C0", "e. c #B4E2E6", "f. c #102E30", "g. c #8CD3D9", "h. c #48B9C2", "i. c #44B7C1", "j. c #6EC7CF", "k. c #287278", "l. c #4ABAC3", "m. c #050D0E", "n. c #0A1B1D", "o. c #020606", "p. c #061011", "q. c #020405", " . + + + + + + @ ", " # + + + + + + + + + $ ", " + % & * * * * * * + = - ", " + ; > , ' ) > > + ! ) + + ", " ~ + { + ] ^ + / ( _ + : < + + + ", " $ + [ + ^ ^ + } + + + | ^ + 1 2 + ", " 3 + @ + ^ ^ + } 4 5 + | ^ ^ + 6 + 7 ", " [ + / + + ^ ^ + } 8 8 + | ^ ^ + 9 + 0 ", " a + b + ^ ^ ^ + } + c + | ^ ^ + d e + ", " f + g + ^ ^ ^ + } h i + | ^ ^ + @ j + k ", " l + m + ^ ^ ^ + } n o + | ^ ^ p + q + g r s ", " + b t + ^ ^ ^ + } + u + | ^ ^ ^ + ] + v w x ", " y + / + ^ ^ ^ ^ + } o z + | ^ ^ ^ + A y + B C D + ", " E + F + ^ ^ ^ ^ + } G b + | ^ ^ ^ + H 4 + I + + + J ", " + + K + ^ ^ ^ ^ + / + L + | ^ ^ ^ M + N + O + ^ ^ ^ P + ", " + h Q + ^ ^ ^ ^ + } 8 G + | ^ ^ ^ ^ + R + S + ^ ^ ^ ^ + T ", " + < + U ^ ^ ^ ^ + } h V + | ^ ^ ^ ^ + W T + r + ^ ^ ^ X v ", " Y + { + ^ ^ ^ ^ ^ + } + Z + | ^ ^ ^ ^ + ` % + f + + ... ", " + O +.+ ^ ^ ^ ^ ^ + } & * + | ^ ^ ^ ^ @.+ * + + #.$. ", " + 8 + %.^ ^ ^ ^ ^ + } z &.+ | ^ ^ ^ ^ ^ + & + + *. ", " + & + ^ ^ ^ ^ ^ ^ + } + + + | ^ ^ ^ ^ ^ + ] =.+ + a ", " *.+ 8 + ^ ^ ^ ^ ^ ^ + } ( q + | ^ ^ ^ ^ ^ + p -.+ ;. ", " + >.,.+ ^ ^ ^ ^ ^ ^ + } 9 '.+ | ^ ^ ^ ^ ^ H + ).+ ", " + % + !.^ ^ ^ ^ ^ ^ + } + + + | ^ ^ ^ ^ ^ ^ + + + ~. ", " S + R + ^ ^ ^ ^ ^ ^ ^ + / _ / + | ^ ^ ^ ^ ^ ^ + F v + ", " !.+ o + ^ ^ ^ ^ ^ ^ ^ + {.+.].+ | ^ ^ ^ ^ ^ + + ^.7 + ", " + /.z + ^ ^ ^ ^ ^ ^ ^ + (.T _.+ | ^ ^ ^ ^ + :.!.+ <.+ ", " + A + [.^ ^ ^ ^ ^ ^ ^ + / _ q + | ^ ^ ^ + (.^ }.+ / + 5 ", " + o + |.^ ^ ^ ^ ^ ^ ^ + } & _ + | ^ ^ 1.+ ^ ^ ^ + 2.P + ", " w 3._ + ^ ^ ^ ^ ^ ^ ^ ^ + } & _ + | ^ 4.+ ^ ^ ^ ^ 5.6.+.+ ", " 7.@ V + ^ ^ ^ ^ ^ ^ ^ ^ + } & _ + K y + ^ ^ ^ ^ ^ 8.K [ L ", " 9.: 0.a.,.,.,.,.,.,.,.b.+ <.,.% + c.+ K g ,.,.,.,.d.B e.+ ", " f.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + g. ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", " h.+ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ + ", " + + ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ + ", " + + ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ + ", " + ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ + T ", " p + ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ + + ", " + + ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ + ", " + ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ 5.+ ", " + ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ' + ", "i.+ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ j.+ + ", "+ + ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ + + ", "+ + ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ + + ", "+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", "k.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + l. ", " + X m.n. D o.p.q.$ "}; ================================================ FILE: resources/pixmaps/midi.xpm ================================================ /* XPM */ static const char * midi_xpm[] = { "14 14 4 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #9E9E9E", " ...... ", " ...+..+... ", " ..+++@@+++.. ", " .++++++++++. ", "..++++++++++..", ".++++++++++++.", ".+..++++++..+.", ".+@.++++++.@+.", ".++++++++++++.", "..+..++++..+..", " .+@.+..+.@+. ", " ..+++..+++.. ", " ...++++... ", " ...... "}; ================================================ FILE: resources/pixmaps/muting.xpm ================================================ /* XPM */ static const char * muting_xpm[] = { "18 18 3 1", " c None", ". c #000000", "+ c #C6EF94", "..................", "..................", "..+.......++++++..", "...+...+...+++.+..", "......+...+.+++...", "..+.+.....+++.++..", ".....+.+..++.++...", "..+.......++++++..", "..................", "..................", "..++++++..+.+..+..", "...++..+...+..+...", "...+.+.+....+..+..", "..+.+.++..+...+...", "..++.+.+...++.+...", "...++.++..+....+..", "..................", ".................."}; ================================================ FILE: resources/pixmaps/n_rec_on.xpm ================================================ /* XPM */ static const char * n_rec_on_xpm[] = { "36 14 8 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #FF0000", "# c #9E9E9E", "$ c #FFBABA", "% c #FF5454", "& c #B51E1E", " ...... ", " ...+..+... @@@@@ @@@", " ..+++##+++.. . @@@ @@", " .++++++++++. .. @@@@ @@", "..++++++++++.. .$. @@@@@ @@", ".++++++++++++. ......$%. @ @@@@ @@", ".+..++++++..+. .$$$$$%%%. @ @@@ @@", ".+#.++++++.#+. .%%%%%%%&. @ @@@ @@", ".++++++++++++. ......%&. @ @@@@@", "..+..++++..+.. .&. @ @@@@@", " .+#.+..+.#+. .. @ @@@@", " ..+++..+++.. . @@ @@@", " ...++++... @@@@@ @@", " ...... "}; ================================================ FILE: resources/pixmaps/note_length.xpm ================================================ /* XPM */ static const char * note_length_xpm[] = { "32 16 2 1", " c None", ". c #000000", " ", " ", " . . ... ... ", " . . ... ... ", " . . . . ", " . . . ... ", " . . . ... ", " . . . . ", " . . . . ", " .... .... .... .... ", " . . .... .... .... ", " .... .... .... .... ", " ", " ", " ", " "}; ================================================ FILE: resources/pixmaps/note_length_inv.xpm ================================================ /* XPM */ static const char * note_length_inv_xpm[] = { "32 16 2 1", " c None", ". c #FFFFFF", " ", " ", " . . ... ... ", " . . ... ... ", " . . . . ", " . . . ... ", " . . . ... ", " . . . . ", " . . . . ", " .... .... .... .... ", " . . .... .... .... ", " .... .... .... .... ", " ", " ", " ", " "}; ================================================ FILE: resources/pixmaps/numbers_off.xpm ================================================ /* XPM */ static const char * numbers_off_xpm[] = { "10 12 2 1", " c None", ". c #000000", " .. .. ", " .. .. ", ".. .. ..", ". .", ".. .. ..", " .. .. ", " .. .. ", ".. .. ..", ". .", ".. .. ..", " .. .. ", " .. .. "}; ================================================ FILE: resources/pixmaps/numbers_on.xpm ================================================ /* XPM */ static const char * numbers_on_xpm[] = { "10 12 2 1", " c None", ". c #000000", " .. .. ", " .. .. ", "..........", "..........", "..........", " .. .. ", " .. .. ", "..........", "..........", "..........", " .. .. ", " .. .. "}; ================================================ FILE: resources/pixmaps/panic.xpm ================================================ /* XPM */ static const char * panic_xpm[] = { "16 16 85 1", " c None", ". c #000000", "+ c #360E0E", "@ c #130909", "# c #2E0606", "$ c #AE1212", "% c #B80202", "& c #C80000", "* c #AE0000", "= c #980000", "- c #A00000", "; c #AC0000", "> c #1F0101", ", c #210707", "' c #880C0C", ") c #FF4141", "! c #FF2B2B", "~ c #FF1717", "{ c #FF0D0D", "] c #FF0909", "^ c #D00000", "/ c #580000", "( c #120000", "_ c #830B0B", ": c #FF2929", "< c #FF1D1D", "[ c #FF0B0B", "} c #FFFFFF", "| c #FE0000", "1 c #F80000", "2 c #F40000", "3 c #C60000", "4 c #520000", "5 c #6D0707", "6 c #DE0000", "7 c #FF1313", "8 c #FF0505", "9 c #FF0101", "0 c #E4E4E4", "a c #E80000", "b c #E00000", "c c #E20000", "d c #DC0000", "e c #A60000", "f c #4E0000", "g c #A80000", "h c #FF0707", "i c #FA0000", "j c #F20000", "k c #EA0000", "l c #D80000", "m c #D20000", "n c #D40000", "o c #820000", "p c #F60000", "q c #D60000", "r c #CE0000", "s c #CC0000", "t c #DA0000", "u c #9A0000", "v c #940000", "w c #880000", "x c #920000", "y c #8C0000", "z c #860000", "A c #BE0000", "B c #6C0000", "C c #600000", "D c #CA0000", "E c #900000", "F c #3E0000", "G c #000202", "H c #B40000", "I c #A40000", "J c #420000", "K c #4A0000", "L c #9E0000", "M c #100000", "N c #540000", "O c #7A0000", "P c #7E0000", "Q c #7C0000", "R c #780000", "S c #480000", "T c #0E0000", " ..+@.... ", " #$%&*=-;*> ", " ,')!~{]{~^/( ", " ._:<[]}}|1234. ", ".567{89}0abcdef.", ".g8hijk00lmmdno.", ".*|pa6q00rsstcu.", ".vbbq^s00ssslb=.", ".wrmrss00sssltx.", ".ymmsss00sssqnw.", ".z^qrssssss^qAB.", ".C*^^rs00srnDEF.", " GfH^^m0}mm^IJ. ", " (K;mnnnn^LJM ", " (NOPQQQRST ", " ........ "}; ================================================ FILE: resources/pixmaps/panic2.xpm ================================================ /* XPM */ static const char * panic2_xpm[] = { "15 11 6 1", " c None", ". c #000000", "+ c #D4506B", "@ c #FF0000", "# c #FFFFFF", "$ c #784848", " ........... ", " .+++++++++. ", " .+@@##@@@$. ", " .+@@##@@@$. ", " .+@@##@@@$. ", " .+@@##@@@$. ", " .+@@@@@@@$. ", " .+@@##@@@$. ", " .+@@##@@@$. ", " .$$$$$$$$$. ", " ........... "}; ================================================ FILE: resources/pixmaps/pause.xpm ================================================ /* XPM */ static const char * pause_xpm[] = { "16 11 5 1", " c None", ". c #000000", "+ c #FFFE06", "@ c #A1AD67", "# c #6C734B", " ..... ..... ", " .+++. .+++. ", " .+@#. .+@#. ", " .+@#. .+@#. ", " .+@#. .+@#. ", " .+@#. .+@#. ", " .+@#. .+@#. ", " .+@#. .+@#. ", " .+@#. .+@#. ", " .###. .###. ", " ..... ..... "}; ================================================ FILE: resources/pixmaps/perfedit.xpm ================================================ /* XPM */ static const char * perfedit_xpm[] = { "23 14 11 1", " c None", ". c #000000", "+ c #FF7795", "@ c #FFFFFF", "# c #B2405C", "$ c #AAAAAA", "% c #7C7C7C", "& c #FFDA6D", "* c #B58F2F", "= c #AA5C03", "- c #C6C6C6", " .. ", " .++. ", " .@+#. ", " .@$%. ", " .&&%. ", " .&&*. ", " .&&*. ", " .&&*. ", " ..........=*..... ", " .--------.=.----. ", " .--------..-----. ", " .--------.------. ", " .---------------. ", " ................. "}; ================================================ FILE: resources/pixmaps/play.xpm ================================================ /* XPM */ static const char * play_xpm[] = { "36 14 10 1", " c None", ". c #000000", "+ c #BFD8FF", "@ c #FFFFFF", "# c #82B1FF", "$ c #1E56AA", "% c #9E9E9E", "& c #CDFFBC", "* c #37FF00", "= c #379319", ".......... ...... ", ".++++++++. ...@..@... ", ".+######$. . ..@@@%%@@@.. ", ".+######$. .. .@@@@@@@@@@. ", ".+######$. .&. ..@@@@@@@@@@..", ".+######$. ......&*. .@@@@@@@@@@@@.", ".+######$. .&&&&&***. .@..@@@@@@..@.", ".+######$. .*******=. .@%.@@@@@@.%@.", ".+######$. ......*=. .@@@@@@@@@@@@.", ".+######$. .=. ..@..@@@@..@..", ".+######$. .. .@%.@..@.%@. ", ".+######$. . ..@@@..@@@.. ", ".$$$$$$$$. ...@@@@... ", ".......... ...... "}; ================================================ FILE: resources/pixmaps/play2.xpm ================================================ /* XPM */ static const char * play2_xpm[] = { "16 11 5 1", " c None", ". c #000000", "+ c #C6EF94", "@ c #9CBF74", "# c #7B935E", " ... ", " .++.. ", " .+@++.. ", " .+@@@++.. ", " .+@@@@@@@.. ", " .+@@@@@@@##. ", " .+@@@@@##.. ", " .+@@@##.. ", " .+@##.. ", " .##.. ", " ... "}; ================================================ FILE: resources/pixmaps/play_on.xpm ================================================ /* XPM */ static const char * play_on_xpm[] = { "36 14 10 1", " c None", ". c #000000", "+ c #96F996", "@ c #FFFFFF", "# c #00F100", "$ c #0C850C", "% c #9E9E9E", "& c #CDFFBC", "* c #37FF00", "= c #379319", ".......... ...... ", ".++++++++. ...@..@... ", ".+######$. . ..@@@%%@@@.. ", ".+######$. .. .@@@@@@@@@@. ", ".+######$. .&. ..@@@@@@@@@@..", ".+######$. ......&*. .@@@@@@@@@@@@.", ".+######$. .&&&&&***. .@..@@@@@@..@.", ".+######$. .*******=. .@%.@@@@@@.%@.", ".+######$. ......*=. .@@@@@@@@@@@@.", ".+######$. .=. ..@..@@@@..@..", ".+######$. .. .@%.@..@.%@. ", ".+######$. . ..@@@..@@@.. ", ".$$$$$$$$. ...@@@@... ", ".......... ...... "}; ================================================ FILE: resources/pixmaps/q_rec.xpm ================================================ /* XPM */ static const char * q_rec_xpm[] = { "36 14 7 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #9E9E9E", "# c #FFBABA", "$ c #FF5454", "% c #B51E1E", " ...... ", " ...+..+... ..... ", " ..+++@@+++.. . .. ... ", " .++++++++++. .. ... .. ", "..++++++++++.. .#. .. ...", ".++++++++++++. ......#$. .. ...", ".+..++++++..+. .#####$$$. .. ...", ".+@.++++++.@+. .$$$$$$$%. .. ...", ".++++++++++++. ......$%. ... .. ", "..+..++++..+.. .%. ... ... ", " .+@.+..+.@+. .. .... ", " ..+++..+++.. . .. ", " ...++++... ... ", " ...... ... "}; ================================================ FILE: resources/pixmaps/q_rec_on.xpm ================================================ /* XPM */ static const char * q_rec_on_xpm[] = { "36 14 8 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #FF0000", "# c #9E9E9E", "$ c #FFBABA", "% c #FF5454", "& c #B51E1E", " ...... ", " ...+..+... @@@@@ ", " ..+++##+++.. . @@ @@@ ", " .++++++++++. .. @@@ @@ ", "..++++++++++.. .$. @@ @@@", ".++++++++++++. ......$%. @@ @@@", ".+..++++++..+. .$$$$$%%%. @@ @@@", ".+#.++++++.#+. .%%%%%%%&. @@ @@@", ".++++++++++++. ......%&. @@@ @@ ", "..+..++++..+.. .&. @@@ @@@ ", " .+#.+..+.#+. .. @@@@ ", " ..+++..+++.. . @@ ", " ...++++... @@@ ", " ...... @@@ "}; ================================================ FILE: resources/pixmaps/quantize.xpm ================================================ /* XPM */ static const char * quantize_xpm[] = { "16 14 2 1", " c None", ". c #000000", " ", " ", " .... ", " .. .. ", " .. .. ", " .. .. ", " .. .. ", " .. .. ", " .. .. ", " .. . .. ", " .. ... ", " ..... ", " . ", " "}; ================================================ FILE: resources/pixmaps/quantize_inv.xpm ================================================ /* XPM */ static const char * quantize_inv_xpm[] = { "16 14 2 1", " c None", ". c #FFFFFF", " ", " ", " .... ", " .. .. ", " .. .. ", " .. .. ", " .. .. ", " .. .. ", " .. .. ", " .. . .. ", " .. ... ", " ..... ", " . ", " "}; ================================================ FILE: resources/pixmaps/rec.xpm ================================================ /* XPM */ static const char * rec_xpm[] = { "36 14 10 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #BFD8FF", "# c #9E9E9E", "$ c #82B1FF", "% c #1E56AA", "& c #FFBABA", "* c #FF5454", "= c #B51E1E", " ...... ..........", " ...+..+... .@@@@@@@@.", " ..+++##+++.. . .@$$$$$$%.", " .++++++++++. .. .@$$$$$$%.", "..++++++++++.. .&. .@$$$$$$%.", ".++++++++++++. ......&*. .@$$$$$$%.", ".+..++++++..+. .&&&&&***. .@$$$$$$%.", ".+#.++++++.#+. .*******=. .@$$$$$$%.", ".++++++++++++. ......*=. .@$$$$$$%.", "..+..++++..+.. .=. .@$$$$$$%.", " .+#.+..+.#+. .. .@$$$$$$%.", " ..+++..+++.. . .@$$$$$$%.", " ...++++... .%%%%%%%%.", " ...... .........."}; ================================================ FILE: resources/pixmaps/rec_ex_buss.xpm ================================================ /* XPM */ static const char * rec_ex_buss_xpm[] = { "9 9 22 1", " c None", ". c #0A0D18", "+ c #161C36", "@ c #090D1F", "# c #050917", "$ c #0A1026", "% c #1F2747", "& c #00FF00", "* c #0E1A46", "= c #131931", "- c #0F1B47", "; c #172557", "> c #051551", ", c #051653", "' c #0A1130", ") c #01F04B", "! c #031041", "~ c #020C32", "{ c #010E3F", "] c #000310", "^ c #000518", "/ c #000412", " .+@#$ ", " %&&&&&* ", "=&&&&&&&-", ";&&&&&&&;", ">&&&&&&&;", ",&&&&&&&;", "'&&&&&&)!", " ~&&&&&{ ", " ]^^^/ "}; ================================================ FILE: resources/pixmaps/rec_ex_channel.xpm ================================================ /* XPM */ static const char * rec_ex_channel_xpm[] = { "9 9 21 1", " c None", ". c #0A0D18", "+ c #161C36", "@ c #090D1F", "# c #050917", "$ c #0A1026", "% c #1F2747", "& c #FFFF00", "* c #0E1A46", "= c #131931", "- c #0F1B47", "; c #172557", "> c #051551", ", c #051653", "' c #0A1130", ") c #031041", "! c #020C32", "~ c #010E3F", "{ c #000310", "] c #000518", "^ c #000412", " .+@#$ ", " %&&&&&* ", "=&&&&&&&-", ";&&&&&&&;", ">&&&&&&&;", ",&&&&&&&;", "'&&&&&&&)", " !&&&&&~ ", " {]]]^ "}; ================================================ FILE: resources/pixmaps/rec_ex_normal.xpm ================================================ /* XPM */ static const char * rec_ex_normal_xpm[] = { "9 9 25 1", " c None", ". c #280509", "+ c #4E090E", "@ c #330004", "# c #280005", "$ c #3B0006", "% c #610E16", "& c #D9252F", "* c #FF2037", "= c #5E000A", "- c #48070D", "; c #E8041B", "> c #60000A", ", c #78000D", "' c #DE1B27", ") c #DA222D", "! c #62000B", "~ c #450005", "{ c #4F0008", "] c #3F0006", "^ c #BC0013", "/ c #4B0008", "( c #1C0002", "_ c #240004", ": c #1E0003", " .+@#$ ", " %&*&&&= ", "-&&&&&;&>", ",&&'&&&),", ">&&&&&&&,", "!&&&&&&&,", "~&&&&&&&{", " ]^&&&&/ ", " (___: "}; ================================================ FILE: resources/pixmaps/rec_on.xpm ================================================ /* XPM */ static const char * rec_on_xpm[] = { "36 14 10 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #FFC1BF", "# c #9E9E9E", "$ c #FF0000", "% c #C80300", "& c #FFBABA", "* c #FF5454", "= c #B51E1E", " ...... ..........", " ...+..+... .@@@@@@@@.", " ..+++##+++.. . .@$$$$$$%.", " .++++++++++. .. .@$$$$$$%.", "..++++++++++.. .&. .@$$$$$$%.", ".++++++++++++. ......&*. .@$$$$$$%.", ".+..++++++..+. .&&&&&***. .@$$$$$$%.", ".+#.++++++.#+. .*******=. .@$$$$$$%.", ".++++++++++++. ......*=. .@$$$$$$%.", "..+..++++..+.. .=. .@$$$$$$%.", " .+#.+..+.#+. .. .@$$$$$$%.", " ..+++..+++.. . .@$$$$$$%.", " ...++++... .@%%%%%%%.", " ...... .........."}; ================================================ FILE: resources/pixmaps/redo.xpm ================================================ /* XPM */ static const char * redo_xpm[] = { "16 14 3 1", " c None", ". c #000000", "+ c #65FF72", " . ", " .. ", " .+. ", " ........++. ", " .++++++++++. ", " .+......++. ", " .+. .+. ", " .+. .. ", " .+. . ", " .+. ", " .+. ", " .+........ ", " .++++++++. ", " .......... "}; ================================================ FILE: resources/pixmaps/right.xpm ================================================ /* XPM */ static const char * right_xpm[] = { "16 11 3 1", " c None", ". c #000000", "+ c #7B935E", " ... ", " ..... ", " ....... ", " ......... ", " ........... ", " .........++. ", " .......++.. ", " .....++.. ", " ...++.. ", " .++.. ", " ... "}; ================================================ FILE: resources/pixmaps/route66.xpm ================================================ /* XPM */ static char * route66_xpm[] = { "64 64 99 2", " c None", ". c #1D1D1D", "+ c #000000", "@ c #141414", "# c #FFFFFF", "$ c #020202", "% c #00030B", "& c #282828", "* c #FBFBFB", "= c #FAFAFA", "- c #F7F7F7", "; c #252525", "> c #060606", ", c #FDFDFD", "' c #D1D1D1", ") c #999999", "! c #666666", "~ c #383838", "{ c #323232", "] c #5C5C5C", "^ c #909090", "/ c #C8C8C8", "( c #F2F2F2", "_ c #E6E6E6", ": c #717171", "< c #5A5A5A", "[ c #E4E4E4", "} c #3D3D3D", "| c #424242", "1 c #EEEEEE", "2 c #F8F8F8", "3 c #555555", "4 c #353535", "5 c #C1C1C1", "6 c #E9E9E9", "7 c #E2E2E2", "8 c #9C9C9C", "9 c #949494", "0 c #B2B2B2", "a c #0D0D0D", "b c #DDDDDD", "c c #888888", "d c #161616", "e c #636363", "f c #FCFCFC", "g c #454545", "h c #9A9A9A", "i c #EDEDED", "j c #DCDCDC", "k c #D7D7D7", "l c #D6D6D6", "m c #F4F4F4", "n c #8E8E8E", "o c #EFEFEF", "p c #B1B1B1", "q c #707070", "r c #6C6C6C", "s c #A9A9A9", "t c #EAEAEA", "u c #656565", "v c #D4D4D4", "w c #BDBDBD", "x c #4D4D4D", "y c #262626", "z c #C4C4C4", "A c #2A2A2A", "B c #767676", "C c #ECECEC", "D c #8C8C8C", "E c #2E2E2E", "F c #F9F9F9", "G c #7D7D7D", "H c #C0C0C0", "I c #D8D8D8", "J c #939393", "K c #6A6A6A", "L c #DEDEDE", "M c #F1F1F1", "N c #989898", "O c #CCCCCC", "P c #F0F0F0", "Q c #838383", "R c #EBEBEB", "S c #D5D5D5", "T c #ACACAC", "U c #808080", "V c #FEFEFE", "W c #E5E5E5", "X c #494949", "Y c #CACACA", "Z c #DFDFDF", "` c #919191", " . c #4B4B4B", ".. c #CFCFCF", "+. c #777777", "@. c #C5C5C5", "#. c #686868", "$. c #A0A0A0", "%. c #050505", " ", " ", " . + + + + + + + ", " + + + + + + + + + + + + + + + + + + + + + ", " + + + + + + + + + + + @ + + + + + + # # + + + + + + + + + + + + + # + + + ", " + + + # # # # + + + + + + + + + + + + + + # # # # # # # # $ + + + + + + + + + + + # # # # # . + + ", " + + + # # # # # # # # # + + + + + + # # # # # # # # # # # # # # # # + + + + + # # # # # # # # # @ + + + ", " + + + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + + + ", " + + + # # # # # # # # # # # + + + + + + + + # # # + + + + + + + # # # + + + + + + + + # # # # # # # # # # + + + ", " + + + # # # # # # # # # # # # + + % % % % % + # # # % % % % % % + # # # % % % % % % % + # # # # # # # # # # # + + + ", " + + + # # # # # # # # # # # # # + + % % % % % + # # # % % % % % % + # # # % % % % % % % + # # # # # # # # # # # # + + + ", " + + + # # # # # # # # # # # # # # + % % # # # # # # # # % + + # # # # # # # % % + # # % % + # # # # # # # # # # # # + + + ", " + + + # # # # # # # # # # # # # # + % % # # # # # # # # % + + # # # # # # # % % + # # % % + # # # # # # # # # # # # + + ", " + + & # # # # # # # # # # # # # + % % % % % % + # # # % % + % % % # # # # % % + # # % % + # # # # # # # # # # # # + + ", " + + * # # # # # # # # # # # # + % % % % % % + # # # % % + % % % # # # # % % + # # % % + # # # # # # # # # # # + + ", " + + # # # # # # # # # # # # # # # # # % % + # # # % % + # # # = # # # % % + # # % % + # # # # # # # # # # * + + ", " + + + # # # # # # # # # # # # # # # # % % + # # # % % + # # # # # # # % % + # - % % + # # # # # # # # # # + + ", " + + # # # # # # # # # # # + % % % % % % + # # # % % + % % % + # # # % % % % % % % + # # # # # # # # # + + + ", " ; + + # # # # # # # # # # + % % % % % % + # # # % % % % % % + # # # % % % % % % % + + + # # # # # # # + + ", " + + # # # # # # # # # # + + + + + + + + # # = + + + + + + + # # # + + + + + + % + + + # # # # # # # + + ", " > + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + + # # # # # # # + + ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + # # # + + + + + + + + ", " + + % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % + + ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", " + + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + ", " + + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + ", " + + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + ", " + + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + ", " + + + # # # # # # # # # , ' ) ! ~ { ] ^ / = # # # # # # # # # , ' ) ! ~ { ] ^ / = # # # # # # # # + + ", " + + ( # # # # # # # # _ : + + + + + + + + < [ # # # # # # # _ : + + + + + + + + < [ # # # # # # # + + ", " + + + # # # # # # # # [ } + + + + + + + + + + | 1 # # # # # [ } + + + + + + + + + + | 1 # # # # # # # + + ", " + + # # # # # # # # 2 3 + + + 4 5 6 7 8 + + + + 9 # # # # 2 3 + + + 4 5 6 7 8 + + + + 9 # # # # # # # + + ", " + + + # # # # # # # # 0 + + + a b # # # # c + + d e - # # # 0 + + + a b # # # # c + + d e - # # # # # # # + + ", " + + # # # # # # # # f g + + + h # # # # # i j 1 , # # # # f g + + + h # # # # # i j 1 , # # # # # # # # # + + + ", " + + # # # # # # # # # k + + + + l # # # # # # # # # # # # # k + + + + l # # # # # # # # # # # # # # # # # # # + + ", " + + # # # # # # # # # 0 + + + + m # # # # # # # # # # # # # 0 + + + + m # # # # # # # # # # # # # # # # # # # # + + ", " + + # # # # # # # # # # n + + + { # o p q } ~ r s t # # # # # n + + + { # o p q } ~ r s t # # # # # # # # # # # # + + ", " + + # # # # # # # # # # u + + + 3 v 4 + + + + + + a w # # # # u + + + 3 v 4 + + + + + + a w # # # # # # # # # # # + + + ", " + + # # # # # # # # # # x + + + { y + + + + + + + + + z # # # x + + + { y + + + + + + + + + z # # # # # # # # # # # + + ", " > + # # # # # # # # # # # A + + + + + B k C b D + + + + E m # # A + + + + + B k C b D + + + + E m # # # # # # # # # m + + ", " + + # # # # # # # # # # # A + + + + < F # # # , G + + + + H # # A + + + + < F # # # , G + + + + H # # # # # # # # # # + + ", " + + + # # # # # # # # # # # x + + + + z # # # # # I + + + + J # # x + + + + z # # # # # I + + + + J # # # # # # # # # # + + + ", " + + + # # # # # # # # # # # K + + + + L # # # # # M + + + + B # # K + + + + L # # # # # M + + + + B # # # # # # # # # # + + + ", " + + + # # # # # # # # # # # N + + + + L # # # # # * + + + + r # # N + + + + L # # # # # * + + + + r # # # # # # # # # # + + + ", " + + + # # # # # # # # # # # w + + + + O # # # # # P + + + + Q # # w + + + + O # # # # # P + + + + Q # # # # # # # # # # + + + ", " + + + # # # # # # # # # # # R + + + + ^ # # # # # S + + + + T # # R + + + + ^ # # # # # S + + + + T # # # # # # # # # # + + + ", " + + # # # # # # # # # # # # U + + + + [ # # # V G + + + + j # # # U + + + + [ # # # V G + + + + j # # # # # # # # # m + + ", " + + # # # # # # # # # # # # W d + + + X Y R Z ` + + + + G # # # # W d + + + X Y R Z ` + + + + G # # # # # # # # # # # + + ", " + + # # # # # # # # # # # # z + + + + + + + + + + + .1 # # # # # z + + + + + + + + + + + .1 # # # # # # # # # # + + + ", " + + # # # # # # # # # # # # # ..X + + + + + + + + +.1 # # # # # # # ..X + + + + + + + + +.1 # # # # # # # # # # # + + ", " + + # # # # # # # # # # # # # - @.D ] E } #.$.k V # # # # # # # # # - @.D ] E } #.$.k V # # # # # # # # # # # + + + ", " + + + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + + ", " + + + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + + ", " + + + ( # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # %.+ + ", " + + + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + + %. ", " + + + + + + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + + + + + + + ", " + + + + + + + + + + + # # # # # # # # # # # # # # # # # # # # + + + + + + + + + + + + ", " + + + + + + + + + + + + # # # # # # # # # # # # # + + + + + + + + + + ", " + + + + %.+ + # # # # # # # # + + + + + + ", " + + + + + # m # # # + + + + + ", " $ + + + + + + + + + ", " + + + + + + ", " ", " "}; ================================================ FILE: resources/pixmaps/route66rwb-32x32.xpm ================================================ /* XPM */ static const char * route66rwb_32x32_xpm[] = { "32 32 394 2", " c None", ". c #373637", "+ c #000303", "@ c #0E1818", "# c #121111", "$ c #0A1111", "% c #191C1C", "& c #152020", "* c #414040", "= c #362626", "- c #CB1C1C", "; c #4B2323", "> c #000E0E", ", c #282D2D", "' c #000000", ") c #191F1F", "! c #000D0C", "~ c #392020", "{ c #B82929", "] c #F31A1A", "^ c #921313", "/ c #1D2222", "( c #091313", "_ c #0C1616", ": c #000708", "< c #2F2C2B", "[ c #911F20", "} c #E23434", "| c #181414", "1 c #252627", "2 c #431314", "3 c #FA1717", "4 c #F10D0D", "5 c #F60A0A", "6 c #FD2222", "7 c #C92323", "8 c #B23635", "9 c #B33232", "0 c #BF1C1C", "a c #FB1C1C", "b c #F80A0A", "c c #EA0606", "d c #EB0707", "e c #FB0505", "f c #F12E2E", "g c #BB1F1F", "h c #BB3F3F", "i c #C30E0F", "j c #F72D2C", "k c #F10C0C", "l c #ED2222", "m c #1B1818", "n c #4F4D4D", "o c #212425", "p c #641D1D", "q c #FA1010", "r c #F20D0D", "s c #CB3333", "t c #DDCDCD", "u c #DCC5C5", "v c #DACCCC", "w c #EE2D2D", "x c #CC1717", "y c #DFD0D0", "z c #DBCBCB", "A c #EF2424", "B c #CD3737", "C c #DECACA", "D c #DACBCC", "E c #ED3535", "F c #F00C0C", "G c #F31616", "H c #201213", "I c #2F2F2F", "J c #060909", "K c #983434", "L c #F70B0B", "M c #C93737", "N c #F6FFFF", "O c #F78989", "P c #F78F8F", "Q c #F22222", "R c #C51515", "S c #FFFFFF", "T c #F19595", "U c #F68E8E", "V c #F21D1D", "W c #CC3C3C", "X c #D78888", "Y c #F5F9F9", "Z c #F43E3E", "` c #F00D0D", " . c #F71111", ".. c #351919", "+. c #313030", "@. c #323030", "#. c #522525", "$. c #C93738", "%. c #F9FFFF", "&. c #D6A8A8", "*. c #D5ADAE", "=. c #ED2828", "-. c #D6ADAD", ";. c #D7ADAE", ">. c #EE2020", ",. c #CD3C3D", "'. c #C90F0F", "). c #F0F0F0", "!. c #F43F3F", "~. c #E91919", "{. c #171616", "]. c #0B1010", "^. c #B72525", "/. c #DD2121", "(. c #FB8B8B", "_. c #FA7D7D", ":. c #E9FCFC", "<. c #F33434", "[. c #F1BBBC", "}. c #F4BABA", "|. c #F12222", "1. c #C60202", "2. c #EFF0F0", "3. c #F60707", "4. c #632829", "5. c #2E2E2F", "6. c #180E0E", "7. c #FA0E0E", "8. c #D62727", "9. c #FEB4B4", "0. c #FBA7A7", "a. c #F0FFFF", "b. c #C41515", "c. c #F8AFAF", "d. c #FBA9A9", "e. c #F21F1F", "f. c #EDAEAE", "g. c #FAFFFF", "h. c #E81F1F", "i. c #0B1414", "j. c #182121", "k. c #AD1D1D", "l. c #F71010", "m. c #F61111", "n. c #D64142", "o. c #F6F7F7", "p. c #F2EDED", "q. c #F3F7F7", "r. c #F53535", "s. c #D52323", "t. c #F7FAFA", "u. c #F3F3F3", "v. c #F52B2B", "w. c #D84646", "x. c #F5F2F2", "y. c #F4EFEF", "z. c #F94444", "A. c #F90F0F", "B. c #842020", "C. c #141414", "D. c #170B0A", "E. c #422C1B", "F. c #412B1B", "G. c #412B1A", "H. c #442615", "I. c #431706", "J. c #431807", "K. c #412917", "L. c #432918", "M. c #421605", "N. c #431806", "O. c #412918", "P. c #432614", "Q. c #421806", "R. c #431A09", "S. c #43402F", "T. c #403B29", "U. c #422C1A", "V. c #412C1B", "W. c #422C1C", "X. c #090202", "Y. c #151421", "Z. c #2423E6", "`. c #2322E3", " + c #2222E3", ".+ c #2323E4", "++ c #1C1CDC", "@+ c #2221E3", "#+ c #1716EA", "$+ c #1313E8", "%+ c #1413E9", "&+ c #2221E4", "*+ c #3635EA", "=+ c #171618", "-+ c #2C2B13", ";+ c #2222F0", ">+ c #1D1DEF", ",+ c #1515EC", "'+ c #A7A7DF", ")+ c #CECDE0", "!+ c #1919ED", "~+ c #0A090C", "{+ c #1D1C29", "]+ c #1F1FF2", "^+ c #1616F0", "/+ c #9595D7", "(+ c #4D4CCD", "_+ c #1C1CF0", ":+ c #202023", "<+ c #141456", "[+ c #1E1EF4", "}+ c #1616F1", "|+ c #9292DB", "1+ c #9A9ADD", "2+ c #1414F0", "3+ c #111014", "4+ c #181814", "5+ c #3030D2", "6+ c #1C1CEF", "7+ c #201FDB", "8+ c #F9F9F4", "9+ c #BBBBDF", "0+ c #1313F0", "a+ c #2C2E74", "b+ c #2E2E2E", "c+ c #202050", "d+ c #1919F9", "e+ c #B0AFE2", "f+ c #FEFDF9", "g+ c #2323E8", "h+ c #1717F0", "i+ c #1A1AF0", "j+ c #2F2FF2", "k+ c #100F0B", "l+ c #050500", "m+ c #2D2DEF", "n+ c #1F1FE9", "o+ c #F2F2EB", "p+ c #CCCCE2", "q+ c #9E9EEE", "r+ c #A4A4EF", "s+ c #9090EB", "t+ c #1818E2", "u+ c #1E1EEF", "v+ c #1B1BEF", "w+ c #3838A2", "x+ c #2A2961", "y+ c #1919F2", "z+ c #5B5BE2", "A+ c #FEFEF5", "B+ c #3333E0", "C+ c #1E1EF5", "D+ c #1A1A2D", "E+ c #3D3DD2", "F+ c #1919EF", "G+ c #B0B0EA", "H+ c #FFFFFD", "I+ c #E6E6F1", "J+ c #DDDDEC", "K+ c #FFFFF8", "L+ c #FEFEFF", "M+ c #F1F1EA", "N+ c #2222E8", "O+ c #1213F1", "P+ c #1A1AF1", "Q+ c #34338B", "R+ c #1C1A16", "S+ c #181812", "T+ c #1D1DEE", "U+ c #1A1AF4", "V+ c #A9A9CD", "W+ c #4B4BE0", "X+ c #1B1BE8", "Y+ c #1818E5", "Z+ c #4242DD", "`+ c #EBEBEF", " @ c #8281E1", ".@ c #413CD4", "+@ c #1C1CEE", "@@ c #2425BE", "#@ c #2B2B22", "$@ c #161629", "%@ c #1E1EF0", "&@ c #1414EE", "*@ c #DBDBFF", "=@ c #B0B1E2", "-@ c #1616F3", ";@ c #1314F0", ">@ c #C1BFF4", ",@ c #CCCCE6", "'@ c #4A45D2", ")@ c #3736D6", "!@ c #1A1A10", "~@ c #1C1D2C", "{@ c #1919F3", "]@ c #AFAFD7", "^@ c #B8B8DB", "/@ c #0E0EF6", "(@ c #1010EE", "_@ c #C6C6ED", ":@ c #C6C6E6", "<@ c #1D19D4", "[@ c #2626C9", "}@ c #242419", "|@ c #161608", "1@ c #2727ED", "2@ c #7878D2", "3@ c #8E8DDC", "4@ c #2626DB", "5@ c #2021D8", "6@ c #8F8FE3", "7@ c #FCFBF9", "8@ c #6E6DD8", "9@ c #1919F0", "0@ c #31329D", "a@ c #2D2D29", "b@ c #1D1C1E", "c@ c #242479", "d@ c #1B1BF1", "e@ c #1E1EEC", "f@ c #E9E8E9", "g@ c #C4C4E4", "h@ c #2121F6", "i@ c #20202F", "j@ c #090900", "k@ c #2626C8", "l@ c #1A1AF3", "m@ c #1D1DF0", "n@ c #2D2DE8", "o@ c #C9C9ED", "p@ c #FFFFFA", "q@ c #F5F4EF", "r@ c #9595E1", "s@ c #1414EB", "t@ c #1C1CF8", "u@ c #2C2C81", "v@ c #1E1E1B", "w@ c #0F0F0C", "x@ c #3D3CA0", "y@ c #1919FE", "z@ c #1E1EEE", "A@ c #1717EC", "B@ c #3333EC", "C@ c #5E5EE2", "D@ c #5F5FEB", "E@ c #201FE2", "F@ c #1919F1", "G@ c #2A2A63", "H@ c #14150E", "I@ c #1B1A19", "J@ c #282826", "K@ c #262597", "L@ c #2E2ED0", "M@ c #4747F1", "N@ c #1B1BF6", "O@ c #1D1DF5", "P@ c #1C1CF2", "Q@ c #1C1CF1", "R@ c #1D1DF4", "S@ c #1C1CF5", "T@ c #2C2CF5", "U@ c #2423C5", "V@ c #3131AA", "W@ c #25256C", "X@ c #141408", "Y@ c #2E2D2D", "Z@ c #161717", "`@ c #27251B", " # c #212006", ".# c #181725", "+# c #34336B", "@# c #2323B7", "## c #2929F4", "$# c #1B1BF3", "%# c #1F1FF9", "&# c #2E2EDF", "*# c #292A81", "=# c #19193B", "-# c #262615", ";# c #0C0C00", "># c #222114", ",# c #201F13", "'# c #1A1A1B", ")# c #191978", "!# c #2D2DF4", "~# c #1818FD", "{# c #2A2AC5", "]# c #161646", "^# c #0D0E08", "/# c #282726", "(# c #1D1D1B", "_# c #0F0E06", ":# c #1F1F26", "<# c #12110E", " ", " . + @ # $ % & ", " * = - ; > , ' ' ) ! ~ { ] ^ / ( ' ' _ : < [ } | ", " 1 2 3 4 5 6 7 8 9 0 a b c d d e f g h i j e k k l m n ", " o p q 4 4 4 r s t u v w x y u z A B C u D E 4 4 4 F G H I ", " J K L 4 4 4 4 r M N O P Q R S T U V W S X Y Z 4 4 4 4 ` ...+.", " @.#.b 4 4 4 4 r $.%.&.*.=.R S -.;.>.,.S '.).!.4 4 4 4 4 ~.{. ", " ].^.k 4 4 4 4 /.(._.:.<.R S [.}.|.W S 1.2.!.4 4 4 4 3.4.5. ", " 6.7.4 4 4 r 8.9.0.a.<.b.S c.d.e.W S f.g.!.4 4 4 4 h.i. ", " j.k.l.m.m. .n.o.p.q.r.s.t.p.u.v.w.x.y.S z.m.m.m.A.B.C. ", " D.E.F.G.F.H.I.J.I.K.L.M.N.I.O.P.Q.R.S.T.E.U.V.W.X. ", " Y.Z.`.`. +`..+++ +`. +`.`.`.`. +`.@+#+$+%+&+`.*+=+ ", " -+;+>+>+>+,+'+)+!+>+>+>+>+>+>+>+,+'+)+!+>+>+>+>+~+ ", " {+]+>+>+^+/+S S (+_+>+>+>+>+>+^+/+S S (+_+>+>+>+:+ ", " <+[+>+}+|+S S 1+2+>+>+>+>+>+}+|+S S 1+2+>+>+>+>+3+ ", " 4+5+6+>+7+8+S 9+0+>+>+>+>+>+>+7+8+S 9+0+>+>+>+>+>+a+b+ ", " c+d+>+}+e+S f+g+h+i+>+>+>+>+}+e+S f+g+h+i+>+>+>+>+j+k+ ", " l+m+>+>+n+o+S p+q+r+s+t+u+>+>+n+o+S p+q+r+s+t+u+>+>+v+w+# ", " x+y+>+>+z+S S S S S S A+B+>+>+z+S S S S S S A+B+>+>+>+C+D+ ", " ' E+>+>+F+G+S S H+I+J+K+L+M+N+O+G+S S H+I+J+K+L+M+N+O+>+P+Q+R+", " S+T+>+>+U+V+S S W+X+Y+Z+`+S @.@V+S S W+X+Y+Z+`+S @.@>++@@@#@", " $@%@>+>+&@*@S =@-@>+>+;@>@S ,@'@*@S =@-@>+>+;@>@S ,@'@>+>+)@!@", " ~@%@>+>+{@]@S ^@/@>+>+(@_@S :@<@]@S ^@/@>+>+(@_@S :@<@>+T+[@}@", " |@1@>+>+6+2@S H+3@4@5@6@7@S 8@9@2@S H+3@4@5@6@7@S 8@9@>+i+0@a@", " b@c@d@>+>+e@f@S S S S S S g@d@>+e@f@S S S S S S g@d@>+>+h@i@ ", " j@k@l@>+m@n@o@p@S S q@r@s@>+>+m@n@o@p@S S q@r@s@>+>+t@u@v@ ", " w@x@y@z@>+A@B@C@D@E@F@>+>+>+z@>+A@B@C@D@E@F@>+>+>+G@H@ ", " I@J@K@L@M@N@O@P@>+>+>+>+>+>+>+>+Q@R@S@T@U@V@W@X@Y@ ", " Z@`@' #.#+#@###$#>+>+T+%#&#*#=#-#;#>#' ", " ,#'#)#!#~#{#]#^#/# ", " (#_#:#<# ", " "}; ================================================ FILE: resources/pixmaps/route66rwb-64x64.xpm ================================================ /* XPM */ static const char * route66rwb_66x66_xpm[] = { "64 64 1035 2", " c None", ". c #5B5C5C", "+ c #000000", "@ c #2B2B2B", "# c #000404", "$ c #1B1D1D", "% c #484748", "& c #343634", "* c #343334", "= c #3B3B3D", "- c #0D0E0E", "; c #040B0B", "> c #000606", ", c #1A1919", "' c #3A3A3A", ") c #575A5A", "! c #141414", "~ c #090A0A", "{ c #061313", "] c #411C1C", "^ c #130F0F", "/ c #030B0B", "( c #0C0B0B", "_ c #1C1C1C", ": c #3D3C3C", "< c #161616", "[ c #000304", "} c #152121", "| c #000505", "1 c #222222", "2 c #4E4E4E", "3 c #050405", "4 c #1C2929", "5 c #9E3030", "6 c #602A2A", "7 c #041717", "8 c #000101", "9 c #080808", "0 c #1E1E1E", "a c #555252", "b c #6E6C6D", "c c #615E60", "d c #010101", "e c #090D0D", "f c #454646", "g c #9D2A2A", "h c #DC1111", "i c #CB3535", "j c #6B1515", "k c #091C1C", "l c #080707", "m c #111313", "n c #434141", "o c #515151", "p c #101010", "q c #030404", "r c #122527", "s c #641F1F", "t c #CB4949", "u c #874747", "v c #000A0A", "w c #494949", "x c #1D2E2E", "y c #D53939", "z c #F10D0D", "A c #EC0D0D", "B c #D23030", "C c #7A3B3B", "D c #252828", "E c #000303", "F c #020303", "G c #010505", "H c #1F2B2B", "I c #6D4545", "J c #C03030", "K c #EB1010", "L c #F00F0F", "M c #F20C0C", "N c #F21010", "O c #EC0E0E", "P c #E11212", "Q c #914848", "R c #1E3131", "S c #060B0B", "T c #0E1416", "U c #4A4444", "V c #9F5858", "W c #DF2828", "X c #EB0E0E", "Y c #EC1616", "Z c #8D2B2B", "` c #020E0E", " . c #414141", ".. c #090E10", "+. c #DC3F3F", "@. c #F10C0C", "#. c #F40C0C", "$. c #F80808", "%. c #D85F5F", "&. c #9F2C2C", "*. c #774D4D", "=. c #71696A", "-. c #645E5C", ";. c #675A5C", ">. c #6C6161", ",. c #795E5E", "'. c #921313", "). c #D53F3F", "!. c #F51515", "~. c #F50B0B", "{. c #F30B0B", "]. c #F70707", "^. c #EB4949", "/. c #B84040", "(. c #820606", "_. c #797575", ":. c #776E6E", "<. c #7B7778", "[. c #7E2728", "}. c #9A1F1F", "|. c #CA5351", "1. c #F33737", "2. c #F60808", "3. c #F51A1A", "4. c #893738", "5. c #070B0B", "6. c #161414", "7. c #6B6B6B", "8. c #302C2E", "9. c #502A2A", "0. c #E11E1E", "a. c #F40909", "b. c #F30707", "c. c #F70B0B", "d. c #E80808", "e. c #EA0909", "f. c #E90909", "g. c #E60E0E", "h. c #EC0A0A", "i. c #EB1111", "j. c #DF0E0E", "k. c #E00E0E", "l. c #EB0D0D", "m. c #F30909", "n. c #EA0E0E", "o. c #E80F0F", "p. c #E90707", "q. c #E90808", "r. c #E80D0C", "s. c #E60D0D", "t. c #EB0909", "u. c #F20A0A", "v. c #F10A0A", "w. c #FF2222", "x. c #8B3838", "y. c #7F7F7F", "z. c #363536", "A. c #592F2F", "B. c #E42C2C", "C. c #F60909", "D. c #EA1010", "E. c #B84646", "F. c #BC8181", "G. c #BC7C7C", "H. c #BD7C7C", "I. c #B98181", "J. c #DA3D3D", "K. c #F30C0C", "L. c #B92D2D", "M. c #BC8282", "N. c #BD7979", "O. c #DE3434", "P. c #AC5252", "Q. c #BB7C7C", "R. c #BA8080", "S. c #D64545", "T. c #F30A0A", "U. c #F10B0B", "V. c #FF0606", "W. c #A33E3E", "X. c #071516", "Y. c #504D4E", "Z. c #151414", "`. c #6C191A", " + c #F22726", ".+ c #F50808", "++ c #E50F0F", "@+ c #B97878", "#+ c #FFFFFF", "$+ c #FA7B7B", "%+ c #F00606", "&+ c #A03939", "*+ c #F86666", "=+ c #F00404", "-+ c #F60101", ";+ c #AE8E8E", ">+ c #FB8D8D", ",+ c #F00D0D", "'+ c #FB0808", ")+ c #B42527", "!+ c #281E1F", "~+ c #000201", "{+ c #3B3B3B", "]+ c #100F0F", "^+ c #010D0D", "/+ c #903434", "(+ c #F21112", "_+ c #F20B0B", ":+ c #E50E0E", "<+ c #B77272", "[+ c #F1EFEF", "}+ c #F5FFFF", "|+ c #F3FFFF", "1+ c #F4FFFF", "2+ c #F27777", "3+ c #F10606", "4+ c #A13737", "5+ c #F8F6F6", "6+ c #F2FAFA", "7+ c #F2FCFC", "8+ c #F26363", "9+ c #F10505", "0+ c #F60202", "a+ c #AC8787", "b+ c #FDFDFD", "c+ c #EBF5F5", "d+ c #E7F4F4", "e+ c #F3F4F4", "f+ c #F88787", "g+ c #C72727", "h+ c #3D3434", "i+ c #404040", "j+ c #121212", "k+ c #B05B5B", "l+ c #F01313", "m+ c #B77171", "n+ c #DCEBEB", "o+ c #F02C2C", "p+ c #F31C1C", "q+ c #F41B1B", "r+ c #F41C1C", "s+ c #F21313", "t+ c #A03636", "u+ c #ECF7F7", "v+ c #E46161", "w+ c #F41717", "x+ c #F31B1B", "y+ c #F21212", "z+ c #F70202", "A+ c #AB8686", "B+ c #FAF1F1", "C+ c #D34545", "D+ c #C81E1E", "E+ c #DEC0C0", "F+ c #F50D0D", "G+ c #AB2020", "H+ c #081212", "I+ c #1B1B1B", "J+ c #5E5C5C", "K+ c #182A2A", "L+ c #E81111", "M+ c #D2D8D8", "N+ c #B75555", "O+ c #B54949", "P+ c #B74B4B", "Q+ c #B34A4D", "R+ c #D72627", "S+ c #E7EBEB", "T+ c #B87575", "U+ c #B54646", "V+ c #B84749", "W+ c #DB2121", "X+ c #AC8686", "Y+ c #FAF0F0", "Z+ c #D23B3B", "`+ c #C71414", " @ c #DDBDBD", ".@ c #E51616", "+@ c #5D2222", "@@ c #6D4F4F", "#@ c #F50E0F", "$@ c #E50E0F", "%@ c #B87274", "&@ c #F97B7B", "*@ c #A13638", "=@ c #AD8688", "-@ c #D43A3A", ";@ c #C70F0F", ">@ c #A63232", ",@ c #000707", "'@ c #3C3B3C", ")@ c #191919", "!@ c #AC3234", "~@ c #F90808", "{@ c #E70E0E", "]@ c #BF6262", "^@ c #FFE5E5", "/@ c #FDDADA", "(@ c #F3F5F5", "_@ c #F77575", ":@ c #FCFBFB", "<@ c #F9FFFF", "[@ c #FAFFFF", "}@ c #F9FCFC", "|@ c #F46363", "1@ c #D23938", "2@ c #E62D2D", "3@ c #2A1B1C", "4@ c #101112", "5@ c #211D1D", "6@ c #EE2626", "7@ c #EF0D0D", "8@ c #E71D1D", "9@ c #F43636", "0@ c #F33434", "a@ c #F23737", "b@ c #BDC8C8", "c@ c #A13636", "d@ c #EEF4F5", "e@ c #E59293", "f@ c #F06767", "g@ c #F06A69", "h@ c #EF6666", "i@ c #F02D2D", "j@ c #D12F30", "k@ c #C50404", "l@ c #DCBBBB", "m@ c #F70808", "n@ c #944A4A", "o@ c #393939", "p@ c #752E2E", "q@ c #FF0909", "r@ c #ED0D0D", "s@ c #DE2D2D", "t@ c #F75F5F", "u@ c #F65B5B", "v@ c #F45D5D", "w@ c #C9D3D3", "x@ c #9F3636", "y@ c #F3FAFB", "z@ c #ED8D8D", "A@ c #F65555", "B@ c #F65858", "C@ c #F65656", "D@ c #F32828", "E@ c #FCF6F6", "F@ c #E37C7C", "G@ c #D95D5D", "H@ c #E8D3D3", "I@ c #F02323", "J@ c #1C1212", "K@ c #080A0A", "L@ c #121111", "M@ c #250A0A", "N@ c #D01A1A", "O@ c #B96D6D", "P@ c #FEF5F5", "Q@ c #FCFCFC", "R@ c #FEFFFF", "S@ c #FEF8F8", "T@ c #FEEEEE", "U@ c #F65E5E", "V@ c #FFFEFE", "W@ c #FDF7F7", "X@ c #FDF5F5", "Y@ c #FEFCFC", "Z@ c #F90A0A", "`@ c #8C3638", " # c #000202", ".# c #4A4848", "+# c #5D5D5D", "@# c #801A1A", "## c #F90D0D", "$# c #B87273", "%# c #FEFEFE", "&# c #F40C0B", "*# c #A4383A", "=# c #FEF7F7", "-# c #F66161", ";# c #AF898A", "># c #FCFDFD", ",# c #FE0B0B", "'# c #453232", ")# c #0A0D0D", "!# c #DA2828", "~# c #EE1818", "{# c #EE1919", "]# c #ED1919", "^# c #E81F1F", "/# c #C97B7B", "(# c #DCCFCF", "_# c #DBC8C8", ":# c #DCC8C8", "<# c #DCCFD0", "[# c #E56464", "}# c #EE1414", "|# c #EF1717", "1# c #C55858", "2# c #DDD0D0", "3# c #DDC3C3", "4# c #E65555", "5# c #EE1313", "6# c #F10E0E", "7# c #C18F8F", "8# c #DBC7C7", "9# c #E2D3D3", "0# c #F6F4F4", "a# c #FBFFFF", "b# c #F49494", "c# c #ED1212", "d# c #F31717", "e# c #BE3838", "f# c #070000", "g# c #1C1B1C", "h# c #242826", "i# c #110304", "j# c #672D2D", "k# c #7E3B3B", "l# c #803A3A", "m# c #7E3A3B", "n# c #7E3A3C", "o# c #7E3A3A", "p# c #803A3C", "q# c #7D3A3A", "r# c #7F3B3A", "s# c #813332", "t# c #7F2A2A", "u# c #7E2A2A", "v# c #7F2B2B", "w# c #802B2B", "x# c #812A2B", "y# c #7F3434", "z# c #7C3B3B", "A# c #823535", "B# c #7F292A", "C# c #7F3535", "D# c #7F3B3B", "E# c #7F3A3B", "F# c #823030", "G# c #7E2B2B", "H# c #813D3E", "I# c #827676", "J# c #828483", "K# c #7E8787", "L# c #7E3636", "M# c #803C3C", "N# c #7E3C3C", "O# c #833D3D", "P# c #4E2121", "Q# c #020000", "R# c #3F3C3F", "S# c #060B07", "T# c #12190C", "U# c #131A0C", "V# c #141A0C", "W# c #14190C", "X# c #171C0F", "Y# c #161A0D", "Z# c #151A0C", "`# c #13180A", " $ c #141306", ".$ c #141205", "+$ c #131305", "@$ c #13190C", "#$ c #15190C", "$$ c #10160B", "%$ c #12110C", "&$ c #181848", "*$ c #2626B6", "=$ c #2525BF", "-$ c #2625BF", ";$ c #2626BF", ">$ c #2524C1", ",$ c #2624C3", "'$ c #2424C1", ")$ c #4343B7", "!$ c #121223", "~$ c #282626", "{$ c #282845", "]$ c #2222E1", "^$ c #1D1DF1", "/$ c #1A1AF2", "($ c #3434EC", "_$ c #2223CE", ":$ c #1C1CF3", "<$ c #1A1AF3", "[$ c #4242E3", "}$ c #16162C", "|$ c #090907", "1$ c #4B4A39", "2$ c #2A2ADD", "3$ c #1D1DEF", "4$ c #1C1CF0", "5$ c #2121E0", "6$ c #BFBFE5", "7$ c #DEDCD3", "8$ c #2E2ECD", "9$ c #1C1CF1", "0$ c #1B1BF1", "a$ c #4343E2", "b$ c #46453A", "c$ c #2929DD", "d$ c #1C1CEF", "e$ c #1F1FD8", "f$ c #AAAAC8", "g$ c #FFFFF9", "h$ c #F5F4ED", "i$ c #3838D9", "j$ c #1919F1", "k$ c #1A1BF1", "l$ c #4443E2", "m$ c #17162C", "n$ c #11100C", "o$ c #212147", "p$ c #2424DF", "q$ c #8B8BC8", "r$ c #FFFFF4", "s$ c #92929C", "t$ c #4343E3", "u$ c #15152B", "v$ c #171817", "w$ c #14130F", "x$ c #2F2F6B", "y$ c #2424E8", "z$ c #1919EF", "A$ c #2828E9", "B$ c #A2A2D9", "C$ c #FFFFF3", "D$ c #EDEDE8", "E$ c #7271D4", "F$ c #2221E9", "G$ c #4242E1", "H$ c #121329", "I$ c #413F40", "J$ c #1E1C1C", "K$ c #050500", "L$ c #2C2C9F", "M$ c #2121F1", "N$ c #1B1BEF", "O$ c #3533F3", "P$ c #989AC4", "Q$ c #FFFFFC", "R$ c #7676BD", "S$ c #1414E7", "T$ c #1B1BF0", "U$ c #4040F0", "V$ c #0E0E23", "W$ c #0F0D0D", "X$ c #1B1B19", "Y$ c #0B0B15", "Z$ c #2020CB", "`$ c #1414F2", " % c #7777CF", ".% c #F9FAF8", "+% c #C8C8E2", "@% c #1F1FE9", "#% c #1D1DF0", "$% c #1E1EFB", "%% c #383665", "&% c #030301", "*% c #4B4B4B", "=% c #2B2B70", "-% c #2B2BF2", ";% c #2828E0", ">% c #C2C2D4", ",% c #AEAEC9", "'% c #2525EA", ")% c #1A19F6", "!% c #4A4DAB", "~% c #040407", "{% c #1E1D1E", "]% c #141420", "^% c #3A39C6", "/% c #1A1AF0", "(% c #5F5EBA", "_% c #F4F4F7", ":% c #E3E2DE", "<% c #3F3FE6", "[% c #3434F4", "}% c #272739", "|% c #373637", "1% c #30306A", "2% c #1919FF", "3% c #4545D6", "4% c #E5E5F1", "5% c #F6F5F2", "6% c #7473D9", "7% c #1B1BEC", "8% c #1818F6", "9% c #4B4BAF", "0% c #090802", "a% c #212121", "b% c #090909", "c% c #27272B", "d% c #2D2DE5", "e% c #1B1BEE", "f% c #7E7DD9", "g% c #F9F9F0", "h% c #FFFFFE", "i% c #D2D1F0", "j% c #2E2EEC", "k% c #1414F0", "l% c #1616EE", "m% c #3131F7", "n% c #2C2B53", "o% c #363433", "p% c #0D0D0D", "q% c #504EA0", "r% c #1818F8", "s% c #3434E6", "t% c #BAB9DE", "u% c #FFFFFB", "v% c #F6F6FA", "w% c #8989D3", "x% c #3B3BD2", "y% c #5B5BEC", "z% c #6565F6", "A% c #4242D2", "B% c #4141D8", "C% c #2424EF", "D% c #1515F2", "E% c #2525D4", "F% c #262613", "G% c #171618", "H% c #23221A", "I% c #2222F2", "J% c #4242DF", "K% c #E5E5DA", "L% c #F6F6F7", "M% c #CBCBD4", "N% c #EEEEFB", "O% c #F0F0FD", "P% c #F2F2FE", "Q% c #ECECF8", "R% c #ECEDFA", "S% c #DCDBEC", "T% c #5B5AC7", "U% c #1313E9", "V% c #1F1FEF", "W% c #5A5A97", "X% c #1D1D1E", "Y% c #7572A3", "Z% c #1919F2", "`% c #2525ED", " & c #A1A1E5", ".& c #AFAFCE", "+& c #2323EC", "@& c #2323D9", "#& c #202034", "$& c #242220", "%& c #151515", "&& c #141404", "*& c #1818EC", "=& c #2323EB", "-& c #9B9AD5", ";& c #A3A3C0", ">& c #2626ED", ",& c #2424F2", "'& c #2E2E95", ")& c #6E6E6E", "!& c #54535E", "~& c #1F1FED", "{& c #3D3CEB", "]& c #EFEED9", "^& c #FEFEFF", "/& c #A6A6C9", "(& c #2121E8", "_& c #1B1CF2", ":& c #3130C3", "<& c #1F1E21", "[& c #2F2D2C", "}& c #252626", "|& c #4444C4", "1& c #7777E6", "2& c #F2F1EE", "3& c #C5C4E4", "4& c #B7B8E4", "5& c #BDBDE9", "6& c #A2A2CD", "7& c #FBFBEB", "8& c #F5F5F0", "9& c #E3E3E2", "0& c #6666D2", "a& c #1515F3", "b& c #1A1AF1", "c& c #3E3EED", "d& c #2D2D53", "e& c #050503", "f& c #1A1A1A", "g& c #2D2C18", "h& c #2322D1", "i& c #6868BF", "j& c #D1D1D0", "k& c #6868E8", "l& c #3333E2", "m& c #2C2CE1", "n& c #3030E4", "o& c #1E1ED1", "p& c #5B5BE0", "q& c #9A9ACD", "r& c #BEBEEE", "s& c #1515E2", "t& c #1313F3", "u& c #3F41D8", "v& c #2B2CF9", "w& c #1F2066", "x& c #0F0F3E", "y& c #1D1DDB", "z& c #414199", "A& c #FEFEF9", "B& c #1E1EEF", "C& c #1A1AEF", "D& c #2B2BE1", "E& c #9A9ACE", "F& c #F8F8F5", "G& c #E9E9F3", "H& c #4E4DCF", "I& c #0F10F6", "J& c #1C20A8", "K& c #1C1CF5", "L& c #1C1CF8", "M& c #28287F", "N& c #686868", "O& c #080841", "P& c #1B1BDC", "Q& c #9E9EF7", "R& c #4D4DB5", "S& c #8D8AE4", "T& c #FEFDF7", "U& c #FDFDF9", "V& c #9191E4", "W& c #0708F4", "X& c #1418A3", "Y& c #1D1CF5", "Z& c #1A1AF6", "`& c #444399", " * c #10100B", ".* c #434343", "+* c #2E2E29", "@* c #201E58", "#* c #2222E3", "$* c #9797F0", "%* c #7A7CE6", "&* c #1717F0", "** c #1818EF", "=* c #5E5EF5", "-* c #F6F6FF", ";* c #F8F7F2", ">* c #7F7ED0", ",* c #090AF6", "'* c #1919F5", ")* c #5150A8", "!* c #161510", "~* c #2B2B2F", "{* c #31312B", "]* c #14144F", "^* c #1F1FE1", "/* c #48479F", "(* c #6968D6", "_* c #1818F1", ":* c #1616F0", "<* c #6C6CED", "[* c #F8F8FB", "}* c #F7F7F2", "|* c #7D7CCE", "1* c #1011F6", "2* c #1418A2", "3* c #1B1BF7", "4* c #414096", "5* c #0F0F0A", "6* c #444342", "7* c #20201C", "8* c #12123D", "9* c #1E1EDB", "0* c #6969D6", "a* c #7676B0", "b* c #0C0CF6", "c* c #1818E5", "d* c #9797DA", "e* c #FAFAFA", "f* c #8686E7", "g* c #0E0FF3", "h* c #181AB0", "i* c #1C1CF4", "j* c #1B1BFA", "k* c #1E1E78", "l* c #010200", "m* c #7C7A7A", "n* c #131211", "o* c #24231E", "p* c #2120D4", "q* c #3E3EEB", "r* c #EEEEDD", "s* c #EEEEEA", "t* c #807CCE", "u* c #1B1BEA", "v* c #1818F2", "w* c #1919F3", "x* c #1C1CF2", "y* c #7877D9", "z* c #DDDCE5", "A* c #F1F2FB", "B* c #4E4FD9", "C* c #1D1DE2", "D* c #3636F6", "E* c #272860", "F* c #2F2E2E", "G* c #3D3DB8", "H* c #2020E6", "I* c #A0A0AD", "J* c #F0EFF0", "K* c #AFAFD9", "L* c #5E5ECC", "M* c #4646C8", "N* c #4141C2", "O* c #5A5CC2", "P* c #B2B2CD", "Q* c #F7F7FE", "R* c #FEFDFC", "S* c #FCFBFD", "T* c #6C69B2", "U* c #3333D4", "V* c #222234", "W* c #282827", "X* c #040303", "Y* c #323222", "Z* c #1D1DEB", "`* c #4D4DE0", " = c #F0EFE0", ".= c #FCFCFE", "+= c #FBFBFE", "@= c #FBFBFD", "#= c #FDFDFE", "$= c #E6E6E9", "%= c #3A3AEA", "&= c #2626F3", "*= c #3C3C9E", "== c #020200", "-= c #4C4949", ";= c #3D3DC4", ">= c #1E1EEA", ",= c #8180DA", "'= c #F7F6F3", ")= c #D9D9DE", "!= c #2929D8", "~= c #2B2BDD", "{= c #393948", "]= c #0E0E0C", "^= c #020203", "/= c #242414", "(= c #2222DC", "_= c #1818E7", ":= c #8E8DDF", "<= c #E8E8F3", "[= c #F4F5F4", "}= c #C5C5E5", "|= c #2C2CD8", "1= c #2727EF", "2= c #292981", "3= c #2E2E2E", "4= c #171717", "5= c #20203D", "6= c #2B2BD7", "7= c #1919F8", "8= c #2323EE", "9= c #5959E2", "0= c #BEBDE5", "a= c #EAE9F4", "b= c #FFFFF2", "c= c #DDDDD4", "d= c #C9C8E8", "e= c #7677D5", "f= c #1919E5", "g= c #1919F0", "h= c #2C2CF6", "i= c #404090", "j= c #111102", "k= c #222123", "l= c #24233C", "m= c #4140C1", "n= c #2222FA", "o= c #1616F1", "p= c #2E2EDE", "q= c #5252EB", "r= c #6E6EE9", "s= c #9E9EE9", "t= c #9F9FD9", "u= c #A9A9E3", "v= c #9191F3", "w= c #4545D1", "x= c #3433DF", "y= c #1A1AEE", "z= c #1717F6", "A= c #4645F4", "B= c #383678", "C= c #0E0E05", "D= c #100F10", "E= c #16141B", "F= c #5A5A91", "G= c #2828F4", "H= c #1818F5", "I= c #2525EB", "J= c #2626E7", "K= c #2929EA", "L= c #2222EE", "M= c #2929EF", "N= c #3635E4", "O= c #3636E3", "P= c #3636E4", "Q= c #3434E1", "R= c #2222EB", "S= c #1818F3", "T= c #2728FD", "U= c #4A4ABD", "V= c #1D1D30", "W= c #030302", "X= c #161515", "Y= c #323033", "Z= c #6668A2", "`= c #1919F9", " - c #1D1DF2", ".- c #1E1EF4", "+- c #6363BC", "@- c #484847", "#- c #050507", "$- c #2B2A2B", "%- c #040404", "&- c #1B1910", "*- c #454449", "=- c #242476", "-- c #5656B5", ";- c #7575D0", ">- c #4C4CDD", ",- c #1A19E0", "'- c #2120E4", ")- c #1F1FE6", "!- c #1F1FEA", "~- c #2121F0", "{- c #2121E7", "]- c #2020E4", "^- c #1C1CE0", "/- c #5353D4", "(- c #4747A1", "_- c #121171", ":- c #2A2A66", "<- c #484844", "[- c #121007", "}- c #737373", "|- c #383638", "1- c #0A0A0A", "2- c #050504", "3- c #14130E", "4- c #070600", "5- c #100E00", "6- c #3C3A28", "7- c #282746", "8- c #272771", "9- c #4848B8", "0- c #2F2FD3", "a- c #2020E1", "b- c #2424F4", "c- c #1B1BF2", "d- c #2727F4", "e- c #2121DD", "f- c #2F2FD0", "g- c #2B2BA9", "h- c #21216D", "i- c #3E3E4B", "j- c #393929", "k- c #0D0C00", "l- c #0B0B04", "m- c #0A0A02", "n- c #0E0F0F", "o- c #1F1F1F", "p- c #646162", "q- c #3C393A", "r- c #292828", "s- c #090804", "t- c #0F0E07", "u- c #070700", "v- c #3E3D30", "w- c #141454", "x- c #35359A", "y- c #3738CE", "z- c #3333F2", "A- c #1E1EF5", "B- c #1A1AF4", "C- c #2D2DF4", "D- c #2F2FCE", "E- c #39398B", "F- c #13142D", "G- c #39392A", "H- c #101007", "I- c #17160F", "J- c #060603", "K- c #0E0E0E", "L- c #313131", "M- c #111111", "N- c #363636", "O- c #1D1C17", "P- c #060600", "Q- c #1E1F2C", "R- c #343462", "S- c #2424B5", "T- c #1919F7", "U- c #1B1BF4", "V- c #1F1FFE", "W- c #3838C3", "X- c #222268", "Y- c #1A1A2C", "Z- c #030401", "`- c #1F1E1B", " ; c #242424", ".; c #2E2D2D", "+; c #3E3E3E", "@; c #1A1A19", "#; c #040400", "$; c #040414", "%; c #0E0E2D", "&; c #3E3EA9", "*; c #3131FB", "=; c #1B1BFF", "-; c #3636C1", ";; c #24243D", ">; c #090918", ",; c #0C0C0A", "'; c #282828", "); c #000003", "!; c #15141B", "~; c #5B5A99", "{; c #232339", "]; c #030308", "^; c #6E6C6C", "/; c #383838", "(; c #6F6C6F", " ", " ", " . + @ # $ % & * ", " = - ; > , ' ) ! ~ { ] ^ / ( _ : < [ } | 1 ", " 2 3 4 5 6 7 8 9 0 a b c _ d e + f g h i j k + l m n o _ p q + r s t u v 0 ", " w + x y z A B C D + E F + + + + + G + H I J K L M N O P Q R S 8 + + + + + E + T U V W X N Y Z ` < ", " .+ ..+.@.z z M #.$.%.&.*.=.-.;.>.,.'.).!.~.M z z z z z z {.].^./.(._.:.<.[.}.|.1.2.{.@.z z @.3.4.5.6.7. ", " 8.+ 9.0.a.z z z z z z b.c.@.d.e.e.e.f.g.h.M z i.j.k.k.k.k.k.l.z m.n.o.p.q.p.r.s.t.u.z z z z z z v.w.x.m + y. ", " z.+ A.B.C.z z z z z z z z D.E.F.G.H.G.G.I.J.u.K.L.M.G.H.G.G.N.O.m.].P.Q.G.G.H.G.R.S.T.z z z z z z z U.V.W.X.+ Y. ", " Z.8 `. +.+z z z z z z z z z ++@+#+#+#+#+#+#+$+%+#.&+#+#+#+#+#+#+*+=+-+;+#+#+#+#+#+#+>+%+z z z z z z z z ,+'+)+!+~+{+ ", " ]+^+/+(+_+z z z z z z z z z z :+<+#+[+}+|+1+|+2+3+#.4+#+5+6+|+1+7+8+9+0+a+b+b+c+d+e+#+f+3+z z z z z z z z z z #.g+h++ i+ ", " j++ k+l+z z z z z z z z z z z z :+m+#+n+o+p+q+r+s+z #.t+#+u+v+w+q+x+y+z z+A+b+B+C+D+E+#+f+3+z z z z z z z z z z z F+G+H+I+ ", " J+p K+L+z z z z z z z z z z z z :+m+#+M+N+O+P+Q+R+{.#.t+#+S+T+U+P+V+W+{.z+X+b+Y+Z+`+ @#+f+3+z z z z z z z z z z z .@+@# ", " 1 + @@#@z z z z z z z z z z z $@%@#+#+#+#+#+#+&@%+#.*@#+#+#+#+#+#+*+=+0+=@b+Y+-@;@ @#+f+3+z z z z z z z z z z z >@,@'@ ", " )@+ !@~@z z z z z z z z z z {@]@^@/@/@/@(@#+_@3+#.t+#+:@<@[@[@}@|@9+0+X+b+Y+1@;@ @#+f+3+z z z z z z z z z @.2@3@4@ ", " + 5@6@{.z z z z z z z z z 7@8@9@0@0@a@b@#+_@3+#.c@#+d@e@f@g@h@i@v.0+X+b+Y+j@k@l@#+f+3+z z z z z z z z z m@n@+ o@ ", " ' + p@q@z z z z z z z z z r@s@t@u@u@v@w@#+_@3+#.x@#+y@z@A@B@C@D@v.z+A+b+E@F@G@H@#+f+3+z z z z z z z z _+I@J@K@ ", " L@M@N@#.z z z z z z z z :+O@#+P@P@P@Q@#+_@3+#.*@#+R@S@P@P@T@U@9+z+A+b+V@W@X@Y@#+f+3+z z z z z z z z Z@`@ #.# ", " +#E @###z z z z z z z z $@$##+%#%#%#%##+_@3+&#*##+%#%#%#%#=#-#9+0+;#>#%#%##+#+#+f+3+z z z z z z z z ,#'#+ ", " )#] !#~#{#]#]#]#]#{#]#^#/#(#_#_#_#:#<#[#}#|#1#2#_#_#_#_#3#4#5#6#7#_#_#8#9#0#a#b#c#{#{#{#{#]#]#]#d#e#f#g# ", " h#i#j#k#l#m#n#o#o#p#q#r#s#t#u#v#v#w#x#y#k#z#A#B#t#t#w#v#v#C#D#E#F#w#G#t#H#I#J#K#L#M#M#M#l#N#o#o#O#P#Q#R# ", " F S#T#U#U#V#U#V#V#U#W#W#U#X#Y#V#U#U#V#V#V#U#V#V#Z#V#V#V#V#U#U#U#V#U#V#`# $.$+$V#@$#$W#U#W#V#V#$$~++ ", " %$&$*$=$-$-$-$-$-$=$-$-$;$>$,$-$=$=$-$-$-$=$-$-$-$-$-$-$-$-$=$=$-$-$-$-$-$-$=$-$-$-$-$-$-$-$'$)$!$~$ ", " + {$]$^$^$^$^$^$^$^$^$^$/$($_$:$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$^$/$($_$:$^$^$^$^$^$^$^$^$^$<$[$}$|$ ", " + 1$2$3$3$3$3$3$3$3$3$4$5$6$7$8$9$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$4$5$6$7$8$9$3$3$3$3$3$3$3$3$0$a$}$+ ", " + b$c$3$3$3$3$3$3$3$d$e$f$g$#+h$i$j$3$3$3$3$3$3$3$3$3$3$3$3$3$d$e$f$g$#+h$i$j$3$3$3$3$3$3$3$k$l$m$+ ", " n$o$5$3$3$3$3$3$3$4$p$q$r$#+#+#+s$d$d$3$3$3$3$3$3$3$3$3$3$3$4$p$q$r$#+#+#+s$d$d$3$3$3$3$3$3$0$t$u$v$ ", " w$x$y$3$3$3$3$3$z$A$B$C$#+#+#+D$E$F$4$3$3$3$3$3$3$3$3$3$3$z$A$B$C$#+#+#+D$E$F$4$3$3$3$3$3$3$0$G$H$I$ ", " J$K$L$M$3$3$3$3$N$O$P$Q$#+#+#+#+R$S$4$3$3$3$3$3$3$3$3$3$3$N$O$P$Q$#+#+#+#+R$S$4$3$3$3$3$3$3$3$T$U$V$W$ ", " X$Y$Z$^$3$3$3$3$`$ %.%#+#+#+#++%@%#%3$3$3$3$3$3$3$3$3$3$3$`$ %.%#+#+#+#++%@%#%3$3$3$3$3$3$3$3$3$$%%%&% ", " *%+ =%-%d$3$3$3$T$;%>%#+#+#+#+,%'%3$3$3$3$3$3$3$3$3$3$3$3$T$;%>%#+#+#+#+,%'%3$3$3$3$3$3$3$3$3$3$3$)%!%~%o@ ", " {%]%^%<$3$3$3$3$/%(%_%#+#+#+:%<%T$3$3$3$3$3$3$3$3$3$3$3$3$/%(%_%#+#+#+:%<%T$3$3$3$3$3$3$3$3$3$3$3$T$[%}%+ ", " |%+ 1%2%3$3$3$3$j$3%4%#+#+#+5%6%7%3$3$3$3$3$3$3$3$3$3$3$3$j$3%4%#+#+#+5%6%7%3$3$3$3$3$3$3$3$3$3$3$3$3$8%9%0%a% ", " b%c%d%9$3$3$3$3$e%f%g%#+#+h%i%j%k%l%j$j$N$3$3$3$3$3$3$3$3$e%f%g%#+#+h%i%j%k%l%j$j$N$3$3$3$3$3$3$3$3$3$N$m%n%+ o% ", " p%+ q%r%3$3$3$3$d$s%t%u%#+#+v%w%x%y%z%A%B%C%D%#%3$3$3$3$3$d$s%t%u%#+#+v%w%x%y%z%A%B%C%D%#%3$3$3$3$3$3$3$3$T$E%F%G% ", " d H%I%3$3$3$3$3$T$J%K%#+#+#+L%M%N%O%P%Q%R%S%T%U%3$3$3$3$3$T$J%K%#+#+#+L%M%N%O%P%Q%R%S%T%U%3$3$3$3$3$3$3$3$3$V%W%+ X% ", " 0 + Y%Z%3$3$3$3$3$`% &#+#+#+#+#+#+#+#+#+#+#+#+#+.&+&3$3$3$3$`% &#+#+#+#+#+#+#+#+#+#+#+#+#+.&+&3$3$3$3$3$3$3$3$3$@&#&$& ", " %&&&*&3$3$3$3$3$3$=&-&#+#+#+#+#+#+#+#+#+#+#+#+#+#+;&>&4$3$3$=&-&#+#+#+#+#+#+#+#+#+#+#+#+#+#+;&>&4$3$3$3$3$3$3$3$,&'&+ )& ", " + !&~&3$3$3$3$3$3${&]&#+#+#+#+#+#+#+#+#+#+#+#+#+^&#+/&(&4$3${&]&#+#+#+#+#+#+#+#+#+#+#+#+#+^&#+/&(&4$3$3$3$3$3$3$_&:&<&[& ", " }&+ |&V%3$3$3$3$3$d$1&#+#+#+#+#+#+2&3&4&5&6&7&8&#+#+#+9&0&a&d$1&#+#+#+#+#+#+2&3&4&5&6&7&8&#+#+#+9&0&a&b&3$3$3$3$3$4$c&d&e& ", " f&g&h&3$3$3$3$3$3$4$i&#+#+#+#+#+j&k&l&m&n&o&p&q&u%#+#+#+r&s&t&i&#+#+#+#+#+j&k&l&m&n&o&p&q&u%#+#+#+r&s&u&b&3$3$3$3$d$v&w&+ ", " + x&y&3$3$3$3$3$3$#%z&#+#+#+#+A&y$B&T$4$d$#%C&D&E&F&#+#+G&H&I&z&#+#+#+#+A&y$B&T$4$d$#%C&D&E&F&#+#+G&H&J&K&3$3$3$3$3$L&M&K$N&", " + O&P&3$3$3$3$3$3$d$Q&#+#+#+#+R&<$3$3$3$3$3$3$k%S&T&#+#+U&V&W&Q&#+#+#+#+R&<$3$3$3$3$3$3$k%S&T&#+#+U&V&X&Y&3$3$3$3$3$Z&`& *.*", " +*@*#*3$3$3$3$3$3$d$$*#+#+#+#+%*&*3$3$3$3$3$3$**=*-*#+#+;*>*,*$*#+#+#+#+%*&*3$3$3$3$3$3$**=*-*#+#+;*>*X&Y&3$3$3$3$3$'*)*!*~*", " {*]*^*3$3$3$3$3$3$#%/*#+#+#+#+(*_*3$3$3$3$3$3$:*<*[*#+#+}*|*1*/*#+#+#+#+(*_*3$3$3$3$3$3$:*<*[*#+#+}*|*2*Y&3$3$3$3$3$3*4*5*6*", " 7*8*9*3$3$3$3$3$3$d$0*#+#+#+#+a*b*4$3$3$3$3$4$c*d*g$#+#+e*f*g*0*#+#+#+#+a*b*4$3$3$3$3$4$c*d*g$#+#+e*f*h*i*3$3$3$3$3$j*k*l*m*", " n*o*p*3$3$3$3$3$3$3$q*r*#+#+#+s*t*u*D%v*w*v*x*y*z*#+#+#+A*B*_*q*r*#+#+#+s*t*u*D%v*w*v*x*y*z*#+#+#+A*B*C*#%3$3$3$3$d$D*E*+ ", " F*+ G*B&3$3$3$3$3$3$H*I*#+#+#+#+J*K*L*M*N*O*P*Q*R*#+#+S*T*N$3$H*I*#+#+#+#+J*K*L*M*N*O*P*Q*R*#+#+S*T*N$#%3$3$3$3$3$0$U*V*W* ", " X*Y*Z*3$3$3$3$3$3$N$`* =#+#+#+#+%#.=+=@=#=%##+#+#+#+$=%=z$3$N$`* =#+#+#+#+%#.=+=@=#=%##+#+#+#+$=%=z$3$3$3$3$3$3$&=*===-= ", " 0 + ;=0$3$3$3$3$3$3$>=,='=#+#+#+#+#+#+#+#+#+#+#+#+)=!=b&3$3$3$>=,='=#+#+#+#+#+#+#+#+#+#+#+#+)=!=b&3$3$3$3$3$3$3$~={=]= ", " ^=/=(=i*3$3$3$3$3$4$_=:=<=#+#+#+#+#+#+#+#+#+[=}=|=0$3$3$3$3$4$_=:=<=#+#+#+#+#+#+#+#+#+[=}=|=0$3$3$3$3$3$3$d$1=2=+ 3= ", " 4=+ 5=6=7=d$3$3$3$3$T$8=9=0=a=b=#+#+#+#+c=d=e=f=b&3$3$3$3$3$3$T$8=9=0=a=b=#+#+#+#+c=d=e=f=b&3$3$3$3$3$3$g=h=i=j=k= ", " w + l=m=n=4$3$3$3$3$d$o=p=q=r=s=t=u=v=w=x=y=4$3$3$3$3$3$3$3$3$d$o=p=q=r=s=t=u=v=w=x=y=4$3$3$3$3$3$d$z=A=B=C=D= ", " {++ E=F=G=H=3$3$3$3$3$4$C&d$I=J=K=L=/%T$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$d$M=N=O=O=P=Q=R=3$3$S=T=U=V=W=)@ ", " X=+ Y=Z=`=:$^$T$N$d$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$N$9$ - -:$.-+-@-#-+ ~$ ", " $-%-+ &-*-=---;->-,-'-)-!-~-#%3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$3$#%B&>={-]-^-/-(-_-:-<-[-+ + f&}- ", " |-1-2-3-4-+ + 5-6-7-8-9-0-a-b-c-d$3$3$3$3$3$3$3$3$3$d$c-d-e-f-g-h-i-j-k-+ l-m-+ + n-o- ", " p-q-r-+ _ j+s-t-u-v-w-x-y-z-A-T$3$3$3$3$3$3$B-C-D-E-F-G-H-I-J-K-L-M-.# ", " N-+ O-P-Q-R-S-z-T-N$3$3$U-V-W-X-Y-Z-`- ;.; ", " +;@;#;$;%;&;*;H==;-;;;>;+ ,; ", " ';+ );!;~;{;];+ f&^; ", " /;+ + + d (; ", " ", " "}; ================================================ FILE: resources/pixmaps/scale.xpm ================================================ /* XPM */ static const char * scale_xpm[] = { "18 12 3 1", " c None", ". c #000000", "+ c #FFFFFF", " .... ", " .... ", " .+++ ", " ..... ", " ..... ", " .+++ ", " ..... ", " ..... ", " .+++ ", " ..... ", " ..... ", " ++++ "}; ================================================ FILE: resources/pixmaps/seq-editor.xpm ================================================ /* XPM */ static const char * seq_editor_xpm[] = { "32 32 9 1", " c None", ". c #CBDAE7", "+ c #B2C0CD", "@ c #566F84", "# c #DB701A", "$ c #415A6F", "% c #264157", "& c #FFFFFF", "* c #5986AC", " ", " . ", " .+@ ", " ### .+@$% ", " ### .+@$% ", " # # .+@$% ", " ## # .+@$% ", " # # .+@$% ", " # # .+@$% ", " # ### .+@$% ", " # ### .+@$% ", " ### # .+@$% ", " ### .+@$% ", " ### .+@$% ", " .+@$% ", " +@$% ", " +@$ ", " @ ", "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", "&&&&&&&&&&&&&&&&&&&&****&&&&&&&&", "&&&&&&&&&&&&&&&&&&&&****&&&&&&&&", "&&&&&&&&****&&&&&&&&&&&&&&&&&&&&", "&&&&&&&&****&&&&&&&&&&&&&&&&&&&&", "&&&&&&&&&&&&&&&&****&&&&&&&&&&&&", "&&&&&&&&&&&&&&&&****&&&&&&&&&&&&", "&&&&****&&&&****&&&&&&&&****&&&&", "&&&&****&&&&****&&&&&&&&****&&&&", "****&&&&&&&&&&&&&&&&&&&&&&&&****", "****&&&&&&&&&&&&&&&&&&&&&&&&****", "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&", "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"}; ================================================ FILE: resources/pixmaps/seq.xpm ================================================ /* XPM */ static const char * sequencer66_xpm[] = { "32 32 7 1", " c None", ". c #8E8E8E", "+ c #8F8F8F", "@ c #F2F2F2", "# c #585858", "$ c #6C6C6C", "% c #808080", " ...................... ", " .......................... ", " ............................ ", " .............................. ", " .............................. ", "+...............................", "................................", "................................", "................................", "................................", "..@@@@@@@@@@@@@@@@@@@@@@@@@@....", "..@@@@@@@@@@@@@@@@@@@@@@@@@@....", "..@@##############@@######@@##..", "..@@##############@@######@@##..", "....@@$$$$@@@@$$$$@@##$$$$@@##$$", "....@@$$$$@@@@$$$$@@##$$$$@@##$$", "......@@%%%%####%%@@##@@%%@@##$$", "......@@%%%%####%%@@##@@%%@@##$$", "@@@@@@@@@@@@@@@@@@@@@@@@@@@@##$$", "@@@@@@@@@@@@@@@@@@@@@@@@@@@@##$$", "..####################@@######$$", "..####################@@######$$", "....$$$$$$$$$$$$$$$$$$$$##$$$$$$", "....$$$$$$$$$$$$$$$$$$$$##$$$$$$", "......%%%%%%%%%%%%%%%%%%%%$$%%%%", "......%%%%%%%%%%%%%%%%%%%%$$%%%%", "............................%%..", " ...........................%%. ", " .............................. ", " ............................ ", " .......................... ", " ...................... "}; ================================================ FILE: resources/pixmaps/seq66.xpm ================================================ /* XPM */ static const char * seq66_xpm[] = { "32 32 7 1", " c None", ". c #BABABA", "+ c #8B8B8B", "@ c #5C5C5C", "# c #2D2D2D", "$ c #000000", "% c #3E3E3E", "................................", "................................", ".+@#$$$#@+......................", "+@#$$$$$#@+.....................", "@##@+..+@@@+....................", "@@@+....+++++@#$#@+..+@#$$$$#%@.", "+@@@+......+@#$$$#@++@#$$$$$#%@.", ".+@#$#@+..+@##@++@@####@++@##%@.", "..+@#$$$#@@@@@+.+@@##@@+.+@##%@.", ".....+@#$####$$$$$###@@+.+@##%@.", ".......+@@###$$$$$###@@+.+@##%@.", "@@@+...+@@##@@+....+@@@+.+@##%@.", "@##@+.+@######@@@##@@@@@++@##%@.", "+@#$$$$$#@++@#$$$$#@++@#$$$$#%@.", ".+@#$$$#@+..+@#$#@+...+@#$$$#%@.", ".........................+@##%@.", ".........................+@##%@.", ".........................+@##%@.", ".....+@#$#@+....+@#$#@+..+@##%@.", "....+@#$$$#@+..+@#$$$#@+........", "...+@##@++@@@++@##@++@@@+.......", "...+@@@+..+++++@@@+..++++.......", "...+@#$$$#@+..+@#$$$#@+.........", "...+@#$$$$#@+++@#$$$$#@++.......", "...+@##@@@####+@##@@@####.......", "...+@@@+.+@@##+@@@+.+@@##.......", "...+@@@+.+@@##+@@@+.+@@##.......", "...+@@@+.+@@##+@@@+.+@@##.......", "....+@@@@@##@+.+@@@@@##@+.......", ".....+@#$$#@+...+@#$$#@+........", "......+@##@+.....+@##@+.........", "................................"}; ================================================ FILE: resources/pixmaps/seq66_32.xpm ================================================ /* XPM */ static const char * seq66_32_xpm[] = { "32 32 3 1", " c None", ". c #A5A5A5", "+ c #000000", "................................", "................................", ".+++++++++..+++++++++.+++++++++.", ".+++++++++..+++++++++.+++++++++.", ".+++++++++..+++++++++.+++++++++.", ".+++........+++.......+++...+++.", ".+++........+++.......+++...+++.", ".+++........+++.......+++...+++.", ".+++++++++..++++++....+++...+++.", ".+++++++++..++++++....+++...+++.", ".+++++++++..++++++....+++...+++.", ".......+++..+++.......+++...+++.", ".......+++..+++.......+++...+++.", ".......+++..+++.......+++...+++.", ".+++++++++..+++++++++.+++++++++.", ".+++++++++..+++++++++.+++++++++.", ".+++++++++..+++++++++.+++++++++.", "...........................+++..", "...++++++++++++.++++++++++++++..", "...++++++++++++.++++++++++++....", "...++++++++++++.++++++++++++....", "...+++++........+++++...........", "...+++++........+++++...........", "...++++++++++++.++++++++++++....", "...++++++++++++.++++++++++++....", "...++++++++++++.++++++++++++....", "...+++++...++++.+++++...++++....", "...+++++...++++.+++++...++++....", "...++++++++++++.++++++++++++....", "...++++++++++++.++++++++++++....", "...++++++++++++.++++++++++++....", "................................"}; ================================================ FILE: resources/pixmaps/sequences.xpm ================================================ /* XPM */ static const char * sequences_xpm[] = { "30 14 3 1", " c None", ". c #000000", "+ c #FFFFFF", " ", " ....... ....... ", " .+++++. .+++++. ", " ....... ....... ", " ....... ....... ", " .+++++. .+++++. ", " ....... ....... ", " ....... ....... ", " .+++++. .+++++. ", " ....... ....... ", " ....... ....... ", " .+++++. .+++++. ", " ....... ....... ", " "}; ================================================ FILE: resources/pixmaps/show.xpm ================================================ /* XPM */ static const char * show_xpm[] = { "16 16 2 1", " c None", ". c #000000", " ", " ............ ", " . . ", " . . ", " . . ", " . . ", " . . ", " . . ", " . . ", " . . ", " . . ", " . . ", " . . ", " . . ", " ............ ", " "}; ================================================ FILE: resources/pixmaps/show_bars_off.xpm ================================================ /* XPM */ static const char * show_bars_off_xpm[] = { "12 20 2 1", " c None", ". c #000000", " ", " ", " .......... ", " . . ", " . . ", " .......... ", " ", " ", " .......... ", " . . ", " . . ", " .......... ", " ", " ", " .......... ", " . . ", " . . ", " .......... ", " ", " "}; ================================================ FILE: resources/pixmaps/show_bars_on.xpm ================================================ /* XPM */ static const char * show_bars_on_xpm[] = { "12 20 2 1", " c None", ". c #000000", " ", " ", " .......... ", " .......... ", " .......... ", " .......... ", " ", " ", " .......... ", " .......... ", " .......... ", " .......... ", " ", " ", " .......... ", " .......... ", " .......... ", " .......... ", " ", " "}; ================================================ FILE: resources/pixmaps/snap.xpm ================================================ /* XPM */ static const char * snap_xpm[] = { "23 12 5 1", " c None", ". c #000000", "+ c #56BEFF", "@ c #BFE8FF", "# c #357293", " . ", " . . . ", " .. . .. ", " .+. . .@. ", "......++. . .@+......", ".@@@@@+++. . .@++@@@@@.", ".+++++++#. . .++++++++.", "......+#. . .++......", " .#. . .+. ", " .. . .. ", " . . . ", " . "}; ================================================ FILE: resources/pixmaps/song-editor.xpm ================================================ /* XPM */ static const char * song_editor_xpm[] = { "32 32 8 1", " c None", ". c #9ED69A", "+ c #79AF75", "@ c #4B8747", "# c #30612D", "$ c #11480C", "% c #FFFFFF", "& c #47C847", " ", " . ", " .+@ ", " .+@#$ ", " .+@#$ ", " .+@#$ ", " .+@#$ ", " .+@#$ ", " .+@#$ ", " .+@#$ ", " .+@#$ ", " .+@#$ ", " .+@#$ ", " .+@#$ ", " .+@#$ ", " +@#$ ", " +@# ", " +@ ", "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%", "&&&&&&&&&&&&%%%%%%%%%%%&&&&&&&&&", "&&&&&&&&&&&&%%%%%%%%%%%&&&&&&&&&", "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%&&&&&&&&&&&&&%%%%%%%%%%%", "%%%%%%%%&&&&&&&&&&&&&%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%", "&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%", "&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&", "%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&", "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%", "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"}; ================================================ FILE: resources/pixmaps/song-snap.xpm ================================================ /* XPM */ static const char * song_snap_xpm[] = { "20 20 3 1", " c None", ". c #000000", "+ c #AD6666", " ", " ", " ", " .. ", " .. ", " .. ", " .. + ", " .. ++ ", " .. ++++++++++++++ ", " .. +++++++++++++++ ", " .. ++++++++++++++ ", " .. ++ ", " .. + ", " .. ", " .. ", " .. ", " .. ", " ", " ", " "}; ================================================ FILE: resources/pixmaps/song_mode.xpm ================================================ /* XPM */ static const char * song_mode_xpm[] = { "18 17 4 1", " c None", ". c #000000", "+ c #A5A5A5", "@ c #C6EF94", "..................", ".++++.+++.+++++++.", ".+.+.+.+..+++++++.", "..+.+++.+.+++++++.", "..................", "..+++.++..+++++++.", ".+.+.+..+.+++++++.", ".++.++.++.+++++++.", "..................", ".@@@@@@@.@@@.@@@@.", ".@.@@@.@@@.@.@@@@.", ".@@@.@@@@@@@.@@@@.", "..................", ".++++++++.+++.+++.", ".++++++++..+.+.+..", ".++++++++.+.+++.+.", ".................."}; ================================================ FILE: resources/pixmaps/song_rec.xpm ================================================ /* XPM */ static const char * song_rec_xpm[] = { "15 11 5 1", " c None", ". c #000000", "+ c #FFC1C1", "@ c #AD6666", "# c #724B4B", " ... ", " ..+++.. ", " .++@@@@@. ", " .+@@@@@@. ", " .+@@@@@@@@. ", " .+@@@@@@@#. ", " .+@@@@@@@#. ", " .@@@@@@#. ", " .@@@@@##. ", " ..###.. ", " ... "}; ================================================ FILE: resources/pixmaps/song_rec_no_snap.xpm ================================================ /* XPM */ static const char * song_rec_no_snap_xpm[] = { "15 9 52 1", " c None", ". c #0A0D18", "+ c #161C36", "@ c #090D1F", "# c #050917", "$ c #0A1026", "% c #1F2747", "& c #5272E8", "* c #3A5DE0", "= c #3255DA", "- c #2E53DC", "; c #2C4DCA", "> c #0E1A46", ", c #000000", "' c #131931", ") c #3A57C2", "! c #3256DA", "~ c #2D51D5", "{ c #2449D4", "] c #183FD2", "^ c #0F39D3", "/ c #1137C5", "( c #0F1B47", "_ c #172557", ": c #294ED7", "< c #1B42D3", "[ c #0E37D0", "} c #0630CE", "| c #032ECD", "1 c #002BCC", "2 c #0632D6", "3 c #051551", "4 c #0832CE", "5 c #0531D5", "6 c #051653", "7 c #022ED0", "8 c #032FD5", "9 c #0A1130", "0 c #082DB6", "a c #002CD2", "b c #002CCE", "c c #042CC0", "d c #031041", "e c #020C32", "f c #0026B4", "g c #002DD6", "h c #002DD4", "i c #0028BE", "j c #010E3F", "k c #000310", "l c #000518", "m c #000412", " .+@#$ ", " %&*=-;> ", ", ')!~{]^/( ,", ", _:<[}|12_ ,", ", ,34|11115_, ,", ", 67111118_ ,", ", 90a1bbacd ,", " efghhij ", " klllm "}; ================================================ FILE: resources/pixmaps/song_rec_off.xpm ================================================ /* XPM */ static const char * song_rec_off_xpm[] = { "9 9 37 1", " c None", ". c #030611", "+ c #090D23", "@ c #040712", "# c #02040E", "$ c #040818", "% c #0D142F", "& c #1C35A2", "* c #193091", "= c #182D88", "- c #162A7E", "; c #070E2B", "> c #080C20", ", c #182B7E", "' c #172C85", ") c #172A7D", "! c #142778", "~ c #142674", "{ c #13236B", "] c #080E2C", "^ c #0A1338", "/ c #172B83", "( c #152879", "_ c #132573", ": c #12236A", "< c #122268", "[ c #132571", "} c #13246D", "| c #05091D", "1 c #112061", "2 c #132369", "3 c #060B22", "4 c #04081A", "5 c #101E5C", "6 c #060B20", "7 c #020308", "8 c #02030A", " .+@#$ ", " %&*==-; ", ">,=')!~{]", "^/(_{:<[^", "]}:<<<<[^", "]:<<<<<}^", "|12<<<:13", " 45}:{16 ", " 78887 " }; ================================================ FILE: resources/pixmaps/song_rec_on.xpm ================================================ /* XPM */ static const char * song_rec_on_xpm[] = { "9 9 51 1", " c None", ". c #0A0D18", "+ c #161C36", "@ c #090D1F", "# c #050917", "$ c #0A1026", "% c #1F2747", "& c #5272E8", "* c #3A5DE0", "= c #3255DA", "- c #2E53DC", "; c #2C4DCA", "> c #0E1A46", ", c #131931", "' c #3A57C2", ") c #3256DA", "! c #2D51D5", "~ c #2449D4", "{ c #183FD2", "] c #0F39D3", "^ c #1137C5", "/ c #0F1B47", "( c #172557", "_ c #294ED7", ": c #1B42D3", "< c #0E37D0", "[ c #0630CE", "} c #032ECD", "| c #002BCC", "1 c #0632D6", "2 c #051551", "3 c #0832CE", "4 c #0531D5", "5 c #051653", "6 c #022ED0", "7 c #032FD5", "8 c #0A1130", "9 c #082DB6", "0 c #002CD2", "a c #002CCE", "b c #042CC0", "c c #031041", "d c #020C32", "e c #0026B4", "f c #002DD6", "g c #002DD4", "h c #0028BE", "i c #010E3F", "j c #000310", "k c #000518", "l c #000412", " .+@#$ ", " %&*=-;> ", ",')!~{]^/", "(_:<[}|1(", "23}||||4(", "56|||||7(", "890|aa0bc", " defgghi ", " jkkkl " }; ================================================ FILE: resources/pixmaps/song_record.xpm ================================================ /* XPM */ static const char * song_record_xpm[] = { "13 12 36 1", " c None", ". c #000000", "+ c #0F0F29", "@ c #1C1CA2", "# c #1A1A94", "$ c #18188A", "% c #111161", "& c #24248A", "* c #1D1DA5", "= c #1B1B97", "- c #1B1B95", "; c #1A1A92", "> c #19198F", ", c #181886", "' c #0F0F55", ") c #151561", "! c #171785", "~ c #16167E", "{ c #151577", "] c #0C0C46", "^ c #151579", "/ c #151575", "( c #141474", "_ c #14146E", ": c #141472", "< c #171783", "[ c #16167C", "} c #121266", "| c #13136D", "1 c #131369", "2 c #0E0E52", "3 c #090939", "4 c #0E0E4E", "5 c #0C0C44", "6 c #13136B", "7 c #0A0A3A", " ..+... ", " .@@#$$#%. ", " .&*=-#;>,'. ", ".)#=#>$!~~{].", ".,#$!~^{/(~_.", ".:<[/(((((~:.", ".}/((((((([|.", ".1{((((((/^%.", ".2//((((({|3.", " .4:////{_5. ", " .]6|6|17. ", " ....... "}; ================================================ FILE: resources/pixmaps/stop.xpm ================================================ /* XPM */ static const char * stop_xpm[] = { "15 11 5 1", " c None", ". c #000000", "+ c #FFC1C1", "@ c #AD6666", "# c #724B4B", " ........... ", " .+++++++++. ", " .+@@@@@@@#. ", " .+@@@@@@@#. ", " .+@@@@@@@#. ", " .+@@@@@@@#. ", " .+@@@@@@@#. ", " .+@@@@@@@#. ", " .+@@@@@@@#. ", " .#########. ", " ........... "}; ================================================ FILE: resources/pixmaps/t_rec_on.xpm ================================================ /* XPM */ static const char * t_rec_on_xpm[] = { "36 14 8 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #FF0000", "# c #9E9E9E", "$ c #FFBABA", "% c #FF5454", "& c #B51E1E", " ...... ", " ...+..+... @@@@@@@@@@@", " ..+++##+++.. . @@ @@@ @@", " .++++++++++. .. @ @@@ @", "..++++++++++.. .$. @@@ ", ".++++++++++++. ......$%. @@@ ", ".+..++++++..+. .$$$$$%%%. @@@ ", ".+#.++++++.#+. .%%%%%%%&. @@@ ", ".++++++++++++. ......%&. @@@ ", "..+..++++..+.. .&. @@@ ", " .+#.+..+.#+. .. @@@ ", " ..+++..+++.. . @@@ ", " ...++++... @@@@@@ ", " ...... "}; ================================================ FILE: resources/pixmaps/thru.xpm ================================================ /* XPM */ static const char * thru_xpm[] = { "62 14 10 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #BFD8FF", "# c #9E9E9E", "$ c #82B1FF", "% c #1E56AA", "& c #FFC396", "* c #FF9F32", "= c #BC681A", " ...... .......... ...... ", " ...+..+... .@@@@@@@@. ...+..+... ", " ..+++##+++.. . .@$$$$$$%. . ..+++##+++.. ", " .++++++++++. .. .@$$$$$$%. .. .++++++++++. ", "..++++++++++.. .&. .@$$$$$$%. .&. ..++++++++++..", ".++++++++++++. ......&*. .@$$$$$$%. ......&*. .++++++++++++.", ".+..++++++..+. .&&&&&***. .@$$$$$$%. .&&&&&***. .+..++++++..+.", ".+#.++++++.#+. .*******=. .@$$$$$$%. .*******=. .+#.++++++.#+.", ".++++++++++++. ......*=. .@$$$$$$%. ......*=. .++++++++++++.", "..+..++++..+.. .=. .@$$$$$$%. .=. ..+..++++..+..", " .+#.+..+.#+. .. .@$$$$$$%. .. .+#.+..+.#+. ", " ..+++..+++.. . .@$$$$$$%. . ..+++..+++.. ", " ...++++... .%%%%%%%%. ...++++... ", " ...... .......... ...... "}; ================================================ FILE: resources/pixmaps/thru_on.xpm ================================================ /* XPM */ static const char * thru_on_xpm[] = { "62 14 10 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #E3DC2F", "# c #9E9E9E", "$ c #FEFC00", "% c #B1AB17", "& c #FFC396", "* c #FF9F32", "= c #BC681A", " ...... .......... ...... ", " ...+..+... .@@@@@@@@. ...+..+... ", " ..+++##+++.. . .@$$$$$$%. . ..+++##+++.. ", " .++++++++++. .. .@$$$$$$%. .. .++++++++++. ", "..++++++++++.. .&. .@$$$$$$%. .&. ..++++++++++..", ".++++++++++++. ......&*. .@$$$$$$%. ......&*. .++++++++++++.", ".+..++++++..+. .&&&&&***. .@$$$$$$%. .&&&&&***. .+..++++++..+.", ".+#.++++++.#+. .*******=. .@$$$$$$%. .*******=. .+#.++++++.#+.", ".++++++++++++. ......*=. .@$$$$$$%. ......*=. .++++++++++++.", "..+..++++..+.. .=. .@$$$$$$%. .=. ..+..++++..+..", " .+#.+..+.#+. .. .@$$$$$$%. .. .+#.+..+.#+. ", " ..+++..+++.. . .@$$$$$$%. . ..+++..+++.. ", " ...++++... .%%%%%%%%. ...++++... ", " ...... .......... ...... "}; ================================================ FILE: resources/pixmaps/tools.xpm ================================================ /* XPM */ static const char * tools_xpm[] = { "13 14 5 1", " c None", ". c #2C2C2C", "+ c #9E9E9E", "@ c #DEB571", "# c #C69641", " ......... ..", "....+++++...+", ". ..........", " ....... ..", " .@@. ", " .@@. ", " .@@. ", " .@@. ", " .##. ", " .##. ", " .##. ", " .##. ", " .##. ", " .... "}; ================================================ FILE: resources/pixmaps/transport_follow.xpm ================================================ /* XPM */ static const char * transport_follow_xpm[] = { "18 18 5 1", " c None", ". c #000000", "+ c #BFE8FF", "@ c #56BEFF", "# c #357293", " ", " ", " ...... . ", " ...... .. ", " .. .+. ", " .. .++. ", " .........@@+. ", " .++..+++@@@@+. ", " .@@..@@@@@@@@+. ", " .@@..@@@@@@@@@#. ", " .@@..@@@@@@@@#. ", " .@@..@@@@@@@#. ", " .........@@#. ", " .. .@#. ", " .. .#. ", " .. .. ", " .. . ", " "}; ================================================ FILE: resources/pixmaps/transpose.xpm ================================================ /* XPM */ static const char * transpose_xpm[] = { "18 18 4 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #4DE8EA", " ", " ... ", " ..+++.. ", ".........+++++....", " ..+++.. ", " ... ", " . ", " .@. ", " ..@@@.. ", ".........@@@@@....", " ...@... ", " .@. ", " .@. ", " .... .@. ", " ..+++. ..@. ", "...+++++.@@@......", " ..+++. .... ", " ... "}; ================================================ FILE: resources/pixmaps/tux.xpm ================================================ /* XPM */ static const char * tux_xpm[] = { "20 24 107 2", " c None", ". c #030303", "+ c #050505", "@ c #161616", "# c #010101", "$ c #020202", "% c #040404", "& c #1E1E1E", "* c #060606", "= c #0E0E0E", "- c #0C0C0C", "; c #000000", "> c #141414", ", c #181818", "' c #4F4F4F", ") c #5D5D5D", "! c #0B0B0B", "~ c #C2C2C2", "{ c #797979", "] c #434343", "^ c #5A5A5A", "/ c #606060", "( c #2F2F2F", "_ c #6A6A6A", ": c #0A0A0A", "< c #494949", "[ c #767676", "} c #888888", "| c #676767", "1 c #2B2B2B", "2 c #686868", "3 c #717171", "4 c #6B6B6B", "5 c #1B1B1B", "6 c #1C1C1C", "7 c #525252", "8 c #808080", "9 c #696969", "0 c #969696", "a c #CDCDCD", "b c #343434", "c c #222222", "d c #D7D7D7", "e c #CFCFCF", "f c #F7F7F7", "g c #FFFFFF", "h c #959595", "i c #090909", "j c #636363", "k c #FDFDFD", "l c #FEFEFE", "m c #B8B8B8", "n c #151515", "o c #0F0F0F", "p c #8C8C8C", "q c #F8F8F8", "r c #FAFAFA", "s c #EFEFEF", "t c #F0F0F0", "u c #565656", "v c #171717", "w c #252525", "x c #ECECEC", "y c #FCFCFC", "z c #B3B3B3", "A c #313131", "B c #5B5B5B", "C c #F9F9F9", "D c #262626", "E c #FBFBFB", "F c #303030", "G c #F1F1F1", "H c #2E2E2E", "I c #444444", "J c #C0C0C0", "K c #E3E3E3", "L c #232323", "M c #121212", "N c #6F6F6F", "O c #757575", "P c #858585", "Q c #818181", "R c #999999", "S c #F6F6F6", "T c #9C9C9C", "U c #7B7B7B", "V c #828282", "W c #707070", "X c #848484", "Y c #878787", "Z c #5F5F5F", "` c #737373", " . c #747474", ".. c #4E4E4E", "+. c #949494", "@. c #7D7D7D", "#. c #838383", "$. c #B9B9B9", "%. c #D5D5D5", "&. c #D2D2D2", "*. c #A6A6A6", "=. c #585858", "-. c #777777", ";. c #7C7C7C", ">. c #5E5E5E", ",. c #7F7F7F", "'. c #424242", " ", " . . . + @ ", " # $ . % & * ", " = - ; > , # . ", " ' ) ! ~ { # . ", " ] ^ / ( _ : $ ", " < [ } | | # # ", " 1 | 2 3 4 5 6 ", " 7 8 9 0 a b - ", " c d d e f g h . i ", " j g k l l g m n . . ", " o p g q k r s t u n # . ", " v w x l k k y k k z A @ # ", " $ B g k r y k k g C D + . ", " % ; g E k s E k y r g F . . . ", " # = g q l G y k y k g H > % $ ", " I w J g g G y k y k K L M , ", " N O P Q < R g q k k k S T L ! H U ", " U 8 8 V W o X g k k g x Y 4 Z ` Q ", " .V Q Q 8 ..+.g l r g z | @.8 Q Q 8 ", " .#.Q Q 8 P $.%.&.e *.n =.V Q Q 8 @. ", " O .-.U #.;.( - - + ; * >.V ,.{ { ", " B 7 b , '.Z ) ", " "}; ================================================ FILE: resources/pixmaps/undo.xpm ================================================ /* XPM */ static const char * undo_xpm[] = { "16 14 3 1", " c None", ". c #000000", "+ c #65FF72", " . ", " .. ", " .+. ", " .++........ ", " .++++++++++. ", " .++......+. ", " .+. .+. ", " .. .+. ", " . .+. ", " .+. ", " .+. ", " ........+. ", " .++++++++. ", " .......... "}; ================================================ FILE: resources/pixmaps/up.xpm ================================================ /* XPM */ static const char * up_xpm[] = { "7 14 2 1", " c None", ". c #000000", " ", " ", " ", " . ", " ... ", " ..... ", ".......", " ... ", " ... ", " ... ", " ", " ", " ", " "}; ================================================ FILE: resources/pixmaps/up_inv.xpm ================================================ /* XPM */ static const char * up_inv_xpm[] = { "7 14 2 1", " c None", ". c #FFFFFF", " ", " ", " ", " . ", " ... ", " ..... ", ".......", " ... ", " ... ", " ... ", " ", " ", " ", " "}; ================================================ FILE: resources/pixmaps/zoom.xpm ================================================ /* XPM */ static const char * zoom_xpm[] = { "16 14 10 1", " c None", ". c #000000", "+ c #65D3FF", "@ c #FFFFFF", "# c #E5E5E5", "$ c #4C9CBA", "% c #BCBCBC", "& c #7F7F7F", "* c #FFD07A", "= c #B76F0B", " ..... ", " ..+++.. ", " ..+@#++.. ", " .+@@++++. ", " .+++++++. ", " .+@++++$. ", " ..++++$.. ", " %..+$$.. ", " ..&%..... ", " .*=. ", " .*==. ", " .*==. ", " .==. ", " .. "}; ================================================ FILE: resources/pixmaps/zoom_in.xpm ================================================ /* XPM */ static const char * zoom_in_xpm[] = { "16 14 9 1", " c None", ". c #000000", "+ c #65D3FF", "@ c #FFFFFF", "# c #4C9CBA", "$ c #BCBCBC", "% c #7F7F7F", "& c #FFD07A", "* c #B76F0B", " ..... ", " ..+++.. ", " ..+@.++.. ", " .+@@.+++. ", " .+.....+. ", " .+@+.++#. ", " ..++.+#.. ", " $..+##.. ", " ..%$..... ", " .&*. ", " .&**. ", " .&**. ", " .**. ", " .. "}; ================================================ FILE: resources/pixmaps/zoom_out.xpm ================================================ /* XPM */ static const char * zoom_out_xpm[] = { "16 14 10 1", " c None", ". c #000000", "+ c #65D3FF", "@ c #FFFFFF", "# c #E5E5E5", "$ c #4C9CBA", "% c #BCBCBC", "& c #7F7F7F", "* c #FFD07A", "= c #B76F0B", " ..... ", " ..+++.. ", " ..+@#++.. ", " .+@@++++. ", " .+.....+. ", " .+@++++$. ", " ..++++$.. ", " %..+$$.. ", " ..&%..... ", " .*=. ", " .*==. ", " .*==. ", " .==. ", " .. "}; ================================================ FILE: resources/seq66_win.rc ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file seq66_win.rc * * This module is an RC (resource file) for the Windows build. * * \library seq66 application * \author Chris Ahlstrom * \date 2023-05-25 * \updates 2023-05-26 * \license GNU GPLv2 or above * * Still in progress. See * * https://www.qtcentre.org/threads/7764-How-to-load-icons-from-resource-file */ #include #include #include #include "afxres.h" IDI_ICON_1 ICON "resources/icons/route66.ico" VS_VERSION_INFO VERSIONINFO FILEVERSION 0,99,6,0 PRODUCTVERSION 0,99,6,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x9L #else FILEFLAGS 0x8L #endif FILEOS 0x40004L FILETYPE 0x2L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "Comments", "\0" VALUE "CompanyName", "Ahlstromcj\0" VALUE "FileDescription", "Seq66 Live MIDI\0" VALUE "FileVersion", "0,99,6,0\0" VALUE "InternalName", "Seq66\0" VALUE "LegalCopyright", "Copyright © 2015-2023\0" VALUE "LegalTrademarks", "\0" VALUE "OriginalFilename", "seq66.aip\0" VALUE "PrivateBuild", "1\0" VALUE "ProductName", "Seq66\0" VALUE "ProductVersion", "0, 99, 6, 0\0" VALUE "SpecialBuild", "\0" END END /* BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END */ /* * seq66_win.rc * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq66.pro ================================================ #****************************************************************************** # seq66.pro (qpseq66) #------------------------------------------------------------------------------ ## # \file seq66.pro # \library qpseq66 application # \author Chris Ahlstrom # \date 2018-11-15 # \update 2023-05-27 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Created for Qt Creator. This file was created for editing the project # sources and for allowing the developer to use "qmake" to configure the # builds. # # This project file is designed only for Qt 5 and, it is to be hoped, above. # Note the "qtc_runnable" flag in CONFIG. It prevents Qt Creator from # automatically creating run configurations for SUBDIRS projects. Qt Creator # creates run configurations only for subprojects that also have "CONFIG += # qtc_runnable" set in their ".pro" files. # # Also note that one can add "rtmidi" to CONFIG in order to replace the # internal PortMidi library with the preferable internal RtMidi library. The # internal PortMidi library is currently meant for Windows and Mac, but can # be used to make a Linux build to test a PortMidi on our preferred platform. # # The application generated by this profile is named "qpseq66", and uses the # built-in Seq66 "portmidi" library. We recommend runnning qmake and make # from a "shadow" directory. See "contrib/scripts/q-make". # #------------------------------------------------------------------------------ TEMPLATE = subdirs CONFIG += static link_prl ordered qtc_runnable c++14 contains (CONFIG, rtmidi) { SUBDIRS = libseq66 libsessions seq_rtmidi seq_qt5 Seq66qt5 Seq66qt5.depends = libseq66 libsessions seq_rtmidi seq_qt5 } else { SEQ66_MIDILIB = portmidi SUBDIRS = libseq66 seq_portmidi seq_qt5 Seq66qt5 Seq66qt5.depends = libseq66 seq_portmidi seq_qt5 } message("SUBDIRS is set to: $${SUBDIRS}") # These do not work on 32-bit Linux using Qt 5.3: # # CONFIG += c++14 -or- QMAKE_CXXFLAGS += -std=gnu++14 QMAKE_CXXFLAGS += -std=c++14 # Install an application icon for Windows to use. But see Seq66qt5.pro instead. # # win32:RC_ICONS += ../seq66/resources/icons/route66.ico # win32:RC_FILE = resources/seq66_win.rc # RESOURCES += seq66.qrc # # The automated resource file generation also uses the values of the following # qmake variables: VERSION, QMAKE_TARGET_COMPANY, QMAKE_TARGET_DESCRIPTION, # QMAKE_TARGET_COPYRIGHT, QMAKE_TARGET_PRODUCT, RC_LANG, RC_CODEPAGE. #****************************************************************************** # seq66.pro (qpseq66) #------------------------------------------------------------------------------ # vim: ts=4 sw=4 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: seq_portmidi/Makefile.am ================================================ #***************************************************************************** # Makefile.am (libseq_portmidi) #----------------------------------------------------------------------------- ## # \file Makefile.am # \library libseq_portmidi # \author Chris Ahlstrom # \date 2015-09-11 # \updates 2015-09-11 # \version $Revision$ # \license $MIDICVT_SUITE_GPL_LICENSE$ # # This file is a makefile for the libseq66 library project. This # makefile provides the skeleton needed to build the libseq66 project # directory using GNU autotools. # #----------------------------------------------------------------------------- #***************************************************************************** # Packing targets. #----------------------------------------------------------------------------- # # Always use Automake in foreign mode (adding foreign to # AUTOMAKE_OPTIONS in Makefile.am). Otherwise, it requires too many # boilerplate files from the GNU coding standards that aren't useful to # us. # #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #***************************************************************************** # EXTRA_DIST #----------------------------------------------------------------------------- EXTRA_DIST = #***************************************************************************** # SUBDIRS #----------------------------------------------------------------------------- SUBDIRS = include src #***************************************************************************** # DIST_SUBDIRS #----------------------------------------------------------------------------- # # DIST_SUBDIRS is used by targets that need to recurse into /all/ # directories, even those which have been conditionally left out of the # build. # # Precisely, DIST_SUBDIRS is used by: # # - make dist # - make distclean # - make maintainer-clean. # # All other recursive targets use SUBDIRS. # #----------------------------------------------------------------------------- DIST_SUBDIRS = $(SUBDIRS) #***************************************************************************** # all-local #----------------------------------------------------------------------------- all-local: @echo "Top source-directory 'top_srcdir' is $(top_srcdir)" @echo "* * * * * All libseq_gktmm2 build items completed * * * * *" #***************************************************************************** # Makefile.am (libseq_portmidi) #----------------------------------------------------------------------------- # vim: ts=3 sw=3 noet ft=automake #----------------------------------------------------------------------------- ================================================ FILE: seq_portmidi/README ================================================ README for seq_portmidi Library Chris Ahlstrom 2017-08-28 to 2019-01-24 This directory provides library code that I call "portmini" to myself. The Seq24 project could use the PortMIDI library in Linux or Windows. However, for our port to Windows, we want to use our own cut-down version of PortMIDI, so we do not have to deal with Java, CMake, and other stuff the Seq66 does not need. So we have removed the pm_csharp, pm_dylib, and pm_java directories, cleaned up the code a bit, and added automake support. # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: seq_portmidi/include/Makefile.am ================================================ #****************************************************************************** # Makefile.am (libseq_portmidi) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library libseq_portmidi library # \author Chris Ahlstrom # \date 2015-09-11 # \update 2021-06-22 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the libseq_portmidi C/C++ # library. # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #****************************************************************************** # CLEANFILES #------------------------------------------------------------------------------ CLEANFILES = *.gc* MOSTLYCLEANFILES = *~ #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = *.h #****************************************************************************** # Items from configure.ac #------------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ GIT_VERSION = @GIT_VERSION@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ prefix = @prefix@ includedir = @seq66includedir@ libdir = @seq66libdir@ datadir = @datadir@ datarootdir = @datarootdir@ seq66includedir = @seq66includedir@ seq66libdir = @seq66libdir@ #****************************************************************************** # Source files (Linux only) #---------------------------------------------------------------------------- pkginclude_HEADERS = \ mastermidibus_pm.hpp \ midibus_pm.hpp \ pminternal.h \ pmlinuxalsa.h \ pmlinux.h \ pmutil.h \ portmidi.h \ porttime.h #****************************************************************************** # uninstall-hook #------------------------------------------------------------------------------ uninstall-hook: @echo "Note: you may want to remove $(pkgincludedir) manually" #****************************************************************************** # Makefile.am (libseq_portmidi) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: seq_portmidi/include/mastermidibus_pm.hpp ================================================ #if ! defined SEQ66_MASTERMIDIBUS_PM_HPP #define SEQ66_MASTERMIDIBUS_PM_HPP /* * This file is part of seq24/seq66. * * seq24 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. * * seq24 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 seq24; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file mastermidibus_pm.hpp * * This module declares/defines the base class for MIDI I/O for Windows, using * our adaptation of the PortMidi API. * * \library seq66 application * \author Seq24 team; modifications by Chris Ahlstrom * \date 2015-07-24 * \updates 2019-03-10 * \license GNU GPLv2 or above * * This mastermidibus module is the Windows (and Linux now!) version of the * mastermidibus module using the PortMidi library. */ #include "midi/mastermidibase.hpp" /* seq66::mastermidibase ABC */ #include "portmidi.h" /* PortMIDI API header file */ namespace seq66 { /** * The class that "supervises" all of the midibus objects. This * implementation uses the PortMidi library, which supports Linux and * Windows, but not JACK or Mac OSX. */ class mastermidibus : public mastermidibase { public: mastermidibus () = delete; mastermidibus (int ppqn, midibpm bpm); virtual ~mastermidibus (); virtual bool activate (); protected: virtual void api_init (int ppqn, midibpm bpm); virtual bool api_get_midi_event (event * in); virtual void api_set_ppqn (int ppqn); virtual void api_set_beats_per_minute (midibpm bpm); /* * Are these necessary? * virtual void api_flush (); virtual void api_start (); virtual void api_stop (); virtual void api_continue_from (midipulse tick); virtual void api_port_start (int client, int port); * */ }; // class mastermidibus } // namespace seq66 #endif // SEQ66_MASTERMIDIBUS_PM_HPP /* * mastermidibus_pm.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_portmidi/include/midibus_pm.hpp ================================================ #if ! defined SEQ66_MIDIBUS_PM_HPP #define SEQ66_MIDIBUS_PM_HPP /* * This file is part of seq24/seq66. * * seq24 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. * * seq24 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 seq24; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midibus_pm.hpp * * This module declares/defines the base class for MIDI I/O for Windows. * * \library seq66 application * \author Seq24 team; modifications by Chris Ahlstrom * \date 2015-07-24 * \updates 2023-12-05 * \license GNU GPLv2 or above * * This midibus module is the Windows (PortMidi) version of the midibus * module. There's enough commonality that is was worth creating a base * class for all midibus classes. */ #include "midi/midibase.hpp" #include "portmidi.h" /* PortMIDI API header file */ namespace seq66 { class event; /** * This class implements with Windows version of the midibus object. */ class midibus : public midibase { /** * The master MIDI bus sets up the buss. */ friend class mastermidibus; private: /** * The PortMidiStream for the Windows/Linux/Mac OSX implementation. */ PortMidiStream * m_pms; /** * The Windows MIDI Mapper will lock the built-in GS Wavetable Synth, * making it unavailable all the time. This flag will allow the * error status to be skipped, eliminating a misleading/annoying message * at start-up. */ bool m_is_port_locked; public: /* * Supports a lot fewer parameters of Seq66 than do other APIs. */ midibus ( int index, int bus_id, int port_id, const std::string & client_name, const std::string & port_name ); virtual ~midibus (); virtual bool is_port_locked () const override { return m_is_port_locked; } void set_port_locked () { m_is_port_locked = true; } protected: virtual int api_poll_for_midi () override; virtual bool api_init_in () override; virtual bool api_init_out () override; virtual void api_continue_from (midipulse tick, midipulse beats) override; virtual void api_start () override; virtual void api_stop () override; virtual void api_clock (midipulse tick) override; virtual void api_play (const event * e24, midibyte channel) override; /* * Functions not implemented in PortMIDI. For example, the "sub" * functions, which subscribe the application to a "virtual" port, can * be implemented in ALSA, but not in Windows. * * virtual bool api_init_out_sub (); // subscribe to output * virtual bool api_init_in_sub (); // subscribe to input * virtual bool api_deinit_out (); // unsubscribe a port * virtual bool api_deinit_in (); // unsubscribe a port * * We should be able to implement this in a "sysex_fix" branch: * * virtual void api_sysex (const event * e24); * * This function should be able to be implemented in Windows and ALSA: * * virtual void api_flush (); */ }; // class midibus (portmidi) } // namespace seq66 #endif // SEQ66_MIDIBUS_PM_HPP /* * midibus_pm.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_portmidi/include/pmerrmm.h ================================================ #ifndef SEQ66_PMERRMM_H #define SEQ66_PMERRMM_H /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmerrmm.h * * Provides system-specific error-messages for Windows. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-04-21 * \updates 2020-07-10 * \license GNU GPLv2 or above * */ #include "pminternal.h" /* internals and platform macros */ #if defined SEQ66_PLATFORM_WINDOWS #if defined __cplusplus extern "C" { #endif extern const char * midi_io_get_dev_caps_error ( const char * devicename, const char * functionname, MMRESULT errcode ); #if defined __cplusplus } // extern "C" #endif #endif // defined SEQ66_PLATFORM_WINDOWS #endif // SEQ66_PMERRMM_H /* * pmerrmm.h * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/include/pminternal.h ================================================ #ifndef SEQ66_PMINTERNAL_H #define SEQ66_PMINTERNAL_H /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pminternal.h * * This file is included by files that implement library internals. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2023-06-28 * \license GNU GPLv2 or above * * Here is a guide to implementers: * * - Provide an initialization function similar to pm_winmm_init(). * - Add your initialization function to pm_init(). Note that your init * function should never require non-standard libraries or fail in any * way. If the interface is not available, simply do not call * pm_add_device(). This means that non-standard libraries should try to do * dynamic linking at runtime using a DLL and return without error if the * DLL cannot be found or if there is any other failure. * - Implement functions as indicated in pm_fns_type to open, read, write, * close, etc. * - Call pm_add_device() for each input and output device, passing it a * pm_fns_type structure. * * Assumptions about pm_fns_type functions are given below. */ #include #include "seq66_platform_macros.h" #if defined _WINDLL #define PMEXPORT __declspec(dllexport) #else #define PMEXPORT #endif /** * Rather than having users install a special .h file for Windows, just put * the required definitions inline here. The porttime.h header file uses * these too, so the definitions are (unfortunately) duplicated there. */ #if defined SEQ66_PLATFORM_WINDOWS // WIN32 #ifndef INT32_DEFINED #define INT32_DEFINED typedef int int32_t; typedef unsigned int uint32_t; #endif #else #include // Linux and OS X have stdint.h #endif // SEQ66_PLATFORM_WINDOWS /** * Default size of buffers for SysEx transmission. */ #define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024 /** * Length of a message header? */ #define HDRLENGTH 50 /** * Any host error message will occupy less than this number of characters. */ #define PM_HOST_ERROR_MSG_LEN 224 #define PM_STRING_MAX 256 #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE 1 #endif /** * TRUE if t1 before t2. */ #define PmBefore(t1, t2) ((t1-t2) < 0) #define none_write_flush pm_fail_timestamp_fn #define none_sysex pm_fail_timestamp_fn #define none_poll pm_fail_fn #define success_poll pm_success_fn #define MIDI_REALTIME_MASK 0xf8 #define is_real_time(msg) \ ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK) #if defined __cplusplus extern "C" { #endif /** * This typedef is the same as seq66::midibyte, but is for use by the C * modules of PortMIDI. */ typedef unsigned char midibyte_t; /** * Pm_Message() encodes a short MIDI message into a 32-bit word. If data1 * and/or data2 are not present, use zero. * * Pm_MessageStatus(), Pm_MessageData1(), and * Pm_MessageData2() extract fields from a 32-bit midi message. */ #define Pm_Message(status, data1, data2) ((((data2) << 16) & 0xFF0000) | \ (((data1) << 8) & 0xFF00) | ((status) & 0xFF)) #define Pm_MessageStatus(msg) ((msg) & 0xFF) #define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF) #define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF) typedef int32_t PmMessage; /**< see PmEvent */ /** * PmTimestamp is used to represent a millisecond clock with arbitrary * start time. The type is used for all MIDI timestampes and clocks. */ typedef int32_t PmTimestamp; /** * Indicates the lack of a device. :-) */ #define pmNoDevice -1 /** * Indicates the structure version of PmDeviceInfo. */ #define PM_STRUCTURE_VERSION 950 /* 0.95.0 */ /** * Holds information about the device and its platform. We are going to * extend this structure by adding the client and port numbers. These * will be the ALSA client and port numbers under Linux, and just the ordinal * numbers under Windows. We will also update structVersion. This value was * never assigned, and was just a random value. We will start using it with * a value of 950 (for 0.95.0 in Seq66). */ typedef struct { int structVersion; /**< This internal structure version. */ const char * interf; /**< Underlying MIDI API, MMSystem, DirectX.i */ const char * name; /**< Device name, e.g. USB MidiSport 1x1. */ int input; /**< True iff input is available. */ int output; /**< True iff output is available. */ int opened; /**< Generic PortMidi code, argument-checking. */ int mapper; /**< True iff this device is a MIDI Mapper. */ int client; /**< Provides the (ALSA) client number. */ int port; /**< Provides the (ALSA) port number. */ } PmDeviceInfo; /** * A type definition for a timer callback. This has the issue that the * PmTimeProcPtr function type has a void pointer parameter, while Pt_Time() * does not. */ typedef PmTimestamp (* PmTimeProcPtr) (void * time_info); /** * Provides an obvious declaration for PortMIDI queues. */ typedef void PmQueue; /** * All midi data comes in the form of PmEvent structures. A SysEx message is * encoded as a sequence of PmEvent structures, with each structure carrying * 4 bytes of the message, i.e. only the first PmEvent carries the status * byte. * * Note that MIDI allows nested messages: the so-called "real-time" MIDI * messages can be inserted into the MIDI byte stream at any location, * including within a SysEx message. MIDI real-time messages are one-byte * messages used mainly for timing (see the MIDI spec). PortMidi retains the * order of non-real-time MIDI messages on both input and output, but it does * not specify exactly how real-time messages are processed. This is * particulary problematic for MIDI input, because the input parser must * either prepare to buffer an unlimited number of SysEx message bytes or to * buffer an unlimited number of real-time messages that arrive embedded in a * long SysEx message. To simplify things, the input parser is allowed to * pass real-time MIDI messages embedded within a SysEx message, and it is up * to the client to detect, process, and remove these messages as they * arrive. * * When receiving SysEx messages, the SysEx message is terminated by either * an EOX status byte (anywhere in the 4 byte messages) or by a non-real-time * status byte in the low order byte of the message. If you get a * non-real-time status byte but there was no EOX byte, it means the SysEx * message was somehow truncated. This is not considered an error; e.g., a * missing EOX can result from the user disconnecting a MIDI cable during * SysEx transmission. * * A real-time message can occur within a SysEx message. A real-time message * will always occupy a full PmEvent with the status byte in the low-order * byte of the PmEvent message field. (This implies that the byte-order of * SysEx bytes and real-time message bytes may not be preserved -- for * example, if a real-time message arrives after 3 bytes of a SysEx message, * the real-time message will be delivered first. The first word of the SysEx * message will be delivered only after the 4th byte arrives, filling the * 4-byte PmEvent message field. * * The timestamp field is observed when the output port is opened with a * non-zero latency. A timestamp of zero means "use the current time", which * in turn means to deliver the message with a delay of latency (the latency * parameter used when opening the output port.) Do not expect PortMidi to * sort data according to timestamps -- messages should be sent in the * correct order, and timestamps MUST be non-decreasing. See also "Example" * for Pm_OpenOutput() above. * * A SysEx message will generally fill many PmEvent structures. On output to * a PortMidiStream with non-zero latency, the first timestamp on SysEx * message data will determine the time to begin sending the message. * PortMidi implementations may ignore timestamps for the remainder of the * SysEx message. * * On input, the timestamp ideally denotes the arrival time of the status * byte of the message. The first timestamp on SysEx message data will be * valid. Subsequent timestamps may denote when message bytes were actually * received, or they may be simply copies of the first timestamp. * * Timestamps for nested messages: If a real-time message arrives in the * middle of some other message, it is enqueued immediately with the * timestamp corresponding to its arrival time. The interrupted non-real-time * message or 4-byte packet of SysEx data will be enqueued later. The * timestamp of interrupted data will be equal to that of the interrupting * real-time message to insure that timestamps are non-decreasing. */ typedef struct { PmMessage message; PmTimestamp timestamp; } PmEvent; /** * Device enumeration mechanism. * Device ids range from 0 to Pm_CountDevices()-1. */ typedef int PmDeviceID; /** * List of PortMIDI errors. * * - pmNoData is a "No error" return, also indicates no data available. * - pmGotData is a "No error" return, also indicates data available. * - pmInvalidDeviceId is an out of range or output device when input is * requested or input device when output is requested or device is * already opened. * - pmBadPtr means the PortMidiStream parameter is NULL, or stream is not * opened, or stream is output when input is required, or stream is input * when output is required. * - pmBadData means illegal MIDI data, e.g. missing EOX. * - pmBufferMaxSize means the buffer is already as large as it can be. * * Note: * * If you add a new error type, be sure to update Pm_GetErrorText(). */ typedef enum { pmHostError = -10000, pmNoError = 0, /* MMSYSERR_NOERROR */ pmNoData = 0, pmGotData = 1, /* MMSYSERR_ERROR */ pmInvalidDeviceId, /* MMSYSERR_BADDEVICEID */ pmInsufficientMemory, /* MMSYSERR_NOTENABLED ! */ pmBufferTooSmall, /* MMSYSERR_ALLOCATED */ pmBufferOverflow, /* MMSYSERR_INVALHANDLE ! */ pmBadPtr, pmBadData, /* MMSYSERR_NOMEM ! */ pmInternalError, pmBufferMaxSize, pmDeviceClosed, pmDeviceOpen, /* MSYSERR_INVALPARAM */ pmWriteToInput, pmReadFromOutput, pmErrOther, pmDeviceLocked, /* grabbed by MIDI Mapper */ /* * If you add a new error type here, be sure to update Pm_GetErrorText()! */ pmErrMax } PmError; /* * These are defined in system-specific files. */ extern void * pm_alloc (size_t s); extern void pm_free (void * ptr); struct pm_internal_struct; /* forward declaration */ /* * These do not use PmInternal because it is not defined yet.... */ typedef PmError (* pm_write_short_fn) ( struct pm_internal_struct * midi, PmEvent * buffer ); typedef PmError (* pm_begin_sysex_fn) ( struct pm_internal_struct * midi, PmTimestamp timestamp ); typedef PmError (* pm_end_sysex_fn) ( struct pm_internal_struct * midi, PmTimestamp timestamp ); typedef PmError (* pm_write_byte_fn) ( struct pm_internal_struct * midi, midibyte_t byte, PmTimestamp timestamp ); typedef PmError (* pm_write_realtime_fn) ( struct pm_internal_struct * midi, PmEvent * buffer ); typedef PmError (* pm_write_flush_fn) ( struct pm_internal_struct * midi, PmTimestamp timestamp ); typedef PmTimestamp (* pm_synchronize_fn) (struct pm_internal_struct * midi); /** * pm_open_fn() should clean up all memory and close the device if any part * of the open fails. */ typedef PmError (* pm_open_fn) ( struct pm_internal_struct * midi, void * driverInfo ); typedef PmError (* pm_abort_fn) (struct pm_internal_struct * midi); /** * pm_close_fn() should clean up all memory and close the device if any part * of the close fails. */ typedef PmError (* pm_close_fn) (struct pm_internal_struct * midi); typedef PmError (* pm_poll_fn) (struct pm_internal_struct * midi); typedef void (* pm_host_error_fn) ( struct pm_internal_struct * midi, // PmInternal * not defined until below char * msg, unsigned ); typedef unsigned (* pm_has_host_error_fn) ( struct pm_internal_struct * midi ); typedef struct { pm_write_short_fn write_short; /**< Output short MIDI msg. */ pm_begin_sysex_fn begin_sysex; /**< Prepare to send SysEx message. */ pm_end_sysex_fn end_sysex; /**< Marks end of SysEx message. */ pm_write_byte_fn write_byte; /**< Accumulate 1 more SysEx byte. */ pm_write_realtime_fn write_realtime; /**< Send real-time message in SysEx. */ pm_write_flush_fn write_flush; /**< Send accumulated unsent data. */ pm_synchronize_fn synchronize; /**< Synch PM time to stream time. */ pm_open_fn open; /**< Open MIDI device. */ pm_abort_fn abort; /**< Abort. */ pm_close_fn close; /**< Close the device. */ pm_poll_fn poll; /**< Read events into PM buffer. */ pm_has_host_error_fn has_host_error; /**< Device has host error message */ pm_host_error_fn host_error; /**< Readable device error, clears/resets. */ } pm_fns_node, * pm_fns_type; typedef struct { /** * Some portmidi state also saved in here (for autmatic * device closing (see PmDeviceInfo struct). */ PmDeviceInfo pub; /** * ID number passed to win32 multimedia API open. */ void * descriptor; /** * Points to PmInternal device, allows automatic device closing. */ void * internalDescriptor; pm_fns_type dictionary; } descriptor_node, * descriptor_type; /** * When open fails, the dictionary gets this set of functions. */ extern pm_fns_node pm_none_dictionary; extern descriptor_type pm_descriptors; extern int pm_descriptor_index; extern int pm_descriptor_max; typedef uint32_t (* time_get_proc_type) (void * time_info); typedef struct pm_internal_struct { /** * which device is open (index to descriptors). */ int device_id; /** * MIDI_IN, or MIDI_OUT. */ short write_flag; /** * where to get the time. */ PmTimeProcPtr time_proc; /** * pass this to get_time(). */ void * time_info; /** * how big is the buffer or queue?. */ int32_t buffer_len; /** * To be documented. */ PmQueue * queue; /** * Time delay in ms between timestamps and actual output set to zero to * get immediate, simple blocking output if latency is zero, timestamps * will be ignored; if midi input device, this field ignored. */ int32_t latency; /** * When SysEx status is seen, this flag becomes true until EOX is seen. * When true, new data is appended to the stream of outgoing bytes. When * overflow occurs, SysEx data is dropped (until an EOX or non-real-timei * status byte is seen) so that, if the overflow condition is cleared, we * don't start sending data from the middle of a SysEx message. If a * SysEx message is filtered, sysex_in_progress is false, causing the * message to be dropped. */ int sysex_in_progress; /** * buffer for 4 bytes of SysEx data. */ PmMessage sysex_message; /** * how many bytes in sysex_message so far. */ int sysex_message_count; /** * flags that filter incoming message classes. */ int32_t filters; /** * filter incoming messages based on channel. */ int32_t channel_mask; /** * timestamp of last message. */ PmTimestamp last_msg_time; /** * time of last synchronization. */ PmTimestamp sync_time; /** * set by PmWrite to current time. */ PmTimestamp now; /** * initially true, used to run first synchronization. */ int first_message; /** * implementation functions. */ pm_fns_type dictionary; /** * system-dependent state. */ void * descriptor; /* * The following are used to expedite SysEx data on Windows, in debug * mode. Based on some profiling, these optimizations cut the time to * process SysEx bytes from about 7.5 to 0.26 usec/byte, but this does * not count time in the driver, so I don't know if it is important. */ /** * addr of ptr to SysEx data. */ midibyte_t * fill_base; /** * offset of next SysEx byte. */ uint32_t * fill_offset_ptr; /** * how many SysEx bytes to write. */ uint32_t fill_length; /* changed from int32_t */ } PmInternal; /* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */ extern void pm_init (void); extern void pm_term (void); /* defined by portMidi, used by pmwinmm */ extern PmError none_write_short (PmInternal * midi, PmEvent * buffer); extern PmError none_write_byte ( PmInternal * midi, midibyte_t byte, PmTimestamp timestamp ); extern PmTimestamp none_synchronize (PmInternal * midi); extern PmError pm_fail_fn (PmInternal * midi); extern PmError pm_fail_timestamp_fn (PmInternal * midi, PmTimestamp timestamp); extern PmError pm_success_fn (PmInternal * midi); extern PmError pm_add_device ( char * interf, char * name, int input, void * descriptor, pm_fns_type dictionary, int client, int port ); extern uint32_t pm_read_bytes ( PmInternal * midi, const midibyte_t * data, int len, PmTimestamp timestamp ); extern void pm_read_short (PmInternal * midi, PmEvent * event); extern int pm_find_default_device (char * pattern, int is_input); #if defined __cplusplus } // extern "C" #endif #endif // SEQ66_PMINTERNAL_H /* * pminternal.h * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/include/pmlinux.h ================================================ #ifndef SEQ66_PMLINUX_H #define SEQ66_PMLINUX_H /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmlinux.h * * Device ID functions for Linux. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2018-04-10 * \license GNU GPLv2 or above * * System-specific definitions. */ #include "pminternal.h" /* PMDeviceID */ #if defined __cplusplus extern "C" { #endif extern PmDeviceID pm_default_input_device_id; extern PmDeviceID pm_default_output_device_id; #if defined __cplusplus } // extern "C" #endif #endif // SEQ66_PMLINUX_H /* * pmlinux.h * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/include/pmlinuxalsa.h ================================================ #ifndef SEQ66_PMLINUXALSA_H #define SEQ66_PMLINUXALSA_H /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmlinuxalsa.h * * ALSA setup/teardown functions for Linux. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2018-04-10 * \license GNU GPLv2 or above * * System-specific definitions for the Linux ALSA sub-system. Compare to the * pmwinmm.h header file. */ #include "pminternal.h" /* PMDeviceID */ #if defined __cplusplus extern "C" { #endif extern PmError pm_linuxalsa_init (void); extern void pm_linuxalsa_term (void); #if defined __cplusplus } // extern "C" #endif #endif // SEQ66_PMLINUXALSA_H /* * pmlinuxalsa.h * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/include/pmmac.h ================================================ #ifndef SEQ66_PMMAC_H #define SEQ66_PMMAC_H /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmmac.h * * Device ID functions for Mac OSX. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2018-05-13 * \updates 2018-05-13 * \license GNU GPLv2 or above * * Provides system-specific definitions for Mac OSX. */ #include "pminternal.h" /* PmDeviceID typedef */ #if defined __cplusplus extern "C" { #endif extern PmDeviceID pm_default_input_device_id; extern PmDeviceID pm_default_output_device_id; #if defined __cplusplus } // extern "C" #endif #endif // SEQ66_PMMAC_H /* * pmmac.h * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/include/pmutil.h ================================================ #ifndef SEQ66_PMUTIL_H #define SEQ66_PMUTIL_H /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmutil.h * * Some helpful utilities for building MIDI applications that use PortMidi. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2018-04-10 * \license GNU GPLv2 or above */ #include "pminternal.h" /* they all include this one anyway */ #if defined __cplusplus extern "C" { #endif /** * A single-reader, single-writer queue is created by Pm_QueueCreate(), which * takes the number of messages and the message size as parameters. The queue * only accepts fixed sized messages. Returns NULL if memory cannot be * allocated. * * This queue implementation uses the "light pipe" algorithm which operates * correctly even with multi-processors and out-of-order memory writes. (See * Alexander Dokumentov, "Lock-free Interprocess Communication," Dr. Dobbs * Portal, http://www.ddj.com/, articleID=189401457, June 15, 2006. This * algorithm requires that messages be translated to a form where no words * contain zeros. Each word becomes its own "data valid" tag. Because of this * translation, we cannot return a pointer to data still in the queue when * the "peek" method is called. Instead, a buffer is preallocated so that * data can be copied there. Pm_QueuePeek() dequeues a message into this * buffer and returns a pointer to it. A subsequent Pm_Dequeue() will copy * from this buffer. * * This implementation does not try to keep reader/writer data in separate * cache lines or prevent thrashing on cache lines. However, this algorithm * differs by doing inserts/removals in units of messages rather than units * of machine words. Some performance improvement might be obtained by not * clearing data immediately after a read, but instead by waiting for the end * of the cache line, especially if messages are smaller than cache lines. * See the Dokumentov article for explanation. * * The algorithm is extended to handle "overflow" reporting. To report an * overflow, the sender writes the current tail position to a field. The * receiver must acknowlege receipt by zeroing the field. The sender will not * send more until the field is zeroed. * * Pm_QueueDestroy() destroys the queue and frees its storage. */ PMEXPORT PmQueue * Pm_QueueCreate (long num_msgs, int32_t bytes_per_msg); PMEXPORT PmError Pm_QueueDestroy (PmQueue * queue); /** * Pm_Dequeue() removes one item from the queue, copying it into msg. * Returns 1 if successful, and 0 if the queue is empty. Returns * pmBufferOverflow if what would have been the next thing in the queue was * dropped due to overflow. (So when overflow occurs, the receiver can * receive a queue full of messages before getting the overflow report. This * protocol ensures that the reader will be notified when data is lost due to * overflow. */ PMEXPORT PmError Pm_Dequeue (PmQueue * queue, void * msg); /** * Pm_Enqueue() inserts one item into the queue, copying it from msg. * Returns pmNoError if successful and pmBufferOverflow if the queue was * already full. If pmBufferOverflow is returned, the overflow flag is set. */ PMEXPORT PmError Pm_Enqueue (PmQueue * queue, void * msg); /** * Pm_QueueFull() returns non-zero if the queue is full. * Pm_QueueEmpty() returns non-zero if the queue is empty. * * Either condition may change immediately because a parallel enqueue or * dequeue operation could be in progress. Furthermore, Pm_QueueEmpty() is * optimistic: it may say false, when due to out-of-order writes, the full * message has not arrived. Therefore, Pm_Dequeue() could still return 0 * after Pm_QueueEmpty() returns false. On the other hand, Pm_QueueFull() is * pessimistic: if it returns false, then Pm_Enqueue() is guaranteed to * succeed. * * Error conditions: Pm_QueueFull() returns pmBadPtr if queue is NULL. * Pm_QueueEmpty() returns FALSE if queue is NULL. */ PMEXPORT int Pm_QueueFull (PmQueue * queue); PMEXPORT int Pm_QueueEmpty (PmQueue * queue); /** * Pm_QueuePeek() returns a pointer to the item at the head of the queue, or * NULL if the queue is empty. The item is not removed from the queue. * Pm_QueuePeek() will not indicate when an overflow occurs. If you want to * get and check pmBufferOverflow messages, use the return value of * Pm_QueuePeek() *only* as an indication that you should call Pm_Dequeue(). * At the point where a direct call to Pm_Dequeue() would return * pmBufferOverflow, Pm_QueuePeek() will return NULL but internally clear the * pmBufferOverflow flag, enabling Pm_Enqueue() to resume enqueuing messages. * A subsequent call to Pm_QueuePeek() will return a pointer to the first * message *after* the overflow. Using this as an indication to call * Pm_Dequeue(), the first call to Pm_Dequeue() will return pmBufferOverflow. * The second call will return success, copying the same message pointed to * by the previous Pm_QueuePeek(). * * When to use Pm_QueuePeek(): (1) when you need to look at the message data * to decide who should be called to receive it. (2) when you need to know a * message is ready but cannot accept the message. * * Note that Pm_QueuePeek() is not a fast check, so if possible, you might as * well just call Pm_Dequeue() and accept the data if it is there. */ PMEXPORT void * Pm_QueuePeek (PmQueue * queue); /** * Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow * condition to the reader (dequeuer). E.g. when transfering data from the OS * to an application, if the OS indicates a buffer overrun, Pm_SetOverflow() * can be used to insure that the reader receives a pmBufferOverflow result * from Pm_Dequeue(). Returns pmBadPtr if queue is NULL, returns * pmBufferOverflow if buffer is already in an overflow state, returns * pmNoError if successfully set overflow state. */ PMEXPORT PmError Pm_SetOverflow (PmQueue * queue); #if defined __cplusplus } // extern "C" #endif #endif // SEQ66_PMUTIL_H /* * pmutil.h * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/include/pmwinmm.h ================================================ #ifndef SEQ66_PMWINMM_H #define SEQ66_PMWINMM_H /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmwinmm.h * * Windows multi-media API setup and teardown functions. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2018-04-10 * \license GNU GPLv2 or above * * Provides system-specific definitions for Windows. */ #if defined __cplusplus extern "C" { #endif extern void pm_winmm_init (void); extern void pm_winmm_term (void); #if defined __cplusplus } // extern "C" #endif #endif // SEQ66_PMWINMM_H /* * pmwinmm.h * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/include/portmidi.h ================================================ #ifndef SEQ66_PORTMIDI_H #define SEQ66_PORTMIDI_H /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file portmidi.h * * PortMidi Portable Real-Time MIDI Library, PortMidi API Header File, * Latest version available at: * * http://sourceforge.net/projects/portmedia * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2024-01-05 * \license GNU GPLv2 or above * * Copyright (c) 1999-2000 Ross Bencina and Phil Burk * Copyright (c) 2001-2006 Roger B. Dannenberg * * 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. * * The text above constitutes the entire PortMidi license; however, the * PortMusic community also makes the following non-binding requests: * * Any person wishing to distribute modifications to the Software is requested * to send the modifications to the original developer so that they can be * incorporated into the canonical version. It is also requested that these * non-binding requests be included along with the license above. */ #if defined __cplusplus extern "C" { #endif #include "pminternal.h" /* not quite internal now, refactoring */ /** * Common memory functions basically the same on all platforms, so moved * to here from the pmlinux, pmmac, and pmwin modules. */ PMEXPORT void pm_free (void * ptr); PMEXPORT void * pm_alloc (size_t s); /** * A single PortMidiStream is a descriptor for an open MIDI device. */ typedef void PortMidiStream; /** * Ugh. */ #define PmStream PortMidiStream PMEXPORT PmError Pm_Initialize (void); PMEXPORT PmError Pm_Terminate (void); PMEXPORT int Pm_HasHostError (PortMidiStream * stream); PMEXPORT const char * Pm_GetErrorText (PmError errnum); #if defined PM_GETHOSTERRORTEXT PMEXPORT void Pm_GetHostErrorText (char * msg, unsigned int len); #endif PMEXPORT int Pm_CountDevices (void); PMEXPORT const PmDeviceInfo * Pm_GetDeviceInfo (PmDeviceID id); PMEXPORT PmError Pm_OpenInput ( PortMidiStream ** stream, PmDeviceID inputDevice, void * inputDriverInfo, int32_t bufferSize, PmTimeProcPtr time_proc, void * time_info ); PMEXPORT PmError Pm_OpenOutput ( PortMidiStream ** stream, PmDeviceID outputDevice, void * outputDriverInfo, int32_t bufferSize, PmTimeProcPtr time_proc, void * time_info, int32_t latency ); PMEXPORT void c_millisleep (int ms); /* * Filter bit-mask definitions */ /** * filter active sensing messages (0xFE): */ #define PM_FILT_ACTIVE (1 << 0x0E) /** * filter system exclusive messages (0xF0): */ #define PM_FILT_SYSEX (1 << 0x00) /** * filter MIDI clock message (0xF8) */ #define PM_FILT_CLOCK (1 << 0x08) /** * filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */ #define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B)) /** * filter tick messages (0xF9) */ #define PM_FILT_TICK (1 << 0x09) /** * filter undefined FD messages */ #define PM_FILT_FD (1 << 0x0D) /** * filter undefined real-time messages */ #define PM_FILT_UNDEFINED PM_FILT_FD /** * filter reset messages (0xFF) */ #define PM_FILT_RESET (1 << 0x0F) /** * filter all real-time messages */ #define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | \ PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET | PM_FILT_TICK) /** * filter note-on and note-off (0x90-0x9F and 0x80-0x8F */ #define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18)) /** * filter channel aftertouch (most midi controllers use this) (0xD0-0xDF) */ #define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D) /** * per-note aftertouch (0xA0-0xAF) */ #define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A) /** * filter both channel and poly aftertouch */ #define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH) /** * Program changes (0xC0-0xCF) */ #define PM_FILT_PROGRAM (1 << 0x1C) /** * Control Changes (CC's) (0xB0-0xBF) */ #define PM_FILT_CONTROL (1 << 0x1B) /** * Pitch Bender (0xE0-0xEF */ #define PM_FILT_PITCHBEND (1 << 0x1E) /** * MIDI Time Code (0xF1) */ #define PM_FILT_MTC (1 << 0x01) /** * Song Position (0xF2) */ #define PM_FILT_SONG_POSITION (1 << 0x02) /** * Song Select (0xF3) */ #define PM_FILT_SONG_SELECT (1 << 0x03) /** * Tuning request (0xF6) */ #define PM_FILT_TUNE (1 << 0x06) /** * All System Common messages (mtc, song position, song select, tune request) */ #define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | PM_FILT_SONG_SELECT | PM_FILT_TUNE) PMEXPORT PmError Pm_SetFilter (PortMidiStream * stream, int32_t filters); #define Pm_Channel(channel) (1 << (channel)) PMEXPORT PmError Pm_SetChannelMask (PortMidiStream * stream, int mask); PMEXPORT PmError Pm_Abort (PortMidiStream * stream); PMEXPORT PmError Pm_Close (PortMidiStream * stream); PMEXPORT int Pm_Read ( PortMidiStream * stream, PmEvent * buffer, int32_t length ); PMEXPORT PmError Pm_Synchronize (PortMidiStream * stream); PMEXPORT PmError Pm_Poll (PortMidiStream * stream); PMEXPORT PmError Pm_Write ( PortMidiStream * stream, PmEvent * buffer, int32_t length ); PMEXPORT PmError Pm_WriteShort ( PortMidiStream * stream, PmTimestamp when, int32_t msg ); PMEXPORT PmError Pm_WriteSysEx ( PortMidiStream * stream, PmTimestamp when, midibyte_t * msg ); /* * New section for accessing static options in the portmidi module. */ PMEXPORT void Pm_set_initialized (int flag); PMEXPORT int Pm_initialized (void); PMEXPORT void Pm_set_exit_on_error (int flag); PMEXPORT int Pm_exit_on_error (void); PMEXPORT void Pm_set_show_debug (int flag); PMEXPORT int Pm_show_debug (void); PMEXPORT void Pm_set_error_present (int flag); PMEXPORT int Pm_error_present (void); PMEXPORT void Pm_set_hosterror_text (const char * msg); PMEXPORT const char * Pm_hosterror_text (void); PMEXPORT char * Pm_hosterror_text_mutable (void); PMEXPORT void Pm_set_hosterror (int flag); PMEXPORT int Pm_hosterror (void); PMEXPORT const char * Pm_error_message (void); PMEXPORT int Pm_device_opened (int deviceid); PMEXPORT int Pm_device_count (void); PMEXPORT void Pm_print_devices (void); /* * New section for writing messages to a log buffer for better debugging. We * need to be able to trace what's happening, and not just see the first error * that cropped up. */ PMEXPORT void pm_log_buffer_alloc (void); PMEXPORT void pm_log_buffer_free (void); PMEXPORT void pm_log_buffer_append (const char * msg); PMEXPORT const char * pm_log_buffer (void); #if defined __cplusplus } // extern "C" #endif #endif // SEQ66_PORTMIDI_H /* * pminternal.h * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/include/porttime.h ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file porttime.h * * A portable interface to millisecond timer. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2024-01-05 * \license GNU GPLv2 or above * * change log for porttime: * * 10-Jun-03 Mark Nelson & RBD * Boost priority of timer thread in ptlinux.c implementation. */ #include "pminternal.h" /* int32_t */ /* Should there be a way to choose the source of time here? */ #if defined __cplusplus extern "C" { #endif #ifndef PMEXPORT #if defined _WINDLL #define PMEXPORT __declspec(dllexport) #else #define PMEXPORT #endif #endif /** * Error codes. */ typedef enum { ptNoError = 0, /**< Success. */ ptHostError = -10000, /**< A system-specific error occurred. */ ptAlreadyStarted, /**< Can't start timer, it is already started. */ ptAlreadyStopped, /**< Can't stop timer, it is already stopped. */ ptInsufficientMemory /**< Memory could not be allocated. */ } PtError; typedef int32_t PtTimestamp; typedef void (PtCallback) (PtTimestamp timestamp, void * userData); /* * Pt_Start() starts a real-time service. * * \param resolution * is the timer resolution in ms. The time will advance every * resolution ms. * * \param callback * Is a function pointer to be called every resolution ms. * * \param userData * Is passed to callback as a parameter. * * \return * Upon success, returns ptNoError. See PtError for other values. */ PMEXPORT PtError Pt_Start ( int resolution, PtCallback * callback, void * userData ); /** * Pt_Stop() stops the timer. * * return value: * Upon success, returns ptNoError. See PtError for other values. */ PMEXPORT PtError Pt_Stop (void); /** * Pt_Started() returns true iff the timer is running. */ PMEXPORT int Pt_Started (void); /** * Pt_Time() returns the current time in ms. */ PMEXPORT PtTimestamp Pt_Time (void); /** * Pt_Sleep() pauses, allowing other threads to run. * * \param duration * The length of the pause in milliseconds. The true duration of the * pause may be rounded to the nearest or next clock tick as determined * by resolution in Pt_Start(). */ PMEXPORT void Pt_Sleep (int32_t duration); /* * New functions to support setting the tempo and PPQN, as well as * converting PortMidi time to MIDI pulses (ticks). */ PMEXPORT void Pt_Set_Midi_Timing (double bpm, int ppqn); PMEXPORT long Pt_Time_To_Pulses (int tsms); PMEXPORT void Pt_Set_Bpm (double bpm); PMEXPORT void Pt_Set_Ppqn (int ppqn); PMEXPORT double Pt_Get_Bpm (void); PMEXPORT int Pt_Get_Tempo_Microseconds (void); PMEXPORT int Pt_Get_Ppqn (void); #if defined __cplusplus } // extern "C" #endif /* * porttime.h * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/seq_portmidi.pro ================================================ #****************************************************************************** # seq_portmidi.pro (pseq66) #------------------------------------------------------------------------------ ## # \file seq_portmidi.pro # \library qpseq66 application # \author Chris Ahlstrom # \date 2018-04-08 # \update 2024-01-07 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Created by and for Qt Creator. This file was created for editing the project # sources only. You may attempt to use it for building too, by modifying this # file here. # # Important: # # This project file is designed only for Qt 5 (and above?). # #------------------------------------------------------------------------------ message($$_PRO_FILE_PWD_) TEMPLATE = lib CONFIG += staticlib config_prl qtc_runnable # These are needed to set up seq66_platform_macros: CONFIG(debug, debug|release) { DEFINES += DEBUG } else { DEFINES += NDEBUG } DEFINES += "SEQ66_MIDILIB=portmidi" DEFINES += "SEQ66_PORTMIDI_SUPPORT=1" TARGET = seq_portmidi # Common: HEADERS += \ include/mastermidibus_pm.hpp \ include/midibus_pm.hpp \ include/pminternal.h \ include/pmutil.h \ include/portmidi.h \ include/porttime.h # Linux # # Added a flag to avoid a warning about casting between function types at # line 1821 of portmidi.c. unix:!macx { HEADERS += include/pmlinux.h include/pmlinuxalsa.h DEFINES -= _UNICODE QMAKE_CFLAGS_WARN_ON += -Wno-cast-function-type } # Mac OSX macx { HEADERS += include/pmmac.h include/pmmacosxcm.h } # Windows windows { HEADERS += include/pmerrmm.h include/pmwinmm.h DEFINES -= UNICODE DEFINES -= _UNICODE QMAKE_CFLAGS_WARN_ON += -Wno-unused-parameter -Wno-cast-function-type } # Common SOURCES += \ src/mastermidibus.cpp \ src/midibus.cpp \ src/pmutil.c \ src/portmidi.c \ src/porttime.c # Linux unix:!macx { SOURCES += src/pmlinux.c src/pmlinuxalsa.c src/ptlinux.c } # Mac OSX # # We have removed the readbinaryplist.c file; we use INI-style files. macx { SOURCES += src/pmmac.c pmmacosxcm.c src/ptmacosx_mach.c } # Windows windows: SOURCES += src/pmwin.c \ src/pmerrmm.c \ src/pmwinmm.c \ src/ptwinmm.c INCLUDEPATH = ../include/qt/portmidi include ../libseq66/include #****************************************************************************** # seq_portmidi.pro (qpseq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: seq_portmidi/src/Makefile.am ================================================ #****************************************************************************** # Makefile.am (libseq_portmidi) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library libseq_portmidi library # \author Chris Ahlstrom # \date 2015-09-11 # \update 2024-01-06 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the libseq_portmidi # C/C++ library. # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #****************************************************************************** # CLEANFILES #------------------------------------------------------------------------------ CLEANFILES = *.gc* MOSTLYCLEANFILES = *~ #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = #****************************************************************************** # Items from configure.ac #------------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ SEQ66_API_MAJOR = @SEQ66_API_MAJOR@ SEQ66_API_MINOR = @SEQ66_API_MINOR@ SEQ66_API_PATCH = @SEQ66_API_PATCH@ SEQ66_API_VERSION = @SEQ66_API_VERSION@ SEQ66_LT_CURRENT = @SEQ66_LT_CURRENT@ SEQ66_LT_REVISION = @SEQ66_LT_REVISION@ SEQ66_LT_AGE = @SEQ66_LT_AGE@ SEQ66_LIBTOOL_VERSION = @SEQ66_LIBTOOL_VERSION@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ seq66docdir = @seq66docdir@ seq66includedir = @seq66includedir@ seq66libdir = @seq66libdir@ prefix = @prefix@ libdir = @seq66libdir@ datadir = @datadir@ #****************************************************************************** # localedir #------------------------------------------------------------------------------ # # 'localedir' is the normal system directory for installed localization # files. # #------------------------------------------------------------------------------ localedir = $(datadir)/locale DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ #****************************************************************************** # aclocal support #------------------------------------------------------------------------------ ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} #***************************************************************************** # libtool #----------------------------------------------------------------------------- version = $(SEQ66_LIBTOOL_VERSION) #****************************************************************************** # Compiler and linker flags (later, PortMidi) #------------------------------------------------------------------------------ AM_CFLAGS = \ -Wno-cast-function-type \ -I../include \ -I$(top_srcdir)/include \ -I$(top_srcdir)/libseq66/include AM_CXXFLAGS = \ -I../include \ -I$(top_srcdir)/include \ -I$(top_srcdir)/libseq66/include #****************************************************************************** # The library to build, a libtool-based library #------------------------------------------------------------------------------ lib_LTLIBRARIES = libseq_portmidi.la #****************************************************************************** # Source files #---------------------------------------------------------------------------- # # We don't use the finddefault*.c modules because Seq66 already has a # more tractable way to store configuration than in Java "prefs". We've added # some functions to porttime.c, so that is now included. # #---------------------------------------------------------------------------- libseq_portmidi_la_SOURCES = \ mastermidibus.cpp \ midibus.cpp \ pmlinuxalsa.c \ pmlinux.c \ pmutil.c \ portmidi.c \ porttime.c \ ptlinux.c libseq_portmidi_la_LDFLAGS = -no-undefined -version-info $(version) libseq_portmidi_la_LIBADD = $(ALSA_LIBS) $(JACK_LIBS) #****************************************************************************** # uninstall-hook #------------------------------------------------------------------------------ # # We'd like to remove /usr/local/include/libseq_gtkmm2-1.0 if it is # empty. However, we don't have a good way to do it yet. # #------------------------------------------------------------------------------ uninstall-hook: @echo "Note: you may want to remove $(libdir) manually" #****************************************************************************** # Makefile.am (libseq_portmidi) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: seq_portmidi/src/mastermidibus.cpp ================================================ /* * This file is part of seq24/seq66. * * seq24 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. * * seq24 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 seq24; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file mastermidibus.cpp * * This module declares/defines the base class for MIDI I/O under one of * Windows' audio frameworks. * * \library seq66 application * \author Seq24 team; modifications by Chris Ahlstrom * \date 2015-07-24 * \updates 2025-07-09 * \license GNU GPLv2 or above * * This file provides a Windows-only implementation of the mastermidibus * class. There is a lot of common code between these two versions! */ #include "midi/event.hpp" /* seq66::event */ #include "mastermidibus_pm.hpp" /* seq66::mastermidibus, PortMIDI */ #include "midibus_pm.hpp" /* seq66::midibus, PortMIDI */ #include "portmidi.h" /* external PortMidi header file */ #include "porttime.h" /* Pt_Time_To_Pulses() */ #include "pmutil.h" /* Pm_Dequeue() */ namespace seq66 { /** * The base-class constructor fills the array for our busses. New features. * Turn off exiting upon errors so that the application has a chance to come * up and display the error(s). Set BPM and PPQN in the PortMidi module. * The ppqn parameter defaults to -1 here. * * \param ppqn * Provides the PPQN value for this object. However, in most cases, the * default base value should be specified. * * \param bpm * Provides the beats per minute value, which defaults to * c_beats_per_minute. */ mastermidibus::mastermidibus (int ppqn, midibpm bpm) : mastermidibase (ppqn, bpm) { Pm_set_exit_on_error(FALSE); Pt_Set_Midi_Timing(double(bpm), ppqn); /* do this first */ Pm_Initialize(); /* do this second */ } /** * The destructor deletes all of the output busses, and terminates the * Windows MIDI manager. */ mastermidibus::~mastermidibus () { Pm_Terminate(); } /** * Here, we want to first make sure that the ports that the OS cannot access * are disable, before we activate them. Otherwise, they fail and prevent * working ports from operating. */ bool mastermidibus::activate () { bool result = mastermidibase::activate(); Pm_print_devices(); return result; } /** * Provides the PortMidi implementation needed for the init() function. * Unlike the seq24 ALSA implementation, this version does NOT support the * --manual-ports option. It initializes as many input and output MIDI * devices as are found by Pm_CountDevices(), and the flags * PmDeviceInfo::input and output determine what category of MIDI device it * is. * * \todo * We still need to reset the PPQN and BPM values via the ALSA API * if they are different here! See the "rtmidi" implementation of * this function. * * \param ppqn * The PPQN value to which to initialize the master MIDI buss. * * \param bpm * The BPM value to which to initialize the master MIDI buss, if * applicable. */ void mastermidibus::api_init (int ppqn, midibpm /*bpm*/) { int num_devices = Pm_device_count(); /* Pm_CountDevices() */ int numouts = 0; int numins = 0; for (int i = 0; i < num_devices; ++i) { const PmDeviceInfo * dev_info = Pm_GetDeviceInfo(i); if (dev_info->output) { /* * The parameters here are the bus index (within the input or output * busarry), the bus ID (currently identical to the bus index, * hmmmmm), the port ID, and the client name. */ midibus * m = new (std::nothrow) midibus ( numouts, dev_info->client, i, /* not dev_info->port */ dev_info->interf, dev_info->name ); m->is_input_port(false); /* add to ctor! */ m->is_virtual_port(false); /* add to ctor! */ if (m_outbus_array.add(m, clock(numouts))) /* not i */ ++numouts; } else if (dev_info->input) { /* * The parameters here are bus index, bus ID, port ID, and client * name. */ midibus * m = new (std::nothrow) midibus ( numins, dev_info->client, i, /* not dev_info->port */ dev_info->interf, dev_info->name ); m->is_input_port(true); /* add to ctor! */ m->is_virtual_port(false); /* add to ctor! */ if (m_inbus_array.add(m, input(numins))) /* not i */ ++numins; } } /* * Already done via base class: set_beats_per_minute(c_beats_per_minute); */ set_ppqn(ppqn); set_sequence_input(false, nullptr); #if defined SEQ66_USE_ANNOUNCE_BUS_WITH_PORTMIDI m_bus_announce = new (std::nothrow) midibus ( snd_seq_client_id(m_alsa_seq), SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE, m_alsa_seq, "system", "announce", 0, m_queue, ppqn, bpm ); m_bus_announce->set_input(true); #endif m_outbus_array.set_all_clocks(); m_inbus_array.set_all_inputs(); } /** * Grab a MIDI event. This function ssumes that [api_]poll_for_midi() has * been called to "prime the pump". * * \todo * Return a bussbyte or c_bussbyte_max value instead of a boolean. * * \param in * Provides the destination point for the event to be filled. * * \return * Returns true if there was no error and an event was obtained. * Note that we cannot set the result to false except at the beginning, * because then only the last device in the list can input data. * We have to break after getting data; this risks starving the later * devices! A FIX TO LOOK INTO!!! TODO!!! */ bool mastermidibus::api_get_midi_event (event * in) { bool result = false; int count = m_inbus_array.count(); for (int b = 0; b < count; ++b) { midibus * m = m_inbus_array.bus(b); if (m->port_enabled()) { PmEvent pme; PmInternal * midi = reinterpret_cast(m->m_pms); PmError err = Pm_Dequeue(midi->queue, &pme); if (err == pmBufferOverflow) /* ignore data retrieved */ { /* * result = false; // pm_errmsg(pmBufferOverflow, deviceid); */ } else if (err == 0) /* empty queue for port */ { /* * result = false; */ } else { /* * We don't need to do this. The perform input loop * sets the timestamp. Let's hope that loop can keep up! * * midipulse ts = midipulse(Pt_Time_To_Pulses(pme.timestamp)); * in->set_timestamp(ts); */ midipulse ts = midipulse(Pt_Time_To_Pulses(pme.timestamp)); midibyte buffer[4]; buffer[0] = Pm_MessageStatus(pme.message); buffer[1] = Pm_MessageData1(pme.message); buffer[2] = Pm_MessageData2(pme.message); result = in->set_midi_event(ts, buffer); /* 0 count */ in->set_input_bus(bussbyte(b)); #if defined SEQ66_PLATFORM_DEBUG_TMI printf("[seq66] input event on PortMidi bus %d\n", int(b)); #endif } } if (result) break; /* process incoming event NOW */ } return result; /* Why no "sysex = false"? */ } /** * Not yet implemented. There is no PPQN code in the original PortMIDI * project. */ void mastermidibus::api_set_ppqn (int /*ppqn*/) { // no code } /** * Not yet implemented. There is no BPM code in the original PortMIDI * project. Tempo is set via a timer (at least in their test application). */ void mastermidibus::api_set_beats_per_minute (midibpm /*bpm*/) { // no code } } // namespace seq66 /* * mastermidibus.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_portmidi/src/midibus.cpp ================================================ /* * This file is part of seq66. * * seq24 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. * * seq24 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 seq24; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midibus.cpp * * This module declares/defines the base class for MIDI I/O under one of * Windows' audio frameworks. * * \library seq66 application * \author Seq24 team; modifications by Chris Ahlstrom * \date 2015-07-24 * \updates 2023-06-28 * \license GNU GPLv2 or above * * This file provides a Windows-only implementation of the midibus class. * It differs from the ALSA implementation in the following particulars: * * - No concept of a buss-name or a port-name, though it does have a * client-name. The ALSA version has an ID, a client address, a * client port, and a user-configurable alias. * - It has a poll_for_midi() function. * - It does not have the following functions: * - init_out_sub() * - init_in_sub() * - deinit_out() * - deinit_in() */ #include "cfg/settings.hpp" /* seq66::rc_settings */ #include "midi/event.hpp" /* seq66::event and macros */ #include "os/timing.hpp" /* seq66::microsleep() */ #include "midibus_pm.hpp" /* seq66::midibus for PortMIDI */ namespace seq66 { #if defined THIS_FUNCTION_IS_NEEDED static std::string s_portname (const std::string & name, int index) { return name + " " + std::to_string(index); } #endif /** * Principal constructor. There's a little confusion with the port ID * parameter(s). Also, the default values of queue, ppqn, bpm, and makevirtual * are passed to the midibase constructor. PortMidi does not support those * constructs. */ midibus::midibus ( int index, int bus_id, int port_id, // port ID and queue number ! const std::string & clientname, const std::string & portname ) : midibase ( rc().application_name(), // appname #if defined THIS_FUNCTION_IS_NEEDED s_portname(clientname, index), // busname, shitty #else clientname, #endif portname, index, bus_id, port_id, // port ID port_id, // queue number usr().use_default_ppqn(), // PPQN flag value (-1) usr().bpm_default(), // 120.0, midibase::io::output, // false, midibase::port::normal // false ), m_pms (nullptr), m_is_port_locked (false) { // Empty body } /** * The destructor closes out the Windows MIDI infrastructure. */ midibus::~midibus () { if (not_nullptr(m_pms)) { Pm_Close(m_pms); m_pms = nullptr; } } /** * Polls for MIDI events. This is the API implementation for PortMidi. * It tests that the queue number (formerly m_pm) is valid first. It does * not assume that the PortMidiStream pointer m_pms is valid. * * The original error-checking was too simplistic. The PmError values of * PmNoError, pmNoData, and pmGotData are actually "no error" codes, if you * read /usr/include/portmidi.h, so we should not print a message if they * occur. FALSE and TRUE are just too limiting. FALSE == pmNoError and * pmNoData, and TRUE == any other value. * * For Windows CPU usage. Sleep for one millisecond, not 10 microseconds. * * \return * Returns 0 if the polling succeeded, and 1 if it failed. */ int midibus::api_poll_for_midi () { int result = 0; if (not_nullptr(m_pms) && queue_number() >= 0) /* buss number */ { PmError err = Pm_Poll(m_pms); if (err == pmGotData) result = 1; } if (result == 0) (void) microsleep(1000); /* std_sleep_us() */ return result; } /** * Initializes the MIDI output port, for PortMidi. * * If there is an error, we set the clocking to e_clock::disable to indicate * we should not bother to use the port. * * \return * Returns true if the output port was successfully opened. */ bool midibus::api_init_out () { PmError err = Pm_OpenOutput ( &m_pms, queue_number(), NULL, 100, NULL, NULL, 0 ); bool result = err == pmNoError; if (! result) { if (err == pmDeviceLocked) /* e.g. by MIDI Mapper */ { errprintf ( "Pm_OpenOutput(): %s; MIDI output locked\n", Pm_GetErrorText(err) ); set_port_locked(); result = true; /* cover up the "error" */ } else { errprintf ( "Pm_OpenOutput(): %s; MIDI output disabled\n", Pm_GetErrorText(err) ); } set_port_unavailable(); (void) set_clock(e_clock::unavailable); /* e_clock::disabled */ } return result; } /** * Initializes the MIDI input port, for PortMidi. * * \return * Returns true if the input port was successfully opened. */ bool midibus::api_init_in () { PmError err = Pm_OpenInput(&m_pms, queue_number(), NULL, 100, NULL, NULL); bool result = err == pmNoError; if (! result) { errprintf("Pm_OpenInput(): %s\n", Pm_GetErrorText(err)); set_input(false); } return result; } /** * Takes a native event, and encodes to a Windows message, and writes it to * the queue. It fills a small byte buffer, sets the MIDI channel, makes a * message of it, and writes the message. * * \question * The subatomic glue (Windows/PortMidi) implementation of Seq24 uses a * mutex to lock this function. Do we need to do that? Done in the * wrapper. * * \param e24 * The MIDI event to play. * * \param channel * The channel on which to play the event. */ void midibus::api_play (const event * e24, midibyte channel) { midibyte buffer[4]; /* temp for midi data */ buffer[0] = e24->get_status(channel); e24->get_data(buffer[1], buffer[2]); PmEvent event; event.timestamp = 0; event.message = Pm_Message(buffer[0], buffer[1], buffer[2]); /* PmError err = */ Pm_Write(m_pms, &event, 1); } /** * Continue from the given tick. This function implements only the * PortMidi-specific code. * * \param tick * The tick to continue from; unused in the PortMidi API implementation. * * \param beats * The calculated beats. This calculation is made in the * midibase::continue_from() function. */ void midibus::api_continue_from (midipulse /* tick */, midipulse beats) { PmEvent event; event.timestamp = 0; event.message = Pm_Message(EVENT_MIDI_CONTINUE, 0, 0); Pm_Write(m_pms, &event, 1); event.message = Pm_Message ( EVENT_MIDI_SONG_POS, (beats & 0x3F80 >> 7), (beats & 0x7F) ); Pm_Write(m_pms, &event, 1); } /** * Sets the MIDI clock a-runnin', if the clock type is not e_clock::none. * This function is called by midibase::start(). */ void midibus::api_start () { if (not_nullptr(m_pms) && port_enabled()) { PmEvent event; event.timestamp = 0; event.message = Pm_Message(EVENT_MIDI_START, 0, 0); Pm_Write(m_pms, &event, 1); } } /** * Stops the MIDI clock, if the clock-type is not e_clock::none. * This function is called by midibase::stop(). */ void midibus::api_stop () { if (not_nullptr(m_pms) && port_enabled()) { PmEvent event; event.timestamp = 0; event.message = Pm_Message(EVENT_MIDI_STOP, 0, 0); Pm_Write(m_pms, &event, 1); } } /** * Generates MIDI clock. This function is called by midibase::clock(). * * \question * The subatomic glue (Windows/PortMidi) implementation of Seq24 uses a * mutex to lock this function. Do we need to do that? We do that, in * midibase::clock(). * * \param tick * The clock tick value, not used in the API implementation of this * function for PortMidi. */ void midibus::api_clock (midipulse /* tick */) { if (not_nullptr(m_pms) && port_enabled()) { PmEvent event; event.timestamp = 0; /* WHY NOT use 'tick' here? */ event.message = Pm_Message(EVENT_MIDI_CLOCK, 0, 0); Pm_Write(m_pms, &event, 1); } } } // namespace seq66 /* * midibus.cpp for PortMIDI * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_portmidi/src/pmerrmm.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmerrmm.c * * System specific error-messages for the Windows MM API. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-04-21 * \updates 2020-07-11 * \license GNU GPLv2 or above * * Please note that this error module is used only in the pmwinmm.h Windows * module. */ #include /* UINT, DWORD, and more */ #include /* MMRESULT, MMSYSERR_xxx */ #include /* C::printf() etc. */ #include /* C::calloc(), C::free() */ #include "portmidi.h" /* for pm_log_buffer functions */ #include "pmerrmm.h" #include "util/basic_macros.h" /** * This printf() stuff is really important for debugging client app w/host * errors. Probably want to do something else besides read/write from/to * console for portability, however. */ /** * Provides error messages for: * * - midiInGetDevCaps(deviceid, lpincaps, szincaps). * - midiOutGetDevCaps(deviceid, lpincaps, szincaps). * * \param devicename * The name of the device, either "MIDIMAPPER" or whatever the operating * system discovers. */ const char * midi_io_get_dev_caps_error ( const char * devicename, const char * functionname, MMRESULT errcode ) { static char s_error_storage[PM_STRING_MAX]; const char * result = "Unknown"; switch (errcode) { case MMSYSERR_NOERROR: result = "None"; break; case MMSYSERR_ALLOCATED: result = "The specified resource is already allocated"; break; case MMSYSERR_BADDEVICEID: result = "The specified device identifier is out of range"; break; case MMSYSERR_INVALFLAG: result = "The specified flags are invalid"; break; case MMSYSERR_INVALHANDLE: result = "The specified device handle is invalid"; break; case MMSYSERR_INVALPARAM: result = "The specified pointer or structure parameter is invalid"; break; case MMSYSERR_NODRIVER: result = "The driver is not installed"; break; case MMSYSERR_NOMEM: result = "The system is unable to allocate or lock memory"; break; case MIDIERR_NODEVICE: result = "No MIDI port found (Midi Mapper)"; break; } (void) snprintf ( s_error_storage, sizeof s_error_storage, "%s() error for device '%s': '%s'\n", functionname, devicename, result ); return &s_error_storage[0]; } /* * pmerrmm.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/src/pmlinux.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmlinux.c * * PortMidi os-dependent code for Linux. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2023-06-02 * \license GNU GPLv2 or above * * This file only needs to implement pm_init(), which calls various routines * to register the available midi devices. This file must be separate from * the main portmidi.c file because it is system dependent, and it is * separate from, pmlinuxalsa.c, because it might need to register non-ALSA * devices as well. * * Note: * * If you add non-ALSA support, you need to fix alsa_poll() in * pmlinuxalsa.c, which assumes all input devices are ALSA. */ #include "seq66-config.h" #include "util/basic_macros.h" /* not_nullptr() macro, etc. */ #include "portmidi.h" /* Pm_set_initialized(), etc. */ #include "pmutil.h" #include "pminternal.h" #if defined SEQ66_HAVE_LIBASOUND #include "pmlinuxalsa.h" #endif PmDeviceID pm_default_input_device_id = -1; PmDeviceID pm_default_output_device_id = -1; /** * Note: * * It is not an error for ALSA to fail to initialize. It may be a design * error that the client cannot query what subsystems are working properly * other than by looking at the list of available devices. */ void pm_init () { #if defined SEQ66_HAVE_LIBASOUND pm_linuxalsa_init(); #else #if defined SEQ66_PORTMIDI_NULL // never defined, at present pm_linuxnull_init(); #endif #endif /* * This is set when we return to Pm_Initialize, but we need it * now in order to (successfully) call Pm_CountDevices(). Ugh. * At least we get to assume UTF-8 here, rather than Window's UTF-16. */ Pm_set_initialized(TRUE); } /** * */ void pm_term (void) { #if defined SEQ66_HAVE_LIBASOUND pm_linuxalsa_term(); #endif } /* * pmlinux.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/src/pmlinuxalsa.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmlinuxalsa.c * * System-specific definitions for Linux's ALSA MIDI subsystem. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2023-09-20 * \license GNU GPLv2 or above * * Written by: * * - Roger Dannenberg (port to Alsa 0.9.x) * - Clemens Ladisch (provided code examples and invaluable consulting) * - Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation) */ #include #include #include #include "seq66_platform_macros.h" /* UNUSED() parameter macro */ #include "pmlinux.h" #include "pmlinuxalsa.h" #include "pmutil.h" #include "portmidi.h" #include "porttime.h" #include "util/basic_macros.h" /* not_nullptr() macro, etc. */ /* * I used many print statements to debug this code. I left them in the * source, and you can turn them on by changing false to true below: */ #define VERBOSE_ON 0 /* * Check for ALSA version. */ #if SND_LIB_MAJOR == 0 && SND_LIB_MINOR < 9 #error needs ALSA 0.9.0 or later #endif /** * These defines are added to help store client/port information in the * device descriptor. One issue with the existing PortMidi library is that * we're casting from a pointer to an integer of different size. So we add * an intprt_t cast into the mix. * * Weird: In C macros, the arguments in parentheses must have no space * beefore the opening parenthesis. */ #define MASK_DESCRIPTOR_CLIENT(x) ( (((int) (intptr_t) (x)) >> 8) & 0xff ) #define MASK_DESCRIPTOR_PORT(x) ( ((int) (intptr_t) (x)) & 0xff ) #define MAKE_DESCRIPTOR(client, port) \ ( (void *) (intptr_t) ( ((client) << 8) | (port)) ) extern pm_fns_node pm_linuxalsa_in_dictionary; extern pm_fns_node pm_linuxalsa_out_dictionary; /** * All input comes here, output queue allocated on seq. */ static snd_seq_t * s_seq = nullptr; /** * Provides an item to hold the ALSA queue. */ static int s_queue; /* one for all ports, reference counted */ /** * A boolean that prevents the ALSA queue from getting reset if it has * already been set. */ static int s_queue_used; /* one for all ports, reference counted */ /** * Holds information about an ALSA port. */ typedef struct alsa_descriptor_struct { int client; int port; int this_port; int in_sysex; snd_midi_event_t * parser; int error; /* host error code */ } alsa_descriptor_node, * alsa_descriptor_type; /** * Copies error text to a potentially short string. */ static void get_alsa_error_text (char * msg, int len, int err) { int errlen = strlen(snd_strerror(err)); if (errlen < len) { strcpy(msg, snd_strerror(err)); } else if (len > 20) { sprintf(msg, "ALSA error %d", err); } else if (len > 4) { strcpy(msg, "ALSA"); } else { msg[0] = 0; } } /** * s_queue is shared by both input and output, reference counted * * \todo * Make the tempo and PPQN settings variable. */ static PmError alsa_use_queue (void) { if (s_queue_used == 0 && not_nullptr(s_seq)) { snd_seq_queue_tempo_t * tempo; s_queue = snd_seq_alloc_queue(s_seq); if (s_queue < 0) { Pm_set_hosterror(s_queue); return pmHostError; } snd_seq_queue_tempo_alloca(&tempo); snd_seq_queue_tempo_set_tempo(tempo, Pt_Get_Tempo_Microseconds()); snd_seq_queue_tempo_set_ppq(tempo, Pt_Get_Ppqn()); Pm_set_hosterror(snd_seq_set_queue_tempo(s_seq, s_queue, tempo)); if (Pm_hosterror() < 0) return pmHostError; snd_seq_start_queue(s_seq, s_queue, NULL); snd_seq_drain_output(s_seq); } ++s_queue_used; return pmNoError; } static void alsa_unuse_queue (void) { if (--s_queue_used == 0) { snd_seq_stop_queue(s_seq, s_queue, NULL); snd_seq_drain_output(s_seq); snd_seq_free_queue(s_seq, s_queue); if (VERBOSE_ON) printf("s_queue freed\n"); } } /** * midi_message_length() -- how many bytes in a message? */ static int midi_message_length (PmMessage message) { message &= 0xff; if (message < 0x80) { return 0; } else if (message < 0xf0) { static const int length[] = {3, 3, 3, 3, 2, 2, 3}; return length[(message - 0x80) >> 4]; } else { static const int length[] = { -1, 2, 3, 2, 0, 0, 1, -1, 1, 0, 1, 1, 1, 0, 1, 1 }; return length[message - 0xf0]; } } static PmError alsa_out_open (PmInternal * midi, void * UNUSED(driverinfo)) { void * client_port = pm_descriptors[midi->device_id].descriptor; alsa_descriptor_type desc = (alsa_descriptor_type) pm_alloc(sizeof(alsa_descriptor_node)); snd_seq_port_info_t * info; int err; if (! desc) return pmInsufficientMemory; snd_seq_port_info_alloca(&info); snd_seq_port_info_set_port(info, midi->device_id); snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_READ); snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); snd_seq_port_info_set_port_specified(info, 1); err = snd_seq_create_port(s_seq, info); if (err < 0) goto free_desc; /* fill in fields of desc, which is passed to pm_write routines */ midi->descriptor = desc; desc->client = MASK_DESCRIPTOR_CLIENT(client_port); desc->port = MASK_DESCRIPTOR_PORT(client_port); desc->this_port = midi->device_id; desc->in_sysex = 0; desc->error = 0; err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &desc->parser); if (err < 0) goto free_this_port; if (midi->latency > 0) /* must delay output using a queue */ { err = alsa_use_queue(); if (err < 0) goto free_parser; err = snd_seq_connect_to ( s_seq, desc->this_port, desc->client, desc->port ); if (err < 0) goto unuse_queue; /* clean up and return on error */ } else { err = snd_seq_connect_to ( s_seq, desc->this_port, desc->client, desc->port ); if (err < 0) goto free_parser; /* clean up and return on error */ } return pmNoError; unuse_queue: alsa_unuse_queue(); free_parser: snd_midi_event_free(desc->parser); free_this_port: snd_seq_delete_port(s_seq, desc->this_port); free_desc: pm_free(desc); Pm_set_hosterror(err); if (err < 0) { get_alsa_error_text ( Pm_hosterror_text_mutable(), PM_HOST_ERROR_MSG_LEN, err ); } return pmHostError; } /** * \note * For cases where the user does not supply a time function, we could * optimize the code by not starting Pt_Time and using the alsa tick time * instead. I didn't do this because it would entail changing the queue * management to start the queue tick count when PortMidi is initialized * and keep it running until PortMidi is terminated. (This should be * simple, but it's not how the code works now.) -- RBD */ static PmError alsa_write_byte (PmInternal * midi, midibyte_t byte, PmTimestamp timestamp) { alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; snd_seq_event_t ev; int err; snd_seq_ev_clear(&ev); if (snd_midi_event_encode_byte(desc->parser, byte, &ev) == 1) { snd_seq_ev_set_dest(&ev, desc->client, desc->port); snd_seq_ev_set_source(&ev, desc->this_port); if (midi->latency > 0) { /* compute relative time of event = timestamp - now + latency */ PmTimestamp now = midi->time_proc ? midi->time_proc(midi->time_info) : Pt_Time() ; /* * if timestamp is zero, send immediately. * Otherwise compute time delay and use delay if positive. */ int when = timestamp; if (when == 0) when = now; when = (when - now) + midi->latency; if (when < 0) when = 0; if (VERBOSE_ON) printf ( "Timestamp %d now %d latency %d, ", (int) timestamp, (int) now, midi->latency ); if (VERBOSE_ON) printf("Scheduling event after %d\n", when); /* * Message is sent in relative ticks, where 1 tick = 1 ms. See the * function banner note. */ snd_seq_ev_schedule_tick(&ev, s_queue, 1, when); } else { /* * ev.queue = SND_SEQ_QUEUE_DIRECT; * ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */ snd_seq_ev_set_direct(&ev); /* send event out without queueing */ } if (VERBOSE_ON) printf("Sending event\n"); err = snd_seq_event_output(s_seq, &ev); if (err < 0) { desc->error = err; return pmHostError; } } return pmNoError; } static PmError alsa_out_close (PmInternal * midi) { alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; if (! desc) return pmBadPtr; Pm_set_hosterror ( snd_seq_disconnect_to(s_seq, desc->this_port, desc->client, desc->port) ); if (Pm_hosterror() != pmNoError) { /* * If there's an error, try to delete the port anyway, but don't * change the Pm_hosterror() value so we retain the first error. */ snd_seq_delete_port(s_seq, desc->this_port); } else { /* * If there's no error, delete the port and retain any error. */ Pm_set_hosterror(snd_seq_delete_port(s_seq, desc->this_port)); } if (midi->latency > 0) alsa_unuse_queue(); snd_midi_event_free(desc->parser); midi->descriptor = nullptr; /* destroy pointer to signify "closed" */ pm_free(desc); if (Pm_hosterror() != pmNoError) { get_alsa_error_text ( Pm_hosterror_text_mutable(), PM_HOST_ERROR_MSG_LEN, Pm_hosterror() ); return pmHostError; } return pmNoError; } static PmError alsa_in_open (PmInternal * midi, void * UNUSED(driverinfo)) { void * client_port = pm_descriptors[midi->device_id].descriptor; alsa_descriptor_type desc = (alsa_descriptor_type) pm_alloc(sizeof(alsa_descriptor_node)); snd_seq_port_info_t * info; snd_seq_port_subscribe_t * sub; snd_seq_addr_t addr; int err; if (! desc) return pmInsufficientMemory; err = alsa_use_queue(); if (err < 0) goto free_desc; snd_seq_port_info_alloca(&info); snd_seq_port_info_set_port(info, midi->device_id); snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_READ); snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); snd_seq_port_info_set_port_specified(info, 1); err = snd_seq_create_port(s_seq, info); if (err < 0) goto free_queue; /* * Fill in the fields of desc, which is passed to pm_write routines. */ midi->descriptor = desc; desc->client = MASK_DESCRIPTOR_CLIENT(client_port); desc->port = MASK_DESCRIPTOR_PORT(client_port); desc->this_port = midi->device_id; desc->in_sysex = 0; desc->error = 0; if (VERBOSE_ON) printf ( "snd_seq_connect_from: %d %d %d\n", desc->this_port, desc->client, desc->port ); snd_seq_port_subscribe_alloca(&sub); addr.client = snd_seq_client_id(s_seq); addr.port = desc->this_port; snd_seq_port_subscribe_set_dest(sub, &addr); addr.client = desc->client; addr.port = desc->port; snd_seq_port_subscribe_set_sender(sub, &addr); snd_seq_port_subscribe_set_time_update(sub, 1); /* * This doesn't seem to work: messages come in with real timestamps. */ snd_seq_port_subscribe_set_time_real(sub, 0); err = snd_seq_subscribe_port(s_seq, sub); /* * err = snd_seq_connect_from(s_seq, desc->this_port, desc->client, * desc->port); */ if (err < 0) goto free_this_port; /* clean up and return on error */ return pmNoError; free_this_port: snd_seq_delete_port(s_seq, desc->this_port); free_queue: alsa_unuse_queue(); free_desc: pm_free(desc); Pm_set_hosterror(err); if (err < 0) { get_alsa_error_text ( Pm_hosterror_text_mutable(), PM_HOST_ERROR_MSG_LEN, err ); } return pmHostError; } static PmError alsa_in_close (PmInternal * midi) { alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; if (! desc) return pmBadPtr; Pm_set_hosterror ( snd_seq_disconnect_from ( s_seq, desc->this_port, desc->client, desc->port ) ); if (Pm_hosterror() != pmNoError) { snd_seq_delete_port(s_seq, desc->this_port); /* try to close port */ } else { Pm_set_hosterror(snd_seq_delete_port(s_seq, desc->this_port)); } alsa_unuse_queue(); pm_free(desc); if (Pm_hosterror() != pmNoError) { get_alsa_error_text ( Pm_hosterror_text_mutable(), PM_HOST_ERROR_MSG_LEN, Pm_hosterror() ); return pmHostError; } return pmNoError; } /** * ALSA documentation is vague. This is supposed to remove any pending output * messages. If you can test and confirm this code is correct, please update * this comment. Unfortunately, I can't even compile it -- my ALSA version * does not implement snd_seq_remove_events_t, so this does not compile. I'll * try again, but it looks like I'll need to upgrade my entire Linux OS. -- * RBD * * This is still true for Debian Sid in 2020! The snd_seq_remove_events_t * "structure" yields the following error: * * error: storage size of ‘info’ isn’t known: snd_seq_remove_events_t * info; */ #if defined SEQ66_SND_SEQ_REMOVE_EVENTS_SUPPORTED static PmError alsa_abort (PmInternal * midi) { alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; snd_seq_remove_events_t info; snd_seq_addr_t addr; addr.client = desc->client; addr.port = desc->port; snd_seq_remove_events_set_dest(&info, &addr); snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST); Pm_hosterror(snd_seq_remove_events(s_seq, &info)); if (Pm_hosterror() != pmNoError) { get_alsa_error_text ( Pm_hosterror_text_mutable(), PM_HOST_ERROR_MSG_LEN, Pm_hosterror() ); return pmHostError; } return pmNoError; } #else static PmError alsa_abort (PmInternal * UNUSED(midi)) { printf("WARNING: alsa_abort() not implemented.\n"); return pmNoError; } #endif static PmError alsa_write_flush (PmInternal * midi, PmTimestamp UNUSED(timestamp)) { alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; desc->error = snd_seq_drain_output(s_seq); if (desc->error < 0) return pmHostError; desc->error = pmNoError; return pmNoError; } static PmError alsa_write_short (PmInternal * midi, PmEvent * event) { int bytes = midi_message_length(event->message); PmMessage msg = event->message; int i; alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; for (i = 0; i < bytes; ++i) { midibyte_t byte = msg; if (VERBOSE_ON) printf("Sending 0x%x\n", byte); alsa_write_byte(midi, byte, event->timestamp); if (desc->error < 0) break; msg >>= 8; /* shift next byte into position */ } if (desc->error < 0) return pmHostError; desc->error = pmNoError; return pmNoError; } /** * alsa_sysex -- implements begin_sysex and end_sysex */ PmError alsa_sysex (PmInternal * UNUSED(midi), PmTimestamp UNUSED(timestamp)) { return pmNoError; } /** * Linux implementation does not use this synchronize function. Apparently, * ALSA data is relative to the time you send it, and there is no reference. * If this is true, this is a serious shortcoming of ALSA. If not true, then * PortMidi has a serious shortcoming -- it should be scheduling relative to * ALSA's time reference. */ static PmTimestamp alsa_synchronize (PmInternal * UNUSED(midi)) { return 0; } static void handle_event (snd_seq_event_t * ev) { int device_id = ev->dest.port; PmInternal * midi = pm_descriptors[device_id].internalDescriptor; PmTimeProcPtr time_proc = midi->time_proc; PmEvent pm_ev; PmTimestamp timestamp; /* * Time stamp should be in ticks, using our queue, where 1 tick = 1 ms. */ assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK); if (time_proc == NULL) { timestamp = ev->time.tick; /* no time_proc, return native ticks, ms */ } else /* translate time to time_proc basis */ { snd_seq_queue_status_t *queue_status; snd_seq_queue_status_alloca(&queue_status); snd_seq_get_queue_status(s_seq, s_queue, queue_status); /* return (now - alsa_now) + alsa_timestamp */ timestamp = (*time_proc)(midi->time_info) + ev->time.tick - snd_seq_queue_status_get_tick_time(queue_status); } pm_ev.timestamp = timestamp; switch (ev->type) { case SND_SEQ_EVENT_NOTEON: pm_ev.message = Pm_Message(0x90 | ev->data.note.channel, ev->data.note.note & 0x7f, ev->data.note.velocity & 0x7f); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_NOTEOFF: pm_ev.message = Pm_Message(0x80 | ev->data.note.channel, ev->data.note.note & 0x7f, ev->data.note.velocity & 0x7f); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_KEYPRESS: pm_ev.message = Pm_Message(0xa0 | ev->data.note.channel, ev->data.note.note & 0x7f, ev->data.note.velocity & 0x7f); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_CONTROLLER: pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, ev->data.control.param & 0x7f, ev->data.control.value & 0x7f); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_PGMCHANGE: pm_ev.message = Pm_Message(0xc0 | ev->data.note.channel, ev->data.control.value & 0x7f, 0); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_CHANPRESS: pm_ev.message = Pm_Message(0xd0 | ev->data.note.channel, ev->data.control.value & 0x7f, 0); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_PITCHBEND: pm_ev.message = Pm_Message(0xe0 | ev->data.note.channel, (ev->data.control.value + 0x2000) & 0x7f, ((ev->data.control.value + 0x2000) >> 7) & 0x7f); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_CONTROL14: if (ev->data.control.param < 0x20) { pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, ev->data.control.param, (ev->data.control.value >> 7) & 0x7f); pm_read_short(midi, &pm_ev); pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, ev->data.control.param + 0x20, ev->data.control.value & 0x7f); pm_read_short(midi, &pm_ev); } else { pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel, ev->data.control.param & 0x7f, ev->data.control.value & 0x7f); pm_read_short(midi, &pm_ev); } break; case SND_SEQ_EVENT_SONGPOS: pm_ev.message = Pm_Message(0xf2, ev->data.control.value & 0x7f, (ev->data.control.value >> 7) & 0x7f); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_SONGSEL: pm_ev.message = Pm_Message(0xf3, ev->data.control.value & 0x7f, 0); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_QFRAME: pm_ev.message = Pm_Message(0xf1, ev->data.control.value & 0x7f, 0); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_START: pm_ev.message = Pm_Message(0xfa, 0, 0); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_CONTINUE: pm_ev.message = Pm_Message(0xfb, 0, 0); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_STOP: pm_ev.message = Pm_Message(0xfc, 0, 0); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_CLOCK: pm_ev.message = Pm_Message(0xf8, 0, 0); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_TUNE_REQUEST: pm_ev.message = Pm_Message(0xf6, 0, 0); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_RESET: pm_ev.message = Pm_Message(0xff, 0, 0); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_SENSING: pm_ev.message = Pm_Message(0xfe, 0, 0); pm_read_short(midi, &pm_ev); break; case SND_SEQ_EVENT_SYSEX: { /* * Assume there is one SysEx byte to process. */ const midibyte_t * ptr = (const midibyte_t *) ev->data.ext.ptr; pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp); break; } } } /** * Poll! Checks for and ignore errors, e.g. input overflow. * * There is an expensive check (while loop) for input data, and it gets data * from device. * * snd_seq_event_input_pending(s_seq, TRUE) checks the presence of events on * the sequencer FIFO; these events are transferred to the input buffer, and * the number of received events is returned. * * snd_seq_event_input_pending(s_seq, FALSE) returns 0 if no events remain * in the input buffer. We think this is all we need for the poll function! * * \note * If there's overflow, this should be reported all the way through to * client. Since input from all devices is merged, we need to find all * input devices and set all to the overflow state. NOTE: this assumes * every input is ALSA based. */ static PmError alsa_poll (PmInternal * UNUSED(midi)) { snd_seq_event_t * ev; while (snd_seq_event_input_pending(s_seq, TRUE) > 0) /* expensive! */ { /* * Cheap check on local input buffer. */ while (snd_seq_event_input_pending(s_seq, FALSE) > 0) { int rslt = snd_seq_event_input(s_seq, &ev); if (rslt >= 0) { handle_event(ev); /* much work! */ } else if (rslt == -ENOSPC) { int i; for (i = 0; i < pm_descriptor_index; i++) { if (pm_descriptors[i].pub.input) { PmInternal * midi = (PmInternal *) pm_descriptors[i].internalDescriptor; /* careful, device may not be open! */ if (not_nullptr(midi)) Pm_SetOverflow(midi->queue); } } } } } return pmNoError; } static unsigned alsa_has_host_error (PmInternal * midi) { alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; return desc->error; } /** * alsa_get_host_error (PmInternal * midi, char * msg, unsigned len) */ static void alsa_get_host_error (struct pm_internal_struct * midi, char * msg, unsigned len) { alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; int err = ((Pm_hosterror() != pmNoError) || desc->error); get_alsa_error_text(msg, len, err); } pm_fns_node pm_linuxalsa_in_dictionary = { none_write_short, none_sysex, none_sysex, none_write_byte, none_write_short, none_write_flush, alsa_synchronize, alsa_in_open, alsa_abort, alsa_in_close, alsa_poll, alsa_has_host_error, alsa_get_host_error }; pm_fns_node pm_linuxalsa_out_dictionary = { alsa_write_short, alsa_sysex, alsa_sysex, alsa_write_byte, alsa_write_short, /* short realtime message */ alsa_write_flush, alsa_synchronize, alsa_out_open, alsa_abort, alsa_out_close, none_poll, alsa_has_host_error, alsa_get_host_error }; /** * Copies a string to the heap. Use this function, rather than strdup(), so * that we call pm_alloc(), not malloc(). This allows PortMidi to avoid * malloc() which might cause priority inversion. Probably ALSA is going to * call malloc()l anyway, so this extra work here may be pointless. */ char * pm_strdup (const char * s) { int len = strlen(s); char * dup = (char *) pm_alloc(len + 1); strcpy(dup, s); return dup; } /** * We tried opening the ALSA port in non-blocking mode. Didn't seem to * offer any benefit. * * - 0 Blocking mode. * - SND_SEQ_NONBLOCK Non-blocking mode. * * We did reduce the polling timeout from 1000 milliseconds (in Seq24) to 100 * milliseconds, and now, after testing, 10 milliseconds, and removed the * additional 100 microsecond wait. * * Not yet used (see midi_alsa_info.cpp): * * static const int c_poll_wait_ms = 10; * static unsigned c_input_caps = * SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; * static unsigned c_output_caps = * SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE; */ static const int c_open_block_mode = SND_SEQ_NONBLOCK; static unsigned c_io_caps = SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE; /** * Checks the port type for not being the "generic" types * SND_SEQ_PORT_TYPE_MIDI_GENERIC and SND_SEQ_PORT_TYPE_SYNTH. * * We might need to add this check!!! * * ((alsatype & SND_SEQ_PORT_TYPE_APPLICATION) == 0) */ static cbool_t check_port_type (snd_seq_port_info_t * pinfo) { unsigned alsatype = snd_seq_port_info_get_type(pinfo); return ( ((alsatype & SND_SEQ_PORT_TYPE_MIDI_GENERIC) == 0) && ((alsatype & SND_SEQ_PORT_TYPE_SYNTH) == 0) ); } /** * Previously, the last parameter to snd_seq_opten() was SND_SEQ_NONBLOCK, * but this would cause messages to be dropped if the ALSA buffer fills up. * The correct behavior is for writes to block until there is room to send * all the data. The client should normally allocate a large enough buffer to * avoid blocking on output. Now that blocking is enabled, the * seq_event_input() will block if there is no input data. This is not what * we want, so must call seq_event_input_pending() to avoid blocking. */ PmError pm_linuxalsa_init (void) { int err = snd_seq_open /* set up ALSA sequencer client */ ( &s_seq, "default", SND_SEQ_OPEN_DUPLEX, c_open_block_mode ); pm_log_buffer_alloc(); /* see portmidi.c & .h */ if (err < 0) { get_alsa_error_text /* ca 2023-05-13 */ ( Pm_hosterror_text_mutable(), PM_HOST_ERROR_MSG_LEN, err ); return err; } else { snd_seq_port_info_t * pinfo; snd_seq_client_info_t * cinfo; snd_seq_client_info_alloca(&cinfo); snd_seq_client_info_set_client(cinfo, -1); while (snd_seq_query_next_client(s_seq, cinfo) >= 0) { unsigned caps; int port = -1; int client = snd_seq_client_info_get_client(cinfo); snd_seq_port_info_alloca(&pinfo); // moved from above snd_seq_port_info_set_client(pinfo, client); snd_seq_port_info_set_port(pinfo, port); while (snd_seq_query_next_port(s_seq, pinfo) >= 0) { /* * Not used in the portmidi implementation: * * const char * clientname; * clientname = snd_seq_client_info_get_name(cinfo); */ const char * clientname; const char * portname; char * clientname2; char * portname2; if (check_port_type(pinfo)) continue; client = snd_seq_client_info_get_client(cinfo); if (client == SND_SEQ_CLIENT_SYSTEM) { /* * Client 0 won't have ports (timer and announce) that * match the MIDI-generic and Synth types checked below. */ continue; } caps = snd_seq_port_info_get_capability(pinfo); clientname = snd_seq_client_info_get_name(cinfo); portname = snd_seq_port_info_get_name(pinfo); // from below clientname2 = pm_strdup(clientname); portname2 = pm_strdup(portname); port = snd_seq_port_info_get_port(pinfo); client = snd_seq_port_info_get_client(pinfo); if (! (caps & c_io_caps)) continue; /* ignore if can't read or write por */ if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) { if (pm_default_output_device_id == -1) pm_default_output_device_id = pm_descriptor_index; (void) pm_add_device ( clientname2, portname2, FALSE, MAKE_DESCRIPTOR(client, port), &pm_linuxalsa_out_dictionary, client, port ); } if (caps & SND_SEQ_PORT_CAP_SUBS_READ) { if (pm_default_input_device_id == -1) pm_default_input_device_id = pm_descriptor_index; (void) pm_add_device ( clientname2, portname2, TRUE, MAKE_DESCRIPTOR(client, port), &pm_linuxalsa_in_dictionary, client, port ); } /* * We MUST NOT free these items, as these pointers are logged * in pm_add_device and are to be available "forever". * * free(portname2); * free(clientname2); */ } } } #if defined SEQ66_PLATFORM_DEBUG if (s_seq) { int npfds = snd_seq_poll_descriptors_count(s_seq, POLLIN); printf("[seq66] %d poll-in descriptors\n", npfds); } #endif return pmNoError; } void pm_linuxalsa_term (void) { if (s_seq) { snd_seq_close(s_seq); pm_free(pm_descriptors); pm_descriptors = nullptr; pm_descriptor_index = 0; pm_descriptor_max = 0; } pm_log_buffer_free(); } /* * pmlinuxalsa.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/src/pmmac.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmmac.c * * PortMidi os-dependent code for Mac OSX. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2018-05-13 * \updates 2023-06-02 * \license GNU GPLv2 or above * This file needs to implement pm_init(), which calls various routines to * register the available MIDI devices. * * This file must be separate from the main portmidi.c file because it is * system dependent, and it is separate from, say, pmmacosxcm.c, because it * might need to register devices for non-CoreMIDI devices. */ #include #include "util/basic_macros.h" /* not_nullptr() macro, etc. */ #include "portmidi.h" /* Pm_set_initialized(), etc. */ #include "pmutil.h" #include "pminternal.h" #include "pmmacosxcm.h" /** * Provides default ID's for non-existent devices. */ PmDeviceID pm_default_input_device_id = -1; PmDeviceID pm_default_output_device_id = -1; /** * pm_initialized is set when we return to Pm_Initialize(), but we need it * now in order to (successfully) call Pm_CountDevices(). */ void pm_init (void) { PmError err = pm_macosxcm_init(); /* * atexit(pm_exit); */ if (err != pmNoError) { Pm_set_initialized(FALSE); } else { Pm_set_initialized(TRUE); } } /** * Calls pm_macosxcm_term() to end the PortMidi session. */ void pm_term (void) { pm_macosxcm_term(); } /* * pmmac.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/src/pmmacosxcm.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmmacosxcm.c * * System specific definitions for the Mac OSX CoreMIDI API. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2018-05-13 * \updates 2023-06-03 * \license GNU GPLv2 or above * * A platform interface to the MacOS X CoreMIDI framework. * * Jon Parise * and subsequent work by Andrew Zeldis and Zico Kolter, * and Roger B. Dannenberg. * * $Id: pmmacosx.c,v 1.17 2002/01/27 02:40:40 jon Exp $ * * \note * Since the input and output streams are represented by MIDIEndpointRef * values and almost no other state, we store the MIDIEndpointRef on * descriptors[midi->device_id].descriptor. The only other state we need * is for errors: we need to know if there is an error and if so, what is * the error text. We use a structure with two kinds of host error: * "error" and "callback_error". That way, asynchronous callbacks do not * interfere with other error information. * * OS X does not seem to have an error-code-to-text function, so we will * just use text messages instead of error codes. */ #include #include #include #include #include #include #include #include "pmmac.h" #include "pmmacosxcm.h" #include "pmutil.h" #include "portmidi.h" #include "porttime.h" /** * The size of the event buffer. */ #define PACKET_BUFFER_SIZE 1024 /** * Maximum overall data rate (the OS X limit is 15000 bytes/second). */ #define MAX_BYTES_PER_S 14000 /** * Apple reports that packets are dropped when the MIDI bytes/sec exceeds 15000. * This is computed by "tracking the number of MIDI bytes scheduled into 1-second * buckets over the last six seconds and averaging these counts." * * This is apparently based on timestamps, not on real time, so we have to avoid * constructing packets that schedule high speed output even if the actual writes * are delayed (which was my first solution). * * The LIMIT_RATE symbol, if defined, enables code to modify timestamps as * follows: * * After each packet is formed, the next allowable timestamp is computed as * this_packet_time + this_packet_len * delay_per_byte This is the minimum * timestamp allowed in the next packet. Note that this distorts accurate * timestamps somewhat. */ #define LIMIT_RATE 1 /** * The size of the SysEx buffer. */ #define SYSEX_BUFFER_SIZE 512 /* is this enough? */ /** * More constants. */ #define MIDI_SYSEX 0xf0 #define MIDI_EOX 0xf7 #define MIDI_STATUS_MASK 0x80 /** * "Ref"s are pointers on 32-bit machines and ints on 64 bit machines. * NULL_REF is our representation of either 0 or NULL. We'll start using the * nullptr macro at some point. */ #if defined __LP64__ #define NULL_REF 0 #else #define NULL_REF NULL #endif /* * Client handle to the MIDI server. */ static MIDIClientRef client = NULL_REF; /* * Input port handle. */ static MIDIPortRef portIn = NULL_REF; /* * Output port handle. */ static MIDIPortRef portOut = NULL_REF; extern pm_fns_node pm_macosx_in_dictionary; extern pm_fns_node pm_macosx_out_dictionary; /** * Oy, another big-ass state structure! */ typedef struct midi_macosxcm_struct { PmTimestamp sync_time; /**< When did we last determine delta? */ UInt64 delta; /**< Delta between stream-time & real-time, ns. */ UInt64 last_time; /**< Last output time in host units. */ int first_message; /**< Tells midi_write() to synch timestamps. */ int sysex_mode; /**< In middle of sending SysEx. */ uint32_t sysex_word; /**< Accumulate data when receiving SysEx. */ uint32_t sysex_byte_count; /**< Count how many received. */ char error[PM_HOST_ERROR_MSG_LEN]; /**< todo. */ char callback_error[PM_HOST_ERROR_MSG_LEN]; /**< todo. */ Byte packetBuffer[PACKET_BUFFER_SIZE]; /**< todo. */ MIDIPacketList * packetList; /**< Pointer to packetBuffer. */ MIDIPacket * packet; /**< Points to MIDI packet. */ Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /**< temp storage for sysex data */ MIDITimeStamp sysex_timestamp; /**< timestamp to use with sysex data */ /**< * Allow for running status (is running status possible here? -rbd): -cpr */ unsigned char last_command; int32_t last_msg_length; /**< todo. */ /* * Limit MIDI data rate (a CoreMidi requirement). When can the next send take * place? */ UInt64 min_next_time; int byte_count; /**< How many bytes in the next packet list? */ Float64 us_per_host_tick; /**< Host clock freq, units of min_next_time. */ UInt64 host_ticks_per_byte; /**< Host clock units/byte at maximum rate. */ } midi_macosxcm_node, * midi_macosxcm_type; /* * private function declarations. */ MIDITimeStamp timestamp_pm_to_cm (PmTimestamp timestamp); PmTimestamp timestamp_cm_to_pm (MIDITimeStamp timestamp); char * cm_get_full_endpoint_name (MIDIEndpointRef endpoint); static int midi_length (int32_t msg) { int status, high, low; static int high_lengths[] = { 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 through 0x70 */ 3, 3, 3, 3, 2, 2, 3, 1 /* 0x80 through 0xf0 */ }; static int low_lengths[] = { 1, 2, 3, 2, 1, 1, 1, 1, /* 0xf0 through 0xf8 */ 1, 1, 1, 1, 1, 1, 1, 1 /* 0xf9 through 0xff */ }; status = msg & 0xFF; high = status >> 4; low = status & 15; return high != 0xF ? high_lengths[high] : low_lengths[low]; } static PmTimestamp midi_synchronize (PmInternal * midi) { midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; UInt64 pm_stream_time_2 = AudioConvertHostTimeToNanos ( AudioGetCurrentHostTime() ); PmTimestamp real_time; UInt64 pm_stream_time; /* * If latency is zero and this is an output, there is no time reference and * midi_synchronize should never be called. */ assert(midi->time_proc); assert(!(midi->write_flag && midi->latency == 0)); do { /* * read real_time between two reads of stream time. */ pm_stream_time = pm_stream_time_2; real_time = (*midi->time_proc)(midi->time_info); pm_stream_time_2 = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); /* * repeat if more than 0.5 ms has elapsed. */ } while (pm_stream_time_2 > pm_stream_time + 500000) ; m->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000); m->sync_time = real_time; return real_time; } /** * Handles a packet of MIDI messages from CoreMIDI. There may be multiple short * messages in one packet (!) */ static void process_packet ( MIDIPacket * packet, PmEvent * event, PmInternal * midi, midi_macosxcm_type m ) { unsigned remaining_length = packet->length; unsigned char * cur_packet_data = packet->data; while (remaining_length > 0) { /* * Are we in the middle of a SysEx message? */ if ( cur_packet_data[0] == MIDI_SYSEX || (m->last_command == 0 && ! (cur_packet_data[0] & MIDI_STATUS_MASK)) ) { m->last_command = 0; /* no running status */ unsigned amt = pm_read_bytes ( midi, cur_packet_data, remaining_length, event->timestamp ); remaining_length -= amt; cur_packet_data += amt; } else if (cur_packet_data[0] == MIDI_EOX) { /* * This should never happen, because pm_read_bytes() should * get and read all EOX bytes. */ midi->sysex_in_progress = FALSE; m->last_command = 0; } else if (cur_packet_data[0] & MIDI_STATUS_MASK) { /* * Compute the length of the next (short) message in the packet. */ unsigned cur_message_length = midi_length(cur_packet_data[0]); if (cur_message_length > remaining_length) { #if defined SEQ66_PLATFORM_DEBUG printf("PortMidi debug msg: not enough data"); #endif return; /* since there's no more data, we're done */ } m->last_msg_length = cur_message_length; m->last_command = cur_packet_data[0]; switch (cur_message_length) { case 1: event->message = Pm_Message(cur_packet_data[0], 0, 0); break; case 2: event->message = Pm_Message ( cur_packet_data[0], cur_packet_data[1], 0 ); break; case 3: event->message = Pm_Message ( cur_packet_data[0], cur_packet_data[1], cur_packet_data[2] ); break; default: /* * A PortMIDI internal error; should never happen. Give up on the * packet if continued after asserttion. */ assert(cur_message_length == 1); return; } pm_read_short(midi, event); remaining_length -= m->last_msg_length; cur_packet_data += m->last_msg_length; } else if (m->last_msg_length > remaining_length + 1) { /* * We have running status, but not enough data. */ #if defined SEQ66_PLATFORM_DEBUG printf("PortMidi debug msg: not enough data in CoreMIDI packet"); #endif return; /* since there's no more data, we're done */ } else /* output message using running status */ { switch (m->last_msg_length) { case 1: event->message = Pm_Message(m->last_command, 0, 0); break; case 2: event->message = Pm_Message(m->last_command, cur_packet_data[0], 0); break; case 3: event->message = Pm_Message ( m->last_command, cur_packet_data[0], cur_packet_data[1] ); break; default: /* * The last_msg_length is invalid -- internal PortMIDI error. */ assert(m->last_msg_length == 1); } pm_read_short(midi, event); remaining_length -= (m->last_msg_length - 1); cur_packet_data += (m->last_msg_length - 1); } } } /** * This function is called when MIDI packets are received. */ static void readProc (const MIDIPacketList * newPackets, void * refCon, void * connRefCon) { PmInternal * midi; midi_macosxcm_type m; PmEvent event; MIDIPacket * packet; unsigned packetIndex; uint32_t now; unsigned status; #if defined SEQ66_PLATFORM_DEBUG printf("readProc: numPackets %d: ", newPackets->numPackets); #endif midi = (PmInternal *) connRefCon; /* get context for this connection */ m = (midi_macosxcm_type) midi->descriptor; assert(m); now = (*midi->time_proc)(midi->time_info); /* synch every 100 ms */ if (m->first_message || m->sync_time + 100 < now) /* milliseconds */ { /* now = */ midi_synchronize(midi); /* time to resync */ m->first_message = FALSE; } packet = (MIDIPacket *) &newPackets->packet[0]; for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) { /* * Set the timestamp and dispatch this message. Do an explicit * conversion. */ event.timestamp = (PmTimestamp) ( (AudioConvertHostTimeToNanos(packet->timeStamp) - m->delta) / (UInt64) 1000000 ); status = packet->data[0]; /* * Process packet as SysEx data if it begins with MIDI_SYSEX, or * MIDI_EOX or non-status byte with no running status. */ if ( status == MIDI_SYSEX || status == MIDI_EOX || ((! (status & MIDI_STATUS_MASK)) && !m->last_command) ) { /* * Previously was: !(status & MIDI_STATUS_MASK)) {, but this could * mistake running status for SysEx data. So reset running status data * -- cpr */ m->last_command = 0; m->last_msg_length = 0; pm_read_bytes(midi, packet->data, packet->length, event.timestamp); } else process_packet(packet, &event, midi, m); packet = MIDIPacketNext(packet); } } static PmError midi_in_open (PmInternal * midi, void * driverInfo) { MIDIEndpointRef endpoint = NULL_REF; midi_macosxcm_type m; OSStatus macHostError; if (midi->time_proc == NULL)i /* insure we have a time_proc for timing */ { if (! Pt_Started()) Pt_Start(1, 0, 0); /* * time_get() does not take a parameter, so coerce it. */ midi->time_proc = (PmTimeProcPtr) Pt_Time; } endpoint = (MIDIEndpointRef)(long) descriptors[midi->device_id].descriptor; if (endpoint == NULL_REF) { return pmInvalidDeviceId; } m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */ midi->descriptor = m; if (is nullptr(m)) return pmInsufficientMemory; m->error[0] = 0; m->callback_error[0] = 0; m->sync_time = 0; m->delta = 0; m->last_time = 0; m->first_message = TRUE; m->sysex_mode = FALSE; m->sysex_word = 0; m->sysex_byte_count = 0; m->packetList = NULL; m->packet = NULL; m->last_command = 0; m->last_msg_length = 0; macHostError = MIDIPortConnectSource(portIn, endpoint, midi); if (macHostError != noErr) { pm_hosterror = macHostError; sprintf ( pm_hosterror_text, "Host error %ld: MIDIPortConnectSource() in midi_in_open()", (long) macHostError ); midi->descriptor = NULL; pm_free(m); return pmHostError; } return pmNoError; } static PmError midi_in_close (PmInternal * midi) { MIDIEndpointRef endpoint; OSStatus macHostError; PmError err = pmNoError; midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; if (is_nullptr(m)) return pmBadPtr; endpoint = (MIDIEndpointRef)(long) descriptors[midi->device_id].descriptor; if (endpoint == NULL_REF) pm_hosterror = pmBadPtr; /* * Shut off the incoming messages before freeing data structures. */ macHostError = MIDIPortDisconnectSource(portIn, endpoint); if (macHostError != noErr) { pm_hosterror = macHostError; sprintf ( pm_hosterror_text, "Host error %ld: MIDIPortDisconnectSource() in midi_in_close()", (long) macHostError ); err = pmHostError; } midi->descriptor = NULL; pm_free(midi->descriptor); return err; } static PmError midi_out_open (PmInternal * midi, void * driverInfo) { midi_macosxcm_type m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */ midi->descriptor = m; if (is_nullptr(m)) { return pmInsufficientMemory; } m->error[0] = 0; m->callback_error[0] = 0; m->sync_time = 0; m->delta = 0; m->last_time = 0; m->first_message = TRUE; m->sysex_mode = FALSE; m->sysex_word = 0; m->sysex_byte_count = 0; m->packetList = (MIDIPacketList *) m->packetBuffer; m->packet = NULL; m->last_command = 0; m->last_msg_length = 0; m->min_next_time = 0; m->byte_count = 0; m->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency(); m->host_ticks_per_byte = (UInt64) ( 1000000.0 / (m->us_per_host_tick * MAX_BYTES_PER_S) ); return pmNoError; } static PmError midi_out_close (PmInternal * midi) { midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; if (is_nullptr(m)) return pmBadPtr; midi->descriptor = NULL; pm_free(midi->descriptor); return pmNoError; } static PmError midi_abort (PmInternal * midi) { PmError err = pmNoError; OSStatus macHostError; MIDIEndpointRef endpoint = (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor; macHostError = MIDIFlushOutput(endpoint); if (macHostError != noErr) { pm_hosterror = macHostError; sprintf ( pm_hosterror_text, "Host error %ld: MIDIFlushOutput()", (long) macHostError ); err = pmHostError; } return err; } static PmError midi_write_flush (PmInternal * midi, PmTimestamp timestamp) { OSStatus macHostError; midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; MIDIEndpointRef endpoint = (MIDIEndpointRef)(long) descriptors[midi->device_id].descriptor; assert(m); assert(endpoint); if (m->packet != NULL) { /* * We are out of space; send the buffer and start refilling it. * Before we can send, maybe delay to limit the data rate. OS X allows * (only) 15KB/s. */ UInt64 now = AudioGetCurrentHostTime(); if (now < m->min_next_time) { usleep ( (useconds_t) ((m->min_next_time - now) * m->us_per_host_tick) ); } macHostError = MIDISend(portOut, endpoint, m->packetList); m->packet = NULL; /* indicate no data in packetList now */ m->min_next_time = now + m->byte_count * m->host_ticks_per_byte; m->byte_count = 0; if (macHostError != noErr) goto send_packet_error; } return pmNoError; send_packet_error: pm_hosterror = macHostError; sprintf /* PLEASE make these a function. */ ( pm_hosterror_text, "Host error %ld: MIDISend() in midi_write()", (long) macHostError ); return pmHostError; } static PmError send_packet ( PmInternal * midi, Byte * message, unsigned messageLength, MIDITimeStamp timestamp ) { PmError err; midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; assert(m); m->packet = MIDIPacketListAdd ( m->packetList, sizeof(m->packetBuffer), m->packet, timestamp, messageLength, message ); m->byte_count += messageLength; if (m->packet == NULL) { /* * We are out of space; send the buffer and start refilling it. Make * midi->packet non-null to fool midi_write_flush into sending. The * timestamp is 0 because midi_write_flush() ignores the timestamp since * timestamps are already in packets. The timestamp parameter is here * because other API's need it. midi_write_flush() can be called from * system-independent code that must be cross-API. */ m->packet = (MIDIPacket *) 4; if ((err = midi_write_flush(midi, 0)) != pmNoError) return err; m->packet = MIDIPacketListInit(m->packetList); assert(m->packet); /* if this fails, it's a programming error */ m->packet = MIDIPacketListAdd ( m->packetList, sizeof(m->packetBuffer), m->packet, timestamp, messageLength, message ); assert(m->packet); /* can't run out of space on first message */ } return pmNoError; } static PmError midi_write_short (PmInternal * midi, PmEvent * event) { PmTimestamp when = event->timestamp; PmMessage what = event->message; MIDITimeStamp timestamp; UInt64 when_ns; midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; Byte message[4]; unsigned messageLength; if (m->packet == NULL) { m->packet = MIDIPacketListInit(m->packetList); /* * This can never fail, right? Failure would indicate something * unrecoverable. */ assert(m->packet); } if (when == 0) when = midi->now; /* compute timestamp */ /* * If latency == 0, midi->now is not valid. We will just set it to zero. */ if (midi->latency == 0) when = 0; when_ns = ((UInt64)(when + midi->latency) * (UInt64) 1000000) + m->delta; timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns); message[0] = Pm_MessageStatus(what); message[1] = Pm_MessageData1(what); message[2] = Pm_MessageData2(what); messageLength = midi_length(what); /* * Make sure we go foreward in time. */ if (timestamp < m->min_next_time) timestamp = m->min_next_time; #if defined LIMIT_RATE if (timestamp < m->last_time) timestamp = m->last_time; m->last_time = timestamp + messageLength * m->host_ticks_per_byte; #endif /* * Add this message to the packet list. */ return send_packet(midi, message, messageLength, timestamp); } static PmError midi_begin_sysex (PmInternal * midi, PmTimestamp when) { UInt64 when_ns; midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; assert(m); m->sysex_byte_count = 0; if (when == 0) when = midi->now; /* compute timestamp */ /* * If latency == 0, midi->now is not valid. We will just set it to zero. */ if (midi->latency == 0) when = 0; when_ns = ((UInt64)(when + midi->latency) * (UInt64) 1000000) + m->delta; m->sysex_timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns); if (m->packet == NULL) { m->packet = MIDIPacketListInit(m->packetList); /* * This can never fail, right? failure would indicate something * unrecoverable. */ assert(m->packet); } return pmNoError; } static PmError midi_end_sysex (PmInternal * midi, PmTimestamp when) { PmError err; midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; assert(m); if (m->sysex_timestamp < m->min_next_time) /* ensure we go foreward in time */ m->sysex_timestamp = m->min_next_time; #if defined LIMIT_RATE if (m->sysex_timestamp < m->last_time) m->sysex_timestamp = m->last_time; m->last_time = m->sysex_timestamp + m->sysex_byte_count * m->host_ticks_per_byte; #endif /* * Now send what's in the buffer. */ err = send_packet(midi, m->sysex_buffer, m->sysex_byte_count, m->sysex_timestamp); m->sysex_byte_count = 0; if (err != pmNoError) { m->packet = NULL; /* flush everything in the packet list */ return err; } return pmNoError; } static PmError midi_write_byte (PmInternal * midi, unsigned char byte, PmTimestamp timestamp) { midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; assert(m); if (m->sysex_byte_count >= SYSEX_BUFFER_SIZE) { PmError err = midi_end_sysex(midi, timestamp); if (err != pmNoError) return err; } m->sysex_buffer[m->sysex_byte_count++] = byte; return pmNoError; } /** * To send a realtime message during a SysEx message, first flush all pending * SysEx bytes into the packet list. then we can just do a normal * midi_write_short(). * */ static PmError midi_write_realtime (PmInternal * midi, PmEvent * event) { PmError err = midi_end_sysex(midi, 0); if (err != pmNoError) return err; return midi_write_short(midi, event); } static unsigned midi_has_host_error (PmInternal * midi) { midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; return (m->callback_error[0] != 0) || (m->error[0] != 0); } static void midi_get_host_error(PmInternal *midi, char *msg, unsigned len) { midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; msg[0] = 0; /* initialize to empty string */ if (m) /* ensure there's an open device to examine */ { if (m->error[0]) { strncpy(msg, m->error, len); m->error[0] = 0; /* clear the error */ } else if (m->callback_error[0]) { strncpy(msg, m->callback_error, len); m->callback_error[0] = 0; /* clear the error */ } msg[len - 1] = 0; /* terminate string */ } } MIDITimeStamp timestamp_pm_to_cm (PmTimestamp timestamp) { UInt64 nanos; if (timestamp <= 0) { return (MIDITimeStamp) 0; } else { nanos = (UInt64)timestamp * (UInt64) 1000000; return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos); } } PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp) { UInt64 nanos; nanos = AudioConvertHostTimeToNanos(timestamp); return (PmTimestamp)(nanos / (UInt64)1000000); } /** * Code taken from http://developer.apple.com/qa/qa2004/qa1374.html. * * Obtain the name of an endpoint without regard for whether it has connections. * The result should be released by the caller. */ 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 = NULL_REF; MIDIEndpointGetEntity(endpoint, &entity); if (entity == NULL_REF) // 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 = NULL_REF; MIDIEntityGetDevice(entity, &device); if (device == NULL_REF) 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; } /** * Obtains the name of an endpoint, following connections. The result should be * released by the caller. */ static CFStringRef ConnectedEndpointName (MIDIEndpointRef endpoint) { CFMutableStringRef result = CFStringCreateMutable(NULL, 0); CFStringRef str; OSStatus err; CFDataRef connections = NULL; // Does the endpoint have connections? bool anyStrings = false; /* err = */ MIDIObjectGetDataProperty ( endpoint, kMIDIPropertyConnectionUniqueID, &connections ); if (connections != NULL) { /* * It has connections, follow them. Concatenate the names of all connected * devices. */ long nConnected = CFDataGetLength(connections) / (int32_t) sizeof(MIDIUniqueID); if (nConnected) { const SInt32 * pid = (const SInt32 *) CFDataGetBytePtr(connections); int i; for (i = 0; i < nConnected; ++i, ++pid) { MIDIUniqueID id = EndianS32_BtoN(*pid); MIDIObjectRef connObject; MIDIObjectType connObjectType; err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType); if (err == noErr) { if ( connObjectType == kMIDIObjectType_ExternalSource || connObjectType == kMIDIObjectType_ExternalDestination ) { /* * Connected to an external device's endpoint (10.3 and * later). */ str = EndpointName((MIDIEndpointRef)(connObject), true); } else { /* * Connected to an external device (10.2) (or something * else, catch-all). */ str = NULL; MIDIObjectGetStringProperty ( connObject, kMIDIPropertyName, &str ); } if (str != NULL) { if (anyStrings) CFStringAppend(result, CFSTR(", ")); else anyStrings = true; CFStringAppend(result, str); CFRelease(str); } } } } CFRelease(connections); } if (anyStrings) return result; /* * Here, either the endpoint had no connections, or we failed to obtain names * for any of them. */ return EndpointName(endpoint, false); } char * cm_get_full_endpoint_name (MIDIEndpointRef endpoint) { #if defined OLDCODE MIDIEntityRef entity; MIDIDeviceRef device; CFStringRef endpointName = NULL; CFStringRef deviceName = NULL; #endif CFStringRef fullName; // = NULL; CFStringEncoding defaultEncoding; char * newName; defaultEncoding = CFStringGetSystemEncoding(); /* string encoding */ fullName = ConnectedEndpointName(endpoint); #if defined OLDCODE MIDIEndpointGetEntity(endpoint, &entity); /* entity & device info */ MIDIEntityGetDevice(entity, &device); /* create the nicely formated name */ MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &endpointName); MIDIObjectGetStringProperty(device, kMIDIPropertyName, &deviceName); if (deviceName != NULL) { fullName = CFStringCreateWithFormat ( NULL, NULL, CFSTR("%@: %@"), deviceName, endpointName ); } else { fullName = endpointName; } #endif /* copy the string into our buffer */ newName = (char *) malloc(CFStringGetLength(fullName) + 1); CFStringGetCString ( fullName, newName, CFStringGetLength(fullName) + 1, defaultEncoding ); /* clean up */ #if defined OLDCODE if (endpointName) CFRelease(endpointName); if (deviceName) CFRelease(deviceName); #endif if (fullName) CFRelease(fullName); return newName; } pm_fns_node pm_macosx_in_dictionary = { none_write_short, none_sysex, none_sysex, none_write_byte, none_write_short, none_write_flush, none_synchronize, midi_in_open, midi_abort, midi_in_close, success_poll, midi_has_host_error, midi_get_host_error, }; pm_fns_node pm_macosx_out_dictionary = { midi_write_short, midi_begin_sysex, midi_end_sysex, midi_write_byte, midi_write_realtime, midi_write_flush, midi_synchronize, midi_out_open, midi_abort, midi_out_close, success_poll, midi_has_host_error, midi_get_host_error, }; PmError pm_macosxcm_init (void) { ItemCount numInputs, numOutputs, numDevices; MIDIEndpointRef endpoint; int i; OSStatus macHostError; char *error_text; /* * Determine the number of MIDI devices on the system. */ numDevices = MIDIGetNumberOfDevices(); numInputs = MIDIGetNumberOfSources(); numOutputs = MIDIGetNumberOfDestinations(); /* * Return prematurely if no devices exist on the system Note that this is not * an error. There may be no devices. Pm_CountDevices() will return zero, * which is correct and useful information */ if (numDevices <= 0) return pmNoError; /* * Initialize the client handle. Then create the input port. Then create the * output port. */ macHostError = MIDIClientCreate(CFSTR("PortMidi"), NULL, NULL, &client); if (macHostError != noErr) { error_text = "MIDIClientCreate() in pm_macosxcm_init()"; goto error_return; } macHostError = MIDIInputPortCreate ( client, CFSTR("Input port"), readProc, NULL, &portIn ); if (macHostError != noErr) { error_text = "MIDIInputPortCreate() in pm_macosxcm_init()"; goto error_return; } macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut); if (macHostError != noErr) { error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()"; goto error_return; } for (i = 0; i < numInputs; ++i) /* iterate over MIDI in devices */ { endpoint = MIDIGetSource(i); if (endpoint == NULL_REF) continue; /* * Set the first input we see to the default. Then register this device * with PortMidi. */ if (pm_default_input_device_id == -1) pm_default_input_device_id = pm_descriptor_index; pm_add_device ( "CoreMIDI", cm_get_full_endpoint_name(endpoint), TRUE, (void *) (long) endpoint, &pm_macosx_in_dictionary, i, 0 /* client and port */ ); } for (i = 0; i < numOutputs; ++i) /* iterate over MIDI out devs */ { endpoint = MIDIGetDestination(i); if (endpoint == NULL_REF) continue; /* * Set the first output we see to the default. Then register this device * with PortMidi. */ if (pm_default_output_device_id == -1) pm_default_output_device_id = pm_descriptor_index; pm_add_device ( "CoreMIDI", cm_get_full_endpoint_name(endpoint), FALSE, (void *)(long) endpoint, &pm_macosx_out_dictionary, i, 0 /* client and port */ ); } return pmNoError; error_return: pm_hosterror = macHostError; sprintf ( pm_hosterror_text, "Host error %ld: %s\n", (long) macHostError, error_text ); pm_macosxcm_term(); /* clear out any opened ports */ return pmHostError; } void pm_macosxcm_term (void) { if (client != NULL_REF) MIDIClientDispose(client); if (portIn != NULL_REF) MIDIPortDispose(portIn); if (portOut != NULL_REF) MIDIPortDispose(portOut); } /* * pmmacosxcm.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/src/pmutil.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmutil.c * * Some helpful utilities for building MIDI applications that use * PortMidi. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2023-05-13 * \license GNU GPLv2 or above * */ #include #include #include /* C::memset(3) in Linux */ #include "portmidi.h" #include "pmutil.h" #include "pminternal.h" /** * PortMidi queue element. */ typedef struct { long head; long tail; long len; long overflow; int32_t msg_size; /* number of int32_t in a message including extra word */ int32_t peek_overflow; int32_t * buffer; int32_t * peek; int32_t peek_flag; } PmQueueRep; /** * Allocates: * * - PmQueueReg queue * - int32_t buffer * - int32_t peek */ PMEXPORT PmQueue * Pm_QueueCreate (long num_msgs, int32_t bytes_per_msg) { const size_t sint32 = sizeof(int32_t); int32_t int32s_per_msg = (int32_t) ( ( (bytes_per_msg + sint32 - 1) & ~(sint32 - 1) ) / sint32 ); PmQueueRep * queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep)); if (! queue) /* memory allocation failed */ return NULL; /* * need extra word per message for non-zero encoding */ queue->len = num_msgs * (int32s_per_msg + 1); queue->buffer = (int32_t *) pm_alloc(queue->len * sint32); memset(queue->buffer, 0, queue->len * sint32); if (! queue->buffer) { pm_free(queue); return NULL; } else /* allocate "peek" buffer */ { queue->peek = (int32_t *) pm_alloc(int32s_per_msg * sint32); if (! queue->peek) { /* free everything allocated so far and return */ pm_free(queue->buffer); pm_free(queue); return NULL; } } memset(queue->buffer, 0, queue->len * sint32); queue->head = queue->tail = 0; /* msg_size is in words */ queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */ queue->overflow = FALSE; queue->peek_overflow = FALSE; queue->peek_flag = FALSE; return queue; } PMEXPORT PmError Pm_QueueDestroy (PmQueue * q) { PmQueueRep * queue = (PmQueueRep *) q; if (!queue || ! queue->buffer || ! queue->peek) /* arg checking */ return pmBadPtr; pm_free(queue->peek); pm_free(queue->buffer); pm_free(queue); return pmNoError; } PMEXPORT PmError Pm_Dequeue(PmQueue * q, void * msg) { static const size_t sint32 = sizeof(int32_t); long head; PmQueueRep * queue = (PmQueueRep *) q; int i; int32_t * msg_as_int32 = (int32_t *) msg; if (! queue) /* arg checking */ return pmBadPtr; /* * A previous peek operation encountered an overflow, but the overflow has * not yet been reported to client, so do it now. No message is returned, * but on the next call, we will return the peek buffer. */ if (queue->peek_overflow) { queue->peek_overflow = FALSE; return pmBufferOverflow; } if (queue->peek_flag) { memcpy(msg, queue->peek, (queue->msg_size - 1) * sint32); queue->peek_flag = FALSE; return pmGotData; } head = queue->head; /* * If writer overflows, it writes queue->overflow = tail+1 so that when the * reader gets to that position in the buffer, it can return the overflow * condition to the reader. The problem is that at overflow, things have * wrapped around, so tail == head, and the reader will detect overflow * immediately instead of waiting until it reads everything in the buffer, * wrapping around again to the point where tail == head. So the condition * also checks that queue->buffer[head] is zero -- if so, then the buffer * is now empty, and we're at the point in the msg stream where overflow * occurred. It's time to signal overflow to the reader. If * queue->buffer[head] is non-zero, there's a message there and we should * read all the way around the buffer before signalling overflow. There is * a write-order dependency here, but to fail, the overflow field would * have to be written while an entire buffer full of writes are still * pending. I'm assuming out-of-order writes are possible, but not that * many. */ if (queue->overflow == head + 1 && ! queue->buffer[head]) { queue->overflow = 0; /* non-overflow condition */ return pmBufferOverflow; } /* * Test to see if there is data in the queue -- test from back to front so * if writer is simultaneously writing, we don't waste time discovering the * write is not finished. */ for (i = queue->msg_size - 1; i >= 0; --i) { if (! queue->buffer[head + i]) return pmNoData; } memcpy(msg, (char *) &queue->buffer[head+1], sint32 * (queue->msg_size-1)); i = queue->buffer[head]; /* fix up zeros */ while (i < queue->msg_size) { int32_t j; --i; /* msg does not have extra word; shift down */ j = msg_as_int32[i]; msg_as_int32[i] = 0; i = j; } /* * Signal that data has been removed by zeroing the buffer. */ memset(&queue->buffer[head], 0, sint32 * queue->msg_size); head += queue->msg_size; /* update head */ if (head == queue->len) head = 0; queue->head = head; return pmGotData; /* success */ } /* * cppcheck: unused function. */ PMEXPORT PmError Pm_SetOverflow (PmQueue * q) { PmQueueRep * queue = (PmQueueRep *) q; long tail; if (! queue) /* arg checking */ return pmBadPtr; /* no more enqueue until receiver acknowledges overflow */ if (queue->overflow) return pmBufferOverflow; tail = queue->tail; queue->overflow = tail + 1; return pmBufferOverflow; } PMEXPORT PmError Pm_Enqueue (PmQueue * q, void * msg) { PmQueueRep * queue = (PmQueueRep *) q; long tail; int i; int32_t * src = (int32_t *) msg; int32_t * ptr; int32_t * dest; int rslt; if (! queue) return pmBadPtr; /* no more enqueue until receiver acknowledges overflow */ if (queue->overflow) return pmBufferOverflow; rslt = Pm_QueueFull(q); /* already checked above: if (rslt == pmBadPtr) return rslt; */ tail = queue->tail; if (rslt) { queue->overflow = tail + 1; return pmBufferOverflow; } /* queue is has room for a message, and overflow flag is cleared */ ptr = &queue->buffer[tail]; dest = ptr + 1; for (i = 1; i < queue->msg_size; i++) { int32_t j = src[i - 1]; if (! j) { *ptr = i; ptr = dest; } else { *dest = j; } dest++; } *ptr = i; tail += queue->msg_size; if (tail == queue->len) tail = 0; queue->tail = tail; return pmNoError; } /** * null pointer -> return "empty" */ PMEXPORT int Pm_QueueEmpty (PmQueue * q) { PmQueueRep * queue = (PmQueueRep *) q; return (! queue) || (queue->buffer[queue->head] == 0 && ! queue->peek_flag); } PMEXPORT int Pm_QueueFull (PmQueue * q) { long tail; int i; PmQueueRep * queue = (PmQueueRep *) q; if (! queue) /* arg checking */ return pmBadPtr; /* test to see if there is space in the queue */ tail = queue->tail; for (i = 0; i < queue->msg_size; ++i) { if (queue->buffer[tail + i]) return TRUE; } return FALSE; } /* * cppcheck: unused function. */ PMEXPORT void * Pm_QueuePeek (PmQueue * q) { PmError rslt; int32_t temp; PmQueueRep * queue = (PmQueueRep *) q; if (! queue) /* arg checking */ return NULL; if (queue->peek_flag) return queue->peek; /* * This is ugly: if peek_overflow is set, then Pm_Dequeue() returns * immediately with pmBufferOverflow, but here, we want Pm_Dequeue() to * really check for data. If data is there, we can return it. */ temp = queue->peek_overflow; queue->peek_overflow = FALSE; rslt = Pm_Dequeue(q, queue->peek); queue->peek_overflow = temp; if (rslt == 1) { queue->peek_flag = TRUE; return queue->peek; } else if (rslt == pmBufferOverflow) { /* * When overflow is indicated, the queue is empty and the first message * that was dropped by Enqueue (signalling pmBufferOverflow to its * caller) would have been the next message in the queue. Pm_QueuePeek * will return NULL, but remember that an overflow occurred. (see * Pm_Dequeue) */ queue->peek_overflow = TRUE; } return NULL; } /* * pmutil.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/src/pmwin.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmwin.c * * PortMidi os-dependent code for Windows. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2023-06-02 * \license GNU GPLv2 or above * This file needs to implement pm_init(), which calls various routines to * register the available MIDI devices. * * This file must be separate from the main portmidi.c file because it is * system dependent, and it is separate from, say, pmwinmm.c, because it * might need to register devices for WinMM, DirectX, and others. */ #include #include #include "util/basic_macros.h" /* not_nullptr() macro, etc. */ #include "portmidi.h" /* Pm_set_initialized(), etc. */ #include "pmutil.h" #include "pmwinmm.h" #include "portmidi.h" /** * This macro is part of Microsoft's tchar.h, but we want to use it only as * a marker for now. * * #include */ #define _T(x) ((char *)(x)) /** * The maximum length of the name of a device. */ #define PATTERN_MAX 256 /** * pm_exit() is called when the program exits. It calls pm_term() to make * sure PortMidi is properly closed. */ static void pm_exit (void) { pm_term(); } /* * pm_init() provides the windows-dependent initialization. It also sets the * atexit() callback to pm_exit(). */ void pm_init (void) { atexit(pm_exit); pm_winmm_init(); /* * Initialize other APIs (DirectX?) here. Don't we need to set * pm_initialized = TRUE here? And call find_default_device()? * No. */ } /** * Calls pm_winmm_term() to end the PortMidi session. */ void pm_term (void) { pm_winmm_term(); } /* * pmwin.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/src/pmwinmm.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file pmwinmm.c * * System specific definitions for the Windows MM API. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2023-06-28 * \license GNU GPLv2 or above * * Check out this site: * * http://donyaquick.com/midi-on-windows/ * "Working with MIDI on Windows (Outside of a DAW)" */ #include "seq66_platform_macros.h" /* compilation environment settings */ #if defined SEQ66_PLATFORM_MSVC /* based on _MSC_VER */ #pragma warning(disable: 4133) /* stop implicit typecast warnings */ #endif /* * Without this #define, InitializeCriticalSectionAndSpinCount is undefined. * This version level means "Windows 2000 and higher". */ #if ! defined _WIN32_WINNT #define _WIN32_WINNT 0x0500 #endif /* * The former is defined in seq66_features.h (included by basic_macros.h), * but what about the latter? Sequencer64 defines the former!!! * * SEQ66_PORTMIDI_SYSEX_PROCESSING */ #include #include #include #include "pmerrmm.h" /* Windows error support, debugging */ #include "pmutil.h" #include "pmwinmm.h" #include "portmidi.h" /* UNUSED() macro, and PortMidi API */ #include "porttime.h" #include "util/basic_macros.h" /* not_nullptr() macro, features */ /* * Asserts are used to verify PortMidi code logic is sound; later may want * something more graceful. * * #include */ /* * WinMM API callback routines. */ static void CALLBACK winmm_in_callback ( HMIDIIN hmi, WORD wmsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ); static void CALLBACK winmm_streamout_callback ( HMIDIOUT hmo, UINT wmsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ); /* * Note that this macro was undefined, as it is in the original PortMidi * library. */ #if defined SEQ66_PORTMIDI_SYSEX_PROCESSING static void CALLBACK winmm_out_callback ( HMIDIOUT hmo, UINT wmsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ); #endif static void winmm_out_delete (PmInternal * midi); extern pm_fns_node pm_winmm_in_dictionary; extern pm_fns_node pm_winmm_out_dictionary; /** * If the Windows MIDI Mapper is active, then the wavetable synthesizer * will not be directly available. */ static cbool_t s_midi_mapper_active = false; /** * \note * WinMM seems to hold onto buffers longer than one would expect, e.g. * when I tried using 2 small buffers to send long SysEx messages, at * some point WinMM held both buffers. This problem was fixed by making * buffers bigger. Therefore, it seems that there should be enough buffer * space to hold a whole SysEx message. * * The bufferSize passed into Pm_OpenInput (passed into here as buffer_len) * will be used to estimate the largest SysEx message (= buffer_len * 4 * bytes). Call that the max_sysex_len = buffer_len * 4. * * For simple midi output (latency == 0), allocate 3 buffers, each with half * the size of max_sysex_len, but each at least 256 bytes. * * For stream output, there will already be enough space in very short * buffers, so use them, but make sure there are at least 16. * * For input, use many small buffers rather than 2 large ones so that when * there are short SysEx messages arriving frequently (as in control surfaces) * there will be more free buffers to fill. Use max_sysex_len / 64 buffers, * but at least 16, of size 64 bytes each. * * The following constants help to represent these design parameters. */ #define NUM_SYSEX_BUFFERS 4 /* not in original PortMID lib! */ #define SYSEX_BYTES_PER_BUFFER 128 /* ditto!!!! */ #define NUM_SIMPLE_SYSEX_BUFFERS 3 #define MIN_SIMPLE_SYSEX_LEN 256 #define MIN_STREAM_BUFFERS 16 #define STREAM_BUFFER_LEN 24 #define INPUT_SYSEX_LEN 64 #define MIN_INPUT_BUFFERS 16 /** * If we run out of space for output (assuming this is due to a SysEx * message), then expand the output buffer by up to NUM_EXPANSION_BUFFERS, in * increments of EXPANSION_BUFFER_LEN. */ #define NUM_EXPANSION_BUFFERS 128 #define EXPANSION_BUFFER_LEN 1024 /** * A SysEx buffer has 3 DWORDS as a header plus the actual message size. * * \warning * The size was assumed to be long, we changed it to DWORD. */ #define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(DWORD) * 3) /** * A MIDIHDR with a SysEx message is the buffer length plus the header size. */ #define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) #if defined SEQ66_PORTMIDI_SYSEX_PROCESSING /** * Size of a MIDIHDR with a buffer contaning multiple MIDIEVENT structures. */ #define MIDIHDR_SIZE(x) ((x) + sizeof(MIDIHDR)) #endif /** * win32 multi-media-system-specific structure passed to MIDI callbacks; * it is global WinMM device informations. * * typedef struct * { * WORD wMid; * WORD wPid; * VERSION vDriverVersion; * char szPname[MAXPNAMELEN]; * DWORD dwSupport; * } MIDIINCAPS; * * typedef struct * { * WORD wMid; * WORD wPid; * VERSION vDriverVersion; * char szPname[MAXPNAMELEN]; * WORD wTechnology; * WORD wVoices; * WORD wNotes; * WORD wChannelMask; * DWORD dwSupport; * } MIDIOUTCAPS; * * wMid Manufacturer identifier of the device driver for the MIDI device. * * wPid Product identifier of the MIDI device. * * vDriverVersion Version number of the device driver. High/Low-order bytes * are the major/minor version numbers. * * szPname[MAXPNAMELEN] Product name in a null-terminated string. * * wTechnology Type of the MIDI device, one of the following: * * MOD_UNKNOWN (0). * MOD_MIDIPORT (1). MIDI hardware port. * MOD_SYNTH (2). Synthesizer. * MOD_SQSYNTH (3). Square wave synthesizer. * MOD_FMSYNTH (4). FM synthesizer. * MOD_MAPPER (5). Microsoft MIDI mapper. * MOD_WAVETABLE (6). Hardware wavetable synthesizer. * MOD_SWSYNTH (7). Software synthesizer. * * wVoices Number of voices supported by internal synthesizer. If a port, * this vale is set to 0. * * wNotes Maximum number of notes that can be played by an internal device. * If a port, it is set to 0. * * wChannelMask Channels that an internal device responds to, LSB = 0, MSB = 15. * Port devices that transmit on all channels set this member to 0xFFFF. * * dwSupport Optional functionality supported by the device. One or more of * the following: * * MIDICAPS_CACHE Supports patch caching. * MIDICAPS_LRVOLUME Supports separate left and right volume control. * MIDICAPS_STREAM Provides direct support for the midiStreamOut function. * MIDICAPS_VOLUME Supports volume control. * */ static MIDIINCAPS * midi_in_caps = nullptr; static MIDIINCAPS midi_in_mapper_caps; static UINT midi_num_inputs = 0; static int midi_input_index = 0; static MIDIOUTCAPS * midi_out_caps = nullptr; static MIDIOUTCAPS midi_out_mapper_caps; static UINT midi_num_outputs = 0; static int midi_output_index = 0; /** * The MIDI_MAPPER descriptor number (-1), defined in mmsystem.h, is another * example of Microsoft's clumsy approach to C code dating back decades. * PortMidi compounds it by not adopting a pointer for the descriptor * universally. Weird stuff. */ static void * PTR_MIDIMAPPER = ((void *) ((uintptr_t) MIDI_MAPPER)); static UINT UINT_MIDIMAPPER = ((UINT) MIDI_MAPPER); /** * This complex structure provides per-device information. */ typedef struct midiwinmm_struct { union { HMIDISTRM stream; /**< Windows handle for stream. */ HMIDIOUT out; /**< Windows handle for out calls. */ HMIDIIN in; /**< Windows handle for in calls. */ } handle; /** * MIDI output messages are sent in these buffers, which are allocated * in a round-robin fashion, using next_buffer as an index. */ LPMIDIHDR * buffers; /**< Pool of buffers for midi in or out data. */ int max_buffers; /**< Length of buffers array. */ int buffers_expanded; /**< Buffers array expanded for extra messages? */ int num_buffers; /**< How many buffers allocated in the array. */ int next_buffer; /**< Index of next buffer to send. */ HANDLE buffer_signal; /**< Used to wait for buffer to become free. */ /* * SysEx buffers will be allocated only when * a SysEx message is sent. The size of the buffer is fixed. */ #if defined SEQ66_PORTMIDI_SYSEX_PROCESSING LPMIDIHDR sysex_buffers[NUM_SYSEX_BUFFERS]; /**< Pool for SysEx data. */ int next_sysex_buffer; /**< Index of next SysEx buffer to send. */ #endif unsigned long last_time; /**< Last output time. */ int first_message; /**< Flag: treat first message differently. */ int sysex_mode; /**< Middle of sending SysEx. */ unsigned long sysex_word; /**< Accumulate data when receiving SysEx. */ unsigned sysex_byte_count; /**< Count how many SysEx bytes received. */ LPMIDIHDR hdr; /**< Message accumulating SysEx to send (?) */ unsigned long sync_time; /**< When did we last determine delta? */ long delta; /**< Stream time minus real time. */ int error; /**< Host error from doing port MIDI call. */ CRITICAL_SECTION lock; /**< Prevents reentrant callbacks (input). */ } midiwinmm_node, * midiwinmm_type; /** * General MIDI device input queries. If we can't open a particular * system-level MIDI interface (WinMM), we consider that system/API * unavailable and move on without an error. This function is called third * at startup. * * In the original PortMidi implementation, midiInGetDevCaps() and * pm_add_devices are called for devices 0 to midiInGetNumDevs()-1. Here, * since we may have already gotten an input mapper, we add to the device * index appropriately. */ static void pm_winmm_general_inputs (void) { midi_num_inputs = midiInGetNumDevs(); /* UINT */ midi_in_caps = pm_alloc(sizeof(MIDIINCAPS) * midi_num_inputs); if (is_nullptr(midi_in_caps)) /* see banner notes */ { pm_log_buffer_append("No MIDI input devices found.\n"); } else { char temp[PM_STRING_MAX]; int ins = (int) midi_num_inputs; #if defined SEQ66_PLATFORM_64_BIT UINT_PTR in; #else UINT in; #endif int index; snprintf(temp, sizeof temp, "%d MIDI inputs found\n", ins); pm_log_buffer_append(temp); for ( in = 0, index = midi_input_index; in < (UINT_PTR) midi_num_inputs; ++in, ++index ) { WORD winerrcode = midiInGetDevCaps ( (UINT) in, /* 0 and up */ (LPMIDIINCAPS) &midi_in_caps[in], sizeof(MIDIINCAPS) ); const char * devname = (const char *) midi_in_caps[in].szPname; if (winerrcode == MMSYSERR_NOERROR) { (void) pm_add_device /* ignore errors */ ( "MMSystem", /* subsystem name */ (char *) devname, /* interface name */ TRUE, /* it is an input */ (void *) in, /* void* descriptor */ &pm_winmm_in_dictionary, /* pm_fns_type */ index, /* client number */ index /* port number */ ); snprintf ( temp, sizeof temp, "[%d] MIDI input dev %d: '%s'\n", index, (int) in, devname ); infoprint(temp); /* log to console */ } else { const char * name = strlen(devname) == 0 ? "Unknown Input" : devname ; const char * errmsg = midi_io_get_dev_caps_error ( name, "Input Devices: midiInGetDevCaps", winerrcode ); snprintf ( temp, sizeof temp, "[%d] '%s' dev %d: error '%s'\n", index, name, (int) in, errmsg ); errprint(temp); /* log to console */ } pm_log_buffer_append(temp); /* log to buffer */ } } } /** * Tries to open the Windows MIDI Mapper, which is represented by the value * MIDI_MAPPER = (-1), which is supposed to be a legal value. If valid, * what client and port numbers to we want to use? However, it is not * valid. This function is called first. Is it still useless if the Coolsoft * MIDI Mapper is installed? * * \note * If MIDI_MAPPER opened as input (the documentation implies you can, * but the current system fails to retrieve input mapper capabilities), * then we still should retrieve some form of setup information. */ static void pm_winmm_mapper_input (void) { #if defined USE_THIS_TEST_CODE UINT count = midiInGetNumDevs(); /* ca 2023-05-12 */ if (count == 0) return; #endif WORD winerrcode = midiInGetDevCaps ( UINT_MIDIMAPPER, (LPMIDIINCAPS) &midi_in_mapper_caps, sizeof(MIDIINCAPS) ); char temp[PM_STRING_MAX]; const char * devname = (const char *) midi_in_mapper_caps.szPname; if (winerrcode == MMSYSERR_NOERROR) { (void) pm_add_device ( "MMSystem", (char *) devname, TRUE, PTR_MIDIMAPPER, &pm_winmm_in_dictionary, midi_input_index, midi_input_index /* client/port 0 */ ); ++midi_input_index; snprintf(temp, sizeof temp, "MIDI Mapper Input: '%s'\n", devname); } else { const char * errmsg = midi_io_get_dev_caps_error ( devname, "MIDI Mapper: midiInGetDevCaps", winerrcode ); snprintf(temp, sizeof temp, "'%s' error: '%s'\n", devname, errmsg); errprint(temp); /* log to console */ } pm_log_buffer_append(temp); /* log to buffer */ } /** * General MIDI device output queries. */ static void pm_winmm_general_outputs (void) { midi_num_outputs = midiOutGetNumDevs(); /* UINT */ midi_out_caps = pm_alloc(sizeof(MIDIOUTCAPS) * midi_num_outputs); if (is_nullptr(midi_out_caps)) { pm_log_buffer_append("No MIDI output devices found.\n"); } else { char temp[PM_STRING_MAX]; int outs = (int) midi_num_outputs; #if defined SEQ66_PLATFORM_64_BIT UINT_PTR out; #else UINT out; #endif int index; snprintf(temp, sizeof temp, "%d MIDI outputs found\n", outs); pm_log_buffer_append(temp); for ( out = 0, index = midi_output_index; out < (UINT) midi_num_outputs; ++out, ++index ) { DWORD winerrcode = midiOutGetDevCaps ( (UINT) out, /* 0 and up */ (LPMIDIOUTCAPS) &midi_out_caps[out], sizeof(MIDIOUTCAPS) ); const char * devname = (const char *) midi_out_caps[out].szPname; if (winerrcode == MMSYSERR_NOERROR) { snprintf ( temp, sizeof temp, "[%d] MIDI output dev %d: '%s'\n", index, (int) out, devname ); (void) pm_add_device ( "MMSystem", (char *) devname, FALSE, (void *) out, &pm_winmm_out_dictionary, index, index ); } else { const char * name = strlen(devname) == 0 ? "Unknown Output" : devname ; const char * errmsg = midi_io_get_dev_caps_error ( name, "Output Devices : midiOutGetDevCaps", winerrcode ); snprintf ( temp, sizeof temp, "[%d] '%s' dev %d: error '%s'\n", index, name, (int) out, errmsg ); errprint(temp); /* log to console */ } pm_log_buffer_append(temp); /* log to buffer */ } } } /** * If MIDI_MAPPER is opened as output (a pseudo MIDI device that maps * device-independent messages into device dependent ones, via the Windows * NT midimapper program), we still should get some setup information. This * function is called second, after the mapper-input function. */ static void pm_winmm_mapper_output (void) { #if defined USE_THIS_TEST_CODE UINT count = midiOutGetNumDevs(); /* ca 2023-05-12 */ if (count == 0) return; #endif WORD winerrcode = midiOutGetDevCaps ( UINT_MIDIMAPPER, (LPMIDIOUTCAPS) &midi_out_mapper_caps, sizeof(MIDIOUTCAPS) ); char temp[PM_STRING_MAX]; const char * devname = (const char *) midi_out_mapper_caps.szPname; if (winerrcode == MMSYSERR_NOERROR) { (void) pm_add_device ( "MMSystem", (char *) devname, FALSE, PTR_MIDIMAPPER, &pm_winmm_out_dictionary, midi_output_index, midi_output_index /* client/port 0 */ ); ++midi_output_index; snprintf(temp, sizeof temp, "Mapper output '%s'\n", devname); } else { const char * errmsg = midi_io_get_dev_caps_error ( devname, "Mapper Out: midiOutGetDevCaps", winerrcode ); snprintf(temp, sizeof temp, "[%s] %s\n", devname, errmsg); errprint(temp); /* log to console */ } pm_log_buffer_append(temp); /* log to buffer */ } /* * Host error handling. */ /** * Check the descriptor for an error status. */ static unsigned winmm_has_host_error (PmInternal * midi) { midiwinmm_type m = (midiwinmm_type) midi->descriptor; return m->error; } /** * str_copy_len() is like strcat, but it won't overrun the destination * string. Just in case the suffix is greater then len, terminate with * zero. * * \return * Length of resulting string. */ static int str_copy_len (char * dst, char * src, int len) { strncpy(dst, src, len); dst[len - 1] = 0; return strlen(dst); } /** * Note that input and output use different WinMM API calls. Make sure there * is an open device (m) to examine. If there is an error, then read and * record the host error. Note that the error codes returned by the * get-error-text functions are for that function. We disable those asserts * here. * * The precondition is that midi != null. */ static void winmm_get_host_error (PmInternal * midi, char * msg, UINT len) { midiwinmm_node * m = not_nullptr(midi) ? (midiwinmm_node *) midi->descriptor : nullptr ; if (not_nullptr_2(m, msg)) { char * hdr1 = "Host error: "; msg[0] = 0; /* set result string to empty */ if (pm_descriptors[midi->device_id].pub.input) { if (m->error != MMSYSERR_NOERROR) { int n = str_copy_len(msg, hdr1, len); int err = midiInGetErrorText(m->error, msg + n, len - n); if (err == MMSYSERR_NOERROR) m->error = MMSYSERR_NOERROR; } } else /* output port */ { if (m->error != MMSYSERR_NOERROR) { int n = str_copy_len(msg, hdr1, len); int err = midiOutGetErrorText(m->error, msg + n, len - n); if (err == MMSYSERR_NOERROR) m->error = MMSYSERR_NOERROR; } } } } /** * Buffer handling. */ static MIDIHDR * allocate_buffer (long data_size) /* should be size_t! */ { LPMIDIHDR hdr = nullptr; if (data_size > 0) { hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size)); if (not_nullptr(hdr)) { MIDIEVENT * evt = (MIDIEVENT *)(hdr + 1); /* placed after header */ hdr->lpData = (LPSTR) evt; hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size); hdr->dwBytesRecorded = hdr->dwFlags = 0; hdr->dwUser = hdr->dwBufferLength; } } return hdr; } #if defined SEQ66_PORTMIDI_SYSEX_PROCESSING /** * we're actually allocating more than data_size because the buffer * will include the MIDIEVENT header in addition to the data */ static MIDIHDR * allocate_sysex_buffer (long data_size) { LPMIDIHDR hdr = nullptr; if (data_size > 0) { hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size)); if (not_nullptr(hdr)) { MIDIEVENT * evt = (MIDIEVENT *)(hdr + 1); /* placed after header */ hdr->lpData = (LPSTR) evt; hdr->dwFlags = hdr->dwUser = 0; } } return hdr; } #endif /** * Buffers is an array of 'count' pointers to an MIDIHDR/MIDIEVENT struct. */ static PmError allocate_buffers (midiwinmm_type m, long data_size, long count) { int i; m->num_buffers = 0; /* in case no memory can be allocated */ m->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); if (is_nullptr(m->buffers)) return pmInsufficientMemory; m->max_buffers = count; for (i = 0; i < count; ++i) { LPMIDIHDR hdr = allocate_buffer(data_size); if (is_nullptr(hdr)) /* free all allocations and return */ { for (i = i - 1; i >= 0; --i) pm_free(m->buffers[i]); /* m->buffers[i] = nullptr too? */ pm_free(m->buffers); /* zero out the pointers? */ m->buffers = nullptr; m->max_buffers = 0; return pmInsufficientMemory; } m->buffers[i] = hdr; /* this may be null if allocation fails */ } m->num_buffers = count; return pmNoError; } #if defined SEQ66_PORTMIDI_SYSEX_PROCESSING /** * sysex_buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ static PmError allocate_sysex_buffers (midiwinmm_type m, long data_size) { PmError rslt = pmNoError; int i; for (i = 0; i < NUM_SYSEX_BUFFERS; ++i) { LPMIDIHDR hdr = allocate_sysex_buffer(data_size); if (is_nullptr(hdr)) rslt = pmInsufficientMemory; m->sysex_buffers[i] = hdr; /* may be null if allocation fails */ if (not_nullptr(hdr)) hdr->dwFlags = 0; /* mark as free */ } return rslt; } /* * cppcheck: unused function. */ /* static */ LPMIDIHDR get_free_sysex_buffer (PmInternal * midi) { LPMIDIHDR r = nullptr; midiwinmm_type m = (midiwinmm_type) midi->descriptor; if (! m->sysex_buffers[0]) { if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER)) return nullptr; } for (;;) /* busy wait to find a free buffer */ { int i; for (i = 0; i < NUM_SYSEX_BUFFERS; ++i) { /* cycle through buffers, modulo NUM_SYSEX_BUFFERS */ m->next_sysex_buffer++; if (m->next_sysex_buffer >= NUM_SYSEX_BUFFERS) m->next_sysex_buffer = 0; r = m->sysex_buffers[m->next_sysex_buffer]; if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_sysex_buffer; } /* * After scanning every buffer and not finding anything, block. */ if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) { warnprint ( "PortMidi warning: get_free_sysex_buffer() wait timed " "out after 1000 ms\n" ); } } found_sysex_buffer: r->dwBytesRecorded = 0; r->dwBufferLength = 0; /* changed to correct value later */ return r; } #endif /** * 1. Cycle through buffers, modulo m->num_buffers. * 2. Expand SysEx buffer. If we're trying to send a sysex message, maybe the * message is too big and we need more message buffers. Expand the buffer * pool by 128KB using 1024-byte buffers. First, expand the buffers array if * necessary. * 3. No Memory. If no memory, we could return a no-memory error, but user * probably will be unprepared to deal with it. Maybe the MIDI driver is * temporarily hung so we should just wait. I don't know the right answer, * but waiting is easier. * 4. Just Keep Polling. Otherwise, we've allocated all NUM_EXPANSION_BUFFERS * buffers, and we have no free buffers to send. We'll just keep polling to * see if any buffers show up. */ static LPMIDIHDR get_free_output_buffer (PmInternal * midi) { LPMIDIHDR r = nullptr; midiwinmm_type m = (midiwinmm_type) midi->descriptor; for (;;) { int i; for (i = 0; i < m->num_buffers; ++i) { m->next_buffer++; /* 1. Cycle through */ if (m->next_buffer >= m->num_buffers) m->next_buffer = 0; r = m->buffers[m->next_buffer]; if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer; } /* * After scanning every buffer and not finding anything, block. */ if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) { warnprint ( "PortMidi warning: get_free_output_buffer() " " wait timed out after 1000ms\n" ); if (! m->buffers_expanded) /* 2. Expand SysEx buff */ { int buffcount = m->num_buffers + NUM_EXPANSION_BUFFERS; size_t sz = buffcount * sizeof(LPMIDIHDR); LPMIDIHDR * new_buffers = (LPMIDIHDR *) pm_alloc(sz); if (is_nullptr(new_buffers)) /* 3. No Memory */ { c_millisleep(1); continue; } /* * Copy buffers to new_buffers and replace buffers. */ sz = m->num_buffers * sizeof(LPMIDIHDR); memcpy(new_buffers, m->buffers, sz); pm_free(m->buffers); m->buffers = new_buffers; m->max_buffers = buffcount; m->buffers_expanded = TRUE; } /* * Next, add one buffer and return it. */ if (m->num_buffers < m->max_buffers) { r = allocate_buffer(EXPANSION_BUFFER_LEN); if (is_nullptr(r)) /* 3. No Memory (again) */ { c_millisleep(1); continue; } m->buffers[m->num_buffers++] = r; goto found_buffer; /* break out of 2 loops */ } /* * 4. Just Keep Polling. We also add a 1 millisecond wait * to see if we can reduce CPU usage. */ c_millisleep(1); } } found_buffer: /* * Actual buffer length is saved in dwUser field. */ r->dwBytesRecorded = 0; r->dwBufferLength = (DWORD) r->dwUser; return r; } #if defined EXPANDING_SYSEX_BUFFERS /* * Note: * * This is not working code, but might be useful if you want to grow * sysex buffers. * * Buffer must be smaller than 64k, but be also a multiple of 4. */ static PmError resize_sysex_buffer (PmInternal * midi, long old_size, long new_size) { LPMIDIHDR big; int i; midiwinmm_type m = (midiwinmm_type) midi->descriptor; if (new_size > 65520) { if (old_size >= 65520) return pmBufferMaxSize; else new_size = 65520; } big = allocate_sysex_buffer(new_size); /* allocate a bigger message */ if (is_nullptr(big)) return pmInsufficientMemory; m->error = midiOutPrepareHeader(m->handle.out, big, sizeof(MIDIHDR)); if (m->error) { pm_free(big); return pmHostError; } /* * Make sure we're not going to overwrite any memory. */ if (old_size <= new_size) memcpy(big->lpData, m->hdr->lpData, old_size); /* * Keep track of how many sysex bytes are in message so far. * Then find out which buffer this was, and replace it. */ big->dwBytesRecorded = m->hdr->dwBytesRecorded; big->dwBufferLength = new_size; for (i = 0; i < NUM_SYSEX_BUFFERS; ++i) { if (m->sysex_buffers[i] == m->hdr) { m->sysex_buffers[i] = big; m->sysex_buffer_size[i] = new_size; pm_free(m->hdr); m->hdr = big; break; } } return pmNoError; } #endif /* * Begin MIDI input implementation */ static PmError allocate_input_buffer (HMIDIIN h, long buffer_len) /* size_t !!! */ { LPMIDIHDR hdr = allocate_buffer(buffer_len); if (is_nullptr(hdr)) return pmInsufficientMemory; Pm_set_hosterror(midiInPrepareHeader(h, hdr, sizeof(MIDIHDR))); if (Pm_hosterror()) { pm_free(hdr); return Pm_hosterror(); } Pm_set_hosterror(midiInAddBuffer(h, hdr, sizeof(MIDIHDR))); return Pm_hosterror(); } /** * Should use UINT: DWORD dwDevice = (DWORD) descriptors[i].descriptor; * * \note * If we return an error code, the device will be closed and memory will be * freed. It's up to the caller to free the parameter "midi". */ static PmError winmm_in_open (PmInternal * midi, void * driverinfo) { int i; midiwinmm_type m; int max_sysex_len = midi->buffer_len * 4; int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN; int devid = midi->device_id; int client = pm_descriptors[devid].pub.client; int port = pm_descriptors[devid].pub.port; #if defined SEQ66_PLATFORM_64_BIT UINT_PTR dev = (UINT_PTR) pm_descriptors[devid].descriptor; UINT dwDevice = (UINT) (dev & 0xFFFFFFFF); #else /* warnings with 64 bit builds: */ UINT dwDevice = (UINT) pm_descriptors[devid].descriptor; #endif if (dwDevice == UINT_MIDIMAPPER) { pm_log_buffer_append("Opening MIDI Mapper for input\n"); } else if (dwDevice < 32) /* just a mere sanity check */ { char temp[64]; snprintf ( temp, sizeof temp, "Opening MIDI input device %d (%d:%d)\n", (int) dwDevice, client, port ); pm_log_buffer_append(temp); } else { pm_log_buffer_append("MIDI input device ID out-of-range\n"); } if (not_nullptr(driverinfo)) { // Anything worth doing here? } /* * Create system dependent device data. */ m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ midi->descriptor = m; if (is_nullptr(m)) goto no_memory; m->handle.in = nullptr; m->buffers = nullptr; /* not used for input */ m->num_buffers = 0; /* not used for input */ m->max_buffers = FALSE; /* not used for input */ m->buffers_expanded = 0; /* not used for input */ m->next_buffer = 0; /* not used for input */ m->buffer_signal = 0; /* not used for input */ #if defined SEQ66_PORTMIDI_SYSEX_PROCESSING for (i = 0; i < NUM_SYSEX_BUFFERS; ++i) m->sysex_buffers[i] = nullptr; /* not used for input */ m->next_sysex_buffer = 0; /* not used for input */ #endif m->last_time = 0; m->first_message = TRUE; /* not used for input */ m->sysex_mode = FALSE; m->sysex_word = 0; m->sysex_byte_count = 0; m->hdr = nullptr; /* not used for input */ m->sync_time = 0; m->delta = 0; m->error = MMSYSERR_NOERROR; /* * 4000 is based on Windows documentation -- that's the value used in the * memory manager. It's small enough that it should not hurt performance * even if it's not optimal. */ InitializeCriticalSectionAndSpinCount(&m->lock, 4000); Pm_set_hosterror ( midiInOpen ( &(m->handle.in), /* input device handle */ dwDevice, /* device ID */ (DWORD_PTR) winmm_in_callback, /* callback address */ (DWORD_PTR) midi, /* callback instance data */ CALLBACK_FUNCTION /* callback is a procedure */ ) ); if (Pm_hosterror()) goto free_in_descriptor; if (num_input_buffers < MIN_INPUT_BUFFERS) num_input_buffers = MIN_INPUT_BUFFERS; for (i = 0; i < num_input_buffers; ++i) { if (allocate_input_buffer(m->handle.in, INPUT_SYSEX_LEN)) { /* * Either Pm_hosterror() was set, or the proper return code is * pmInsufficientMemory. */ goto close_device; } } Pm_set_hosterror(midiInStart(m->handle.in)); /* start device */ if (Pm_hosterror()) goto reset_device; return pmNoError; /* * Undo steps leading up to the detected error. At the same time, we * ignore return codes because we already have an error to report. */ reset_device: (void) midiInReset(m->handle.in); close_device: (void) midiInClose(m->handle.in); free_in_descriptor: pm_free(midi->descriptor); /* was assigned to m above */ midi->descriptor = nullptr; no_memory: if (Pm_hosterror()) { int hosterror = Pm_hosterror(); /* MMRESULT err = */ midiInGetErrorText ( (MMRESULT) hosterror, Pm_hosterror_text_mutable(), PM_HOST_ERROR_MSG_LEN ); if ((MMRESULT) hosterror != MMSYSERR_NOERROR) { /* * Anything worth doing here to handle the error? Let's do this, * though it might not occur. Compare to the output port handling * below. */ if ((MMRESULT) hosterror == MMSYSERR_ALLOCATED) return pmDeviceLocked; } return pmHostError; } /* * if ! Pm_hosterror(), then the error must be pmInsufficientMemory. * Note: if we return an error code, the device will be closed and memory * will be freed. It's up to the caller to free the parameter midi. */ return pmInsufficientMemory; } static PmError winmm_in_poll (PmInternal * midi) { midiwinmm_type m = (midiwinmm_type) midi->descriptor; return m->error; } /** * Closes an open MIDI input device. It assumes that the MIDI parameter is not * null (checked by the caller). */ static PmError winmm_in_close (PmInternal * midi) { midiwinmm_type m = (midiwinmm_type) midi->descriptor; if (is_nullptr(m)) return pmBadPtr; int err = midiInStop(m->handle.in); /* device to be closed */ if (err != pmNoError) { midiInReset(m->handle.in); /* try to reset and close port */ midiInClose(m->handle.in); Pm_set_hosterror(err); } else if ((err = midiInReset(m->handle.in)) != pmNoError) { midiInClose(m->handle.in); /* best effort: close midi port */ Pm_set_hosterror(err); } else Pm_set_hosterror(midiInClose(m->handle.in)); DeleteCriticalSection(&m->lock); pm_free(m); midi->descriptor = nullptr; /* was identical to m */ if (Pm_hosterror() != pmNoError) { int texterr = midiInGetErrorText ( Pm_hosterror(), Pm_hosterror_text_mutable(), PM_HOST_ERROR_MSG_LEN ); if (texterr != MMSYSERR_NOERROR) { // handle the error } return pmHostError; } return pmNoError; } /** * Callback function executed via midiInput SW interrupt [via midiInOpen()]. * NOTE: we do not just EnterCriticalSection() here because an MIM_CLOSE * message arrives when the port is closed, but then the m->lock has been * destroyed. * * If this callback is reentered with data, we're in trouble. It's hard to * imagine that Microsoft would allow callbacks to be reentrant -- isn't the * model that this is like a hardware interrupt? -- but I've seen reentrant * behavior using a debugger, so it happens. * * dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of message * LOB; dwParam2 is time message received by input device driver, specified * in [ms] from when midiInStart called. each message is expanded to include * the status byte. * * If dwParam1 is not a status byte; ignore it. This happened running the * sysex.c test under Win2K with MidiMan USB 1x1 interface, but I can't * reproduce it. -RBD */ static void CALLBACK winmm_in_callback ( HMIDIIN hMidiIn, /* midiInput device Handle */ WORD wMsg, /* MIDI msg */ DWORD_PTR dwInstance, /* application data */ DWORD_PTR dwParam1, /* MIDI data */ DWORD_PTR dwParam2 /* device timestamp (re recent midiInStart) */ ) { PmInternal * midi = (PmInternal *) dwInstance; midiwinmm_type m = (midiwinmm_type) midi->descriptor; EnterCriticalSection(&m->lock); switch (wMsg) { case MIM_DATA: /* see the notes above about re-entry etc. */ { if ((dwParam1 & 0x80) == 0) { /* * See RBD's note above. */ } else /* we have data to process */ { PmEvent event; if (midi->time_proc) dwParam2 = (*midi->time_proc)(midi->time_info); event.timestamp = (PmTimestamp) dwParam2; event.message = (PmMessage) dwParam1; pm_read_short(midi, &event); } break; } case MIM_LONGDATA: { MIDIHDR * lpMidiHdr = (MIDIHDR *) dwParam1; midibyte_t * data = (midibyte_t *) lpMidiHdr->lpData; unsigned processed = 0; int remaining = lpMidiHdr->dwBytesRecorded; if (midi->time_proc) dwParam2 = (*midi->time_proc)(midi->time_info); /* * Can there be more than 1 message in a buffer? Assume yes and * iterate through them. */ while (remaining > 0) { unsigned amt = pm_read_bytes ( midi, data + processed, remaining, dwParam2 ); remaining -= amt; processed += amt; if (amt == 0) break; } /* * When a device is closed, the pending MIM_LONGDATA buffers are * returned to this callback with dwBytesRecorded == 0. In this * case, we do not want to send them back to the interface (if * we do, the interface will not close, and Windows OS may (!!!) hang). * Note: no error checking. I don't think this can fail except * possibly for MMSYSERR_NOMEM, but the pain of reporting this * unlikely but probably catastrophic error does not seem worth it. */ if (lpMidiHdr->dwBytesRecorded > 0) { MMRESULT rslt; lpMidiHdr->dwBytesRecorded = 0; lpMidiHdr->dwFlags = 0; rslt = midiInPrepareHeader(hMidiIn, lpMidiHdr, sizeof(MIDIHDR)); if (rslt == MMSYSERR_NOERROR) rslt = midiInAddBuffer(hMidiIn, lpMidiHdr, sizeof(MIDIHDR)); if (rslt != MMSYSERR_NOERROR) { // handle the error } } else { midiInUnprepareHeader(hMidiIn, lpMidiHdr, sizeof(MIDIHDR)); pm_free(lpMidiHdr); } break; } case MIM_OPEN: break; case MIM_CLOSE: break; case MIM_ERROR: { errprint("MIM_ERROR\n"); } break; case MIM_LONGERROR: { errprint("MIM_LONGERROR\n"); } break; default: break; } LeaveCriticalSection(&m->lock); } /* * MIDI output implementation. * * This section begins the helper routines used by midiOutStream interface. */ /** * Adds timestamped short msg to buffer, and returns fullp. * * Huh? * If the addition of three more words (a message) would extend beyond * the buffer length, then return TRUE (full). */ static int add_to_buffer ( /* midiwinmm_type m, */ LPMIDIHDR hdr, unsigned long delta, unsigned long msg ) { unsigned long * ptr = (unsigned long *) (hdr->lpData + hdr->dwBytesRecorded); *ptr++ = delta; /* dwDeltaTime */ *ptr++ = 0; /* dwStream */ *ptr++ = msg; /* dwEvent */ hdr->dwBytesRecorded += 3 * sizeof(long); return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength; } static PmTimestamp pm_time_get (midiwinmm_type m) { MMTIME mmtime; MMRESULT winerrcode; mmtime.wType = TIME_TICKS; mmtime.u.ticks = 0; winerrcode = midiStreamPosition(m->handle.stream, &mmtime, sizeof(mmtime)); return winerrcode == MMSYSERR_NOERROR ? mmtime.u.ticks : 0 ; } /* * End helper routines used by midiOutStream interface */ static PmError winmm_out_open (PmInternal * midi, void * UNUSED(driverinfo)) { midiwinmm_type m; MIDIPROPTEMPO propdata; MIDIPROPTIMEDIV divdata; int output_buffer_len; int num_buffers; int max_sysex_len = midi->buffer_len * 4; int devid = midi->device_id; int client = pm_descriptors[devid].pub.client; int port = pm_descriptors[devid].pub.port; cbool_t opening_midimapper = false; #if defined SEQ66_PLATFORM_64_BIT UINT_PTR dev = (UINT_PTR) pm_descriptors[devid].descriptor; UINT dwDevice = (UINT) (dev & 0xFFFFFFFF); #else /* warnings with 64 bit builds: */ UINT dwDevice = (UINT) pm_descriptors[devid].descriptor; #endif if (dwDevice == UINT_MIDIMAPPER) /* UINT_PTR 4294967295 */ { opening_midimapper = true; pm_log_buffer_append("Opening MIDI Mapper for output\n"); } else if (dwDevice < 32) /* sanity check */ { char temp[64]; snprintf ( temp, sizeof temp, "Opening MIDI output device #%d (%d:%d)\n", (int) dwDevice, client, port ); pm_log_buffer_append(temp); } else { pm_log_buffer_append("MIDI output device ID out-of-range\n"); } /* * Create system dependent device data. */ m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ midi->descriptor = m; if (is_nullptr(m)) goto no_memory; m->handle.out = nullptr; m->buffers = nullptr; m->num_buffers = m->max_buffers = m->next_buffer = 0; m->buffers_expanded = FALSE; #if defined SEQ66_PORTMIDI_SYSEX_PROCESSING m->sysex_buffers[0] = nullptr; m->sysex_buffers[1] = nullptr; m->next_sysex_buffer = 0; #endif m->last_time = 0; m->first_message = TRUE; /* we treat first message as special case */ m->sysex_mode = FALSE; m->sysex_word = m->sysex_byte_count = 0; m->hdr = nullptr; m->sync_time = m->delta = 0; m->error = MMSYSERR_NOERROR; /* * Create a signal; should only fail with very serious problems. */ m->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL); if (is_nullptr(m->buffer_signal)) { errprint("Failed to create signal for output"); // What to do??? } if (midi->latency == 0) /* open the device */ { /* * Use simple MIDI out calls. Note: the same callback function as * for StreamOpen(). Please note that the error codes returned by * Microsoft functions don't line up completely with the values in the * PmError enumeration. */ Pm_set_hosterror ( midiOutOpen ( (LPHMIDIOUT) & m->handle.out, /* device Handle */ dwDevice, /* device ID */ (DWORD_PTR) winmm_streamout_callback, /* callback fn */ (DWORD_PTR) midi, /* cb instance data */ CALLBACK_FUNCTION /* callback type */ ) ); } else { /* * Use stream-based MIDI output (schedulable in future). */ Pm_set_hosterror ( midiStreamOpen ( &m->handle.stream, /* device Handle */ (LPUINT) &dwDevice, /* device ID ptr */ 1, /* must = 1 */ (DWORD_PTR) winmm_streamout_callback, (DWORD_PTR) midi, /* callback data */ CALLBACK_FUNCTION ) ); } if (Pm_hosterror() != pmNoError) /* MMSYSERR_NOERROR */ goto free_out_descriptor; if (midi->latency == 0) { num_buffers = NUM_SIMPLE_SYSEX_BUFFERS; output_buffer_len = max_sysex_len / num_buffers; if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN) output_buffer_len = MIN_SIMPLE_SYSEX_LEN; } else { num_buffers = max(midi->buffer_len, midi->latency / 2); if (num_buffers < MIN_STREAM_BUFFERS) num_buffers = MIN_STREAM_BUFFERS; output_buffer_len = STREAM_BUFFER_LEN; propdata.cbStruct = sizeof(MIDIPROPTEMPO); propdata.dwTempo = Pt_Get_Tempo_Microseconds(); /* us per quarter */ infoprintf("tempo in us = %ld\n", (long) propdata.dwTempo); Pm_set_hosterror ( midiStreamProperty ( m->handle.stream, (LPBYTE) & propdata, MIDIPROP_SET | MIDIPROP_TEMPO ) ); if (Pm_hosterror() != pmNoError) goto close_device; divdata.cbStruct = sizeof(MIDIPROPTEMPO); divdata.dwTimeDiv = Pt_Get_Ppqn(); /* divs per 1/4 */ infoprintf("time div = %ld\n", (long) divdata.dwTimeDiv); Pm_set_hosterror ( midiStreamProperty ( m->handle.stream, (LPBYTE) & divdata, MIDIPROP_SET | MIDIPROP_TIMEDIV ) ); if (Pm_hosterror() != pmNoError) goto close_device; } if (allocate_buffers(m, output_buffer_len, num_buffers)) goto free_buffers; if (midi->latency != 0) /* start the device */ { Pm_set_hosterror(midiStreamRestart(m->handle.stream)); if (Pm_hosterror() != pmNoError) /* MMSYSERR_NOERROR */ goto free_buffers; } if (opening_midimapper) s_midi_mapper_active = true; return pmNoError; free_buffers: close_device: midiOutClose(m->handle.out); free_out_descriptor: midi->descriptor = nullptr; winmm_out_delete(midi); /* free buffers etc */ no_memory: if (Pm_hosterror() != pmNoError) { int hosterror = Pm_hosterror(); /* MMRESULT err = */ midiOutGetErrorText ( (MMRESULT) hosterror, Pm_hosterror_text_mutable(), PM_HOST_ERROR_MSG_LEN ); if ((MMRESULT) hosterror != MMSYSERR_NOERROR) /* "nullify" error? */ { /* * This error means the specified resource is already allocated. * In this case, likely locked by the MIDI Mapper. */ if ( s_midi_mapper_active && ((MMRESULT) hosterror == MMSYSERR_ALLOCATED) ) { return pmDeviceLocked; } } return pmHostError; } return pmInsufficientMemory; } /** * Carefully frees the data associated with MIDI. It deletes * system-dependent device data. We don't report errors here, better not * to stop cleanup. */ static void winmm_out_delete (PmInternal * midi) { midiwinmm_type m = (midiwinmm_type) midi->descriptor; if (not_nullptr(m)) { int i; if (m->buffer_signal) CloseHandle(m->buffer_signal); for (i = 0; i < m->num_buffers; ++i) { if (m->buffers[i]) /* free buffer for stream output */ { pm_free(m->buffers[i]); m->buffers[i] = nullptr; } } pm_free(m->buffers); m->buffers = nullptr; m->num_buffers = m->max_buffers = 0; #if defined SEQ66_PORTMIDI_SYSEX_PROCESSING for (i = 0; i < NUM_SYSEX_BUFFERS; ++i) /* free SysEx buffers */ if (m->sysex_buffers[i]) pm_free(m->sysex_buffers[i]); #endif } pm_free(m); /* delete descriptor */ midi->descriptor = nullptr; /* same as m at top */ } /* * See the comments for winmm_in_close(). */ static PmError winmm_out_close (PmInternal * midi) { midiwinmm_type m = (midiwinmm_type) midi->descriptor; if (m->handle.out) { if (midi->latency == 0) Pm_set_hosterror(midiOutClose(m->handle.out)); else Pm_set_hosterror(midiStreamClose(m->handle.stream)); winmm_out_delete(midi); /* regardless of outcome, free memory */ } if (Pm_hosterror()) { int err = midiOutGetErrorText ( Pm_hosterror(), (char *) Pm_hosterror_text_mutable(), PM_HOST_ERROR_MSG_LEN ); if (err != MMSYSERR_NOERROR) { // handle the error } return pmHostError; } return pmNoError; } /** * This function aborts, and only stops output streams. */ static PmError winmm_out_abort (PmInternal * midi) { midiwinmm_type m = (midiwinmm_type) midi->descriptor; m->error = MMSYSERR_NOERROR; if (midi->latency > 0) m->error = midiStreamStop(m->handle.stream); return m->error ? pmHostError : pmNoError; } /** * Bytes recorded should be 0. As pointed out by Nigel Brown, 20Sep06, * dwBytesRecorded should be zero. This is set in get_free_sysex_buffer(). The * msg length goes in dwBufferLength in spite of what Microsoft documentation * says (or doesn't say). */ static PmError winmm_write_flush ( PmInternal * midi, PmTimestamp timestamp // unused ) { midiwinmm_type m = (midiwinmm_type) midi->descriptor; if (timestamp != 0) { // Just avoids a warning. } if (not_nullptr(m) && not_nullptr(m->hdr)) { m->error = midiOutPrepareHeader(m->handle.out, m->hdr, sizeof(MIDIHDR)); if (m->error) { /* do not send message */ } else if (midi->latency == 0) { /* * Bytes recorded should be 0. */ m->hdr->dwBufferLength = m->hdr->dwBytesRecorded; m->hdr->dwBytesRecorded = 0; m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); } else { m->error = midiStreamOut(m->handle.stream, m->hdr, sizeof(MIDIHDR)); } midi->fill_base = nullptr; m->hdr = nullptr; if (m->error) { m->hdr->dwFlags = 0; /* release the buffer */ return pmHostError; } } return pmNoError; } #if defined GARBAGE static PmError winmm_write_sysex_byte (PmInternal * midi, midibyte_t byte) { midiwinmm_type m = (midiwinmm_type) midi->descriptor; midibyte_t * msg_buffer; /* * At the beginning of SysEx, m->hdr is null. Allocate a buffer if none * allocated yet. */ if (is_nullptr(m->hdr)) { m->hdr = get_free_output_buffer(midi); if (is_nullptr(m->hdr)) return pmInsufficientMemory; m->sysex_byte_count = 0; } msg_buffer = (midibyte_t *) (m->hdr->lpData); /* where to write */ if (m->hdr->lpData != (char *) (m->hdr + 1)) { // What to do with this error? } if (m->sysex_byte_count >= m->hdr->dwBufferLength) /* check overflow */ { /* allocate a bigger message -- double it every time */ LPMIDIHDR big = allocate_buffer(m->sysex_byte_count * 2); if (is_nullptr(big)) return pmInsufficientMemory; m->error = midiOutPrepareHeader(m->handle.out, big, sizeof(MIDIHDR)); if (m->error) { m->hdr = nullptr; return pmHostError; } memcpy(big->lpData, msg_buffer, m->sysex_byte_count); msg_buffer = (midibyte_t *)(big->lpData); if (m->buffers[0] == m->hdr) { m->buffers[0] = big; pm_free(m->hdr); m->hdr = nullptr; } else if (m->buffers[1] == m->hdr) { m->buffers[1] = big; pm_free(m->hdr); m->hdr = nullptr; } m->hdr = big; } msg_buffer[m->sysex_byte_count++] = byte; /* append byte to message */ if (byte == MIDI_EOX) /* have a complete message? */ { m->hdr->dwBytesRecorded = m->sysex_byte_count; m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); m->hdr = nullptr; /* stop using message buffer */ if (m->error) return pmHostError; } return pmNoError; } #endif // GARBAGE static PmError winmm_write_short (PmInternal * midi, PmEvent * event) { midiwinmm_type m = (midiwinmm_type) midi->descriptor; PmError rslt = pmNoError; if (is_nullptr(m)) return pmHostError; if (midi->latency == 0) /* use midiOut interface, ignore timestamps */ { m->error = midiOutShortMsg(m->handle.out, event->message); if (m->error) rslt = pmHostError; } else /* use midiStream interface -- pass data through buffers */ { unsigned long when = event->timestamp; unsigned long delta; int full; if (when == 0) when = midi->now; /* * 'when' is in real_time; translate it to the intended stream time. * Make sure we don't go backward in time */ when += m->delta + midi->latency; if (when < m->last_time) when = m->last_time; delta = when - m->last_time; m->last_time = when; /* * Before we insert any data, we must have a buffer. * Stream interface buffers are allocated when stream is opened. */ if (is_nullptr(m->hdr)) m->hdr = get_free_output_buffer(midi); full = add_to_buffer(/*m,*/ m->hdr, delta, event->message); if (full) rslt = winmm_write_flush(midi, when); } return rslt; } #define winmm_begin_sysex winmm_write_flush #ifndef winmm_begin_sysex static PmError winmm_begin_sysex (PmInternal * midi, PmTimestamp timestamp) { midiwinmm_type m = (midiwinmm_type) midi->descriptor; PmError rslt = pmNoError; if (midi->latency == 0) { /* do nothing -- it's handled in winmm_write_byte */ } else { /* * SysEx expects an empty SysEx buffer, so send whatever is here. */ rslt = winmm_write_flush(midi); } return rslt; } #endif static PmError winmm_end_sysex (PmInternal * midi, PmTimestamp timestamp) { /* * Could check for callback_error here, but I haven't checked what happens * if we exit early and don't finish the sysex msg and clean up. */ midiwinmm_type m = (midiwinmm_type) midi->descriptor; PmError rslt = pmNoError; LPMIDIHDR hdr = m->hdr; if (! hdr) return rslt; /* * Something bad happened earlier, do not report an error because it would * have been reported (at least) once already. A(n old) version of MIDI * YOKE requires a zero byte after the sysex message, but do not increment * dwBytesRecorded. */ hdr->lpData[hdr->dwBytesRecorded] = 0; if (midi->latency == 0) { #if defined DEBUG_PRINT_BEFORE_SENDING_SYSEX /* DEBUG CODE: */ { int i; int len = m->hdr->dwBufferLength; printf("OutLongMsg %d ", len); for (i = 0; i < len; ++i) printf("%2x ", (midibyte_t)(m->hdr->lpData[i])); printf("\n"); } #endif } else { /* * Using stream interface. There are accumulated bytes in m->hdr to * send using midiStreamOut. add bytes recorded to MIDIEVENT length, * but don't count the MIDIEVENT data (3 longs) */ MIDIEVENT * evt = (MIDIEVENT *) (hdr->lpData); evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long); /* round up BytesRecorded to multiple of 4 */ hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3; } rslt = winmm_write_flush(midi, timestamp); return rslt; } /** * Write a sysex byte. What a long function! */ static PmError winmm_write_byte ( PmInternal * midi, midibyte_t byte, PmTimestamp timestamp ) { PmError rslt = pmNoError; midiwinmm_type m = (midiwinmm_type) midi->descriptor; // assert(m); LPMIDIHDR hdr = m->hdr; midibyte_t * msg_buffer; if (is_nullptr(hdr)) { m->hdr = hdr = get_free_output_buffer(midi); if (is_nullptr(hdr)) { // handle the error } midi->fill_base = (midibyte_t *) m->hdr->lpData; midi->fill_offset_ptr = (uint32_t *)(&(hdr->dwBytesRecorded)); /* * When buffer fills, Pm_WriteSysEx() will revert to calling * pmwin_write_byte(), which expects to have space, so leave one byte * free for pmwin_write_byte(). Leave another byte of space for zero * after message to make early version of MIDI YOKE driver happy -- * therefore dwBufferLength - 2 */ midi->fill_length = hdr->dwBufferLength - 2; if (midi->latency != 0) { unsigned long when = (unsigned long) timestamp; unsigned long delta; unsigned long * ptr; if (when == 0) when = midi->now; /* * 'when' is in real_time; translate it to the intended stream * time Then make sure we don't go backward in time. Data will * be added at an offset of dwBytesRecorded. */ when += m->delta + midi->latency; if (when < m->last_time) when = m->last_time; delta = when - m->last_time; m->last_time = when; ptr = (unsigned long *) hdr->lpData; *ptr++ = delta; *ptr++ = 0; *ptr = MEVT_F_LONG; hdr->dwBytesRecorded = 3 * sizeof(long); /* see note above */ } } msg_buffer = (midibyte_t *)(hdr->lpData); msg_buffer[hdr->dwBytesRecorded++] = byte; /* add data byte */ /* * See if buffer is full; leave one byte extra for pad * Then write what we've got and continue. */ #if defined SEQ66_USE_EXPANDING_SYSEX_BUFFERS if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) /* rslt = */ winmm_end_sysex(midi, timestamp); /* * This code is here as an aid in case you want sysex buffers to expand * to hold large messages completely. If so, you will want to change * SYSEX_BYTES_PER_BUFFER above to some variable that remembers the * buffer size. A good place to put this value would be in the * hdr->dwUser field. * * rslt = resize_sysex_buffer(midi, m->sysex_byte_count, * m->sysex_byte_count * 2); * * * if (rslt == pmBufferMaxSize) // if the buffer can't be resized * * The dwBytesRecorded field gets wiped out, so we'll save it. */ int bytesRecorded = hdr->dwBytesRecorded; rslt = resize_sysex_buffer(midi, bytesRecorded, 2 * bytesRecorded); hdr->dwBytesRecorded = bytesRecorded; if (rslt == pmBufferMaxSize) /* if buffer can't be resized */ { // no code } #else if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) rslt = winmm_end_sysex(midi, timestamp); #endif return rslt; } static PmTimestamp winmm_synchronize (PmInternal * midi) { midiwinmm_type m; unsigned long pm_stream_time_2; unsigned long real_time; unsigned long pm_stream_time; /* * Only synchronize if we are using the stream interface. Then * figure out the time. The do-loop reads real_time between two * reads of stream time, and repeats if more than 1 ms elapsed. */ if (midi->latency == 0) return 0; m = (midiwinmm_type) midi->descriptor; pm_stream_time_2 = pm_time_get(m); do { pm_stream_time = pm_stream_time_2; real_time = (*midi->time_proc)(midi->time_info); pm_stream_time_2 = pm_time_get(m); } while (pm_stream_time_2 > pm_stream_time + 1); m->delta = pm_stream_time - real_time; m->sync_time = real_time; return real_time; } #if defined SEQ66_PORTMIDI_SYSEX_PROCESSING /** * The winmm_out_callback() recycles SysEx buffers. A future optimization: * eliminate UnprepareHeader calls -- they aren't necessary; however, this code * uses the prepared-flag to indicate which buffers are free, so we need to do * something to flag empty buffers if we leave them prepared. */ static void CALLBACK winmm_out_callback ( HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 // unused ) { PmInternal * midi = (PmInternal *) dwInstance; midiwinmm_type m = (midiwinmm_type) midi->descriptor; LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; int err = 0; /* set to 0 so that no buffer match will also be an error */ if (hmo != 0 && dwParam2 != 0) { // Eliminate warnings: pretend these parameters are used. } if (wMsg == MOM_DONE) /* see note above */ { MMRESULT ret = midiOutUnprepareHeader ( m->handle.out, hdr, sizeof(MIDIHDR) ); if (ret != MMSYSERR_NOERROR) { // handle the error } } err = SetEvent(m->buffer_signal); /* notify sender, buffer available */ if (! err) /* false -> error */ { // handle the error } } #endif /** * The winmm_streamout_callback() unprepares (frees) the buffer header. Even * if an error is pending, I think we should unprepare messages and signal * their arrival in case the client is blocked waiting for the buffer. */ static void CALLBACK winmm_streamout_callback ( HMIDIOUT hmo, // unused UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 // unused ) { PmInternal * midi = (PmInternal *) dwInstance; midiwinmm_type m = (midiwinmm_type) midi->descriptor; LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; int err; if (hmo != 0 && dwParam2 != 0) { // Eliminate warnings: pretend these parameters are used. } if (wMsg == MOM_DONE) /* see note above */ { MMRESULT r = midiOutUnprepareHeader(m->handle.out, hdr, sizeof(MIDIHDR)); if (r != MMSYSERR_NOERROR) { // handle the error } } err = SetEvent(m->buffer_signal); if (! err) /* false -> error */ { // handle the error } } /* * Begin exported functions */ #define winmm_in_abort pm_fail_fn pm_fns_node pm_winmm_in_dictionary = { none_write_short, none_sysex, none_sysex, none_write_byte, none_write_short, none_write_flush, winmm_synchronize, winmm_in_open, winmm_in_abort, winmm_in_close, winmm_in_poll, winmm_has_host_error, winmm_get_host_error }; pm_fns_node pm_winmm_out_dictionary = { winmm_write_short, winmm_begin_sysex, winmm_end_sysex, winmm_write_byte, winmm_write_short, /* short realtime message */ winmm_write_flush, winmm_synchronize, winmm_out_open, winmm_out_abort, winmm_out_close, none_poll, winmm_has_host_error, winmm_get_host_error }; /** * Initialize WinMM interface. Note that if there is something wrong with * winmm (e.g. it is not supported or installed), it is not an error. We * should simply return without having added any devices to the table. * Hence, no error code is returned. Furthermore, this init code is called * along with every other supported interface, so the user would have a * very hard time figuring out what hardware and API generated the error. * Finally, it would add complexity to pmwin.c to remember where the error * code came from in order to convert to text. */ void pm_winmm_init (void) { pm_log_buffer_alloc(); /* see portmidi.c & .h */ #if defined SEQ66_PORTMIDI_SYSEX_PROCESSING TO DO #endif pm_winmm_mapper_input(); pm_winmm_mapper_output(); pm_winmm_general_inputs(); pm_winmm_general_outputs(); } /** * No error codes are returned, even if errors are encountered, because * there is probably nothing the user could do (e.g. it would be an error * to retry. */ void pm_winmm_term (void) { int doneany = 0; int i; for (i = 0; i < pm_descriptor_index; ++i) { PmInternal * midi = pm_descriptors[i].internalDescriptor; if (not_nullptr(midi)) { midiwinmm_type m = (midiwinmm_type) midi->descriptor; if (m->handle.out) { if (doneany == 0) /* close next open device */ { infoprint("Begin closing open devices...\n"); doneany = 1; } /* * Report any host errors; this EXTREMELY useful when * trying to debug client app. */ if (winmm_has_host_error(midi)) { char temp[PM_STRING_MAX]; char msg[PM_HOST_ERROR_MSG_LEN]; winmm_get_host_error(midi, msg, PM_HOST_ERROR_MSG_LEN); snprintf(temp, sizeof temp, "[%d] '%s'\n", i, msg); errprint(temp); /* log to console */ pm_log_buffer_append(temp); /* log to buffer */ } (*midi->dictionary->close)(midi); /* close all ports */ } } } pm_free(midi_in_caps); midi_in_caps = nullptr; pm_free(midi_out_caps); midi_out_caps = nullptr; if (doneany) { const char * msg = "devices left open have been closed.\n"; warnprint(msg); /* log to console */ pm_log_buffer_append(msg); /* log to buffer */ } pm_descriptor_index = 0; pm_log_buffer_free(); } /* * pmwinmm.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/src/portmidi.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file portmidi.c * * Provides the basic PortMIDI API. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2023-12-24 * \license GNU GPLv2 or above * * Notes on host error reporting: * * PortMidi errors (of type PmError) are generic, system-independent * errors. When an error does not map to one of the more specific * PmErrors, the catch-all code pmHostError is returned. This means that * PortMidi has retained a more specific system-dependent error code. The * caller can get more information by calling Pm_HasHostError() to test * if there is a pending host error, and Pm_GetHostErrorText() to get a * text string describing the error. Host errors are reported on a * per-device basis because only after you open a device does PortMidi * have a place to record the host error code. I.e. only those routines * that receive a (PortMidiStream *) argument check and report errors. * One exception to this is that Pm_OpenInput() and Pm_OpenOutput() can * report errors even though when an error occurs, there is no * PortMidiStream* to hold the error. Fortunately, both of these * functions return any error immediately, so we do not really need * per-device error memory. Instead, any host error code is stored in a * global, pmHostError is returned, and the user can call * Pm_GetHostErrorText() to get the error message (and the invalid stream * parameter will be ignored.) The functions pm_init and pm_term do not * fail or raise errors. The job of pm_init is to locate all available * devices so that the caller can get information via PmDeviceInfo(). If * an error occurs, the device is simply not listed as available. * * Host errors come in two flavors: * * - host error * - host error during callback * * These can occur w/midi input or output devices. (b) can only happen * asynchronously (during callback routines), whereas (a) only occurs while * synchronously running PortMidi and any resulting system dependent calls. * Both (a) and (b) are reported by the next read or write call. You can * also query for asynchronous errors (b) at any time by calling * Pm_HasHostError(). * * Notes on compile-time switches: * * DEBUG assumes stdio and a console. Use this if you want automatic, * simple error reporting, e.g. for prototyping. If you are using MFC or * some other graphical interface with no console, DEBUG probably should be * undefined. Actually, for Seq66, the output can be re-routed to a * log-file for trouble-shooting. * * For Seq66, we want to see these errors, all the time, and they can be * redirected to a log file via the "-o log=filename.log" or "--option * log=filename.log" command-line options. */ #include /* C::strcasestr() GNU function. */ #if defined _MSC_VER #pragma warning(disable: 4244) // warnings about downsize typecasts #pragma warning(disable: 4018) // warnings about signed/unsigned /* * We will need an implementation of strcasestr() in terms of a Microsoft * non-case-sensitive string comparison, for the freakin' Microsoft compiler. */ #endif #include #include #include "util/basic_macros.h" /* not_nullptr() macro, etc. */ #include "pmutil.h" #include "portmidi.h" #include "porttime.h" #if defined SEQ66_PLATFORM_LINUX #include /* Linux C::usleep() function */ #elif defined SEQ66_PLATFORM_UNIX #include /* POSIX C::select() function */ #endif #if defined SEQ66_PLATFORM_WINDOWS #include /* Windows C::Sleep() function */ #endif #if defined SEQ66_PLATFORM_DEBUG #undef DEBUG_ALLOC_TRACKING /* define only when debugging */ #if defined DEBUG_ALLOC_TRACKING /* define only when debugging */ #include /* uintptr_t */ static int bad_pointer (void * ptr) { uintptr_t vp = (uintptr_t) ptr; #if defined SEQ66_PLATFORM_64_BIT int result = vp == 0xbaadf00dbaadf00d || vp == 0xdeadbeefdeadbeef || vp == 0xcdcdcdcdcdcdcdcd || vp == 0xcccccccccccccccc ; #else int result = vp == 0xbaadf00d || vp == 0xdeadbeef || vp == 0xcdcdcdcd || vp == 0xcccccccc ; #endif if (result) printf("Bad pointer!\n"); return result; } #endif #endif /** * MIDI status (event) values. */ #define MIDI_STATUS_MASK 0x80 #define MIDI_NOTE_ON 0x90 #define MIDI_NOTE_OFF 0x80 #define MIDI_CHANNEL_AT 0xD0 #define MIDI_POLY_AT 0xA0 #define MIDI_PROGRAM 0xC0 #define MIDI_CONTROL 0xB0 #define MIDI_PITCHBEND 0xE0 #define MIDI_MTC 0xF1 #define MIDI_SONGPOS 0xF2 #define MIDI_SONGSEL 0xF3 #define MIDI_TUNE 0xF6 #define MIDI_CLOCK 0xF8 #define MIDI_ACTIVE 0xFE #define MIDI_SYSEX 0xF0 #define MIDI_EOX 0xF7 #define MIDI_START 0xFA #define MIDI_STOP 0xFC #define MIDI_CONTINUE 0xFB #define MIDI_F9 0xF9 #define MIDI_FD 0xFD #define MIDI_RESET 0xFF /** * Tests for an empty queue. */ #define is_empty(midi) ((midi)->tail == (midi)->head) /** * Indicates an errant or unassigned device ID. */ #define PORTMIDI_BAD_DEVICE_ID (-1) /* * This value is not static so that pm_init can set it directly if needed. * (see pmmac.c:pm_init()) */ static int pm_initialized = FALSE; /** * Internal error code, not exposed to the application. */ static int pm_hosterror = FALSE; /** * Internal error message, not exposed to the application. */ static char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN]; /** * System implementation of portmidi interface. We should wrap this in a * function as well. */ int pm_descriptor_max = 0; /* declared in pminternal.h */ int pm_descriptor_index = 0; /* declared in pminternal.h */ descriptor_type pm_descriptors = nullptr; /* declared in pminternal.h */ /* * Improved access to error messages and debugging features. These will be * accessible only via external functions defined after this section of static * declarations. */ /** * A boolean to indicate to show debug messages. Will start at false by * default for now. */ static int pm_show_debug = 1; /** * A boolean to indicate to exit upon error. Will start at true by default * for now. */ static int pm_exit_on_error = 1; /** * A boolean to indicate the presence of a PortMidi error. */ static int pm_error_present = FALSE; /** * Like pm_hosterror_text[], but will be exposed to the outside world and * might contain additional information. */ static char pm_error_message [PM_HOST_ERROR_MSG_LEN]; #if defined USE_C_MILLISLEEP /** * A simplistic sleep function for Linux and Windows C code. */ void c_millisleep (int ms) { #if defined SEQ66_PLATFORM_LINUX (void) usleep(1000 * ms); #elif defined SEQ66_PLATFORM_UNIX struct timeval tv; struct timeval * tvptr = &tv; tv.tv_usec = long(ms % 1000) * 1000; tv.tv_sec = long(ms / 1000); (void) select(0, 0, 0, 0, tvptr) != (-1); #elif defined SEQ66_PLATFORM_WINDOWS Sleep((DWORD) ms); #else #error c_millisleep not defined for this platform #endif } #else void c_millisleep (int ms) { (void) ms; } #endif // USE_C_MILLISLEEP /** * Provides a case-insensitive implementation of strstr() that doesn't need a * C extension function. Not necessarily efficient, and handles only string * that are relatively small. * * \param src * Provides the string that is suspected of containing the target. * * \param target * Provides the string to be searched for. * * \return * If all the parameters are suitable (not null, non-zero length, not too * long [256 bytes]), returns true if the target was found. Otherwise, * returns false. */ static int strstrcase (const char * src, const char * target) { int result = false; int lensrc = 0; int lentarget = 0; int ok = not_nullptr(src); if (ok) { lensrc = strlen(src); ok = lensrc > 0; } if (ok) { ok = not_nullptr(target); if (ok) { lentarget = strlen(target); ok = lentarget > 0; } } if (ok) { char tempsrc[PM_STRING_MAX]; char temptarget[PM_STRING_MAX]; int isrc; int itarget; (void) snprintf(tempsrc, sizeof tempsrc, "%s", src); (void) snprintf(temptarget, sizeof temptarget, "%s", target); for (isrc = 0; isrc < lensrc; ++isrc) tempsrc[isrc] = (char) tolower((unsigned char) src[isrc]); for (itarget = 0; itarget < lentarget; ++itarget) temptarget[itarget] = (char) tolower((unsigned char) target[itarget]); result = not_nullptr(strstr(tempsrc, temptarget)); } return result; } /** * Common memory functions basically the same on all platforms, so moved * to here from the pmlinux, pmmac, and pmwin modules. */ /** * A wrapper for malloc(). * * \param s * Provides the number of bytes to allocate. * * \return * Returns a pointer to the allocated memory, or a null pointer if the * size parameter is 0. */ void * pm_alloc (size_t s) { #if defined DEBUG_ALLOC_TRACKING void * result; if (s > 0) { result = malloc(s); printf("%p <-- pm_alloc()\n", result); } else { result = nullptr; printf("nullptr <-- pm_alloc()\n"); } return result; #else return s > 0 ? malloc(s) : nullptr ; #endif } /** * A wrapper for free(). It would be nice to be able so see if the pointer * was already freed, as calling free() twice on the same pointer is * undefined. The caller can guard against this by setting the pointer to * null explicitly after calling this function. * * This should be moved to a common module, as it is also defined in the * architecture-specific modules. * * \param ptr * The pointer to be freed. It is ignored if null. */ void pm_free (void * ptr) { #if defined DEBUG_ALLOC_TRACKING printf("pm_free(%p)\n", ptr); if (not_nullptr(ptr) && ! bad_pointer(ptr)) free(ptr); #else if (not_nullptr(ptr)) free(ptr); #endif } /** * Error setter. */ void Pm_set_hosterror_text (const char * msg) { if (not_nullptr(msg)) { int len = strlen(msg); if (len > 0) { strncpy(&pm_hosterror_text[0], msg, sizeof pm_hosterror_text); pm_hosterror_text[sizeof pm_hosterror_text - 1] = 0; } else pm_hosterror_text[0] = 0; } else pm_hosterror_text[0] = 0; } const char * Pm_hosterror_text (void) { const char * result = nullptr; if (strlen(pm_hosterror_text) > 0) result = &pm_hosterror_text[0]; return result; } char * Pm_hosterror_text_mutable (void) { return &pm_hosterror_text[0]; } void Pm_set_hosterror (int flag) { pm_hosterror = flag; } int Pm_hosterror (void) { return pm_hosterror; } void Pm_set_initialized (int flag) { pm_initialized = flag; } int Pm_initialized (void) { return pm_initialized; } void Pm_set_show_debug (int flag) { pm_show_debug = flag; } int Pm_show_debug (void) { return pm_show_debug; } void Pm_set_exit_on_error (int flag) { pm_exit_on_error = flag; } int Pm_exit_on_error (void) { return pm_exit_on_error; } void Pm_set_error_present (int flag) { pm_error_present = flag; } int Pm_error_present (void) { return pm_error_present; } /** * \setter pm_host_error_message * Also sets pm_error_present; sets it to true if the message has a * non-zero length, and false if 0 length or the pointer is null. * Thus, "Pm_set_error_message(nullptr)" is a good canonical way to * clear the error message. */ void Pm_set_error_message (const char * msg) { if (not_nullptr(msg)) { if (strlen(msg) == 0) { pm_error_message[0] = 0; Pm_set_error_present(FALSE); } else { snprintf(pm_error_message, sizeof pm_error_message, "%s", msg); Pm_set_error_present(TRUE); pm_log_buffer_append(pm_error_message); } } else { pm_error_message[0] = 0; Pm_set_error_present(FALSE); } } /** * \getter pm_host_error_message * * \return * Returns &pm_error_message[0]. But, if the message buffer is empty * (the first character is null), an internal (static) dummy message * pointer is returned instead, so as not to cause exceptions in C++ * callers. */ const char * Pm_error_message (void) { static const char * s_unknown_msg = "portmidi error"; return pm_error_message[0] == '\0' ? s_unknown_msg : &pm_error_message[0] ; } /** * It seems pointless to allocate memory and copy the string, so do the work * of Pm_GetHostErrorText() directly. */ static PmError pm_errmsg (PmError err, int deviceid) { if (err == pmHostError) { char temp[PM_STRING_MAX]; (void) snprintf ( temp, sizeof temp, "portmidi host error: [%d] '%s'\n", deviceid, pm_hosterror_text ); pm_log_buffer_append(temp); Pm_set_error_message(temp); Pm_set_hosterror(FALSE); /* clear internal flag */ pm_hosterror_text[0] = 0; /* clear the message */ } else if (err != pmNoError) { char temp[PM_STRING_MAX]; const char * errmsg = Pm_GetErrorText(err); (void) snprintf ( temp, sizeof temp, "portmidi call failed: [%d] '%s'\n", deviceid, errmsg ); Pm_set_error_message(temp); pm_log_buffer_append(temp); } else { /* * Anything to clear if there is no error? */ #if defined SEQ66_PLATFORM_DEBUG_TMI fprintf(stderr, "portmidi debug message\n"); #endif } return err; } /** * Describe interface/device pair to library. This is called at * intialization time, once for each interface (e.g. DirectSound) and device * (e.g. SoundBlaster 1). The strings are retained but NOT COPIED, so do not * destroy them! * * \param interf * The name of the interface. It is "MMSystem" for Windows. * * \param name * The name of the device. This is a pointer to an external entity, such * as midi_in_mapper_caps.szPname. * * \param input * A boolean that is set to 1 (true) if the device is an input device, * and 0 (false) if it is an output device. * * \param descriptor * Provides additional information for a given API, a device number or a * device handle, depending on the operating system or multi-media API. * * \param dictionary * Indicates the function signature of MIDI handler functions, we think. * * \param client * Provides a client number for the device. This value is an ALSA * concept. For other APIs, it is simply the ordinal of the device as it * was looked up. * * \param port * Provides a port number for the device. This value is an ALSA * concept. For other APIs, it is simply the ordinal of the port as it * was looked up, or simply 0. * * \return * pmInvalidDeviceId if device memory is exceeded otherwise returns * pmNoError. */ PmError pm_add_device ( char * interf, char * name, int input, void * descriptor, pm_fns_type dictionary, int client, int port ) { char temp[80]; int ismapper = not_nullptr(strstrcase(name, "mapper")); const int index = pm_descriptor_index; if (index >= pm_descriptor_max) { const size_t sdesc = sizeof(descriptor_node); // expand descriptors descriptor_type new_descriptors = (descriptor_type) pm_alloc(sdesc * (pm_descriptor_max + 32)); if (is_nullptr(new_descriptors)) return pmInsufficientMemory; if (not_nullptr(pm_descriptors)) { memcpy(new_descriptors, pm_descriptors, sdesc * pm_descriptor_max); pm_free(pm_descriptors); // pm_descriptors = nullptr; } pm_descriptor_max += 32; pm_descriptors = new_descriptors; } pm_descriptors[index].pub.structVersion = PM_STRUCTURE_VERSION; pm_descriptors[index].pub.interf = interf; pm_descriptors[index].pub.name = name; pm_descriptors[index].pub.input = input; pm_descriptors[index].pub.output = ! input; pm_descriptors[index].pub.mapper = ismapper; pm_descriptors[index].pub.client = client; pm_descriptors[index].pub.port = port; /* * Default state: nothing to close (for automatic device closing). */ pm_descriptors[index].pub.opened = FALSE; /* * ID number passed to Win32 multimedia API open function. */ pm_descriptors[index].descriptor = descriptor; /* * Points to PmInternal, allows automatic device closing. */ pm_descriptors[index].internalDescriptor = nullptr; pm_descriptors[index].dictionary = dictionary; ++pm_descriptor_index; snprintf ( temp, sizeof temp - 1, "PortMidi [%d]: %s %s:%s added", index, (input ? "Input" : "Output"), interf, name ); infoprint(temp); (void) strcat(temp, "\n"); pm_log_buffer_append(temp); return pmNoError; } /** * Utility to look up device, given a pattern. Note: the pattern is modified. * We first parse pattern into name_pref and interf_pref parts. */ int pm_find_default_device (char * pattern, int is_input) { int id = pmNoDevice; int i; char * interf_pref = ""; /* initially assume it's not there */ char * name_pref = strstr(pattern, ", "); if (name_pref) /* found separator, adjust pointer */ { interf_pref = pattern; name_pref[0] = 0; name_pref += 2; } else name_pref = pattern; /* whole string is the name pattern */ for (i = 0; i < pm_descriptor_index; ++i) { const PmDeviceInfo * info = Pm_GetDeviceInfo(i); if ( info->input == is_input && strstr(info->name, name_pref) && strstr(info->interf, interf_pref) ) { id = i; break; } } return id; } /** * Get devices count; IDs range from 0 to Pm_CountDevices()-1. */ PMEXPORT int Pm_CountDevices (void) { Pm_Initialize(); /* no error checking; does not fail */ return pm_descriptor_index; } /** * Pm_GetDeviceInfo() returns a pointer to a PmDeviceInfo structure referring * to the device specified by id. If ID is out of range the function returns * NULL. * * The returned structure is owned by the PortMidi implementation and must not * be manipulated or freed. The pointer is guaranteed to be valid between * calls to Pm_Initialize() and Pm_Terminate(). */ PMEXPORT const PmDeviceInfo * Pm_GetDeviceInfo (PmDeviceID id) { Pm_Initialize(); /* no error check needed */ if (id >= 0 && id < pm_descriptor_index) return &pm_descriptors[id].pub; return nullptr; } /** * Provides a "no-op" function pointer. */ PmError pm_success_fn (PmInternal * UNUSED(midi)) { return pmNoError; } /** * Returns an error if called. */ PmError none_write_short (PmInternal * UNUSED(midi), PmEvent * UNUSED(buffer)) { return pmBadPtr; } /** * Provides a placeholder for begin_sysex() and flush(). */ PmError pm_fail_timestamp_fn (PmInternal * UNUSED(midi), PmTimestamp UNUSED(timestamp)) { return pmBadPtr; } PmError none_write_byte ( PmInternal * UNUSED(midi), midibyte_t UNUSED(byte), PmTimestamp UNUSED(timestamp) ) { return pmBadPtr; } /** * A generic function, returns error if called. */ PmError pm_fail_fn (PmInternal * UNUSED(midi)) { return pmBadPtr; } static PmError none_open (PmInternal * UNUSED(midi), void * UNUSED(driverinfo)) { return pmBadPtr; } static void none_get_host_error ( PmInternal * UNUSED(midi), char * msg, unsigned UNUSED(len) ) { *msg = 0; // empty string } static unsigned none_has_host_error (PmInternal * UNUSED(midi)) { return FALSE; } PmTimestamp none_synchronize (PmInternal * UNUSED(midi)) { return 0; } /** * Defines for the abort and close functions. */ #define none_abort pm_fail_fn #define none_close pm_fail_fn /** * Lookup? */ pm_fns_node pm_none_dictionary = { none_write_short, none_sysex, none_sysex, none_write_byte, none_write_short, none_write_flush, none_synchronize, none_open, none_abort, none_close, none_poll, none_has_host_error, none_get_host_error /* incompatible function signature! */ }; /** * Translate portmidi error number into human readable message. These strings * are constants (set at compile time) so client has no need to allocate * storage */ PMEXPORT const char * Pm_GetErrorText (PmError errnum) { const char * msg; switch(errnum) { case pmNoError: msg = ""; break; case pmHostError: msg = "Host error"; break; case pmInvalidDeviceId: msg = "Invalid device ID"; break; case pmInsufficientMemory: msg = "Insufficient memory"; break; case pmBufferTooSmall: msg = "Buffer too small"; break; case pmBadPtr: msg = "Bad pointer"; break; case pmInternalError: msg = "Internal portmidi error"; break; case pmBufferOverflow: msg = "Buffer overflow"; break; case pmBadData: msg = "Invalid MIDI message data"; break; case pmBufferMaxSize: msg = "Buffer cannot be made larger"; break; case pmDeviceClosed: msg = "Device is closed"; break; case pmDeviceOpen: msg = "Device already open"; break; case pmWriteToInput: msg = "Write to input device"; break; case pmReadFromOutput: msg = "Read from output device"; break; case pmErrOther: msg = "Unspecified error"; break; case pmDeviceLocked: msg = "Device locked"; break; default: msg = "Illegal error number"; break; } return msg; } #if defined PM_GETHOSTERRORTEXT /** * Translate portmidi host error into human readable message. These strings * are computed at run time, so client has to allocate storage. After this * routine executes, the host error is cleared. This can be called whenever * you get a pmHostError return value. The error will always be in the * global pm_hosterror_text. */ PMEXPORT void Pm_GetHostErrorText (char * msg, unsigned len) { if (Pm_hosterror()) { strncpy(msg, (char *) pm_hosterror_text, len); Pm_set_hosterror(FALSE); /* * Clear the message; not necessary, but it might help with debugging */ pm_hosterror_text[0] = 0; msg[len - 1] = 0; /* make sure string is terminated */ } else msg[0] = 0; /* no string to return */ } #endif /** * Test whether stream has a pending host error. Normally, the client finds * out about errors through returned error codes, but some errors can occur * asynchronously where the client does not explicitly call a function, and * therefore cannot receive an error code. The client can test for a pending * error using Pm_HasHostError(). If true, the error can be accessed and * cleared by calling Pm_GetErrorText(). Errors are also cleared by calling * other functions that can return errors, e.g. Pm_OpenInput(), * Pm_OpenOutput(), Pm_Read(), Pm_Write(). The client does not need to call * Pm_HasHostError(). Any pending error will be reported the next time the * client performers an explicit function call on the stream, e.g. an input or * output operation. Until the error is cleared, no new error codes will be * obtained, even for a different stream. */ PMEXPORT int Pm_HasHostError (PortMidiStream * stream) { if (Pm_hosterror()) return TRUE; if (stream) { PmInternal * midi = (PmInternal *) stream; Pm_set_hosterror((*midi->dictionary->has_host_error)(midi)); if (Pm_hosterror()) { midi->dictionary->host_error ( midi, pm_hosterror_text, PM_HOST_ERROR_MSG_LEN ); return TRUE; /* now error message is global */ } } return FALSE; } /** * Pm_Initialize() is the library initialisation function - call this before * using the library. */ PMEXPORT PmError Pm_Initialize (void) { if (! Pm_initialized()) { pm_descriptor_max = 0; /* declared in pminternal.h */ pm_descriptor_index = 0; /* declared in pminternal.h */ pm_descriptors = nullptr; /* declared in pminternal.h */ Pm_set_error_message(nullptr); Pm_set_hosterror(FALSE); pm_hosterror_text[0] = 0; /* the null string */ pm_init(); Pm_set_initialized(TRUE); } return pmNoError; } /** * Pm_Terminate() is the library termination function - call this after * using the library. */ PMEXPORT PmError Pm_Terminate (void) { if (Pm_initialized()) { pm_term(); /* * If there are no devices, descriptors might still be null. The Linux * code has already done this, but the Windows version does not * AFAICT. */ if (not_nullptr(pm_descriptors)) { pm_free(pm_descriptors); pm_descriptors = nullptr; } pm_descriptor_index = 0; pm_descriptor_max = 0; Pm_set_initialized(FALSE); } return pmNoError; } /** * Read up to length messages from source into buffer. Pm_Read() retrieves * midi data into a buffer, and returns the number of events read. Result is * a non-negative number unless an error occurs, in which case a PmError * value will be returned. * * Buffer Overflow: * * The problem: if an input overflow occurs, data will be lost, * ultimately because there is no flow control all the way back to the * data source. When data is lost, the receiver should be notified and * some sort of graceful recovery should take place, e.g. you shouldn't * resume receiving in the middle of a long sysex message. * * With a lock-free fifo, which is pretty much what we're stuck with to * enable portability to the Mac, it's tricky for the producer and consumer * to synchronously reset the buffer and resume normal operation. * * Solution: the buffer managed by PortMidi will be flushed when an overflow * occurs. The consumer (Pm_Read()) gets an error message (pmBufferOverflow) * and ordinary processing resumes as soon as a new message arrives. The * remainder of a partial sysex message is not considered to be a "new * message" and will be flushed as well. * * This function polls for MIDI data in the buffer. It either simply checks * for data, or attempts first to fill the buffer with data from the MIDI * hardware; this depends on the implementation. We could call Pm_Poll() * here, but that would redo a lot of redundant parameter checking, so I * copied some code from Pm_Poll to here. * * For Linux, the poll function is alsa_poll() in the pmlinuxalsa module. * That function does a lot of work, so a simpler version that just check for * data is called, to support Seq66's midibus object. * * \return * Returns the number of messages actually read, or an error code. */ PMEXPORT int Pm_Read (PortMidiStream * stream, PmEvent * buffer, int32_t length) { /* * From here to the END marker, basically identical to Pm_Poll(). */ PmInternal * midi = (PmInternal *) stream; int deviceid = PORTMIDI_BAD_DEVICE_ID; PmError err = pmNoError; int n = 0; Pm_set_hosterror(FALSE); if (is_nullptr(midi)) err = pmBadPtr; else { deviceid = midi->device_id; if (! pm_descriptors[deviceid].pub.opened) err = pmDeviceClosed; else if (! pm_descriptors[deviceid].pub.input) err = pmReadFromOutput; else err = (*(midi->dictionary->poll))(midi); /* see the banner */ } if (err != pmNoError) { if (err == pmHostError) { midi->dictionary->host_error ( midi, pm_hosterror_text, PM_HOST_ERROR_MSG_LEN ); Pm_set_hosterror(TRUE); } #if defined SEQ66_PLATFORM_DEBUG fprintf(stderr, "Pm_Read error\n"); #endif #if defined USE_C_MILLISLEEP (void) c_millisleep(1); #endif return pm_errmsg(err, deviceid); } /* * END: return ! Pm_QueueEmpty(midi->queue); */ while (n < length) { PmError qerr = Pm_Dequeue(midi->queue, buffer++); if (qerr == pmBufferOverflow) /* ignore data gotten so far */ return pm_errmsg(pmBufferOverflow, deviceid); else if (qerr == 0) /* empty queue */ break; ++n; } return n; } /** * Pm_Poll() tests whether input is available, returning TRUE (PmGotData = * 1), FALSE (PmNoData = 0), or an error value. This is a pretty weird way * of dealing with polling status. */ PMEXPORT PmError Pm_Poll (PortMidiStream * stream) { PmInternal * midi = (PmInternal *) stream; int deviceid = PORTMIDI_BAD_DEVICE_ID; PmError result = pmNoError; Pm_set_hosterror(FALSE); if (is_nullptr(midi)) { result = pmBadPtr; } else { deviceid = midi->device_id; if (! pm_descriptors[deviceid].pub.opened) result = pmDeviceClosed; else if (! pm_descriptors[deviceid].pub.input) result = pmReadFromOutput; else result = (*(midi->dictionary->poll))(midi); } if (result != pmNoError) { if (result == pmHostError) { midi->dictionary->host_error ( midi, pm_hosterror_text, PM_HOST_ERROR_MSG_LEN ); Pm_set_hosterror(TRUE); } #if defined SEQ66_PLATFORM_DEBUG fprintf(stderr, "Pm_Poll error\n"); #endif return pm_errmsg(result, deviceid); } #if defined SEQ66_PLATFORM_DEBUG_TMI fprintf(stderr, "Pm_Poll()\n"); #endif return ! Pm_QueueEmpty(midi->queue); } /** * This is called from Pm_Write and Pm_WriteSysEx to issue a call to the * system-dependent end_sysex function and handle the error return. */ static PmError pm_end_sysex (PmInternal * midi) { PmError err = (*midi->dictionary->end_sysex)(midi, 0); midi->sysex_in_progress = FALSE; if (err == pmHostError) { midi->dictionary->host_error ( midi, pm_hosterror_text, PM_HOST_ERROR_MSG_LEN ); Pm_set_hosterror(TRUE); } return err; } /** * To facilitate correct error-handling, Pm_Write(), Pm_WriteShort(), and * Pm_WriteSysEx() all operate a state machine that "outputs" calls to * write_short(), begin_sysex(), write_byte(), end_sysex(), and * write_realtime(). * * Pm_Write() writes midi data from a buffer. This may contain: * * - short messages * - sysex messages that are converted into a sequence of PmEvent * structures, e.g. sending data from a file or forwarding them * from midi input. * * Use Pm_WriteSysEx() to write a SysEx message stored as a contiguous * array of bytes. SysEX data may contain embedded real-time messages. */ PMEXPORT PmError Pm_Write (PortMidiStream * stream, PmEvent * buffer, int32_t length) { PmInternal * midi = (PmInternal *) stream; int deviceid = PORTMIDI_BAD_DEVICE_ID; PmError err = pmNoError; Pm_set_hosterror(FALSE); int i; int bits; if (is_nullptr(midi)) { err = pmBadPtr; } else { deviceid = midi->device_id; if (! pm_descriptors[deviceid].pub.opened) err = pmDeviceClosed; else if (! pm_descriptors[deviceid].pub.output) err = pmWriteToInput; else err = pmNoError; } if (err != pmNoError) goto pm_write_error; if (midi->latency == 0) { midi->now = 0; } else { midi->now = (*(midi->time_proc))(midi->time_info); if (midi->first_message || midi->sync_time + 100 /*ms*/ < midi->now) { midi->now = (*midi->dictionary->synchronize)(midi); /* resync */ midi->first_message = FALSE; } } /* Error recovery: * * When a SysEx is detected, we call dictionary->begin_sysex() * followed by calls to dictionary->write_byte() and * dictionary->write_realtime() until an end-of-SysEx is detected, * when we call dictionary->end_sysex(). After an error occurs, * Pm_Write() continues to call functions. For example, it will * continue to call write_byte() even after an error sending a SysEx * message, and end_sysex() will be called when an EOX or * non-real-time status is found. When errors are detected, * Pm_Write() returns immediately, so it is possible that this will * drop data and leave SysEx messages in a partially transmitted * state. */ for (i = 0; i < length; ++i) { uint32_t msg = buffer[i].message; bits = 0; if (Pm_MessageStatus(msg) == MIDI_SYSEX) { if (midi->sysex_in_progress) { /* error: previous SysEx was not terminated by EOX */ midi->sysex_in_progress = FALSE; err = pmBadData; goto pm_write_error; } midi->sysex_in_progress = TRUE; if ( ( err = (*midi->dictionary->begin_sysex) ( midi, buffer[i].timestamp ) ) != pmNoError ) { goto pm_write_error; } if ( ( err = (*midi->dictionary->write_byte) ( midi, MIDI_SYSEX, buffer[i].timestamp ) ) != pmNoError) { goto pm_write_error; } bits = 8; /* fall through to continue SysEx processing */ } else if ((msg & MIDI_STATUS_MASK) && (Pm_MessageStatus(msg) != MIDI_EOX)) { if (midi->sysex_in_progress) /* a non-SysEx message? */ { if (is_real_time(msg)) /* a realtime message? */ { err = (*midi->dictionary->write_realtime) ( midi, &(buffer[i]) ); if (err!= pmNoError) goto pm_write_error; } else { midi->sysex_in_progress = FALSE; err = pmBadData; /* * Ignore any error from this, because we already have * one. Pass 0 as timestamp -- it's ignored. */ (*midi->dictionary->end_sysex)(midi, 0); goto pm_write_error; } } else { /* * Regular short MIDI message */ err = (*midi->dictionary->write_short)(midi, &(buffer[i])); if (err != pmNoError) goto pm_write_error; continue; } } if (midi->sysex_in_progress) { /* * Send SysEx bytes until EOX. See if we can accelerate data * transfer. */ if ( bits == 0 && midi->fill_base && /* 4 bytes to copy */ (*midi->fill_offset_ptr) + 4 <= midi->fill_length && (msg & 0x80808080) == 0 ) { /* * All data. Copy 4 bytes from msg to fill_base + fill_offset */ midibyte_t * ptr = midi->fill_base + *(midi->fill_offset_ptr); ptr[0] = msg; ptr[1] = msg >> 8; ptr[2] = msg >> 16; ptr[3] = msg >> 24; (*midi->fill_offset_ptr) += 4; continue; } while (bits < 32) /* no acceleration, copy byte-by-byte */ { midibyte_t midi_byte = (midibyte_t) (msg >> bits); err = (*midi->dictionary->write_byte) ( midi, midi_byte, buffer[i].timestamp ); if (err != pmNoError) goto pm_write_error; if (midi_byte == MIDI_EOX) { err = pm_end_sysex(midi); if (err != pmNoError) goto error_exit; break; } bits += 8; } } else { /* not in SysEx mode, but message did not start with status */ err = pmBadData; goto pm_write_error; } } /* after all messages are processed, send the data */ if (! midi->sysex_in_progress) err = (*midi->dictionary->write_flush)(midi, 0); pm_write_error: if (err == pmHostError) { midi->dictionary->host_error ( midi, pm_hosterror_text, PM_HOST_ERROR_MSG_LEN ); Pm_set_hosterror(TRUE); } error_exit: return pm_errmsg(err, deviceid); } /** * Pm_WriteShort() writes a timestamped non-system-exclusive midi message. * Messages are delivered in order as received, and timestamps must be * non-decreasing. (But timestamps are ignored if the stream was opened * with latency = 0.) */ PMEXPORT PmError Pm_WriteShort (PortMidiStream * stream, PmTimestamp when, PmMessage msg) { PmEvent event; event.timestamp = when; event.message = msg; return Pm_Write(stream, &event, 1); } #define BUFLEN ((int) (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage))) /** * Pm_WriteSysEx() writes a timestamped system-exclusive midi message. * Allocate buffer space for PM_DEFAULT_SYSEX_BUFFER_SIZE bytes. Each * PmEvent holds sizeof(PmMessage) bytes of SysEx data. */ PMEXPORT PmError Pm_WriteSysEx (PortMidiStream * stream, PmTimestamp when, midibyte_t * msg) { PmInternal * midi = (PmInternal *) stream; PmEvent buffer[BUFLEN]; int buffer_size = 1; /* first time, send 1. After that, it's BUFLEN */ /* * The next byte in the buffer is represented by an index, bufx, and * a shift in bits. */ int shift = 0; int bufx = 0; buffer[0].message = 0; buffer[0].timestamp = when; for (;;) { #if defined SEQ66_PLATFORM_DEBUG fprintf(stderr, "Pm_WriteSysEx\n"); #endif buffer[bufx].message |= ((*msg) << shift); /* put next byte in buffer */ shift += 8; if (*msg++ == MIDI_EOX) break; if (shift == 32) { shift = 0; if (++bufx == buffer_size) // ++bufx; { PmError err = Pm_Write(stream, buffer, buffer_size); if (err) return err; /* Pm_Write() called errmsg() */ bufx = 0; /* prepare to fill another buffer */ buffer_size = BUFLEN; if (midi->fill_base) /* optimization? just copy bytes? */ { while (*(midi->fill_offset_ptr) < midi->fill_length) { midi->fill_base[(*midi->fill_offset_ptr)++] = *msg; if (*msg++ == MIDI_EOX) { err = pm_end_sysex(midi); if (err != pmNoError) return pm_errmsg(err, midi->device_id); goto end_of_sysex; } } /* * I thought that I could do a pm_Write here and change * this if to a loop, avoiding calls in Pm_Write() to the * slower write_byte, but since sysex_in_progress is * true, this will not flush the buffer, and we'll * infinite loop: * * err = Pm_Write(stream, buffer, 0); * if (err) * return err; * * Instead, the way this works is that Pm_Write() calls * write_byte on 4 bytes. The first, since the buffer is * full, will flush the buffer and allocate a new one. * This primes the buffer so that we can return to the * loop above and fill it efficiently without a lot of * function calls. */ buffer_size = 1; /* get another message started */ } } buffer[bufx].message = 0; buffer[bufx].timestamp = when; } } /* keep inserting bytes until you find MIDI_EOX */ end_of_sysex: /* * Finished sending full buffers, but there may be a partial one left. */ if (shift != 0) ++bufx; /* add partial message to buffer length */ if (bufx > 0) /* number of PmEvents to send from buffer */ { PmError err = Pm_Write(stream, buffer, bufx); if (err) return err; } return pmNoError; } /** * Pm_OpenInput() opens input devices. * * stream is the address of a PortMidiStream pointer which will receive a * pointer to the newly opened stream. * * inputdev is the id of the device used for input (see PmDeviceID above). * * inputinfo is a pointer to an optional driver specific data structure * containing additional information for device setup or handle processing. * inputinfo is never required for correct operation. If not used * inputinfo should be NULL. * * For input, the buffersize specifies the number of input events to be * buffered waiting to be read using Pm_Read(). * * 'latency' is the delay in milliseconds applied to timestamps to determine * when the output should actually occur. (If latency is < 0, 0 is assumed.) * If latency is zero, timestamps are ignored and all output is delivered * immediately. If latency is greater than zero, output is delayed until the * message timestamp plus the latency. (NOTE: the time is measured relative to * the time source indicated by time_proc. Timestamps are absolute, not * relative delays or offsets.) In some cases, PortMidi can obtain better * timing than your application by passing timestamps along to the device * driver or hardware. Latency may also help you to synchronize midi data to * audio data by matching midi latency to the audio buffer latency. * * time_proc is a pointer to a procedure that returns time in milliseconds. It * may be NULL, in which case a default millisecond timebase (PortTime) is * used. If the application wants to use PortTime, it should start the timer * (call Pt_Start) before calling Pm_OpenInput or Pm_OpenOutput. If the * application tries to start the timer *after* Pm_OpenInput or Pm_OpenOutput, * it may get a ptAlreadyStarted error from Pt_Start, and the application's * preferred time resolution and callback function will be ignored. time_proc * result values are appended to incoming MIDI data, and time_proc times are * used to schedule outgoing MIDI data (when latency is non-zero). * * time_info is a pointer passed to time_proc. * * Example: If I provide a timestamp of 5000, latency is 1, and time_proc * returns 4990, then the desired output time will be when time_proc returns * timestamp+latency = 5001. This will be 5001-4990 = 11ms from now. * * \return * Upon success Pm_Open() returns PmNoError and places a pointer to a * valid PortMidiStream in the stream argument. If a call to Pm_Open() * fails a nonzero error code is returned (see PMError above) and the * value of port is invalid. Any stream that is successfully opened * should eventually be closed by calling Pm_Close(). */ PMEXPORT PmError Pm_OpenInput ( PortMidiStream ** stream, PmDeviceID inputdev, void * inputinfo, int32_t buffersize, PmTimeProcPtr time_proc, void * time_info ) { PmInternal * midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); PmError err = pmNoError; Pm_set_hosterror(FALSE); if (is_nullptr(midi)) { err = pmInsufficientMemory; goto error_return; } if (not_nullptr(stream)) *stream = nullptr; else err = pmBadPtr; if (inputdev < 0 || inputdev >= pm_descriptor_index) err = pmInvalidDeviceId; else if (! pm_descriptors[inputdev].pub.input) err = pmInvalidDeviceId; else if (pm_descriptors[inputdev].pub.opened) err = pmDeviceOpen; if (err != pmNoError) goto error_return; if (not_nullptr(stream)) *stream = midi; midi->device_id = inputdev; midi->write_flag = FALSE; midi->time_proc = time_proc; midi->time_info = time_info; /* * Windows adds timestamps in the driver, and these are more accurate than * using a time_proc, so do not automatically provide a time proc. Non-win * implementations may want to provide a default time_proc in their * system-specific midi_out_open() method. */ if (buffersize <= 0) buffersize = 256; /* default buffer size */ midi->queue = Pm_QueueCreate(buffersize, (int32_t) sizeof(PmEvent)); if (! midi->queue) { *stream = nullptr; pm_free(midi); /* free portMidi data */ err = pmInsufficientMemory; goto error_return; } midi->buffer_len = buffersize; /* portMidi input storage */ midi->latency = 0; /* not used */ midi->sysex_in_progress = FALSE; midi->sysex_message = 0; midi->sysex_message_count = 0; midi->filters = PM_FILT_ACTIVE; midi->channel_mask = 0xFFFF; midi->sync_time = 0; midi->first_message = TRUE; midi->dictionary = pm_descriptors[inputdev].dictionary; midi->fill_base = nullptr; midi->fill_offset_ptr = nullptr; midi->fill_length = 0; pm_descriptors[inputdev].internalDescriptor = midi; /* open system dependent input device */ err = (*midi->dictionary->open)(midi, inputinfo); if (err) { *stream = nullptr; pm_descriptors[inputdev].internalDescriptor = nullptr; /* free portMidi data */ Pm_QueueDestroy(midi->queue); pm_free(midi); } else { /* portMidi input open successful */ pm_descriptors[inputdev].pub.opened = TRUE; } error_return: /* * Note: If there is a pmHostError, it is the responsibility of the * system-dependent code (*midi->dictionary->open)() to set pm_hosterror * and pm_hosterror_text. */ return pm_errmsg(err, inputdev); } /** * Pm_OpenOutput() opens output devices. * * outputdev is the id of the device used for output (see PmDeviceID * above.) * * outputinfo is a pointer to an optional driver specific data structure * containing additional information for device setup or handle processing. * outputinfo is never required for correct operation. If not used * outputinfo should be NULL. * * For output, buffersize specifies the number of output events to be buffered * waiting for output. (In some cases -- see below -- PortMidi does not buffer * output at all and merely passes data to a lower-level API, in which case * buffersize is ignored.) * * See Pm_OpenInput() for more information on parameters. */ PMEXPORT PmError Pm_OpenOutput ( PortMidiStream ** stream, PmDeviceID outputdev, void * outputinfo, int32_t buffersize, PmTimeProcPtr time_proc, void * time_info, int32_t latency ) { PmInternal * midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); PmError err = pmNoError; Pm_set_hosterror(FALSE); if (not_nullptr(stream)) *stream = nullptr; else err = pmBadPtr; if (outputdev < 0 || outputdev >= pm_descriptor_index) err = pmInvalidDeviceId; else if (! pm_descriptors[outputdev].pub.output) err = pmInvalidDeviceId; else if (pm_descriptors[outputdev].pub.opened) err = pmDeviceOpen; if (err != pmNoError) goto error_return; /* create portMidi internal data */ *stream = midi; if (! midi) { err = pmInsufficientMemory; goto error_return; } midi->device_id = outputdev; midi->write_flag = TRUE; midi->time_proc = time_proc; /* * If latency > 0, we need a time reference. If none is provided, * use PortTime library. */ if (is_nullptr(time_proc) && latency != 0) { if (! Pt_Started()) Pt_Start(1, 0, 0); /* * time_get does not take a parameter, so coerce. But... * * warning: cast between incompatible function types from * ‘PtTimestamp (*)(void)’ [int (*)(void)} to ‘PmTimestamp * * (*)(void *)’ */ midi->time_proc = (PmTimeProcPtr) Pt_Time; } midi->time_info = time_info; midi->buffer_len = buffersize; midi->queue = nullptr; /* unused by output */ /* * If latency zero, output immediate (timestamps ignored). if latency < * 0, use 0, but don't return an error. */ if (latency < 0) latency = 0; midi->latency = latency; midi->sysex_in_progress = FALSE; midi->sysex_message = 0; /* unused by output */ midi->sysex_message_count = 0; /* unused by output */ midi->filters = 0; /* not used for output */ midi->channel_mask = 0xFFFF; midi->sync_time = 0; midi->first_message = TRUE; midi->dictionary = pm_descriptors[outputdev].dictionary; midi->fill_base = nullptr; midi->fill_offset_ptr = nullptr; midi->fill_length = 0; pm_descriptors[outputdev].internalDescriptor = midi; /* * Open system-dependent output device, calling, for example, * winmm_out_open(). */ err = (*midi->dictionary->open)(midi, outputinfo); if (err) { *stream = nullptr; pm_descriptors[outputdev].internalDescriptor = nullptr; pm_free(midi); /* free portMidi data */ } else pm_descriptors[outputdev].pub.opened = TRUE; /* input-open success */ error_return: /* * Note: system-dependent code must set pm_hosterror and pm_hosterror_text * if a pmHostError occurs. */ return pm_errmsg(err, outputdev); } /** * Pm_SetChannelMask() filters incoming messages based on channel. The mask * is a 16-bit bitfield corresponding to appropriate channels. The Pm_Channel * macro can assist in calling this function. i.e. to set receive only input * on channel 1, call with Pm_SetChannelMask(Pm_Channel(1)); Multiple channels * should be OR'd together, like Pm_SetChannelMask(Pm_Channel(10) | * Pm_Channel(11)) * * Note that channels are numbered 0 to 15 (not 1 to 16). Most synthesizer and * interfaces number channels starting at 1, but PortMidi numbers channels * starting at 0. * * All channels are allowed by default */ PMEXPORT PmError Pm_SetChannelMask (PortMidiStream * stream, int mask) { PmInternal * midi = (PmInternal *) stream; PmError err = pmNoError; if (is_nullptr(midi)) err = pmBadPtr; else midi->channel_mask = mask; return pm_errmsg(err, PORTMIDI_BAD_DEVICE_ID); } /** * Pm_SetFilter() sets filters on an open input stream to drop selected input * types. By default, only active sensing messages are filtered. To prohibit, * say, active sensing and sysex messages, call Pm_SetFilter(stream, * PM_FILT_ACTIVE | PM_FILT_SYSEX); * * Filtering is useful when midi routing or midi thru functionality is being * provided by the user application. For example, you may want to exclude * timing messages (clock, MTC, start/stop/continue), while allowing * note-related messages to pass. Or you may be using a sequencer or * drum-machine for MIDI clock information but want to exclude any notes it * may play. */ PMEXPORT PmError Pm_SetFilter (PortMidiStream * stream, int32_t filters) { int deviceid = PORTMIDI_BAD_DEVICE_ID; PmInternal * midi = (PmInternal *) stream; PmError err = pmNoError; if (is_nullptr(midi)) err = pmBadPtr; else { deviceid = midi->device_id; if (! pm_descriptors[deviceid].pub.opened) err = pmDeviceClosed; else midi->filters = filters; } return pm_errmsg(err, deviceid); } /** * Pm_Close() closes a midi stream, flushing any pending buffers. (PortMidi * attempts to close open streams when the application exits -- this is * particularly difficult under Windows.) If it is an open device, the * device_id will be valid and the device should be in the opened state. * * For Seq66, we have added a check for pm_descriptors being null. * This happens because the ~mastermidbus() function calls Pm_Terminate(), * which frees that array. Then ~midibus() calls Pm_Close(). * * I think we ought to straighten this out at some point. */ PMEXPORT PmError Pm_Close (PortMidiStream * stream) { int deviceid = PORTMIDI_BAD_DEVICE_ID; PmInternal * midi = (PmInternal *) stream; PmError err = pmNoError; Pm_set_hosterror(FALSE); if (not_nullptr(pm_descriptors)) { if (is_nullptr(midi)) err = pmBadPtr; else { deviceid = midi->device_id; if (deviceid < 0 || deviceid >= pm_descriptor_index) err = pmInvalidDeviceId; else if (! pm_descriptors[deviceid].pub.opened) err = pmDeviceClosed; } if (err == pmNoError) { err = (*midi->dictionary->close)(midi); /* close the device */ /* even if an error occurred, continue with cleanup */ pm_descriptors[deviceid].internalDescriptor = nullptr; pm_descriptors[deviceid].pub.opened = FALSE; if (midi->queue) Pm_QueueDestroy(midi->queue); pm_free(midi); } } /* * System-dependent code must set pm_hosterror and pm_hosterror_text if a * pmHostError occurs. */ return pm_errmsg(err, deviceid); } /** * Pm_Synchronize() instructs PortMidi to (re)synchronize to the time_proc * passed when the stream was opened. Typically, this is used when the stream * must be opened before the time_proc reference is actually advancing. In * this case, message timing may be erratic, but since timestamps of zero mean * "send immediately," initialization messages with zero timestamps can be * written without a functioning time reference and without problems. Before * the first MIDI message with a non-zero timestamp is written to the stream, * the time reference must begin to advance (for example, if the time_proc * computes time based on audio samples, time might begin to advance when an * audio stream becomes active). After time_proc return values become valid, * and BEFORE writing the first non-zero timestamped MIDI message, call * Pm_Synchronize() so that PortMidi can observe the difference between the * current time_proc value and its MIDI stream time. * * In the more normal case where time_proc values advance continuously, there * is no need to call Pm_Synchronize. PortMidi will always synchronize at the * first output message and periodically thereafter. */ PmError Pm_Synchronize (PortMidiStream * stream ) { PmInternal * midi = (PmInternal *) stream; PmError err = pmNoError; if (is_nullptr(midi)) { err = pmBadPtr; } else { int deviceid = midi->device_id; // PORTMIDI_BAD_DEVICE_ID if (! pm_descriptors[deviceid].pub.output) err = pmErrOther; else if (! pm_descriptors[deviceid].pub.opened) err = pmDeviceClosed; else midi->first_message = TRUE; } return err; } /** * Pm_Abort() terminates outgoing messages immediately The caller should * immediately close the output port; this call may result in transmission of * a partial midi message. There is no abort for Midi input because the user * can simply ignore messages in the buffer and close an input device at any * time. */ PMEXPORT PmError Pm_Abort (PortMidiStream * stream) { PmInternal * midi = (PmInternal *) stream; int deviceid = PORTMIDI_BAD_DEVICE_ID; PmError err = pmNoError; if (is_nullptr(midi)) err = pmBadPtr; else { deviceid = midi->device_id; if (! pm_descriptors[deviceid].pub.output) err = pmErrOther; else if (! pm_descriptors[deviceid].pub.opened) err = pmDeviceClosed; else err = (*midi->dictionary->abort)(midi); } if (err == pmHostError) { midi->dictionary->host_error ( midi, pm_hosterror_text, PM_HOST_ERROR_MSG_LEN ); Pm_set_hosterror(TRUE); } return pm_errmsg(err, deviceid); } /** * pm_channel_filtered returns non-zero if the channel mask is blocking the * current channel. */ #define pm_channel_filtered(status, mask) \ ((((status) & 0xF0) != 0xF0) && (!(Pm_Channel((status) & 0x0F) & (mask)))) /* * The following two functions will checks to see if a MIDI message matches * the filtering criteria. Since the SysEx routines only want to filter * realtime messages, we need to have separate routines. */ /** * pm_realtime_filtered returns non-zero if the filter will kill the current * message. Note that only realtime messages are checked here. */ #define pm_realtime_filtered(status, filters) \ ((((status) & 0xF0) == 0xF0) && ((1 << ((status) & 0xF)) & (filters))) /* * return ((status == MIDI_ACTIVE) && (filters & PM_FILT_ACTIVE)) * || ((status == MIDI_CLOCK) && (filters & PM_FILT_CLOCK)) * || ((status == MIDI_START) && (filters & PM_FILT_PLAY)) * || ((status == MIDI_STOP) && (filters & PM_FILT_PLAY)) * || ((status == MIDI_CONTINUE) && (filters & PM_FILT_PLAY)) * || ((status == MIDI_F9) && (filters & PM_FILT_F9)) * || ((status == MIDI_FD) && (filters & PM_FILT_FD)) * || ((status == MIDI_RESET) && (filters & PM_FILT_RESET)) * || ((status == MIDI_MTC) && (filters & PM_FILT_MTC)) * || ((status == MIDI_SONGPOS) && (filters & PM_FILT_SONG_POSITION)) * || ((status == MIDI_SONGSEL) && (filters & PM_FILT_SONG_SELECT)) * || ((status == MIDI_TUNE) && (filters & PM_FILT_TUNE)); */ /* * pm_status_filtered returns non-zero if a filter will kill the current * message, based on status. Note that SysEx and real time are not checked. * It is up to the subsystem (winmm, core midi, alsa) to filter SysEx, as it * is handled more easily and efficiently at that level. Realtime message are * filtered in pm_realtime_filtered. */ #define pm_status_filtered(status, filters) \ ((1 << (16 + ((status) >> 4))) & (filters)) /* * return ((status == MIDI_NOTE_ON) && (filters & PM_FILT_NOTE)) * || ((status == MIDI_NOTE_OFF) && (filters & PM_FILT_NOTE)) * || ((status == MIDI_CHANNEL_AT) && (filters & PM_FILT_CHANNEL_AFTERTOUCH)) * || ((status == MIDI_POLY_AT) && (filters & PM_FILT_POLY_AFTERTOUCH)) * || ((status == MIDI_PROGRAM) && (filters & PM_FILT_PROGRAM)) * || ((status == MIDI_CONTROL) && (filters & PM_FILT_CONTROL)) * || ((status == MIDI_PITCHBEND) && (filters & PM_FILT_PITCHBEND)); */ static void pm_flush_sysex (PmInternal * midi, PmTimestamp timestamp) { PmEvent event; /* there may be nothing in the buffer */ if (midi->sysex_message_count == 0) return; /* nothing to flush */ event.message = midi->sysex_message; event.timestamp = timestamp; /* copied from pm_read_short, avoids filtering */ if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) { midi->sysex_in_progress = FALSE; } midi->sysex_message_count = 0; midi->sysex_message = 0; } /** * pm_read_short() and pm_read_bytes() are the interface between * system-dependent MIDI input handlers and the system-independent PortMIDI * code. The input handler MUST obey these rules: * * -# All short input messages must be sent to pm_read_short(), which * enqueues them to a FIFO for the application. * -# Each buffer of sysex bytes should be reported by calling * pm_read_bytes() (which sets midi->sysex_in_progress). After the EOX * byte, pm_read_bytes() will clear sysex_in_progress */ /** * pm_read_short() is the place where all input messages arrive from * system-dependent code such as pmwinmm.c. Here, the messages are entered * into the PortMidi input buffer. MIDI filtering is applied here. */ void pm_read_short (PmInternal * midi, PmEvent * event) { int status = Pm_MessageStatus(event->message); if ( ! pm_status_filtered(status, midi->filters) && (!is_real_time(status) || ! pm_realtime_filtered(status, midi->filters)) && !pm_channel_filtered(status, midi->channel_mask) ) { /* * if SysEx is in progress and we get a status byte, it had better be * a realtime message or the starting SYSEX byte; otherwise, we exit * the sysex_in_progress state */ if (midi->sysex_in_progress && (status & MIDI_STATUS_MASK)) { /* * Two choices: real-time or not. If it's real-time, then this * should be delivered as a SysEx byte because it is embedded in a * SysEx message. */ if (is_real_time(status)) { midi->sysex_message |= (status << (8 * midi->sysex_message_count++)); if (midi->sysex_message_count == 4) pm_flush_sysex(midi, event->timestamp); } else { /* * Otherwise, it's not real-time. This interrupts a SysEx * message in progress */ midi->sysex_in_progress = FALSE; } } else if (Pm_Enqueue(midi->queue, event) == pmBufferOverflow) midi->sysex_in_progress = FALSE; } } /** * Reads one (partial) SysEx msg from MIDI data. * * \return * Returns how many bytes were processed. */ unsigned pm_read_bytes ( PmInternal * midi, const midibyte_t * data, int len, PmTimestamp timestamp ) { int i = 0; /* index into data, must not be unsigned (!) */ PmEvent event; event.timestamp = timestamp; /* * Note that since buffers may not have multiples of 4 bytes, * pm_read_bytes may be called in the middle of an outgoing 4-byte * PortMidi message. sysex_in_progress indicates that a SysEx has been * sent but no eox. */ if (len == 0) return 0; /* sanity check */ if (! midi->sysex_in_progress) { while (i < len) /* process all data */ { midibyte_t byte = data[i++]; if ( byte == MIDI_SYSEX && ! pm_realtime_filtered(byte, midi->filters) ) { midi->sysex_in_progress = TRUE; i--; /* back up so code below gets SYSEX byte */ break; /* continue looping below to process msg */ } else if (byte == MIDI_EOX) { midi->sysex_in_progress = FALSE; return i; /* done with one message */ } else if (byte & MIDI_STATUS_MASK) { /* * We're getting MIDI but no sysex in progress. Either the * SYSEX status byte was dropped or the message was filtered. * Drop the data, but send any embedded realtime bytes. * Assume that this is a real-time message: it is an error to * pass non-real-time messages to pm_read_bytes */ event.message = byte; pm_read_short(midi, &event); } } /* all bytes in the buffer are processed */ } /* * Now, isysex_in_progress) { if ( midi->sysex_message_count == 0 && i <= len - 4 && ( (event.message = (((PmMessage) data[i]) | (((PmMessage) data[i+1]) << 8) | (((PmMessage) data[i+2]) << 16) | (((PmMessage) data[i+3]) << 24))) & 0x80808080) == 0 ) { /* all data, no status */ if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) midi->sysex_in_progress = FALSE; i += 4; } else { while (i < len) { midibyte_t byte = data[i++]; /* send one byte at a time */ if ( is_real_time(byte) && pm_realtime_filtered(byte, midi->filters) ) { continue; /* real-time data is filtered; omit */ } midi->sysex_message |= (byte << (8 * midi->sysex_message_count++)); if (byte == MIDI_EOX) { midi->sysex_in_progress = FALSE; pm_flush_sysex(midi, event.timestamp); return i; } else if (midi->sysex_message_count == 4) { pm_flush_sysex(midi, event.timestamp); /* * After handling at least one non-data byte and reaching * a 4-byte message boundary, resume trying to send 4 at a * time in the outer loop. */ break; } } } } return i; } /* * Ad hoc safe C accessors. */ /** * Returns true if the given device number is valid and the device is opened. * * \param deviceid * The device to be tested, ranging from 0 to less than * pm_descriptor_index. * * \return * Returns the opened status of a valid device. */ int Pm_device_opened (int deviceid) { int result = not_nullptr(pm_descriptors); if (result) { result = pm_descriptor_index >= 0 && deviceid < pm_descriptor_index; if (result) result = pm_descriptors[deviceid].pub.opened ? TRUE : FALSE ; } return result; } /** * The official accessor of pm_descriptor_index. Unlike Pm_CountDevices(), * this function does not call Pm_Initialize() first. */ int Pm_device_count (void) { return pm_descriptor_index; } void Pm_print_devices (void) { int dev; for (dev = 0; dev < pm_descriptor_index; ++dev) { char temp[PM_STRING_MAX]; int status = Pm_device_opened(dev); const PmDeviceInfo * dev_info = Pm_GetDeviceInfo(dev); const char * io = dev_info->output == 1 ? "output" : "unknown" ; const char * opstat = status == 1 ? "opened" : "closed" ; if (dev_info->input == 1) io = "input"; snprintf ( temp, sizeof temp, "PortMidi %s %d: %s %s %s", dev_info->interf, dev, dev_info->name, io, opstat ); infoprint(temp); (void) strcat(temp, "\n"); pm_log_buffer_append(temp); } } /** * Provides a large buffer to hold all errors we log so that the application * can retrieve them. The printf() call's don't always appear due to our * debugging symbols being undefined, so we want a more reliable/constrained * way to handle them. * * Currently used only in Windows applications. */ static int s_pmlogmm_max_size = 8192; static int s_pmlogmm_current_size = 0; static char * s_pmlogmm_buffer = nullptr; /** * Allocates the error-message buffer, and makes sure it is all zeroes. * We will not bother with reallocating a larger buffer until we find a need * for it. */ void pm_log_buffer_alloc (void) { const char * msg = "INTERNAL PORTMIDI MESSAGES:\n\n"; s_pmlogmm_buffer = calloc(s_pmlogmm_max_size, sizeof(char)); pm_log_buffer_append(msg); } /** * Deallocates the error-message buffer. */ void pm_log_buffer_free (void) { if (not_nullptr(s_pmlogmm_buffer)) { pm_free(s_pmlogmm_buffer); s_pmlogmm_buffer = nullptr; s_pmlogmm_current_size = 0; } } /** * Appends a message to the message buffer. The message should end in a * newline. */ void pm_log_buffer_append (const char * msg) { if (not_nullptr(s_pmlogmm_buffer)) { int currsize = s_pmlogmm_current_size; int msgsize = strlen(msg); if (msgsize > 0) { currsize += msgsize; if (currsize < s_pmlogmm_max_size) { char * dest = s_pmlogmm_buffer + s_pmlogmm_current_size; (void) strcpy(dest, msg); s_pmlogmm_current_size = currsize; } } } } /** * Provides read-only access to the message buffer. The caller should check * this value for being null before using it. */ const char * pm_log_buffer (void) { if (is_nullptr(s_pmlogmm_buffer) || strlen(s_pmlogmm_buffer) == 0) { static const char * s_dummy = "Empty/null log buffer"; return s_dummy; } else return (const char *) s_pmlogmm_buffer; } /* * portmidi.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/src/porttime.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file porttime.c * * Portable API for millisecond timer. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2018-05-25 * \license GNU GPLv2 or above * * There is no machine-independent implementation code to put here. */ #include "porttime.h" /** * Important constants from PortMidi. */ static double s_pm_beats_per_minute = 125; static int s_pm_tempo_microseconds = 480000; static int s_pm_ppqn = 480; /** * New functions to support setting the tempo and PPQN, as well as converting * PortMidi time to MIDI pulses (ticks). Note that the setting of PPQN here * is much simpler than seq66::choose_ppqn() in the settings module. Here, * if the value is 0, the default value 192 is used. */ void Pt_Set_Midi_Timing (double bpm, int ppqn) { Pt_Set_Bpm(bpm); Pt_Set_Ppqn(ppqn); } void Pt_Set_Bpm (double bpm) { if (bpm > 0.0) { s_pm_beats_per_minute = bpm; s_pm_tempo_microseconds = (int) (60000000.0 / bpm); } } void Pt_Set_Ppqn (int ppqn) { s_pm_ppqn = ppqn > 0 ? ppqn : 192 ; } /** * Convert the milliseconds timestamp to pulses (ticks). */ long Pt_Time_To_Pulses (int tsms) { return (long) (tsms * s_pm_beats_per_minute / 60000); } double Pt_Get_Bpm (void) { return s_pm_beats_per_minute; } int Pt_Get_Tempo_Microseconds (void) { return s_pm_tempo_microseconds; } int Pt_Get_Ppqn (void) { return s_pm_ppqn; } /* * porttime.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/src/ptlinux.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file ptlinux.c * * Portable timer implementation for Linux. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2024-01-13 * \license GNU GPLv2 or above * * Implementation Notes (by Mark Nelson): * * Unlike Windows, Linux has no system call to request a periodic * callback, so if Pt_Start() receives a callback parameter, it must * create a thread that wakes up periodically and calls the provided * callback function. If running as superuser, use setpriority() to * renice thread to -20. One could also set the timer thread to a * real-time priority (SCHED_FIFO and SCHED_RR), but this is dangerous * for This is necessary because if the callback hangs it'll never * return. A more serious reason is that the current scheduler * implementation busy-waits instead of sleeping when realtime threads * request a sleep of <=2ms (as a way to get around the 10ms * granularity), which means the thread would never let anyone else on * the CPU. * * Change Log: * * 18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer * thread. Simplified implementation notes. * * stdlib, stdio, unistd, and sys/types were added because they appeared * in a Gentoo patch, but I'm not sure why they are needed. -RBD */ #include #include #include #include #include #include #include #include "porttime.h" /* * The ftime(2) system call structure -- deprecated. But since it is used * only internally in the module to transfer parts of the time, we define * a partial replacement here. The following from timeb are not needed: * * short timezone; // minutes west of CUT * short dstflag; // DST == non-zero */ struct timeb_simple { time_t time; /* seconds since the Epoch */ unsigned short millitm; /* + milliseconds since the Epoch */ }; /* * REDUNDANT */ #define TRUE 1 #define FALSE 0 static int time_started_flag = FALSE; static struct timeb_simple time_offset = { 0, 0 }; static pthread_t pt_thread_pid; static int pt_thread_created = FALSE; /* * note that this is static data -- we only need one copy */ typedef struct { int id; int resolution; PtCallback * callback; void * userData; } pt_callback_parameters; /** * Holds an ID to a callback function. */ static int pt_callback_proc_id = 0; /** * The ftime() function, which returns he current tim in seconds and * milliseconds since the Epoch, is deprecated in favor or clock_gettime(2). */ void Pt_ftime (struct timeb_simple * tp) { struct timespec temptime; int rc = clock_gettime(CLOCK_REALTIME_COARSE, &temptime); if (rc == 0) { tp->time = temptime.tv_sec; tp->millitm = temptime.tv_nsec / 1000000; } else { tp->time = 0; tp->millitm = 0; } } /** * To kill a process, just increment the pt_callback_proc_id. * * printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, parameters->id); */ static void * Pt_CallbackProc (void * p) { pt_callback_parameters * parameters = (pt_callback_parameters *) p; int mytime = 1; if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20); while (pt_callback_proc_id == parameters->id) { /* wait for a multiple of resolution ms */ struct timeval timeout; int delay = mytime++ * parameters->resolution - Pt_Time(); if (delay < 0) delay = 0; timeout.tv_sec = 0; timeout.tv_usec = delay * 1000; select(0, NULL, NULL, NULL, &timeout); (*(parameters->callback))(Pt_Time(), parameters->userData); } return NULL; } /** * */ PtError Pt_Start (int resolution, PtCallback * callback, void * userData) { if (time_started_flag) return ptNoError; /* * Need this set before process runs. */ Pt_ftime(&time_offset); // ftime(&time_offset); if (callback) { int res; pt_callback_parameters * lparms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters)); if (! lparms) return ptInsufficientMemory; lparms->id = pt_callback_proc_id; lparms->resolution = resolution; lparms->callback = callback; lparms->userData = userData; res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, lparms); if (res != 0) return ptHostError; pt_thread_created = TRUE; } time_started_flag = TRUE; return ptNoError; } PtError Pt_Stop (void) { ++pt_callback_proc_id; if (pt_thread_created) { pthread_join(pt_thread_pid, NULL); pt_thread_created = FALSE; } time_started_flag = FALSE; return ptNoError; } int Pt_Started (void) { return time_started_flag; } PtTimestamp Pt_Time (void) { long seconds, milliseconds; struct timeb_simple now; Pt_ftime(&now); // ftime(&now); seconds = now.time - time_offset.time; milliseconds = now.millitm - time_offset.millitm; return seconds * 1000 + milliseconds; } void Pt_Sleep (int32_t duration) { usleep(duration * 1000); } /* * ptlinux.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/src/ptmacosx_cf.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file ptmacosx_cf.c * * Portable timer implementation for Mac OS X. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2018-05-14 * \updates 2024-01-05 * \license GNU GPLv2 or above */ #include #include #include #include #import #import #import #import #include "porttime.h" #define THREAD_IMPORTANCE 30 #define LONG_TIME 1000000000.0 static int time_started_flag = FALSE; static CFAbsoluteTime startTime = 0.0; static CFRunLoopRef timerRunLoop; typedef struct { int resolution; PtCallback * callback; void * userData; } PtThreadParams; void Pt_CFTimerCallback (CFRunLoopTimerRef timer, void * info) { PtThreadParams *params = (PtThreadParams*)info; (*params->callback)(Pt_Time(), params->userData); } static void * Pt_Thread (void * p) { CFTimeInterval timerInterval; CFRunLoopTimerContext timerContext; CFRunLoopTimerRef timer; PtThreadParams *params = (PtThreadParams*)p; /* raise the thread's priority */ kern_return_t error; thread_extended_policy_data_t extendedPolicy; thread_precedence_policy_data_t precedencePolicy; extendedPolicy.timeshare = 0; error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, (thread_policy_t)&extendedPolicy, THREAD_EXTENDED_POLICY_COUNT); if (error != KERN_SUCCESS) { mach_error("Couldn't set thread timeshare policy", error); } precedencePolicy.importance = THREAD_IMPORTANCE; error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, (thread_policy_t)&precedencePolicy, THREAD_PRECEDENCE_POLICY_COUNT); if (error != KERN_SUCCESS) { mach_error("Couldn't set thread precedence policy", error); } /* set up the timer context */ timerContext.version = 0; timerContext.info = params; timerContext.retain = NULL; timerContext.release = NULL; timerContext.copyDescription = NULL; /* create a new timer */ timerInterval = (double)params->resolution / 1000.0; timer = CFRunLoopTimerCreate(NULL, startTime + timerInterval, timerInterval, 0, 0, Pt_CFTimerCallback, &timerContext); timerRunLoop = CFRunLoopGetCurrent(); CFRunLoopAddTimer(timerRunLoop, timer, CFSTR("PtTimeMode")); /* run until we're told to stop by Pt_Stop() */ CFRunLoopRunInMode(CFSTR("PtTimeMode"), LONG_TIME, false); CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, CFSTR("PtTimeMode")); CFRelease(timer); free(params); return NULL; } PtError Pt_Start (int resolution, PtCallback * callback, void * userData) { printf("Pt_Start() called\n"); if (time_started_flag) /* make sure we're not already playing */ { return ptAlreadyStarted; } else { PtThreadParams * params = (PtThreadParams*) malloc ( sizeof(PtThreadParams) ); if (params != NULL) { pthread_t pthread_id; startTime = CFAbsoluteTimeGetCurrent(); if (callback) { params->resolution = resolution; params->callback = callback; params->userData = userData; pthread_create(&pthread_id, NULL, Pt_Thread, params); } time_started_flag = TRUE; free(params); } return ptNoError; } } PtError Pt_Stop (void) { printf("Pt_Stop called\n"); CFRunLoopStop(timerRunLoop); time_started_flag = FALSE; return ptNoError; } int Pt_Started (void) { return time_started_flag; } PtTimestamp Pt_Time (void) { CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); return (PtTimestamp)((now - startTime) * 1000.0); } void Pt_Sleep (int32_t duration) { usleep(duration * 1000); } /* * ptmacosx_cf.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/src/ptmacosx_mach.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file ptmacosx_mach.c * * Another ortable timer implementation for Mac OS X. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2018-05-14 * \updates 2024-01-05 * \license GNU GPLv2 or above */ #include #include #include #import #import #import #import #include #include "porttime.h" #include "sys/time.h" #include "pthread.h" #define NSEC_PER_MSEC 1000000 #define THREAD_IMPORTANCE 30 static int time_started_flag = FALSE; static UInt64 start_time; static pthread_t pt_thread_pid; /* note that this is static data -- we only need one copy */ typedef struct { int id; int resolution; PtCallback * callback; void * userData; } pt_callback_parameters; static int pt_callback_proc_id = 0; static void * Pt_CallbackProc (void * p) { pt_callback_parameters * parameters = (pt_callback_parameters *) p; int mytime = 1; kern_return_t error; thread_extended_policy_data_t extendedPolicy; thread_precedence_policy_data_t precedencePolicy; extendedPolicy.timeshare = 0; error = thread_policy_set ( mach_thread_self(), THREAD_EXTENDED_POLICY, (thread_policy_t) &extendedPolicy, THREAD_EXTENDED_POLICY_COUNT ); if (error != KERN_SUCCESS) { mach_error("Couldn't set thread timeshare policy", error); } precedencePolicy.importance = THREAD_IMPORTANCE; error = thread_policy_set ( mach_thread_self(), THREAD_PRECEDENCE_POLICY, (thread_policy_t)&precedencePolicy, THREAD_PRECEDENCE_POLICY_COUNT ); if (error != KERN_SUCCESS) { mach_error("Couldn't set thread precedence policy", error); } /* to kill a process, just increment the pt_callback_proc_id */ while (pt_callback_proc_id == parameters->id) { /* wait for a multiple of resolution ms */ UInt64 wait_time; int delay = mytime++ * parameters->resolution - Pt_Time(); PtTimestamp timestamp; if (delay < 0) delay = 0; wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC); wait_time += AudioGetCurrentHostTime(); error = mach_wait_until(wait_time); timestamp = Pt_Time(); (*(parameters->callback))(timestamp, parameters->userData); } free(parameters); return NULL; } PtError Pt_Start (int resolution, PtCallback * callback, void * userData) { if (time_started_flag) return ptAlreadyStarted; start_time = AudioGetCurrentHostTime(); if (callback) { int res; pt_callback_parameters * parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters)); if (! parms) return ptInsufficientMemory; parms->id = pt_callback_proc_id; parms->resolution = resolution; parms->callback = callback; parms->userData = userData; res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms); if (res != 0) return ptHostError; } time_started_flag = TRUE; return ptNoError; } PtError Pt_Stop (void) { /* printf("Pt_Stop called\n"); */ ++pt_callback_proc_id; pthread_join(pt_thread_pid, NULL); time_started_flag = FALSE; return ptNoError; } int Pt_Started (void) { return time_started_flag; } PtTimestamp Pt_Time (void) { UInt64 clock_time, nsec_time; clock_time = AudioGetCurrentHostTime() - start_time; nsec_time = AudioConvertHostTimeToNanos(clock_time); return (PtTimestamp)(nsec_time / NSEC_PER_MSEC); } void Pt_Sleep (int32_t duration) { usleep(duration * 1000); } /* * ptmacosx_mach.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_portmidi/src/ptwinmm.c ================================================ /* * This file is part of seq66, adapted from the PortMIDI project. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file ptwinmm.c * * Portable timer implementation for Win32. * * \library seq66 application * \author PortMIDI team; modifications by Chris Ahlstrom * \date 2017-08-21 * \updates 2024-01-05 * \license GNU GPLv2 or above */ #include #include #include "porttime.h" TIMECAPS caps; static long time_offset = 0; static int time_started_flag = FALSE; static long time_resolution; static MMRESULT timer_id; static PtCallback * time_callback; void CALLBACK winmm_time_callback ( UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2 ) { (*time_callback)(Pt_Time(), (void *) dwUser); } PMEXPORT PtError Pt_Start (int resolution, PtCallback * callback, void * userData) { if (time_started_flag) return ptAlreadyStarted; timeBeginPeriod(resolution); time_resolution = resolution; time_offset = timeGetTime(); time_started_flag = TRUE; time_callback = callback; if (callback) { timer_id = timeSetEvent ( resolution, 1, winmm_time_callback, (DWORD_PTR) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION ); if (! timer_id) return ptHostError; } return ptNoError; } PMEXPORT PtError Pt_Stop (void) { if (! time_started_flag) return ptAlreadyStopped; if (time_callback && timer_id) { timeKillEvent(timer_id); time_callback = NULL; timer_id = 0; } time_started_flag = FALSE; timeEndPeriod(time_resolution); return ptNoError; } PMEXPORT int Pt_Started (void) { return time_started_flag; } PMEXPORT PtTimestamp Pt_Time (void) { return timeGetTime() - time_offset; } PMEXPORT void Pt_Sleep (int32_t duration) { Sleep(duration); } /* * ptwinmm.c * * vim: sw=4 ts=4 wm=4 et ft=c */ ================================================ FILE: seq_qt5/Makefile.am ================================================ #***************************************************************************** # Makefile.am (libseq_qt5) #----------------------------------------------------------------------------- ## # \file Makefile.am # \library libseq_qt5 # \author Chris Ahlstrom # \date 2017-09-05 # \updates 2018-03-11 # \version $Revision$ # \license $MIDICVT_SUITE_GPL_LICENSE$ # # This file is a makefile for the seq_qt5 library project. This # makefile provides the skeleton needed to build the seq_qt5 project # directory using GNU autotools. # #----------------------------------------------------------------------------- #***************************************************************************** # Packing targets. #----------------------------------------------------------------------------- # # Always use Automake in foreign mode (adding foreign to # AUTOMAKE_OPTIONS in Makefile.am). Otherwise, it requires too many # boilerplate files from the GNU coding standards that aren't useful to # us. # #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #***************************************************************************** # EXTRA_DIST #----------------------------------------------------------------------------- EXTRA_DIST = #***************************************************************************** # SUBDIRS #----------------------------------------------------------------------------- SUBDIRS = include src forms #***************************************************************************** # DIST_SUBDIRS #----------------------------------------------------------------------------- # # DIST_SUBDIRS is used by targets that need to recurse into /all/ # directories, even those which have been conditionally left out of the # build. # # Precisely, DIST_SUBDIRS is used by: # # - make dist # - make distclean # - make maintainer-clean. # # All other recursive targets use SUBDIRS. # #----------------------------------------------------------------------------- DIST_SUBDIRS = $(SUBDIRS) #***************************************************************************** # all-local #----------------------------------------------------------------------------- all-local: @echo "Top source-directory 'top_srcdir' is $(top_srcdir)" @echo "* * * * * All libseq_qt5 build items completed * * * * *" #***************************************************************************** # Makefile.am (libseq_qt5) #----------------------------------------------------------------------------- # vim: ts=3 sw=3 noet ft=automake #----------------------------------------------------------------------------- ================================================ FILE: seq_qt5/README ================================================ README for Seq66 Qt Support Chris Ahlstrom 2017-09-06 to 2017-09-06 No need to install QtCreator and multiple versions of Qt dev-tools. qtdeclarative5-dev qtbase5-dev-tools qtchooser And be sure to prefix header-file names with their sub-directory: #include // not just "". # vim: sw=4 ts=4 wm=4 et ft=sh ================================================ FILE: seq_qt5/forms/Makefile.am ================================================ #****************************************************************************** # Makefile.am (libseq_qt5) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library libseq_qt5 library # \author Chris Ahlstrom # \date 2018-03-09 # \update 2025-01-25 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the libseq_qt5 C++ # library. # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #****************************************************************************** # CLEANFILES #------------------------------------------------------------------------------ CLEANFILES = *.ui.h MOSTLYCLEANFILES = *~ #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = *.ui #****************************************************************************** # Items from configure.ac #------------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ GIT_VERSION = @GIT_VERSION@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ prefix = @prefix@ includedir = @seq66includedir@ libdir = @seq66libdir@ datadir = @datadir@ datarootdir = @datarootdir@ seq66includedir = @seq66includedir@ seq66libdir = @seq66libdir@ #****************************************************************************** # Source files #---------------------------------------------------------------------------- UI_FILES = \ qlfoframe.ui \ qliveframeex.ui \ qmutemaster.ui \ qperfeditframe64.ui \ qsabout.ui \ qsappinfo.ui \ qsbuildinfo.ui \ qseditoptions.ui \ qseqeditex.ui \ qseqeditframe64.ui \ qseqeventframe.ui \ qsetmaster.ui \ qslivegrid.ui \ qslogview.ui \ qsmainwnd.ui UI_H_FILES = \ qlfoframe.ui.h \ qliveframeex.ui.h \ qmutemaster.ui.h \ qperfeditframe64.ui.h \ qsabout.ui.h \ qsappinfo.ui.h \ qsbuildinfo.ui.h \ qseditoptions.ui.h \ qseqeditex.ui.h \ qseqeditframe64.ui.h \ qseqeventframe.ui.h \ qsetmaster.ui.h \ qslivegrid.ui.h \ qslogview.ui.h \ qsmainwnd.ui.h #****************************************************************************** # uninstall-hook #------------------------------------------------------------------------------ uninstall-hook: @echo "Note: you may want to remove $(pkgincludedir) manually" #****************************************************************************** # Makefile.am (libseq_qt5) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: seq_qt5/forms/qlfoframe.ui ================================================ qlfoframe 0 0 663 360 0 0 LFO 10 10 601 31 11 true LFO Data Editor true Pattern #xxxx Use the measure length, rather than pattern length, as the length of the LFO period. Use Measure (vs Length) If checked, multiply existing event values by the waveform, rather than forcing the events to match the waveform. Multiply 350 40 291 251 true Wave Type 10 30 251 211 1 12 false &None false Sine false Sawtooth false Re&verse Sawtooth false Triangle true false E&xponential Rise true false Exponential Fall false DC Only 350 293 296 34 3 0 28 Resets the pattern events to the original set or to the last "locked" set of events. &Reset 0 28 Makes the current, modified events official and non-resettable. Further modifications can be applied. &Lock 0 28 Close the LFO frame. If the pattern is modified, raise the modify flag for the MIDI tune. &Close 18 53 323 281 6 DC Offset Qt::AlignCenter 0 0 16 16777215 DC offset to add to modulating waveform. Qt::Vertical QSlider::NoTicks 72 0 100.0 Qt::AlignCenter Depth Qt::AlignCenter 0 0 16 16777215 Depth of modulation. Qt::Vertical QSlider::NoTicks 72 0 100.0 Qt::AlignCenter Periods Qt::AlignCenter 0 0 16 16777215 Number of periods per pattern. Subject to anti-aliasing. Qt::Vertical QSlider::NoTicks 72 0 100.0 Qt::AlignCenter Phase Qt::AlignCenter 0 0 16 16777215 Phase of the LFO, 0 to 1 is 0 to 360 degrees. Qt::Vertical false false QSlider::NoTicks 72 0 360.0 Qt::AlignCenter m_measures_check_box m_range_slider m_range_text m_speed_slider m_speed_text m_phase_slider m_phase_text m_radio_wave_none m_radio_wave_sine m_radio_wave_saw m_radio_wave_revsaw m_radio_wave_triangle m_radio_wave_exp m_radio_wave_revexp m_button_reset m_button_close ================================================ FILE: seq_qt5/forms/qliveframeex.ui ================================================ qliveframeex 0 0 774 440 680 280 11 Live Grid ================================================ FILE: seq_qt5/forms/qmutemaster.ui ================================================ qmutemaster 0 0 848 439 Mute Master Mute Master: viewing/editing of mute groups. The Mute Master QFrame::Shape::NoFrame QFrame::Shadow::Plain 9 9 844 391 8 QLayout::SizeConstraint::SetMinimumSize 120 22 120 28 11 true Mute-Groups 16777215 28 11 true Click group row to show its layout Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter QLayout::SizeConstraint::SetMinAndMaxSize 2 136 22 152 22 11 true Group Patterns QLayout::SizeConstraint::SetMinimumSize 2 QLayout::SizeConstraint::SetMinimumSize false 0 0 116 28 120 28 11 true Mutes File Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter false 0 0 200 28 200 28 For reference only. Go to Edit / Preferences / Session to edit this name. Qt::ScrollBarPolicy::ScrollBarAlwaysOff false 0 28 16777215 28 Indicates if the 'mutes' file is active. See Edit / Preferences / Session / .mutes. Active 11 true Read/Write and Format 11 Mute-groups are saved in the MIDI file. To MIDI 11 Mute-groups are saved to the "mutes" file at exit. To Mutes 80 16777215 11 Mute statuses are saved as hexadecimal (e.g. 0xFF). Useful if set-size is larger than 4 x 8. He&x 80 16777215 11 Mute statuses are saved as stanzas of 1's and 0's. Default setting. &Binary 11 If checked, read mute-groups from the MIDI file. Takes precedence over the 'mutes' file. From MIDI 11 If checked, the mute-groups are read from a common 'mutes' file. Superceded by "From MIDI". From M&utes 160 16777215 11 If checked, toggling a mute-group changes only patterns specified in that group; patterns armed by the user stay on. Legacy behavior toggles mute-group and armed patterns. Toggle &Only Active 170 16777215 11 If there are no non-zero mute-group values, do not save them to the MIDI file. Strip &If No Mutes 12 true 400 280 400 340 11 false 32 4 true false 16 18 true 96 16777215 11 When active, the mute-group button here can toggle the mute-groups. &Triggers 96 16777215 11 Removes all mute-groups from the mute-group setup. &Zero Out false 16 16 Reserved for expansion. true 72 0 96 16777215 11 Saves mute groups to a file in the session configuration. Save &File 96 16777215 11 Present a dialog to load a 'mutes' file. &Load File false 0 0 16 16 A star if mute-group modifications affecting the mutes file are present. - m_group_table m_check_from_midi m_radio_binary m_check_to_midi m_check_from_mutes m_radio_hex m_check_to_mutes m_check_toggle_active m_check_strip_empty m_button_trigger m_button_clear_all m_button_save m_button_load m_button_reserved m_button_set_mutes m_check_mutes_active m_mute_basename ================================================ FILE: seq_qt5/forms/qpatternfix.ui ================================================ qpatternfix 0 0 688 476 0 0 Frame 12 8 631 38 11 true Pattern-Fix Editor Qt::Orientation::Horizontal 40 20 100 0 true Pattern No. Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter true 56 0 56 28 true QFrame::Shape::Box QFrame::Shadow::Sunken 2 0 108 0 64 16777215 true Max. Tick Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 96 0 64 28 true QFrame::Shape::Box QFrame::Shadow::Sunken 2 8 56 261 151 true Length Change false 9 33 259 104 22 false If an integer, the number of measures is changed; events are compressed or expanded as needed. If a fraction (e.g. 3/4), the pattern is reduced to one measure with the given time signature. &Measures/Time Sig false Shrink/expand the pattern by a factor, accounting for the time of the last event. Scale &Factor false No change in pattern length. &None false false 530 56 141 221 true true Applied Effects false false 8 35 121 181 true false Alteration false true false Events will be shifted. Shift true false Events will be reversed. Reverse false true false Events will be compressed. Shrin&k false true false Events will expand. E&xpand true false The time signature will change. Time Sig 8 220 221 201 true Other Fixes 12 24 201 161 false Move events left to remove delays. &Align left false Align right false Reverse the order of all events, flipping the measures. Reverse measures false Reverse notes, preserving relative location. Reverse in place false When scaling, don't change note length. &Preserve note length 250 380 381 38 0 28 Apply the settings. &Set 0 28 Go back to the original pattern. &Reset 28 0 Close this dialog. &Close 270 56 258 321 0 0 true Alteration false Remap notes in the opposite direction of "Note-map". Rev. Note-map false No alteration operations will be done. None true 48 16777215 If not blank, replaces the snap/2 value. false Do partial quantization. &Tight Q ticks false Fully quantization to the snap value. F&ull Q ticks 48 16777215 Open the file dialog to select a .notemap/.drums file. ... false Jitter the timestamps of the events. Time/tick jitter false Remap notes as per the loaded 'drum' file. Note-map false Randomize event magnitude or note velocity. Random amplitude false 48 16777215 true 48 16777215 Plus/minus range of time jitter in ticks. true 48 16777215 If not blank, replaces the snap value. true 48 16777215 Plus/minus range of magnitude change in MIDI units. false Random pitch 48 16777215 btn_change_none line_edit_none btn_change_pick line_edit_measures btn_change_scale line_edit_scale btn_align_left btn_set btn_reset btn_close btn_effect_expand btn_effect_shrink btn_effect_shift ================================================ FILE: seq_qt5/forms/qperfeditex.ui ================================================ qperfeditex 0 0 861 572 Song Editor ================================================ FILE: seq_qt5/forms/qperfeditframe64.ui ================================================ qperfeditframe64 0 0 859 387 0 0 600 320 Frame QFrame::Shape::NoFrame QFrame::Shadow::Plain 2 2 2 0 0 0 148 218 148 16777215 QFrame::Shape::Box QFrame::Shadow::Raised true 0 0 16 307 0 0 0 0 520 240 true 0 0 691 305 0 0 580 240 1 20 0 20 16777215 Show triggers at half height. [ v ] - 20 0 20 16777215 Reset triggers to normal height. [ 0 ] 0 20 0 20 16777215 Double the height of triggers. [ V ] + false 84 26 84 26 Time-line and L and R markers. false 0 0 520 26 16777215 26 true 0 0 670 16 0 0 false 15 24 20 24 false 0 28 28 80 28 80 28 Qt::FocusPolicy::NoFocus Undo previous operation(s). [ Ctrl-Z ] 28 28 80 28 80 28 Qt::FocusPolicy::NoFocus Redo last operations(s). [ Shift-Ctrl-Z ] 28 28 80 28 80 28 Set piano roll to follow progress bar. 28 28 80 28 80 28 Qt::FocusPolicy::NoFocus Zoom out horizontally in piano roll. [ z ] The 0 key resets zoom. 28 28 80 28 Qt::FocusPolicy::NoFocus Zoom in horizontally in piano roll. [ Z ] The 0 key resets zoom. 36 28 80 28 Toggles painting mode. Or hold the right mouse button. [ p to enter, x to leave ] Enter true 28 28 80 28 true Set "song record" snap. An added trigger snaps to the length of the pattern or snap setting. Growth occurs at snap length. Snap true 70 28 80 28 Qt::FocusPolicy::NoFocus Selects movement and sizing snap value. "L" = full length. "1/N" is 1/Nth of a measure. Affects L/R markers. Use L only for short patterns. -1 28 28 80 28 Resets transpose to 0 in the performance. Trans 96 28 128 28 The number of semitones to transpose the performance, for transposable patterns. Transposition is done during playback. 36 28 80 28 Qt::FocusPolicy::NoFocus Set looping between L/R markers. Loop 33 12 true 0 0 Qt::Orientation::Vertical 36 28 80 28 Qt::FocusPolicy::NoFocus Collapse pattern between L/R markers. 33 12 36 28 80 28 Qt::FocusPolicy::NoFocus Expand space between L/R markers. 33 12 36 28 80 28 Qt::FocusPolicy::NoFocus Expand and copy between L/R markers. 33 12 40 28 80 28 Expand piano roll to the right for more editing room. --> 33 22 28 28 80 28 true Resets Trigger Transpose to 0. TT 66 28 80 28 Transposition value for Trigger Transpose. Range: -60 to +60 (5 octaves each way). Apply it to a trigger via Shift-Left click. To reset, set to 0 and apply it. 86 28 128 28 true Shows duration of the song based on the longest track or Song layout. Click to toggle between H:M:S and B:B:T format. 20:20.20 false false Qt::Orientation::Horizontal 40 20 0 0 2 2 qscrollmaster QScrollArea
qscrollmaster.h
1
================================================ FILE: seq_qt5/forms/qplaylistframe.ui ================================================ qplaylistframe 0 0 825 423 Frame 6 0 781 30 11 true Playlist File false 0 24 16777215 24 Full path to the play-list file. Read-only. Click "Load List" to select a playlist file. None true 6 30 824 309 2 24 24 24 24 Allows the user to find a directory and select it, instead of typing the full path name in the edit control to the left. ... 0 24 16777215 24 11 true MIDI #: 0 24 340 24 11 The base directory for the play-list selected in the table below. Use the "..." to the right to select the directory. The directory is not used in the MIDI file contains a full path to the file. None false true 0 24 16777215 24 11 true Song Dir: 74 24 16777215 24 11 Provides the ordering and control number for this play-list, when added. Ranges from 0 to 127. 127 127 24 24 32 24 11 000 11 true MIDI #: 300 16777215 11 true Song Selection 400 16777215 11 true Playlist File Selection 24 24 32 24 11 000 true 228 24 228 24 11 Name of the currently-selected play-list section. Edit to enable "Add List" and "Modify List". None true true 0 24 300 24 11 Directory where the selected song is stored. Use the "Load Song" button to select an existing song. None true true 208 24 208 24 11 Base file-name of the currently selected song. Read-only. Use the Load Song button to modify this entry. None true true 0 24 16777215 24 11 true MIDI Dir: If set to an asterisk, this directory is part of the song's file-name. * 0 24 64 24 11 Provides the ordering and control number for this song, when added. Ranges from 0 to 127. 127 127 24 24 24 24 Look up a .playlist file and load it into the play-list table. ... 24 24 24 24 Look up a MIDI or WRK file and add it to the song table. ... false 24 24 24 24 false 24 24 24 24 Reserved for expansion false 24 24 24 24 2 320 16777215 11 Shows existing play-lists read from the play-list file. Qt::ScrollBarPolicy::ScrollBarAlwaysOff QAbstractScrollArea::SizeAdjustPolicy::AdjustToContentsOnFirstShow QAbstractItemView::EditTrigger::NoEditTriggers true 16 2 true 18 21 false 12 true 0 0 76 25 76 25 11 Opens a dialog to set a directory and play-list file. Create File false 76 25 76 25 11 Creates a new play-list file. Add List false 76 25 76 25 11 Update the current list with the 3 editable fields above. Modify 76 25 76 25 11 To do: Removes the selected playlist from this play-list set. Delete false 76 25 76 25 11 Saves the current state of the play-lists file, including song information. Save Lists 300 0 310 16777215 11 Shows MIDI tunes in the currently-selected play-list. Qt::ScrollBarPolicy::ScrollBarAlwaysOff QAbstractScrollArea::SizeAdjustPolicy::AdjustToContentsOnFirstShow QAbstractItemView::EditTrigger::NoEditTriggers true 16 2 true 18 21 false 12 true 76 25 76 25 11 Opens a file dialog to select a MIDI song to add to the current play-list; the Directory and Current values above are updated automatically. In Non Session Manager mode, the file is imported. Load false 76 25 76 25 11 Adds the song information specified above to the current play-list section; it is easier to use "Load Song". Add Song false 76 25 76 25 11 Modifies the information about the currently-selected song in the 3 entry fields above. Modify true 76 25 76 25 11 Removes the current song from the current play-list; the song itself is not deleted. Delete 76 32 76 40 11 If checked, the loaded play-list file is active. The lists and songs will be loaded when the arrow keys are pressed or the configured MIDI control comes in. Lists active 8 344 617 69 0 24 16777215 24 11 true MIDI Base Directory false 0 24 16777215 24 11 Base directory holding all files in all playlists. Read-only. None true 0 24 16777215 24 11 true Current Song Path false 0 24 16777215 24 11 Full path to the currently-selected song. Read-only. None true 634 344 134 83 11 true Auto: 11 If checked, moving to the next song automatically arms (unmutes) all patterns in the song, if there is no Song layout. Arm 11 If checked, the next selected song starts playing automatically. (Perhaps it should also load the next song and keep going!) Play 11 If checked, auto-arm and auto-play are also enabled, and once Play is pressed, the whole play-list is played without a break. NOT YET ENABLED. Advance ================================================ FILE: seq_qt5/forms/qsabout.ui ================================================ qsabout 0 0 475 362 About Seq66 18 true Seq66 [qseq66] Qt::AlignmentFlag::AlignCenter Qt::Orientation::Horizontal 0 32 16777215 42 10 true (replaced at run-time) Qt::AlignmentFlag::AlignCenter <html><head/><body><p align="center">Loop-based MIDI sequencer<br>Licensed under the GNU General Public License v2/v3</p></body></html> Qt::AlignmentFlag::AlignCenter true <a href="https://github.com/ahlstromcj/seq66"><span style=" text-decoration: underline; color:#0000ff;">GitHub/ahlstromcj/seq66</span></a> Qt::AlignmentFlag::AlignCenter <a href="mailto:ahlstromcj@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">ahlstromcj@gmail.com</span></a> Qt::AlignmentFlag::AlignCenter false false false false false 16777215 16777215 <html><head/><body><p align="center">Based on Seq24, Seq32, Sequencer64, and Kepler34 </p></body></html> Qt::AlignmentFlag::AlignCenter true <a href="http://filter24.org/seq24"><span style=" text-decoration: underline; color:#0000ff;">Filter24.org/seq24</span></a> Qt::AlignmentFlag::AlignCenter <a href="https://github.com/Stazed/seq32"><span style=" text-decoration: underline; color:#0000ff;">GitHub/Stazed/Seq32</span></a> Qt::AlignmentFlag::AlignCenter <a href="https://github.com/oli-kester/kepler34"><span style=" text-decoration: underline; color:#0000ff;">GitHub/oli-kester/kepler34</span></a> Qt::AlignmentFlag::AlignCenter ================================================ FILE: seq_qt5/forms/qsappinfo.ui ================================================ qsappinfo 0 0 763 507 Keystroke Summaries 11 true Keystroke Tables Qt::AlignmentFlag::AlignCenter Active panel <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } </style></head><body style=" font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">(Filled at runtime)</p></body></html> 3 110 0 &Common 110 0 &Automation 110 0 &Pattern Roll 110 0 &Song Roll 110 0 &Hot-Keys 110 0 &Mute Keys false 110 0 false 110 0 Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Ok textBrowser buttonCommonKeys buttonAutomation buttonSeqroll buttonPerfroll buttonHotKeys buttonBox accepted() qsappinfo accept() 248 254 157 274 buttonBox rejected() qsappinfo reject() 316 260 286 274 ================================================ FILE: seq_qt5/forms/qsbuildinfo.ui ================================================ qsbuildinfo 0 0 503 489 Build Information 204 440 91 32 Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Ok 28 12 57 16 11 true Seq66 Qt::AlignmentFlag::AlignCenter 304 8 77 20 11 true Version Qt::AlignmentFlag::AlignCenter 30 40 441 381 true Build Information buttonBox accepted() qsbuildinfo accept() 248 254 157 274 buttonBox rejected() qsbuildinfo reject() 316 260 286 274 ================================================ FILE: seq_qt5/forms/qseditoptions.ui ================================================ qseditoptions 0 0 750 670 11 Preferences 580 16777215 QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::Reset false true 11 true 4 A list of MIDI output ports and clock settings. MIDI &Clock 10 422 601 71 11 false true 20 10 141 17 11 true Meta Events 11 30 571 31 true 0 28 Change track to hold tempo. 4 true false 0 0 28 140 16777215 Saves the tempo track number to the MIDI file. Set 11 true Tempo Track 11 true BPM Precision Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 0 28 64 16777215 30 520 181 17 11 true Client Name:ID/UUID false 220 510 185 28 Holds the MIDI client ID, as applicable. true 0 10 10 240 17 240 0 11 true Ports and Clocking 10 40 701 241 11 false QFrame::Shape::NoFrame 2 1 Qt::ScrollBarPolicy::ScrollBarAsNeeded true 0 0 701 241 420 12 121 17 11 true MIDI Clock 28 370 573 45 11 true MIDI Ctrl Out (Display) Bus 30 300 569 70 11 true Sets the position after which MIDI clocking begins. Clock Start Modulo (ticks) 82 28 1 1024 64 true 11 false If checked, the output port map was active. MIDI I/O Port Maps 11 true Buss Override 0 28 80 32 Saves maps of I/O port numbers and names to the 'rc' file. Each port number can be found by name-lookup. Recreate 0 32 Clear 10 290 601 131 10 496 601 51 groupBoxMetaEvents groupBox_2 groupBox_10 label lineEditClientId portsClocksLabel clocksScrollArea label_2 horizontalLayoutWidget_2 layoutWidgetCtrlOut Provides a table to select the desired MIDI input port. MIDI &Input 10 370 611 161 11 false false false 8 10 181 17 11 true Input/Output Options 20 38 571 129 Use virtual (manual) I/O ports If checked, incoming data routes to the pattern slot by channel number (0 to 15). Record into patterns by bus (overrides channel) 48 16777215 8 2 true false 96 16777215 Outputs Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 48 16777215 4 2 Auto-enable virtual ports 96 16777215 Inputs Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter If checked, incoming data routes to the pattern slot by bus/port number. Overrides input by channel. Record into patterns by channel 16 8 129 17 11 true Input Buses 6 32 691 251 11 false QFrame::Shape::NoFrame 2 1 Qt::ScrollBarPolicy::ScrollBarAsNeeded true 0 0 691 251 32 316 585 49 11 true MIDI Ctrl In (Automation) Bus 10 299 611 81 groupBox_3 groupBoxInputOptions inputBussesLabel inputsScrollArea horizontalLayoutWidget Provides some minor tweaks of the user-interface. Edit the 'usr' file for far more control over settings. &Display 10 220 589 281 11 false false 8 4 570 275 0 24 16777215 24 rc: [Seq66] port-naming = long Long port/buss names * false 0 24 16777215 24 Shows the status of rc / [Seq66] / verbose. Always set to false at exit. Verbose console output 0 24 16777215 24 rc: [Seq66] port-naming = pair Client:port buss names * 0 24 16777215 24 usr: [user-interface-settings] global-seq-feature Global background/scale/key 0 24 16777215 24 rc: [interaction-method] double-click-edit Double click for pattern editor 0 24 16777215 24 Enable to suppress error prompts at startup. Useful if the prompts are going to be ignored anyway. Suppress startup error messages 0 24 16777215 24 usr: [user-interface-settings] swap-coordinates Swap grid coordinates (be aware!) usr: [user-interface-settings] dark-theme Applies a couple of color tweaks, such as using inverse font color for slots. Tweak color for dark theme usr: [user-interface-settings] progress-box-elliptical If set, the grid slot progress boxes are ellipses. Elliptical progess box 0 24 16777215 24 rc: [recent-files] full-paths Show full path of recent files 0 24 16777215 24 Load most-recent file at startup. Works only if there is no active playlist. rc: [recent-files] load-most-recent Load most-recent file (startup) usr: [user-interface-settings] follow-progress Sets the default setting for the follow-progress button in the piano rolls. Follow progress by default 0 24 16777215 24 usr: [user-interface-settings] progress-bar-thick Bold grid slot font/box 11 true UI Boolean Options 0 24 16777215 24 usr: [user-ui-tweaks] lock-main-window Lock main window true 11 false false usr: [user-interface-settings] gridlines-thick Thick grid lines false 11 true false Reserved 16 30 513 183 11 false Shown 0 28 16777215 28 11 false Scales the height of main window at startup. Range: 0.5 to 3.0 1.0 true 0 28 16777215 28 11 false Set the set-size rows. Range: 4 to 8. Default: 4. 4 true 11 false usr: [user-ui-tweaks] progress-box-width, -height Progress boxes (fraction) true 0 28 16777215 28 11 false An abbreviated display of a long pattern to save CPU time. If more notes than this value, a fingerprint is shown. Range: 32 to 128, 0 disables. Default: 32. 32 true 11 false usr: [user-ui-tweaks] fingerprint-size Fingerprint size 0 0 11 false usr: [user-ui-tweaks] key-height Editor key height 0 28 16777215 28 11 false true 0 28 16777215 28 11 false Set the set-size columns. Range: 4 to 12. Default: 8. 8 true 0 28 16777215 28 11 false Heights of the progress box as a fraction of button height. Range: 0.1 to 1.0. Default: 0.4. 0.4 true 0 28 16777215 28 11 false Width of the progress box as a fraction of button width. Range: 0.5 to 1.0. Default: 0.8. 0.8 true x 65 28 16777215 28 11 false Sets default height of piano keys in the pattern editor. Ranges from 6 to 32. The "-0+" buttons or "v0V" keys zoom vertically. 4 24 10 x true 11 false usr: [user-interface-settings] mainwnd-rows, -columns Set size (rows x columns) true x 11 false usr: [user-interface-settings] window-scale, -y Grid scaling & spacing 0 28 16777215 28 11 false Scales the width of the main window at startup. Range: 0.5 to 3.0 1.0 240 10 281 16 10 true false Restart needed for most settings 12 10 161 17 11 true User UI Tweaks 20 500 551 22 10 true false * Ignored if port-mapping (the default) is enabled 10 0 591 211 groupBox_4 groupBoxBooleanOptions layoutWidgetGuiOptions labelRestartNeeded label_6 label_19 Provides settings for JACK transport and JACK MIDI. &JACK 10 10 611 241 8 10 181 17 11 true JACK Transport/MIDI 364 8 201 17 11 true JACK Server Settings 360 29 241 212 11 false Frame rate Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter false 11 false The number of JACK frames per second. 48000 Hz by default. 11 false Period size Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter false 11 false The number of frames in a period. A period is a "cycle", and is the number of frames in a period and the number frames between JACK process callbacks. 11 false N periods Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter false 11 false The number of ALSA periods, 2 by default, 3 recommended for USB. Also called "latency". Does not effect the time between process callbacks. 11 false JACK ticks/beat Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter false 11 false Indicates the number of JACK ticks per beat, normally PPQN/10. 11 false Beats/minute Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter false 11 false JACK's current beats/minute setting. 11 false Period override Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter false 11 false 20 200 151 32 10 true false PortMIDI true 30 40 339 166 11 false Sets Seq66 as the MIDI Master if no master exists. Master conditional 11 false Sets Seq66 as the MIDI Master. Transport master 11 false Turns on Seq66 JACK transport as a client. Enable JACK transport 11 false Requests Seq66 to use the JACK system if it is running. Otherwise, ALSA is used. Native JACK &MIDI (requires a restart) 11 false If checked (the default), then Seq66 automatically connects to the MIDI I/O ports it finds. JACK auto-connect 10 259 349 151 false 8 10 291 17 11 true JACK/ALSA Current Start Mode 168 54 173 49 10 true false (See Play tab for user's setting of Start mode) 31 300 141 64 false 11 false Play MIDI in Live Mode: each pattern plays in an endless loop. &Live mode false 11 false Play MIDI in Song Mode: playback controlled by the layout in the Song Editor. &Song mode 380 270 211 121 JACK Transport Qt::AlignmentFlag::AlignCenter 11 false Co&nnect 11 false &Disconnect Qt::Orientation::Vertical 20 40 370 260 251 151 layoutWidgetJackConnect groupBox r_group_jack_transport r_group_jack_start layoutWidgetJack layoutWidgetJackTransport Provides play options. Minimal at present. &Play 9 9 701 151 0 120 11 true Flags 20 32 441 111 11 false Unfinished notes are turned back on when playback resumes. &Resume Note Ons at start/stop or sequence toggle. 11 false The PPQN of the MIDI file loaded overrides the default PPQN. Otherwise, the file PPQN scales to the default PPQN. Use file's PPQN for pre-existing files (recommended). 11 false Employ the snap interval when recording song triggers. Song-record snap. 11 false Automatically add the starting time signature as an event in the new pattern. Automatically add starting time signature event. 9 220 701 132 0 132 11 true Sets Mode 24 24 500 23 11 false Normal. Only the active set ("play-screen") can be armed. 24 48 500 23 11 false Auto-Arm. When moving to the ne&xt set, it automatically arms. 24 72 500 23 11 false Additive. As each sets is activated, the others keep playing. 24 96 500 23 11 false All Sets. All sets can play at the same time. 9 360 701 111 0 108 16777215 16777215 11 true Song Start Mode 24 24 500 23 11 false Li&ve. Musician controls arming/muting. 24 48 500 23 11 false Song. Song-editor layout controls arming/muting. 24 72 500 23 11 false Auto. Song mode is used if a song-editor layout exists. 20 180 61 21 0 0 120 16777215 11 true PPQN Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 220 185 460 18 460 0 200 16777215 11 false Base Seq66 PPQN for new or converted files. 100 180 100 26 100 16777215 Changes the default PPQN from 192 to another value. Saved in the 'usr' file when Seq66 exits. true 9600 32 QComboBox::InsertPolicy::NoInsert true 10 170 701 41 aakar 11 true groupBox_5 group_flags group_setsmode group_startmode label_ppqn label_ppqn_2 combo_box_ppqn &Metronome 20 20 203 200 11 true Time Signature Qt::AlignmentFlag::AlignCenter 11 false Beat width Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 11 false Beats/bar Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter Qt::Orientation::Vertical 20 40 true 11 true Measures true 11 true Count-In true true Enabled Recording 222 20 325 201 6 8 11 true Output Specs Qt::AlignmentFlag::AlignCenter 11 true Main Note 11 true Sub Notes 11 false Patch Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 11 false Note Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 11 false Velocity Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 11 false Length fraction Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter Qt::Orientation::Vertical 20 40 225 28 Reload Metronome 21 260 521 105 100 28 11 true Metro buss 266 28 268 28 11 true Channel Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 64 28 64 28 100 28 11 true Record buss 266 28 266 28 Qt::Orientation::Horizontal 40 20 100 28 11 true Thru buss 266 28 266 28 11 true Channel Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 64 28 64 28 10 10 571 231 10 250 571 121 groupBox_7 groupBox_6 metroLayoutWidget metroLayoutWidget2 layoutWidgetMetro P&attern 24 10 217 441 11 false 8 40 191 344 13 Armed Record Tighten record Quantize record Note-map record Wrap-around MIDI Thru If checked, apply options only when a new pattern editor is opened. Apply only to new 11 true Record Loop Style Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter 0 28 8 5 250 10 401 131 16 40 259 88 65 28 The amount of time randomization to apply with jitter application. 2 64 16 65 28 Amplitude of randomization for items that range from 0 to 127. Set this before application. 1 20 11 false Jitter 1/ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 11 false in MIDI units 11 false of Snap 11 false Amplitude Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 250 160 401 111 20 40 371 28 11 false Overloads the Esc key to use as a Close key Esc key in piano roll closes external editor 10 10 261 18 11 true Additional 'usr' Options 20 70 371 23 11 false If set, split an SMF 0 file into multiple tracks Automatic conversion of SMF 0 to SMF 1 30 20 191 18 11 true Pattern status options to apply when creating/opening a pattern. Pattern Editor Options 250 20 171 18 11 true Randomization Sessio&n 8 8 301 131 11 false 12 32 285 23 None. Normal Se&q66 operation. 12 64 285 23 NSM. NSM debug mode. 12 96 285 23 JACK. JACK session management. 8 4 105 17 11 true Session 336 10 341 91 10 true false NSM use is automatic if Seq66 runs in an NSM session. The check-mark causes Seq66 to run as if in NSM, for debugging. JACK session management occurs only if checked, even if Seq66 is run by it. 16 152 149 17 11 true UUID/NSM URL 44 176 233 25 20 210 673 231 36 70 96 23 11 false .rc true 36 100 96 23 11 false .usr 36 130 96 23 11 false .mutes 36 160 96 23 11 false .playlist true 376 70 96 23 11 false .ctrl + false 376 100 96 23 11 false .drums + false 8 70 25 23 true true 8 100 25 23 true 8 130 25 23 true 8 160 25 23 true 348 70 25 23 true 348 100 25 23 8 36 57 17 11 true true Active 64 36 65 17 11 true true Save true 140 70 144 28 true 140 100 144 28 true 140 130 144 28 true 140 160 144 28 true 480 70 144 28 true 480 100 144 28 144 36 141 17 11 true true Base Filename 140 190 144 28 Provides the base name of the palette file. This file always resides in the Seq66 configuration directory (~/.config/seq66 or the NSM session directory). Any path added to this name is stripped. 8 190 25 23 false 36 190 96 23 11 false .palette + 344 36 53 17 11 true true Active 400 36 53 17 11 true true Save 482 36 141 17 11 true true Base Filename true 348 130 25 23 false 376 130 96 23 11 false .qss + true 480 130 144 28 342 190 132 28 11 false Useful for storing updates to Seq66's palette layout. &Store Palette 4 4 161 17 11 true Configuration Files 292 72 28 24 ... 292 102 28 24 ... 292 132 28 24 ... 292 162 28 24 ... 292 192 28 24 ... 631 72 28 24 ... 632 102 28 24 ... 632 132 28 24 ... 200 2 481 22 11 true false + Cannot alter data for these files in Seq66. Text-edit them. false 376 158 101 28 11 false .patches + 348 162 21 21 480 160 144 28 632 163 28 24 ... 487 190 132 28 11 false Useful for creating or copying a patches file. Store Patches 330 150 341 41 11 true true * The changes made require a complete exit and re-run. Save your work first. Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 10 450 101 22 Browser Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 20 485 101 22 PDF Viewer Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 140 456 491 28 Specifies an alternate browser executable such as /usr/bin/evince. The complete path is not needed if in one's PATH. 140 486 491 28 Specifies an alternate PDF viewer such as C:/Users/user/AppData/Local/Apps/Evince-2.32.0.145/bin/evince.exe or /usr/bin/zathura. 639 459 28 24 Click to open a file dialog to set the browser. ... 639 490 28 24 Click to open a file dialog to set the PDF viewer. ... 8 139 301 71 false 350 110 141 25 16777215 56 11 false Restarts the whole application, reloading any changed settings. Restart Session groupBoxSaveAtExit groupBox_8 group_session label_session label_nsm_url lineEditNsmUrl label_exit_required label_3 label_24 lineEditBrowser lineEditPdfViewer pushButtonSetBrowser pushButtonSetPdfViewer pushButtonReload spinKeyHeight lineEditUiScaling lineEditUiScalingHeight lineEditSetSize lineEditSetSizeColumns lineEditProgressBox lineEditProgressBoxHeight lineEditFingerprintSize checkBoxVerbose checkBoxLoadMostRecent checkBoxShowFullRecentPaths pushButtonStoreMap pushButtonTempoTrack ioPortsMappedCheck lineEditTempoTrack lineEditClientId clocksScrollArea checkBoxRecordByBuss inputsScrollArea btnJackConnect btnJackDisconnect chkJackConditional chkJackMaster chkJackTransport chkJackNative radio_live_mode radio_song_mode chkNoteResume chkUseFilesPPQN combo_box_ppqn spinBoxClockStartModulo ================================================ FILE: seq_qt5/forms/qseqeditex.ui ================================================ qseqeditex 0 0 889 572 Pattern ================================================ FILE: seq_qt5/forms/qseqeditframe64.ui ================================================ qseqeditframe64 0 0 750 486 0 0 700 458 Pattern QLayout::SetMinimumSize 2 2 2 2 6 1 5 QLayout::SetFixedSize 4 QLayout::SetMinimumSize 0 0 40 64 48 16777215 QFrame::Box QFrame::Raised 1 1 true 0 0 42 134 48 10 48 10 5 true false Toggle display of note tool-tips when hovering. false 0 0 300 64 QFrame::Sunken Qt::ScrollBarAlwaysOn Qt::ScrollBarAlwaysOn QAbstractScrollArea::AdjustToContents true 0 0 673 140 0 0 6 QLayout::SetMinimumSize QLayout::SetMinimumSize 0 0 0 48 28 48 28 Toggles transposability of the pattern. Transp 48 28 48 28 If transposable, apply active note-map to note events. [ c for selected notes ] Map true 48 28 48 28 Toggles the drum mode of event entry. Drum 20 20 48 28 48 28 Resets chord generation to Off. Chord 48 24 48 28 0 0 300 64 16777215 156 QFrame::StyledPanel QFrame::Sunken true 0 0 662 154 true 16 28 16 28 12 true Toggle the showing of the active scale or chord. + true false true 16 28 16 28 16 28 12 true Toggle filtering out off-scale or off-chord notes. - true false 16 28 16 28 Toggle showing some event values in hex (0xnn) notation. x true true 16 28 16 28 Toggle the display of the horizontal grid-line numbers. # true false false 16 28 16 28 true false 0 QLayout::SetDefaultConstraint 0 28 80 28 Undo previous operation(s). [ Ctrl-Z ] U 0 28 80 28 Redo last operation(s). [ Shift-Ctrl-Z ] R 0 28 80 28 false true Quantize the selected notes. [ q r t ] Q Ctrl+Q 0 28 80 28 Shows Tools popup menus. T Ctrl+T 0 28 80 28 Set piano roll to follow progress bar. F Ctrl+F 0 28 80 28 Resets to default drawing snap size. S 23 12 Ctrl+S 68 28 80 28 Selects snap size for spacing drawn events. 36 28 80 28 Sets note length for drawing to the default value, 1/16. Note 30 16 68 28 80 28 Selects the note length for drawing notes. 28 28 80 28 Horizontal zoom level. Resets to default value, 1:2. Z 64 28 80 28 Horizontal zoom in pixels:ticks, the number of ticks in 1 pixel. A lower value zooms in [ z 0 Z ]. 28 28 80 28 Musical key selection. Resets key selection to C. K 50 28 80 28 Selects the musical key. 20 28 80 28 Resets musical scale to Off. S 26 14 0 28 132 28 Musical scale to show on piano roll background. 30 28 80 28 Background sequence popup menu. Seq 26 12 true 0 0 0 28 148 28 Another pattern/sequence to show on piano roll background. Off true Qt::Horizontal 40 20 0 48 28 48 28 Event to show in data panel popup menu. Event 28 28 28 28 Present event-to-show popup menu. Data true 156 28 172 28 Displays name of event being shown. Note On true 40 28 48 28 Activate L/R looping. Loop 33 12 true true 40 28 48 28 Brings up an LFO window to modulate data values. LFO 28 28 64 28 The number of repetitions in Live mode. 0 (default) means unlimited repetitions. A value of 1 is a "one-shot" loop. Values up to 999 are supported by this control. 999 42 28 52 28 When active, the pattern is unmuted. Play 36 14 64 28 84 28 When active, incoming MIDI goes through to output. Thru 60 14 42 28 52 28 Record incoming MIDI. [ o ] Record 36 14 Ctrl+R 42 28 52 28 Quantize-record incoming MIDI. QRecord 36 14 Ctrl+S 100 28 116 28 Recording type for loop patterns: Merge (overdub) events; Overwrite events; Expand pattern length while recording; Oneshot recording of a pattern. QComboBox::AdjustToContents 30 28 48 28 Resets recording velocity to Free (incoming velocity). Vel 72 28 84 28 Selects Free mode (incoming velocity) of recording, or a velocity to force upon input. QComboBox::AdjustToContents 1 0 QLayout::SetFixedSize 0 0 16 24 16 24 Vertical zoom out. [ v ]. - 18 24 18 24 Vertical zoom reset. [ 0 ] 0 16 24 16 24 Vertical zoom in. [ V ] + 0 0 300 28 16777215 28 QFrame::StyledPanel QFrame::Sunken Qt::ScrollBarAlwaysOff Qt::ScrollBarAlwaysOff QAbstractScrollArea::AdjustToContents true 0 0 666 28 0 28 false 20 28 20 28 true 0 QLayout::SetDefaultConstraint 0 0 44 28 64 28 true Pattern/sequence number. QFrame::StyledPanel QFrame::Sunken 1023 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 24 28 200 28 false true Pattern/Sequence name/title. Keep it short. Untitled false 36 28 40 28 Add the current time signature as a meta event at the L marker. 3/4 0 0 20 28 64 28 Resets beats/bar to 4. B 0 0 50 28 80 28 Selection of beats/bar settings. true 4 0 0 20 28 64 28 Resets beat-width to 4. W 0 0 50 28 80 28 Selects beat width (time signature denominator). true 4 0 0 22 28 64 28 Resets pattern length to 1. L 24 20 Ctrl+S 0 0 64 28 80 28 Selects pattern length. It can be overridden by the "Expand" setting. true 1 96 28 120 28 Selects a chord to create when a note is painted. 0 0 20 28 80 28 Resets output MIDI buss number to 0. Buss 30 16 0 0 120 28 220 28 Selects MIDI output buss; stored with the pattern. 0 0 32 28 80 28 Resets output MIDI channel number to 1. C 0 0 56 28 128 28 Selects MIDI output channel; stored with the pattern. Qt::Horizontal 40 20 3 0 0 48 24 48 24 Toggle between selection and note-entry modes in the piano roll. [ p x ] Note true 0 0 300 24 16777215 24 QFrame::StyledPanel QFrame::Sunken true 0 0 670 22 true 16 24 16 24 Show the pattern-fix dialog. false qscrollmaster QScrollArea
qscrollmaster.h
1
qscrollslave QScrollArea
qscrollslave.h
1
================================================ FILE: seq_qt5/forms/qseqeventframe.ui ================================================ qseqeventframe 0 0 900 416 0 0 900 500 Events QFrame::Shape::NoFrame QFrame::Shadow::Plain 3 2 9 17 491 380 0 0 440 380 492 999 Events in the pattern/sequence. Events can be edited only with the controls at the right. QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents false QAbstractItemView::SelectionBehavior::SelectRows Qt::PenStyle::DashLine false 48 7 16 18 510 270 311 69 0 0 2 Qt::ScrollBarPolicy::ScrollBarAlwaysOff (Enter Meta/SysEx event text here) 520 348 261 52 QLayout::SizeConstraint::SetFixedSize 20 2 false 64 24 90 24 Deletes the current event, then inserts a new event at the time shown above, with the data items shown above. Modif&y false 64 24 90 24 Deletes the currently-selected MIDI event. &Delete false 64 24 90 24 Appends the text of events to a file for trouble-shooting. It is in the same directory as the MIDI file, and has a name of the form "filename-pattern-#.text", where '#' is the number of the pattern. D&ump false 64 24 90 24 Saves current changes to the pattern. This step must be done to preserve the changes. Then the file must be saved to make the changes permanent. Tricky, but safer. Store false 64 24 90 24 Inserts a new event at the time shown above, with the data items shown above. &Insert false 64 24 90 24 Deletes all of the events in this pattern. Clear 500 12 331 32 6 QLayout::SizeConstraint::SetMinimumSize 10 36 0 48 16777215 true 1024 Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 0 0 248 24 256 24 Anonymous Pro 12 Loop Name 499 48 331 58 QLayout::SizeConstraint::SetFixedSize 10 2 64 24 64 24 8 true If enabled, this setting allows two linked Note events to be selected at the same time. Sel Linked true false 0 0 248 24 248 24 true QFrame::Shape::NoFrame Time Signature and PPQN Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter 64 24 64 16777215 true 0 0 248 24 248 24 true QFrame::Shape::NoFrame Measures and Events Count Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter 511 240 288 28 QLayout::SizeConstraint::SetFixedSize 8 40 24 40 24 The second data byte, if applicable. Change using decimal or hex (0xnn) notation. 0x00 24 24 24 24 true D1 Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 64 24 64 24 Ticks 64 16777215 Hex 24 24 24 24 true D0 Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 40 24 40 24 The first data byte, if applicable. Change using decimal or hex (0xnn) notation. 0x00 502 109 321 131 4 72 24 72 24 true Category Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter 160 24 180 24 8 8 QComboBox::InsertPolicy::NoInsert 72 24 72 24 true Time 0 0 160 28 180 28 Current time in bars:beats:pulses. Edit to insert or modify events. 001:1:000 72 24 72 24 true Event 160 28 180 28 true 12 32 QComboBox::InsertPolicy::NoInsert true 48 0 48 16777215 72 24 72 24 true Chan 160 28 180 28 true -1 12 17 QComboBox::InsertPolicy::NoInsert 1 true eventTableWidget entry_ev_timestamp combo_ev_name channel_combo_box entry_ev_data_0 entry_ev_data_1 button_del button_clear button_ins button_save button_modify button_dump ================================================ FILE: seq_qt5/forms/qsessionframe.ui ================================================ qsessionframe 0 0 690 460 Frame 17 42 149 18 100 0 11 true Session Path Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter false 189 38 480 28 420 28 480 28 11 Qt::ScrollBarPolicy::ScrollBarAlwaysOff false true <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } </style></head><body style=" font-family:'Noto Sans'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'WenQuanYi Zen Hei'; font-size:10pt; font-weight:456;">None</span></p></body></html> 29 72 137 18 132 18 11 true Server URL Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter false 189 68 480 28 420 28 480 30 11 Qt::ScrollBarPolicy::ScrollBarAlwaysOff false true <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } </style></head><body style=" font-family:'Noto Sans'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'WenQuanYi Zen Hei'; font-size:10pt; font-weight:456;">None</span></p></body></html> 21 158 145 32 80 32 11 true Session Log File Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter false 559 340 100 32 100 32 16777215 40 11 Restarts the whole application, reloading any changed settings. Restart false 189 8 480 28 420 24 480 30 11 Qt::ScrollBarPolicy::ScrollBarAlwaysOff false true <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } </style></head><body style=" font-family:'Noto Sans'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'WenQuanYi Zen Hei'; font-size:10pt; font-weight:456;">Normal</span></p></body></html> 9 10 161 18 11 true Session Manager Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 21 102 145 18 11 true Config File Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 373 102 125 18 11 true Client ID/UUID Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter false 189 98 160 28 0 28 16777215 30 11 Qt::ScrollBarPolicy::ScrollBarAlwaysOff false true <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } </style></head><body style=" font-family:'Noto Sans'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'WenQuanYi Zen Hei'; font-size:10pt; font-weight:456;">None</span></p></body></html> false 513 98 156 28 0 28 16777215 30 11 Qt::ScrollBarPolicy::ScrollBarAlwaysOff false true <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } </style></head><body style=" font-family:'Noto Sans'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'WenQuanYi Zen Hei'; font-size:10pt; font-weight:456;">None</span></p></body></html> false 179 190 480 28 0 28 16777215 30 11 true -1 Current Song Path true Qt::CursorMoveStyle::LogicalMoveStyle 15 130 151 20 11 true Macro Execution Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 217 130 452 24 0 24 11 QComboBox::InsertPolicy::InsertAlphabetically 189 130 21 22 179 160 449 28 0 28 11 Provides a log file to write Seq66 messages. All console output is redirected to this file; to see it in the console, click the "X" button to remove this filename. false 31 280 501 91 11 This text is stored as Meta Text in the first track. It can include any comments desired, including author and dates. 41 260 125 17 11 true Song Info 167 260 393 17 11 true (free formatted plain text, carriage returns allowed) 269 380 191 22 0 22 true Characters remaining Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 479 380 71 22 0 22 11 true 1024 Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter 559 280 100 32 100 32 16777215 40 11 Save Info 149 380 65 26 11 Changes the current track number, ranging from 0 to the highest pattern. Then, if any Meta Text is present in that pattern, then the first one is shown. 25 220 141 22 11 true Last Directory Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter false 179 220 480 28 0 28 11 true Last Used Directory N/A 55 194 111 22 11 true Song Path Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 29 380 131 22 11 true Pattern No. 640 160 28 28 11 Clear the log file to allow logging messages to the console. Must exit the application and start it again to have it take effect. No easy way to undo rerouting standard I/O to file. sessionManagerNameText sessionNameText sessionUrlText displayNameText clientIdText macroComboBox pushButtonReload songPathText ================================================ FILE: seq_qt5/forms/qsetmaster.ui ================================================ qsetmaster 0 0 841 428 Set Master Set Master allows creation, viewing, deletion, and moving of sets. The Set Master QFrame::Shape::NoFrame QFrame::Shadow::Plain 10 10 778 412 true 400 248 448 340 Qt::ScrollBarPolicy::ScrollBarAlwaysOff QAbstractScrollArea::SizeAdjustPolicy::AdjustToContentsOnFirstShow false 32 3 true false 18 21 300 156 16777215 156 Shows the number and name of each pattern in the screen-set. true Show a condensed list of sets and their patterns. Show Set Info Move the selected set down in the list. Triggers Qt::Orientation::Horizontal 40 20 Move the selected set up in the list. Set 0 false false true Clears the selected set. No undo! Careful! Clear Set Qt::Orientation::Horizontal 40 20 true 64 0 Down true 64 0 Up Set No. Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter Name m_set_table m_set_contents_text m_set_number_text m_set_name_text m_button_down m_button_toggle_trigger_mode m_button_set_0 m_button_up m_button_show m_button_delete ================================================ FILE: seq_qt5/forms/qslivegrid.ui ================================================ qslivegrid 0 0 691 366 0 0 330 100 Frame QFrame::Shape::NoFrame QFrame::Shadow::Plain 0 0 0 0 0 0 0 660 180 QFrame::Shape::NoFrame QFrame::Shadow::Plain 0 12 10 6 10 0 true 38 28 324 28 11 true Set Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 184 28 999 28 Edit the Set/Bank name here. 400 28 800 28 11 QFrame::Shape::StyledPanel QFrame::Shadow::Sunken 56 0 64 16777215 11 true Normal 36 28 164 28 11 Activates/deactivates background recording. BG 28 16 true 36 28 164 28 11 Loads and activates/deactivates the metronome. M 28 20 true 100 26 164 28 11 Determines the function of each slot, normally just loop control. 80 28 164 28 11 Makes this the active set. Activate 80 28 164 28 11 Cycles through recording styles. LMode 80 28 164 28 11 Cycles through quantized recording modes. RMode txtBankName comboGridMode buttonActivate buttonLoopMode buttonRecordMode ================================================ FILE: seq_qt5/forms/qslogview.ui ================================================ qslogview 0 0 781 497 Seq66 Log 33 33 701 401 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } </style></head><body style=" font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">(Filled at runtime)</p></body></html> false 460 450 111 27 200 16777215 Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Ok true 30 9 101 18 11 true Seq66 Log Qt::AlignmentFlag::AlignCenter 180 450 111 27 200 16777215 &Refresh 170 8 291 19 11 true (latest entries at bottom) textBrowser buttonBox accepted() qslogview accept() 248 254 157 274 buttonBox rejected() qslogview reject() 316 260 286 274 ================================================ FILE: seq_qt5/forms/qsmainwnd.ui ================================================ qsmainwnd 0 0 900 618 0 0 720 480 12 PreferDefault ArrowCursor qseq66 :/pixmaps/logo.xpm:/pixmaps/logo.xpm 0 0 1 1 Qt::LayoutDirection::LeftToRight 4 4 4 4 4 6 true 60 24 12 true PreferDefault Clicking on the logo will remove all external pattern editors and live frames (but not the external song editor). Seq66 true 14 24 20 24 _ false 48 0 48 16777215 11 true PreferDefault Indicates the format (0 or 1) of the MIDI tune. SMF 0 true false 72 24 84 24 11 true PreferDefault Indicates if in JACK/ALSA/PortMidi mode. PortMidi true false 56 24 56 24 11 true PreferDefault Indicates if in JACK Transport Master or Slave mode. Master true true 80 28 92 28 Change the PPQN value of song for ALL sets. This modifies the song; a "Save" prompt will occur at exit. It does not change Seq66's default PPQN. false 20 QComboBox::InsertPolicy::NoInsert true 148 28 164 28 Sets output bus for all patterns in the current set. Modifies the song. 20 0 0 60 28 64 28 Qt::FocusPolicy::NoFocus Global Beats Per Measure -1 17 6 24 6 24 12 true PreferDefault / Qt::AlignmentFlag::AlignCenter 0 0 60 28 64 28 Qt::FocusPolicy::NoFocus Global Beat Width (length of beat) 86 0 12 true PreferDefault This button displays the current time during playback. Clicking it toggles between B:B:T and H:M:S format. 008:16:0000 32 16777215 Test TEST 0 0 80 18 180 18 0 QLayout::SizeConstraint::SetMaximumSize 0 0 0 0 1 1 1 1 11 true PreferDefault Qt::FocusPolicy::TabFocus Qt::LayoutDirection::LeftToRight QTabWidget::TabShape::Rounded 7 false 0 0 1 1 false &Live QLayout::SizeConstraint::SetFixedSize 0 0 0 1 1 &Song QLayout::SizeConstraint::SetFixedSize 0 0 0 1 1 0 0 E&ditor QLayout::SizeConstraint::SetFixedSize 0 0 0 E&vents 10 10 821 511 QLayout::SizeConstraint::SetMaximumSize 0 0 &Playlists 10 10 821 511 QLayout::SizeConstraint::SetDefaultConstraint 0 0 Se&ts 10 8 821 513 QLayout::SizeConstraint::SetDefaultConstraint 0 0 &Mutes 10 10 821 511 QLayout::SizeConstraint::SetDefaultConstraint 0 0 Sessio&n 10 10 821 511 QLayout::SizeConstraint::SetDefaultConstraint 76 24 16777215 28 12 true PreferDefault Set Name 240 24 240 28 12 false PreferDefault Editable, this shows the name of the current set. New 128 0 24 16777215 28 12 true PreferDefault Mute-group name MG 108 24 16777215 28 0: Group #1 true false 40 24 44 28 9 false PreferDefault Indicates the loading of the system during playback. - Qt::AlignmentFlag::AlignCenter 60 24 60 28 12 true PreferDefault Active Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter false 56 24 64 28 12 false PreferDefault The number of the active screen-set or active external live frame. 1024 true 60 24 60 28 12 true PreferDefault Immediately make set 0 active. Set 0 24 24 24 28 12 true PreferDefault Resets the play-set to the current set, no matter what sets-mode is in force. ! 32 24 40 28 11 true PreferDefault Set Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 80 24 80 28 12 false PreferDefault Selects the active set. 6 0 0 32 24 48 28 Qt::FocusPolicy::NoFocus Panic! Turns off all notes. Panic 16 16 false 0 0 32 24 48 28 Qt::FocusPolicy::NoFocus Stop playback. Shift-click rewinds transport. Stop false 0 0 32 24 48 28 Pause/continue. Preserves current location. Pause 0 0 32 24 48 28 Qt::FocusPolicy::NoFocus Start/continue playback. Play true false false 28 24 28 28 Activates either the "current" pattern, all input-buss patterns, or all channel patterns. Rec false 32 24 48 28 Activate L/R looping. Loop 33 12 true false 0 0 28 24 28 28 Qt::FocusPolicy::NoFocus Record live sequence changes into the Song Editor. Ctrl key disables snap for the recording. Shift key enables recording at start of playback. Rec true 0 0 32 24 48 28 12 true PreferDefault Shows/toggles keep-queue status. Does not show one-shot queue status. Q 0 0 32 24 48 28 Mute Group Learn. Click, then press a key to store the state of the sequences in the Shift of that key. Learn 16 24 16 28 48 24 56 28 12 true PreferDefault Click to toggle the mute status of all tracks. Mute 0 0 40 24 48 28 Opens the external song-editor. [ Ctrl-E ] Perf 28 20 0 0 40 24 64 28 12 true PreferDefault Qt::FocusPolicy::NoFocus Toggle playback using the Song Editor triggers. Live 24 16 true false 56 24 64 28 12 true PreferDefault PPQN 192 11 true Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter true 36 24 36 28 12 true PreferDefault Tap in time to set beats/minute (BPM). After 5 seconds of no taps, the counter resets to 0. 0 34 24 36 28 11 true PreferDefault BPM Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter true 80 24 80 28 12 false PreferDefault Qt::FocusPolicy::ClickFocus Allows the setting of the beats/minute. Qt::LayoutDirection::LeftToRight 2.000000000000000 600.000000000000000 120.000000000000000 0 0 900 28 11 true PreferDefault &File &Export &Import &Help &Edit &New Create a new Seq66 MIDI file, clearing all song information. Ctrl+N MIDI to &Current Set... Import MIDI file into (different) bank/set. Ctrl+I Pro&ject Configuraton... Import a configuration from another directory. Ctrl+J &Open... Open a MIDI or Seq66 MIDI file. Ctrl+O Open &Playlist... Open a Seq66 playlist file. &Recent... Open a Recent File. &Preferences... Preferences/Options (still in progress). Ctrl+P Qt::ShortcutContext::ApplicationShortcut &Save Save MIDI file in Seq66 format. Ctrl+S Save &As... Save As a Seq66 MIDI file with a different name. Ctrl+Shift+S &Quit &About... &Build Info... App &Keys... About Qt... Tools... &Song... Export song layout as a Seq66 MIDI file with triggers consolidated into a single one for each unmuted pattern. &MIDI Only... Export without Seq66 SeqSpecs, just MIDI events and messages. SMF &0... Convert to SMF 0 and export without Seq66 SeqSpecs. &Song Editor Brings up the Song Editor in an external window. Ctrl+E Apply Song &Transpose Transposes the transposable tracks. &Clear Mute Groups Clears mute groups so one can start over again. &Reload Mute Groups Reloads mute groups from the "mutes" file. &Mute All Tracks Mutes all tracks. Ctrl+M &Unmute All Tracks Arms all tracks in the current set. Ctrl+U To&ggle All Tracks Toggles mute status of all tracks in the current set. Ctrl+T &Close Close Session &Song Summary File... Writes a text summary of the song to a file in the same directory as the original, with the extension ".text". C&opy Current Set Past&e To Current Set P&aste To Current Set &Playlist... Import a playlist into the session. Ctrl+L &MIDI into Session &Tutorial &User Manual Pro&ject Configuration... &View Log ================================================ FILE: seq_qt5/include/Makefile.am ================================================ #****************************************************************************** # Makefile.am (libseq_qt5) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library libseq_qt5 library # \author Chris Ahlstrom # \date 2017-09-06 # \update 2026-03-13 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the libseq_qt5 C++ # library. # # Removed: # # qseqeditframe.hpp # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) CLEANFILES = *.gc* *.moc.cpp MOSTLYCLEANFILES = *~ #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = *.h *.hpp #****************************************************************************** # Items from configure.ac #------------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ GIT_VERSION = @GIT_VERSION@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ prefix = @prefix@ includedir = @seq66includedir@ libdir = @seq66libdir@ datadir = @datadir@ datarootdir = @datarootdir@ #****************************************************************************** # Source files # # Moved to contrib/code: qchannelpopup.hpp \ # #---------------------------------------------------------------------------- pkginclude_HEADERS = \ gui_palette_qt5.hpp \ palettefile.hpp \ qbase.hpp \ qclocklayout.hpp \ qeditbase.hpp \ qinputcheckbox.hpp \ qlfoframe.hpp \ qliveframeex.hpp \ qloopbutton.hpp \ qmutemaster.hpp \ qpatternfix.hpp \ qperfbase.hpp \ qperfeditex.hpp \ qperfeditframe64.hpp \ qperfnames.hpp \ qperfroll.hpp \ qperftime.hpp \ qplaylistframe.hpp \ qportwidget.hpp \ qsabout.hpp \ qsappinfo.hpp \ qsbuildinfo.hpp \ qscrollmaster.h \ qscrollslave.h \ qseditoptions.hpp \ qseqbase.hpp \ qseqdata.hpp \ qseqeditex.hpp \ qseqeditframe64.hpp \ qseqeventframe.hpp \ qseqframe.hpp \ qseqkeys.hpp \ qseqroll.hpp \ qseqtime.hpp \ qsessionframe.hpp \ qsetmaster.hpp \ qseventslots.hpp \ qslivebase.hpp \ qslivegrid.hpp \ qslogview.hpp \ qslotbutton.hpp \ qsmaintime.hpp \ qsmainwnd.hpp \ qstriggereditor.hpp \ qt5_helper.h \ qt5_helpers.hpp \ qt5nsmanager.hpp #****************************************************************************** # uninstall-hook #------------------------------------------------------------------------------ uninstall-hook: @echo "Note: you may want to remove $(pkgincludedir) manually" #****************************************************************************** # Makefile.am (libseq_qt5) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: seq_qt5/include/gui_palette_qt5.hpp ================================================ #if ! defined SEQ66_GUI_PALETTE_QT5_HPP #define SEQ66_GUI_PALETTE_QT5_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file gui_palette_qt5.hpp * * This module declares/defines the class for providing Qt 5 colors. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-02-23 * \updates 2025-10-13 * \license GNU GPLv2 or above * * This module defines some QColor objects. We might consider replacing the * color accessor names with names that reflect their usage [e.g. instead of * using light_grey(), we could provide a scale_color() function instead, * since light-grey is the color used to draw scales on the pattern editor. * Note that the color names come from /usr/share/X11/rgb.txt as Qt requires. */ #include /* std::unique_ptr<> */ #include #include #include "cfg/basesettings.hpp" /* seq66::basesettings class */ #include "util/palette.hpp" /* seq66::palette map class */ namespace seq66 { /** * Provides a type for the color object for the GUI framework. */ using Color = QColor; /** * Provides a type for the brush object for the GUI framework. These * objects are held in std::unique_ptr<>. */ using Brush = QBrush; /** * Provides a map to brush styles. The first values are NoBrush, * SolidPattern, and DenseXPattern (X = 1 to 7), and these are the ones we * are most interested in. They are defined in the QtCore/qnamespace.h * header file. The maximum useful value is ConicalGradientPattern = 17. * Note that QBrush is a plain enumeration declared in qnamespace.h. * However, we create the brushes in the gui_palette_qt5 constructor. */ using BrushStyle = Qt::BrushStyle; /** * Provides a map to pen styles. Note that QPenStyle is a plain enumeration * declared in qnamespace.h. */ using PenStyle = Qt::PenStyle; /** * Implements a stock palette of QColor elements. */ class gui_palette_qt5 : public basesettings { public: enum class brush { empty, /**< Brush for empty space, usually "no brush". */ note, /**< Brush for drawing notes in pattern editor. */ scale, /**< Brush for drawing lines denoting musical scale. */ backseq, /**< Brush for drawing lines denoting background seq. */ chord /**< Brush for drawing lines *not* in selected chord. */ }; enum class penstyle { empty, /**< Qt::NoPen. */ solid, /**< The default penstyle is Qt::SolidLine. */ dash, /**< Qt::DashLine. */ dot, /**< Qt::DotLine. */ dashdot, /**< Qt::DashDotLine. */ dashdotdot, /**< Qt::DashDotDotLine. */ customdash /**< Qt::CustomDashLine (not supported at this time). */ }; enum class pen { measure, beat, fourth, step }; private: using BrushPtr = std::unique_ptr; /** * Holds the color palette for drawing on slot backgrounds. */ palette m_palette; /** * Holds the color palette for drawing notes on slot backgrounds. This * is not quite an inverse palette, but consists of colors that show well * on the background colors. */ palette m_pen_palette; /** * Holds the invertible colors using in drawing pattern labels, drum * notes, tempo, grid lines, and various text items. This holds the * normal values. */ palette m_nrm_palette; /** * Holds the invertible colors using in drawing pattern labels, drum * notes, tempo, grid lines, and various text items. This holds the * inverse values. */ palette m_inv_palette; /** * Indicates if we have loaded the static colors. */ bool m_statics_are_loaded; /** * Flags the presence of the inverse color palette. */ bool m_is_inverse; /** * Provides a hint that the palette (or matching theme) is overall * "dark" for the user-interface elements, which are separate from * the Qt theme. */ bool m_dark_theme; /** * Provides a hint that the backgrounds of grids, etc. are dark. */ bool m_dark_ui; /** * Stock brushes to increase speed. As of 2023-02-26, we use * pointers to be able to reassign brushes property for Qt. */ BrushPtr m_empty_brush; BrushStyle m_empty_brush_style; BrushPtr m_note_brush; /* for both notes and triggers */ BrushStyle m_note_brush_style; /* ditto */ BrushPtr m_scale_brush; BrushStyle m_scale_brush_style; BrushPtr m_backseq_brush; BrushStyle m_backseq_brush_style; BrushPtr m_chord_brush; BrushStyle m_chord_brush_style; /** * A convenience to indicate that the linear gradient pattern brush * is in used. */ bool m_use_gradient_brush; /** * Stock pen enumeration values. */ PenStyle m_measure_pen_style; /* style of each bar line */ PenStyle m_beat_pen_style; /* style of each beat line */ PenStyle m_fourth_pen_style; /* style of each 1/4 beat line */ PenStyle m_step_pen_style; /* style of small step lines */ public: gui_palette_qt5 (const std::string & filename = ""); gui_palette_qt5 (const gui_palette_qt5 &) = delete; gui_palette_qt5 & operator = (const gui_palette_qt5 &) = delete; virtual ~gui_palette_qt5 (); static Color calculate_inverse (const Color & c); static int palette_size () { return palette_to_int(PaletteColor::max); } static int invertible_size () { return inv_palette_to_int(InvertibleColor::max); } void reset () { reset_backgrounds(); reset_pens(); reset_invertibles(); } void reset_backgrounds (); void reset_pens (); void reset_invertibles (); /** * \param index * Provides the color index into the palette. * * \return * Returns the corresponding color name from the palette. */ std::string get_color_name (PaletteColor index) const { return m_palette.get_color_name(index); } std::string get_color_name_ex (PaletteColor index) const { return m_palette.get_color_name_ex(index); } std::string get_pen_color_name (PaletteColor index) const { return m_pen_palette.get_color_name(index); } const Color & get_color (PaletteColor index) const { return m_palette.get_color(index); } std::string get_color_name (InvertibleColor index) const { return m_nrm_palette.get_color_name(index); } std::string get_inv_color_name (InvertibleColor index) const { return m_inv_palette.get_color_name(index); } const Color & get_color (InvertibleColor index) const; const Color & get_inverse_color (InvertibleColor index) const { return m_inv_palette.get_color(index); } /** * \param index * Provides the color index into the palette. * * \return * Returns the corresponding color from the pen palette. */ const Color & get_pen_color (PaletteColor index) const { return m_pen_palette.get_color(index); } std::string make_color_stanza (int number, bool inverse = false) const; bool add_color_stanza (const std::string & stanza, bool inverse = false); Color get_color_ex ( PaletteColor index, double h, double s = 0.65, double v = 1.0 ) const; Color invert (Color c, bool usealpha = false) const; Color get_color_fix (PaletteColor index) const; Color get_color_inverse (PaletteColor index) const; #if defined SEQ66_PROVIDE_AUTO_COLOR_INVERSION void fill_inverse_colors (); #endif void load_static_colors (bool inverse = true); bool is_theme_color (const Color & c) const; /** * Indicates if the inverse color palette is loaded, and if the * use considers the matching theme to be dark. */ bool is_inverse () const { return m_is_inverse; } bool dark_theme () const { return m_dark_theme; } void dark_theme (bool flag) { m_dark_theme = flag; } bool dark_ui () const { return m_dark_ui; } void dark_ui (bool flag) { m_dark_ui = flag; } /** * A convenience function to hide some details of checking for * sequence color codes. */ bool no_color (int c) const { return m_palette.no_color(PaletteColor(c)); } void clear (); void clear_invertible (); /* * Brush handling. */ Brush & get_brush (brush index); BrushStyle get_brush_style (const std::string & name) const; std::string get_brush_name (BrushStyle b) const; bool set_brushes ( const std::string & emptybrush, const std::string & notebrush, const std::string & scalebrush, const std::string & backseqbrush, const std::string & chordbrush ); bool get_brush_names ( std::string & emptybrush, std::string & notebrush, std::string & scalebrush, std::string & backseqbrush, std::string & chordbrush ); bool use_gradient_brush () const { return m_use_gradient_brush; } /* * Pen style handling. */ PenStyle pen_style (pen p); bool get_pen_names ( std::string & measurepen, std::string & beatpen, std::string & fourpen, std::string & steppen ); bool set_pens ( const std::string & measurepen, const std::string & beatpen, const std::string & fourpen, const std::string & steppen ); private: /* * Brush handling. */ const std::string & brush_name (int index) const; bool make_brush ( BrushPtr & brush, BrushStyle & brushstyle, BrushStyle temp ); bool add ( int index, const Color & bg, const std::string & bgname, const Color & fg, const std::string & fgname ); bool add_invertible ( int index, const Color & bg, const std::string & bgname, const Color & fg, const std::string & fgname ); /* * Pen style handling. */ const std::string & pen_name (int index) const; PenStyle get_pen (penstyle p); penstyle get_pen_index (PenStyle ps); std::string get_penstyle_name (penstyle p); PenStyle get_pen_style (const std::string & penname); }; // class gui_palette_qt5 /* * Free functions for color. */ extern gui_palette_qt5 & global_palette (); extern Color get_color_fix (PaletteColor index); extern Color get_pen_color (PaletteColor index); extern Color background_paint (); extern Color foreground_paint (); extern Color label_paint (); extern Color sel_paint (); extern Color drum_paint (); extern Color tempo_paint (); extern Color note_in_paint (); extern Color note_out_paint (); extern Color black_key_paint (); extern Color white_key_paint (); extern Color progress_paint (); extern Color backseq_paint (); extern Brush backseq_brush (); extern Color chord_paint (); extern Brush chord_brush (); extern Color bar_paint (); /* heavy lines, dk_grey */ extern Color grey_paint (); /* medium lines, grey */ extern Color step_paint (); /* light lines, dk_grey */ extern Color beat_paint (); /* beat lines */ extern Color near_paint (); extern Color backtime_paint (); extern Color backdata_paint (); extern Color backevent_paint (); extern Color backkeys_paint (); extern Color backnames_paint (); extern Color octave_paint (); extern Color text_paint (); extern Color text_time_paint (); extern Color text_data_paint (); extern Color note_event_paint (); extern Color text_keys_paint (); extern Color text_names_paint (); extern Color text_slots_paint (); extern Color scale_paint (); extern Color extra_paint (); extern std::string get_color_name (PaletteColor index); extern std::string get_color_name_ex (PaletteColor index); extern bool no_color (int c); extern bool is_theme_color (const Color & c); extern bool is_dark_ui (); extern Brush gui_empty_brush (); extern Brush gui_note_brush (); /* for notes and triggers */ extern bool gui_use_gradient_brush (); /* ditto */ extern Brush gui_scale_brush (); extern Brush gui_backseq_brush (); extern Brush gui_chord_brush (); extern PenStyle gui_measure_pen_style (); extern PenStyle gui_beat_pen_style (); extern PenStyle gui_fourth_pen_style (); extern PenStyle gui_step_pen_style (); } // namespace seq66 #endif // SEQ66_GUI_PALETTE_QT5_HPP /* * gui_palette_qt5.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/palettefile.hpp ================================================ #if ! defined SEQ66_PALETTEFILE_HPP #define SEQ66_PALETTEFILE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file palettefile.hpp * * This module declares/defines the base class for managind the ~/.seq66rc * configuration file. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-12-21 * \updates 2021-06-22 * \license GNU GPLv2 or above * * Provides support for a mute-groups configuration file. */ #include /* std::ofstream and ifstream */ #include "cfg/configfile.hpp" /* seq66::configfile class */ namespace seq66 { class gui_palette_qt5; /** * Provides a file for reading and writing the application's mute-group * configuration file. The settings that are passed around are provided * or used by the performer class. */ class palettefile final : public configfile { private: /** * Holds a reference to the palette object to be acted upon by this * class. */ gui_palette_qt5 & m_palettes; public: palettefile ( gui_palette_qt5 & palettes, const std::string & filename, rcsettings & rcs ); palettefile () = delete; palettefile (const palettefile &) = delete; palettefile & operator = (const palettefile &) = delete; ~palettefile () = default; virtual bool parse () override; virtual bool write () override; bool parse_stream (std::ifstream & file); bool write_stream (std::ofstream & file); static int palette_size () { return 32; } private: bool write_map_entries (std::ofstream & file) const; gui_palette_qt5 & palettes () { return m_palettes; } }; // class palettefile /* * Free functions for working with play-list files. */ extern bool open_palette ( gui_palette_qt5 & pal, const std::string & source ); extern bool save_palette ( gui_palette_qt5 & pal, const std::string & destination ); extern bool save_palette ( gui_palette_qt5 & pal, const std::string & source, const std::string & destination ); } // namespace seq66 #endif // SEQ66_PALETTEFILE_HPP /* * palettefile.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qbase.hpp ================================================ #if ! defined SEQ66_QBASE_HPP #define SEQ66_QBASE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ /** * \file qbase.hpp * * This module declares/defines the base class for operations common to all * seq66 windows. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-07-22 * \updates 2024-12-12 * \license GNU GPLv2 or above * * Provides a abstract base class so that both the old and the new Qt * sequence and performance frames can be supported. These concepts * are in the queue to be supported: * * - PPQN. The performer now maintains the PPQN for the whole * application. The user-interface classes have to deal with changes * in the PPQN. (Some will also have to deal with changes in BPM, * beats per minute.) * - Zoom. Zoom interacts with PPQN. * - Dirtiness. This indicates if the user-interface should be drawn. */ #include "play/performer.hpp" /* seq66::performer */ namespace seq66 { /** * This frame is the basis for editing an individual MIDI sequence. */ class qbase { public: /** * We need a way to distinguish settings made at construction time versus * settings made by the user, as well as changes that ignore an attempted * user setting. */ enum class status { startup, edit, undo }; private: /** * Provides a reference to the performance object. */ performer & m_performer; /** * Dirty! Being dirty means that not only does the window need updating, * but there are changes made that need to be saved. */ mutable bool m_is_dirty; /** * All ready to go. This variable is to be used to keep from setting dirty * status over and over while initializing the user-interface... which * calls paintEvent() over and over. */ mutable bool m_is_initialized; public: qbase (performer & p /* , int zoom */ ); virtual ~qbase (); void stop_playing () { perf().auto_stop(); } void pause_playing () { perf().auto_pause(); } void start_playing () { perf().auto_play(); } protected: int ppqn () const { return perf().ppqn(); /* go right to the source for PPQN */ } bool is_dirty () const { return m_is_dirty; } bool is_initialized () const { return m_is_initialized; } public: void set_initialized (bool flag = true) const { m_is_initialized = flag; } void uninitialize () { m_is_initialized = false; } const performer & perf () const { return m_performer; } performer & perf () { return m_performer; } public: virtual bool check_dirty () const { bool result = m_is_dirty; m_is_dirty = false; return result; } virtual bool change_ppqn (int ppqn) = 0; virtual bool change_bpm (midibpm /*bpm*/) { return true; // no code in most cases } virtual void set_dirty () { m_is_dirty = true; } protected: virtual void update_midi_buttons () { // no buttons at this level } }; // class qbase } // namespace seq66 #endif // SEQ66_QBASE_HPP /* * qbase.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qclocklayout.hpp ================================================ #if ! defined SEQ66_QCLOCKLAYOUT_HPP #define SEQ66_QCLOCKLAYOUT_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qclocklayout.hpp * * This class supports a MIDI Clocks label and a set of radio-buttons for * selecting the clock style (off, on POS, on MOD), associating it with a * particular output buss. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-05-19 * \updates 2022-01-20 * \license GNU GPLv2 or above * * Provides the layout for a single MIDI output buss clocking user-interface * setup. */ #include "qportwidget.hpp" /* seq66::qportwidget base class */ /* * Forward references. */ class QButtonGroup; class QGroupBox; class QHBoxLayout; class QLabel; class QRadioButton; class QSpacerItem; namespace seq66 { /** * This class is a widget that supports a row of radio-buttons that let the * user set the type of clocking for each MIDI output buss: * * - Disabled * - Off. * - On (Pos). * - On (Mod). */ class qclocklayout : public qportwidget { Q_OBJECT public: qclocklayout (QWidget * parent, performer & p, int bus); virtual ~qclocklayout () { // no code needed } QHBoxLayout * layout () { return m_horizlayout_clockline; } private: void setup_ui (); signals: private slots: void clock_callback_clicked (int id); private: /** * m_horizlayout_clockline holds the label and all of the radio buttons for * a given MIDI output buss. */ QHBoxLayout * m_horizlayout_clockline; /* see layout() */ /** * The spacer between the buss name and button-group. */ QSpacerItem * m_spacer_clock; /** * The name of the MIDI output buss represented by this object. */ QLabel * m_label_outputbusname; /** * Port disabled. See the banner for the setup_ui() function. */ QRadioButton * m_rbutton_portdisabled; /** * Clocking off. See the banner for the setup_ui() function. */ QRadioButton * m_rbutton_clockoff; /** * Clocking re position. See the banner for the setup_ui() function. */ QRadioButton * m_rbutton_clockonpos; /** * Clocking re clock-start modulo setting. See the banner for the * setup_ui() function. */ QRadioButton * m_rbutton_clockonmod; /** * Contains all of the radio-buttons. */ QButtonGroup * m_rbutton_group; }; // class qclocklayout } // namespace seq66 #endif // SEQ66_QCLOCKLAYOUT_HPP /* * qclocklayout.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qeditbase.hpp ================================================ #if ! defined SEQ66_QEDITBASE_HPP #define SEQ66_QEDITBASE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qeditbase.hpp * * This module declares/defines the base class for the sequence and * performance editing frames of Seq66's Qt 5 version. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-08-05 * \updates 2025-07-21 * \license GNU GPLv2 or above * * This class will be the base class for the qseqbase and qperfbase classes. * Both kinds of editing involve selection, movement, zooming, etc. */ #include "cfg/zoomer.hpp" /* seq66::zoomer class */ #include "util/rect.hpp" /* seq66::rect rectangle class */ #include "gui_palette_qt5.hpp" /* gui_pallete_qt5::Color etc. */ #include "qbase.hpp" /* seq66:qbase super base class */ namespace seq66 { class performer; class sequence; /** * The dimensions and offset of the virtual keyboard at the left of the * piano roll. Some have been moved to the GUI (Qt) that needs them. */ const int c_keyboard_padding_x = 6; /* Qt version of keys padding */ /** * The default value of the snap in the sequence/performance editors. */ const int c_default_snap = 16; /* default snap from app limits */ /** * Provides basic functionality to be inherited by qseqbase and qperfbase. */ class qeditbase : public qbase { private: /** * Indicates dark GUI backgrounds are drawn. */ bool m_dark_ui; /** * Colors common to the sequence and song edit panes. We want to * initialize them only at startup, to make painting faster. We could * make them constant, but we might eventually want to reload a new * palette on the fly. LATER. */ const Color m_back_color; const Color m_fore_color; const Color m_label_color; const Color m_sel_color; const Color m_drum_color; const Color m_progress_color; const Color m_beat_color; const Color m_step_color; const Color m_octave_color; const Color m_note_in_color; const Color m_note_out_color; const Color m_tempo_color; const Color m_grey_color; const Color m_extra_color; /** * Similarly, we provide stock brushes that are configurable. This saves * changing brush colors and styles so often. */ const Brush m_blank_brush; /* for clearing out rectangles, etc. */ const Brush m_note_brush; /* also for perfroll trigger boxes? */ const Brush m_scale_brush; /* background, usually hatched */ const Brush m_backseq_brush; /* another background, usually hatched */ const bool m_use_gradient; /* paint notes/triggers with gradient */ protected: /** * TIme-saving settings for pens. Based on the following functions: * * - usr().progress_bar_thick() * - usr().progress_bar_thickness() * - usr().gridlines_thick() */ int m_progress_bar_width; /* 1, 2, ... */ int m_measure_pen_width; /* will be set to 2 or 1 */ int m_beat_pen_width; /* almost always 1 */ int m_horiz_pen_width; /* 1 or 2 */ Qt::PenStyle m_measure_pen_style; /* almost always Qt::SolidLine */ Qt::PenStyle m_beat_pen_style; /* Qt::DotLine or Qt::SolidLine */ Qt::PenStyle m_four_pen_style; /* Qt::DashDotLine or ... */ Qt::PenStyle m_step_pen_style; /* Qt::DotLine, usually */ /** * The previous selection rectangle, used for undrawing it. Accessed by * the getter/setting functions old_rect(). */ seq66::rect m_old; /** * Used in moving and pasting notes. Accessed by the getter/setting * functions selection(). */ seq66::rect m_selected; /** * Handles all the ins-and-outs of our zoom and zoom expansion feature. */ zoomer m_zoomer; /** * Provides additional padding to move items rightward to account for * slight differences in the layout of items inside the edit frames. It * makes the time, roll, trigger/event, and data measures line up * properly. */ int m_padding_x; /** * The permanent snap for drawing the grid, barring a change in the PPQN. * It is used to calculate the actual snap given the PPQN and the * zoom setting. */ int m_grid_snap; /** * The event-snap setting for the piano roll grid. Same meaning as for * the event-bar grid. This value is the denominator of the note size * used for the snap. */ int m_snap; /** * Provides the length of a beat, in ticks. */ midipulse m_beat_length; /** * Provides the length of a measure or bar, in ticks. */ midipulse m_measure_length; /** * Set when highlighting a bunch of events. */ bool m_selecting; /** * Set when in note-adding or trigger-adding mode. This flag was moved * from both the fruity and the seq66 seqroll classes. */ bool m_adding; /** * Set when moving a bunch of events. */ bool m_moving; /** * Indicates the beginning of moving some events. Used in the fruity and * seq66 mouse-handling modules. */ bool m_moving_init; /** * Indicates that the notes are to be extended or reduced in length. */ bool m_growing; /** * Indicates the painting of events. Used in the fruity and seq66 * mouse-handling modules. */ bool m_painting; /** * Indicates that we are in the process of pasting notes. */ bool m_paste; /** * The x location of the mouse when dropped. Would be good to allocate * this to a base class for all grid panels. */ int m_drop_x; /** * The x location of the mouse when dropped. Would be good to allocate * this to a base class for all grid panels. */ int m_drop_y; /** * Current x coordinate of pointer. */ int m_current_x; int m_last_snap_x; /** * Current y coordinate of pointer. */ int m_current_y; int m_last_snap_y; /** * Provides the location of the progress bar. */ int m_progress_x; /** * Provides the old location of the progress bar, for "playhead" * tracking. */ int m_old_progress_x; /** * Provides the current scroll page in which the progress bar resides. */ int m_scroll_page; /** * Progress bar follow state. */ bool m_progress_follow; /** * The horizontal value of the scroll window in units of * ticks/pulses/divisions. */ int m_scroll_offset; /** * The vertical offset of the scroll window in units of sequences or MIDI * notes/keys. */ int m_scroll_offset_v; /** * The horizontal value of the scroll window in units of pixels. */ int m_scroll_offset_x; /** * The vertical value of the scroll window in units of pixels. */ int m_scroll_offset_y; /** * Provides the height of a unit. For qseqroll, this is note height. */ int m_unit_height; /** * See qseqroll::keyY * c_notes_count + 1. */ int m_total_height; public: qeditbase ( performer & perf, int zoom, int scalex = 1, int padding = 0, int snap = c_default_snap, int unit_height = 1, int total_height = 1 ); bool dark_ui () const { return m_dark_ui; } const Color & back_color () const { return m_back_color; } const Color & fore_color () const { return m_fore_color; } const Color & label_color () const { return m_label_color; } const Color & sel_color () const { return m_sel_color; } const Color & drum_color () const { return m_drum_color; } const Color & progress_color () const { return m_progress_color; } const Color & beat_color () const { return m_beat_color; } const Color & step_color () const { return m_step_color; } const Color & octave_color () const { return m_octave_color; } const Color & note_in_color () const { return m_note_in_color; } const Color & note_out_color () const { return m_note_out_color; } bool use_gradient () const { return m_use_gradient; /* paint notes/triggers with gradient */ } int horiz_pen_width () const { return m_horiz_pen_width; } int progress_bar_width () const { return m_progress_bar_width; } int measure_pen_width () const { return m_measure_pen_width; } int beat_pen_width () const { return m_beat_pen_width; } const Qt::PenStyle & measure_pen_style () const { return m_measure_pen_style; } const Qt::PenStyle & beat_pen_style () const { return m_beat_pen_style; } const Qt::PenStyle & fourth_pen_style () const { return m_four_pen_style; } const Qt::PenStyle & step_pen_style () const { return m_step_pen_style; } const Color & tempo_color () const { return m_tempo_color; } const Color & grey_color () const { return m_grey_color; } const Color & extra_color () const { return m_extra_color; } const Brush & blank_brush () const { return m_blank_brush; } const Brush & note_brush () const { return m_note_brush; } const Brush & scale_brush () const { return m_scale_brush; } const Brush & backseq_brush () const { return m_backseq_brush; } const seq66::rect & old_rect () const { return m_old; } seq66::rect & old_rect () { return m_old; } const seq66::rect & selection () const { return m_selected; } seq66::rect & selection () { return m_selected; } const zoomer & z () const { return m_zoomer; } zoomer & z () { return m_zoomer; } /* * Eventually have all clients access m_zoomer directly. */ int zoom () const { return m_zoomer.zoom(); } int scale () const { return m_zoomer.scale(); } int scale_zoom () const { return m_zoomer.scale_zoom(); } bool expanded_zoom () const { return m_zoomer.expanded_zoom(); } int zoom_expansion () const { return m_zoomer.zoom_expansion(); } /** * Indicates if we're selecting, moving, growing, or pasting. * * \return * Returns true if one of those four flags are set. */ bool select_action () const { return selecting() || growing() || drop_action(); } /** * Indicates if we're drag-pasting, selecting, moving, growing, or * pasting. * * \return * Returns true if one of those five flags are set. */ virtual bool normal_action () const { return select_action(); } /** * Indicates if we're moving or pasting. * * \return * Returns true if one of those two flags are set. */ virtual bool drop_action () const { return moving(); } int snap () const { return m_snap; } /* * This value changes only when the user changes the snap value. * The default snap is 16 ticks. */ int grid_snap () const { return m_grid_snap; } midipulse beat_length () const { return m_beat_length; } midipulse measure_length () const { return m_measure_length; } bool selecting () const { return m_selecting; } bool adding () const { return m_adding; } bool moving () const { return m_moving; } bool moving_init () const { return m_moving_init; } bool growing () const { return m_growing; } bool painting () const { return m_painting; } bool paste () const { return m_paste; } int drop_x () const { return m_drop_x; } int drop_y () const { return m_drop_y; } void snap_drop_x () { snap_x(m_drop_x); } void snap_drop_y () { snap_y(m_drop_y); } int current_x () const { return m_current_x; } int current_y () const { return m_current_y; } int progress_x () const { return m_progress_x; } int old_progress_x () const { return m_old_progress_x; } int scroll_page () const { return m_scroll_page; } bool progress_follow () const { return m_progress_follow; } virtual int scroll_offset () const { return m_scroll_offset; } int scroll_offset_v () const { return m_scroll_offset_v; } int scroll_offset_x () const { return m_scroll_offset_x; } int scroll_offset_y () const { return m_scroll_offset_y; } int unit_height () const { return m_unit_height; } int total_height () const { return m_total_height; } public: virtual bool change_ppqn (int ppq) override; /** * Make the view cover less horizontal length. The lowest zoom possible * is 1. But, if the user still wants to zoom in some more, we fake it * by using "zoom expansion". This factor increases the pixel spread by * a factor of 1, 2, 4, or 8. */ virtual bool zoom_in () { return m_zoomer.zoom_in(); } virtual bool zoom_out () { return m_zoomer.zoom_out(); } virtual bool set_zoom (int z) { return m_zoomer.set_zoom(z); } virtual bool reset_zoom (int ppq = 0) { return m_zoomer.reset_zoom(ppq); } virtual bool check_dirty () const override; void set_snap (midipulse snap) { m_snap = int(snap); } void set_grid_snap (midipulse snap); void reset_grid_snap () { set_grid_snap(c_default_snap); } protected: void horiz_pen_width (int w) { m_horiz_pen_width = w; } void progress_bar_width (int w) { m_progress_bar_width = w; } void measure_pen_width (int w) { m_measure_pen_width = w; } void beat_pen_width (int w) { m_beat_pen_width = w; } void measure_pen_style (const Qt::PenStyle & ps) { m_measure_pen_style = ps; } void beat_pen_style (const Qt::PenStyle & ps) { m_beat_pen_style = ps; } void four_pen_style (const Qt::PenStyle & ps) { m_four_pen_style = ps; } void step_pen_style (const Qt::PenStyle & ps) { m_step_pen_style = ps; } virtual int horizSizeHint () const; void old_rect (seq66::rect & r) { m_old = r; } void selection (seq66::rect & r); /* moved due to debugging issue */ void clear_action_flags (); /* ditto */ void selecting (bool v) { m_selecting = v; } void adding (bool v) { m_adding = v; } void moving (bool v) { m_moving = v; } void moving_init (bool v) { m_moving_init = v; } void growing (bool v) { m_growing = v; } void painting (bool v) { m_painting = v; } void paste (bool v) { m_paste = v; } void drop_x (int v) { m_drop_x = v; } void drop_y (int v) { m_drop_y = v; } void current_x (int v) { m_current_x = v; } void current_y (int v) { m_current_y = v; } void progress_x (int v) { m_progress_x = v; } void old_progress_x (int v) { m_old_progress_x = v; } void scroll_page (int v) { m_scroll_page = v; } void progress_follow (bool v) { m_progress_follow = v; } virtual void scroll_offset (int v) { m_scroll_offset = v; } void scroll_offset_v (int v) { m_scroll_offset_v = v; } void scroll_offset_x (int v) { m_scroll_offset_x = v; } void scroll_offset_y (int v) { m_scroll_offset_y = v; } void unit_height (int v) { m_unit_height = v; } void total_height (int v) { m_total_height = v; } protected: void snap_x (int & x); bool snap_current_x (); void snap_y (int & y) { y -= y % m_unit_height; /* not c_names_y */ } bool snap_current_y (); void swap_x () { int temp = m_current_x; m_current_x = m_drop_x; m_drop_x = temp; } void swap_y () { int temp = m_current_y; m_current_y = m_drop_y; m_drop_y = temp; } /* * qseqroll: int x_offset = xoffset(tick) - scroll_offset_x() */ int xoffset (midipulse tick) const { return m_zoomer.tix_to_pix(tick) + m_padding_x; } /** * Calculates a suitable starting zoom value for the given PPQN value. * The default starting zoom is 2, but this value is suitable only for * PPQN of 192 and below. Also, zoom currently works consistently only * if it is a power of 2. For starters, we scale the zoom to the * selected ppqn, and then shift it each way to get a suitable power of * two. * * \param ppqn * The ppqn of interest. * * \return * Returns the power of 2 appropriate for the given PPQN value. */ int zoom_power_of_2 (int ppq) { return m_zoomer.zoom_power_of_2(ppq); } void convert_x (int x, midipulse & tick) { tick = m_zoomer.pix_to_tix(x); } void convert_xy (int x, int y, midipulse & ticks, int & seq); void convert_ts (midipulse ticks, int seq, int & x, int & y); void convert_ts_box_to_rect ( midipulse tick_s, midipulse tick_f, int seq_h, int seq_l, seq66::rect & r ); /** * Meant to be overridden by derived classes to change a user-interface * item, such as the mouse pointer, when entering an adding mode. * * \param a * The value of the status of adding (e.g. a note). */ virtual void set_adding (bool a) { adding(a); } void start_paste(); private: /* * Takes screen coordinates, give us notes/keys (to be generalized to * other vertical user-interface quantities) and ticks (always the * horizontal user-interface quantity). Compare this function to * qbase::pix_to_tix(). */ /* virtual */ midipulse pix_to_tix (int x) const { return m_zoomer.pix_to_tix(x); } /* virtual */ int tix_to_pix (midipulse ticks) const { return m_zoomer.tix_to_pix(ticks); } }; // class qeditbase } // namespace seq66 #endif // SEQ66_QEDITBASE_HPP /* * qeditbase.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qinputcheckbox.hpp ================================================ #if ! defined SEQ66_QINPUTCHECKBOX_HPP #define SEQ66_QINPUTCHECKBOX_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qinputcheckbox.hpp * * This class supports a MIDI Input check-box, associating it with a * particular input buss. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-05-20 * \updates 2022-01-20 * \license GNU GPLv2 or above * */ #include "qportwidget.hpp" /* seq66::qportwidget base class */ /* * Forward reference. */ class QCheckBox; namespace seq66 { /** * This class represents a single line holding the setting (enabled/disabled * for a single MIDI input buss. */ class qinputcheckbox : public qportwidget { Q_OBJECT public: qinputcheckbox (QWidget * parent, performer & p, int bus); virtual ~qinputcheckbox () { // no code needed } QCheckBox * input_checkbox () { return m_chkbox_inputactive; } private: void setup_ui (); signals: private slots: void input_callback_clicked (int id); private: QCheckBox * m_chkbox_inputactive; }; } // namespace seq66 #endif // SEQ66_QINPUTCHECKBOX_HPP /* * qinputcheckbox.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qlfoframe.hpp ================================================ #if ! defined SEQ66_QLFOFRAME_HPP #define SEQ66_QLFOFRAME_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qlfoframe.hpp * * This module declares/defines the base class for the LFO window. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2025-07-13 * \license GNU GPLv2 or above * * Provides a way to modulate MIDI controller events. */ #include #include "play/seq.hpp" /* seq66::seq::pointer & sequence */ /* * Forward declarations for Qt. */ class QButtonGroup; class QLineEdit; /* * This is necessary to keep the compiler from thinking Ui::qlfoframe * would be found in the seq66 namespace. */ namespace Ui { class qlfoframe; } namespace seq66 { class performer; class qseqdata; class qseqeditframe64; /** * This class is the Qt 5 version of the lfownd class. It has one important * difference, in that the wave type is chosen via radio-buttons, rather than * a slider. And the numbers can be edited directly. */ class qlfoframe final : public QFrame { friend class qseqeditframe64; Q_OBJECT public: qlfoframe ( performer & p, sequence & s, qseqdata & sdata, qseqeditframe64 * editparent = nullptr, QWidget * parent = nullptr ); virtual ~qlfoframe (); /* * Not yet implemented ! * * void toggle_visible (); */ protected: virtual void closeEvent (QCloseEvent *) override; private: performer & perf () { return m_performer; } sequence & track () { return m_seq; } /** * Converts a slider value to a double value. Slider values are a 100 * times (m_scale_factor) what they need to be. */ double to_double (int v) { return double(v) / m_scale_factor; } /** * Converts a double value to a slider value. */ int to_slider (double v) { return int(v * double(m_scale_factor) + 0.5); } void set_value_text (double value, QLineEdit * textline); void wave_type_change (int waveid); signals: private slots: /* * We use a lambda function for the slot for the QButtonGroup :: * buttonClicked() signal that sets the wave-form to use. */ void scale_lfo_change (); void value_text_change (); void range_text_change (); void speed_text_change (); void phase_text_change (); void use_measure_clicked (int state); void multiply_clicked (int state); void lock (); void reset (); private: /** * The use Qt user-interface object pointer. */ Ui::qlfoframe * ui; /** * Provides a way to treat the wave radio-buttons as a group. Had issues * trying to set this up in Qt Creator. To get the checked value, * use its checkedButton() function. */ QButtonGroup * m_wave_group; /** * Access to the performance controller. */ performer & m_performer; /** * The sequence associated with this window. */ sequence & m_seq; /** * The qseqdata associated with this window. */ qseqdata & m_seqdata; /** * Holds the original data in order to allow for a complete undo of the * changes. */ eventlist m_backup_events; /** * The seqedit frame that owns (sort of) this LFO window. */ qseqeditframe64 * m_edit_frame; /** * We need a scale factor in order to use the integer values of the * sliders with two digits of precision after the decimal. */ const int m_scale_factor = 100; /** * Value. Ranges from 0.0 to 127.0. It is initialized to its starting * value, 64.0. Also defined are the minimum and maximum, but as statics * in the cpp module. */ double m_value; /** * Range. Ranges from 0.0 to 127.0. It is initialized to its starting * value, 64.0. Also defined are the minimum and maximum, but as statics * in the cpp module. */ double m_range; /** * Speed. */ double m_speed; /** * Phase. */ double m_phase; /** * Wave type from the calculations module. */ waveform m_wave; /** * If true, use the measure as the range for periodicity, as opposed to * the full length of the pattern. */ bool m_use_measure; /** * If true, rather than generating the waveform in MIDI events, use * the waveform to scale the existing events. */ bool m_multiply; /** * Indicates that the events of the pattern are irrevocably modified. * We could use an undo stack. Worth it? */ bool m_modify_locked; /** * Indicates the LFO modified status. */ bool m_is_modified; }; // class qlfoframe } // namespace seq66 #endif // SEQ66_QLFOFRAME_HPP /* * qlfoframe.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qliveframeex.hpp ================================================ #if ! defined SEQ66_QLIVEFRAMEEX_HPP #define SEQ66_QLIVEFRAMEEX_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qliveframeex.hpp * * This module declares/defines the base class for the external * sequence-editing window. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-09-16 * \updates 2021-11-19 * \license GNU GPLv2 or above * * The sequence editing window is known as the "Pattern Editor". Kepler34 * provides an editor embedded within a tab, but we supplement that with a * more sophisticated external editor, which works a lot more like the Gtkmm * seqedit class. */ #include class QCloseEvent; namespace Ui { class qliveframeex; } namespace seq66 { class performer; class sequence; class qslivebase; class qsmainwnd; /** * Provides a container for a qslivebase (the base class for qslivegrid) * object. Thus, the Qt 5 version of Seq66 has an (additional) external * seqedit window like its Gtkmm-2.4 counterpart. */ class qliveframeex final : public QWidget { Q_OBJECT public: qliveframeex ( performer & p, int ssnum, qsmainwnd * parent = nullptr ); virtual ~qliveframeex (); void update_draw_geometry (); void update_sequence (int seqno, bool redo); protected: virtual void closeEvent (QCloseEvent *) override; virtual void changeEvent (QEvent * event) override; const performer & perf () const { return m_performer; } performer & perf () { return m_performer; } signals: private slots: private: Ui::qliveframeex * ui; performer & m_performer; int m_screenset; qsmainwnd * m_live_parent; qslivebase * m_live_frame; }; // class qliveframeex } // namespace seq66 #endif // SEQ66_QLIVEFRAMEEX_HPP /* * qliveframeex.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qloopbutton.hpp ================================================ #if ! defined SEQ66_QLOOPBUTTON_HPP #define SEQ66_QLOOPBUTTON_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qloopbutton.hpp * * This module declares/defines the base class for drawing on a pattern-slot * button. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-06-28 * \updates 2025-04-27 * \license GNU GPLv2 or above * */ #include #include "qslotbutton.hpp" /* seq66::qslotbutton base class */ namespace seq66 { class performer; /** * This class provides for drawing text and a progress bar on a qslotbutton. */ class qloopbutton final : public qslotbutton { public: class textbox { friend class qloopbutton; private: int m_x, m_y, m_w, m_h, m_flags; std::string m_label; public: textbox (); void set ( int x, int y, int w, int h, int flags, std::string label ); }; // nested class textbox class progbox { friend class qloopbutton; private: int m_x; /**< x coordinate in the button as per the width. */ int m_y; /**< y coordinate in the button as per the height. */ int m_w; /**< The actual width based on button and x value. */ int m_h; /**< Actual heighth based on button and y value. */ int m_center_x; /**< The center of the progress box width. */ int m_center_y; /**< The center of the progress box height. */ public: progbox (); progbox (const progbox &) = default; progbox & operator = (const progbox &) = default; void set (int w, int h); int x () const { return m_x; } int y () const { return m_y; } int w () const { return m_w; } int h () const { return m_h; } int center_x () const { return m_center_x; } int center_y () const { return m_center_y; } }; // nested class progbox private: /** * Allows for tailorable progress-box sizes as a percentage of the button * size. * * Horizontal: 0.50 to 0.80 * Vertical: 0.10 to 0.40 with 0 to turn off drawing it. */ static bool sm_draw_progress_box; static double sm_progress_w_fraction; static double sm_progress_h_fraction; static const int scm_progress_event_margin; static const int sm_vert_draw_text_threshold; static const int sm_vert_compressed_threshold; static const int sm_horiz_compressed_threshold; static const int sm_base_height; static const float sm_left_width_factor; static const float sm_right_width_factor; /** * Provides a buffer that represents a condensed version of long * patterns, so that we don't have to waste time drawing hundreds of * events in the tiny box centered in the pattern button. */ bool m_show_average; bool m_fingerprint_inited; bool m_fingerprinted; size_t m_fingerprint_size; std::vector m_fingerprint; std::vector m_fingerprint_count; /** * Optional scaling for the notes in the progress box, to give a more * realistic depiction of the pitches. */ int m_note_min; int m_note_max; /** * Provides a pointer to the sequence displayed by this button. Note that * we do not want to use a shared pointer. First, semantically this button * does not own the sequence, and second, there seems to be a race * condition (valgrind.log) that causes this pointer to be deleted * completely before performer can reset the sequence. * * seq::pointer m_seq; * sequence * m_seq; */ seq::pointer m_seq; /** * Checked status. Flat status. */ bool m_is_checked; bool m_is_flat; /** * Holds the value of usr().progress_box_show_cc(). */ bool m_show_cc; /** * The thickness of the vertical progress bar, either 1 or 2. */ int m_prog_thickness; /** * Provides the background color of the progress bar. */ Color m_prog_back_color; /** * Provides the foreground color of the progress bar. */ Color m_prog_fore_color; /** * The font for drawing text. */ QFont m_text_font; /** * Text and progress-box support members. */ bool m_text_initialized; bool m_draw_text; textbox m_top_left; textbox m_top_right; textbox m_bottom_left; textbox m_bottom_right; progbox m_progress_box; progbox m_event_box; bool m_use_gradient; public: qloopbutton ( const qslivegrid * const slotparent, seq::number slotnumber, const std::string & label, const std::string & hotkey, seq::pointer seqp, QWidget * parent = nullptr ); virtual ~qloopbutton () { // no code needed } bool use_gradient () const { return m_use_gradient; } static bool boxes_initialized (bool reset = false); static void progress_box_size (double w, double h); virtual seq::pointer loop () override { return m_seq; } virtual void setup () override; virtual void reupdate (bool all = true) override; virtual void set_checked (bool flag) override; virtual void set_flat (bool flag) override; virtual bool toggle_enabled () override; virtual bool toggle_checked () override; protected: void draw_progress (QPainter & p, midipulse tick, bool tiny = false); void draw_progress_box (QPainter & painter); void draw_pattern (QPainter & painter); void initialize_fingerprint (); private: /* * Painting event to draw on the button surface. Called automatically when * the layout-grid is drawn. */ virtual void paintEvent (QPaintEvent *) override; virtual void focusInEvent (QFocusEvent *) override; virtual void focusOutEvent (QFocusEvent *) override; virtual void resizeEvent (QResizeEvent *) override; private: bool initialize_text (); }; // class qloopbutton } // namespace seq66 #endif // SEQ66_QLOOPBUTTON_HPP /* * qloopbutton.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qmutemaster.hpp ================================================ #if ! defined SEQ66_QMUTEMASTER_HPP #define SEQ66_QMUTEMASTER_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qmutemaster.hpp * * This module declares/defines the base class for the screen-set manager. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-05-29 * \updates 2023-10-09 * \license GNU GPLv2 or above * * We want to be able to survey the existing mute-groups. */ #include #include /* std::vector container */ #include "ctrl/keycontainer.hpp" /* class seq66::keycontainer */ #include "ctrl/opcontainer.hpp" /* class seq66::opcontainer */ #include "play/mutegroups.hpp" /* seq66::mutegroup, mutegroups */ #include "play/performer.hpp" /* seq66::performer class */ #include "play/setmapper.hpp" /* seq66::setmapper class */ /** * Forward references. */ class QPushButton; class QTableWidgetItem; class QTimer; /* * This is necessary to keep the compiler from thinking Ui::qmutemaster * would be found in the seq66 namespace. */ namespace Ui { class qmutemaster; } namespace seq66 { class qsmainwnd; /** * This class helps manage screensets, including selecting the current * playscreen and showing, in brief form, the contents of each set. */ class qmutemaster final : public QFrame, protected performer::callbacks { friend class qsmainwnd; friend class qseditoptions; private: /** * Provides human-readable names for the columns of the set table. */ enum class column_id { group_number, group_count, group_keyname, group_name }; enum class enabling { disable, leave, enable }; private: Q_OBJECT public: qmutemaster ( performer & p, qsmainwnd * mainparent, QWidget * parent = nullptr ); virtual ~qmutemaster(); void reload_mute_groups (); protected: virtual bool on_mutes_change ( mutegroup::number setno, performer::change mod ) override; /* * virtual bool on_group_learn (bool state) override; * virtual bool on_sequence_change (seq::number seqno, ...) override; */ protected: // overrides of event handlers virtual void closeEvent (QCloseEvent *) override; virtual void keyPressEvent (QKeyEvent *) override; virtual void keyReleaseEvent (QKeyEvent *) override; virtual void changeEvent (QEvent *) override; private: void modify_rc (); void modify_mutes (); void unmodify_mutes (); void reset () { m_is_initialized = false; } private: bool needs_update () const { bool result = m_needs_update; m_needs_update = false; return result; } void group_needs_update () { m_needs_update = true; } void create_group_buttons (); void update_group_buttons (enabling tomodify = enabling::leave); void handle_group_button (int row, int column); /* in lambda slot */ void handle_group_change (int groupno); bool group_control ( automation::action a, int /*d0*/, int index, bool inverse ); int current_group () const { return m_current_group; } bool set_current_group (int row); void create_pattern_buttons (); void update_pattern_buttons (enabling tomodify = enabling::leave); void handle_pattern_button (int row, int column); /* in lambda slot */ bool trigger () const { return m_trigger_active; } void trigger (bool flag) { m_trigger_active = flag; } void set_bin_hex (bool bin_checked); void modify_mutes_file (bool flag); void set_column_widths (int total_width); void setup_table (); bool initialize_table (); bool group_line ( mutegroup::number row, int mutecount, const std::string & keyname, const std::string & groupname ); #if defined PASS_KEYSTROKES_TO_PARENT bool handle_key_press (const keystroke & k); bool handle_key_release (const keystroke & k); #endif QTableWidgetItem * cell (screenset::number row, column_id col); void clear_pattern_mutes (); bool load_mutegroups (const std::string & fullfilespec); bool save_mutegroups (const std::string & fullfilespec); // UNUSED void modify_midi (); signals: private slots: void conditional_update (); void slot_table_click ( int row, int /*column*/, int /*prevrow*/, int /*prevcolumn*/ ); void slot_clear_all_mutes (); void slot_cell_changed (int row, int column); void slot_bin_mode (bool ischecked); void slot_hex_mode (bool ischecked); void slot_trigger (); #if defined USE_MUTES_FILE_TEXTEDIT void slot_mutes_file_modify (); #endif #if defined USE_GROUP_UPDATE_BUTTON void slot_set_mutes (); #endif #if defined USE_REMOVED_MUTEMASTER_BUTTONS void slot_pattern_offset (int index); void slot_fill_mutes (); void slot_down (); void slot_up (); #endif void slot_write_to_midi (); void slot_write_to_mutes (); void slot_strip_empty (); void slot_load_mutes (); void slot_load_midi (); void slot_toggle_active (); void slot_load (); void slot_save (); private: /** * The use Qt user-interface object pointer. */ Ui::qmutemaster * ui; /** * A timer for refreshing the frame as needed. */ QTimer * m_timer; /** * The main window that owns this window. */ qsmainwnd * m_main_window; /** * Set at the end of the constructor to avoid spurious modification * flagging. */ bool m_is_initialized; /** * Indicates if "To MIDI" and "To Mutes" are active. */ bool m_to_midi_active; bool m_to_mutes_active; /** * Access to buttons, more flexible for swapping coordinates. */ using buttons = std::vector; /** * Access to all the mute-group buttons. This is an array forever fixed to * 4 x 8, because that's about all the keystrokes we have available to * allocate to mute-groups. We would use mutegroups::Rows() and * mutegroups::Columns(), but C++ does not allow functions as array sizes. */ buttons m_group_buttons; /** * Access to all the pattern buttons. It is the same size as the group * grid, but might be page-able in the future. See the pending * m_pattern_offset member. */ buttons m_pattern_buttons; /** * Indicates the currently-selected group number. */ int m_current_group; /** * Indicates the number of groups in the grid. Essentially constant at 4 * x 8 = 32. */ int m_group_count; /** * Holds the row and column of the button corresponding to the * currently-selected mute-group. */ int m_button_row; int m_button_column; /** * If true, button click can activate existing mute groups. * Indicates that the group buttons are enabled, but will "only" trigger * the clicked mute-group. */ bool m_trigger_active; /** * Indicates that the view should be refreshed. */ mutable bool m_needs_update; /** * Holds the current status of all of the pattern buttons in the * currently active mute-group in the user-interface. */ midibooleans m_pattern_mutes; /** * A future feature to allow for slot shifting to handle set sizes like 64 * and 96. */ seq::number m_pattern_offset; }; } // namespace seq66 #endif // SEQ66_QMUTEMASTER_HPP /* * qmutemaster.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qpatternfix.hpp ================================================ #if ! defined SEQ66_QPATTERNFIX_HPP #define SEQ66_QPATTERNFIX_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qpatternfix.hpp * * This module declares/defines the base class for the pattern-fix window. * * \library seq66 application * \author Chris Ahlstrom * \date 2022-04-09 * \updates 2025-06-14 * \license GNU GPLv2 or above * * Provides a way to modulate MIDI controller events. */ #include #include "midi/calculations.hpp" /* seq66::lengthfix, alteration */ #include "play/seq.hpp" /* seq66::seq::pointer & sequence */ /* * Forward declarations for Qt. */ class QButtonGroup; class QLineEdit; /* * This is necessary to keep the compiler from thinking Ui::qpatternfix * would be found in the seq66 namespace. */ namespace Ui { class qpatternfix; } namespace seq66 { class performer; class qseqdata; class qseqeditframe64; class qstriggereditor; /** * This class is the Qt 5 version of the lfownd class. It has one important * difference, in that the wave type is chosen via radio-buttons, rather than * a slider. And the numbers can be edited directly. */ class qpatternfix final : public QFrame { Q_OBJECT public: qpatternfix ( performer & p, sequence & s, qseqeditframe64 * editparent, QWidget * parent = nullptr ); virtual ~qpatternfix (); bool modified () const { return m_is_modified; } void modify (); void unmodify (bool reset_fields = true); protected: virtual void closeEvent (QCloseEvent *) override; private: performer & perf () { return m_performer; } sequence & track () { return m_seq; } void set_dirty (); void set_value_text (double value, QLineEdit * textline); void wave_type_change (int waveid); void initialize (bool startup); signals: private slots: /* * We use lambda functions for the slot for the QButtonGroup :: * buttonClicked() signals. */ void slot_effect_clear (); void slot_length_fix (int fixlengthid); void slot_measure_change (); void slot_scale_change (); void slot_alt_change (int altid); void slot_tighten_change (); void slot_full_change (); void slot_jitter_change (); void slot_random_change (); void slot_random_pitch_change (); void slot_notemap_file (); void slot_align_left_change (int dummy); void slot_align_right_change (int dummy); void slot_reverse_change (int dummy); void slot_reverse_in_place (int dummy); void slot_save_note_length (int dummy); void slot_set (); void slot_reset (); private: /** * The use Qt user-interface object pointer. */ Ui::qpatternfix * ui; /** * To access the radio-buttons in the GroupBox, ui->group_box_length, we * need to create a button-group. */ QButtonGroup * m_fixlength_group; /** * Access to radio-buttons for alteration. */ QButtonGroup * m_alt_group; /** * Access to the performance controller. */ performer & m_performer; /** * The sequence associated with this window. */ sequence & m_seq; /** * Holds the original data in order to allow for a complete undo of the * changes. */ eventlist m_backup_events; /** * Holds the original pattern length in measures. */ int m_backup_measures; /** * Holds the original beats per bar. */ int m_backup_beats; /** * Holds the original beat width. */ int m_backup_width; /** * The seqedit frame that owns (sort of) this LFO window. */ qseqeditframe64 * m_edit_frame; /** * The current way the user has selected to fix the length. */ lengthfix m_length_type; /** * The current way the user has selected for alteration. */ alteration m_alt_type; /** * The range of tightening to apply. Normally this is snap() / 2. */ int m_tighten_range; /** * The range of full quantization to apply. Normally this is snap(). */ int m_full_range; /** * The range of amplitude randomization to apply. For control events, * this is a magnitude of the control. For notes, it is the velocity. * Program change need not apply. :-) */ int m_random_range; /** * The range of note randomization to apply to note events. */ int m_pitch_range; /** * The range of jitter to apply. Here, jitter is a randomization of * the event time-stamp by plus/minus a value in the range of the jitter. * Defaults to usr().jitter_range(ppqn() / 4). */ int m_jitter_range; /** * Holds the file-name of the notemap file, which is a '.drums' file, * but we also want to support '.notemap'. */ std::string m_notemap_file; /** * If true, use reverse-mapping via the note-map file. */ bool m_reverse_notemap; /** * The current number of measures for the adjustment. This is a double * so that it can be fractional, such as "3/4" --> 0.75. But otherwise, * it must be an integer (truncated) value. */ double m_measures; /** * The current scale factor in the user-interface. */ double m_scale_factor; /** * Indicates if left-alignment of the pattern is specified. */ bool m_align_left; /** * Indicates if right-alignment of the pattern is specified. */ bool m_align_right; /** * Reverses the timestamps of event, while preserving the duration of the * notes. The new timestamp is the distance of the event from the end * (length) of the pattern, which we call the "reference". */ bool m_reverse; /** * Similar to reverse, except that the last event is used as the * "reference" (instead of the pattern length). */ bool m_reverse_in_place; /** * Indicates to preserve note length when rescaling. Otherwise, the * end-time of the note is scaled as well. */ bool m_save_note_length; /** * Indicates to treat the measures text like a time signature. * Triggered by the presence of a "/" and valid beats and width. */ bool m_use_time_sig; /** * Time signature beats. */ int m_time_sig_beats; /** * Time signature beats. */ int m_time_sig_width; /** * Indicates the modified status of the user interface. * The performer::modify() status is called only when the Set * button is pushed. */ bool m_is_modified; /** * Indicates if the pattern was modified before the dialog was opened. * If not, it is safe to unmodify it in slot_reset(). */ bool m_was_clean; }; // class qpatternfix } // namespace seq66 #endif // SEQ66_QPATTERNFIX_HPP /* * qpatternfix.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qperfbase.hpp ================================================ #if ! defined SEQ66_QPERFBASE_HPP #define SEQ66_QPERFBASE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qperfbase.hpp * * This module declares/defines the base class for the various song panes * of Seq66's Qt 5 version. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-07-14 * \updates 2025-04-30 * \license GNU GPLv2 or above * * This class is the base class for qperfroll, qperfnames, and qperftime. */ #include "qeditbase.hpp" /* seq66:qeditbase base class */ namespace seq66 { class performer; class sequence; /** * Provides constants for the perfroll object (performance editor). * Note the current dependence on the width of a font pixmap's character! * So we will use the font's numeric accessors soon. */ const int c_names_x = 6 * 24; /* used in qperfroll, qperfnames */ const int c_names_y = 22; /* used in qperfroll, qperfnames */ const int c_perf_scale_x = 32; /* units are ticks per pixel */ /** * The MIDI note grid in the sequence editor */ class qperfbase : public qeditbase { private: /** * Set size. Holds the 'usr' value for easy access. */ int m_set_size; /** * Allows for expansion of the song-editor horizontally. Starts out * at 1.25. See qperfbase::horizSizeHint(). */ float m_width_factor = 1.25f; /** * Provides the height of the track and names displays. Starts at * c_name_y, and can be halved or doubled from that. We could allow it * to be more than doubled, but that doesn't seem necessary. A height * less than half is unworkable. */ int m_track_height; /** * Indicates if the track height is halved. */ bool m_track_thin; /** * Indicates if the track height is doubled. */ bool m_track_thick; public: qperfbase ( performer & perf, int zoom = c_default_perf_zoom, int snap = c_default_snap, int unitheight = 1, int totalheight = 1 ); int set_size () const { return m_set_size; } bool track_thin () const { return m_track_thin; } bool track_thick () const { return m_track_thick; } int track_height () const { return m_track_height; } void increment_width () { m_width_factor += 0.50f; } float width_factor () const { return m_width_factor; } protected: virtual int horizSizeHint () const override; void force_resize (QWidget *); void set_size (int sz) { m_set_size = sz; } void set_thin () { m_track_height = c_names_y / 2; m_track_thick = false; m_track_thin = true; } void set_thick () { m_track_height = c_names_y * 2; m_track_thick = true; m_track_thin = false; } void set_normal () { m_track_height = c_names_y; m_track_thick = false; m_track_thin = false; } protected: void convert_x (int x, midipulse & tick); void convert_xy (int x, int y, midipulse & ticks, int & seq); void convert_ts (midipulse ticks, int seq, int & x, int & y); void convert_ts_box_to_rect ( midipulse tick_s, midipulse tick_f, int seq_h, int seq_l, seq66::rect & r ); }; // class qperfbase } // namespace seq66 #endif // SEQ66_QPERFBASE_HPP /* * qperfbase.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qperfeditex.hpp ================================================ #if ! defined SEQ66_QPERFEDITEX_HPP #define SEQ66_QPERFEDITEX_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qperfeditex.hpp * * This module declares/defines the base class for the external * performance-editing window. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-07-21 * \updates 2018-10-15 * \license GNU GPLv2 or above * * The performance editing window is known as the "Song Editor". Kepler34 * provides an editor embedded within a tab, but we supplement that with a * more sophisticated external editor, which works a lot more like the Gtkmm * perfedit class. */ #include /* * Forward reference. */ class QCloseEvent; namespace Ui { class qperfeditex; } namespace seq66 { class performer; class sequence; class qperfeditframe64; class qperfnames; class qperfroll; class qsmainwnd; /** * Provides a container for a qperfeditframe64 object. Thus, the Qt 5 version * of Seq66 has an external seqedit window like its Gtkmm-2.4 * counterpart. */ class qperfeditex final : public QWidget { friend class qsmainwnd; Q_OBJECT public: qperfeditex (performer & p, qsmainwnd * parent = nullptr); virtual ~qperfeditex (); void update_sizes (); void set_loop_button (bool looping); protected: virtual void closeEvent (QCloseEvent *) override; private: const performer & perf () const { return m_performer; } performer & perf () { return m_performer; } qperfroll * perf_roll (); qperfnames * perf_names (); signals: private slots: private: Ui::qperfeditex * ui; performer & m_performer; qsmainwnd * m_edit_parent; qperfeditframe64 * m_edit_frame; }; // class qperfeditex } // namespace seq66 #endif // SEQ66_QPERFEDITEX_HPP /* * qperfeditex.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qperfeditframe64.hpp ================================================ #if ! defined SEQ66_QPERFEDITFRAME64_HPP #define SEQ66_QPERFEDITFRAME64_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qperfeditframe64.hpp * * This module declares/defines the base class for the Performance Editor, * also known as the Song Editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-07-18 * \updates 2025-07-17 * \license GNU GPLv2 or above * * Note that the z and Z keys, when focus is on the perfroll (piano roll), * will zoom the view horizontally. Other keys are also available. */ #include #include "cfg/settings.hpp" /* seq66::combolist class, helpers */ #include "midi/midibytes.hpp" /* seq66::midipulse alias */ #include "qscrollmaster.h" /* qscrollmaster::dir enum class */ namespace Ui { class qperfeditframe64; } namespace seq66 { class performer; class qperfeditex; class qperfnames; class qperfroll; class qperftime; /** * This class is an improved version of qperfeditframe. */ class qperfeditframe64 final : public QFrame { friend class qperfeditex; friend class qperfroll; friend class qsmainwnd; private: Q_OBJECT public: qperfeditframe64 ( performer & p, QWidget * parent, bool isexternal = false ); virtual ~qperfeditframe64 (); bool is_external () const { return m_is_external; } int get_beat_width () const { return m_beat_width; } void set_beat_width (int bw) { m_beat_width = bw; set_guides(); } int get_beats_per_measure () const { return m_beats_per_measure; } void set_beats_per_measure (int bpm) { m_beats_per_measure = bpm; set_guides(); } void follow_progress (); void scroll_to_tick (midipulse tick); void update_sizes (); void set_dirty (); void set_loop_button (bool looping); private: void set_snap (midipulse s); void set_guides (); const combolist & snap_list () const { return m_snap_list; } performer & perf () { return m_mainperf; } qperfroll * perf_roll () { return m_perfroll; } qperfnames * perf_names () { return m_perfnames; } bool zoom_in (); bool zoom_out (); bool set_zoom (int z); bool reset_zoom (); protected: void set_transpose (int transpose); void update_entry_mode (bool on); void scroll_by_step (qscrollmaster::dir d); void adjust_for_zoom (int zprevious); bool zoom_key_press (bool shifted, int key); protected: // overrides of event handlers virtual void keyPressEvent (QKeyEvent *) override; virtual void keyReleaseEvent (QKeyEvent *) override; private slots: void update_grid_snap (int snapindex); void slot_zoom_in (); void slot_zoom_out (); void reset_transpose (); void update_transpose (int index); void marker_collapse (); void marker_expand (); void marker_expand_copy (); void marker_loop (bool loop); void grow (); void follow (bool ischecked); void entry_mode (bool ischecked); void slot_duration (bool ischecked); void reset_trigger_transpose (bool ischecked); void set_trigger_transpose (int tpose); bool v_zoom_in (); bool v_zoom_out (); bool reset_v_zoom (); private: Ui::qperfeditframe64 * ui; performer & m_mainperf; QPalette * m_palette; bool m_is_external; bool m_duration_mode; /* true == H:M:S.fraction */ bool m_move_L_marker; combolist m_snap_list; int m_snap; /* set snap-to in pulses/ticks */ int m_beats_per_measure; int m_beat_width; int m_trigger_transpose; qperfroll * m_perfroll; qperfnames * m_perfnames; qperftime * m_perftime; }; // class qperfeditframe64 } // namespace seq66 #endif // SEQ66_QPERFEDITFRAME64_HPP /* * qperfeditframe64.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qperfnames.hpp ================================================ #if ! defined SEQ66_QPERFNAMES_HPP #define SEQ66_QPERFNAMES_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qperfnames.hpp * * This module declares/defines the base class for performance names. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2025-06-15 * \license GNU GPLv2 or above * */ #include #include "qperfbase.hpp" /* for constants and base class */ #include "gui_palette_qt5.hpp" /* gui_pallete_qt5::Color etc. */ namespace seq66 { class performer; class qperfeditframe64; /** * Sequence labels for the side of the song editor */ class qperfnames final : public QWidget, public qperfbase { friend class qperfeditframe64; friend class qperfroll; Q_OBJECT public: qperfnames (performer & p, QWidget * parent); virtual ~qperfnames () { // no code } void resize () { force_resize(this); } bool use_gradient () const { return m_use_gradient; } protected: void reupdate (); void set_preview_row (int row); int name_x (int i) { return m_nametext_x + i; } int name_y (int i) { return track_height() * i; } protected: // Qt overrides virtual void paintEvent (QPaintEvent *) override; virtual void keyPressEvent (QKeyEvent *) override; virtual void mousePressEvent (QMouseEvent *) override; virtual void mouseReleaseEvent (QMouseEvent *) override; virtual void mouseMoveEvent (QMouseEvent *) override; virtual void mouseDoubleClickEvent (QMouseEvent *) override; virtual QSize sizeHint () const override; virtual void wheelEvent (QWheelEvent *) override; private: int convert_y (int y); const Color & preview_color () const { return m_preview_color; } signals: /* * Open or create an editor window for the selected pattern. */ void signal_call_editor_ex (int seqid, bool active); private slots: /* * void conditional_update (); */ private: QFont m_font; int m_nametext_x; Color m_preview_color; /* will reduce its alpha value */ bool m_is_previewing; int m_preview_row; QLinearGradient m_name_grad; QLinearGradient m_muted_name_grad; bool m_use_gradient; }; // class qperfnames } // namespace seq66 #endif // SEQ66_QPERFNAMES_HPP /* * qperfnames.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qperfroll.hpp ================================================ #if ! defined SEQ66_QPERFROLL_HPP #define SEQ66_QPERFROLL_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qperfroll.hpp * * This module declares/defines the base class for the Qt 5 version of the * Performance window piano roll. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2025-06-15 * \license GNU GPLv2 or above * * This class represents the central piano-roll user-interface area of the * performance/song editor. */ #include #include "qperfbase.hpp" /* seq66::qperfbase base class */ /* * Forward references. */ class QKeyEvent; class QMouseEvent; class QTimer; namespace seq66 { class performer; class qperfeditframe64; class qperfnames; /** * The grid in the song editor for setting out sequences */ class qperfroll final : public QWidget, public qperfbase { friend class qperfeditframe64; /* for scrolling a horizontal page */ Q_OBJECT public: qperfroll ( performer & p, int zoom, int snap = c_default_snap, qperfnames * seqnames = nullptr, qperfeditframe64 * frame = nullptr, QWidget * parent = nullptr ); virtual ~qperfroll (); void set_guides (midipulse snap, midipulse measure, midipulse beat); void set_trigger_transpose (int tpose) { m_trigger_transpose = tpose; } bool v_zoom_in (); bool v_zoom_out (); bool reset_v_zoom (); virtual bool zoom_in () override; virtual bool zoom_out () override; virtual bool reset_zoom (int ppq = 0) override; private: bool in_selection_area (midipulse tick); bool move_by_key (bool forward, bool single = true); int seq_id_from_xy (int /*click_x*/, int click_y); void draw_grid (QPainter & painter, const QRect & r); void draw_triggers (QPainter & painter, const QRect & r); void resize () { force_resize(this); } private: virtual void paintEvent (QPaintEvent *) override; virtual void mousePressEvent (QMouseEvent *) override; virtual void mouseReleaseEvent (QMouseEvent *) override; virtual void mouseMoveEvent (QMouseEvent *) override; virtual void mouseDoubleClickEvent (QMouseEvent *) override; virtual void keyPressEvent (QKeyEvent *) override; virtual void keyReleaseEvent (QKeyEvent *) override; virtual QSize sizeHint () const override; signals: /* * Open or create an editor window for the selected pattern. */ void signal_call_editor_ex (int seqid, bool active); public slots: void undo (); void redo (); void conditional_update (); private: virtual void set_adding (bool adding) override; qperfeditframe64 * frame64 () { return m_parent_frame; } const qperfeditframe64 * frame64 () const { return m_parent_frame; } qperfnames * perf_names () { return m_perf_names; } const qperfnames * perf_names () const { return m_perf_names; } // We could add these functions to performer and here: // // cut_selected_trigger() // copy_selected_trigger() // paste_trigger() // void half_split_trigger (int seq, midipulse tick); void add_trigger (int seq, midipulse tick); void delete_trigger (int seq, midipulse tick); void follow_progress (); private: qperfeditframe64 * m_parent_frame; /** * Holds a pointer to the qperfnames pane that is associated with the * qperfroll piano roll. */ qperfnames * m_perf_names; /** * Pre-allocation of gradient brushes for drawing triggers. */ QLinearGradient m_back_grad; QLinearGradient m_sel_grad; QTimer * m_timer; QFont m_font; int m_trigger_transpose; midipulse m_tick_s; // start of tick window midipulse m_tick_f; // end of tick window int m_seq_h; // highest seq in window int m_seq_l; // lowest seq in window int m_drop_track; // sequence selection midipulse m_drop_tick; midipulse m_drop_tick_offset; // ticks clicked from trigger midipulse m_last_tick; // tick using at last mouse event bool m_box_select; bool m_grow_direction; bool m_adding_pressed; }; // class qperfroll } // namespace seq66 #endif // SEQ66_QPERFROLL_HPP /* * qperfroll.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qperftime.hpp ================================================ #if ! defined SEQ66_QPERFTIME_HPP #define SEQ66_QPERFTIME_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qperftime.hpp * * The time bar shows markers and numbers for the measures of the song, * and also depicts the left and right markers. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2022-08-30 * \license GNU GPLv2 or above * */ #include #include "qperfbase.hpp" /* * Forward references. */ class QPaintEvent; class QMouseEvent; class QTimer; namespace seq66 { class performer; class qperfeditframe64; class qperfeditframe; /** * The time bar for the song editor */ class qperftime final : public QWidget, public qperfbase { friend class qperfeditframe64; /* for scrolling a horizontal page */ Q_OBJECT public: qperftime ( performer & a_perf, int zoom, int snap = c_default_snap, qperfeditframe64 * frame = nullptr, QWidget * parent = nullptr ); virtual ~qperftime (); void set_guides (midipulse snap, midipulse measure, midipulse beat); void resize () { force_resize(this); } private: void increment_size () { // TODO } qperfeditframe64 * frame64() { return m_parent_frame; } const qperfeditframe64 * frame64() const { return m_parent_frame; } protected: // override Qt event handlers virtual void paintEvent (QPaintEvent *) override; virtual void mousePressEvent (QMouseEvent *) override; virtual void mouseReleaseEvent (QMouseEvent *) override; virtual void mouseMoveEvent (QMouseEvent *) override; virtual void keyPressEvent (QKeyEvent *) override; virtual QSize sizeHint () const override; virtual void wheelEvent (QWheelEvent *) override; private: qperfeditframe64 * m_parent_frame; QTimer * m_timer; QFont m_font; bool m_move_L_marker; signals: public slots: void conditional_update (); }; // class qperftime } // namespace seq66 #endif // SEQ66_QPERFTIME_HPP /* * qperftime.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qplaylistframe.hpp ================================================ #if ! defined SEQ66_QPLAYLISTFRAME_HPP #define SEQ66_QPLAYLISTFRAME_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qplaylistframe.hpp * * This module declares/defines the base class for a simple playlist editor * based on Qt 5. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-09-04 * \updates 2023-07-17 * \license GNU GPLv2 or above * */ #include #include "util/basic_macros.hpp" /* nullptr and related macros */ /* * Qt forward references. */ class QTableWidgetItem; class QTimer; namespace Ui { class qplaylistframe; } namespace seq66 { class performer; class qsmainwnd; /** * Provides a frame for the Playlist tab. */ class qplaylistframe final : public QFrame { friend class qsmainwnd; Q_OBJECT private: /** * Provides human-readable names for the columns of the playlist and song * tables. */ enum column_id_t { CID_MIDI_NUMBER, CID_ITEM_NAME }; public: qplaylistframe ( performer & p, qsmainwnd * window, QWidget * frameparent = nullptr ); virtual ~qplaylistframe (); private: void set_row_heights (int height); void set_column_widths (); void reset_playlist (int listindex = 0); void reset_playlist_file_name (); void set_current_playlist (); void set_current_song (); void fill_playlists (); void fill_songs (); QTableWidgetItem * cell (bool isplaylist, int row, column_id_t col); performer & perf () { return m_performer; } bool load_playlist (const std::string & fullfilespec = ""); protected: // overrides of event handlers virtual void keyPressEvent (QKeyEvent *) override; virtual void keyReleaseEvent (QKeyEvent *) override; private: void list_unmodify (); void song_unmodify (); void enable_midi_widgets (bool enable); const qsmainwnd * parent () const { return m_parent; } qsmainwnd * parent () { return m_parent; } signals: private slots: void slot_list_click_ex (int, int, int, int); void slot_song_click_ex (int, int, int, int); void slot_file_create_click(); void slot_list_dir_click (); void slot_list_load_click (); void slot_list_add_click (); void slot_list_modify_click (); void slot_list_remove_click (); void slot_list_save_click (); void slot_song_load_click (); void slot_song_select_click (); void slot_song_add_click (); void slot_song_modify_click (); void slot_song_remove_click (); void slot_playlist_active_click (); void slot_auto_arm_click (); void slot_auto_play_click (); void slot_auto_advance_click (); void conditional_update (); void list_modify (); void list_modify (int); void list_modify (const QString &); void song_modify (); void song_modify (int); void song_modify (const QString &); private: Ui::qplaylistframe * ui; private: /** * A timer for screen refreshing. */ QTimer * m_timer; /** * The performer object. */ performer & m_performer; /** * The main window parent of this frame. */ qsmainwnd * m_parent; /** * Provides the currently-selected single rows for the playlist and song * tables. */ int m_current_list_index; int m_current_song_index; }; // class qplaylistframe } // namespace seq66 #endif // SEQ66_QPLAYLISTFRAME_HPP /* * qplaylistframe.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qportwidget.hpp ================================================ #if ! defined SEQ66_QPORTWIDGET_HPP #define SEQ66_QPORTWIDGET_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qportwidget.hpp * * This base class supports qclocklayout and qinputcheckbox. * * \library seq66 application * \author Chris Ahlstrom * \date 2022-01-20 * \updates 2022-01-20 * \license GNU GPLv2 or above * */ #include namespace seq66 { class performer; class qseditoptions; /** * This class is a widget that supports a row of radio-buttons that let the * user set the type of clocking for each MIDI output buss: * * - Disabled * - Off. * - On (Pos). * - On (Mod). */ class qportwidget : public QWidget { public: qportwidget (QWidget * parent, performer & p, int bus); virtual ~qportwidget () { // no code needed } protected: performer & perf () { return m_performance; } qseditoptions * parent_widget () { return m_parent_widget; } int bus () const { return m_bus; } signals: private slots: private: /** * Provides a reference to the single performer object associated with the * MIDI output buss represented by this layout. One question is will we * have to change the reference to a shared pointer. */ performer & m_performance; /** * Provides the buss number, re 0, of the MIDI I/O bus represented by * this port widget. */ int m_bus; /** * For telling the parent window to change states. */ qseditoptions * m_parent_widget; }; // class qportwidget } // namespace seq66 #endif // SEQ66_QPORTWIDGET_HPP /* * qportwidget.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qsabout.hpp ================================================ #if ! defined SEQ66_QSABOUT_HPP #define SEQ66_QSABOUT_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsabout.hpp * * The time bar shows markers and numbers for the measures of the song, * and also depicts the left and right markers. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2018-03-03 * \license GNU GPLv2 or above * */ #include /* * This is necessary to keep the compiler from thinking Ui::qsabout * should be in the seq66 namespace. */ namespace Ui { class qsabout; } namespace seq66 { class qsabout final : public QDialog { Q_OBJECT public: explicit qsabout (QWidget * parent = nullptr); virtual ~qsabout (); private: Ui::qsabout * ui; }; // class qsabout } // namespace seq66 #endif // SEQ66_QSABOUT_HPP /* * qsabout.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qsappinfo.hpp ================================================ #if ! defined SEQ66_QSAPPINFO_H #define SEQ66_QSAPPINFO_H /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsappinfo.hpp * * This dialog provides some context-specific help. * * \library seq66 application * \author Chris Ahlstrom * \date 2023-08-21 * \updates 2023-09-12 * \license GNU GPLv2 or above * */ #include namespace Ui { class qsappinfo; } namespace seq66 { class qsappinfo final : public QDialog { Q_OBJECT public: explicit qsappinfo (QWidget * parent = nullptr); virtual ~qsappinfo (); private: void open_html ( const std::string & basename, const std::string & comment ); private slots: void slot_common_keys (); void slot_automation_keys (); void slot_seqroll_keys (); void slot_songroll_keys (); void slot_hot_keys (); void slot_mutes_keys (); private: Ui::qsappinfo * ui; }; // class qsappinfo } // namespace seq66 #endif // SEQ66_QSAPPINFO_H /* * qsappinfo.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qsbuildinfo.hpp ================================================ #if ! defined SEQ66_QSBUILDINFO_H #define SEQ66_QSBUILDINFO_H /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsbuildinfo.hpp * * The time bar shows markers and numbers for the measures of the song, * and also depicts the left and right markers. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-05-30 * \updates 2018-05-30 * \license GNU GPLv2 or above * */ #include namespace Ui { class qsbuildinfo; } namespace seq66 { class qsbuildinfo final : public QDialog { Q_OBJECT public: explicit qsbuildinfo (QWidget * parent = nullptr); virtual ~qsbuildinfo (); private: Ui::qsbuildinfo * ui; }; // class qsbuildinfo } // namespace seq66 #endif // SEQ66_QSBUILDINFO_H /* * qsbuildinfo.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qscrollmaster.h ================================================ #ifndef SEQ66_QSCROLLMASTER_H #define SEQ66_QSCROLLMASTER_H /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ /** * \file qscrollmaster.h * * This module declares/defines a class for controlling other QScrollAreas * from this one. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-06-18 * \updates 2024-12-27 * \license GNU GPLv2 or above * */ #include #include #include /* std::list container class */ /* * Forward declarations. The Qt header files are moved into the cpp file. */ class QFrame; class QScrollBar; /* * Note that there is no namespace; the Qt uic specification does not seem to * support them well. Also note the lack of namespace seq66 for this class. */ /** * Derived from QScrollArea, this class provides a way to pass any horizontal * or vertical scrollbar value changes on to one or more other QScrollBars. * Any number (even 0) of horizontal or vertical scrollbars can be added to * this object. See the qseqroll class and the class that creates it, * qseqeditframe64. */ class qscrollmaster : public QScrollArea { public: enum class dir { left, right, up, down }; private: using container = std::list; private: /** * Holds a list of external vertical scroll bars to me maintained. */ container m_v_scrollbars; /** * Holds a list of external horizontal scroll bars to me maintained. */ container m_h_scrollbars; /** * Holds a pointer to this scroll-area's vertical scrollbar. */ QScrollBar * m_self_v_scrollbar; /** * Holds a pointer to this scroll-area's horizontal scrollbar. */ QScrollBar * m_self_h_scrollbar; public: qscrollmaster (QWidget * qf); virtual ~qscrollmaster (); void add_v_scroll (QScrollBar * qsb) { m_v_scrollbars.push_back(qsb); } void add_h_scroll (QScrollBar * qsb) { m_h_scrollbars.push_back(qsb); } QScrollBar * v_scroll () { return m_self_v_scrollbar; } QScrollBar * h_scroll () { return m_self_h_scrollbar; } QSize viewport_size () const { return viewportSizeHint(); } void scroll_to_x (int x); void scroll_x_to_factor (float f); void scroll_x_by_factor (float f); void scroll_x_by_step (dir d); void scroll_to_y (int y); void scroll_y_to_factor (float f); void scroll_y_by_factor (float f); void scroll_y_by_step (dir d); void show_values () const; protected: // QWidget overrides virtual void wheelEvent (QWheelEvent *) override; virtual void scrollContentsBy (int dx, int dy) override; }; // class qscrollmaster #endif // SEQ66_QSCROLLMASTER_H /* * qscrollmaster.h * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qscrollslave.h ================================================ #ifndef SEQ66_QSCROLLSLAVE_H #define SEQ66_QSCROLLSLAVE_H /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ /** * \file qscrollslave.h * * This module declares/defines a class for ignoring arrow events. * * \library seq66 application * \author Chris Ahlstrom * \date 2023-05-23 * \updates 2023-05-24 * \license GNU GPLv2 or above * */ #include #include #include /* std::list container class */ /* * Forward declarations. The Qt header files are moved into the cpp file. */ class QFrame; class QScrollBar; class qscrollmaster; /* * Note that there is no namespace; the Qt uic specification does not seem to * support them well. Also note the lack of namespace seq66 for this class. */ /** * Derived from QScrollArea, this class provides a way to pass any horizontal * or vertical scrollbar value changes on to one or more other QScrollBars. * Any number (even 0) of horizontal or vertical scrollbars can be added to * this object. See the qseqroll class and the class that creates it, * qseqeditframe64. */ class qscrollslave : public QScrollArea { private: /** * An unowned pointer used to pass keystrokes to the scroll master. */ qscrollmaster * m_master; public: qscrollslave (QWidget * qf); virtual ~qscrollslave (); void attach_master (qscrollmaster * qsm); protected: // QWidget overrides virtual void keyPressEvent (QKeyEvent *) override; virtual void keyReleaseEvent (QKeyEvent *) override; virtual void wheelEvent (QWheelEvent *) override; }; // class qscrollslave #endif // SEQ66_QSCROLLSLAVE_H /* * qscrollslave.h * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qseditoptions.hpp ================================================ #if ! defined SEQ66_QSEDITOPTIONS_HPP #define SEQ66_QSEDITOPTIONS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseditoptions.hpp * * This dialog contains many tabs for editing various aspects of * Seq66 operation. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2025-07-26 * \license GNU GPLv2 or above */ #include #include #include "cfg/settings.hpp" /* seq66::combolist class, helpers */ namespace Ui { class qseditoptions; } class QButtonGroup; class QComboBox; class QLineEdit; namespace seq66 { class performer; class qsmainwnd; /** * Provides a dialog class for Edit / Preferences. */ class qseditoptions final : public QDialog { friend class qsmainwnd; Q_OBJECT public: qseditoptions ( performer & perf, QWidget * parent = nullptr ); virtual ~qseditoptions(); void enable_bus_item (int bus, bool enabled); void reload_needed (bool flag); private: void midi_through_check (); void setup_clock_combo_box (int buses); void refresh_clock_combo_box (); void setup_input_combo_box (int buses); void refresh_input_combo_box (); void setup_tab_midi_clock (); void setup_tab_midi_input (); void setup_tab_display (); void setup_tab_jack (); void setup_tab_play_options (); void setup_tab_metronome (); void setup_tab_pattern (); void setup_tab_session (); void exit_required (); void set_enabled (QDialogButtonBox::StandardButton bcode, bool on); void set_text ( QDialogButtonBox::StandardButton bcode, const std::string & text ); void show_button (QDialogButtonBox::StandardButton bcode, bool show); void enable_reload_button (bool flag); void repopulate_channel_menu (int buss); void repopulate_thru_channel_menu (int buss); void modify_rc (); void modify_metronome (bool enablereload = true); void modify_ctrl (); void modify_usr (); void sync (); /* makes dialog reflect internal settings */ void sync_rc (); /* makes dialog reflect internal settings */ void sync_usr (); /* makes dialog reflect internal settings */ void backup (); /* backup preferences for cancel-changes */ bool set_ppqn_combo (); bool set_buffer_size_combo (); void set_scaling_fields (); void set_set_size_fields (); void set_progress_box_fields (); void ui_scaling_helper ( const QString & widthtext, const QString & heighttext ); void show_sets_mode (rcsettings::setsmode sm); void show_start_mode (sequence::playback sm); void show_session (usrsettings::session sm); void state_unchanged (); void state_changed (); void state_applied (); void activate_ctrl_file (); bool load_file_name ( QLineEdit * lineedit, const std::string & fileextension ); bool load_executable_name ( QLineEdit * lineedit, const std::string & fname ); bool reload_needed () const { return m_reload_needed; } const combolist & ppqn_list () const { return m_ppqn_list; } const combolist & buffer_size_list () const { return m_buffer_size_list; } const performer & perf () const { return m_perf; } performer & perf () { return m_perf; } private slots: void slot_sets_mode (int buttonno); void slot_start_mode (int buttonno); void slot_jack_mode (int buttonno); void slot_jack_connect (); void slot_jack_disconnect (); void slot_master_cond (); void slot_time_master (); void slot_transport_support (); void slot_jack_midi (); void slot_jack_auto_connect (); void slot_io_maps (); #if defined SEQ66_ALLOW_PORTMAP_CLEAR void slot_remove_io_maps (); #endif void slot_activate_io_maps (); void slot_session (int buttonno); void slot_nsm_url (); void slot_note_resume (); void slot_ppqn_by_text (const QString & text); void slot_buffer_size_by_text (const QString & text); void slot_use_file_ppqn (); void slot_song_record_snap (); void slot_add_time_sig (); void slot_key_height (); void slot_ui_scaling (); void slot_grid_spacing (); void slot_set_size_rows (); void slot_set_size_columns (); void slot_progress_box_width (); void slot_progress_box_height (); void slot_progress_box_shown (); void slot_fingerprint_size (); #if defined USE_VERBOSE_CHECKBOX void slot_verbose_active_click (); #endif void slot_quiet_active_click (); void slot_load_most_recent_click (); void slot_show_full_paths_click (); void slot_long_buss_names_click (); void slot_pair_buss_names_click (); void slot_lock_main_window_click (); void slot_dark_theme_click (); void slot_swap_coordinates_click (); void slot_bold_grid_slots_click (); void slot_gridlines_thick_click (); void slot_elliptical_click (); void slot_follow_progress_click (); void slot_double_click_edit (); void slot_global_seq_feature (); void slot_rc_save_click (); #if defined USE_RC_NAME_CHANGE void slot_rc_filename (); void slot_load_rc_filename (); #endif void slot_usr_save_click (); #if defined SEQ66_CAN_DEACTIVATE_USR void slot_usr_active_click (); #endif void slot_usr_filename (); void slot_load_usr_filename (); void slot_mutes_save_click (); void slot_mutes_active_click (); void slot_mutes_filename (); void slot_load_mutes_filename (); void slot_playlist_save_click (); void slot_playlist_active_click (); void slot_playlist_filename (); void slot_load_playlist_filename (); void slot_ctrl_active_click (); #if defined SEQ66_CAN_SAVE_CTRL void slot_ctrl_save_click (); #endif void slot_ctrl_filename (); void slot_load_ctrl_filename (); void slot_drums_active_click (); #if defined SEQ66_CAN_SAVE_DRUMS void slot_drums_save_click (); #endif void slot_drums_filename (); void slot_load_drums_filename (); void slot_stylesheet_active_click (); void slot_stylesheet_filename (); void slot_load_stylesheet_filename (); void slot_browser_executable (); void slot_load_browser_executable (); void slot_pdf_executable (); void slot_load_pdf_viewer_executable (); void slot_patches_save_now_click (); void slot_patches_active_click (); void slot_patches_filename (); void slot_load_patches_filename (); void slot_palette_save_now_click (); void slot_palette_save_inverse (); void slot_palette_active_click (); void slot_palette_filename (); void slot_load_palette_filename (); void slot_clock_start_modulo (int arg); void slot_output_bus (int arg); void slot_output_bus_enable (); void slot_input_bus (int arg); void slot_input_bus_enable (); void slot_bpm_precision (int index); void slot_tempo_track (); void slot_tempo_track_set (); void slot_buss_override (); void slot_record_by_buss (); void slot_record_by_channel (); void slot_virtual_ports (); void slot_enable_virtual_ports (); void slot_virtual_out_count (); void slot_virtual_in_count (); void slot_metro_beats_per_bar (); void slot_metro_beat_width (); void slot_metro_main_patch (); void slot_metro_main_note (); void slot_metro_main_velocity (); void slot_metro_main_fraction (); void slot_metro_sub_patch (); void slot_metro_sub_note (); void slot_metro_sub_velocity (); void slot_metro_sub_fraction (); void slot_metro_buss (int index); void slot_metro_channel (int index); void slot_metro_count_in (); void slot_metro_count_in_measures (); void slot_metro_recording (); void slot_metro_recording_measures (); void slot_metro_record_buss (int index); void slot_metro_thru_buss (int index); void slot_metro_thru_channel (int index); void slot_metro_reload (); void slot_escape_pattern (); void slot_convert_to_smf_1 (); void slot_pattern_arm (); void slot_pattern_tighten (); void slot_pattern_qrecord (); void slot_pattern_notemap (); void slot_pattern_new_only (); void slot_pattern_record (); void slot_pattern_thru (); void slot_pattern_wraparound (); void slot_record_style (int index); void slot_jitter (int jitr); void slot_amplitude (int jitr); void okay (); void cancel (); void apply (); void reset (); private: Ui::qseditoptions * ui; QButtonGroup * m_live_song_buttons; qsmainwnd * m_parent_widget; performer & m_perf; combolist m_ppqn_list; combolist m_buffer_size_list; bool m_is_initialized; int m_inbus_count; int m_outbus_count; /* * Backup variables for settings. */ rcsettings m_backup_rc; usrsettings m_backup_usr; /** * Indicates that a reload is necessary for at least one important * setting. */ bool m_reload_needed; }; // class qseditoptions } // namespace seq66 #endif // SEQ66_QSEDITOPTIONS_HPP /* * qseditoptions.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qseqbase.hpp ================================================ #if ! defined SEQ66_QSEQBASE_HPP #define SEQ66_QSEQBASE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqbase.hpp * * This module declares/defines the base class for the various editing panes * of Seq66's Qt 5 version. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-06-20 * \updates 2023-09-11 * \license GNU GPLv2 or above * * This class is a base class for qseqroll, qseqdata, qtriggereditor, and * qseqtime, and the four panes of the qseqeditframe64 class. Used as a * mix-in class. */ #include "play/seq.hpp" /* seq66::seq::pointer */ #include "qeditbase.hpp" /* seq66::qbase basic UI date */ namespace seq66 { class qseqeditframe64; /** * The MIDI note grid in the sequence editor */ class qseqbase : public qeditbase { private: /** * Holds a pointer to the edit-frame window. We now have only one, more * adaptable, seqedit frame to worry about. */ qseqeditframe64 * m_parent_frame; /** * Provides a reference to the sequence represented by piano roll. */ sequence & m_seq; /** * Tells where the dragging started, the x value. */ int m_move_delta_x; /** * Tells where the dragging started, the y value. */ int m_move_delta_y; /** * This item is used in the fruityseqroll module. */ int m_move_snap_offset_x; public: qseqbase ( performer & p, sequence & s, qseqeditframe64 * frame, int zoom, int snap = c_default_snap, int unit_height = 1, int total_height = 1 ); virtual bool check_dirty () const override; bool mark_unmodified (); bool mark_modified (); void set_measures (int len); int get_measures (); protected: int move_delta_x () const { return m_move_delta_x; } int move_delta_y () const { return m_move_delta_y; } int move_snap_offset_x () const { return m_move_snap_offset_x; } qseqeditframe64 * frame64 () { return m_parent_frame; } const qseqeditframe64 * frame64 () const { return m_parent_frame; } void move_delta_x (int v) { m_move_delta_x = v; } void move_delta_y (int v) { m_move_delta_y = v; } void move_snap_offset_x (int v) { m_move_snap_offset_x = v; } /* * We are not the owner of this shared pointer. */ const sequence & track () const { return m_seq; } sequence & track () { return m_seq; } /* * Takes screen corrdinates, give us notes/keys (to be generalized to * other vertical user-interface quantities) and ticks (always the * horizontal user-interface quantity). */ void convert_xy (int x, int y, midipulse & ticks, int & note); void convert_tn (midipulse ticks, int note, int & x, int & y); void convert_tn_box_to_rect ( midipulse tick_s, midipulse tick_f, int note_h, int note_l, seq66::rect & r ); }; // class qseqbase } // namespace seq66 #endif // SEQ66_QSEQBASE_HPP /* * qseqbase.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qseqdata.hpp ================================================ #if ! defined SEQ66_QSEQDATA_HPP #define SEQ66_QSEQDATA_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqdata.hpp * * This module declares/defines the base class for plastering * pattern/sequence data information in the data area of the pattern editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2026-04-21 * \license GNU GPLv2 or above * * The data pane is the drawing-area below the seqedit's event area, and * contains vertical lines whose height matches the value of each data event. * The height of the vertical lines is editable via the mouse. * * Another feature. Drawing a circular "grab handle" when an event is * crossed by the mouse or is selected. This is progress on the way to * improving issue #115. * * For issue #140, the height of the data pane is increased beyond 128 * pixels, to allow better access to the grab handle for extreme data * values. */ #include #include #include #include #include #include "midi/midibytes.hpp" /* midibyte, midipulse aliases */ #include "play/sequence.hpp" /* sequence::editmode mode */ #include "qseqbase.hpp" /* seq66::qseqbase mixin class */ namespace seq66 { class performer; /** * Displays the data values for MIDI events such as Mod Wheel and Pitchbend. * They are displayed as vertical lines with an accompanying numeric value. */ class qseqdata final : public QWidget, public qseqbase, protected performer::callbacks { friend class qseqroll; friend class qstriggereditor; friend class qseqeditframe64; public: /** * Various types of MIDI data have different wrinkles to how they are * displayed. Better than a bunch of booleans! Compare this list to * the seq66::sequence::draw type in the sequence module. */ enum class type { note, tempo, time_signature, program_change, pitchbend, text, max }; Q_OBJECT public: qseqdata ( performer & p, sequence & s, qseqeditframe64 * frame, int zoom, int snap, QWidget * parent, int height = 0 ); virtual ~qseqdata (); void set_data_type (midibyte a_status, midibyte a_control); int data_y (midibyte value) const; /** * Moves the bottom coordinate of the pane up a little. */ int bottom () const { return data_y(0); } bool is_drum_mode () const { return m_edit_mode == sequence::editmode::drum; } bool is_tempo () const { return m_data_type == type::tempo; } bool is_time_signature () const { return m_data_type == type::time_signature; } bool is_program_change () const { return m_data_type == type::program_change; } bool is_pitchbend () const { return m_data_type == type::pitchbend; } bool is_text () const { return m_data_type == type::text; } midibyte status () const { return m_status; } midibyte cc () const { return m_cc; } bool show_hex_values () const { return m_show_hex_values; } bool show_level_numbers () const { return m_show_level_numbers; } private: void flag_dirty (); /* tricky code */ void update_edit_mode (sequence::editmode mode); #if defined SEQ66_ALLOW_RELATIVE_VELOCITY_CHANGE void set_adjustment (midipulse tick_start, midipulse tick_finish); #endif void show_hex_values (bool flag) { m_show_hex_values = flag; } void show_level_numbers (bool flag) { m_show_level_numbers = flag; } private: // performer::callback overrides virtual bool on_ui_change (seq::number seqno) override; protected: virtual void paintEvent (QPaintEvent * ) override; virtual void resizeEvent (QResizeEvent *) override; virtual void mousePressEvent (QMouseEvent *) override; virtual void mouseReleaseEvent (QMouseEvent *) override; virtual void mouseMoveEvent (QMouseEvent *) override; virtual QSize sizeHint () const override; virtual void wheelEvent (QWheelEvent *) override; signals: private slots: void conditional_update (); private: QTimer * m_timer; QFont m_font; /** * A kludge to account for differences between the external and tabbed * sequence-editing frames. */ int m_keyboard_padding_x; /** * Indicates, for speed, that we're using half of the 128 pixel data * area. */ bool m_short_dataarea; /** * Provides a way to shrink the height of the data area. Defaults to 128. */ int m_dataarea_y; /** * Replaces a number of booleans denoting which type of events are * to be shown. */ type m_data_type; /** * Indicates the edit mode, note versus drum. */ sequence::editmode m_edit_mode; /** * What events is the data window currently editing? */ midibyte m_status; /** * What events is the data window currently editing? */ midibyte m_cc; /** * If set, show hex numbers for some display items. Defaults * to false. Can be modified by clicking the btn_show_hex button. */ bool m_show_hex_values; /** * If set (the default), then the level numbers of the data pane * are shown. Sometimes they get in the way. */ bool m_show_level_numbers; /** * Used when dragging a new-level adjustment slope with the mouse. */ bool m_line_adjust; /** * Use when doing a relative adjustment of notes by dragging. */ bool m_relative_adjust; /** * A feature shamelessly stolen from stazed's Seq32, in progress. * Supporting drag handles in the near future. */ bool m_drag_handle; /** * Keeps track of the X-location of the mouse, in ticks. */ midipulse m_mouse_tick; /** * The precision of event-line detection in ticks. This depends * upon the PPQN, obviously. This value starts at 2 pixels and is * corrected to ticks by the pix_to_tix() function. */ midipulse m_handle_delta; /** * This value is true if the mouse is being dragged in the data pane, * which is done in order to change the height and value of each data * line. */ bool m_dragging; }; // class qseqdata } // namespace seq66 #endif // SEQ66_QSEQDATA_HPP /* * qseqdata.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qseqeditex.hpp ================================================ #if ! defined SEQ66_QSEQEDITEX_HPP #define SEQ66_QSEQEDITEX_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseditex.hpp * * This module declares/defines the base class for the external * sequence-editing window. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-06-15 * \updates 2023-06-23 * \license GNU GPLv2 or above * * The sequence editing window is known as the "Pattern Editor". Kepler34 * provides an editor embedded within a tab, but we supplement that with a * more sophisticated external editor, which works a lot more like the Gtkmm * seqedit class. */ #include class QCloseEvent; namespace Ui { class qseqeditex; } namespace seq66 { class performer; class sequence; class qseqeditframe64; class qsmainwnd; /** * Provides a container for a qseqeditframe64 object. Thus, the Qt 5 version * of Seq66 has an external seqedit window like its Gtkmm-2.4 * counterpart. */ class qseqeditex final : public QWidget { Q_OBJECT public: qseqeditex ( performer & p, int seqid, qsmainwnd * parent = nullptr ); virtual ~qseqeditex (); void update_draw_geometry (); void set_title (bool modified = false); protected: performer & perf () const { return m_performer; } protected: virtual void closeEvent (QCloseEvent *) override; private: Ui::qseqeditex * ui; performer & m_performer; int m_seq_id; qsmainwnd * m_edit_parent; qseqeditframe64 * m_edit_frame; }; // class qseqeditex } // namespace seq66 #endif // SEQ66_QSEQEDITEX_HPP /* * qseditex.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qseqeditframe64.hpp ================================================ #if ! defined SEQ66_QSEQEDITFRAME64_HPP #define SEQ66_QSEQEDITFRAME64_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ /** * \file qseqeditframe64.hpp * * This module declares/defines the edit frame for sequences. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-06-15 * \updates 2026-04-21 * \license GNU GPLv2 or above * */ #include "qscrollmaster.h" /* qscrollmaster::dir enum class */ #include "qseqframe.hpp" /* QFrame and seq66::qseqframe */ #include "cfg/settings.hpp" /* seq66::combolist class, helpers */ #include "play/performer.hpp" /* seq66::performer::callbacks */ #include "play/setmapper.hpp" /* seq66::setmapper and others */ /** * Need to check before applying the change? Currently the measure length * is adjusted. Kept around "just in case". */ #undef USE_WOULD_TRUNCATE_BPB_BW /** * Experimenting with weird issue where changing the number of measures * via the length drop-down works in the external pattern editor, but * not in the tab. The old code (circa 0.99.12) does not work, either. */ #undef USE_LEGACY_MEASURES_ADJUSTMENT /** * Specifies the reported final size of the main window when the larger edit * frame "kicks in". See the comments for qsmainwnd::refresh(). The final * vertical size of the main window ends up at around 700, puzzling! The * vertical size of the "external" edit-frame is only about 600. Here are * the current measured (via kruler) heights: * * - Top panel: 90 * - Time pane: 20 * - Roll pane: 250 * - Event pane: 27 * - Data pane: 128 * - Bottom panel: 57 * * That total is 572. * * - qseqframe_height = 558, qseqeditframe64.ui * - qsmainwnd_height = 580, qsmainwnd.ui */ /* * A few forward declarations. The Qt header files are in the cpp file. */ class QIcon; class QMenu; class QWidget; namespace Ui { class qseqeditframe64; } /* * Note that the forward references somewhat duplicate those in qseqframe. */ namespace seq66 { class qlfoframe; class qpatternfix; class qseqeditex; class screenset; /** * This frame holds tools for editing an individual MIDI sequence. This * frame is a more advanced version of qseqeditframe (now moved to * contrib/code), which was based on Kepler34's EditFrame class. */ class qseqeditframe64 final : public qseqframe, protected performer::callbacks { friend class qlfoframe; friend class qpatternfix; friend class qperfroll; friend class qseqbase; friend class qseqdata; friend class qseqeditex; friend class qseqkeys; friend class qseqroll; friend class qseqtime; friend class qstriggereditor; Q_OBJECT private: /** * Enumerates the events we support for editing. Note that tempo and * time-signature are meta events and must be handled differently. * And text covers a number of difference meta text events. */ enum class event_index { note_on, note_off, aftertouch, control_change, program_change, channel_pressure, pitch_wheel, tempo, time_signature, text }; public: qseqeditframe64 ( performer & p, sequence & s, QWidget * parent, bool shorter = false ); virtual ~qseqeditframe64 (); void stop_timer (); void get_position (int & x, int & y); void initialize_panels (); void set_editor_mode (sequence::editmode mode); bool follow_progress (bool expand = false); void scroll_to_tick (midipulse tick); void scroll_to_note (int note); int edit_channel () const { return m_edit_channel; } protected: void set_track_change (bool modified = true); void set_external_frame_title (bool modified = true); void adjust_for_zoom (int zprevious); bool zoom_key_press (bool shifted, int key); private: /* performer::callback overrides */ virtual bool on_automation_change (automation::slot s) override; virtual bool on_sequence_change ( seq::number seqno, performer::change ctype ) override; virtual bool on_trigger_change ( seq::number seqno, performer::change mod ) override; virtual bool on_resolution_change ( int ppqn, midibpm bp, performer::change ch ) override; private: /* qbase and qseqframe overrides */ virtual bool change_ppqn (int ppqn) override; virtual void update_midi_buttons () override; virtual void update_note_entry (bool on) override; virtual void set_dirty () override; virtual bool zoom_in () override; virtual bool zoom_out () override; virtual bool set_zoom (int z) override; virtual bool reset_zoom (int ppq = 0) override; virtual void update_draw_geometry () override; protected: /* QWidget overrides */ virtual bool eventFilter (QObject * target, QEvent * event) override; virtual void paintEvent (QPaintEvent * ) override; virtual void resizeEvent (QResizeEvent *) override; virtual void wheelEvent (QWheelEvent *) override; virtual void keyPressEvent (QKeyEvent *) override; virtual void keyReleaseEvent (QKeyEvent *) override; virtual void closeEvent (QCloseEvent *) override; private: bool short_version () const { return m_short_version; } #if defined USE_WOULD_TRUNCATE_BPB_BW bool would_truncate (int bpb, int bw); #endif bool would_truncate (int measures); void scroll_by_step (qscrollmaster::dir d); void set_show_scale_or_chords (); void set_filter_painted_notes (); void remove_lfo_frame (); void remove_patternfix_frame (); QIcon * create_menu_image (bool state); void set_log_timesig_text (int bpb, int bw); void set_log_timesig_status (bool flag); bool log_timesig (bool islogbutton); bool detect_time_signature (); void setup_record_styles (); void setup_alterations (); void q_record_change (alteration mode, toggler t); void set_toggle_qrecord_button (); private: /* combo-box list accessors */ const combolist & measures_list () const { return m_measures_list; } const combolist & beats_per_bar_list () const { return m_beats_per_bar_list; } const combolist & beatwidth_list () const { return m_beatwidth_list; } const combolist & snap_list () const { return m_snap_list; } const combolist & zoom_list () const { return m_zoom_list; } const combolist & rec_vol_list () const { return m_rec_vol_list; } signals: private slots: void conditional_update (); void slot_reset_zoom (); void slot_update_zoom (int index); void update_seq_name (); void slot_log_timesig (); void slot_pattern_fix (); void slot_show_scale_or_chords (); void slot_filter_painted_notes (); void slot_show_hex (); void slot_show_level_numbers (); void update_beats_per_bar (int index); void text_beats_per_bar (); void update_beat_width (int index); void text_beat_width (); void reset_beats_per_bar (); void reset_beat_width (); #if defined USE_LEGACY_MEASURES_ADJUSTMENT void text_measures (const QString & text); #else void update_measures (int index); void text_measures_edit (); #endif void reset_measures (); void transpose (bool ischecked); void update_chord (int index); void reset_chord (); void update_midi_bus (int index); void reset_midi_bus (); void update_midi_channel (int index); void reset_midi_channel (); void undo (); void redo (); /* * Tools button and handlers. */ void tools (); void select_all_notes (); void inverse_note_selection (); void note_pitch_selection (); void quantize_notes (); void tighten_notes (); void jitter_notes (); void randomize_note_velocities (); void transpose_notes (); void transpose_harmonic (); void remap_notes (); void tooltip_mode (bool ischecked); void note_entry (bool ischecked); /* * More slots. */ void sequences (); void update_grid_snap (int index); void reset_grid_snap (); void update_note_length (int index); void reset_note_length (); void update_key (int index); void reset_key (); void update_scale (int index); void reset_scale (); void editor_mode (bool ischecked); void loop_mode (bool ischecked); void events (); void data (); void show_lfo_frame (); void show_pattern_fix (); void slot_play_change (bool ischecked); void slot_thru_change (bool ischecked); void slot_record_change (bool ischecked); void slot_q_record_change (bool ischecked); void slot_record_style (int index); void slot_recording_volume (int index); void slot_loop_count (int value); void reset_recording_volume (); void slot_follow (bool ischecked); void v_zoom_in (); void v_zoom_out (); void reset_v_zoom (); private: /* slot helper functions */ void do_action (eventlist::edit action, int var); void insert_macro (const std::string & macroname); void popup_tool_menu (); void popup_sequence_menu (); void repopulate_usr_combos (int buss, int channel); void repopulate_event_menu (int buss, int channel); void repopulate_mini_event_menu (int buss, int channel); void repopulate_midich_combo (int buss); bool add_back_set (QMenu ** qm, screenset & s, screenset::number index); bool add_back_sequence (QMenu ** qm, seq::pointer p, seq::number sn); private: /* setters and getters */ void set_beats_per_bar (int bpm, qbase::status qs = qbase::status::edit); void set_beat_width (int bw, qbase::status qs = qbase::status::edit); void set_bpb_and_bw ( int bpb, int bw, qbase::status qs = qbase::status::edit ); void set_measures (int len, qbase::status qs = qbase::status::edit); void set_midi_channel ( int midichannel, qbase::status qs = qbase::status::edit ); void set_midi_bus (int midibus, qbase::status qs = qbase::status::edit); void set_note_length (int nlen); void set_snap (midipulse s); void set_key (int key, qbase::status qs = qbase::status::edit); void set_scale (int key, qbase::status qs = qbase::status::edit); void set_chord (int chord, qbase::status qs = qbase::status::edit); void set_background_sequence ( int seqnum, qbase::status qs = qbase::status::edit ); void set_transpose_image (bool istransposable); void set_event_entry ( QMenu * menu, const std::string & text, bool present, midibyte status, midibyte control = 0 ); void set_event_entry (QMenu * menu, bool present, event_index ei); void set_data_type (midibyte status, midibyte control = 0); void set_recording_volume (int recvol); void enable_note_menus (); QWidget * rollview (); QWidget * rollwidget () const; private: /** * Needed for Qt. */ Ui::qseqeditframe64 * ui; /** * We want to support holding this frame in a qseqeditex window, to * be able modify the parent's title bar and get position information. * is a qseqeditex object. If null, then this frame is embedded in the * main window. */ qseqeditex * m_qseqeditex_frame; /** * This item is not null if this frame is embedded in the main window. * It is actually the Edit tab widget. */ QWidget * m_edit_tab_widget; /** * Indicates to compress this window vertically, for use in the Edit tab. */ bool m_short_version; /** * Hold the current L/R looping status. */ bool m_is_looping; /** * The LFO window object that might used by the pattern editor. */ qlfoframe * m_lfo_wnd; /** * The pattern-fix window object that might used by the pattern editor. */ qpatternfix * m_patternfix_wnd; /** * Menus for Tools and its Harmonic Transpose sub-menu. * Menu for the Background Sequences button. * Menu for the Event Data button. * Menu for the "mini" Event Data button. */ QMenu * m_tools_popup; QMenu * m_tools_harmonic; QMenu * m_tools_pitch; QMenu * m_tools_timing; QMenu * m_sequences_popup; QMenu * m_events_popup; QMenu * m_minidata_popup; /** * Holds the measure selection for the beats-per-measure combo-box. */ combolist m_measures_list; /** * Provides the length of the sequence in measures. */ int m_measures; /** * Holds the beats-per-bar selection for the beats-per-bar combo-box. */ combolist m_beats_per_bar_list; /** * Holds the current beats-per-measure selection and the value to log * when the time-sig button is clicked. */ int m_beats_per_bar; int m_beats_per_bar_to_log; /** * Holds the beat-width selection for the beats-width combo-box. */ combolist m_beatwidth_list; /** * Holds the current beat-width selection and the value to log * when the time-sig button is clicked. */ int m_beat_width; int m_beat_width_to_log; /** * Holds the list for the snap settings. These also apply to the note * settings. */ combolist m_snap_list; /** * Used in setting the snap-to value in pulses, off = 1. */ int m_snap; /** * Holds the list for the zoom settings. */ combolist m_zoom_list; /** * Holds the list for the zoom settings. */ combolist m_rec_vol_list; /** * The default length of a note to be inserted by a right-left-click * operation. */ int m_note_length; /** * Setting for the music scale, can now be saved with the sequence. */ int m_scale; /** * Setting for the current chord generation; not now saved with the * sequence. */ int m_chord; /** * Setting for the music key, can now be saved with the sequence. */ int m_key; /** * Setting for the background sequence, can now be saved with the * sequence. */ int m_bgsequence; /** * Indicates what MIDI output bus is active for this pattern. */ bussbyte m_edit_bus; /** * Indicates what MIDI input bus is active for this pattern. * Currently not part of the user-interface. */ bussbyte m_edit_in_bus; /** * Indicates what MIDI channel the data window is currently editing. */ int m_edit_channel; /** * Indicates the first event found in the sequence while setting up the * data menu via set_event_entry(). If no events exist, the value is * max_midibyte() [0xFF]. */ midibyte m_first_event; /** * Provides the string describing the first event, or "(no events)". */ std::string m_first_event_name; /** * Indicates that the focus has already been changed to this sequence. */ bool m_have_focus; /** * Indicates if this sequence is in note-edit more versus drum-edit mode. */ sequence::editmode m_edit_mode; /** * Indicates the last-selected recording mode, for use with safely using * the one-shot reset option. */ recordstyle m_last_record_style; /** * Set to the armed/mute status of the sequence being edited, changed * whenever the arm status changes.. */ bool m_armed_status; /** * If set (the default is not set), show hex numbers for some display * items. Defaults to false. Can be modified by clicking the btn_show_hex * button. */ bool m_show_hex_values; /** * If set (the default), then the level numbers of the data pane * are shown. Sometimes they get in the way. */ bool m_show_level_numbers; /** * Update timer for pass-along to the roll, event, and data classes. */ QTimer * m_timer; private: /* * Documented in the cpp file. */ static int sm_initial_snap; static int sm_initial_note_length; static int sm_initial_chord; }; // class qseqeditframe64 } // namespace seq66 #endif // SEQ66_QSEQEDITFRAME64_HPP /* * qseqeditframe64.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qseqeventframe.hpp ================================================ #if ! defined SEQ66_QSEQEVENTFRAME_HPP #define SEQ66_QSEQEVENTFRAME_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ /** * \file qseqeventframe.hpp * * This module declares/defines the edit frame for sequences. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-08-13 * \updates 2026-03-13 * \license GNU GPLv2 or above * */ #include /* used as a base class */ #include /* std::shared_ptr<>, unique_ptr<> */ #include "play/performer.hpp" /* seq66::performer::callbacks base */ #include "play/seq.hpp" /* seq66::seq::pointer & sequence */ #include "util/basic_macros.hpp" /* nullptr and related macros */ #include "qseventslots.hpp" /* seq66::qseventslots */ /* * This does not work due to connect() issues. * * #include "qchannelpopup.hpp" // seq66::qchannelpopup class // */ /** * Forward reference. */ class QMenu; class QTableWidgetItem; namespace Ui { class qseqeventframe; } namespace seq66 { class qseventslots; class qseqeventframe final : public QFrame, protected performer::callbacks { friend class qseventslots; private: /** * Provides human-readable names for the columns of the event table. */ enum class column_id { timestamp, eventname, buss, channel, data_0, data_1, link }; /** * This enumeration provides manifest constants for the event categories. */ using category = enum { channel_message = 0, system_message, meta_event, seqspec_event }; /** * This enumeration provides manifest constants for the channel messages. */ using channelmessage = enum { note_off = 0, note_on, aftertouch, control_change, program_change, channel_pressure, pitch_wheel }; Q_OBJECT public: qseqeventframe ( performer & p, sequence & s, QWidget * parent = nullptr ); virtual ~qseqeventframe (); /* * Called only externally by qsmainwind::load_event_editor(). */ void set_initialized () { m_initialized = true; }; protected: virtual bool on_sequence_change ( seq::number seqno, performer::change ctype ) override; /* * virtual bool on_group_learn (bool state) override; * virtual bool on_set_change (screenset::number setno) override; */ private: void set_selection_multi (bool multi); void set_row_heights (int height); void set_row_height (int row, int height); void set_column_widths (int total_width); void set_seq_title (const std::string & title); void set_seq_time_sig_and_ppqn (const std::string & sig); void set_seq_lengths (const std::string & mevents); void set_seq_channel (const std::string & channel); void set_event_category (const std::string & c); void set_event_timestamp (const std::string & ts); void set_event_name (const std::string & n); void set_event_channel (int channel); void set_event_data_0 (const std::string & d); void set_event_data_1 (const std::string & d); void set_event_plaintext (const std::string & t); void set_event_system (const std::string & t); void set_event_seqspec (const std::string & t); void set_event_line ( int row, const std::string & evtimestamp, const std::string & evname, const std::string & busno, const std::string & evchannel, const std::string & evdata0, const std::string & evdata1, const std::string & linktime ); void set_event_line (int row, const editable_event & ev); /* overload */ void set_event_line (int row); /* overload */ void set_dirty (bool flag = true); bool initialize_table (); std::string make_seq_title (); std::string get_lengths (); void data_0_helper (int d0); void check_channel_msg_index (int index); sequence & track () { return m_seq; } private: QTableWidgetItem * cell (int row, column_id col); void current_row (int row); int current_row () const; void populate_category_combo (); void populate_status_combo (); void populate_system_combo (); void populate_meta_combo (); void populate_seqspec_combo (); void repopulate_midich_combo (); std::string filename_prompt ( const std::string & prompt, const std::string & file ); void handle_control_popup (); void handle_program_popup (); void set_controller_entry ( QMenu * menu, const std::string & text, midibyte status, midibyte control ); void set_control_type (midibyte status, midibyte control); protected: // overrides of event handlers virtual void keyPressEvent (QKeyEvent *) override; virtual void keyReleaseEvent (QKeyEvent *) override; private slots: void slot_table_click_ex (int row, int column, int prevrow, int prevcol); void slot_row_selected (); void slot_link_status (); void slot_delete (); void slot_insert (); void slot_modify (); void slot_save (); void slot_clear (); void slot_dump (); void slot_cancel (); void update_seq_name (); void slot_midi_channel (int index); void slot_event_name (int index); void slot_event_category (int index); void slot_hex_data_state (int state); void slot_pulse_time_state (int state); void slot_ev_data_0_edit (const QString &); void slot_meta_text_change (); void slot_event_popup (); private: Ui::qseqeventframe * ui; private: /** * Provides a reference to the sequence that this dialog is meant to view * or modify. */ sequence & m_seq; /** * This object holds an editable_events container, and helps this * user-interface class manage the list of events. */ std::unique_ptr m_eventslots; /** * If true, selecting a note event also selects its linked event. */ bool m_linked_selection; /** * If true, show the data bytes in hexadecimal format. */ bool m_show_data_as_hex; /** * If true, show the time as pulses instead of B:B:T. */ bool m_show_time_as_pulses; /** * Indicates the dialog has now been set up. We need this to * avoid dirtying the dialog during setup. Weird. */ bool m_initialized; /** * Indicates if the user has selected Channel Message and either * Control or Program. We'd like to do a name lookup for these values. */ bool m_in_control; bool m_in_program; /** * Indicates a modification is active. */ bool m_is_dirty; /** * Holds the index of the channel named "None". */ int m_no_channel_index; /** * The popup onto which submenus are tacked. */ QMenu * m_select_popup; }; // class qseqeventframe } // namespace seq66 #endif // QSEQEVENTFRAME_HPP /* * qseqeventframe.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qseqframe.hpp ================================================ #if ! defined SEQ66_QSEQFRAME_HPP #define SEQ66_QSEQFRAME_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ /** * \file qseqframe.hpp * * This module declares/defines the edit frame for sequences. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-07-27 * \updates 2025-07-17 * \license GNU GPLv2 or above * * Provides an abstract base class so that both the old and the new Qt * sequence-edit frames can be supported. * * For now, we're abstracting the zoom functionality. Later, we * can abstract other code common between the two frames. */ #include #include "qeditbase.hpp" /* seq66:qeditbase super base class */ #include "play/seq.hpp" /* seq66::seq::pointer & sequence */ #include "play/sequence.hpp" /* seq66::seq::pointer & sequence */ /* * Forward declarations. Some Qt header files are in the cpp file. */ class QWidget; namespace Ui { class qseqframe; } namespace seq66 { class qseqkeys; class qseqtime; class qseqroll; class qseqrollpix; class qseqdata; class qstriggereditor; /** * This frame is the basis for editing an individual MIDI sequence. */ class qseqframe : public QFrame, public qeditbase { friend class qseqroll; Q_OBJECT public: qseqframe ( performer & p, sequence & s, int basezoom, QWidget * parent = nullptr ); virtual ~qseqframe (); bool repitch_all (); bool repitch_selected (); public: // protected: const sequence & track () const { return m_seq; } sequence & track () { return m_seq; } virtual void update_note_entry (bool on) = 0; virtual void update_draw_geometry () = 0; public: virtual bool zoom_in () override; virtual bool zoom_out () override; virtual bool set_zoom (int z) override; virtual bool reset_zoom (int ppq = 0) override; virtual void set_dirty () override; private: sequence & m_seq; protected: qseqkeys * m_seqkeys; qseqtime * m_seqtime; qseqroll * m_seqroll; qseqdata * m_seqdata; qstriggereditor * m_seqevent; }; // class qseqframe } // namespace seq66 #endif // SEQ66_QSEQFRAME_HPP /* * qseqframe.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qseqkeys.hpp ================================================ #if ! defined SEQ66_QSEQKEYS_HPP #define SEQ66_QSEQKEYS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqkeys.hpp * * This module declares/defines the base class for the left-side piano of * the pattern/sequence panel. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2023-10-19 * \license GNU GPLv2 or above * * We've added the feature of a right-click toggling between showing the * main octave values (e.g. "C1" or "C#1") versus the numerical MIDI * values of the keys. */ #include #include "cfg/usrsettings.hpp" /* seq66::show enum class */ #include "play/seq.hpp" /* seq66::seq::pointer */ #include "gui_palette_qt5.hpp" /* gui_pallete_qt5::Color etc. */ #include "qseqbase.hpp" /* seq66::qseqbase mixin class */ namespace seq66 { class performer; /** * Draws the piano keys in the sequence editor. */ class qseqkeys final : public QWidget, public qseqbase { Q_OBJECT friend class qseqroll; public: qseqkeys ( performer & perf, sequence & s, qseqeditframe64 * frame, QWidget * parent, /* QScrollArea */ int keyheight, int keyareaheight ); virtual ~qseqkeys () { // no code needed } int preview_key () const { return m_preview_key; } void preview_key (int key); bool is_preview_key (int key) const { return key == m_preview_key; } bool previewing () const { return m_is_previewing; } bool preview_on () const { return m_preview_on; } void preview_on (bool on) { m_preview_on = on; } int note_height () const { return m_key_y; } int total_height () const { return m_key_area_y; } bool v_zoom_in (); bool v_zoom_out (); bool reset_v_zoom (); void set_key (int k); protected: bool set_note_height (int h); protected: // Qt overrides virtual void paintEvent (QPaintEvent *) override; virtual void mousePressEvent (QMouseEvent *) override; virtual void mouseReleaseEvent (QMouseEvent *) override; virtual void mouseMoveEvent (QMouseEvent *) override; virtual QSize sizeHint () const override; virtual void wheelEvent (QWheelEvent * ev) override; signals: public slots: // void conditional_update (); private: void convert_y (int y, int & note); /** * Detects a black key. * * \param key * The key to analyze. * * \return * Returns true if the key is black (value 1, 3, 6, 8, or 10). */ bool is_black_key (int key) const { return key == 1 || key == 3 || key == 6 || key == 8 || key == 10; } const Color & preview_color () const { return m_preview_color; } const Color & white_color () const { return m_white_key_color; } const Color & black_color () const { return m_black_key_color; } void total_height (int y) { if (y > 0) m_key_area_y = y; } void note_height (int y) { if (y > 0) m_key_y = y; } private: QFont m_font; /** * The default value is to show the octave letters on the vertical * virtual keyboard. There are 4 other modes of note name/number display. */ showkeys m_show_key_names; /** * This value indicates the key value as selected in the seqedit. It * ranges from 0 to 11, with 0 being C. */ int m_key; int m_key_y; int m_key_area_y; const Color m_preview_color; const Color m_white_key_color; const Color m_black_key_color; bool m_is_previewing; bool m_preview_on; int m_preview_key; }; // class qseqkeys } // namespace seq66 #endif // SEQ66_QSEQKEYS_HPP /* * qseqkeys.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qseqroll.hpp ================================================ #if ! defined SEQ66_QSEQROLL_HPP #define SEQ66_QSEQROLL_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqroll.hpp * * This module declares/defines the base class for drawing on the piano * roll of the patterns editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2025-10-14 * \license GNU GPLv2 or above * * We are currently moving toward making this class a base class. * * User jean-emmanual added support for disabling the following of the * progress bar during playback. See the qseqbase::m_progress_follow member. */ #include #include "cfg/scales.hpp" /* seq66::scales enum class */ #include "play/sequence.hpp" /* sequence::editmode mode */ #include "util/rect.hpp" /* seq66::rect class */ #include "qseqbase.hpp" /* seq66::qseqbase mixin class */ /* * Forward references */ class QLabel; class QMessageBox; class qscrollmaster; class QTimer; namespace seq66 { class performer; class qseqeditframe64; class qseqkeys; /** * The MIDI note grid in the pattern (sequence) editor. */ class qseqroll final : public QWidget, public qseqbase { friend class qseqframe; /* for qseqroll::set_dirty() access */ friend class qseqeditframe64; Q_OBJECT public: qseqroll ( performer & perf, sequence & s, qseqeditframe64 * parent, qseqkeys * seqkeys_wid, int zoom = c_default_seq_zoom, int snap = c_default_snap, sequence::editmode mode = sequence::editmode::note, int unit_height = 1, int total_height = 1 ); virtual ~qseqroll (); bool follow_progress (qscrollmaster * qsm, bool expand = false); int note_height () const; bool v_zoom_in (); bool v_zoom_out (); bool reset_v_zoom (); virtual bool zoom_in () override; virtual bool zoom_out () override; virtual bool reset_zoom (int ppq = 0) override; const Brush & backseq_brush () const { return m_backseq_brush; } const Brush & chord_brush () const { return m_chord_brush; } protected: bool show_scale_or_chords () const { return m_show_scale_or_chords; } bool filter_painted_notes () const { return m_filter_painted_notes; } protected: virtual void set_dirty () override; private: virtual void scroll_offset (int v) override; virtual int scroll_offset () const override { return qseqbase::scroll_offset(); } void toggle_show_scale_or_chords (); void toggle_filter_painted_notes (); void flag_dirty (); /* tricky code */ void set_redraw (); bool is_drum_mode () const { return m_edit_mode == sequence::editmode::drum; } int get_note_length () const { return m_note_length; } void set_note_length (int len) { m_note_length = len; } int note_to_pix (int n) const; void set_chord (int chord); void set_key (int key); void set_scale (int scale); void set_background_sequence (bool state, int seq); void analyze_seq_notes (); void show_note_tooltip (int mx, int my); int note_off_length () const; bool add_painted_note (midipulse tick, int note, bool first = false); bool zoom_key_press (bool shifted, int key); bool movement_key_press (int key); bool get_selected_box (); private: // overrides for painting, mouse/keyboard events, & size hints virtual void paintEvent (QPaintEvent *) override; virtual void resizeEvent (QResizeEvent *) override; virtual void mousePressEvent (QMouseEvent *) override; virtual void mouseReleaseEvent (QMouseEvent *) override; virtual void mouseMoveEvent (QMouseEvent *) override; virtual void keyPressEvent (QKeyEvent *) override; virtual QSize sizeHint () const override; private: virtual void set_adding (bool a_adding) override; #if defined USE_GROW_SELECTED_NOTES_FUNCTION void grow_selected_notes (int dx); #endif void set_tooltip_mode (bool ischecked) { m_show_note_info = ischecked; } int snapped_x (int x); void move_selected_notes (int dx, int dy); void snap_y (int & y); void start_paste(); void draw_grid (QPainter & painter, const QRect & r); void draw_notes (QPainter & painter, const QRect & r, bool background); void draw_drum_notes (QPainter & painter, const QRect & r, bool background); void draw_drum_note (QPainter & painter, int x, int y); void call_draw_notes (QPainter & painter, const QRect & view); #if defined SEQ66_SHOW_TEMPO_IN_PIANO_ROLL void draw_tempo (QPainter & painter, int x, int y, int velocity); #endif void draw_ghost_notes ( QPainter & painter, const seq66::rect & selection /* why is seq66 scoped needed??? */ ); void update_edit_mode (sequence::editmode mode); private: /** * Pre-allocation of gradient brushes for drawing notes. */ QLinearGradient m_note_grad; QLinearGradient m_wrap_grad; QLinearGradient m_sel_grad; /** * Used for showing the estimated scale/key upon a Ctrl-K in the qseqroll. */ QMessageBox * m_analysis_msg; QFont m_font; /** * The colors (from the palette) for the background sequence and * for showing non-chord notes. */ const Brush m_backseq_brush; const Brush m_chord_brush; /** * Holds a pointer to the qseqkeys pane that is associated with the * qseqroll piano roll. */ qseqkeys * m_seqkeys_wid; /** * Screen update timer. */ QTimer * m_timer; /** * Indicates the musical scale in force for this sequence. */ scales m_scale; /** * A position value. Need to clarify what exactly this member is used * for. */ int m_pos; /** * Indicates either that chord support is disabled (0), or a particular * chord is to be created when inserting notes. */ chords m_chord; /** * The current musical key selected. */ keys m_key; /** * If true (the default is false), this will allow hovering to show * the values for a note in a tooltip. */ bool m_show_note_info; /** * The label that serves as a tooltip. */ QLabel * m_note_tooltip; /** * Holds the note length in force for this sequence. Used in the * seq66seqroll module only. */ int m_note_length; /** * Provides the number of ticks to shave off of the end of painted notes. * Also used when the user attempts to shrink a note to zero (or less * than zero) length. */ const midipulse m_note_off_margin; /** * Holds the value of the musical background sequence that is shown in * cyan (formerly grey) on the background of the piano roll. */ int m_background_sequence; /** * Set to true if the drawing of the background sequence is to be done. */ bool m_draw_background_seq; /** * Set to true (the default) if the scale or chord is to be shown. */ bool m_show_scale_or_chords; /** * Set to true (not the default) if the scale or chord setting is * to be used to ignore painted notes not in the scale or chord. */ bool m_filter_painted_notes; /** * The current status/event selected in the seqedit. Not used in seqroll * at present. */ midibyte m_status; /** * The current MIDI control value selected in the seqedit. Not used in * seqroll at present. */ midibyte m_cc; /** * Indicates the edit mode, note versus drum. */ sequence::editmode m_edit_mode; /** * Indicates to draw the whole grid. */ bool m_draw_whole_grid; /** * The starting time, in ticks, of the current frame. */ mutable midipulse m_t0; /** * The ending time, in ticks, of the current frame. */ mutable midipulse m_t1; /** * The width of a frame in ticks. */ mutable midipulse m_frame_ticks; int m_note_x; // note drawing variables; can we dump 'em? int m_note_width; int m_note_y; /** * Offset for keys. */ int m_keypadding_x; bool m_v_zooming; /** * The ranges of the selection, needed to draw ghost notes. * The coordinates are of the form (x, y) == (ticks, pixels). * The top-left corner is (start tick, high note) and the * bottom right corner is (end tick, low note). It is anticipated * that the highest tick possible will fit within an int. */ seq66::rect m_selection; int m_sel_offset_x; int m_sel_offset_y; /** * Hold the note value first grabbed when starting a move. */ int m_last_base_note; /** * Stores the setting of usr().pattern_wraparound(). It is used in * drawing wrapped notes. */ bool m_link_wraparound; signals: public slots: void conditional_update (); // void update_edit_mode (sequence::editmode mode); }; // class qseqroll } // namespace seq66 #endif // SEQ66_QSEQROLL_HPP /* * qseqroll.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qseqtime.hpp ================================================ #if ! defined SEQ66_QSEQTIME_HPP #define SEQ66_QSEQTIME_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqtime.hpp * * This module declares/defines the base class for drawing the * time/measures bar at the top of the patterns/sequence editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2024-11-18 * \license GNU GPLv2 or above * */ #include #include #include #include #include "qseqbase.hpp" /* seq66::qseqbase mixin class */ namespace seq66 { class performer; class qseqeditframe64; /** * The timebar for the sequence editor */ class qseqtime final : public QWidget, public qseqbase { friend class qseqeditframe64; /* for scrolling a horizontal page */ Q_OBJECT public: qseqtime ( performer & p, sequence & s, qseqeditframe64 * frame, int zoom, QWidget * parent /* QScrollArea */ ); virtual ~qseqtime (); protected: virtual void paintEvent (QPaintEvent *) override; virtual void resizeEvent (QResizeEvent *) override; virtual void mousePressEvent (QMouseEvent *) override; virtual void mouseReleaseEvent (QMouseEvent *) override; virtual void mouseMoveEvent (QMouseEvent *) override; virtual void keyPressEvent (QKeyEvent *) override; virtual QSize sizeHint () const override; virtual void wheelEvent (QWheelEvent *) override; private: void set_END_marker (bool expanding); void draw_grid (QPainter & painter, const QRect & r); void draw_markers (QPainter & painter); signals: private slots: void conditional_update (); private: QTimer * m_timer; QFont m_font; bool m_move_L_marker; bool m_expanding; /* * Currently these are only 8-bit characters. */ char m_L_marker[2]; char m_R_marker[2]; char m_END_marker[8]; }; // class qseqtime } // namespace seq66 #endif // SEQ66_QSEQTIME_HPP /* * qseqtime.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qsessionframe.hpp ================================================ #if ! defined SEQ66_QSESSIONFRAME_H #define SEQ66_QSESSIONFRAME_H /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsessionframe.hpp * * This module declares/defines the class for showing session information. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-08-24 * \updates 2023-09-21 * \license GNU GPLv2 or above * * We want to be able to survey the existing mute-groups. */ #include /* * This is necessary to keep the compiler from thinking Ui::qsessionframe * would be found in the seq66 namespace. */ namespace Ui { class qsessionframe; } namespace seq66 { class performer; class qsmainwnd; /** * Provides a frame for the Sessions tab. */ class qsessionframe : public QFrame { Q_OBJECT public: qsessionframe ( performer & p, qsmainwnd * mainparent, QWidget * parent = nullptr ); virtual ~qsessionframe(); public: void session_manager (const std::string & text); void session_path (const std::string & text); void session_display_name (const std::string & text); void session_client_id (const std::string & text); void session_URL (const std::string & text); void session_log_file (const std::string & text); void song_path (const std::string & text); void last_used_dir (const std::string & text); void reload_song_info (); void populate_macro_combo (); void enable_reload_button (bool flag); protected: performer & perf () { return m_performer; } void sync_track_label (); void sync_track_high (); protected: // overrides of event handlers virtual void keyPressEvent (QKeyEvent *) override; virtual void keyReleaseEvent (QKeyEvent *) override; signals: private slots: void slot_flag_reload (); void slot_songinfo_change (); void slot_save_info (); void slot_track_number (int trk); void slot_macros_active (); void slot_macro_pick (const QString &); void slot_log_file (); void slot_log_file_clear (); private: Ui::qsessionframe * ui; private: /** * The main window that owns this window. */ qsmainwnd * m_main_window; /** * The main player :-). */ performer & m_performer; /** * Holds the currently selected track, needed when the track selection * changes in order to clear the "next match" flag. */ int m_current_track; /** * A counter for Meta Text events when a track contains more than one. */ int m_current_text_number; /** * The highest-numbered track, plus one, kept synchronized with * performer::m_sequence_high. */ int m_track_high; }; } // namespace seq66 #endif // SEQ66_QSESSIONFRAME_H /* * qsessionframe.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qsetmaster.hpp ================================================ #if ! defined SEQ66_QSETMASTER_HPP #define SEQ66_QSETMASTER_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsetmaster.hpp * * This module declares/defines the base class for the screen-set manager. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-05-11 * \updates 2023-11-11 * \license GNU GPLv2 or above * * We want to be able to survey the existing screen-sets and sequences, and * be able to pick them via buttons and keystrokes rather then using the * set-spinner in the live frame. * * Also, we want to get a quick idea of what screen-sets and sequences are * loaded and active. */ #include #include /* std::vector container */ #include "ctrl/keycontainer.hpp" /* class seq66::keycontainer */ #include "ctrl/opcontainer.hpp" /* class seq66::opcontainer */ #include "play/performer.hpp" /* class seq66::performer */ /** * Forward references. */ class QPushButton; class QTableWidgetItem; class QTimer; /* * This is necessary to keep the compiler from thinking Ui::qsetmaster * would be found in the seq66 namespace. */ namespace Ui { class qsetmaster; } namespace seq66 { class qsmainwnd; /** * This class helps manage screensets, including selecting the current * playscreen and showing, in brief form, the contents of each set. */ class qsetmaster final : public QFrame, protected performer::callbacks { private: /** * Provides human-readable names for the columns of the set table. */ enum class column_id { set_number, set_seq_count, set_name }; using buttons = std::vector; private: Q_OBJECT public: qsetmaster ( performer & p, qsmainwnd * mainparent, QWidget * parent = nullptr ); virtual ~qsetmaster(); private: virtual bool on_set_change ( screenset::number setno, performer::change ctype ) override; /* * virtual bool on_group_learn (bool state) override; * virtual bool on_sequence_change (seq::number seqno, ...) override; */ private: // overrides of event handlers virtual void closeEvent (QCloseEvent *) override; virtual void keyPressEvent (QKeyEvent *) override; virtual void keyReleaseEvent (QKeyEvent *) override; virtual void changeEvent (QEvent *) override; private: bool needs_update () const { bool result = m_needs_update; m_needs_update = false; return result; } void set_needs_update (); /* used for GUI updates and file mods */ void create_set_buttons (); void handle_set (int setno); void delete_set (int setno); bool set_control ( automation::action a, int /*d0*/, int /*d1*/, int index, bool inverse ); bool populate_default_ops (); int current_row () const { return m_current_row; } void current_row (int row); void set_column_widths (int total_width); void setup_table (); bool initialize_table (); bool set_line ( screenset & sset, screenset::number row ); #if defined PASS_KEYSTROKES_TO_PARENT bool handle_key_press (const keystroke & k); bool handle_key_release (const keystroke & k); #endif QTableWidgetItem * cell (screenset::number row, column_id col); void move_helper (int oldrow, int newrow); signals: private slots: void conditional_update (); void slot_set_name (); void slot_show_sets (); void slot_move_down (); void slot_move_up (); void slot_delete (); void slot_table_click_ex ( int row, int /*column*/, int /*prevrow*/, int /*prevcolumn*/ ); void slot_cell_changed (int row, int column); void slot_toggle_trigger_mode (); void slot_set_0 (); private: /** * The use Qt user-interface object pointer. */ Ui::qsetmaster * ui; private: /** * Holds a map of midioperation functors to be used to control patterns, * mute-groups, and automation functions. */ opcontainer m_operations; /** * A timer for refreshing the frame as needed. */ QTimer * m_timer; /** * The main window that owns this window. */ qsmainwnd * m_main_window; /** * Access to all the screenset buttons. */ buttons m_set_buttons; /** * Indicates the currently-selected set number. */ int m_current_set; /** * Indicates the currently-selected set-table row. */ int m_current_row; /** * Current number of rows in the set table. */ int m_current_row_count; /** * Indicates that the view should be refreshed. */ mutable bool m_needs_update; /** * Enables using the buttons to select the playing screenset. */ bool m_trigger_mode; /** * Prevents slots from working while the tables are built. */ bool m_table_initializing; /** * Indicates that this view is embedded in a frame, and thus permanent. * Commented out because we no longer support an external setmaster * frame. * * bool m_is_permanent; */ }; } // namespace seq66 #endif // SEQ66_QSETMASTER_HPP /* * qsetmaster.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qseventslots.hpp ================================================ #if ! defined SEQ66_QSEVENTSLOTS_HPP #define SEQ66_QSEVENTSLOTS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseventslots.hpp * * This module declares/defines the base class for displaying events in their * editing slots. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-08-13 * \updates 2023-05-03 * \license GNU GPLv2 or above * * This class supports the left side of the Qt 5 version of the Event Editor * window. One big difference from the Gtkmm-2.4 version is that a table * widget will be used to display the events. */ #include "midi/editable_events.hpp" /* seq66::editable_events container */ #include "play/seq.hpp" /* seq66::seq::pointer & sequence */ /** * Indicates that an event index is not useful. */ #define SEQ66_NULL_EVENT_INDEX (-1) namespace seq66 { class qseqeventframe; class performer; /** * This class implements the left-side list of events in the pattern * event-edit window. */ class qseventslots final { friend class qseqeventframe; private: /** * Provides a link to the qseqeventframe that created this object. */ qseqeventframe & m_parent; /** * Provides a reference to the sequence that this dialog is meant to view * or modify. */ sequence & m_seq; /** * Holds the editable events for this sequence. This container is what * is edited, and any changes made to it are not saved to the container * until the user pushes the "save" button. */ editable_events m_event_container; /** * Holds the current event (i.e. most recently inserted) for usage by the * caller, the event-edit frame. */ editable_event m_current_event; /** * The current number of events in the edited container. */ int m_event_count; /** * Holds the previous length of the edited sequence, in MIDI pulses, so * that we can detect changes in the length of the sequence. */ midipulse m_last_max_timestamp; /** * Holds the current number of measures, for display purposes. */ int m_measures; /** * Counts the number of displayed events, which depends on how many * events there are (m_event_count) and the size of the event list * (m_line_maximum). */ int m_line_count; /** * Counts the maximum number of displayed events, which depends on * the size of the event list (and thus the size of the dialog box for * the event editor). */ int m_line_maximum; /** * Provides a little overlap for paging through the frame. */ int m_line_overlap; /** * The index of the event that is 0th in the visible list of events. * It is used in numbering the events that are shown in the event-slot * frame. Do not confuse it with m_current_index, which is relative to * the frame, not the container-beginning. DEPRECATE? */ int m_top_index; /** * Indicates the current row (and index of the current event) in the * event table. This event will also be pointed to by the * m_current_event iterator. Do not confuse it with m_top_index, which * is relative to the container-beginning, not the frame. */ int m_current_index; /** * Indicates where the user click in the list of events. It is the index * of the selection. Semi-redundant, used only externally, by * qseqeventframe. */ int m_current_row; /** * Provides the top "pointer" to the start of the editable-events section * that is being shown in the user-interface. */ editable_events::iterator m_top_iterator; /** * Provides the bottom "pointer" to the end of the editable-events section * that is being shown in the user-interface. */ editable_events::iterator m_bottom_iterator; /** * Provides the "pointer" to the event currently in focus. */ editable_events::iterator m_current_iterator; /** * Indicates the event index that matches the index value of the vertical * pager. */ int m_pager_index; /** * Indicates to show data values in hexadecimal format. */ bool m_show_data_as_hex; /** * If true, show the time as pulses instead of B:B:T. */ bool m_show_time_as_pulses; public: qseventslots (performer & p, qseqeventframe & parent, sequence & s); /** * Let's provide a do-nothing virtual destructor. */ virtual ~qseventslots () { // I got nothin' } void clear () { m_event_container.clear(); } midipulse get_length () const { return m_event_container.get_length(); } int count_to_link (const editable_event & source) const { return m_event_container.count_to_link(source); } editable_event & lookup_link (const editable_event & ee) { return m_event_container.lookup_link(ee); } const editable_event & current_event () const { return m_current_event; } editable_event & current_event () { return m_current_event; } bool empty () const { return m_event_container.empty(); /* m_event_count == 0 */ } int count () const { return m_event_container.count(); /* m_event_count */ } /** * Returns the current number of rows (events) in the qseventslots's * display. */ int line_count () const { return m_line_count; } /** * \getter m_line_maximum * Returns the maximum number of rows (events) in the qseventslots's * display. */ int line_maximum () const { return m_line_maximum; } /** * Provides the "page increment" or "line increment" of the frame, * This value is the current line-maximum of the frame minus its * overlap value. */ int line_increment () const { return m_line_maximum - m_line_overlap; } int top_index () const { return m_top_index; } int current_row () const { return m_current_row; } void current_row (int row) { m_current_row = row; } int pager_index () const { return m_pager_index; } std::string time_string (midipulse lt); private: const sequence & track () const { return m_seq; } sequence & track () { return m_seq; } void hexadecimal (bool flag) { m_show_data_as_hex = flag; } void pulses (bool flag) { m_show_time_as_pulses = flag; } bool load_events (); bool load_table (); midibyte string_to_channel (const std::string & channel); std::string events_to_string () const; void set_current_event ( const editable_events::iterator ei, int index, bool full_redraw = true ); void set_table_event (editable_event & ev, int row); std::string data_string (midibyte d); std::string event_to_string ( const editable_event & ev, int index, bool usehex = false ) const; bool insert_event (editable_event ev); bool insert_event ( const std::string & evtimestamp, const std::string & evname, const std::string & evdata0, const std::string & evdata1, const std::string & ch = "", const std::string & text = "" ); bool delete_current_event (); bool modify_current_event ( int row, const std::string & evtimestamp, const std::string & evname, const std::string & evdata0, const std::string & evdata1, const std::string & ch = "", const std::string & text = "" ); bool modify_current_channel_event ( int row, const std::string & evdata0, const std::string & evdata1, const std::string & channel ); bool save_events (); void select_event ( int event_index = SEQ66_NULL_EVENT_INDEX, bool full_redraw = true ); void set_event_text ( const std::string & evchannel, const std::string & evtimestamp, const std::string & evname, const std::string & evdata0, const std::string & evdata1, int channel ); #if defined QSEVENTSLOTS_FUNCTION_USED void page_movement (int new_value); #endif void page_topper (editable_events::iterator newcurrent); int decrement_top (); int increment_top (); int decrement_current (); int increment_current (); int decrement_bottom (); int increment_bottom (); int calculate_measures () const; }; // class qseventslots } // namespace seq66 #endif // SEQ66_QSEVENTSLOTS_HPP /* * qseventslots.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qslivebase.hpp ================================================ #if ! defined SEQ66_QSLIVEBASE_HPP #define SEQ66_QSLIVEBASE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qslivebase.hpp * * This module declares/defines the base class for the Qt 5 version of * the pattern window. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-06-22 * \updates 2029-17-14 * \license GNU GPLv2 or above * * The qslivebase and its child class, qslivegride, are Sequencer66's * analogue to the Gtkmm mainwid class. These classes display a grid of * patterns (loops) that can be controlled via the grid. */ #include #include "play/performer.hpp" /* seq66::performer class */ #include "play/screenset.hpp" /* seq66::screenset class */ class QEvent; namespace seq66 { class keystroke; class performer; class qsmainwnd; /** * This base class provides access to the performer, the main window, some * basic items needed for drawing text, handling banks (sets), and * manipulating sequences/loops/patterns. */ class qslivebase : public QFrame { friend class qsmainwnd; friend class qliveframeex; public: qslivebase ( performer & perf, /* performance master */ qsmainwnd * window, /* functional parent */ screenset::number bank = screenset::unassigned(), QWidget * parent = nullptr /* the Qt parent */ ); virtual ~qslivebase (); bool set_bank (int newBank, bool hasfocus = false); void set_bank (); // bank number retrieved from performer screenset::number bank_id () const { return m_bank_id; // same as the screen-set number } const std::string & bank_name () const { return m_bank_name; } const performer & perf () const { return m_performer; } int rows () const { return perf().rows(); } int columns () const { return perf().columns(); } seq::number seq_offset () const; bool seq_to_grid (seq::number seqno, int & row, int & column) const { return perf().seq_to_grid(seqno, row, column, is_external()); } int spacing () const { return m_mainwnd_spacing; } protected: performer & perf () { return m_performer; } void set_needs_update (bool flag = true) { m_needs_update = flag; } bool check_needs_update () const { bool result = m_needs_update; m_needs_update = false; return result; } bool is_external () const { return m_is_external; } protected: qsmainwnd * parent () { return m_parent; } const qsmainwnd * parent () const { return m_parent; } bool can_paste () const { return m_can_paste; } void can_paste (bool flag) { m_can_paste = flag; } seq::number current_seq () const { return m_current_seq; } void current_seq (seq::number n) { m_current_seq = n; } seq::number hover_seq () const { return m_hover_seq; } void hover_seq (seq::number n) { m_hover_seq = n; } virtual void update_bank (int bank); virtual void update_bank () { // no code, override to recreate current bank } virtual void update_bank_name (const std::string & /*name*/) { // no code, see qslivegrid } virtual void update_sequence (seq::number /*seqno*/, bool /*redo*/) { // no code (yet), see qslivegrid } virtual void color_by_number (int i) = 0; /* implemented!!! */ virtual void set_midi_bus (int b); virtual void set_midi_channel (int b); virtual void set_midi_in_bus (int b); virtual bool recreate_all_slots () { return false; } virtual void refresh () { set_needs_update(); } /* * No support for refreshing only a specific slot in this base-class * version. */ virtual void refresh (seq::number /*seqno*/) { set_needs_update(); } virtual void set_bank_values ( const std::string & name = "", int id = 0 ) { m_bank_name = name; m_bank_id = id; } private: // pure virtual functions virtual void calculate_base_sizes (seq::number, int &, int &) { // no code } virtual void set_playlist_name ( const std::string & plname = "", bool modified = false ) = 0; virtual void reupdate () = 0; /* update() */ virtual void update_geometry () = 0; /* updateGeometry() */ virtual void change_event (QEvent *) = 0; /* changeEvent() */ virtual void draw_sequences () { // no code } virtual bool draw_sequence (seq::pointer, seq::number) { return true; } virtual bool draw_slot (seq::number) { return true; } /* * The integer parameters are x, y, w, and h. */ virtual void draw_box (QPainter &, int, int, int, int) { // no code } protected: /** * Access to the most important class in Sequencer66. */ performer & m_performer; /** * Access to the main window. */ qsmainwnd * m_parent; /** * Provide the font used for drawing text in qslivegrid. Note that text * in the qslivegrid's qslotbuttons will use setText() for drawing the * slot numbers, and qloopbutton has its own font for the buttons. */ QFont m_font; /** * Kepler34 calls "screensets" by the name "banks". Same as the * screen-set number. This is either the constructor-specified bank, or * is the same as the current bank/set logged in the performer. */ int m_bank_id; /** * A copy of the bank name, which is not necessarily the name of the * playing screen-set. */ std::string m_bank_name; /** * These values are assigned to the values given by the constants of * similar names in globals.h (obsolete), and we will make them * parameters or user-interface configuration items later. Some of them * already have counterparts in the usrsettings class. */ int m_mainwnd_spacing; /* from usr().mainwnd_spacing(): 2 to 16 */ int m_space_rows; /* total space between all rows (e.g. 2*4) */ int m_space_cols; /* ditto for columns (e.g. 2 * 8) */ /** * Provides a convenience variable for avoiding multiplications. It is * equal to rows * columns. */ const int m_screenset_slots; /** * Width of a pattern slot in pixels. Corresponds to the live frame's * m_seqarea_x value. */ int m_slot_w; /** * Height of a pattern slot in pixels. Corresponds to the live frame's * m_seqarea_y value. */ int m_slot_h; /** * Used in beat pulsing in the qsmaintime bar, which is a bit different * than the legacy progress pill in maintime. */ int m_last_metro; /** * Holds the current transparency value, used in beat-pulsing for fading. */ int m_alpha; /** * For mouse interaction, holds the current sequence/loop/pattern number * indicated by clicking in the live frame. The hover-seq indicates * where the mouse is regardless of clicking. */ seq::number m_current_seq; seq::number m_hover_seq; /** * Holds the initial sequence number when attempting to move the sequence. */ seq::number m_source_seq; /** * Indicates previously-selected sequence number when copying it. */ bool m_button_down; /** * */ bool m_moving; // are we moving between slots /** * */ bool m_adding_new; // new seq here, wait for double click /** * Indicates that there is something to paste. */ bool m_can_paste; /** * */ bool m_has_focus; /** * Indicates this live frame is in an external window. It does not have * a tab widget as a parent, and certain menu entries cannot be used. */ bool m_is_external; /** * Indicates a need for a button update, as opposed to a complete redraw * of all the buttons. */ mutable bool m_needs_update; }; // class qslivebase } // namespace seq66 #endif // SEQ66_QSLIVEBASE_HPP /* * qslivebase.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qslivegrid.hpp ================================================ #if ! defined SEQ66_QSLIVEGRID_HPP #define SEQ66_QSLIVEGRID_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qslivegrid.hpp * * This module declares/defines the base class for the Qt 5 version of * the pattern window. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-06-21 * \updates 2025-07-17 * \license GNU GPLv2 or above * * * The qslivegrid is Sequencer66's alternative to the qsliveframe class (now * moved to contrib/code for posterity). But instead of a large pixmap, it * consists of a grid of pushbuttons. */ #include /* std::function, function objects */ #include /* std::vector<> */ #include "qslivebase.hpp" /* seq66::qslivebase ABC */ #include "midi/midifile.hpp" /* for exporting a track */ #include "play/screenset.hpp" /* seq66::screenset class */ /* * Qt forward references. */ class QMenu; class QTimer; class QMessageBox; namespace Ui { class qslivegrid; } namespace seq66 { class keystroke; class performer; class qsmainwnd; class qslotbutton; /** * Provides a grid of Qt buttons to implement the Live frame. * * The protected inheritance could be moved to qslivebase, maybe. */ class qslivegrid final : public qslivebase, protected performer::callbacks { friend class qsmainwnd; friend class qliveframeex; private: /** * This vector represents all buttons. Rows and columns calculated only * when necessary. */ using buttons = std::vector; private: Q_OBJECT public: qslivegrid ( performer & perf, /* performance master */ qsmainwnd * window, /* functional parent */ screenset::number bank = screenset::unassigned(), QWidget * parent = nullptr /* the Qt parent */ ); virtual ~qslivegrid (); private: // overrides of qslivebase functions virtual void refresh () override { qslivebase::refresh(); (void) refresh_all_slots(); } virtual void refresh (seq::number seqno) override { if (seqno == seq::all()) refresh(); } virtual void color_by_number (int i) override; virtual void set_playlist_name ( const std::string & plname = "", bool modified = false ) override; virtual void set_bank_values ( const std::string & name = "", int id = 0 ) override; virtual void update_bank (int bank) override; virtual void update_bank () override; virtual void update_bank_name (const std::string & name) override; virtual void update_sequence (seq::number seqno, bool redo) override; virtual void reupdate () override; virtual void update_geometry () override; virtual void change_event (QEvent *) override; private: /* performer::callback overrides */ virtual bool on_trigger_change ( seq::number seqno, performer::change mod ) override; virtual bool on_automation_change (automation::slot s) override; private: // overrides of event handlers virtual void paintEvent (QPaintEvent *) override; virtual void resizeEvent (QResizeEvent *) override; virtual void mousePressEvent (QMouseEvent *) override; virtual void mouseReleaseEvent (QMouseEvent *) override; virtual void mouseMoveEvent (QMouseEvent *) override; virtual void mouseDoubleClickEvent (QMouseEvent *) override; virtual void keyPressEvent (QKeyEvent *) override; virtual void keyReleaseEvent (QKeyEvent *) override; virtual void changeEvent (QEvent *) override; /* * Trial for drag-and-drop onto the Live grid. */ virtual void dragEnterEvent (QDragEnterEvent * event) override; virtual void dragMoveEvent (QDragMoveEvent * event) override; virtual void dragLeaveEvent (QDragLeaveEvent * event) override; virtual void dropEvent (QDropEvent * event) override; private: virtual bool recreate_all_slots () override; bool can_clear () const { return ! perf().is_seq_empty(current_seq()); } seq::number seq_id_from_xy (int click_x, int click_y); qslotbutton * create_one_button (seq::number seqno); qslotbutton * button (int row, int column); qslotbutton * loop_button (seq::number seqno); bool get_slot_coordinate (int x, int y, int & row, int & column); bool handle_key_press (const keystroke & k); bool handle_key_release (const keystroke & k); bool delete_slot (int row, int column); bool delete_slot (seq::number seqno); bool delete_all_slots (); bool refresh_all_slots (); bool modify_slot (qslotbutton * newslot, int row, int column); void button_toggle_enabled (seq::number seqno); void button_toggle_checked (seq::number seqno); void button_toggle_flat (seq::number seqno); void alter_sequence (seq::number seqno); void create_loop_buttons (); void clear_loop_buttons (); void measure_loop_buttons (); void setup_button (qslotbutton * pb); void popup_menu (); void sequence_key_check (); void show_record_style (); void show_record_alteration (); void show_grid_mode (); void populate_grid_mode (); void set_grid_mode (); void enable_solo (bool enable); void update_state (); signals: /* * Hmmm, no bool parameters. Seem the connections in qsmainwnd. */ void signal_call_editor (int seqid); /* editor tab for pattern */ void signal_call_editor_ex (int seqid); /* editor window for " */ void signal_call_edit_events (int seqid); /* event tab for pattern */ void signal_live_frame (int ssnum); /* live frame for screen # */ private slots: void flatten_sequence (); void export_sequence (); void copy_sequence (); void cut_sequence (); void paste_sequence (); void merge_sequence (); void delete_sequence (); void clear_sequence (); void conditional_update (); void new_sequence (); void edit_sequence (); void edit_sequence_ex (); void edit_events (); void record_sequence (); void new_live_frame (); void slot_set_bank_name (); void slot_activate_bank (bool clicked); void slot_record_style (bool clicked); void slot_record_alteration (bool clicked); void slot_toggle_metronome (bool clicked); void slot_toggle_background_record (bool clicked); void slot_grid_mode (int index); private: Ui::qslivegrid * ui; QMenu * m_popup; QTimer * m_timer; QMessageBox * m_msg_box; /** * Indicates if the buttons should (re)drawn. */ bool m_redraw_buttons; /** * A two-dimensional vector of buttons containing a vector of rows, each * row being a vector of columns. * * The fastest varying index is the row: m_loop_buttons[column][row]. */ buttons m_loop_buttons; /** * Layout of buttons for determining sequence numbers. */ int m_x_min; int m_x_max; int m_y_min; int m_y_max; }; // class qslivegrid } // namespace seq66 #endif // SEQ66_QSLIVEGRID_HPP /* * qslivegrid.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qslogview.hpp ================================================ #if ! defined SEQ66_QSLOGVIEW_H #define SEQ66_QSLOGVIEW_H /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qslogview.hpp * * This dialog provides some context-specific help. * * \library seq66 application * \author Chris Ahlstrom * \date 2025-01-25 * \updates 2025-01-25 * \license GNU GPLv2 or above * */ #include namespace Ui { class qslogview; } namespace seq66 { class qslogview final : public QDialog { Q_OBJECT public: explicit qslogview (QWidget * parent = nullptr); virtual ~qslogview (); public: void refresh (); private slots: void slot_refresh_log_view (); private: Ui::qslogview * ui; }; // class qslogview } // namespace seq66 #endif // SEQ66_QSLOGVIEW_H /* * qslogview.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qslotbutton.hpp ================================================ #if ! defined SEQ66_QSLOTBUTTON_HPP #define SEQ66_QSLOTBUTTON_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qslotbutton.hpp * * This module declares/defines the base class for drawing on a pattern-slot * button. * * \library seq66 application * \author Chris Ahlstrom * \date 2021-09-19 * \updates 2022-07-22 * \license GNU GPLv2 or above * * All this button can do is enable a new pattern to be created. It is * impossible to recreate live-frame features like drag-and-drop patterns, * using Qt slots for toggle or press actions. The qslivegrid class disables * the use of slots. Instead, it calculates the button number based on the * mouse pointer and handles the button operation on behalf of the button. */ #include #include #include "gui_palette_qt5.hpp" /* seq66::Color */ #include "play/seq.hpp" /* seq66::seq sequence-plus class */ namespace seq66 { class performer; class qslivegrid; /** * The timebar for the sequence editor */ class qslotbutton : public QPushButton { friend class qslivegrid; protected: /** * Holds a pointer to the parent, needed to evaluated changes in * user-interface size. */ const qslivegrid * const m_slot_parent; /** * Indicates the sequence number of the slot. Needed when the slot is * empty (has a null seq::pointer), which will always be true for a * slot-button. */ seq::number m_slot_number; /** * Provides the initial labelling for this button. */ std::string m_label; /** * Provides the hot-key (slot-key) for this button, provided by the * performer. */ std::string m_hotkey; /** * More colors, to save precious time, at the cost of luxurious space. */ const Color m_drum_color; #if defined DRAW_TEMPO_LINE const Color m_tempo_color; #endif const Color m_progress_color; /* * Can be modified to match a Qt theme. * We have the button-text color, the color of lines in the progress box, * and the background color specified by the user. */ Color m_label_color; Color m_text_color; Color m_pen_color; Color m_back_color; /** * Indicates we are running with less than the usual number of rows, 4. */ bool m_vert_compressed; /** * Indicates we are running with more than the usual number of columns, * 8. */ bool m_horiz_compressed; /** * Indicates if the slot button has a pattern assigned to it. Defaults to * false; gets set in the derived class (qloopbutton). */ bool m_is_active; /** * Indicates if the button is checkable, or just clickable. Empty slots * need to be enabled, but not checkable, so that we can do different * things with them. We could probably just hook up a different callback * to qslotbuttons versus qloopbuttons. */ bool m_is_checkable; /** * Used in repainting the button. Usually more important for the derived * class (qloopbutton). */ mutable bool m_is_dirty; public: qslotbutton ( const qslivegrid * const slotparent, seq::number slotnumber, const std::string & label, const std::string & hotkey, QWidget * parent = nullptr ); virtual ~qslotbutton () { // no code needed } virtual void setup (); virtual seq::pointer loop () { static seq::pointer s_dummy; return s_dummy; } virtual void set_checked (bool /*flag*/) { // no code for this base class } virtual void set_flat (bool /*flag*/) { // no code for this base class } virtual bool toggle_enabled () { return false; /* no functionality in base class */ } virtual bool toggle_checked () { return false; /* no functionality in base class */ } seq::number slot_number () const { return m_slot_number; } const std::string & label () const { return m_label; } std::string hotkey () const { return m_hotkey; } bool is_active () const { return m_is_active; } bool is_checkable () const { return m_is_checkable; } protected: const qslivegrid * slot_parent () const { return m_slot_parent; } void make_inactive () { m_is_active = false; } void make_active () { m_is_active = true; } void make_checkable () { m_is_checkable = true; } bool is_dirty () const { return m_is_dirty; } void set_dirty (bool f) { m_is_dirty = f; } bool horiz_compressed () const { return m_horiz_compressed; } void horiz_compressed (bool f) { m_horiz_compressed = f; } bool vert_compressed () const { return m_vert_compressed; } void vert_compressed (bool f) { m_vert_compressed = f; } const Color & text_color () const { return m_text_color; } const Color & label_color () const { return m_label_color; } const Color & drum_color () const { return m_drum_color; } #if defined DRAW_TEMPO_LINE const Color & tempo_color () const { return m_tempo_color; } #endif const Color & progress_color () const { return m_progress_color; } const Color & pen_color () const { return m_pen_color; } const Color & back_color () const { return m_back_color; } virtual void reupdate (bool /*all*/) { // no code, handles empty button } protected: void label_color (Color c) { m_label_color = c; } void text_color (Color c) { m_text_color = c; } void pen_color (Color c) { m_pen_color = c; } void back_color (Color c) { m_back_color = c; } }; // class qslotbutton } // namespace seq66 #endif // SEQ66_QSLOTBUTTON_HPP /* * qslotbutton.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qsmaintime.hpp ================================================ #if ! defined SEQ66_QSMAINTIME_HPP #define SEQ66_QSMAINTIME_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsmaintime.hpp * * This module declares/defines the base class for the "time" progress * window. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2019-07-08 * \license GNU GPLv2 or above * */ #include #include #include namespace seq66 { class performer; /** * A beat indicator widget */ class qsmaintime final : public QWidget { Q_OBJECT public: qsmaintime ( performer & perf, QWidget * parent, int beats_per_measure = 4, int beat_width = 4 ); virtual ~qsmaintime () { // no code } int beats_per_measure () const { return m_beats_per_measure; } void beats_per_measure (int bpm); int beat_width () const { return m_beat_width; } void beat_width (int bw); protected: virtual void paintEvent (QPaintEvent *) override; virtual QSize sizeHint () const override; private: const performer & perf () const { return m_main_perf; } private: const performer & m_main_perf; QColor m_color; QFont m_font; int m_beats_per_measure; int m_beat_width; #if defined SEQ66_USE_METRONOME_FADE int m_alpha; #endif int m_last_metro; }; // class qsmaintime } // namespace seq66 #endif // SEQ66_QSMAINTIME_HPP /* * qsmaintime.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qsmainwnd.hpp ================================================ #if ! defined SEQ66_QSMAINWND_HPP #define SEQ66_QSMAINWND_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsmainwnd.hpp * * This module declares/defines the base class for the main window. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2025-07-19 * \license GNU GPLv2 or above * * The main window is known as the "Patterns window" or "Patterns panel". It * holds the "Pattern Editor" or "Sequence Editor". The main window consists * of two object: qsmainwnd, which provides the user-interface elements that * surround the patterns, and qslivegrid, which implements the behavior of * the pattern slots. Also included are tabs for managing mute-groups, MIDI * events, playlists, and information about the current session. */ #include #include #include "cfg/settings.hpp" /* seq66::combolist helper class */ #include "midi/midibytes.hpp" /* alias midibpm */ #include "play/performer.hpp" /* seq66::performer class */ /* * Q_DECLARE_METATYPE(seq66::screenset::number) doesn't work, had to switch to * using int. * * Also, we get this warning, even with "#include " or * : * * QObject::connect: Cannot queue arguments of type 'QItemSelection' * (Make sure 'QItemSelection' is registered using qRegisterMetaType().) */ /* * Forward declarations. */ class QCloseEvent; class QErrorMessage; class QFileDialog; class QMessageBox; class QResizeEvent; class QTimer; /* * The Qt UI namespace. */ namespace Ui { class qsmainwnd; } namespace seq66 { class keystroke; class qliveframeex; class qmutemaster; class qperfeditex; class qperfeditframe64; class qplaylistframe; class qsabout; class qsappinfo; class qsbuildinfo; class qseditoptions; class qseqeditex; class qseqeventframe; class qseqframe; class qsessionframe; class qsetmaster; class qslivegrid; class qslogview; class qsmaintime; class qt5nsmanager; class smanager; /** * The main window of Kepler34... er, I mean Seq66. */ class qsmainwnd final : public QMainWindow, protected performer::callbacks { friend class qmutemaster; friend class qplaylistframe; friend class qseditoptions; friend class qsessionframe; friend class qsetmaster; friend class qslivegrid; friend class qt5nsmanager; Q_OBJECT public: qsmainwnd ( performer & p, const std::string & midifilename = "", bool usensm = false, qt5nsmanager * sessionmgr = nullptr ); virtual ~qsmainwnd (); void enable_reload_button (bool flag); bool open_file (const std::string & path); void show_error_box (const std::string & msg_text); bool show_error_box_ex ( const std::string & msgtext, bool isporterror = false ); bool show_timed_error_box ( const std::string & msgtext, int timeout = 5000 ); void remove_editor (int seq); void remove_qperfedit (); void hide_qperfedit (bool hide = false); void remove_live_frame (int ssnum); void enable_bus_item (int bus, bool enabled); void set_ppqn_text (const std::string & text); void set_ppqn_text (int ppq); void reset_ppqn (); void lock_main_window (bool lockit); int ppqn () const { return cb_perf().ppqn(); } std::string specify_playlist_folder (const std::string & defalt = ""); bool specify_playlist () { return specify_list_dialog(); } bool open_playlist () { return open_list_dialog(); } bool save_playlist () { return save_list_dialog(); } bool use_nsm () const { return m_use_nsm; } void session_manager (const std::string & text); void session_path (const std::string & text); void session_display_name (const std::string & text); void session_client_id (const std::string & text); void session_URL (const std::string & text); void session_log_file (const std::string & text); void song_path (const std::string & text); void last_used_dir (const std::string & text); protected: void use_nsm (bool flag) { m_use_nsm = flag; } bool recreate_all_slots (); bool refresh_captions (); bool load_into_session (const std::string & selectedfile); protected: // performer callbacks virtual bool on_group_learn (bool learning) override; virtual bool on_group_learn_complete ( const keystroke & k, bool success ) override; virtual bool on_automation_change (automation::slot s) override; virtual bool on_sequence_change ( seq::number seqno, performer::change ctype ) override; virtual bool on_trigger_change ( seq::number seqno, performer::change mod ) override; virtual bool on_set_change ( screenset::number setno, performer::change ctype ) override; virtual bool on_mutes_change ( mutegroup::number group, performer::change mod ) override; virtual bool on_resolution_change ( int ppqn, midibpm bp, performer::change ch ) override; virtual bool on_song_action (bool signal, playlist::action) override; protected: bool report_message ( const std::string & msg, bool good, bool showcancel = true ); private: // overrides of event handlers virtual void keyPressEvent (QKeyEvent * event) override; virtual void keyReleaseEvent (QKeyEvent *) override; virtual void closeEvent (QCloseEvent *) override; virtual void changeEvent (QEvent *) override; virtual void resizeEvent (QResizeEvent *) override; /* EXPERIMENTAL * virtual void mouseMoveEvent (QMouseEvent *) override; */ private: const combolist & ppqn_list () const { return m_ppqn_list; } const combolist & beatwidth_list () const { return m_beatwidth_list; } const combolist & beats_per_bar_list () const { return m_beats_per_bar_list; } void enable_save (bool flag = true); void enable_save_update (bool flag = true); void make_perf_frame_in_tab (); bool check (); std::string midi_filename_prompt ( const std::string & prompt, const std::string & filename = "", bool promptoverwrite = false ); std::string project_folder_prompt (const std::string & prompt); void update_play_status (); void update_record_by_status (); void update_window_title (const std::string & fn = ""); void update_recent_files_menu (); void create_action_connections (); void create_action_menu (); void remove_everything (); void remove_all_editors (); void remove_ex_editors (); void update_all_editors_titles (bool modified); void remove_all_live_frames (); void remove_edit_tab_frame (); void remove_event_tab_frame (); void set_tap_button (int beats); void set_beats_per_minute (double bp, bool blockchange = false); void redo_live_frame (); bool handle_key_press (const keystroke & k); bool handle_key_release (const keystroke & k); void show_song_mode (bool songmode); bool make_edit_frame (int seqid); bool make_event_frame (int seqid); void connect_editor_slots (); void connect_nsm_slots (); void connect_normal_slots (); bool show_open_file_dialog (std::string & selectedfile); bool specify_list_dialog (); bool open_list_dialog (); bool save_list_dialog (); bool open_mutes_dialog (); bool save_mutes_dialog (const std::string & basename = ""); void update_tap (midibpm bpm); bool set_ppqn_combo (); void stop (bool rewind = false); private: /** * An alias for keeping track of external sequence edits. */ using edit_container = std::map; /** * An alias for keeping track of external live-frames. */ using live_container = std::map; private: Ui::qsmainwnd * ui; qt5nsmanager * m_session_mgr; /* LATER: unique_ptr()? */ int m_initial_width; int m_initial_height; qslivegrid * m_live_frame; qperfeditex * m_perfedit; qperfeditframe64 * m_song_frame64; qseqframe * m_edit_frame; qseqeventframe * m_event_frame; qplaylistframe * m_playlist_frame; QMessageBox * m_msg_error; /* QErrorMessage */ QMessageBox * m_msg_save_changes; QTimer * m_timer; QMenu * m_menu_recent; QList m_recent_action_list; qsmaintime * m_beat_ind; qseditoptions * m_dialog_prefs; qsabout * m_dialog_about; qsbuildinfo * m_dialog_build_info; qsappinfo * m_dialog_app_info; qslogview * m_dialog_log_view; qsessionframe * m_session_frame; qsetmaster * m_set_master; qmutemaster * m_mute_master; combolist m_ppqn_list; combolist m_beatwidth_list; combolist m_beats_per_bar_list; /** * Experiment: how to better detect changes in BPM. */ midibpm m_main_bpm; /** * Holds the last value of the MIDI-control-in status, used in displaying * the current status when it changed. */ automation::ctrlstatus m_control_status; /** * Holds the current setting of the song mode, since * ui->btnSongPlay->isChecked() seems not to be reliable. */ bool m_song_mode; /** * Hold the current L/R looping status. */ bool m_is_looping; /** * Duty now for the future! */ bool m_use_nsm; /** * Provides a workaround for a race condition when a MIDI file-name is * provided on the command line. This would cause the title to be * "unnamed". */ bool m_is_title_dirty; /** * Indicates whether to show the time as bar:beats:ticks or as * hours:minutes:seconds. The default is true: bar:beats:ticks. */ bool m_tick_time_as_bbt; /** * Holds the last performer tick, so that we can avoid refreshing the * B:B:T display and the beat indicator when not necessary. */ midipulse m_previous_tick; /** * Holds the current playing state. Used when needed to update the * stop/pause/play buttons. */ bool m_is_playing_now; /** * Holds a list of the sequences currently under edit. We do not want to * open the same sequence in two different editors. Also, we need to be * able to delete any open qseqeditex windows when exiting the * application. */ edit_container m_open_editors; /** * Holds a list of open external qliveframeex objects. */ live_container m_open_live_frames; /** * Indicates the visibility of the external performance-edit frame. */ bool m_perf_frame_visible; /** * Indicates the current set for the mainwnd, regardless of the current * play-screen. */ screenset::number m_current_main_set; /** * Indicates to shrink or hide some elements of the user interface, * primarily the seqedit frame. */ bool m_shrunken; signals: void signal_set_change (int setno); void signal_song_action (int action); private slots: void slot_open_edit_prefs (); void slot_summary_save (); void slot_tutorial (); void slot_user_manual (); void slot_set_home (); void update_bank (int newBank); void update_bank_text (); void start_playing (); void set_loop (bool loop); void pause_playing (); void stop_playing (); void set_song_mode (bool song_mode); void song_recording (bool record); void recording_ex (bool record); void panic (); void update_bpm (double bpm); void edit_bpm (); void update_set_change (int setno); void update_song_action (int playaction); void update_ppqn_by_text (const QString & text); void update_midi_bus (int bindex); void update_beats_per_measure (int bmindex); void update_beat_length (int blindex); void open_recent_file (); void new_file (); void new_session (); bool save_file (const std::string & fname = "", bool updatemenu = true); bool save_session (); bool save_file_as (); bool export_project (const std::string & fname = ""); bool export_file_as_midi (const std::string & fname = ""); bool export_file_as_smf_0 (const std::string & fname = ""); bool export_song (const std::string & fname = ""); void quit (); void import_midi_into_set (); /* normal import into set */ void import_midi_into_session (); /* import MIDI into session */ void import_project (); /* import a configuration */ void import_playlist (); void select_and_load_file (); void show_open_list_dialog (); /* connected only in normal use */ void show_save_list_dialog (); /* NOT YET CONNECTED */ void show_open_mutes_dialog (); /* NOT YET CONNECTED */ void show_save_mutes_dialog (); /* NOT YET CONNECTED */ void show_qsabout (); void show_qsbuildinfo (); void show_qsappinfo (); void show_qslogview (); void tabWidgetClicked (int newindex); void conditional_update (); /* redraw certain GUI elements */ void load_editor (int seqid); void load_event_editor (int seqid); void load_qseqedit (int seqid); void load_qseqedit_ex (int seqid, bool active); void load_qperfedit (bool on); void load_live_frame (int ssnum); void load_session_frame (); void load_set_master (); void load_mute_master (); void toggle_time_format (bool on); void reset_sets (); void open_performance_edit (); void apply_song_transpose (); void reload_mute_groups (); void clear_mute_groups (); void set_song_mute_on (); void set_song_mute_off (); void set_song_mute_toggle (); void set_playscreen_copy (); void set_playscreen_paste (); void learn_toggle (); void tap (); void queue_it (); void slot_test (); void slot_close_externals(); void slot_show_hide (); private: void remove_set_master (); void update_time (midipulse tick); void toggle_loop (); qt5nsmanager * session () { return m_session_mgr; } }; // class qsmainwnd } // namespace seq66 #endif // SEQ66_QSMAINWND_HPP /* * qsmainwnd.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qstriggereditor.hpp ================================================ #if ! defined SEQ66_QSTRIGGEREDITOR_HPP #define SEQ66_QSTRIGGEREDITOR_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qstriggereditor.hpp * * This module declares/defines the base class for the Pattern Editor window * piano roll. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2023-12-07 * \license GNU GPLv2 or above * * This class represents the central piano-roll user-interface area of the * performance/song editor. It is the Qt version of the seqevent class, and * is used in qseqeditframe64. */ #include #include #include #include #include #include "midi/midibytes.hpp" /* seq66::midibyte, other aliases */ #include "qseqbase.hpp" /* seq66::qseqbase base class */ namespace seq66 { class performer; class qseqdata; class qseqeditframe64; /** * Displays the triggers for MIDI events (e.g. Mod Wheel, Pitch Bend) in the * event pane underneath the qseqroll pane. * * Note that the qeqbase mixin class is publicly inherited so that the * qseqeditrame classes can access the public member of this class. */ class qstriggereditor final : public QWidget, public qseqbase { friend class qseqeditframe64; Q_OBJECT public: qstriggereditor ( performer & perf, sequence & s, qseqeditframe64 * frame, int zoom, int snap, int keyheight, QWidget * parent, int xoffset ); virtual ~qstriggereditor (); void set_data_type (midibyte status, midibyte control); bool is_tempo () const { return m_is_tempo; } bool is_time_signature () const { return m_is_time_signature; } bool is_program_change () const { return m_is_program_change; } private: void flag_dirty (); /* tricky code */ int select_events ( eventlist::select selmode, midipulse start, midipulse finish ); void is_tempo (bool flag) { m_is_tempo = flag; } void is_time_signature (bool flag) { m_is_time_signature = flag; } void is_program_change (bool flag) { m_is_program_change = flag; } protected: virtual void paintEvent (QPaintEvent *) override; virtual void resizeEvent (QResizeEvent *) override; virtual void mousePressEvent (QMouseEvent *) override; virtual void mouseReleaseEvent (QMouseEvent *) override; virtual void mouseMoveEvent (QMouseEvent *) override; virtual void keyPressEvent (QKeyEvent *) override; virtual void keyReleaseEvent (QKeyEvent *) override; virtual QSize sizeHint () const override; virtual void wheelEvent (QWheelEvent *) override; private: void draw_grid (QPainter & painter, const QRect & r); signals: public slots: void conditional_update (); private: virtual void set_adding (bool adding) override; void x_to_w (int x1, int x2, int & x, int & w); void start_paste (); void convert_x (int x, midipulse & tick); void convert_t (midipulse ticks, int & x); void drop_event (midipulse tick); bool movement_key_press (int key); void move_selected_events (midipulse dt); private: QTimer * m_timer; int m_x_offset; int m_key_y; bool m_is_tempo; /* a reasonably editable meta event */ bool m_is_time_signature; /* a reasonably displayable meta event */ bool m_is_program_change; /* a special case */ midibyte m_status; /* event seqdata is currently editing */ midibyte m_cc; /* controller being edited */ }; // class qstriggereditor } // namespace seq66 #endif // SEQ66_QSTRIGGEREDITOR_HPP /* * qstriggereditor.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qt5_helper.h ================================================ #if ! defined SEQ66_QT5_HELPER_H #define SEQ66_QT5_HELPER_H /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qt5_helper.h * * This module declares/defines more helpful macros. * * \library seq66 application * \author Chris Ahlstrom * \date 2022-04-10 * \updates 2026-01-23 * \license GNU GPLv2 or above * * This file is meant to be included in the C++ "cpp" modules, after any * Qt header-file #includes. */ /* * A macro to use in a few places, to make things a little easier. * Strictly macro substitution. */ #include /* #include "qconfig.h" */ #undef QT_VERSION_L58 #undef QT_VERSION_5 #undef QT_VERSION_6 #undef QT_VERSION_7 #undef QT5_HELPER_RADIO_SIGNAL #if QT_VERSION < QT_VERSION_CHECK(5, 8, 0) #define QT_VERSION_L58 #elif QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #define QT_VERSION_5 #elif QT_VERSION < QT_VERSION_CHECK(7, 0, 0) #define QT_VERSION_6 #elif QT_VERSION < QT_VERSION_CHECK(8, 0, 0) #define QT_VERSION_7 #endif #if QT_VERSION < QT_VERSION_CHECK(5, 8, 0) #define QT5_HELPER_RADIO_SIGNAL \ static_cast(&QButtonGroup::buttonClicked) #elif QT_VERSION < QT_VERSION_CHECK(5, 15, 0) #define QT5_HELPER_RADIO_SIGNAL QOverload::of(&QButtonGroup::buttonClicked) #else #define QT5_HELPER_RADIO_SIGNAL QOverload::of(&QButtonGroup::idClicked) #endif #endif // SEQ66_QT5_HELPER_H /* * qt5_helper.h * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qt5_helpers.hpp ================================================ #if ! defined SEQ66_QT5_HELPERS_HPP #define SEQ66_QT5_HELPERS_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qt5_helpers.hpp * * This module declares/defines some helpful free functions to support Qt and * C++ integration. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-03-14 * \updates 2026-03-13 * \license GNU GPLv2 or above * */ #include "ctrl/keymap.hpp" /* seq66::qt_modkey_ordinal() */ #include "ctrl/keystroke.hpp" /* seq66::keystroke wrapper class */ #include "qt5_helper.h" /* Qt versioning macros */ /* * Currently disabled because it's nearly impossible to get the absolute * position of the mouse correct with multiple monitors. */ #undef SEQ66_SHOW_GENERIC_TOOLTIPS /** * The install_scroll_filter_function is currently unused. */ #undef SEQ66_INSTALL_SCROLL_FILTER class QAction; class QComboBox; class QIcon; class QKeyEvent; class QInputEvent; class QLayoutItem; class QLineEdit; class QMenu; class QMouseEvent; class QPushButton; class QScrollArea; class QSpinBox; class QTimer; class QWidget; /* * Don't document the namespace. */ namespace seq66 { class combolist; /* * Free constants in the seq66 namespace. These values are simply visible * booleans for using file dialogs. */ const bool SavingFile = true; const bool OpeningFile = false; const bool ConfigFile = true; const bool NormalFile = false; /* * Free functions in the seq66 namespace. */ extern std::string qt_get_color (QPushButton * button); extern std::string qt_set_color ( const std::string & colorname, QPushButton * button ); extern void qt_set_icon (const char * pixmap_array [], QPushButton * button); extern std::string qt_icon_theme (); extern bool qt_prompt_ok ( const std::string & text, const std::string & info ); extern void qt_info_box (QWidget * self, const std::string & msg); extern void qt_error_box (QWidget * self, const std::string & msg); extern std::string qt_get_string ( QWidget * self, const std::string & title, const std::string & label, const std::string & text = "" ); extern keystroke qt_keystroke ( QKeyEvent * event, keystroke::action rp, bool testing = false ); extern QString qt (const std::string & text); /* * See qt5_helper.h for these macros. */ #if defined QT_VERSION_L58 /* see qt5_helper.h */ #error Qt less than 5.8 is not supported. #endif extern int qt_mouse_x (QMouseEvent * ev); extern int qt_mouse_y (QMouseEvent * ev); #if defined QT_VERSION_7 /* see qt5_helper.h */ #error Qt 7 is not supported. #endif extern void qt_set_layout_visibility (QLayoutItem * item, bool visible); extern QTimer * qt_timer ( QObject * self, const std::string & name, int redraw_factor, const char * slotname ); extern void enable_combobox_item ( QComboBox * box, int index, bool enabled = true ); extern void set_combobox_item ( QComboBox * box, int index, const std::string & text ); extern bool populate_midich_combo ( QComboBox * combo, int buss, int channel ); extern bool fill_combobox ( QComboBox * box, const combolist & clist, const std::string & value = "", const std::string & prefix = "", const std::string & suffix = "" ); #if defined SEQ66_INSTALL_SCROLL_FILTER extern bool install_scroll_filter (QWidget * monitor, QScrollArea * target); #endif extern void set_spin_value (QSpinBox * spin, int value); extern QAction * new_qaction (const std::string & text, const QIcon & micon); extern QAction * new_qaction (const std::string & text, QObject * parent); extern QMenu * new_qmenu (const std::string & text, QWidget * parent = nullptr); extern bool show_open_midi_file_dialog (QWidget * parent, std::string & file); extern bool show_import_midi_file_dialog (QWidget * parent, std::string & file); extern bool show_select_project_dialog ( QWidget * parent, std::string & selecteddir, std::string & selectedfile ); extern bool show_playlist_dialog ( QWidget * parent, std::string & file, bool saving ); extern bool show_text_file_dialog (QWidget * parent, std::string & file); extern bool show_exe_file_dialog (QWidget * parent, std::string & file); extern bool show_file_dialog ( QWidget * parent, std::string & selectedfile, const std::string & prompt = "", const std::string & filterlist = "", bool saving = false, bool forceconfig = false, const std::string & extension = "", bool promptoverwrite = true ); extern bool show_file_select_dialog ( QWidget * parent, const std::string & extension, std::string & selecteddir, std::string & selectedfile ); extern bool show_folder_dialog ( QWidget * parent, std::string & selectedfolder, const std::string & prompt = "", bool forcehome = false ); extern void tooltip_with_keystroke ( QWidget * widget, const std::string & keyname, int duration = -1 ); extern void tooltip_for_filename ( QLineEdit * lineedit, const std::string & filespec, int duration = -1 ); #if defined SEQ66_SHOW_GENERIC_TOOLTIPS extern void generic_tooltip ( QWidget * widget, const std::string & tiptext, int x, int y, int msduration = -1 ); #endif extern bool is_empty (const QLineEdit * lineedit); } // namespace seq66 #endif // SEQ66_QT5_HELPERS_HPP /* * qt5_helpers.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/include/qt5nsmanager.hpp ================================================ #if ! defined SEQ66_QT5NSMANAGER_HPP #define SEQ66_QT5NSMANAGER_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qt5nsmanager.hpp * * This module declares/defines the main module for the JACK/ALSA "qt5" * implementation of this application. * * \library qt5nsmanager application * \author Chris Ahlstrom * \date 2020-03-15 * \updates 2024-11-07 * \license GNU GPLv2 or above * * This is an attempt to change from the hoary old (or, as H.P. Lovecraft * would style it, "eldritch") gtkmm-2.4 implementation of Seq66. */ #include /* Qt 5 QObject class */ #include /* std::unique_ptr<> */ #include "sessions/clinsmanager.hpp" /* seq66::clinsmanager */ #include "qsmainwnd.hpp" /* Qt 5 qsmainwnd main window */ /* * ":dirty:message:optional-gui:switch:" */ const std::string c_qt5_nsm_capabilities {":dirty:message:optional-gui:"}; /* * Forward reference */ class QApplication; class QTimer; namespace seq66 { /** * Provides the graphical user-interface implementation for the session * manager client. */ class qt5nsmanager : public QObject, public clinsmanager { friend class qsmainwnd; /* for send_visibility() */ Q_OBJECT public: qt5nsmanager () = delete; qt5nsmanager ( QApplication & app, QObject * parent = nullptr, const std::string & caps = c_qt5_nsm_capabilities ); qt5nsmanager (const qt5nsmanager &) = delete; qt5nsmanager & operator = (const qt5nsmanager &) = delete; virtual ~qt5nsmanager (); virtual bool close_session (std::string & msg, bool ok = true) override; virtual bool create_window () override; virtual void show_message ( const std::string & tag, const std::string & msg ) const override; virtual void show_error ( const std::string & tag, const std::string & msg ) const override; virtual bool run () override; virtual void session_manager_name (const std::string & mgrname) override; virtual void session_manager_path (const std::string & pathname) override; virtual void session_display_name (const std::string & dispname) override; virtual void session_client_id (const std::string & clid) override; /* * Version 0.99.16. Added to access main window to clear modified * flag and remove editor windows. */ virtual bool save_session (std::string & msg, bool ok = true) override; private: void quit (); void handle_show_hide (bool hide); void show_gui (); void hide_gui (); void send_visibility (bool visible); void set_last_dirty (); void client_show_hide (); void set_session_url (); signals: /* from session client callbacks */ private slots: void conditional_update (); /* timer poll for dirty/clean */ private: QApplication & m_application; QTimer * m_timer; std::unique_ptr m_window; bool m_was_hidden; }; // class qt5nsmanager } // namespace seq66 #endif // SEQ66_QT5NSMANAGER_HPP /* * qt5nsmanager.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/seq_qt5.pro ================================================ #****************************************************************************** # seq_qt5.pro (qpseq66) #------------------------------------------------------------------------------ ## # \file seq_qt5.pro # \library qpseq66 application # \author Chris Ahlstrom # \date 2018-04-08 # \update 2025-01-25 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Created by and for Qt Creator This file was created for editing the project # sources only. You may attempt to use it for building too, by modifying this # file here. # # Important: # # This project file is designed only for Qt 5 (and above?). # # Removed: # # qseqeditframe.cpp/.hpp/.ui # #------------------------------------------------------------------------------ message($$_PRO_FILE_PWD_) QT += core gui widgets TEMPLATE = lib CONFIG += staticlib config_prl qtc_runnable c++14 DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050F00 # These are needed to set up seq66_platform_macros: CONFIG(debug, debug|release) { DEFINES += DEBUG } else { DEFINES += NDEBUG } contains (CONFIG, rtmidi) { MIDILIB = rtmidi DEFINES += "SEQ66_MIDILIB=rtmidi" DEFINES += "SEQ66_RTMIDI_SUPPORT=1" } else { MIDILIB = portmidi DEFINES += "SEQ66_MIDILIB=portmidi" DEFINES += "SEQ66_PORTMIDI_SUPPORT=1" } TARGET = seq_qt5 # Target file directory: # DESTDIR = bin # # Intermediate object files directory: # OBJECTS_DIR = generated_files # # Intermediate moc files directory: # MOC_DIR = moc # # Not UIC_DIR :-D UI_DIR = forms FORMS += forms/qlfoframe.ui \ forms/qliveframeex.ui \ forms/qmutemaster.ui \ forms/qpatternfix.ui \ forms/qperfeditex.ui \ forms/qperfeditframe64.ui \ forms/qplaylistframe.ui \ forms/qsabout.ui \ forms/qsappinfo.ui \ forms/qsbuildinfo.ui \ forms/qseditoptions.ui \ forms/qseqeditex.ui \ forms/qseqeditframe64.ui \ forms/qseqeventframe.ui \ forms/qsessionframe.ui \ forms/qsetmaster.ui \ forms/qslivegrid.ui \ forms/qslogview.ui \ forms/qsmainwnd.ui HEADERS += include/gui_palette_qt5.hpp \ include/palettefile.hpp \ include/qbase.hpp \ include/qclocklayout.hpp \ include/qeditbase.hpp \ include/qinputcheckbox.hpp \ include/qlfoframe.hpp \ include/qliveframeex.hpp \ include/qloopbutton.hpp \ include/qmutemaster.hpp \ include/qpatternfix.hpp \ include/qperfbase.hpp \ include/qperfeditex.hpp \ include/qperfeditframe64.hpp \ include/qperfnames.hpp \ include/qperfroll.hpp \ include/qperftime.hpp \ include/qplaylistframe.hpp \ include/qportwidget.hpp \ include/qsabout.hpp \ include/qsappinfo.hpp \ include/qsbuildinfo.hpp \ include/qscrollmaster.h \ include/qscrollslave.h \ include/qseditoptions.hpp \ include/qseqbase.hpp \ include/qseqdata.hpp \ include/qseqeditex.hpp \ include/qseqeditframe64.hpp \ include/qseqeventframe.hpp \ include/qseqframe.hpp \ include/qseqkeys.hpp \ include/qseqroll.hpp \ include/qseqtime.hpp \ include/qsessionframe.hpp \ include/qsetmaster.hpp \ include/qseventslots.hpp \ include/qslivebase.hpp \ include/qslivegrid.hpp \ include/qslogview.hpp \ include/qslotbutton.hpp \ include/qsmaintime.hpp \ include/qsmainwnd.hpp \ include/qstriggereditor.hpp \ include/qt5_helper.h \ include/qt5_helpers.hpp \ include/qt5nsmanager.hpp SOURCES += src/gui_palette_qt5.cpp \ src/palettefile.cpp \ src/qbase.cpp \ src/qclocklayout.cpp \ src/qeditbase.cpp \ src/qinputcheckbox.cpp \ src/qlfoframe.cpp \ src/qliveframeex.cpp \ src/qloopbutton.cpp \ src/qmutemaster.cpp \ src/qpatternfix.cpp \ src/qperfbase.cpp \ src/qperfeditex.cpp \ src/qperfeditframe64.cpp \ src/qperfnames.cpp \ src/qperfroll.cpp \ src/qperftime.cpp \ src/qplaylistframe.cpp \ src/qportwidget.cpp \ src/qsabout.cpp \ src/qsappinfo.cpp \ src/qsbuildinfo.cpp \ src/qscrollmaster.cpp \ src/qscrollslave.cpp \ src/qseditoptions.cpp \ src/qseqbase.cpp \ src/qseqdata.cpp \ src/qseqeditex.cpp \ src/qseqeditframe64.cpp \ src/qseqeventframe.cpp \ src/qseqframe.cpp \ src/qseqkeys.cpp \ src/qseqroll.cpp \ src/qseqtime.cpp \ src/qsessionframe.cpp \ src/qsetmaster.cpp \ src/qseventslots.cpp \ src/qslivebase.cpp \ src/qslivegrid.cpp \ src/qslogview.cpp \ src/qslotbutton.cpp \ src/qsmaintime.cpp \ src/qsmainwnd.cpp \ src/qstriggereditor.cpp \ src/qt5_helpers.cpp \ src/qt5nsmanager.cpp # The output of the uic command goes to the seq_qt5/forms directory in # the shadow directory, and cannot be located unless the OUT_PWD macro # is used to find that directory. INCLUDEPATH = include \ ../include/qt/$${MIDILIB} \ ../libseq66/include \ ../libsessions/include \ ../seq_$${MIDILIB}/include \ ../resources \ $$OUT_PWD #****************************************************************************** # seq_qt5.pro (qpseq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: seq_qt5/src/Makefile.am ================================================ #****************************************************************************** # Makefile.am (libseq_qt5) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library libseq_qt5 library # \author Chris Ahlstrom # \date 2018-01-01 # \update 2026-03-13 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the libseq_qt5 C/C++ # library. # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 subdir-objects MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #****************************************************************************** # CLEANFILES #------------------------------------------------------------------------------ # # Do not forget to clean the moc/uic/rcc generated files. # #------------------------------------------------------------------------------ CLEANFILES = *.gc* *.moc.cpp ../forms/*.ui.h MOSTLYCLEANFILES = *~ #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = #****************************************************************************** # Items from configure.ac #------------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ GIT_VERSION = @GIT_VERSION@ SEQ66_API_MAJOR = @SEQ66_API_MAJOR@ SEQ66_API_MINOR = @SEQ66_API_MINOR@ SEQ66_API_PATCH = @SEQ66_API_PATCH@ SEQ66_API_VERSION = @SEQ66_API_VERSION@ SEQ66_LT_CURRENT = @SEQ66_LT_CURRENT@ SEQ66_LT_REVISION = @SEQ66_LT_REVISION@ SEQ66_LT_AGE = @SEQ66_LT_AGE@ SEQ66_LIBTOOL_VERSION = @SEQ66_LIBTOOL_VERSION@ QT_CXXFLAGS = @QT_CXXFLAGS@ QT_MOC = @QT_MOC@ QT_RCC = @QT_RCC@ QT_UIC = @QT_UIC@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ prefix = @prefix@ includedir = @seq66includedir@ libdir = @seq66libdir@ datadir = @datadir@ datarootdir = @datarootdir@ seq66includedir = @seq66includedir@ seq66libdir = @seq66libdir@ #****************************************************************************** # localedir #------------------------------------------------------------------------------ # # 'localedir' is the normal system directory for installed localization # files. # #------------------------------------------------------------------------------ localedir = $(datadir)/locale DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ #***************************************************************************** # libtool #----------------------------------------------------------------------------- version = $(SEQ66_LIBTOOL_VERSION) #***************************************************************************** # git_version #----------------------------------------------------------------------------- # git_version = $(shell git describe --abbrev=7 --always --tags) #----------------------------------------------------------------------------- git_version = $(shell git describe --tags --long) git_branch = $(shell git branch | grep -e ^*) git_info = "$(git_version) $(git_branch)" #***************************************************************************** # Qt 5 results directories (avoid polluting the src directory) #----------------------------------------------------------------------------- # # The UI forms are stored in the "forms" directory, while the generated files # are stored in the "qt" directory, for both moc'ing and uic'ing. # # hppdir = ../include # #----------------------------------------------------------------------------- thisdir = $(top_srcdir)/seq_qt5 incdir = $(top_srcdir)/seq_qt5/include formsdir = $(top_srcdir)/seq_qt5/forms resourcedir = $(top_srcdir)/resources libseq66_incdir = $(top_srcdir)/libseq66/include libsessions_incdir = $(top_srcdir)/libsessions/include libseq66_libs = -L$(libseq66_libdir) -lseq66 libseq66_file = $(libseq66_libdir)/libseq66.la libsessions_libs = -L$(libseq66_libdir) -lsessions libsessions_file = $(libseq66_libdir)/libsessions.la seq_rtmidi_incdir = $(top_srcdir)/seq_rtmidi/include seq_portmidi_incdir = $(top_srcdir)/seq_portmidi/include #****************************************************************************** # Compiler and linker flags (later, PortMidi) #------------------------------------------------------------------------------ # # Unfortunately, we need to add the platform-specific include directories # because we include the performer module in some modules, and it includes # the platform-specific stuff. # # Notes: # # - None of ALSA or JACK CFLAGS seem to be defined. # - We include "-I.." and require that the Qt5-generated modules be # included as: "#include "qt/generated_module.h". # - -DQT_DISABLE_DEPRECATED_BEFORE=0x050F00 causes warnings due to # mismatches, mostly in qnamespace.h. So we put it only in # seq_qt5.pro for now. # #------------------------------------------------------------------------------ AM_CXXFLAGS = \ -I../include \ -I$(thisdir) \ -I$(incdir) \ -I$(formsdir) \ -I$(libseq66_incdir) \ -I$(libsessions_incdir) \ -I$(seq_portmidi_incdir) \ -I$(seq_rtmidi_incdir) \ -I$(resourcedir) \ $(ALSA_CFLAGS) \ $(QT_CXXFLAGS) \ $(JACK_CFLAGS) \ -DSEQ66_GIT_VERSION=\"$(git_info)\" #****************************************************************************** # Moc flags #------------------------------------------------------------------------------ MOC_CPPFLAGS = -I$(top_srcdir)/include # Deprecated synonym ofr Q_OS_DARWIN. Do not use. # if BUILD_QTMIDI # MOC_CPPFLAGS += -DQ_OS_MAC # endif #****************************************************************************** # Qt 5 rules #------------------------------------------------------------------------------ # # $@ is the name of the file being generated; # $< is the first prerequisite (the source file) # #------------------------------------------------------------------------------ SUFFIXES = .h .hpp .cpp .moc.cpp .qrc .qrc.cpp .ui .ui.h # The moc.cpp files are normally deposited in the include directory! # # $(QT_MOC) $(MOC_CPPFLAGS) -o ../include/$(notdir $@) $< %.moc.cpp: %.hpp $(QT_MOC) $(MOC_CPPFLAGS) -o $@ $< # The ui.h files are deposited in the forms directory. %.ui.h: %.ui $(QT_UIC) -o $@ $< # The qrc files hold resources needed that aren't code. %.qrc.cpp: %.qrc $(QT_RCC) -o $@ $< #****************************************************************************** # The library to build, a libtool-based library #------------------------------------------------------------------------------ lib_LTLIBRARIES = libseq_qt5.la #****************************************************************************** # Source files #---------------------------------------------------------------------------- # # We don't want to put Qt5-generated files into the "include" or "src" # directory; we want them in the "qt5" directory. # # moc_sources = $(moc_qt_headers:.hpp=.moc.cpp) # # However.... # # Added qperfnames, qperfroll, qperftime, qseqdata, qseqkeys, qsmaintime, # qseqroll, qseqtime, qstriggereditor, due to vtable error for their # constructors or destructors. # # Also note that we have to include ALL modules that declare QOBJECT in this # list, otherwise we get "undefined reference to 'vtable...'" for # their constructors. Took awhile to figure that one out; qtcreator/qmake # took care of that issue, but in automake we have to do it ourselves. # # Moved to contrib/code: qchannelpopup.hpp # #---------------------------------------------------------------------------- moc_qt_headers = \ ../include/qclocklayout.hpp \ ../include/qinputcheckbox.hpp \ ../include/qlfoframe.hpp \ ../include/qliveframeex.hpp \ ../include/qloopbutton.hpp \ ../include/qmutemaster.hpp \ ../include/qpatternfix.hpp \ ../include/qperfeditex.hpp \ ../include/qperfeditframe64.hpp \ ../include/qperfnames.hpp \ ../include/qperfroll.hpp \ ../include/qperftime.hpp \ ../include/qplaylistframe.hpp \ ../include/qportwidget.hpp \ ../include/qsabout.hpp \ ../include/qsappinfo.hpp \ ../include/qsbuildinfo.hpp \ ../include/qseditoptions.hpp \ ../include/qseqdata.hpp \ ../include/qseqeditex.hpp \ ../include/qseqeditframe64.hpp \ ../include/qseqeventframe.hpp \ ../include/qseqframe.hpp \ ../include/qseqkeys.hpp \ ../include/qseqroll.hpp \ ../include/qseqtime.hpp \ ../include/qsessionframe.hpp \ ../include/qsetmaster.hpp \ ../include/qseventslots.hpp \ ../include/qslivegrid.hpp \ ../include/qslogview.hpp \ ../include/qsmaintime.hpp \ ../include/qsmainwnd.hpp \ ../include/qstriggereditor.hpp \ ../include/qt5nsmanager.hpp moc_sources = $(moc_qt_headers:.hpp=.moc.cpp) uic_qt_forms = \ $(formsdir)/qlfoframe.ui \ $(formsdir)/qliveframeex.ui \ $(formsdir)/qmutemaster.ui \ $(formsdir)/qpatternfix.ui \ $(formsdir)/qperfeditex.ui \ $(formsdir)/qperfeditframe64.ui \ $(formsdir)/qplaylistframe.ui \ $(formsdir)/qsabout.ui \ $(formsdir)/qsappinfo.ui \ $(formsdir)/qsbuildinfo.ui \ $(formsdir)/qseditoptions.ui \ $(formsdir)/qseqeditex.ui \ $(formsdir)/qseqeditframe64.ui \ $(formsdir)/qseqeventframe.ui \ $(formsdir)/qsessionframe.ui \ $(formsdir)/qsetmaster.ui \ $(formsdir)/qslivegrid.ui \ $(formsdir)/qslogview.ui \ $(formsdir)/qsmainwnd.ui uic_sources = $(uic_qt_forms:.ui=.ui.h) BUILT_SOURCES = $(uic_sources) $(moc_sources) #****************************************************************************** # BUILT_SOURCES # # Moved to contrib/code: qchannelpopup.cpp # #------------------------------------------------------------------------------ libseq_qt5_la_SOURCES = \ gui_palette_qt5.cpp \ palettefile.cpp \ qbase.cpp \ qclocklayout.cpp \ qeditbase.cpp \ qinputcheckbox.cpp \ qlfoframe.cpp \ qliveframeex.cpp \ qloopbutton.cpp \ qmutemaster.cpp \ qpatternfix.cpp \ qperfbase.cpp \ qperfeditex.cpp \ qperfeditframe64.cpp \ qperfnames.cpp \ qperfroll.cpp \ qperftime.cpp \ qplaylistframe.cpp \ qportwidget.cpp \ qsabout.cpp \ qsappinfo.cpp \ qsbuildinfo.cpp \ qscrollmaster.cpp \ qscrollslave.cpp \ qseditoptions.cpp \ qseqbase.cpp \ qseqdata.cpp \ qseqeditex.cpp \ qseqeditframe64.cpp \ qseqeventframe.cpp \ qseqframe.cpp \ qseqkeys.cpp \ qseqroll.cpp \ qsessionframe.cpp \ qseqtime.cpp \ qsetmaster.cpp \ qseventslots.cpp \ qslivebase.cpp \ qslivegrid.cpp \ qslogview.cpp \ qslotbutton.cpp \ qsmaintime.cpp \ qsmainwnd.cpp \ qstriggereditor.cpp \ qt5_helpers.cpp \ qt5nsmanager.cpp \ $(moc_sources) libseq_qt5_la_LDFLAGS = -version-info $(version) libseq_qt5_la_LIBADD = $(ALSA_LIBS) $(JACK_LIBS) #****************************************************************************** # uninstall-hook #------------------------------------------------------------------------------ # # We'd like to remove /usr/local/include/libseq_qt5-1.0 if it is # empty. However, we don't have a good way to do it yet. # #------------------------------------------------------------------------------ uninstall-hook: @echo "Note: you may want to remove $(libdir) manually" #****************************************************************************** # Makefile.am (libseq_qt5) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: seq_qt5/src/gui_palette_qt5.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file gui_palette_qt5.cpp * * This module declares/defines the class for providing GTK/GDK colors. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-02-23 * \updates 2025-10-13 * \license GNU GPLv2 or above * * One possible idea would be a color configuration that would radically * change drawing of the lines and pixmaps, opening up the way for night * views and color schemes that match the desktop theme. * * There are some predefined QColor objects: white, black, red, darkRed, * green, darkGreen, blue, darkBlue, cyan, darkCyan, magenta, darkMagenta, * yellow, darkYellow, gray, darkGray, lightGray, color0 and color1, * accessible as members of the Qt namespace (ie. Qt::red). Many of these * colors can now be modified from a 'palette' file. * * These uses are made of each color: * * - Black. The background color of armed patterns. The color of * most lines in the user interface, including the main grid * lines. * - White. The default background color of just about everything * drawn in the application. * - Grey. The color of minor grid lines and the markers for the * currently-selected scale. * - Dark grey. The color of some grid lines, and the background * of a queued pattern slot. * - Light grey. The color of some grid lines. * - Red. The optional color of progress bars. * - Orange. The fill-in color for selected notes and events. * - Dark orange. The color of selected event data lines and the * color of the selection box for events to be pasted. * - Yellow. The background of the pattern and name slots for empty * patterns. The text color for selected empty pattern slots. * - Green. Not yet used. * - Blue. Not yet used. * - Dark cyan. The background color of muted patterns currently in * edit, or the pattern that contains the original data for an * imported SMF 0 song. The text color of an unmuted pattern * currently in edit. These colors apply to the pattern editor and * the song editor. The color of the selected background pattern * in the song editor. * - Line color. The generic line color, meant for expansion. * Currently black. * - Progress color. The progress line color. Black by default, but * can be set to other colors. * - Background color. The currently-in-use background color. Can * vary a lot when a pixmap is being redrawn. * - Foreground color. The currently-in-use foreground color. Can * vary a lot when a pixmap is being redrawn. */ #include "cfg/settings.hpp" /* seq66::rc() or seq66::usr() */ #include "gui_palette_qt5.hpp" /* seq66::gui_palette_qt5 */ #include "util/basic_macros.hpp" /* seq66::file_error() function */ #include "util/strfunctions.hpp" /* seq66 string functions */ namespace seq66 { /** * Provide access to the internal, basic color palette. We use a static * function to access this item (plus a couple of things we don't need). * Note that we have to associate a file-name later, if any. A little * clumsy; but we don't want to move this into a non-Qt-GUI module. */ gui_palette_qt5 & global_palette () { static gui_palette_qt5 s_pallete; return s_pallete; } Color get_color_fix (PaletteColor index) { return global_palette().get_color_fix(index); } Color get_pen_color (PaletteColor index) { return global_palette().get_pen_color(index); } std::string get_color_name (PaletteColor index) { return global_palette().get_color_name(index); } std::string get_color_name_ex (PaletteColor index) { return global_palette().get_color_name_ex(index); } bool no_color (int c) { return global_palette().no_color(c); } bool is_theme_color (const Color & c) { return global_palette().is_theme_color(c); } /** * Indicates the user is specifying a Seq66-drawn user-interface that has * a dark grid background. This is separate from usr().dark_theme(). */ bool is_dark_ui () { return global_palette().dark_ui(); } /** * Foreground paint uses the invertible color for black. */ Color foreground_paint () { return global_palette().get_color(InvertibleColor::black); } /** * Background paint uses the invertible color for white. */ Color background_paint () { return global_palette().get_color(InvertibleColor::white); } Color label_paint () { return global_palette().get_color(InvertibleColor::label); } Color sel_paint () { return global_palette().get_color(InvertibleColor::selection); } Color drum_paint () { return global_palette().get_color(InvertibleColor::drum); } Color tempo_paint () { return global_palette().get_color(InvertibleColor::tempo); } Color note_in_paint () { return global_palette().get_color(InvertibleColor::note_in); } Color note_out_paint () { return global_palette().get_color(InvertibleColor::note_out); } Color black_key_paint () { return global_palette().get_color(InvertibleColor::black_key); } Color white_key_paint () { return global_palette().get_color(InvertibleColor::white_key); } Color progress_paint () { return global_palette().get_color(InvertibleColor::progress); } Color backseq_paint () { return global_palette().get_color(InvertibleColor::backseq); } Color chord_paint () { return global_palette().get_color(InvertibleColor::backseq); } Color bar_paint () { return global_palette().get_color(InvertibleColor::dk_grey); } Color grey_paint () { return global_palette().get_color(InvertibleColor::grey); } Color step_paint () { return global_palette().get_color(InvertibleColor::lt_grey); } Color beat_paint () { return global_palette().get_color(InvertibleColor::beat); } Color near_paint () { return global_palette().get_color(InvertibleColor::near); } Color backtime_paint () { return global_palette().get_color(InvertibleColor::backtime); } Color backdata_paint () { return global_palette().get_color(InvertibleColor::backdata); } Color backevent_paint () { return global_palette().get_color(InvertibleColor::backevent); } Color backkeys_paint () { return global_palette().get_color(InvertibleColor::backkeys); } Color backnames_paint () { return global_palette().get_color(InvertibleColor::backnames); } Color octave_paint () { return global_palette().get_color(InvertibleColor::octave); } Color text_paint () { return global_palette().get_color(InvertibleColor::text); } Color text_time_paint () { return global_palette().get_color(InvertibleColor::texttime); } Color text_data_paint () { return global_palette().get_color(InvertibleColor::textdata); } Color note_event_paint () { return global_palette().get_color(InvertibleColor::noteevent); } Color text_keys_paint () { return global_palette().get_color(InvertibleColor::textkeys); } Color text_names_paint () { return global_palette().get_color(InvertibleColor::textnames); } Color text_slots_paint () { return global_palette().get_color(InvertibleColor::textslots); } Color scale_paint () { return global_palette().get_color(InvertibleColor::scale); } Color extra_paint () { return global_palette().get_color(InvertibleColor::extra); } Brush gui_empty_brush () { return global_palette().get_brush(gui_palette_qt5::brush::empty); } Brush gui_note_brush () { return global_palette().get_brush(gui_palette_qt5::brush::note); } bool gui_use_gradient_brush () { return global_palette().use_gradient_brush(); } Brush gui_scale_brush () { return global_palette().get_brush(gui_palette_qt5::brush::scale); } Brush gui_backseq_brush () { return global_palette().get_brush(gui_palette_qt5::brush::backseq); } Brush gui_chord_brush () { return global_palette().get_brush(gui_palette_qt5::brush::chord); } PenStyle gui_measure_pen_style () { return global_palette().pen_style(gui_palette_qt5::pen::measure); } PenStyle gui_beat_pen_style () { return global_palette().pen_style(gui_palette_qt5::pen::beat); } PenStyle gui_fourth_pen_style () { return global_palette().pen_style(gui_palette_qt5::pen::fourth); } PenStyle gui_step_pen_style () { return global_palette().pen_style(gui_palette_qt5::pen::step); } /** * Secret color to indicate to use the corresponding theme color. Used for a * label-color as the default color. */ static Color m_theme; /** * Bright constant colors */ static Color m_black; static Color m_red; static Color m_green; static Color m_yellow; static Color m_blue; static Color m_magenta; static Color m_cyan; static Color m_white; /** * Dark static colors */ static Color m_dk_black; static Color m_dk_red; static Color m_dk_green; static Color m_dk_yellow; static Color m_dk_blue; static Color m_dk_magenta; static Color m_dk_cyan; static Color m_dk_white; /** * Extended colors in the palette. */ static Color m_orange; static Color m_pink; static Color m_color_18; static Color m_color_19; static Color m_color_20; static Color m_color_21; static Color m_color_22; static Color m_grey; static Color m_dk_orange; static Color m_dk_pink; static Color m_color_26; static Color m_color_27; static Color m_color_28; static Color m_color_29; static Color m_lt_grey; static Color m_dk_grey; /** * Principal constructor. In the constructor one can only allocate colors; * get_window() returns 0 because this window has not yet been realized. * Also note that the possible color names that can be used are found in * /usr/share/X11/rgb.txt. * * Note that Qt::NoBrush will yield a black background, while * Qt::SolidPattern will yield a background using the background color (such * as white). * * Also note that we want the note/trigger brush to be a linear gradient by * default, but we do not create a gradient to be used by the constructor. * Instead, we create the gradient in the qloopbutton, qseqroll, qperfnames, * and qperfroll class via a "use gradient" flag. We pass Qt::SolidPattern * below to avoid a Qt warning. * */ gui_palette_qt5::gui_palette_qt5 (const std::string & filename) : basesettings (filename), m_palette (), m_pen_palette (), m_nrm_palette (), m_inv_palette (), m_statics_are_loaded (false), m_is_inverse (false), m_dark_theme (false), m_dark_ui (false), m_empty_brush (new (std::nothrow) Brush(Qt::SolidPattern)), m_empty_brush_style (Qt::SolidPattern), m_note_brush (new (std::nothrow) Brush(Qt::SolidPattern)), m_note_brush_style (Qt::LinearGradientPattern), m_scale_brush (new (std::nothrow) Brush(Qt::Dense3Pattern)), m_scale_brush_style (Qt::Dense3Pattern), m_backseq_brush ( new (std::nothrow) Brush(Qt::Dense2Pattern) ), m_backseq_brush_style (Qt::Dense2Pattern), m_chord_brush ( new (std::nothrow) Brush(Qt::BDiagPattern) ), m_chord_brush_style (Qt::BDiagPattern), m_use_gradient_brush (true), m_measure_pen_style (get_pen(penstyle::solid)), m_beat_pen_style (get_pen(penstyle::solid)), m_fourth_pen_style (get_pen(penstyle::dot)), m_step_pen_style (get_pen(penstyle::dot)) { load_static_colors(usr().inverse_colors()); /* this must come first */ reset(); } /** * Provides a destructor to delete allocated objects. */ gui_palette_qt5::~gui_palette_qt5 () { // Anything to do? } void gui_palette_qt5::load_static_colors (bool inverse) { m_is_inverse = inverse; if (! m_statics_are_loaded) { m_statics_are_loaded = true; m_theme = Color(0xAD, 0xBE, 0xEF, 0xDE); /* #DEADBEEF */ m_black = Color("black"); m_red = Color("red"); m_green = Color("green"); m_yellow = Color("yellow"); m_blue = Color("blue"); m_magenta = Color("magenta"); m_cyan = Color("cyan"); m_white = Color("white"); m_dk_black = Color("dark slate grey"); m_dk_red = Color("dark red"); m_dk_green = Color("dark green"); m_dk_yellow = Color("dark yellow"); m_dk_blue = Color("dark blue"); m_dk_magenta = Color("dark magenta"); m_dk_cyan = Color("dark cyan"); m_dk_white = Color("grey"); m_orange = Color("orange"); m_pink = Color("pink"); m_color_18 = Color("pale green"); m_color_19 = Color("khaki"); m_color_20 = Color("light blue"); m_color_21 = Color("violet"); m_color_22 = Color("turquoise"); m_grey = Color(128, 128, 128); /* "grey" */ m_dk_orange = Color("dark orange"); m_dk_pink = Color("deep pink"); m_color_26 = Color("sea green"); m_color_27 = Color("dark khaki"); m_color_28 = Color("dark slate blue"); m_color_29 = Color("dark violet"); m_lt_grey = Color(192, 192, 192); /* "light slate grey" */ m_dk_grey = Color( 72, 72, 72); /* "dark slate grey" */ } } /** * Static function to invert a given Color. */ Color gui_palette_qt5::calculate_inverse (const Color & c) { int r, g, b, a; c.getRgb(&r, &g, &b, &a); r = a - r; g = a - g; b = a - b; return Color(r, g, b, a); } /** * Returns true if the corresponding theme color is to be used. * See the usage in qslotbutton. */ bool gui_palette_qt5::is_theme_color (const Color & c) const { return c == m_theme; } /** * Clears the background and foreground color containers, and adds the "None" * color to make sure it is always present. */ void gui_palette_qt5::clear () { m_palette.clear(); m_palette.add(PaletteColor::none, m_white, "None"); m_pen_palette.clear(); m_pen_palette.add(PaletteColor::none, m_black, "Black"); } void gui_palette_qt5::clear_invertible () { m_nrm_palette.clear(); m_inv_palette.clear(); } /** * Adds all of the main palette colors in the PaletteColor enumeration into * the palette contain. The palette is meant to be used to color sequences * differently, though this feature is not yet supported in the Gtkmm-2.4 * version of Seq66. * * Weird, this shows up as black! Same in Gtkmm! * * m_palette.add(PaletteColor::DK_YELLOW, m_dk_yellow, "Dk Yellow"); * * Slot background colors. */ void gui_palette_qt5::reset_backgrounds () { m_palette.clear(); m_palette.add(PaletteColor::none, m_white, "None"); m_palette.add(PaletteColor::black, m_black, "Black"); m_palette.add(PaletteColor::red, m_red, "Red"); m_palette.add(PaletteColor::green, m_green, "Green"); m_palette.add(PaletteColor::yellow, m_yellow, "Yellow"); m_palette.add(PaletteColor::blue, m_blue, "Blue"); m_palette.add(PaletteColor::magenta, m_magenta, "Magenta"); m_palette.add(PaletteColor::cyan, m_cyan, "Cyan"); m_palette.add(PaletteColor::white, m_white, "White"); m_palette.add(PaletteColor::dk_black, m_dk_black, "Dark Black"); m_palette.add(PaletteColor::dk_red, m_dk_red, "Dark Red"); m_palette.add(PaletteColor::dk_green, m_dk_green, "Dark Green"); m_palette.add(PaletteColor::dk_yellow, m_yellow, "Dark Yellow"); m_palette.add(PaletteColor::dk_blue, m_dk_blue, "Dark Blue"); m_palette.add(PaletteColor::dk_magenta, m_dk_magenta, "Dark Magenta"); m_palette.add(PaletteColor::dk_cyan, m_dk_cyan, "Dark Cyan"); m_palette.add(PaletteColor::dk_white, m_dk_white, "Dark White"); m_palette.add(PaletteColor::orange, m_orange, "Orange"); m_palette.add(PaletteColor::pink, m_pink, "Pink"); m_palette.add(PaletteColor::color_18, m_color_18, "Pale Green"); m_palette.add(PaletteColor::color_19, m_color_19, "Khaki"); m_palette.add(PaletteColor::color_20, m_color_20, "Light Blue"); m_palette.add(PaletteColor::color_21, m_color_21, "Light Magenta"); m_palette.add(PaletteColor::color_22, m_color_22, "Turquoise"); m_palette.add(PaletteColor::grey, m_grey, "Grey"); m_palette.add(PaletteColor::dk_orange, m_dk_orange, "Dk Orange"); m_palette.add(PaletteColor::dk_pink, m_dk_pink, "Dark Pink"); m_palette.add(PaletteColor::color_26, m_color_26, "Sea Green"); m_palette.add(PaletteColor::color_27, m_color_27, "Dark Khaki"); m_palette.add(PaletteColor::color_28, m_color_28, "Dark Slate Blue"); m_palette.add(PaletteColor::color_29, m_color_29, "Dark Violet"); m_palette.add(PaletteColor::color_30, m_lt_grey, "Light Grey"); m_palette.add(PaletteColor::dk_grey, m_dk_grey, "Dark Grey"); } /** * Sets pen colors, */ void gui_palette_qt5::reset_pens () { m_pen_palette.clear(); m_pen_palette.add(PaletteColor::none, m_black, "Black"); m_pen_palette.add(PaletteColor::black, m_white, "White"); m_pen_palette.add(PaletteColor::red, m_white, "White"); m_pen_palette.add(PaletteColor::green, m_white, "White"); m_pen_palette.add(PaletteColor::yellow, m_black, "Black"); m_pen_palette.add(PaletteColor::blue, m_white, "White"); m_pen_palette.add(PaletteColor::magenta, m_white, "White"); m_pen_palette.add(PaletteColor::cyan, m_black, "Black"); m_pen_palette.add(PaletteColor::white, m_black, "Black"); m_pen_palette.add(PaletteColor::dk_black, m_white, "White"); m_pen_palette.add(PaletteColor::dk_red, m_white, "White"); m_pen_palette.add(PaletteColor::dk_green, m_white, "White"); m_pen_palette.add(PaletteColor::dk_yellow, m_black, "Black"); m_pen_palette.add(PaletteColor::dk_blue, m_white, "White"); m_pen_palette.add(PaletteColor::dk_magenta, m_white, "White"); m_pen_palette.add(PaletteColor::dk_cyan, m_white, "White"); m_pen_palette.add(PaletteColor::dk_white, m_white, "White"); m_pen_palette.add(PaletteColor::orange, m_white, "White"); m_pen_palette.add(PaletteColor::pink, m_black, "Black"); m_pen_palette.add(PaletteColor::color_18, m_black, "Black"); m_pen_palette.add(PaletteColor::color_19, m_black, "Black"); m_pen_palette.add(PaletteColor::color_20, m_black, "Black"); m_pen_palette.add(PaletteColor::color_21, m_black, "Black"); m_pen_palette.add(PaletteColor::color_22, m_black, "Black"); m_pen_palette.add(PaletteColor::grey, m_black, "Black"); m_pen_palette.add(PaletteColor::dk_orange, m_black, "Black"); m_pen_palette.add(PaletteColor::dk_pink, m_black, "Black"); m_pen_palette.add(PaletteColor::color_26, m_black, "Black"); m_pen_palette.add(PaletteColor::color_27, m_black, "Black"); m_pen_palette.add(PaletteColor::color_28, m_black, "Black"); m_pen_palette.add(PaletteColor::color_29, m_black, "Black"); m_pen_palette.add(PaletteColor::color_30, m_black, "Black"); m_pen_palette.add(PaletteColor::dk_grey, m_black, "Black"); } /** * The size of these two ui-palettes is now 32. */ void gui_palette_qt5::reset_invertibles () { m_nrm_palette.clear(); m_nrm_palette.add(InvertibleColor::black, m_black, "Foreground"); m_nrm_palette.add(InvertibleColor::white, m_white, "Background"); m_nrm_palette.add(InvertibleColor::label, m_theme, "Label"); m_nrm_palette.add(InvertibleColor::selection, m_orange, "Selection"); m_nrm_palette.add(InvertibleColor::drum, m_red, "Drum"); m_nrm_palette.add(InvertibleColor::tempo, m_yellow, "Tempo"); m_nrm_palette.add(InvertibleColor::note_in, m_white, "Note Fill"); m_nrm_palette.add(InvertibleColor::note_out, m_black, "Note Border"); m_nrm_palette.add(InvertibleColor::black_key, m_black, "Black Keys"); m_nrm_palette.add(InvertibleColor::white_key, m_white, "White Keys"); m_nrm_palette.add(InvertibleColor::progress, m_red, "Progress Bar"); m_nrm_palette.add(InvertibleColor::backseq, m_dk_cyan, "Back Pattern"); m_nrm_palette.add(InvertibleColor::grey, m_grey, "Medium Line"); m_nrm_palette.add(InvertibleColor::dk_grey, m_dk_grey, "Heavy Line"); m_nrm_palette.add(InvertibleColor::lt_grey, m_lt_grey, "Light Line"); m_nrm_palette.add(InvertibleColor::beat, m_black, "Beat"); m_nrm_palette.add(InvertibleColor::near, m_yellow, "Near"); m_nrm_palette.add(InvertibleColor::backtime, m_grey, "Time Brush"); m_nrm_palette.add(InvertibleColor::backdata, m_grey, "Data Brush"); m_nrm_palette.add(InvertibleColor::backevent, m_grey, "Event Brush"); m_nrm_palette.add(InvertibleColor::backkeys, m_grey, "Keys Brush"); m_nrm_palette.add(InvertibleColor::backnames, m_grey, "Names Brush"); m_nrm_palette.add(InvertibleColor::octave, m_grey, "Octave Line"); m_nrm_palette.add(InvertibleColor::text, m_black, "Text"); m_nrm_palette.add(InvertibleColor::texttime, m_black, "Time Text"); m_nrm_palette.add(InvertibleColor::textdata, m_black, "Data Text"); m_nrm_palette.add(InvertibleColor::noteevent, m_white, "Note Event"); m_nrm_palette.add(InvertibleColor::textkeys, m_black, "Keys Text"); m_nrm_palette.add(InvertibleColor::textnames, m_black, "Names Text"); m_nrm_palette.add(InvertibleColor::textslots, m_black, "Slots Text"); m_nrm_palette.add(InvertibleColor::scale, m_lt_grey, "Scale Brush"); m_nrm_palette.add(InvertibleColor::extra, m_black, "Extra"); m_inv_palette.clear(); m_inv_palette.add(InvertibleColor::black, m_white, "Foreground"); m_inv_palette.add(InvertibleColor::white, m_black, "Background"); m_inv_palette.add(InvertibleColor::label, m_white, "Label"); m_inv_palette.add(InvertibleColor::selection, m_orange, "Selection"); m_inv_palette.add(InvertibleColor::drum, m_green, "Drum"); m_inv_palette.add(InvertibleColor::tempo, m_magenta, "Tempo"); m_inv_palette.add(InvertibleColor::note_in, m_black, "Note Fill"); m_inv_palette.add(InvertibleColor::note_out, m_white, "Note Border"); m_inv_palette.add(InvertibleColor::black_key, m_white, "Black Keys"); m_inv_palette.add(InvertibleColor::white_key, m_black, "White Keys"); m_inv_palette.add(InvertibleColor::progress, m_red, "Progress Bar"); m_inv_palette.add(InvertibleColor::backseq, m_dk_cyan, "Back Pattern"); m_inv_palette.add(InvertibleColor::grey, m_grey, "Medium Line"); m_inv_palette.add(InvertibleColor::dk_grey, m_white, "Heavy Line"); m_inv_palette.add(InvertibleColor::lt_grey, m_lt_grey, "Light Line"); m_inv_palette.add(InvertibleColor::beat, m_white, "Beat"); m_inv_palette.add(InvertibleColor::near, m_yellow, "Near"); m_inv_palette.add(InvertibleColor::backtime, m_grey, "Time Brush"); m_inv_palette.add(InvertibleColor::backdata, m_grey, "Data Brush"); m_inv_palette.add(InvertibleColor::backevent, m_grey, "Event Brush"); m_inv_palette.add(InvertibleColor::backkeys, m_grey, "Keys Brush"); m_inv_palette.add(InvertibleColor::backnames, m_grey, "Names Brush"); m_inv_palette.add(InvertibleColor::octave, m_white, "Octave Line"); m_inv_palette.add(InvertibleColor::text, m_white, "Text"); m_inv_palette.add(InvertibleColor::texttime, m_white, "Time Text"); m_inv_palette.add(InvertibleColor::textdata, m_white, "Data Text"); m_inv_palette.add(InvertibleColor::noteevent, m_black, "Note Event"); m_inv_palette.add(InvertibleColor::textkeys, m_white, "Keys Text"); m_inv_palette.add(InvertibleColor::textnames, m_white, "Names Text"); m_inv_palette.add(InvertibleColor::textslots, m_white, "Slots Text"); m_inv_palette.add(InvertibleColor::scale, m_lt_grey, "Scale Brush"); m_inv_palette.add(InvertibleColor::extra, m_white, "Extra"); m_empty_brush->setColor(get_color(InvertibleColor::white)); m_empty_brush->setStyle(m_empty_brush_style); m_note_brush->setColor(get_color(InvertibleColor::white)); if (! m_use_gradient_brush) /* avoid Qt warning */ m_note_brush->setStyle(m_note_brush_style); m_scale_brush->setColor(get_color(InvertibleColor::scale)); m_scale_brush->setStyle(m_scale_brush_style); m_backseq_brush->setColor(get_color(InvertibleColor::backseq)); m_backseq_brush->setStyle(m_backseq_brush_style); m_chord_brush->setColor(get_color(InvertibleColor::backseq)); m_chord_brush->setStyle(m_chord_brush_style); } /** * Gets a color, but returns a modified value via the function * Qt::Color::setHsv(h, s, v). This function sets the color, by specifying * hue, saturation, and value (brightness). * * \param index * The index into the Seq66 stock palette. * * \param h * Hue factor, has no default at this time. Kepler34 treats this value * as a 1.0 factor for the hue. * * \param s * Saturation, in the range 0..1, defaults to 0.65. * * \param v * Value (a.k.a. brightness), defaults to 1, in the range 0..1. */ Color gui_palette_qt5::get_color_ex ( PaletteColor index, double h, double s, double v ) const { Color c = m_palette.get_color(index); c.setHsv(c.hue() * h, c.saturation() * s, c.value() * v); return c; } /** * Gets a color and fixes its HSV in a stock manner, unless the index does * not specify a color. * * \param index * The index into the Seq66 stock palette. * * \return * Returns a copy of the fixed color. If it is not a color, then * "white" is returned. */ Color gui_palette_qt5::get_color_fix (PaletteColor index) const { if (m_palette.no_color(index)) { return m_palette.get_color(PaletteColor::none); } else { Color c = m_palette.get_color(index); if (c.value() != 255) c.setHsv(c.hue(), c.saturation() * 0.65, c.value()); return c; } } /** * Converts a color to its inverse. */ Color gui_palette_qt5::invert (Color c, bool usealpha) const { int r, g, b, a; c.getRgb(&r, &g, &b, &a); if (! usealpha) a = 255; r = a - r; g = a - g; b = a - b; return Color(r, g, b, a); } /** * Gets a color from the palette, based on the index value, and returns the * inverted version. * * This is not always the best way to get good contrast, though. * * \param index * Indicates which color to get. This index is checked for range, and, if * out of range, the default color object, indexed by PaletteColor::none, * is returned. * * \param index * The index into the Seq66 stock palette. * * \return * Returns a copy of the inverted color. */ Color gui_palette_qt5::get_color_inverse (PaletteColor index) const { if (index != PaletteColor::none) { Color c = m_palette.get_color(index); return invert(c); } else return m_black; } std::string gui_palette_qt5::make_color_stanza (int number, bool inverse) const { std::string result; if (number >= 0) { Color backc; Color textc; std::string bname = "\""; std::string tname = "\""; if (inverse) { InvertibleColor index = static_cast(number); backc = m_nrm_palette.get_color(index); textc = m_inv_palette.get_color(index); bname += get_color_name(index); tname += get_inv_color_name(index); } else { PaletteColor index = static_cast(number); backc = m_palette.get_color(index); textc = m_pen_palette.get_color(index); bname += get_color_name(index); tname += get_pen_color_name(index); } bname += "\""; tname += "\""; int br, bg, bb, ba; int tr, tg, tb, ta; char temp[128]; backc.getRgb(&br, &bg, &bb, &ba); textc.getRgb(&tr, &tg, &tb, &ta); snprintf ( temp, sizeof temp, "%2d %18s [ 0x%02X%02X%02X%02X ] %18s [ 0x%02X%02X%02X%02X ]", number, bname.c_str(), ba, br, bg, bb, tname.c_str(), ta, tr, tb, tg ); result = temp; } return result; } /** * This function processes .palette file lines that lines match the * string generated by gui_palette_qt5:: make_color_stanza(): * \verbatim [palette] 0 "Black" [ 0xFF000000 ] "White" [ 0xFFFFFFFF ] [ui-palette] 0 "Foreground" [ 0xFF00FF00 ] "Foreground" [ 0xFFFFFFFF ] \endverbatim * * The "[palette]" section specifies the colors used to paint the * colored progress box of the grid-slots. * * The first column is the color's palette number, ranging from 0 to 31. * The second column is the name of the background color. * The third columns is a bracketed hexadecimal number that holds the * ARGB (Alpha+RGB) values for the background color of the progress box. * The fourth column is the foreground color name, and the fifth column * is the ARGB for the foreground color for the notes in the progress box. * * The "[ui-palette]" section specifies the normal and inverse colors * used for drawing grids, MIDI events, and the virtual piano keys. * These colors are independent of the selected Qt theme or style sheet, * and generally need adjustment to enhance visibility. The color names * are element names. * * Note that the alpha values are not currently used and are all * set to FF. * * \param stanza * Provides a palette stanza as desribed above. * * \param invertible * Indicates which palettes are to hold this entry. If false, the * m_palette and m_pen_palette (the background and foreground of the * progress box) are used. If true, the the m_nrm_palette and * m_inv_palette are used. * * \return * If all items are available and used, then true is returned. * Otherwise false is returned. */ bool gui_palette_qt5::add_color_stanza ( const std::string & stanza, bool invertible ) { bool result = ! stanza.empty(); if (result) { std::string backname; unsigned backargb; std::string textname; unsigned textargb; int number = string_to_int(stanza); /* gets first column value */ std::string argb; std::string::size_type lpos = 0; backname = next_quoted_string(stanza); result = ! backname.empty(); if (result) { lpos = stanza.find_first_of("["); argb = next_bracketed_string(stanza, lpos - 1); result = ! argb.empty(); if (result) backargb = string_to_unsigned(argb); } if (result) { textname = next_quoted_string(stanza, lpos); result = ! textname.empty(); } if (result) { lpos = stanza.find_first_of("[", lpos + 1); argb = next_bracketed_string(stanza, lpos - 1); result = ! argb.empty(); if (result) textargb = string_to_unsigned(argb); } if (result) { Color background(backargb); Color foreground(textargb); if (invertible) { result = add_invertible ( number, background, backname, foreground, textname ); } else { result = add ( number, background, backname, foreground, textname ); } } } return result; } /** * This function adds one pair of colors to m_palette (the palette of * selectable colors that can be assigned to a sequence's grid-slot) and * to m_pen_palette (the colors used to draw notes in the grid-slot's * progress box). * * - m_palette is a background color * - m_pen_palette is a foreground color. */ bool gui_palette_qt5::add ( int number, const Color & bg, const std::string & bgname, const Color & fg, const std::string & fgname ) { bool result = number >= 0 && number < palette_size(); if (result) { PaletteColor index = static_cast(number); result = m_palette.add(index, bg, bgname); if (result) result = m_pen_palette.add(index, fg, fgname); } return result; } /** * This function adds colors to m_nrm_palette and its inverse * palette m_inv_palette. These colors are used to draw the foreground * and background of grids, the painted notes and other evernts, * and the "virtual piano" keys. */ bool gui_palette_qt5::add_invertible ( int number, const Color & bg, const std::string & bgname, const Color & fg, const std::string & fgname ) { bool result = number >= 0; if (result) { InvertibleColor index = static_cast(number); result = m_nrm_palette.add(index, bg, bgname); if (result) result = m_inv_palette.add(index, fg, fgname); } return result; } const Color & gui_palette_qt5::get_color (InvertibleColor index) const { const Color & result = is_inverse() ? m_inv_palette.get_color(index) : m_nrm_palette.get_color(index) ; return result; } #if defined SEQ66_PROVIDE_AUTO_COLOR_INVERSION /** * This function is provided more for experiments in color. It assumes * normal startup and color initialization (including reading the * palette file) has been done. * palette m_palette; palette m_pen_palette; palette m_nrm_palette; palette m_inv_palette; * */ void gui_palette_qt5::fill_inverse_colors () { const palette::container & source1 = m_palette.entries(); palette::container & dest1 = m_pen_palette.entries(); for (int index1 = 0; index1 < int(source1.size()); ++index1) { auto inpair = source1.find(index1); auto outpair = dest1.find(index1); if (inpair != source1.end() && outpair != dest1.end()) { Color inverse = invert(inpair->second.ppt_color); outpair->second.ppt_color = inverse; } else break; } const palette::container & source2 = m_nrm_palette.entries(); palette::container & dest2 = m_inv_palette.entries(); for (int index2 = 0; index2 < int(source2.size()); ++index2) { auto inpair = source2.find(index2); auto outpair = dest2.find(index2); if (inpair != source2.end() && outpair != dest2.end()) { Color inverse = invert(inpair->second.ppt_color); outpair->second.ppt_color = inverse; } else break; } } #endif // defined SEQ66_PROVIDE_AUTO_COLOR_INVERSION /** * Provides the names of the Qt::BrushStyle enumeration, with the word * "Pattern" dropped off (NoBrush left as is). */ const std::string & gui_palette_qt5::brush_name (int index) const { static const std::string s_empty; static const int s_maximum = static_cast(Qt::ConicalGradientPattern) + 1; static const std::string s_brush_names [] { "nobrush", "solid", "dense1", "dense2", "dense3", "dense4", "dense5", "dense6", "dense7", "horizontal", "vertical", "cross", "bdiag", "fdiag", "diagcross", "lineargradient", "radialgradient", "conicalgradient" }; return index >= 0 && index < s_maximum ? s_brush_names[index] : s_empty ; } BrushStyle gui_palette_qt5::get_brush_style (const std::string & name) const { if (name.empty()) { return Qt::NoBrush; } else { BrushStyle result = Qt::TexturePattern; /* illegal here */ int maximum = static_cast(Qt::ConicalGradientPattern) + 1; for (int counter = 0; counter < maximum; ++counter) { if (name == brush_name(counter)) { result = static_cast(counter); break; } } return result; } } std::string gui_palette_qt5::get_brush_name (BrushStyle b) const { int index = static_cast(b); return brush_name(index); } /** * QBrush constructors: * * QBrush(const QGradient & gradient) * QBrush(Qt::BrushStyle style) * * QBrush(const QBrush & other) * QBrush(const QImage & image) * QBrush(const QPixmap & pixmap) * QBrush(Qt::GlobalColor color, const QPixmap & pixmap) * QBrush(const QColor &color, const QPixmap & pixmap) * QBrush(Qt::GlobalColor color, Qt::BrushStyle style = Qt::SolidPattern) * QBrush(const QColor & color, Qt::BrushStyle style = Qt::SolidPattern) * QBrush() * * Note that we will actually support only the linear gradient for use in most * applications, but we leave the other gradients in place for now. */ bool gui_palette_qt5::make_brush ( BrushPtr & brush, BrushStyle & brushstyle, BrushStyle temp ) { bool result = false; switch (temp) { case Qt::NoBrush: case Qt::SolidPattern: case Qt::Dense1Pattern: case Qt::Dense2Pattern: case Qt::Dense3Pattern: case Qt::Dense4Pattern: case Qt::Dense5Pattern: case Qt::Dense6Pattern: case Qt::Dense7Pattern: case Qt::HorPattern: case Qt::VerPattern: case Qt::CrossPattern: case Qt::BDiagPattern: case Qt::FDiagPattern: case Qt::DiagCrossPattern: brush.reset(new (std::nothrow) Brush(temp)); if (brush) { result = true; brush->setStyle(temp); brushstyle = temp; } break; case Qt::LinearGradientPattern: case Qt::RadialGradientPattern: case Qt::ConicalGradientPattern: { QLinearGradient qlg; /* (0, 0) to (1, 1) */ brush.reset(new (std::nothrow) Brush(qlg)); if (brush) { result = true; brush->setStyle(temp); brushstyle = temp; } } break; case Qt::TexturePattern: break; } return result; } bool gui_palette_qt5::set_brushes ( const std::string & emptybrush, const std::string & notebrush, const std::string & scalebrush, const std::string & backseqbrush, const std::string & chordbrush ) { BrushStyle temp = get_brush_style(emptybrush); bool result = temp != Qt::TexturePattern; /* used as illegal value */ if (result) { /* * Empty brush */ (void) make_brush(m_empty_brush, m_empty_brush_style, temp); temp = get_brush_style(notebrush); result = temp != Qt::TexturePattern; if (result) { /* * Note brush */ m_use_gradient_brush = temp == Qt::LinearGradientPattern; (void) make_brush(m_note_brush, m_note_brush_style, temp); temp = get_brush_style(notebrush); result = temp != Qt::TexturePattern; } if (result) { /* * Scale brush */ (void) make_brush(m_scale_brush, m_scale_brush_style, temp); temp = get_brush_style(scalebrush); result = temp != Qt::TexturePattern; } if (result) { /* * Background sequence brush */ (void) make_brush(m_backseq_brush, m_backseq_brush_style, temp); temp = get_brush_style(backseqbrush); result = temp != Qt::TexturePattern; } if (result) { /* * Chord brush */ (void) make_brush(m_chord_brush, m_chord_brush_style, temp); temp = get_brush_style(chordbrush); result = temp != Qt::TexturePattern; } } return result; } bool gui_palette_qt5::get_brush_names ( std::string & emptybrush, std::string & notebrush, std::string & scalebrush, std::string & backseqbrush, std::string & chordbrush ) { bool result = true; std::string temp = get_brush_name(m_empty_brush_style); emptybrush = temp; if (temp.empty()) result = false; temp = get_brush_name(m_note_brush_style); notebrush = temp; if (temp.empty()) result = false; temp = get_brush_name(m_scale_brush_style); scalebrush = temp; if (temp.empty()) result = false; temp = get_brush_name(m_backseq_brush_style); backseqbrush = temp; if (temp.empty()) result = false; temp = get_brush_name(m_chord_brush_style); chordbrush = temp; if (temp.empty()) result = false; return result; } Brush & gui_palette_qt5::get_brush (brush index) { static Brush s_dummy; switch (index) { case brush::empty: return *m_empty_brush; break; case brush::note: return *m_note_brush; break; case brush::scale: return *m_scale_brush; break; case brush::backseq: return *m_backseq_brush; break; case brush::chord: return *m_chord_brush; break; default: return s_dummy; break; } return s_dummy; } /** * Provides the names of the Qt lines/pen enumeration, with the word * "Pen" dropped off (NoPen left as is). */ const std::string & gui_palette_qt5::pen_name (int index) const { static const std::string s_empty; static const int s_maximum = static_cast(Qt::CustomDashLine) + 1; static const std::string s_pen_names [] { "nopen", "solid", "dash", "dot", "dashdot", "dashdotdot", "customdash" }; return index >= 0 && index < s_maximum ? s_pen_names[index] : s_empty ; } bool gui_palette_qt5::get_pen_names ( std::string & measurepen, std::string & beatpen, std::string & fourpen, std::string & steppen ) { penstyle p = get_pen_index(m_measure_pen_style); measurepen = get_penstyle_name(p); p = get_pen_index(m_beat_pen_style); beatpen = get_penstyle_name(p); p = get_pen_index(m_fourth_pen_style); fourpen = get_penstyle_name(p); p = get_pen_index(m_step_pen_style); steppen = get_penstyle_name(p); return true; /* for now */ } PenStyle gui_palette_qt5::pen_style (pen penindex) { static PenStyle s_dummy { Qt::NoPen }; switch (penindex) { case pen::measure: return m_measure_pen_style; break; case pen::beat: return m_beat_pen_style; break; case pen::fourth: return m_fourth_pen_style; break; case pen::step: return m_step_pen_style; break; } return s_dummy; } PenStyle gui_palette_qt5::get_pen (penstyle index) { static PenStyle s_dummy { Qt::NoPen }; switch (index) { case penstyle::empty: return Qt::NoPen; break; case penstyle::solid: return Qt::SolidLine; break; case penstyle::dash: return Qt::DashLine; break; case penstyle::dot: return Qt::DotLine; break; case penstyle::dashdot: return Qt::DashDotLine; break; case penstyle::dashdotdot: return Qt::DashDotDotLine; break; case penstyle::customdash: return Qt::DashLine; break; } return s_dummy; } gui_palette_qt5::penstyle gui_palette_qt5::get_pen_index (PenStyle ps) { penstyle result = penstyle::empty; switch (ps) { case Qt::NoPen: result = penstyle::empty; break; case Qt::SolidLine: result = penstyle::solid; break; case Qt::DashLine: result = penstyle::dash; break; case Qt::DotLine: result = penstyle::dot; break; case Qt::DashDotLine: result = penstyle::dashdot; break; case Qt::DashDotDotLine: result = penstyle::dashdotdot; break; case Qt::CustomDashLine: result = penstyle::customdash; break; case Qt::MPenStyle: result = penstyle::empty; break; } return result; } /** * We could convert the penstyle index to an integer and look it up. */ PenStyle gui_palette_qt5::get_pen_style (const std::string & penname) { PenStyle result = Qt::NoPen; int maximum = static_cast(Qt::CustomDashLine) + 1; for (int counter = 0; counter < maximum; ++counter) { if (penname == pen_name(counter)) { result = get_pen(static_cast(counter)); break; } } return result; } /** * Converts a PenStyle to a penstyle name. We could use the method done * by get_brush_name(). Or we could convert the pen index to an * integer. */ std::string gui_palette_qt5::get_penstyle_name (penstyle p) { int index = static_cast(p); return pen_name(index); } bool gui_palette_qt5::set_pens ( const std::string & measurepen, const std::string & beatpen, const std::string & fourpen, const std::string & steppen ) { PenStyle ps = get_pen_style(measurepen); m_measure_pen_style = ps; ps = get_pen_style(beatpen); m_beat_pen_style = ps; ps = get_pen_style(fourpen); m_fourth_pen_style = ps; ps = get_pen_style(steppen); m_step_pen_style = ps; return true; /* for now */ } } // namespace seq66 /* * gui_palette_qt5.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/palettefile.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file palettefile.cpp * * This module declares/defines the base class for managing the reading and * writing of the 'palette' file. * * \library seq66 application * \author Seq24 team; modifications by Chris Ahlstrom * \date 2020-12-21 * \updates 2025-10-13 * \license GNU GPLv2 or above * */ #include /* std::setw() */ #include /* std::cout */ #include "cfg/settings.hpp" /* seq66::rc(), seq66::usr() */ #include "gui_palette_qt5.hpp" /* seq66::gui_palette_qt5 */ #include "palettefile.hpp" /* seq66::palettefile class */ namespace seq66 { static const int s_usr_file_version = 1; /** * Principal constructor. * * Versions: * * 0: The initial version, plus some additions not kept track of. * 1: Added a [pens] section to allow altering the vertical grid * lines. Also affected by [user-interface-settings] * gridlines-thick = true. * * \param mapper * Provides the palette reference to be acted upon. * * \param filename * Provides the name of the palette file; this is usually a full path * file-specification to the "mutes" file using this object. * * \param rcs * The configfile currently requires an rcsetting object, but it is not * yet used here. */ palettefile::palettefile ( gui_palette_qt5 & mapper, const std::string & filename, rcsettings & rcs ) : configfile (filename, rcs, ".palette"), m_palettes (mapper) { version(s_usr_file_version); } /** * Parse the ~/.config/seq66/qseq66.palette file-stream. * * [comments] Header commentary is skipped during parsing. However, we now * try to read an optional comment block. This block is part of the palette * container, not part of the rcsettings object. * * Also, to account for expansion of the ui-palette (invertible palette). */ bool palettefile::parse_stream (std::ifstream & file) { bool result = true; file.seekg(0, std::ios::beg); /* seek to start */ (void) parse_version(file); std::string s = parse_comments(file); if (! s.empty()) palettes().comments_block().set(s); const std::string tag = "[hints]"; bool ok = line_after(file, tag); if (ok) { bool flag = get_boolean(file, tag, "dark-theme"); palettes().dark_theme(flag); if (flag) usr().dark_theme(true); flag = get_boolean(file, tag, "dark-ui"); palettes().dark_ui(flag); } ok = line_after(file, "[palette]"); if (ok) { int count = 0; /* limited to 32 palette entries */ m_palettes.clear(); for (;;) { if (count > gui_palette_qt5::palette_size()) { ok = false; break; } else { ok = m_palettes.add_color_stanza(line()); if (ok) ++count; else break; } if (! next_data_line(file)) break; } if (ok) ok = count == gui_palette_qt5::palette_size(); } if (ok) { if (line_after(file, "[ui-palette]")) { int count = 0; /* limited to 32 palette entries */ m_palettes.clear_invertible(); for (;;) { if (count > gui_palette_qt5::invertible_size()) { ok = false; break; } else { ok = m_palettes.add_color_stanza(line(), true); if (ok) ++count; else break; } if (! next_data_line(file)) break; } if (ok) ok = count == gui_palette_qt5::invertible_size(); } } if (ok) { const std::string tag = "[brushes]"; std::string sempty = get_variable(file, tag, "empty"); std::string snote = get_variable(file, tag, "note"); std::string sscale = get_variable(file, tag, "scale"); std::string sbackseq = get_variable(file, tag, "backseq"); std::string schord = get_variable(file, tag, "chord"); ok = m_palettes.set_brushes(sempty, snote, sscale, sbackseq, schord); } if (ok) { const std::string tag = "[pens]"; std::string bar = get_variable(file, tag, "measure"); std::string beat = get_variable(file, tag, "beat"); std::string fourthbeat = get_variable(file, tag, "fourth"); std::string step = get_variable(file, tag, "step"); ok = m_palettes.set_pens(bar, beat, fourthbeat, step); } if (! ok) { warn_message("Palettes reset to defaults"); m_palettes.reset(); } return result; } /** * Get the number of sequence definitions provided in the [mute-group] * section. See the rcfile class for full information. * * \param p * Provides the performance object to which all of these options apply. * * \return * Returns true if the file was able to be opened for reading. * Currently, there is no indication if the parsing actually succeeded. */ bool palettefile::parse () { std::ifstream file(name(), std::ios::in | std::ios::ate); bool result = ! name().empty() && file.is_open(); if (result) { file_message("Read palette", name()); result = parse_stream(file); } else { std::string msg = "Read open fail"; file_error(msg, name()); msg += ": "; msg += name(); append_error_message(msg); result = false; } return result; } /** * Writes the [palettes] section to the given file stream. * * \param file * Provides the output file stream to write to. * * \return * Returns true if the write operations all succeeded. */ bool palettefile::write_stream (std::ofstream & file) { file << "# Seq66 0.99.17 (and above) palette configuration file\n" << "#\n" << "# " << name() << "\n" << "# Written on " << get_current_date_time() << "\n" << "#\n" << "# This file can be used to change the colors used by patterns\n" << "# and in some parts of the user-interface. It must be active and\n" << "# specified in the 'rc' file.\n" << "\n" << "# Note: colors of the time display are currently set in the\n" << "# 'usr' file.\n" << "\n" ; /* * [comments] */ file << "[Seq66]\n\n" "config-type = \"palette\"\n" "version = " << version() << "\n\n" "# The [comments] section can document this file. Lines starting with\n" "# '#', '[', or that have no characters end the comment.\n\n" "[comments]\n\n" << palettes().comments_block().text() << "\n" << "# Set 'dark-theme' to true if the matching style-sheet is dark. This\n" "# also set the 'dark-theme' setting in the 'usr' file.\n" "# Set 'dark-ui' to true if the palette background elements are dark.\n" "\n" "[hints]\n" "\n" ; write_boolean(file, "dark-theme", palettes().dark_theme()); write_boolean(file, "dark-ui", palettes().dark_ui()); file << "\n" "# [palette] affects the pattern colors selected (by number). First is\n" "# the color number, 0 to 31. Next is the name of the background color.\n" "# The first stanza [square brackets] are the background ARGB values.\n" "# The second provides the foreground color name and ARGB values. The\n" "# alpha values should be set to FF.\n" "\n" "[palette]\n" "\n" ; for (int number = 0; number < gui_palette_qt5::palette_size(); ++number) { std::string stanza = m_palettes.make_color_stanza(number); if (stanza.empty()) break; else file << stanza << "\n"; } file << "\n" "# Similar to the [palette] section, but applies to the custom-drawn\n" "# piano rolls and the --inverse option. The values: color number (0\n" "# to 31); main color feature name; main color value; inverse color\n" "# feature name; and the --inverse color value.\n" "\n" "[ui-palette]\n" "\n" ; for (int number = 0; number < gui_palette_qt5::invertible_size(); ++number) { std::string stanza = m_palettes.make_color_stanza(number, true); if (stanza.empty()) break; else file << stanza << "\n"; } std::string sempty; std::string snote; std::string sscale; std::string sbackseq; std::string schord; file << "\n" "# This section defines brush styles to use. The names are based on the\n" "# names in the Qt::BrushStyle enumeration. The names are:\n" "#\n" "# nobrush, solid, dense1, dense2, dense3, dense4, dense5, dense6,\n" "# dense7, horizontal, vertical, cross, bdiag, fdiag, diagcross,\n" "# lineargradient, radialgradient, and conicalgradient.\n" "#\n" "# However, lineargradient is the only one supported, and, if set and\n" "# using an elliptical progress box, it's gradient is radial.\n" "#\n" "# For 'empty', best to just use 'solid' (try others and see why).\n" "# For 'note', use only 'solid' or the default, 'lineargradient'. These\n" "# also apply to the progress box and triggers.\n" "\n" "[brushes]\n" "\n" ; if (m_palettes.get_brush_names(sempty, snote, sscale, sbackseq, schord)) { file << "empty = " << sempty << "\n" << "note = " << snote << "\n" << "scale = " << sscale << "\n" << "backseq = " << sbackseq << "\n" << "chord = " << schord << "\n" ; } std::string smeasure; std::string sbeat; std::string sfourthbeat; std::string sstep; file << "\n" "# This section defines pen styles to use for vertical time lines.\n" "# The names are based on the names in the Qt::PenStyle enumeration.\n" "# They are:\n" "# nopen, solid, dash, dot, dashdot, dashdotdot, customdash (which\n" "# is currently not supported).\n" "#\n" "# 'measure' and 'beat' are obvious. 'fourth' is a line for the 1/4s of\n" "# a beat, and 'step' is the smallest unit (normally 6 pixels).\n" "\n" "[pens]\n" "\n" ; if (m_palettes.get_pen_names(smeasure, sbeat, sfourthbeat, sstep)) { file << "measure = " << smeasure << "\n" << "beat = " << sbeat << "\n" << "fourth = " << sfourthbeat << "\n" << "step = " << sstep << "\n" ; } write_seq66_footer(file); return true; } /** * This options-writing function is just about as complex as the * options-reading function. * * \return * Returns true if the write operations all succeeded. */ bool palettefile::write () { std::ofstream file(name(), std::ios::out | std::ios::trunc); bool result = ! name().empty() && file.is_open(); if (result) { file_message("Write palette", name()); result = write_stream(file); file.close(); } else file_error("Write open fail", name()); return result; } bool open_palette ( gui_palette_qt5 & pal, const std::string & source ) { bool result = ! source.empty(); if (result) { palettefile palfile(pal, source, rc()); /* add msg? */ result = palfile.parse(); if (result) { // Anything worth doing? } else { std::string msg = "Open failed: "; msg += source; (void) error_message(msg); } } else { file_error("Palette file to open", "none"); } return result; } /** * This function saves the palettes to a file. It is primarily useful * in getting any new additions/upgrades to the palettes. Older palette * files can be updated using side-by-side editing in a text editor. * * Now, consider this sequence: * * -# The programmer adds more palette entries to the InvertibleColors. * This changes the number of entries from N0 to N1 > N0. * -# The application loads a pre-existing palette file, or none. * -# xxx * * \param [inout] pal * Provides the palette object. * * \param destination * Provides the directory to which the play-list file is to be saved. * * \return * Returns true if the operation succeeded. */ bool save_palette ( gui_palette_qt5 & pal, const std::string & destination ) { bool result = ! destination.empty(); if (result) { palettefile palfile(pal, destination, rc()); palfile.name(destination); result = palfile.write(); if (! result) file_error("Write failed", destination); } else file_error("Palette file", "none"); return result; } /** * This function reads the source palette file and then saves it to the new * location. * * \param [inout] pal * Provides the palette object. * * \param source * Provides the input file name from which the palette will be filled. * * \param destination * Provides the directory to which the play-list file is to be saved. * * \return * Returns true if the operation succeeded. */ bool save_palette ( gui_palette_qt5 & pal, const std::string & source, const std::string & destination ) { bool result = ! source.empty(); if (result) { std::string msg = source + " --> " + destination; palettefile palfile(pal, source, rc()); /* * TMI: file_message("Palette save", msg); */ result = palfile.parse(); if (result) result = save_palette(pal, destination); else file_error("Open failed", source); } else file_error("Palette file", "none"); return result; } } // namespace seq66 /* * palettefile.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qbase.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qbase.cpp * * This module declares/defines the base class for managing editing. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-07-22 * \updates 2023-09-09 * \license GNU GPLv2 or above * * We eventually want to migrate all user-interface widgets so that they * ultimately use this base class. Then we aim to greatly reduce the use of * timers to update the widgets. */ #include "cfg/settings.hpp" /* seq66::usr() */ #include "play/performer.hpp" /* seq66::performer class */ #include "qbase.hpp" /* * Do not document the name space. */ namespace seq66 { /** * Sets initial values for common Qt window/frames. * * \param p * Provides the performer object to use for interacting with this * sequence. Among other things, this object provides the active PPQN. */ qbase::qbase (performer & p) : m_performer (p), m_is_dirty (false), m_is_initialized (false) { // No code needed } /** * This destructor no longer does anything. */ qbase::~qbase () { // No code needed } } // namespace seq66 /* * qbase.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qclocklayout.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qclocklayout.cpp * * This class supports a MIDI Clocks label and a set of radio-buttons for * selecting the clock style (off, on POS, on MOD), associating it with a * particular output buss. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-05-19 * \updates 2025-08-18 * \license GNU GPLv2 or above * * This class represents one line in the Edit Preferences MIDI Clocks tab. */ #include #include #include #include #include #include "play/performer.hpp" /* seq66::performer class */ #include "qclocklayout.hpp" /* seq66::qclocklayout class */ #include "qseditoptions.hpp" /* seq66::qseditoptions class */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ namespace seq66 { static const size_t c_max_name_length = 48; /* 40, 32 */ /** * Creates a single line in the MIDI Clocks "Clock" group-box. We will use * the words "clock" or "port" for the MIDI output port represented by this * widget. Here are the jobs we have to do: * * -# Get the label for the port and set it. * -# Add the tooltips for the clock radio-buttons. * -# Add the clock radio-buttons to m_horizlayout_clocklive. * -# Connect to the radio-button slots: * - clock_callback_disable(). * - clock_callback_off(). * - clock_callback_on(). * - clock_callback_mod(). */ qclocklayout::qclocklayout (QWidget * parent, performer & p, int bus) : qportwidget (parent, p, bus), m_horizlayout_clockline (nullptr), m_spacer_clock (nullptr), m_label_outputbusname (nullptr), m_rbutton_portdisabled (nullptr), m_rbutton_clockoff (nullptr), m_rbutton_clockonpos (nullptr), m_rbutton_clockonmod (nullptr), m_rbutton_group (nullptr) { setup_ui(); /* defined below, not in .h/.hpp */ connect ( m_rbutton_group, SIGNAL(buttonClicked(int)), this, SLOT(clock_callback_clicked(int)) ); } /** * The tool-tips. * * "This setting disables the usage of this output port, completely." * "It is needed in some cases for devices that are detected, but " * "cannot be used (e.g. devices locked by another application)." * * "MIDI Clock will be disabled. Used for conventional playback." * * "MIDI Clock will be sent. MIDI Song Position and MIDI Continue " * "will be sent if starting after tick 0 in song mode; otherwise " * "MIDI Start is sent." * * "MIDI Clock will be sent. MIDI Start will be sent and clocking " * "will begin once the song position has reached the modulo of " * "the specified Size. Use for gear that doesn't respond to Song " * "Position." */ void qclocklayout::setup_ui () { std::string busname; e_clock clocking; bool gotbussinfo = perf().ui_get_clock(bus(), clocking, busname, false); if (gotbussinfo) { if (busname.length() > c_max_name_length) busname = busname.substr(0, c_max_name_length); m_horizlayout_clockline = new QHBoxLayout(); m_horizlayout_clockline->setContentsMargins(0, 0, 0, 0); m_spacer_clock = new QSpacerItem ( 2, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Minimum ); QString qbname = qt(busname); m_label_outputbusname = new QLabel(); m_label_outputbusname->setText(qbname); m_rbutton_portdisabled = new QRadioButton("Disabled"); m_rbutton_clockoff = new QRadioButton("Off"); m_rbutton_clockonpos = new QRadioButton("On:Pos"); m_rbutton_clockonmod = new QRadioButton("On:Mod"); m_rbutton_group = new QButtonGroup(this); m_rbutton_group->addButton ( m_rbutton_portdisabled, int(e_clock::disabled) ); m_rbutton_group->addButton(m_rbutton_clockoff, int(e_clock::none)); m_rbutton_group->addButton(m_rbutton_clockonpos, int(e_clock::pos)); m_rbutton_group->addButton(m_rbutton_clockonmod, int(e_clock::mod)); m_horizlayout_clockline->addWidget(m_label_outputbusname); m_horizlayout_clockline->addItem(m_spacer_clock); m_horizlayout_clockline->addWidget(m_rbutton_portdisabled); m_horizlayout_clockline->addWidget(m_rbutton_clockoff); m_horizlayout_clockline->addWidget(m_rbutton_clockonpos); m_horizlayout_clockline->addWidget(m_rbutton_clockonmod); bool unavailable = perf().is_port_unavailable ( bus(), midibase::io::output ); if (unavailable) { m_label_outputbusname->setEnabled(false); m_rbutton_portdisabled->setChecked(true); m_rbutton_portdisabled->setEnabled(false); m_rbutton_clockoff->setChecked(false); m_rbutton_clockoff->setEnabled(false); m_rbutton_clockonpos->setEnabled(false); m_rbutton_clockonmod->setEnabled(false); /* * Overridden by the Clock tab's tooltip. * * m_rbutton_portdisabled->setToolTip("Port is unavailable"); */ } else { switch (clocking) { case e_clock::unavailable: m_label_outputbusname->setEnabled(false); m_rbutton_portdisabled->setChecked(true); m_rbutton_portdisabled->setEnabled(false); m_rbutton_clockonpos->setEnabled(false); m_rbutton_clockonmod->setEnabled(false); break; case e_clock::disabled: m_label_outputbusname->setEnabled(true); m_rbutton_portdisabled->setChecked(true); m_rbutton_portdisabled->setEnabled(true); m_rbutton_clockonpos->setEnabled(true); m_rbutton_clockonmod->setEnabled(true); break; case e_clock::none: m_label_outputbusname->setEnabled(true); m_rbutton_clockoff->setChecked(true); break; case e_clock::pos: m_label_outputbusname->setEnabled(true); m_rbutton_clockonpos->setChecked(true); break; case e_clock::mod: m_label_outputbusname->setEnabled(true); m_rbutton_clockonmod->setChecked(true); break; case e_clock::max: /* will never occur */ break; } } } } /** * Sets the clocking value based on in incoming parameter. We have to use * this particular slot in order to handle all of the radio-buttons. * * \param id * Provides the ID code of the button that was clicked. We set these ID * values explicitly, via addButton(ptrbutton, int(e_clock::disabled)). * For some reason, probably because -1 is a special flag for this * callback, -1 [e_clock::disabled] gets converted to -2. So we have to * adjust. */ void qclocklayout::clock_callback_clicked (int id) { if (id == (-2)) id = (-1); /* e_clock::disabled */ e_clock clocking = static_cast(id); bool enable = clocking != e_clock::disabled; perf().ui_set_clock(bus(), clocking); m_label_outputbusname->setEnabled(enable); m_rbutton_portdisabled->setEnabled(enable); m_rbutton_clockoff->setEnabled(true); m_rbutton_clockonpos->setEnabled(enable); m_rbutton_clockonmod->setEnabled(enable); parent_widget()->enable_bus_item(bus(), enable); /* tell the parent */ } } // namespace seq66 /* * qclocklayout.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qeditbase.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qeditbase.cpp * * This module declares/defines the base class for drawing on the piano * roll of the song editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-08-05 * \updates 2025-07-21 * \license GNU GPLv2 or above * * We are currently moving toward making this class a base class. * * User jean-emmanual added support for disabling the following of the * progress bar during playback. See the seqroll::m_progress_follow member. * * Note that substeps are always a different color, pen width of 1, and * dotted style. "*" marks the default set in this class. * * Grid * Line Category Normal Thick * * Any Progress bar 1 * 2+ * * Perf roll, Measure pen 1 * 2 * time, Measure style Qt::SolidLine * Qt::SolidLine * names Beat pen * 1 * 1 * Beat style Qt::DotLine Qt::DotLine * Four style N/A N/A * * Seq roll, Measure pen * 2 * 2 * time, Measure style * Qt::SolidLine * Qt::SolidLine * event Beat pen * 1 * 1 * Beat style * Qt::SolidLine * Qt::SolidLine * Four style Qt::DotLine * Qt::DashDotLine */ #include "cfg/settings.hpp" /* seq66::usr() */ #include "play/performer.hpp" /* seq66::performer class */ #include "qeditbase.hpp" namespace seq66 { /* * See qeditbase::horizSizeHint(). */ static const float c_horizontal_factor = 1.25f; /** * Principal constructor. */ qeditbase::qeditbase ( performer & p, int initialzoom, int snap, int scalex, int padding, int unit_height, int total_height ) : qbase (p), m_dark_ui (is_dark_ui()), m_back_color (background_paint()), m_fore_color (foreground_paint()), m_label_color (label_paint()), m_sel_color (sel_paint()), m_drum_color (drum_paint()), m_progress_color (progress_paint()), m_beat_color (beat_paint()), m_step_color (step_paint()), m_octave_color (octave_paint()), m_note_in_color (note_in_paint()), m_note_out_color (note_out_paint()), m_tempo_color (tempo_paint()), m_grey_color (grey_paint()), m_extra_color (extra_paint()), m_blank_brush (gui_empty_brush()), m_note_brush (gui_note_brush()), m_scale_brush (gui_scale_brush()), m_backseq_brush (gui_backseq_brush()), m_use_gradient (gui_use_gradient_brush()), m_progress_bar_width (2), m_measure_pen_width (2), m_beat_pen_width (1), m_horiz_pen_width (1), m_measure_pen_style (gui_measure_pen_style()), m_beat_pen_style (gui_beat_pen_style()), m_four_pen_style (gui_fourth_pen_style()), m_step_pen_style (gui_step_pen_style()), m_old (), /* past selection box */ m_selected (), /* current sel box */ m_zoomer (p.ppqn(), initialzoom, scalex), m_padding_x (padding), m_grid_snap (0), /* set below */ m_snap (0), /* calculated below */ m_beat_length (p.ppqn()), /* see change_ppqn() */ m_measure_length (m_beat_length * 4), /* see change_ppqn() */ m_selecting (false), m_adding (false), m_moving (false), m_moving_init (false), m_growing (false), m_painting (false), m_paste (false), m_drop_x (0), m_drop_y (0), m_current_x (0), m_last_snap_x (0), m_current_y (0), m_last_snap_y (0), m_progress_x (0), m_old_progress_x (0), m_scroll_page (0), m_progress_follow (usr().follow_progress()), m_scroll_offset (0), m_scroll_offset_v (0), m_scroll_offset_x (0), m_scroll_offset_y (0), m_unit_height (unit_height), m_total_height (total_height) { if (usr().progress_bar_thick()) { if (usr().progress_bar_thickness() > 1) m_progress_bar_width = usr().progress_bar_thickness(); } set_grid_snap(snap); } void qeditbase::set_grid_snap (midipulse snap) { midipulse snaptick = ( rescale_tick(snap, perf().ppqn(), usr().base_ppqn()) ); m_grid_snap = int(snap); m_snap = snaptick; } bool qeditbase::snap_current_x () { snap_x(m_current_x); bool result = m_last_snap_x != m_current_x; if (result) m_last_snap_x = m_current_x; return result; } bool qeditbase::snap_current_y () { snap_y(m_current_y); bool result = m_last_snap_y != m_current_y; if (result) m_last_snap_y = m_current_y; return result; } /** * int hint = perf().get_max_trigger() / scale_zoom() + 2000; * Why 2000? I forget! Let's use a factor instead. Also see * qperfbase::horizSizeHint(). * */ int qeditbase::horizSizeHint () const { return tix_to_pix(perf().get_max_trigger()) * c_horizontal_factor; } /** * Handles changes to the PPQN value in one place. Useful mainly at startup * time for adjusting the user-interface zooming. * * The m_ticks_per_bar member replaces the global ppqn times 16. This * construct is parts-per-quarter-note times 4 quarter notes times 4 * sixteenth notes in a bar. (We think...) * * The m_scale member starts out at c_perf_scale_x, which is 32 ticks * per pixel at the default tick rate of 192 PPQN. We adjust this now. * But note that this calculation still involves the c_perf_scale_x constant. * * \todo * Resolve the issue of c_perf_scale_x versus m_scale in perfroll. */ bool qeditbase::change_ppqn (int p) { m_beat_length = p; /* perf().ppqn() */ m_measure_length = m_beat_length * 4; /* what about beat width? */ return m_zoomer.change_ppqn(p); } /** * Snap = number pulses to snap to. Zoom = number of pulses per pixel. Thus * snap / zoom = number pixels to snap to. However, while looking into * issue #12, discovered that we need to scale by the actual PPQN versus the * base PPQN (192). */ void qeditbase::snap_x (int & x) { int sczoom = scale_zoom(); if (sczoom > 0) { /* * ca 2024-11-21. This is not the proper PPQN value to use. * We need the actual current PPQN. * * int mod = usr().base_ppqn() * m_snap / sczoom / ppqn(); * * Actually, we should not have to adjust via PPQN, since * zoom is already adjusted to compensate. * * int pu = usr().base_ppqn(); * int mod = pu * m_snap / sczoom / ppqn(); */ int mod = m_snap / sczoom; if (mod <= 0) mod = 1; x -= x % mod; } } /** * Checks to see if the song is running or if the "dirty" flag had been * set. The obtuse code here helps in debugging. Also, we might consider * using the new on_ui_change() callback. */ bool qeditbase::check_dirty () const { bool result = qbase::check_dirty(); if (! result) result = perf().needs_update(); return result; } void qeditbase::convert_ts (midipulse ticks, int seq, int & x, int & y) { x = tix_to_pix(ticks); y = m_total_height - ((seq + 1) * m_unit_height) - 1; } /** * Converts a time-stamp/sequence box to a rectangle (rect object). * * \param tick_s * The starting tick of the rectangle. * * \param tick_f * The finishing tick of the rectangle. * * \param seq_h * The high sequence row of the rectangle. * * \param seq_l * The low sequence row of the rectangle. * * \param [out] r * The destination rectangle for the calculations. */ void qeditbase::convert_ts_box_to_rect ( midipulse tick_s, midipulse tick_f, int seq_h, int seq_l, seq66::rect & r ) { int x1, y1, x2, y2; convert_ts(tick_s, seq_h, x1, y1); /* convert box to X,Y values */ convert_ts(tick_f, seq_l, x2, y2); rect::xy_to_rect(x1, y1, x2, y2, r); r.height_incr(m_unit_height); } void qeditbase::selection (seq66::rect & r) { m_selected = r; } /** * Clears all the mouse-action flags. */ void qeditbase::clear_action_flags () { m_selecting = m_moving = m_growing = m_paste = m_moving_init = m_painting = false; } } // namespace seq66 /* * qeditbase.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qinputcheckbox.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qinputcheckbox.cpp * * This class supports a MIDI Input check-box, associating it with a * particular MIDI input buss. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-05-20 * \updates 2023-05-16 * \license GNU GPLv2 or above * * This class is used in the qseditoptions settings-dialog class. */ #include #include "play/performer.hpp" /* seq66::performer class */ #include "qinputcheckbox.hpp" /* seq66::qinputcheckbox class */ #include "qseditoptions.hpp" /* seq66::qseditoptions class */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ namespace seq66 { /** * Creates a single line in the MIDI Input group-box. We will use * the words "input" or "port" for the MIDI input port represented by this * widget. Here are the jobs we have to do: * * -# Get the label for the port and set it. * -# Add the tooltips for the input radio-buttons. * -# Add the input radio-buttons to m_horizlayout_clocklive. * -# Connect to the check-button slot. * * \param parent * This parameter points to the qseditoption object that owns this * check-box. * * \param p * Provides the performer for some lookup activities. * * \parma bus * Provides the index into the list of inputs provided by the system. If * there is an input-port-map, then this bus number must be translated to * the true/actual system bus number before usage. */ qinputcheckbox::qinputcheckbox (QWidget * parent, performer & p, int bus) : qportwidget (parent, p, bus), m_chkbox_inputactive (nullptr) { setup_ui(); connect ( m_chkbox_inputactive, SIGNAL(stateChanged(int)), this, SLOT(input_callback_clicked(int)) ); } /** * Sets up the user-interface for one MIDI input buss. If the port is an ALSA * "announce" port (an input system port), it will be disabled. */ void qinputcheckbox::setup_ui () { std::string busname; bool inputing; bool gotbussinfo = perf().ui_get_input(bus(), inputing, busname); if (gotbussinfo) { QString qbname = qt(busname); bool unavailable = perf().is_port_unavailable ( bus(), midibase::io::input ); m_chkbox_inputactive = new QCheckBox(qbname); m_chkbox_inputactive->setChecked(inputing); if (! unavailable) unavailable = perf().is_input_system_port(bus()); if (unavailable) m_chkbox_inputactive->setToolTip("Port is unavailable"); m_chkbox_inputactive->setEnabled(! unavailable); } } /** * Sets the clocking value based on in incoming parameter. We have to use * this particular slot in order to handle all of the radio-buttons. * Note that there is no explicit and separate "disabled" state for input * like there is for clocks. * * \param state * Provides the state of the check-box that was clicked. */ void qinputcheckbox::input_callback_clicked (int state) { bool inputing = state == Qt::Checked; perf().ui_set_input(bus(), inputing); parent_widget()->enable_bus_item(bus(), true); /* tell the parent */ } } // namespace seq66 /* * qinputcheckbox.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qlfoframe.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qlfoframe.cpp * * This module declares/defines the base class for the LFO window. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2025-07-24 * \license GNU GPLv2 or above * * The LFO (low-frequency oscillator) provides a way to modulate the * selected type of event in the data. One can inserts a whole series of * pitch-wheel control events, for example, and then apply vibrato to them. * Various waveforms (sine, triangle, etc.) can be applied, at varying depths * and frequency of modulating, including a DC offset. */ #include #include #include "seq66-config.h" /* defines SEQ66_QMAKE_RULES */ #include "play/performer.hpp" /* seq66::performer class */ #include "qlfoframe.hpp" /* seq66::qlfoframe class */ #include "qseqdata.hpp" /* seq66::qseqdata for status, CC */ #include "qseqeditframe64.hpp" /* seq66::qseqeditframe64, parent */ #include "qt5_helper.h" /* QT5_HELPER_RADIO_SIGNAL macro */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qlfoframe.h" #else #include "forms/qlfoframe.ui.h" #endif namespace seq66 { /** * Static members. */ static double s_value_min = 0.0; static double s_value_def = 64.0; static double s_value_max = 127.0; static double s_range_min = 0.0; static double s_range_def = 64.0; static double s_range_max = 127.0; static double s_speed_min = 0.0; static double s_speed_def = 1.0; /* actually number of periods */ static double s_speed_max = 16.0; static double s_phase_min = 0.0; static double s_phase_max = 360.0; /* was 1.0, kind of silly */ /* * Signal buttonClicked(int) is overloaded in this class. To connect to this * signal by using the function pointer syntax, Qt provides a convenient * helper for obtaining the function pointer as used below, using a lambda * function. */ qlfoframe::qlfoframe ( performer & p, sequence & s, qseqdata & sdata, qseqeditframe64 * editparent, QWidget * parent ) : QFrame (parent), ui (new Ui::qlfoframe), m_wave_group (nullptr), m_performer (p), m_seq (s), m_seqdata (sdata), m_backup_events (s.events()), /* copy original events for reset() */ m_edit_frame (editparent), m_value (s_value_def), m_range (s_range_def), m_speed (s_speed_def), m_phase (s_phase_min), m_wave (waveform::none), m_use_measure (true), m_multiply (false), m_modify_locked (false), m_is_modified (false) { ui->setupUi(this); connect(ui->m_button_lock, SIGNAL(clicked()), this, SLOT(lock())); connect(ui->m_button_reset, SIGNAL(clicked()), this, SLOT(reset())); /* * Necessary for the close button to actually work. Otherwise * closeEvent() does not get called. */ connect(ui->m_button_close, SIGNAL(clicked()), this, SLOT(close())); /* * Wave type radio buttons. */ m_wave_group = new QButtonGroup(this); m_wave_group->addButton(ui->m_radio_wave_none, cast(waveform::none)); m_wave_group->addButton(ui->m_radio_wave_sine, cast(waveform::sine)); m_wave_group->addButton(ui->m_radio_wave_saw, cast(waveform::sawtooth)); m_wave_group->addButton ( ui->m_radio_wave_revsaw, cast(waveform::reverse_sawtooth) ); m_wave_group->addButton(ui->m_radio_wave_triangle, cast(waveform::triangle)); m_wave_group->addButton(ui->m_radio_wave_exp, cast(waveform::exponential)); m_wave_group->addButton ( ui->m_radio_wave_revexp, cast(waveform::reverse_exponential) ); m_wave_group->addButton(ui->m_radio_wave_dc, cast(waveform::dc)); ui->m_radio_wave_none->setChecked(true); /* match m_wave member init */ connect ( m_wave_group, QT5_HELPER_RADIO_SIGNAL, [=](int id) { wave_type_change(id); } ); /* * Order of calls is important here. */ ui->v_value_layout->setAlignment(ui->m_value_slider, Qt::AlignHCenter); ui->m_value_slider->setToolTip("DC offset for modulation, 0 to 127."); ui->m_value_slider->setMinimum(to_slider(s_value_min)); ui->m_value_slider->setMaximum(to_slider(s_value_max)); ui->m_value_slider->setValue(to_slider(m_value)); set_value_text(m_value, ui->m_value_text); connect ( ui->m_value_slider, SIGNAL(valueChanged(int)), this, SLOT(scale_lfo_change()) ); connect ( ui->m_value_text, SIGNAL(editingFinished()), this, SLOT(value_text_change()) ); /* * Order of calls is important here. */ ui->v_range_layout->setAlignment(ui->m_range_slider, Qt::AlignHCenter); ui->m_range_slider->setToolTip("Controls depth of modulation, 0 to 127."); ui->m_range_slider->setMinimum(to_slider(s_range_min)); ui->m_range_slider->setMaximum(to_slider(s_range_max)); ui->m_range_slider->setValue(to_slider(m_range)); set_value_text(m_range, ui->m_range_text); connect ( ui->m_range_slider, SIGNAL(valueChanged(int)), this, SLOT(scale_lfo_change()) ); connect ( ui->m_range_text, SIGNAL(editingFinished()), this, SLOT(range_text_change()) ); /* * Order of calls is important here. Also we replace the hardwired * tool-tip. */ ui->v_speed_layout->setAlignment(ui->m_speed_slider, Qt::AlignHCenter); ui->m_speed_slider->setToolTip ( "Speed (periods): number of periods per pattern or measure.\n" "For long patterns, set this parameter high. Beware of anti-aliasing.\n" ); ui->m_speed_slider->setMinimum(to_slider(s_speed_min)); ui->m_speed_slider->setMaximum(to_slider(s_speed_max)); ui->m_speed_slider->setValue(to_slider(m_speed)); set_value_text(m_speed, ui->m_speed_text); connect ( ui->m_speed_slider, SIGNAL(valueChanged(int)), this, SLOT(scale_lfo_change()) ); connect ( ui->m_speed_text, SIGNAL(editingFinished()), this, SLOT(speed_text_change()) ); /* * Order of calls is important here. */ ui->v_phase_layout->setAlignment(ui->m_phase_slider, Qt::AlignHCenter); ui->m_phase_slider->setToolTip ( "Phase: phase shift of the LFO waveform. " "Ranges from 0 to 360 degrees." ); ui->m_phase_slider->setMinimum(s_phase_min); ui->m_phase_slider->setMaximum(s_phase_max); ui->m_phase_slider->setValue(m_phase); set_value_text(m_phase, ui->m_phase_text); connect ( ui->m_phase_slider, SIGNAL(valueChanged(int)), this, SLOT(scale_lfo_change()) ); connect ( ui->m_phase_text, SIGNAL(editingFinished()), this, SLOT(phase_text_change()) ); ui->m_measures_check_box->setChecked(m_use_measure); connect ( ui->m_measures_check_box, SIGNAL(stateChanged(int)), this, SLOT(use_measure_clicked(int)) ); ui->m_multiply_check_box->setChecked(m_multiply); connect ( ui->m_multiply_check_box, SIGNAL(stateChanged(int)), this, SLOT(multiply_clicked(int)) ); std::string plabel = "Pattern #"; std::string number = std::to_string(int(track().seq_number())); plabel += number; ui->m_pattern_label->setText(qt(plabel)); plabel = "LFO #"; plabel += number; setWindowTitle(qt(plabel)); setFixedSize(width(), height()); } /** * Deletes the user-interface object. */ qlfoframe::~qlfoframe() { delete ui; if (m_is_modified) perf().modify(); } /** * A helper function to set the text of the slider control's text field. */ void qlfoframe::set_value_text ( double value, QLineEdit * textline ) { char valtext[16]; snprintf(valtext, sizeof valtext, "%g", value); textline->setText(valtext); } /** * Gets the "value" number from the text field when editing is finished (when * Enter is struck. */ void qlfoframe::value_text_change () { QString t = ui->m_value_text->text(); bool ok; double v = t.toDouble(&ok); if (ok && (v >= s_value_min && v <= s_value_max)) ui->m_value_slider->setValue(to_slider(v)); } void qlfoframe::range_text_change () { QString t = ui->m_range_text->text(); bool ok; double v = t.toDouble(&ok); if (ok && (v >= s_range_min && v <= s_range_max)) ui->m_range_slider->setValue(to_slider(v)); } void qlfoframe::speed_text_change () { QString t = ui->m_speed_text->text(); bool ok; double v = t.toDouble(&ok); if (ok && (v >= s_speed_min && v <= s_speed_max)) ui->m_speed_slider->setValue(to_slider(v)); } void qlfoframe::phase_text_change () { QString t = ui->m_phase_text->text(); bool ok; double v = t.toDouble(&ok); if (ok && (v >= s_phase_min && v <= s_phase_max)) ui->m_phase_slider->setValue(v); } void qlfoframe::wave_type_change (int waveid) { m_wave = waveform_cast(waveid); /* * For issue #139, do not reset the data when changing the waveform * type. The user can always press the Reset button. Also, don't * rescale if "None" was selected, but do reset. * * reset(); */ if (m_wave != waveform::none) scale_lfo_change(); else reset(); } /** * Changes the scaling provided by this window. Changes take place right * away in this callback, and would require multiple undoes to fully undo. * Hence the reset() function. * * If "None" is selected, this function does nothing; otherwise * it would zero out events. */ void qlfoframe::scale_lfo_change () { if (m_wave == waveform::none) return; m_value = to_double(ui->m_value_slider->value()); /* DC offset */ m_range = to_double(ui->m_range_slider->value()); /* modulation depth */ m_speed = to_double(ui->m_speed_slider->value()); /* periods to apply */ m_phase = ui->m_phase_slider->value(); /* phase (degrees) */ lfoparameters lp { m_value, m_range, m_speed, m_phase, m_wave, m_use_measure, m_multiply }; track().change_event_data_lfo(lp, m_seqdata.status(), m_seqdata.cc()); m_seqdata.set_dirty(); char tmp[16]; snprintf(tmp, sizeof tmp, "%g", m_value); ui->m_value_text->setText(tmp); snprintf(tmp, sizeof tmp, "%g", m_range); ui->m_range_text->setText(tmp); snprintf(tmp, sizeof tmp, "%g", m_speed); ui->m_speed_text->setText(tmp); snprintf(tmp, sizeof tmp, "%g", m_phase); ui->m_phase_text->setText(tmp); m_is_modified = true; } void qlfoframe::use_measure_clicked (int state) { bool usem = state == Qt::Checked; if (usem != m_use_measure) { m_use_measure = usem; scale_lfo_change(); } } void qlfoframe::multiply_clicked (int state) { bool usem = state == Qt::Checked; if (usem != m_multiply) { m_multiply = usem; /* * Not needed for this checkbox: scale_lfo_change(); */ } } void qlfoframe::lock () { m_backup_events = m_seq.events(); /* lock in changes */ track().set_dirty(); /* for redrawing */ m_is_modified = true; m_modify_locked = true; } /** * Restore the original events of the pattern. */ void qlfoframe::reset () { track().events() = m_backup_events; track().set_dirty(); /* for redrawing */ m_seqdata.set_dirty(); /* for redrawing */ if (! m_modify_locked) m_is_modified = false; } void qlfoframe::closeEvent (QCloseEvent * event) { if (not_nullptr(m_edit_frame)) m_edit_frame->remove_lfo_frame(); if (m_is_modified) perf().modify(); event->accept(); } } // namespace seq66 /* * qlfoframe.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qliveframeex.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qliveframeex.cpp * * This module declares/defines the base class for the external live frame * window. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-09-16 * \updates 2021-11-19 * \license GNU GPLv2 or above * * This frame holds an external "Live" window that shows the grid of buttons * for each set in the Seq66 MIDI file. */ #include #include "seq66-config.h" /* defines SEQ66_QMAKE_RULES */ #include "cfg/settings.hpp" /* seq66::usr() config functions */ #include "play/performer.hpp" /* seq66::performer class */ #include "play/sequence.hpp" /* seq66::sequence class */ #include "qliveframeex.hpp" /* seq66::qliveframeex container */ #include "qslivegrid.hpp" /* seq66::qslivegrid panel */ #include "qt5_helpers.hpp" /* seq66::qt(), qt_set_icon() etc. */ #include "qsmainwnd.hpp" /* seq66::qsmainwnd parent class */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qliveframeex.h" #else #include "forms/qliveframeex.ui.h" #endif namespace seq66 { /** * * \param p * Provides the performer object to use for interacting with this live * frame. * * \param ssnum * Provides the screen-set number. * * \param parent * Provides the parent window/widget for this container window. Defaults * to null. Note that this parameter does not link this class to the * parent as a QWidget, because that would make this class appear inside * the qsmainwnd user-interface. */ qliveframeex::qliveframeex (performer & p, int ssnum, qsmainwnd * parent) : QWidget (nullptr), ui (new Ui::qliveframeex), m_performer (p), m_screenset (ssnum), m_live_parent (parent), m_live_frame (nullptr) { ui->setupUi(this); QGridLayout * layout = new QGridLayout(this); m_live_frame = new (std::nothrow) qslivegrid(p, parent, ssnum, nullptr); if (not_nullptr(m_live_frame)) layout->addWidget(m_live_frame); if (usr().window_is_scaled()) /* use scaling if applicable */ { QSize s = size(); int h = s.height(); int w = s.width(); int width = usr().scale_size(w); int height = usr().scale_size_y(h); resize(width, height); if (not_nullptr(m_live_frame)) m_live_frame->repaint(); } show(); if (not_nullptr(m_live_frame)) { m_live_frame->update_bank(ssnum); m_live_frame->show(); } } qliveframeex::~qliveframeex() { if (not_nullptr(m_live_frame)) /* ca 2021-11-08 PROVISIONAL */ delete m_live_frame; delete ui; } /** * Removes the child, which is the enclosed live frame. */ void qliveframeex::closeEvent (QCloseEvent *) { if (not_nullptr(m_live_parent)) m_live_parent->remove_live_frame(m_screenset); } /** * Updates the live frame. */ void qliveframeex::update_draw_geometry () { if (not_nullptr(m_live_frame)) m_live_frame->update_geometry(); } void qliveframeex::update_sequence (int seqno, bool redo) { if (not_nullptr(m_live_frame)) m_live_frame->update_sequence(seq::number(seqno), redo); } /** * This function is called when focus changes. We forward the call to the * actual live-frame or live-grid. */ void qliveframeex::changeEvent (QEvent * event) { QWidget::changeEvent(event); if (event->type() == QEvent::ActivationChange) { std::string name = m_live_frame->bank_name(); std::string t = "Set "; t += std::to_string(m_live_frame->bank_id()); if (! name.empty()) { t += ": "; t += name; } setWindowTitle(qt(t)); m_live_frame->change_event(event); /* changeEvent(event) */ } } } // namespace seq66 /* * qliveframeex.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qloopbutton.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qloopbutton.cpp * * This module declares/defines the base class for drawing a pattern-slot * button. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-06-28 * \updates 2025-06-03 * \license GNU GPLv2 or above * * A paint event is a request to repaint all/part of a widget. It happens for * the following reasons: repaint() or update() was invoked; the widget was * obscured and then uncovered; or other reasons. Widgets can repaint their * entire surface, but slow widgets need to optimize by painting only the * requested region: QPaintEvent::region(); painting is clipped to that * region. * * Qt tries to speed painting by merging multiple paint events into one. When * update() is called several times or the window system sends several paint * events, Qt merges these events into one event with a larger region * [QRegion::united()]. repaint() does not permit this optimization, so use * update() whenever possible. * * When paint occurs, the update region is normally erased, so you are * painting on the widget's background. * * The qloopbutton turns off the Qt::WA_Hover attribute. This attribute * makes the button repaint whenever the mouse moves over it, which wastes * CPU cycles and makes it hard to keep the button text and progress bar * intact. */ #include #include #include /* std::sin(radians) */ #include /* std::strcat() */ #include "cfg/settings.hpp" /* seq66::usr().scale_size(), etc. */ #include "gui_palette_qt5.hpp" /* gui_pallete_qt5::Color etc. */ #include "qloopbutton.hpp" /* seq66::qloopbutton slot */ #include "qt5_helpers.hpp" /* seq66::qt(), qt_set_icon() etc. */ /** * Removed an attempt to use the inverse of the background color for drawing * text. It doesn't work with some GTK themes. Use Qt style sheets or a * 'palette' file instead. The background border can look garish, too. */ static bool s_elliptical_prog_box = false; /** * Alpha values for various states, not yet members, not yet configurable. */ static const int s_alpha_playing = 255; static const int s_alpha_muted = 100; static const int s_alpha_qsnap = 180; static const int s_alpha_queued = 64; static const int s_alpha_oneshot = 64; static const int s_alpha_popup = 32; /** * Font and annunciator sizes. These are for normal size, and get scaled for * other sizes. */ static const int s_fontsize_main = 8; // 7; static const int s_fontsize_large = 10; // for usr:progress-bar-thick static const int s_fontsize_record = 8; // 5; static const int s_radius_record = 8; // 9, 8; namespace seq66 { /** * Textbox functions. */ qloopbutton::textbox::textbox () : m_x (0), m_y (0), m_w (0), m_h (0), m_flags (0), m_label () { // no code } void qloopbutton::textbox::set ( int x, int y, int w, int h, int flags, std::string label ) { m_x = x; m_y = y; m_w = w; m_h = h; m_flags = flags; m_label = label; } /** * Progress-box values. */ bool qloopbutton::sm_draw_progress_box = true; double qloopbutton::sm_progress_w_fraction = 0.80; /* 0.0, 0.50 - 0.80 */ double qloopbutton::sm_progress_h_fraction = 0.25; /* 0.0, 0.10 - 0.40 */ const int qloopbutton::scm_progress_event_margin = 3; /* better viewing */ const int qloopbutton::sm_vert_draw_text_threshold = 40; const int qloopbutton::sm_vert_compressed_threshold = 45; const int qloopbutton::sm_horiz_compressed_threshold = 45; const int qloopbutton::sm_base_height = 12; /* rough font hght */ const float qloopbutton::sm_left_width_factor = 0.70; const float qloopbutton::sm_right_width_factor = 0.50; qloopbutton::progbox::progbox () : m_x (0), m_y (0), m_w (0), m_h (0), m_center_x (0), m_center_y (0) { // no code } /** * Let's do it like seq24/seq64, but not so tall, just enough to show progress. * * \param w * Provides the width of the progress box. It is the width of the button * and is adjusted by the user's desired width fraction, which defaults * to 0.8, but can be set to 0.5 to 1.0. * * \param h * Provides the height of the progress box. It is the height of the button * and is adjusted by the user's desired height fraction, which defaults * to 0.25, but can be set to 0.10 to 1.0. */ void qloopbutton::progbox::set (int w, int h) { m_x = int(double(w) * (1.0 - sm_progress_w_fraction) / 2.0); m_y = int(double(h) * (1.0 - sm_progress_h_fraction) / 2.0); m_w = w - 2 * m_x; m_h = h - 2 * m_y; m_center_x = m_x + m_w / 2; m_center_y = m_y + m_h / 2; } /** * Principal constructor. */ qloopbutton::qloopbutton ( const qslivegrid * const slotparent, seq::number slotnumber, const std::string & label, const std::string & hotkey, seq::pointer seqp, QWidget * parent ) : qslotbutton (slotparent, slotnumber, label, hotkey, parent), m_show_average (false), /* last versus average */ m_fingerprint_inited (false), m_fingerprinted (false), m_fingerprint_size (usr().fingerprint_size()), m_fingerprint (m_fingerprint_size), /* reserve vector space */ m_fingerprint_count (m_fingerprint_size), m_note_min (usr().progress_note_min()), m_note_max (usr().progress_note_max()), m_seq (seqp), /* loop() */ m_is_checked (loop()->armed()), m_is_flat (false), m_show_cc (usr().progress_box_show_cc()), m_prog_thickness (usr().progress_bar_thick() ? 2 : 1), m_prog_back_color (Qt::black), m_prog_fore_color (Qt::green), m_text_font (), m_text_initialized (false), m_draw_text (true), m_top_left (), m_top_right (), m_bottom_left (), m_bottom_right (), m_progress_box (), m_event_box (), m_use_gradient (gui_use_gradient_brush()) { /* * Does not seem to work. */ setMouseTracking(true); /* NEW ca 2025-09-17 */ sm_draw_progress_box = usr().progress_box_shown(); s_elliptical_prog_box = usr().progress_box_elliptical(); m_text_font.setBold(usr().progress_bar_thick()); m_text_font.setLetterSpacing(QFont::AbsoluteSpacing, 1); make_active(); make_checkable(); set_checked(m_is_checked); int c = loop() ? loop()->color() : palette_to_int(PaletteColor::none) ; if (c != palette_to_int(PaletteColor::black)) back_color(get_color_fix(PaletteColor(c))); } /** * Static function to control when the qloopbutton text boxes get * initialized. we want the statics to be initialized at "start up" and * when the main window is resized. */ bool qloopbutton::boxes_initialized (bool reset) { static bool s_boxes_initialized = false; if (reset) { s_boxes_initialized = false; return false; } else { bool result = s_boxes_initialized; s_boxes_initialized = true; return result; } } void qloopbutton::progress_box_size (double w, double h) { if (w >= 0.50 && w <= 1.0) sm_progress_w_fraction = w; if (h >= 0.10 && h <= 1.0) sm_progress_h_fraction = h; } /** * We can set up the relative locations and the box sizes just once every * time the UI size is re-established. * * Variables: l = left, r = right, t = top, and b = bottom */ bool qloopbutton::initialize_text () { bool result = ! m_text_initialized; if (result) { #if defined SEQ66_PLATFORM_DEBUG_TMI static bool sm_show_message = true; #endif int w = width(); int h = height(); if (h < sm_vert_draw_text_threshold) m_draw_text = false; #if defined SEQ66_PLATFORM_DEBUG_TMI if (sm_show_message) { printf("button size %d x %d\n", w, h); sm_show_message = false; } #endif int dx = usr().scale_size(4); int dy = usr().scale_size_y(2); int lw = int(sm_left_width_factor * w); int rw = int(sm_right_width_factor * w); int lx = dx; int ty = dy + 2; int bh = usr().scale_size_y(sm_base_height); int rx = int(0.50 * w) + lx - dx - 1; int by = int(0.85 * h) + dy - 4; /* 3 */ int basefontsize = usr().progress_bar_thick() ? s_fontsize_large : s_fontsize_main ; int fontsize = usr().scale_font_size(basefontsize); if (vert_compressed()) { ty += 2; by -= 2; bh = bh / 2 - 2; } if (horiz_compressed()) { lx += 2; rx -= 2; } m_progress_box.set(w, h); m_event_box = m_progress_box; m_event_box.m_x += scm_progress_event_margin; m_event_box.m_y += scm_progress_event_margin; m_event_box.m_w -= scm_progress_event_margin * 2; m_event_box.m_h -= scm_progress_event_margin * 2; m_text_font.setPointSize(fontsize); /* * Code from performer::sequence_label(). */ bussbyte bus = loop()->seq_midi_bus(); int bpb = int(loop()->get_beats_per_bar()); int bw = int(loop()->get_beat_width()); int sn = loop()->seq_number(); int trx = rx; int lflags = Qt::AlignLeft | Qt::AlignVCenter; int rflags = Qt::AlignRight | Qt::AlignVCenter; std::string lengthstr = std::to_string(loop()->get_measures()); std::string chanstr = loop()->channel_string(); std::string lowerleft, hotkey; char tmp[32]; if (horiz_compressed()) { snprintf(tmp, sizeof tmp, "%-2d %d-%s", sn, bus, chanstr.c_str()); } else if (sn > 999) { snprintf ( tmp, sizeof tmp, "%d %d-%s %d/%d", sn, bus, chanstr.c_str(), bpb, bw ); } else { snprintf ( tmp, sizeof tmp, "#%-2d %d-%s %d/%d", sn, bus, chanstr.c_str(), bpb, bw ); } lowerleft = std::string(tmp); hotkey = "[" + m_hotkey + "]"; if (loop()->modified()) lengthstr += "*"; else if (loop()->loop_count_max() > 0) lengthstr += "+"; if (loop()->has_in_bus()) { std::string inbus = std::to_string(int(loop()->seq_midi_in_bus())); inbus += ":"; lengthstr = inbus + lengthstr; trx -= 2; /* * fontsize */ } m_top_left.set(lx, ty, lw, bh, lflags, loop()->name()); if (! horiz_compressed()) m_top_right.set(trx, ty, rw, bh, rflags, lengthstr); m_bottom_left.set(lx, by, lw, bh, lflags, lowerleft); m_bottom_right.set(rx, by, rw, bh, rflags, hotkey); m_text_initialized = true; } else result = m_text_initialized; return result; } /** * This function examines the current sequence to determine how many notes it * has, and the range of note values (pitches). */ void qloopbutton::initialize_fingerprint () { const int i1 = int(m_fingerprint_size); if (! m_fingerprint_inited && i1 > 0) { int n0, n1; bool have_notes = loop()->minmax_notes(n0, n1); /* fill n0 and n1 */ if (have_notes) have_notes = loop()->event_threshold(); if (have_notes) { midipulse t1 = loop()->get_length(); /* t0 = 0 */ if (t1 == 0) return; int x0 = m_event_box.x(); int x1 = x0 + m_event_box.w(); int xw = x1 - x0; int y0 = m_event_box.y(); int y1 = y0 + m_event_box.h(); int yh = y1 - y0; if (m_note_max > 0) { n0 = m_note_min; n1 = m_note_max; } /* * Added an octave of padding above and below for looks. Also use * n0 and n1 as the min/max notes of the whole sequence. */ n0 -= 12 / 6; n0 = clamp_midibyte_value(n0); n1 += 12 / 6; n1 = clamp_midibyte_value(n1); for (int i = 0; i < i1; ++i) m_fingerprint[i] = m_fingerprint_count[i] = 0; int nh = n1 - n0; loop()->draw_lock(); for (auto cev = loop()->cbegin(); ! loop()->cend(cev); ++cev) { sequence::note_info ni; sequence::draw dt = loop()->get_next_note(ni, cev); if (dt == sequence::draw::finish) break; int x = x0 + (ni.start() * xw) / t1; int y = y0 + yh * (ni.note() - n0) / nh; int i = i1 * (x - x0) / xw; if (i < 0) i = 0; else if (i >= i1) i = i1 - 1; if (m_show_average) /* not sure how useful this is */ { ++m_fingerprint_count[i]; m_fingerprint[i] += midishort(y); } else m_fingerprint[i] = midishort(y); } loop()->draw_unlock(); for (int i = 0; i < i1; ++i) { if (m_fingerprint_count[i] > 1) m_fingerprint[i] /= m_fingerprint_count[i]; } m_fingerprinted = true; } } m_fingerprint_inited = true; } /** * Sets up the foreground and background colors of the button and the * appropriate setAutoFillBackground() setting. We've removed the garish * painting of the pattern color on the button borders. */ void qloopbutton::setup () { int c = loop() ? loop()->color() : palette_to_int(PaletteColor::none) ; if (c == palette_to_int(PaletteColor::black)) { /* * No coloring on the button borders. */ } else { /* * This sets the color to the background, which results in an ugly * colored border around the button in many themes. */ Color backcolor = get_color_fix(PaletteColor(c)); m_prog_back_color = backcolor; } setEnabled(true); setCheckable(is_checkable()); setAttribute(Qt::WA_Hover, false); /* avoid nasty repaints */ } void qloopbutton::set_checked (bool flag) { m_is_checked = flag; setChecked(flag); } void qloopbutton::set_flat (bool flag) { m_is_flat = flag; setChecked(flag); } bool qloopbutton::toggle_enabled () { bool enabled = ! isEnabled(); setEnabled(enabled); return true; } bool qloopbutton::toggle_checked () { bool result = loop()->sequence_playing_toggle(); if (result) { bool checked = loop()->armed(); set_checked(checked); reupdate(true); } return result; } /** * Call the update() function of this button. * * \param all * If true, the whole button is updated. Otherwise, only the progress * box is updated. The default is true. */ void qloopbutton::reupdate (bool all) { if (is_active()) { if (all) { m_text_initialized = false; if (initialize_text()) update(); } else { int x = m_progress_box.m_x; int y = m_progress_box.m_y; int w = m_progress_box.m_w; int h = m_progress_box.m_h; update(x, y, w, h); } } } /** * Draws the text and progress panel. * \verbatim ---------------------------- top | Title Length | | Armed | | ------------ | | | P A N E L | | | ------------ | | | bottom | buss-chan 4/4 hotkey | ---------------------------- \endverbatim * * Note that we first call QPushButton::paintEvent(pev) to make sure that the * click highlights/unhighlight this checkable button. And this call must be * done first, otherwise the application segfaults. * * Issue #89: Added showing "Unqueued" if queueing enabled while the pattern * was playing. */ void qloopbutton::paintEvent (QPaintEvent * pev) { QPushButton::paintEvent(pev); QPainter painter(this); if (loop()) { midipulse tick = loop()->get_last_tick(); if (initialize_text() || tick == 0) { QRectF box ( m_top_left.m_x, m_top_left.m_y, m_top_left.m_w, m_top_left.m_h ); QString title(qt(m_top_left.m_label)); /* * painter.setPen(label_color()); // text issue #50 * painter.setPen(text_paint()); */ painter.setPen(text_color()); painter.setFont(m_text_font); if (m_draw_text) /* title */ { painter.drawText(box, m_top_left.m_flags, title); title = qt(m_top_right.m_label); box.setRect ( m_top_right.m_x, m_top_right.m_y, m_top_right.m_w, m_top_right.m_h ); painter.drawText(box, m_top_right.m_flags, title); } if (loop()->recording()) { int radius = usr().scale_size(s_radius_record) + 2; int clx = m_top_right.m_x + m_top_right.m_w - radius - 12; int cly = m_top_right.m_y + m_top_right.m_h; QPen pen2(drum_paint()); painter.save(); if (use_gradient()) { QRadialGradient rgrad(clx, cly, radius); rgrad.setColorAt(0, Qt::white); rgrad.setColorAt(1, drum_paint()); painter.setBrush(QBrush(rgrad)); } else { QBrush brush(drum_paint(), Qt::SolidPattern); painter.setBrush(brush); } painter.setPen(pen2); painter.drawEllipse(clx, cly, radius, radius); if (loop()->alter_recording()) /* Q, Tighten, etc. */ { char rlabel[4] = { 0, 0, 0, 0 }; int tlx = clx + usr().scale_size(2); int tly = cly + radius - usr().scale_size(2) + 2; int fontsize = usr().scale_font_size(s_fontsize_record); QFont font; font.setPointSize(fontsize); font.setBold(true); painter.setPen(Qt::white); /* Qt::black */ painter.setFont(font); if (loop()->quantizing()) std::strcat(rlabel, "Q "); else if (loop()->tightening()) std::strcat(rlabel, "T "); else if (loop()->notemapping()) std::strcat(rlabel, "N "); if (loop()->expanded_recording()) std::strcat(rlabel, ">"); painter.drawText(tlx, tly, rlabel); } painter.restore(); } if (m_draw_text) { title = qt(m_bottom_left.m_label); box.setRect ( m_bottom_left.m_x, m_bottom_left.m_y, m_bottom_left.m_w, m_bottom_left.m_h ); painter.drawText(box, m_bottom_left.m_flags, title); title = qt(m_bottom_right.m_label); box.setRect ( m_bottom_right.m_x, m_bottom_right.m_y, m_bottom_right.m_w, m_bottom_right.m_h ); painter.drawText(box, m_bottom_right.m_flags, title); } set_checked(loop()->armed()); /* gets hot-key toggle to show */ if (loop()->is_new_pattern()) { title = "New"; } else { if (loop()->armed()) { title = loop()->get_queued() ? "Unqueued" : "Armed" ; } else if (loop()->get_queued()) title = "Queued"; else if (loop()->one_shot()) title = "One-shot"; else title = "Muted"; } if (m_draw_text) { // TODO: If vertically compressed, smaller font size needed int line2y = 2 * usr().scale_font_size(6); box.setRect ( m_top_left.m_x, m_top_left.m_y + line2y, m_top_left.m_w, m_top_left.m_h ); painter.drawText(box, m_top_left.m_flags, title); } initialize_fingerprint(); } if (sm_draw_progress_box) draw_progress_box(painter); draw_pattern(painter); bool tiny = ! (loop()->is_playable() && loop()->armed()); draw_progress(painter, tick, tiny); } else { std::string snstring = std::to_string(m_slot_number); snstring += ": NO LOOP!"; setEnabled(false); setText(qt(snstring)); } } /** * Draws the progress box, progress bar, and and indicator for non-empty * pattern slots. * * Idea: * * Merge this inside of drawing the events! */ void qloopbutton::draw_progress ( QPainter & painter, midipulse tick, bool tiny ) { midipulse t1 = loop()->get_length(); if (t1 > 0) { QBrush brush(m_prog_back_color, Qt::SolidPattern); QPen pen(progress_color()); /* Qt::black */ int x = m_event_box.x(); int w = m_event_box.w(); int yh; int yoffset; if (tiny) { yh = 6; yoffset = m_event_box.h() - 6; } else { yh = m_event_box.h() - 2; yoffset = 1 ; } int y0 = m_event_box.y() + yoffset; int y1 = y0 + yh; x += int(w * tick / t1); pen.setWidth(m_prog_thickness); pen.setStyle(Qt::SolidLine); painter.setBrush(brush); painter.setPen(pen); painter.drawLine(x, y1, x, y0); } } /** * Draws the progress box, progress bar, and an indicator for non-empty * pattern slots. */ void qloopbutton::draw_progress_box (QPainter & painter) { QBrush brush(m_prog_back_color, Qt::SolidPattern); QPen pen(pen_color()); /* #50: text_color() */ const int penwidth = m_prog_thickness; /* 2 */ bool qsnap = loop()->snap_it(); Color backcolor = back_color(); if (qsnap) /* playing, queued, ... */ { backcolor.setAlpha(s_alpha_qsnap); pen.setColor(Qt::gray); /* instead of Qt::black */ pen.setStyle(Qt::SolidLine); } else if (loop()->has_popup()) { backcolor.setAlpha(s_alpha_popup); } else if (loop()->armed()) /* armed, playing */ { backcolor.setAlpha(s_alpha_playing); } else if (loop()->get_queued()) { backcolor = Qt::gray; backcolor.setAlpha(s_alpha_queued); pen.setStyle(Qt::SolidLine); } else if (loop()->one_shot()) /* one-shot queued */ { backcolor = Qt::black; backcolor.setAlpha(s_alpha_oneshot); pen.setColor(Qt::darkGray); pen.setStyle(Qt::DotLine); } else /* unarmed, muted */ { backcolor.setAlpha(s_alpha_muted); pen.setStyle(Qt::SolidLine); } pen.setWidth(penwidth); if (use_gradient()) { if (s_elliptical_prog_box) { /* * Probably not worth making this a member. */ QRadialGradient grad ( m_progress_box.center_x(), m_progress_box.center_y(), m_progress_box.w() ); grad.setColorAt(0.01, backcolor.darker()); grad.setColorAt(0.5, backcolor.lighter()); grad.setColorAt(0.99, backcolor.darker()); painter.setPen(Qt::NoPen); painter.setBrush(QBrush(grad)); painter.drawEllipse ( m_progress_box.x(), m_progress_box.y(), m_progress_box.w(), m_progress_box.h() ); } else { /* * Probably not worth making this a member. */ QLinearGradient grad ( m_progress_box.x(), m_progress_box.y(), m_progress_box.x(), m_progress_box.y() + m_progress_box.h() ); grad.setColorAt(0.01, backcolor.darker()); grad.setColorAt(0.5, backcolor.lighter()); grad.setColorAt(0.99, backcolor.darker()); painter.setBrush(QBrush(grad)); painter.fillRect ( m_progress_box.x(), m_progress_box.y(), m_progress_box.w(), m_progress_box.h(), grad ); } } else { brush.setColor(backcolor); painter.setPen(pen); painter.setBrush(brush); if (s_elliptical_prog_box) { painter.drawEllipse ( m_progress_box.x(), m_progress_box.y(), m_progress_box.w(), m_progress_box.h() ); } else { painter.drawRect ( m_progress_box.x(), m_progress_box.y(), m_progress_box.w(), m_progress_box.h() ); } } } /** * Draws the progress box, progress bar, and and indicator for non-empty * pattern slots. Two style of drawing are done: * * - If sequence::event_threshold() is true, then the calculated * number of measures is greater than 4. We don't need to draw the * whole set of notes. Instead, we draw the calculated finger print. * - Otherwise, the sequence is short and we draw it normally. */ void qloopbutton::draw_pattern (QPainter & painter) { midipulse t1 = loop()->get_length(); if (loop()->event_count() > 0 && t1 > 0) { QBrush brush(m_prog_back_color, Qt::SolidPattern); QPen pen(text_color()); int x0 = m_event_box.x(); int y0 = m_event_box.y(); int xw = m_event_box.w(); int yh = m_event_box.h(); if (m_fingerprinted) { if (loop()->transposable()) pen.setColor(pen_color()); /* not text_color() */ else pen.setColor(drum_color()); float x = float(x0); float dx = float(xw) / (m_fingerprint_size - 1); pen.setWidth(2); painter.setPen(pen); for (int i = 0; i < int(m_fingerprint_size); ++i, x += dx) { midishort fp = m_fingerprint[i]; if (fp > 0 && fp != max_midibyte()) painter.drawPoint(int(x), int(fp)); } } else { int height = max_midi_value(); int n0, n1; if (m_note_max > 0) { n0 = m_note_min; n1 = m_note_max; } else { bool have_notes = loop()->minmax_notes(n0, n1); if (have_notes) { /* * Added an octave of padding above and below for looks. */ n0 -= 12; n0 = clamp_midibyte_value(n0); n1 += 12; n1 = clamp_midibyte_value(n1); } else { n0 = 0; n1 = max_midi_value(); } } height = n1 - n0; pen.setWidth(1); if (loop()->transposable()) pen.setColor(pen_color()); /* issue #50 text_color() */ else pen.setColor(drum_color()); painter.setPen(pen); loop()->draw_lock(); for (auto cev = loop()->cbegin(); ! loop()->cend(cev); ++cev) { sequence::note_info ni; sequence::draw dt = loop()->get_next_note(ni, cev); if (dt == sequence::draw::finish) break; int tick_s_x = (ni.start() * xw) / t1; int sx = x0 + tick_s_x; /* start x */ if (dt == sequence::draw::tempo) { midibpm max = usr().midi_bpm_maximum(); midibpm min = usr().midi_bpm_minimum(); double tempo = double(ni.velocity()); int y = int((max - tempo) / (max - min) * yh) + y0; brush.setColor(tempo_paint()); painter.setBrush(brush); painter.drawEllipse(sx, y, 4, 4); } else { int y = y0 + yh * (n1 - ni.note()) / height; if (dt == sequence::draw::program) { brush.setColor(drum_paint()); painter.setBrush(brush); painter.drawEllipse(sx, y, 4, 4); } else if ( dt == sequence::draw::controller || dt == sequence::draw::pitchbend ) { if (m_show_cc) painter.drawPoint(sx, y); } else { int tick_f_x = (ni.finish() * xw) / t1; if ( ! sequence::is_draw_note(dt) || tick_f_x <= tick_s_x ) { tick_f_x = tick_s_x + 1; } int fx = x0 + tick_f_x; /* finish x */ painter.drawLine(sx, y, fx, y); } } } loop()->draw_unlock(); } } } /** * This event occurs only upon a click. */ void qloopbutton::focusInEvent (QFocusEvent *) { m_text_initialized = false; } /** * This event occurs only upon a click. */ void qloopbutton::focusOutEvent (QFocusEvent *) { m_text_initialized = false; } /** * This event occurs only upon a click. */ void qloopbutton::resizeEvent (QResizeEvent * qrep) { QSize s = qrep->size(); vert_compressed(s.height() < sm_vert_compressed_threshold); horiz_compressed(s.width() < sm_horiz_compressed_threshold); /* * Weird, makes us re-init every damn time. * * boxes_initialized(true); // reset to false // */ m_text_initialized = false; QWidget::resizeEvent(qrep); } } // namespace seq66 /* * qloopbutton.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qmutemaster.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qmutemaster.cpp * * This module declares/defines the base class for the mute-master tab. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-05-29 * \updates 2026-04-19 * \license GNU GPLv2 or above * */ #include /* Needed for QKeyEvent::accept() */ #include #include #include #include "seq66-config.h" /* defines SEQ66_QMAKE_RULES */ #include "cfg/settings.hpp" /* seq66::rc() */ #include "ctrl/keystroke.hpp" /* seq66::keystroke class */ #include "play/performer.hpp" /* seq66::performer class */ #include "qmutemaster.hpp" /* seq66::qmutemaster, this class */ #include "qsmainwnd.hpp" /* seq66::qsmainwnd main window */ #include "qt5_helpers.hpp" /* seq66::qt_keystroke() etc. */ #include "util/filefunctions.hpp" /* seq66::name_has_path() */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qmutemaster.h" #else #include "forms/qmutemaster.ui.h" #endif /** * Specifies the current hardwired value for set_row_heights(). */ static const int c_table_row_height = 18; static const int c_table_fix = 16; static const int c_button_size = 26; // 28; // 26; // 24; /* * Don't document the namespace. */ namespace seq66 { /** * * \param p * Provides the performer object to use for interacting with this frame. * * \param mainparent * Provides the parent window, which will call this frame up and also * needs to be notified if it goes away. * * \param parent * Provides the parent window/widget for this container window. Defaults * to null. * */ qmutemaster::qmutemaster ( performer & p, qsmainwnd * mainparent, QWidget * parent ) : QFrame (parent), performer::callbacks (p), ui (new Ui::qmutemaster), m_timer (nullptr), m_main_window (mainparent), m_is_initialized (false), m_to_midi_active (false), m_to_mutes_active (false), m_group_buttons (mutegroups::Size(), nullptr), /* "2-D" arrary */ m_pattern_buttons (p.screenset_size(), nullptr), /* group_count()*/ m_current_group (seq::unassigned()), /* important */ m_group_count (cb_perf().mutegroup_count()), m_button_row (seq::unassigned()), m_button_column (seq::unassigned()), m_trigger_active (false), m_needs_update (true), m_pattern_mutes (), m_pattern_offset (0) { ui->setupUi(this); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); clear_pattern_mutes(); /* empty the pattern bits */ create_group_buttons(); create_pattern_buttons(); setup_table(); /* row and column sizing */ (void) initialize_table(); /* fill with mute-group information */ QString mgfname = qt(rc().mute_group_filename()); bool mutesactive = rc().mute_group_file_active(); ui->m_mute_basename->setPlainText(mgfname); ui->m_check_mutes_active->setChecked(mutesactive); #if defined USE_MUTES_FILE_TEXTEDIT bool mgfactive = rc().mute_group_file_active(); ui->m_mute_basename->setEnabled(mgfactive); connect ( ui->m_mute_basename, SIGNAL(textChanged()), this, SLOT(slot_mutes_file_modify()) ); #endif /* * First set the status of the bin/hex radio buttons, and only then * connect the bin/hex radio buttons and set them as per the configured * status at start-up. */ set_bin_hex(! cb_perf().group_format_hex()); connect ( ui->m_radio_binary, SIGNAL(toggled(bool)), this, SLOT(slot_bin_mode(bool)) ); connect ( ui->m_radio_hex, SIGNAL(toggled(bool)), this, SLOT(slot_hex_mode(bool)) ); ui->m_button_trigger->setEnabled(true); ui->m_button_trigger->setCheckable(true); connect ( ui->m_button_trigger, SIGNAL(clicked()), this, SLOT(slot_trigger()) ); ui->m_button_set_mutes->setEnabled(false); #if defined USE_GROUP_UPDATE_BUTTON connect ( ui->m_button_set_mutes, SIGNAL(clicked()), this, SLOT(slot_set_mutes()) ); #endif #if defined USE_REMOVED_MUTEMASTER_BUTTONS /* * If we add back the pattern-offset spin-box, we can put this * to the right of the xxxx label. */ bool large = cb_perf().screenset_size() > mutegroups::Size(); if (large) large = (cb_perf().screenset_size() % mutegroups::Size()) == 0; if (large) { int max = cb_perf().screenset_size() / mutegroups::Size() - 1; ui->m_pattern_offset_spinbox->setRange(0, max); } ui->m_pattern_offset_spinbox->setEnabled(large); connect ( ui->m_pattern_offset_spinbox, SIGNAL(valueChanged(int)), this, SLOT(slot_pattern_offset(int)) ); ui->m_button_down->setEnabled(false); connect(ui->m_button_down, SIGNAL(clicked()), this, SLOT(slot_down())); ui->m_button_up->setEnabled(false); #endif ui->m_button_load->setEnabled(true); connect(ui->m_button_load, SIGNAL(clicked()), this, SLOT(slot_load())); /* * We don't care if the user wants to save the current mute-groups * right away. They might have been read from the MIDI file, and the * user wants to keep a copy of the mute-groups. */ ui->m_button_save->setEnabled(true); connect(ui->m_button_save, SIGNAL(clicked()), this, SLOT(slot_save())); /* * To MIDI */ bool tomidi = cb_perf().group_save_to_midi(); ui->m_check_to_midi->setEnabled(true); ui->m_check_to_midi->setChecked(tomidi); m_to_midi_active = tomidi; connect ( ui->m_check_to_midi, SIGNAL(stateChanged(int)), this, SLOT(slot_write_to_midi()) ); /* * To Mutes */ bool tofile = cb_perf().group_save_to_mutes(); ui->m_check_to_mutes->setEnabled(true); ui->m_check_to_mutes->setChecked(tofile); m_to_mutes_active = tofile; connect ( ui->m_check_to_mutes, SIGNAL(stateChanged(int)), this, SLOT(slot_write_to_mutes()) ); connect ( ui->m_button_clear_all, SIGNAL(clicked()), this, SLOT(slot_clear_all_mutes()) ); ui->m_check_strip_empty->setEnabled(true); ui->m_check_strip_empty->setChecked(cb_perf().strip_empty()); connect ( ui->m_check_strip_empty, SIGNAL(stateChanged(int)), this, SLOT(slot_strip_empty()) ); ui->m_check_from_mutes->setEnabled(true); ui->m_check_from_mutes->setChecked ( cb_perf().group_load_from_mutes() ); connect ( ui->m_check_from_mutes, SIGNAL(stateChanged(int)), this, SLOT(slot_load_mutes()) ); ui->m_check_from_midi->setEnabled(true); ui->m_check_from_midi->setChecked ( cb_perf().group_load_from_midi() ); connect ( ui->m_check_from_midi, SIGNAL(stateChanged(int)), this, SLOT(slot_load_midi()) ); ui->m_check_toggle_active->setEnabled(true); ui->m_check_toggle_active->setChecked ( cb_perf().toggle_active_only() ); connect ( ui->m_check_toggle_active, SIGNAL(stateChanged(int)), this, SLOT(slot_toggle_active()) ); handle_group_button(0, 0); /* guaranteed to be present */ handle_group_change(0); /* select the first group */ cb_perf().enregister(this); /* register this for notifications */ group_needs_update(); /* guarantee the initial load */ m_is_initialized = true; m_timer = qt_timer(this, "qmutemaster", 3, SLOT(conditional_update())); } qmutemaster::~qmutemaster() { if (not_nullptr(m_timer)) m_timer->stop(); cb_perf().unregister(this); /* unregister this immediately */ delete ui; } void qmutemaster::conditional_update () { if (needs_update()) /* perf().needs_update() too iffy */ { update_group_buttons(); update_pattern_buttons(); update(); } } #if defined USE_REMOVED_MUTEMASTER_BUTTONS void qmutemaster::slot_pattern_offset (int index) { m_pattern_offset = index * cb_perf().screenset_size(); } void qmutemaster::slot_fill_mutes () { if (cb_perf().reset_mute_groups()) /* or clear_mute_groups() w/modify */ { if (initialize_table()) { modify_mutes(); group_needs_update(); } } } /** * A slot for handle_group_change(), meant to move a table row down. * Not yet ready for primetime. * * Actually this concept makes no sense. */ void qmutemaster::slot_down () { if (set_current_group(current_group() + 1)) handle_group_change(current_group()); } /** * A slot for handle_group_change(), meant to move a table row up. * Not yet ready for primetime. */ void qmutemaster::slot_up () { if (set_current_group(current_group() - 1)) handle_group_change(current_group()); } #endif void qmutemaster::slot_clear_all_mutes () { if (cb_perf().clear_mutes()) { modify_mutes(); update_group_buttons(); /* see conditional_update() */ update_pattern_buttons(); /* ditto */ } } /** * Done only at construction. */ void qmutemaster::clear_pattern_mutes () { int count = cb_perf().mutegroup_count(); /* always 32 */ m_pattern_mutes.clear(); /* midibooleans */ if (count > 0) m_pattern_mutes.resize(count); m_pattern_offset = 0; } void qmutemaster::setup_table () { QStringList columns; columns << "Group" << "Active" << "Key" << "Group Name"; /* * This advice is bs. The actual answer is to set the horizontal header to be * visible. * * ui->m_group_table->insertColumn(0); * ui->m_group_table->insertColumn(1); * ui->m_group_table->insertColumn(2); * ui->m_group_table->insertColumn(3); */ ui->m_group_table->setHorizontalHeaderLabels(columns); ui->m_group_table->setSelectionBehavior(QAbstractItemView::SelectRows); int w = ui->m_group_table->width(); set_column_widths(w - c_table_fix); const int rows = ui->m_group_table->rowCount(); for (int r = 0; r < rows; ++r) ui->m_group_table->setRowHeight(r, c_table_row_height); connect ( ui->m_group_table, SIGNAL(currentCellChanged(int, int, int, int)), this, SLOT(slot_table_click(int, int, int, int)) ); connect ( ui->m_group_table, SIGNAL(cellChanged(int, int)), this, SLOT(slot_cell_changed(int, int)) ); } /** * Currently, the only cell that can be changed is the "Group Name". * This can modify the '.mutes' file, the MIDI file, or both. * * Also need to refresh the save setting in Edit / Preferences / Session * if active (or all the time?) */ void qmutemaster::slot_cell_changed (int row, int column) { column_id cid = static_cast(column); if (cid == column_id::group_name) { mutegroup::number m = mutegroup::number(row); QTableWidgetItem * c = cell(m, cid); QString qtext = c->text(); std::string name = qtext.toStdString(); if (cb_perf().group_name(m, name)) modify_mutes(); } } /** * Scales the columns against the provided window width. The width factors * should add up to 1. However, we make the name column narrower. */ void qmutemaster::set_column_widths (int total_width) { ui->m_group_table->setColumnWidth(0, int(0.150f * total_width)); ui->m_group_table->setColumnWidth(1, int(0.150f * total_width)); ui->m_group_table->setColumnWidth(2, int(0.150f * total_width)); ui->m_group_table->setColumnWidth(3, int(0.55f * total_width)); } /** * Updated to not disabled the last group button when the next group button * is clicked, when in trigger mode. */ bool qmutemaster::set_current_group (int group) { bool result = group != current_group(); if (result) result = group >= 0 && group < cb_perf().mutegroup_count(); if (result) { int lastgroup = current_group(); int gridrow, gridcolumn; if (mutegroups::group_to_grid(group, gridrow, gridcolumn)) { m_button_row = gridrow; m_button_column = gridcolumn; m_current_group = group; QPushButton * temp = m_group_buttons[group]; temp->setEnabled(true); temp->setChecked(false); if (lastgroup != seq::unassigned()) { temp = m_group_buttons[lastgroup]; if (! trigger()) temp->setEnabled(false); temp->setChecked(false); } } } return result; } bool qmutemaster::initialize_table () { int rows = cb_perf().mutegroup_count(); bool result = rows > 0; ui->m_group_table->clearContents(); if (result) { for (int row = 0; row < rows; ++row) { mutegroup::number g = mutegroup::number(row); int mutecount = cb_perf().count_mutes(g); std::string keyname = cb_perf().lookup_mute_key(row); std::string groupname = cb_perf().group_name(row); (void) group_line(g, mutecount, keyname, groupname); } ui->m_group_table->clearSelection(); } return result; } /** * Retrieve the table cell at the given row and column. * * \param row * The row number, which should be in the range of 0 to 32. * * \param col * The column enumeration value, which indicates one of these four * columns: number, count, keyname, and name. * * \return * Returns a pointer the table widget-item for the given row and column. * If out-of-range, a null pointer is returned. */ QTableWidgetItem * qmutemaster::cell (mutegroup::number row, column_id col) { int column = int(col); QTableWidgetItem * result = ui->m_group_table->item(row, column); if (is_nullptr(result)) { result = new (std::nothrow) QTableWidgetItem; if (not_nullptr(result)) { ui->m_group_table->setItem(row, column, result); if (col != column_id::group_name) result->setFlags(result->flags() ^ Qt::ItemIsEditable); } } return result; } /** * Obtains the group number (row), number of active (arming) items in the * mute-group, and the keystroke used to access that mute-group. These are * then set into the cells of the corresponding column. */ bool qmutemaster::group_line ( mutegroup::number row, int mutecount, const std::string & keyname, const std::string & groupname ) { bool result = false; QTableWidgetItem * qtip = cell(row, column_id::group_number); if (not_nullptr(qtip)) { std::string groupnostr = std::to_string(int(row)); qtip->setText(qt(groupnostr)); qtip = cell(row, column_id::group_count); if (not_nullptr(qtip)) { std::string gcountstr = std::to_string(mutecount); qtip->setText(qt(gcountstr)); qtip = cell(row, column_id::group_keyname); if (not_nullptr(qtip)) { qtip->setText(qt(keyname)); qtip = cell(row, column_id::group_name); if (not_nullptr(qtip)) { qtip->setText(qt(groupname)); result = true; } } } } return result; } /** * Handles a click in the table that lists the mute groups. */ void qmutemaster::slot_table_click ( int row, int /*column*/, int /*prevrow*/, int /*prevcolumn*/ ) { int rows = cb_perf().mutegroup_count(); /* always 32 */ if (rows > 0) { if (row >= 0 && row < rows) { if (set_current_group(row)) { ui->m_button_trigger->setEnabled(true); ui->m_button_set_mutes->setEnabled(false); #if defined USE_REMOVED_MUTEMASTER_BUTTONS ui->m_button_down->setEnabled(true); ui->m_button_up->setEnabled(true); #endif group_needs_update(); } } } } void qmutemaster::closeEvent (QCloseEvent * event) { cb_perf().unregister(this); /* unregister this immediately */ /* * ca 2026-04-23 Let's stop the timer first. */ if (not_nullptr(m_timer)) m_timer->stop(); event->accept(); } /** * Creates a grid of buttons in the group/set grid layout. This grid is * always 4 x 8, as discussed in the setmapper::grid_to_group() function, but * if a smaller set number (count) is used, some buttons will be unlabelled * and disabled. * * Note that the largest number of mute-groups is 4 x 8 = 32. This * limitation is practically necessary because there are only so many * available keys on the keyboard for pattern, mute-group, and set control. * * Done at construction time. */ void qmutemaster::create_group_buttons () { const QSize btnsize = QSize(c_button_size, c_button_size); for (int group = 0; group < mutegroups::Size(); ++group) { int row, column; if (mutegroups::group_to_grid(group, row, column)) { std::string gstring = std::to_string(group); QPushButton * temp = new QPushButton(qt(gstring)); temp->setFixedSize(btnsize); ui->setGridLayout->addWidget(temp, row, column); connect /* connect lambda function */ ( temp, &QPushButton::released, [=] { handle_group_button(row, column); } ); temp->show(); temp->setCheckable(true); temp->setEnabled(false); m_group_buttons[group] = temp; } } } /** * Updates the top buttons that indicate the mute-groups present during this * run of the current MIDI file. */ void qmutemaster::update_group_buttons (enabling tomodify) { midibooleans groups = cb_perf().get_active_groups(); for (int group = 0; group < mutegroups::Size(); ++group) { QPushButton * temp = m_group_buttons[group]; temp->setChecked(bool(groups[group])); if (tomodify != enabling::leave) temp->setEnabled(tomodify == enabling::enable); } } void qmutemaster::set_bin_hex (bool bin_checked) { if (bin_checked) { ui->m_radio_binary->setChecked(true); ui->m_radio_hex->setChecked(false); } else { ui->m_radio_binary->setChecked(false); ui->m_radio_hex->setChecked(true); } } #if defined USE_MUTES_FILE_TEXTEDIT void qmutemaster::slot_mutes_file_modify () { modify_mutes_file(true); } #endif void qmutemaster::slot_bin_mode (bool ischecked) { cb_perf().group_format_hex(! ischecked); set_bin_hex(ischecked); modify_mutes_file(true); } void qmutemaster::slot_hex_mode (bool ischecked) { cb_perf().group_format_hex(ischecked); set_bin_hex(! ischecked); modify_mutes_file(true); } void qmutemaster::slot_trigger () { trigger(! trigger()); if (trigger()) { update_group_buttons(enabling::enable); update_pattern_buttons(enabling::disable); } else { int row = m_button_row; int column = m_button_column; mutegroup::number group = mutegroups::grid_to_group(row, column); QPushButton * temp = m_group_buttons[group]; update_group_buttons(enabling::disable); update_pattern_buttons(enabling::disable); temp->setEnabled(true); } } #if defined USE_GROUP_UPDATE_BUTTON /** * The calls to set mutes: Fills midibooleans bit and calls performer :: * set_mutes(), with a parameter of true so that the mutes are also copied to * rcsettings for when the user of the Mutes tab wants to save the file. */ void qmutemaster::slot_set_mutes () { midibooleans bits = m_pattern_mutes; bool ok = cb_perf().set_mutes(current_group(), bits, true); if (ok) { ui->m_button_save->setEnabled(true); ui->m_button_set_mutes->setEnabled(false); } else { // TODO show the error } } #endif /** * This looks goofy, but we offload the dialog handling to qsmainwnd, which * has the boolean function qsmainwnd::open_mutes_dialog(), which returns * true if the user clicked OK and the call to qmutemaster::load_mutegroups() * succeeded. Circular! * * ca 2023-10-01 TODO: copy to the m_mute_basename QPlainTextEdit * and also update the .mutes name in Edit / Preferences / Session. */ void qmutemaster::slot_load () { if (not_nullptr(m_main_window)) { if (m_main_window->open_mutes_dialog()) /* calls load_mutegroups() */ { /* * Some of this also done in constructor. */ QString mgfname = qt(rc().mute_group_filename()); bool mutesactive = rc().mute_group_file_active(); bool groupload = cb_perf().group_load_from_mutes(); ui->m_mute_basename->setPlainText(mgfname); ui->m_check_mutes_active->setChecked(mutesactive); ui->m_check_from_mutes->setChecked(groupload); unmodify_mutes(); } } } /** * Called in qsmainwnd. See above. */ bool qmutemaster::load_mutegroups (const std::string & mutefile) { bool result = cb_perf().open_mutegroups(mutefile); if (result) file_message("Open mute-groups", mutefile); else file_error("Open failed", mutefile); return result; } /** * Saves to the specified 'mutes' file, even if not checked or not * active. Note that ui->m_mute_basename is read-only. */ void qmutemaster::slot_save () { if (not_nullptr(m_main_window)) { std::string fname = rc().mute_group_filename(); if (fname.empty()) { /* * Should we modify rc? */ } else { if (name_has_path(fname)) { std::string directory; std::string basename; bool ok = filename_split(fname, directory, basename); if (ok) fname = basename; } } if (m_main_window->save_mutes_dialog(rc().mute_group_filespec())) { /* * fname is the previous name; the function above saves * the new name. * * rc().mute_group_filename(fname); */ modify_mutes_file(false); if (fname != rc().mute_group_filespec()) modify_rc(); QString mgfname = qt(rc().mute_group_filename()); ui->m_mute_basename->setPlainText(mgfname); } } } /** * Currently not in use! See slot_save() instead. */ bool qmutemaster::save_mutegroups (const std::string & mutefile) { bool result = cb_perf().save_mutegroups(mutefile); if (result) { file_message("Wrote mute-groups", mutefile); unmodify_mutes(); /* ui->m_button_save->setEnabled(false) */ } else file_message("Write failed", mutefile); return result; } /** * This function handles a change in the "To MIDI" check-box. */ void qmutemaster::slot_write_to_midi () { bool midichecked = ui->m_check_to_midi->isChecked(); bool muteschecked = ui->m_check_to_mutes->isChecked(); m_to_midi_active = midichecked; m_to_mutes_active = muteschecked; if (cb_perf().group_save(midichecked, muteschecked)) modify_mutes_file(true); } /** * This function handles a change in the "To Mutes" check-box. * * Do we want to set this here? The user should use the Session tab in * Preferences to set this, IMHO. * * bool mgfactive = rc().mute_group_file_active(); */ void qmutemaster::slot_write_to_mutes () { bool midichecked = ui->m_check_to_midi->isChecked(); bool muteschecked = ui->m_check_to_mutes->isChecked(); m_to_midi_active = midichecked; m_to_mutes_active = muteschecked; if (cb_perf().group_save(midichecked, muteschecked)) modify_mutes_file(true); } void qmutemaster::slot_strip_empty () { bool ischecked = ui->m_check_strip_empty->isChecked(); if (cb_perf().strip_empty(ischecked)) modify_mutes_file(true); } /** * This function handles a change in the "From Mutes" check-box. * * Do we want to set this here? The user should use the Session tab in * Preferences to set this, IMHO. * * bool mgfactive = rc().mute_group_file_active(); */ void qmutemaster::slot_load_mutes () { bool muteschecked = ui->m_check_from_mutes->isChecked(); bool midichecked = ui->m_check_from_midi->isChecked(); if (cb_perf().load_mute_groups(midichecked, muteschecked)) { /* * Too much: modify_mutes() can also modify 'rc' and the song. */ if (m_is_initialized) { ui->m_button_set_mutes->setText("*"); ui->m_button_save->setEnabled(true); } } } /** * This function handles a change in the "From MIDI" check-box. */ void qmutemaster::slot_load_midi () { slot_load_mutes(); } void qmutemaster::slot_toggle_active () { bool ischecked = ui->m_check_toggle_active->isChecked(); cb_perf().toggle_active_only(ischecked); modify_mutes_file(true); } /** * This function handles one of the group-selection buttons in the grid of * "Mute-Groups" buttons. */ void qmutemaster::handle_group_button (int row, int column) { mutegroup::number group = mutegroups::grid_to_group(row, column); QPushButton * button = m_group_buttons[group]; bool checked = button->isChecked(); if (trigger()) /* we are in trigger mode */ { if (checked) /* ignore the button */ { button->setChecked(false); /* keep it unchecked */ } else /* turn on the mute-group */ { mutegroup::number m = mutegroups::grid_to_group(row, column); (void) cb_perf().toggle_mutes(m); button->setChecked(true); /* keep it checked */ ui->m_group_table->selectRow(m); } } else { if (checked) /* was inactive, now active */ update_pattern_buttons(enabling::enable); else /* was active, now inactive */ update_pattern_buttons(enabling::disable); ui->m_button_set_mutes->setEnabled(true); } } void qmutemaster::handle_group_change (int groupno) { if (groupno != m_current_group) { set_current_group(groupno); update_group_buttons(); update_pattern_buttons(); if (cb_perf().mutegroup_count() > 0) ui->m_group_table->selectRow(0); } } /** * Handles mute-group changes from other dialogs. These changes involve only * the adding/subtracting of patterns to an old or a new group. * * Don't think we need to call modify_mutes() here.... But it gets modified * anyway by initialize table. That should not be done unless it is a real * change. */ bool qmutemaster::on_mutes_change (mutegroup::number group, performer::change mod) { bool result = ! mutegroup::none(group); if (result) { bool ok = mod != performer::change::max; if (ok) { if (mod == performer::change::yes) /* ca 2023-11-07 */ result = initialize_table(); if (result) { update_group_buttons(enabling::enable); group_needs_update(); } } } return result; } void qmutemaster::reload_mute_groups () { bool ok = initialize_table(); if (ok) { update_group_buttons(enabling::enable); group_needs_update(); } } /* * We must accept() the key-event, otherwise even key-events in the QLineEdit * items are propagated to the parent, where they then get passed to the * performer as if they were keyboards controls (such as a pattern-toggle * hot-key). * * Plus, here, we have no real purpose for the code, so we macro it out. * What's up with that, Spanky? */ void qmutemaster::keyPressEvent (QKeyEvent * event) { #if defined PASS_KEYSTROKES_TO_PARENT keystroke kkey = qt_keystroke(event, keystroke::action::press); bool done = handle_key_press(kkey); if (done) group_needs_update(); else QWidget::keyPressEvent(event); #else event->accept(); #endif } void qmutemaster::keyReleaseEvent (QKeyEvent * event) { #if defined PASS_KEYSTROKES_TO_PARENT keystroke kkey = qt_keystroke(event, keystroke::action::release); bool done = handle_key_release(kkey); if (done) update(); else QWidget::keyReleaseEvent(event); #else event->accept(); #endif } #if defined PASS_KEYSTROKES_TO_PARENT bool qmutemaster::handle_key_press (const keystroke & k) { ctrlkey ordinal = k.key(); const keycontrol & kc = cb_perf().key_controls().control(ordinal); bool result = kc.is_usable(); if (result) { // no code } return result; } bool qmutemaster::handle_key_release (const keystroke & k) { bool done = cb_perf().midi_control_keystroke(k); if (! done) { // no code } return done; } #endif // defined PASS_KEYSTROKES_TO_PARENT /** * This is not called when focus changes. */ void qmutemaster::changeEvent (QEvent * event) { QWidget::changeEvent(event); if (event->type() == QEvent::ActivationChange) { // no code } } bool qmutemaster::group_control ( automation::action a, int /*d0*/, int index, bool inverse ) { bool result = a == automation::action::toggle; if (result && ! inverse) handle_group_change(index); return result; } /* * Section for controlling the pattern grid. */ /** * Creates a grid of buttons in the pattern grid layout. This grid is * currently 4 x 8, but might eventually approach 12 x 8. We shall see. * Activating a button adds that pattern to the currently-selected * mute-group. * * Done at construction time. */ void qmutemaster::create_pattern_buttons () { const QSize btnsize = QSize(c_button_size, c_button_size); int tracks = cb_perf().screenset_size(); for (int t = 0; t < tracks; ++t) { /* * Issue #87. This is the wrong mapper, it is a set mapper, not * a seqence mapper: * * if (cb_perf().master_index_to_grid(t, row, column)) */ int row, column; if (cb_perf().seq_to_grid(t, row, column)) { std::string gstring = std::to_string(t); QPushButton * temp = new QPushButton(qt(gstring)); ui->patternGridLayout->addWidget(temp, row, column); temp->setFixedSize(btnsize); connect /* connect lambda function */ ( temp, &QPushButton::released, [=] { handle_pattern_button(row, column); } ); temp->show(); temp->setCheckable(true); temp->setEnabled(false); m_pattern_buttons[t] = temp; } } } /** * Updates the bottom buttons that indicate the mute-groups present during * this run of the current MIDI file. */ void qmutemaster::update_pattern_buttons (enabling tomodify) { midibooleans mutes = cb_perf().get_mutes(current_group()); if (! mutes.empty()) { m_pattern_mutes = mutes; int tracks = cb_perf().screenset_size(); if (size_t(tracks) > m_pattern_buttons.size()) { file_message("too many tracks to mute", std::to_string(tracks)); tracks = int(m_pattern_buttons.size()); } for (int t = 0; t < tracks; ++t) { QPushButton * temp = m_pattern_buttons[t]; temp->setChecked(bool(mutes[t])); if (tomodify != enabling::leave) temp->setEnabled(tomodify == enabling::enable); } } } /** * This function handles one of the pattern buttons in the grid of pattern * buttons. All it does is change the status of the appropriate bit in the * midibooleans pattern vector. */ void qmutemaster::handle_pattern_button (int row, int column) { seq::number s = cb_perf().grid_to_seq(row, column); QPushButton * temp = m_pattern_buttons[s]; bool bitisset = bool(m_pattern_mutes[s]); bool enabled = temp->isChecked(); if (bitisset != enabled) { m_pattern_mutes[s] = midibool(enabled); #if defined USE_GROUP_UPDATE_BUTTON ui->m_button_set_mutes->setEnabled(true); modify_mutes_file(true); #else midibooleans bits = m_pattern_mutes; bool ok = cb_perf().set_mutes(current_group(), bits, true); if (ok) { ui->m_button_save->setEnabled(true); if (m_to_mutes_active) modify_mutes_file(true); if (m_to_midi_active) modify_midi(); } #endif } } /** * For MIDI save only. */ void qmutemaster::modify_midi () { if (m_is_initialized) cb_perf().modify(); if (not_nullptr(m_main_window)) m_main_window->enable_save(true); } /** * Also see mute_file_change(). * * This function applies to.... Not sure yet, since we no longer allow * the base 'mutes' file-name to be changed here. */ void qmutemaster::modify_rc () { if (m_is_initialized) { rc().auto_rc_save(true); rc().modify(); } } /** * Also see modify_mutes_file(). * * This function applies to changes that affect mutes independent of a * 'mutes' file. */ void qmutemaster::modify_mutes () { group_needs_update(); if (m_is_initialized) { ui->m_button_set_mutes->setText("*"); ui->m_button_save->setEnabled(true); if (m_to_mutes_active) { rc().auto_mutes_save(true); modify_mutes_file(true); } if (m_to_midi_active) modify_midi(); } } /** * Can't unmodify the tune or the 'mutes' file here. */ void qmutemaster::unmodify_mutes () { group_needs_update(); ui->m_button_set_mutes->setText("-"); ui->m_button_save->setEnabled(false); rc().auto_mutes_save(false); } /** * Calls this whenever a mutes file is saved or loaded, no matter if the * "To Mutes" option is active or not. * * \param flag * If true, the 'mutes' file has been loaded, and we have to assume * that they have been modified. If false, the 'mutes' file has been * saved. */ void qmutemaster::modify_mutes_file (bool flag) { if (flag) { modify_mutes(); /* rc().auto_mutes_save(true) */ } else { unmodify_mutes(); rc().auto_mutes_save(false); } } } // namespace seq66 /* * qmutemaster.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qpatternfix.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qpatternfix.cpp * * This module declares/defines the base class for the pattern-fix window. * * \library seq66 application * \author Chris Ahlstrom * \date 2022-04-09 * \updates 2025-06-14 * \license GNU GPLv2 or above * * This dialog provides a way to combine the following pattern adjustments: * * - Left-alignment. * - Fitting to a given number of measures. * - Arbitrary scaling for compression and expansion over time. * - Quantization or tightening. * * It acts on all events in the track; no selection needed. In this, it is * similar to the LFO dialog. * * This dialog was inspired by Ahlstrom's poor playing and timing skills. */ #include #include "seq66-config.h" /* defines SEQ66_QMAKE_RULES */ #include "play/performer.hpp" /* seq66::performer class */ #include "qpatternfix.hpp" /* seq66::qpatternfix class */ #include "qseqdata.hpp" /* seq66::qseqdata for status, CC */ #include "qstriggereditor.hpp" /* seq66::qstriggereditor class */ #include "qseqeditframe64.hpp" /* seq66::qseqeditframe64, parent */ #include "qt5_helper.h" /* QT5_HELPER_RADIO_SIGNAL macro */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ #include "util/strfunctions.hpp" /* seq66::string_to_double() */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qpatternfix.h" #else #include "forms/qpatternfix.ui.h" #endif /* * Don't document the namespace. */ namespace seq66 { /* * Signal buttonClicked(int) is overloaded in this class. To connect to this * signal by using the function pointer syntax, Qt provides a convenient * helper for obtaining the function pointer as used below, using a lambda * function. */ qpatternfix::qpatternfix ( performer & p, sequence & s, qseqeditframe64 * editparent, QWidget * parent ) : QFrame (parent), ui (new Ui::qpatternfix), m_fixlength_group (nullptr), m_alt_group (nullptr), m_performer (p), m_seq (s), /* track() accessor */ m_backup_events (s.events()), /* for slot_reset() */ m_backup_measures (s.get_measures()), m_backup_beats (s.get_beats_per_bar()), m_backup_width (s.get_beat_width()), m_edit_frame (editparent), m_length_type (lengthfix::none), /* lengthfix fp_fixtype */ m_alt_type (alteration::none), /* alteration fp_... */ m_tighten_range (s.snap() / 2), m_full_range (s.snap()), m_random_range (usr().randomization_amount()), m_pitch_range (usr().randomization_amount()), m_jitter_range (usr().jitter_range(s.get_ppqn() / 4)), /* fp_... */ m_notemap_file (rc().notemap_filename()), m_reverse_notemap (false), m_measures (double(m_backup_measures)), /* fp_measures */ m_scale_factor (1.0), /* fp_scale_factor */ m_align_left (false), /* bool fp_align_left */ m_align_right (false), /* bool fp_align_right */ m_reverse (false), /* bool fp_reverse */ m_reverse_in_place (false), /* bool fp_reverse_... */ m_save_note_length (false), /* bool fp_save_note... */ m_use_time_sig (false), /* bool fp_use_time_... */ m_time_sig_beats (s.get_beats_per_bar()), /* int fp_beats_per_bar */ m_time_sig_width (s.get_beat_width()), /* int fp_beat_width */ m_is_modified (false), m_was_clean (! s.modified()) { ui->setupUi(this); initialize(true); setFixedSize(width(), height()); } /** * Deletes the user-interface object. */ qpatternfix::~qpatternfix() { delete ui; } void qpatternfix::initialize (bool startup) { std::string value = std::to_string(int(track().get_length())); ui->label_pulses->setText(qt(value)); value = std::to_string(int(m_measures)); ui->line_edit_measures->setText(qt(value)); value = std::to_string(1); /* actually a float */ ui->line_edit_scale->setText(qt(value)); ui->btn_effect_alteration->setEnabled(false); ui->btn_effect_shift->setEnabled(false); ui->btn_effect_reverse->setEnabled(false); ui->btn_effect_shrink->setEnabled(false); ui->btn_effect_expand->setEnabled(false); ui->btn_effect_time_sig->setEnabled(false); /* * ui->btn_effect_alteration->setChecked(false); * ui->btn_effect_shift->setChecked(false); * ui->btn_effect_reverse->setChecked(false); * ui->btn_effect_shrink->setChecked(false); * ui->btn_effect_expand->setChecked(false); * ui->btn_effect_time_sig->setChecked(false); */ slot_effect_clear(); ui->btn_align_left->setChecked(false); ui->btn_align_right->setChecked(false); ui->btn_reverse->setChecked(false); ui->btn_reverse_in_place->setChecked(false); ui->btn_save_note_length->setChecked(false); ui->btn_set->setEnabled(false); ui->btn_reset->setEnabled(false); if (startup) { /* * Read-only labelling. Pattern number and pattern length. */ std::string value = std::to_string(int(track().seq_number())); ui->label_pattern->setText(qt(value)); std::string plabel = "Pattern #"; plabel += value; setWindowTitle(qt(plabel)); /* * Length (Measures or Scaling) Change. */ m_fixlength_group = new QButtonGroup(this); m_fixlength_group->addButton(ui->btn_change_none, cast(lengthfix::none)); m_fixlength_group->addButton ( ui->btn_change_pick, cast(lengthfix::measures) ); m_fixlength_group->addButton ( ui->btn_change_scale, cast(lengthfix::rescale) ); ui->btn_change_none->setChecked(true); connect ( m_fixlength_group, QT5_HELPER_RADIO_SIGNAL, [=](int id) { slot_length_fix(id); } /* lambda function */ ); connect ( ui->line_edit_measures, SIGNAL(editingFinished()), this, SLOT(slot_measure_change()) ); connect ( ui->line_edit_scale, SIGNAL(editingFinished()), this, SLOT(slot_scale_change()) ); ui->line_edit_none->hide(); /* unused, hide it */ /* * Alteration. None, tighten, quantize, jitter, random, and notemap, * all mutually exclusive. */ m_alt_group = new QButtonGroup(this); ui->group_box_quantize->setEnabled(true); /* * None */ m_alt_group->addButton(ui->btn_alt_none, cast(alteration::none)); /* * Tighten */ m_alt_group->addButton ( ui->btn_alt_tighten, cast(alteration::tighten) ); /* * Quantize */ m_alt_group->addButton(ui->btn_alt_full, cast(alteration::quantize)); /* * Jitter */ m_alt_group->addButton(ui->btn_alt_jitter, cast(alteration::jitter)); /* * Random */ m_alt_group->addButton(ui->btn_alt_random, cast(alteration::random)); /* * Random Note Pitch */ m_alt_group->addButton ( ui->btn_alt_random_pitch, cast(alteration::random_pitch) ); /* * Note-map and Reverse Note-map */ m_alt_group->addButton(ui->btn_alt_notemap, cast(alteration::notemap)); m_alt_group->addButton ( ui->btn_alt_rev_notemap, cast(alteration::rev_notemap) ); /* * Enable, disable, or hide the corresponding group items. * Currently the jitter edit value is used. * * ui->line_edit_alt_jitter->hide(); */ ui->btn_alt_none->setChecked(true); ui->line_edit_alt_none->hide(); /* reveal once code in place */ connect ( m_alt_group, QT5_HELPER_RADIO_SIGNAL, [=](int id) { slot_alt_change(id); } /* lambda function */ ); /* * TODO: Add similar slots for Tighten, Full (quantization), * Random, and maybe a button to load a note-map file. * * We want to be able to change from the snap values for quantization. */ value = std::to_string(m_tighten_range); ui->line_edit_alt_tighten->setText(qt(value)); connect ( ui->line_edit_alt_tighten, SIGNAL(editingFinished()), this, SLOT(slot_tighten_change()) ); value = std::to_string(m_full_range); ui->line_edit_alt_full->setText(qt(value)); connect ( ui->line_edit_alt_full, SIGNAL(editingFinished()), this, SLOT(slot_full_change()) ); value = std::to_string(m_random_range); ui->line_edit_alt_random->setText(qt(value)); connect ( ui->line_edit_alt_random, SIGNAL(editingFinished()), this, SLOT(slot_random_change()) ); value = std::to_string(m_pitch_range); ui->line_edit_alt_random_pitch->setText(qt(value)); connect ( ui->line_edit_alt_random_pitch, SIGNAL(editingFinished()), this, SLOT(slot_random_pitch_change()) ); value = std::to_string(m_jitter_range); ui->line_edit_alt_jitter->setText(qt(value)); connect ( ui->line_edit_alt_jitter, SIGNAL(editingFinished()), this, SLOT(slot_jitter_change()) ); connect ( ui->btn_notemap_file, SIGNAL(clicked()), this, SLOT(slot_notemap_file()) ); /* * The "read-only" Effect group. The slot here merely keeps them from * being checked by the user. If we make them readonly the text is * difficult to read in many Qt themes. */ connect ( ui->btn_effect_alteration, SIGNAL(clicked()), this, SLOT(slot_effect_clear()) ); connect ( ui->btn_effect_shift, SIGNAL(clicked()), this, SLOT(slot_effect_clear()) ); connect ( ui->btn_effect_reverse, SIGNAL(clicked()), this, SLOT(slot_effect_clear()) ); connect ( ui->btn_effect_shrink, SIGNAL(clicked()), this, SLOT(slot_effect_clear()) ); connect ( ui->btn_effect_expand, SIGNAL(clicked()), this, SLOT(slot_effect_clear()) ); connect ( ui->btn_effect_time_sig, SIGNAL(clicked()), this, SLOT(slot_effect_clear()) ); /* * Other Fixes. */ ui->group_box_effect->setEnabled(true); ui->group_box_other->setEnabled(true); connect ( ui->btn_align_left, SIGNAL(stateChanged(int)), this, SLOT(slot_align_left_change(int)) ); connect ( ui->btn_align_right, SIGNAL(stateChanged(int)), this, SLOT(slot_align_right_change(int)) ); connect ( ui->btn_reverse, SIGNAL(stateChanged(int)), this, SLOT(slot_reverse_change(int)) ); connect ( ui->btn_reverse_in_place, SIGNAL(stateChanged(int)), this, SLOT(slot_reverse_in_place(int)) ); connect ( ui->btn_save_note_length, SIGNAL(stateChanged(int)), this, SLOT(slot_save_note_length(int)) ); /* * Bottom buttons. */ ui->btn_close->setEnabled(true); connect(ui->btn_set, SIGNAL(clicked()), this, SLOT(slot_set())); connect(ui->btn_reset, SIGNAL(clicked()), this, SLOT(slot_reset())); connect(ui->btn_close, SIGNAL(clicked()), this, SLOT(close())); } else { /* * Grouped buttons. */ ui->btn_change_none->setChecked(true); ui->btn_alt_none->setChecked(true); } } void qpatternfix::modify () { ui->btn_set->setEnabled(true); ui->btn_reset->setEnabled(true); m_is_modified = true; } /** * All this does is ... seems a bit useless. * * because we don't want to force the user to have to modify anything to * Set the values again. */ void qpatternfix::unmodify (bool reset_fields) { if (reset_fields) { ui->btn_reset->setEnabled(false); std::string temp = std::to_string(track().get_measures()); ui->line_edit_measures->setText(qt(temp)); ui->line_edit_scale->setText("1.0"); /* * ui->btn_effect_shift->setChecked(false); * ui->btn_effect_shrink->setChecked(false); * ui->btn_effect_expand->setChecked(false); */ slot_effect_clear(); } m_is_modified = false; } void qpatternfix::slot_effect_clear () { ui->btn_effect_alteration->setChecked(false); ui->btn_effect_shift->setChecked(false); ui->btn_effect_reverse->setChecked(false); ui->btn_effect_shrink->setChecked(false); ui->btn_effect_expand->setChecked(false); ui->btn_effect_time_sig->setChecked(false); } void qpatternfix::slot_length_fix (int fixlengthid) { m_length_type = lengthfix_cast(fixlengthid); if (m_length_type != lengthfix::none) modify(); } /** * The user can enter either an integer measure count to set the measures * directly, or a fraction of the form x/y to scale the number of measures. * It sets the lengthfix::measures item rather than lengthfix::rescale. * * Tricky: If setting integer measures, the end effect is scaling, not * truncating. In fact, the only difference between measure and scale-factor * changes is minimal. * * Tricky: A fractional measures value, such as "3/4", is converted to a * decimal number (here, "0.75") by string_to_double(). */ void qpatternfix::slot_measure_change () { QString t = ui->line_edit_measures->text(); std::string tc = t.toStdString(); double m = string_to_double(tc, 4.0, 4); /* measures is a float here */ if (sequence::valid_scale_factor(m, true)) /* applies to measures, too */ { int beats = m_time_sig_beats; int width = m_time_sig_width; bool is_fraction = string_to_time_signature(tc, beats, width); bool different = fnotequal(m, m_measures); if (different || is_fraction) { bool floater = is_floating_string(tc); m_length_type = floater ? lengthfix::rescale : lengthfix::measures; ui->btn_change_pick->setChecked(true); m_use_time_sig = is_fraction; if (is_fraction) { std::string ts = time_signature_string ( m_time_sig_beats, m_time_sig_width ); double oldts = string_to_double(ts, 1.0, 2.0); /* m = new */ m_scale_factor = m / oldts; /* * For altering a pattern via a new time signature, * we don't want to alter the measure in the pattern; * it will be pure scaling up and down of the notes. */ if (! is_fraction) m_measures = trunc_measures(m_measures / m_scale_factor); } else { m_scale_factor = m / m_measures; m_measures = trunc_measures(m); /* float truncation */ } if (beats > 0 && beats < 96) /* just a sanity check */ m_time_sig_beats = beats; /* fraction numerator */ if (width > 0 && width < 96) /* just a sanity check */ m_time_sig_width = width; /* fraction denominator */ /* * More reliable is to use the scale factor, since measures * get rounded up and match the current pattern length. * * m = m_measures; * * midipulse curlength = track().get_length(); * midipulse newlength = midipulse(track().unit_measure() * m); * bool shrunk = newlength < curlength; * bool expanded = newlength > curlength; */ bool shrunk = flessthan(m_scale_factor, 1.0); bool expanded = fgreaterthan(m_scale_factor, 1.0); ui->btn_effect_shrink->setChecked(shrunk); ui->btn_effect_expand->setChecked(expanded); ui->btn_effect_time_sig->setChecked(is_fraction); std::string scale = double_to_string(m_scale_factor, 2); std::string meass = double_to_string(m_measures); ui->line_edit_scale->setText(qt(scale)); ui->line_edit_measures->setText(qt(meass)); modify(); /* indicate fix-change */ } } else { int beats, width; bool is_time_sig = string_to_time_signature(tc, beats, width); ui->btn_effect_time_sig->setChecked(is_time_sig); } } void qpatternfix::slot_scale_change () { QString t = ui->line_edit_scale->text(); std::string tc = t.toStdString(); double v = string_to_double(tc, 1.0); if (sequence::valid_scale_factor(v) && v != m_scale_factor) { ui->btn_change_scale->setChecked(true); m_scale_factor = v; m_measures = trunc_measures(m_measures * v); m_length_type = lengthfix::rescale; /* * Why do this? * * m_time_sig_beats = m_time_sig_width = 0; * m_use_time_sig = false; */ ui->btn_effect_time_sig->setChecked(false); bool shrunk = flessthan(m_scale_factor, 1.0); bool expanded = fgreaterthan(m_scale_factor, 1.0); ui->btn_effect_shrink->setChecked(shrunk); ui->btn_effect_expand->setChecked(expanded); std::string scale = double_to_string(m_scale_factor, 2); std::string meass = double_to_string(m_measures); ui->line_edit_scale->setText(qt(scale)); ui->line_edit_measures->setText(qt(meass)); modify(); } } void qpatternfix::slot_alt_change (int quanid) { alteration quantype = quantization_cast(quanid); if (m_alt_type != quantype) { bool not_none = quantype != alteration::none; m_alt_type = quantype; ui->btn_effect_alteration->setChecked(not_none); modify(); } } void qpatternfix::slot_tighten_change () { QString t = ui->line_edit_alt_tighten->text(); std::string tc = t.toStdString(); int m = string_to_int(tc, 0); if (m > 0 && m < track().get_ppqn()) /* sanity check */ { ui->btn_alt_tighten->setChecked(true); m_tighten_range = m; m_alt_type = alteration::tighten; modify(); } } void qpatternfix::slot_full_change () { QString t = ui->line_edit_alt_full->text(); std::string tc = t.toStdString(); int m = string_to_int(tc, 0); if (m > 0 && m < track().get_ppqn()) /* sanity check */ { ui->btn_alt_full->setChecked(true); m_full_range = m; m_alt_type = alteration::quantize; modify(); } } void qpatternfix::slot_random_change () { QString t = ui->line_edit_alt_random->text(); std::string tc = t.toStdString(); int m = string_to_int(tc, 0); if (m > 0 && m < 9999) /* sanity check */ { ui->btn_alt_random->setChecked(true); m_random_range = m; m_alt_type = alteration::random; modify(); } } void qpatternfix::slot_random_pitch_change () { QString t = ui->line_edit_alt_random_pitch->text(); std::string tc = t.toStdString(); int m = string_to_int(tc, 0); if (m > 0 && m < c_notes_count) { ui->btn_alt_random_pitch->setChecked(true); m_pitch_range = m; m_alt_type = alteration::random_pitch; modify(); } } void qpatternfix::slot_jitter_change () { QString t = ui->line_edit_alt_jitter->text(); std::string tc = t.toStdString(); int m = string_to_int(tc, 0); if (m > 0 && m < track().get_ppqn()) /* sanity check */ { ui->btn_alt_jitter->setChecked(true); m_jitter_range = m; m_alt_type = alteration::jitter; modify(); } } /** * Here, we need a dialog to optionally select a different note-map file. */ void qpatternfix::slot_notemap_file () { bool ok = show_file_dialog ( this, m_notemap_file, "Select a note-map/drums file", "Map files (*.drums *.notemap);;Drums (*.drums);;" "Note maps (*.notemap);;All files (*)", OpeningFile, NormalFile /* , "*.drums" */ ); if (ok) printf("Current note-map file: '%s'\n", m_notemap_file.c_str()); } void qpatternfix::slot_align_left_change (int state) { bool is_set = state == Qt::Checked; bool changed = is_set != m_align_left; if (changed) { m_align_left = is_set; ui->btn_effect_shift->setChecked(is_set); if (is_set) { ui->btn_align_right->setChecked(false); ui->btn_reverse->setChecked(false); ui->btn_reverse_in_place->setChecked(false); } modify(); } } void qpatternfix::slot_align_right_change (int state) { bool is_set = state == Qt::Checked; bool changed = is_set != m_align_right; if (changed) { m_align_right = is_set; ui->btn_effect_shift->setChecked(is_set); if (is_set) { ui->btn_align_left->setChecked(false); ui->btn_reverse->setChecked(false); ui->btn_reverse_in_place->setChecked(false); } modify(); } } void qpatternfix::slot_reverse_change (int state) { bool is_set = state == Qt::Checked; bool changed = is_set != m_reverse; if (changed) { m_reverse = is_set; if (is_set) { m_reverse_in_place = false; ui->btn_align_left->setChecked(false); ui->btn_align_right->setChecked(false); ui->btn_reverse_in_place->setChecked(false); ui->btn_effect_reverse->setChecked(true); } else { bool ripchecked = ui->btn_reverse_in_place->isChecked(); if (! ripchecked) ui->btn_effect_reverse->setChecked(false); } modify(); } } void qpatternfix::slot_reverse_in_place (int state) { bool is_set = state == Qt::Checked; bool changed = is_set != m_reverse_in_place; if (changed) { m_reverse_in_place = is_set; if (is_set) { m_reverse = false; ui->btn_align_left->setChecked(false); ui->btn_align_right->setChecked(false); ui->btn_reverse->setChecked(false); ui->btn_effect_reverse->setChecked(true); } else { bool rchecked = ui->btn_reverse->isChecked(); if (! rchecked) ui->btn_effect_reverse->setChecked(false); } modify(); } } void qpatternfix::slot_save_note_length (int state) { bool is_set = state == Qt::Checked; bool changed = is_set != m_save_note_length; if (changed) { m_save_note_length = is_set; modify(); } } /** * A convenience/encapsulation function to trigger redrawing. */ void qpatternfix::set_dirty () { track().set_dirty(); if (not_nullptr(m_edit_frame)) m_edit_frame->set_track_change(true); // m_edit_frame->set_dirty(); } void qpatternfix::slot_set () { m_notemap_file = rc().filespec_helper(m_notemap_file); midipulse len = track().get_length(); fixparameters fp /* value structure */ { m_length_type, m_alt_type, len, m_tighten_range, m_full_range, m_random_range, m_pitch_range, m_jitter_range, m_align_left, m_align_right, m_reverse, m_reverse_in_place, m_save_note_length, m_use_time_sig, m_time_sig_beats, m_time_sig_width, m_measures, m_scale_factor, m_notemap_file, m_reverse_notemap, fixeffect::none }; bool success = perf().fix_pattern(track().seq_number(), fp); if (success) { bool alteration = m_alt_type != alteration::none; bool bitshifted = bit_test(fp.fp_effect, fixeffect::shifted); bool bitreversed = bit_test(fp.fp_effect, fixeffect::reversed); bool bitshrunk = bit_test(fp.fp_effect, fixeffect::shrunk); bool bitexpanded = bit_test(fp.fp_effect, fixeffect::expanded); std::string temp = std::to_string(int(fp.fp_length)); ui->label_pulses->setText(qt(temp)); if (! bitreversed) bitreversed = bit_test(fp.fp_effect, fixeffect::reversed_abs); if (m_use_time_sig) { if (not_nullptr(m_edit_frame)) { m_edit_frame->set_bpb_and_bw ( m_time_sig_beats, m_time_sig_width ); } temp = std::to_string(m_time_sig_beats); temp += "/"; temp += std::to_string(m_time_sig_width); } else temp = double_to_string(m_measures); ui->line_edit_measures->setText(qt(temp)); temp = double_to_string(m_scale_factor); ui->line_edit_scale->setText(qt(temp)); ui->btn_effect_alteration->setChecked(alteration); ui->btn_effect_shift->setChecked(bitshifted); ui->btn_effect_shrink->setChecked(bitshrunk); ui->btn_effect_expand->setChecked(bitexpanded); ui->btn_effect_reverse->setChecked(bitreversed); (void) track().verify_and_link(); /* refresh */ set_dirty(); /* for redrawing */ unmodify(false); /* keep fields */ } } void qpatternfix::slot_reset () { track().events() = m_backup_events; /* restore events */ track().apply_length ( m_backup_beats, 0, m_backup_width, m_backup_measures ); /* * Ordered as in the constructor initializer list. */ m_length_type = lengthfix::none; m_alt_type = alteration::none; m_tighten_range = track().snap() / 2; m_full_range = track().snap(); m_random_range = usr().randomization_amount(); m_jitter_range = usr().jitter_range(track().get_ppqn() / 4); m_notemap_file = rc().notemap_filename(); m_reverse_notemap = false; m_measures = double(m_backup_measures); m_scale_factor = 1.0; m_align_left = false; m_align_right = false; m_reverse = false; m_reverse_in_place = false; m_save_note_length = false; m_time_sig_beats = m_backup_beats; m_time_sig_width = m_backup_width; initialize(false); std::string temp = std::to_string(int(track().get_length())); ui->label_pulses->setText(qt(temp)); if (m_use_time_sig) { if (not_nullptr(m_edit_frame)) { m_edit_frame->set_bpb_and_bw(m_backup_beats, m_backup_width); } temp = std::to_string(m_backup_beats); temp += "/"; temp += std::to_string(m_backup_width); m_use_time_sig = false; } else temp = double_to_string(m_measures); (void) track().verify_and_link(); /* refresh */ ui->line_edit_measures->setText(qt(temp)); temp = double_to_string(m_scale_factor); ui->line_edit_scale->setText(qt(temp)); if (m_was_clean) track().unmodify(); set_dirty(); /* for redrawing */ } void qpatternfix::closeEvent (QCloseEvent * event) { if (not_nullptr(m_edit_frame)) m_edit_frame->remove_patternfix_frame(); event->accept(); } } // namespace seq66 /* * qpatternfix.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qperfbase.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qperfbase.cpp * * This module declares/defines the base class for drawing on the piano * roll of the song editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-07-14 * \updates 2025-04-30 * \license GNU GPLv2 or above * * We are currently moving toward making this class a base class. * * User jean-emmanual added support for disabling the following of the * progress bar during playback. See the seqroll::m_progress_follow member. */ #include /* QWidget::resize() */ #include "cfg/settings.hpp" /* seq66::usr() */ #include "play/performer.hpp" /* seq66::performer class */ #include "qperfbase.hpp" /* seq66::qperfbase class */ namespace seq66 { /** * Primary constructor. */ qperfbase::qperfbase ( performer & p, int zoom, int snap, int unitheight, int totalheight ) : qeditbase (p, zoom, snap, c_perf_scale_x, 0, unitheight, totalheight), m_set_size (usr().set_size()), m_width_factor (1.25f), m_track_height (c_names_y), m_track_thin (false), m_track_thick (false) { beat_pen_style(Qt::DotLine); /* default is SolidLine */ if (usr().gridlines_thick()) /* otherise use defaults */ { horiz_pen_width(2); } else { measure_pen_width(1); } } /** * performer::get_max_extent() gets the longest of the lengthiest trigger or * lengthiest pattern. We pad it to 2000 or the result + 200, whichever is * greater. This should cover most use cases. */ int qperfbase::horizSizeHint () const { int result = z().tix_to_pix(perf().get_max_extent()); result = int(result * width_factor()); return result; } /** * Force a resize so that a sizeHint() call will occur so that the scrollbars * for the perfroll and perfnames will update upon a change in the number of * sets. Have to do it twice because Qt sometimes optimizes the non-change * out and doesn't call sizeHint(). Kind of crufty, but invisible to the * user as far as we can see. */ void qperfbase::force_resize (QWidget * self) { int w = self->geometry().width(); int h = self->geometry().height(); self->resize(w + 1, h + 1); self->resize(w, h); } void qperfbase::convert_x (int x, midipulse & tick) { tick = z().pix_to_tix(x); /* x * m_scale_zoom + tick_offset */ } /** * Converts an (x, y) point on the user interface to the corresponding tick * and patter number. */ void qperfbase::convert_xy (int x, int y, midipulse & tick, int & seq) { tick = z().pix_to_tix(x); seq = y / track_height(); if (seq >= perf().sequence_max()) seq = perf().sequence_max() - 1; else if (seq < 0) seq = 0; } /** * Converts ticks to a user-interface x value, and converts a sequence number * to a y value. A kind of inverse function of convert_xy(). */ void qperfbase::convert_ts (midipulse ticks, int seq, int & x, int & y) { if (seq >= 0) { x = z().tix_to_pix(ticks); y = m_total_height - ((seq + 1) * m_unit_height) - 1; } else x = y = 0; } /** * * \param tick_s * The starting tick of the rectangle. * * \param tick_f * The finishing tick of the rectangle. * * \param seq_h * The high sequence row of the rectangle. * * \param seq_l * The low sequence row of the rectangle. * * \param [out] r * The destination rectangle for the calculations. */ void qperfbase::convert_ts_box_to_rect ( midipulse tick_s, midipulse tick_f, int seq_h, int seq_l, seq66::rect & r ) { int x1, y1, x2, y2; convert_ts(tick_s, seq_h, x1, y1); /* convert box to X,Y values */ convert_ts(tick_f, seq_l, x2, y2); rect::xy_to_rect(x1, y1, x2, y2, r); r.height_incr(m_unit_height); } } // namespace seq66 /* * qperfbase.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qperfeditex.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qperfeditex.cpp * * This module declares/defines the base class for the external performance * edit window. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-07-21 * \updates 2023-10-15 * \license GNU GPLv2 or above * */ #include #include "play/performer.hpp" /* seq66::performer class */ #include "play/sequence.hpp" /* seq66::sequence class */ #include "qperfeditex.hpp" #include "qperfeditframe64.hpp" #include "qperfnames.hpp" #include "qperfroll.hpp" #include "qsmainwnd.hpp" /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qperfeditex.h" #else #include "forms/qperfeditex.ui.h" #endif /* * Don't document the namespace. */ namespace seq66 { /** * This function wraps an external qperfeditframe64 frame. * * \param p * Provides the performer object to use for interacting with this sequence. * * \param parent * Provides the parent window/widget for this container window. Defaults * to null. Note that this parameter does not link this class to the * parent as a QWidget, because that would make this class appear inside * the qsmainwnd user-interface. */ qperfeditex::qperfeditex (performer & p, qsmainwnd * parent) : QWidget (nullptr), ui (new Ui::qperfeditex), m_performer (p), m_edit_parent (parent), m_edit_frame (nullptr) { ui->setupUi(this); QGridLayout * layout = new QGridLayout(this); m_edit_frame = new qperfeditframe64(p, this, true); /* external frame */ layout->addWidget(m_edit_frame); show(); m_edit_frame->show(); } /** * Deletes the user interface. It does not tell the editor parent to remove * this object. Contrary to previous claims, why would it need to do that * here? See the closeEvent() override. */ qperfeditex::~qperfeditex() { delete ui; } /** * Override in order to tell the parent frame to remove this fellow * from its memory. */ void qperfeditex::closeEvent (QCloseEvent *) { if (not_nullptr(m_edit_parent)) m_edit_parent->hide_qperfedit(true); } /** * See usage in qsmainwnd. It basically tells the edit-frame to update * itself based on some user-interface or zoom changes. (Don't quote me on * that!) */ void qperfeditex::update_sizes () { if (not_nullptr(m_edit_frame)) m_edit_frame->update_sizes(); } void qperfeditex::set_loop_button (bool looping) { if (not_nullptr(m_edit_frame)) m_edit_frame->set_loop_button(looping); } qperfroll * qperfeditex::perf_roll () { qperfroll * result = nullptr; if (not_nullptr(m_edit_frame)) result = m_edit_frame->perf_roll(); return result; } qperfnames * qperfeditex::perf_names () { qperfnames * result = nullptr; if (not_nullptr(m_edit_frame)) result = m_edit_frame->perf_names(); return result; } } // namespace seq66 /* * qperfeditex.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qperfeditframe64.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qperfeditframe64.cpp * * This module declares/defines the base class for the Performance Editor, * also known as the Song Editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-07-18 * \updates 2026-03-14 * \license GNU GPLv2 or above * * The Song Editor allows the musician to layout the play-back of the * patterns already created using the Pattern editor. */ #include /* Needed for QKeyEvent::accept() */ #include #include "play/performer.hpp" /* seq66::performer class */ #include "qperfeditframe64.hpp" #include "qperfnames.hpp" #include "qperfroll.hpp" #include "qperftime.hpp" #include "qt5_helpers.hpp" /* seq66::qt_set_icon() */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qperfeditframe64.h" #else #include "forms/qperfeditframe64.ui.h" #endif /* * We prefer to load the pixmaps on the fly, rather than deal with those * friggin' resource files. */ #include "pixmaps/collapse.xpm" #include "pixmaps/copy.xpm" #include "pixmaps/expand.xpm" #include "pixmaps/expandgrid.xpm" /* "pixmaps/right.xpm" */ #include "pixmaps/finger.xpm" #include "pixmaps/follow.xpm" #include "pixmaps/loop.xpm" #include "pixmaps/redo.xpm" #include "pixmaps/transpose.xpm" #include "pixmaps/undo.xpm" #include "pixmaps/zoom_in.xpm" #include "pixmaps/zoom_out.xpm" namespace seq66 { /** * Helps with making the page leaps slightly smaller than the width of the * piano roll scroll area. Same value as used in qseqeditframe64. */ static const int c_progress_page_overlap = 80; /** * Trigger transpose ranges. */ static const int c_trigger_transpose_min = (-60); static const int c_trigger_transpose_max = 60; /** * Principal constructor, has a reference to a performer object. * * \param p * Refers to the main performance object. * * \param parent * The Qt widget that owns this frame. Either the qperfeditex (the * external window holding this frame) or the "Song" tab object in the * main window. * * \param isexternal * Indicates that this is an external frame, so that we can reveal the Loop * button. */ qperfeditframe64::qperfeditframe64 ( seq66::performer & p, QWidget * parent, bool isexternal ) : QFrame (parent), ui (new Ui::qperfeditframe64), m_mainperf (p), m_palette (nullptr), m_is_external (isexternal), m_duration_mode (true), m_move_L_marker (false), m_snap_list (perf_snap_items()), /* issue #44, no "current" */ m_snap (8), m_beats_per_measure (4), m_beat_width (4), m_trigger_transpose (0), m_perfroll (nullptr), m_perfnames (nullptr), m_perftime (nullptr) { ui->setupUi(this); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); /* * Snap. Fill options for grid snap combo box and set the default. * We need an obvious macro for "6". * * Not needed: snap_list().current("Length"); */ (void) fill_combobox(ui->cmbGridSnap, snap_list(), "4", "1/"); // "1/4" connect ( ui->cmbGridSnap, SIGNAL(currentIndexChanged(int)), this, SLOT(update_grid_snap(int)) ); /* * Create and add the scroll-area and widget-container for this frame. If * we're using the qscrollmaster (a QScrollArea), the scroll-area will * contain only the qperfoll. Otherwise, it will contain the qperfroll, * qperfnames, and qperftime. In either case the widget-container * contains all three panels. * * Create the piano roll panel, the names panel, and time panel of the * song editor frame. */ m_perfnames = new (std::nothrow) qperfnames(m_mainperf, ui->namesScrollArea); ui->namesScrollArea->setWidget(m_perfnames); /* * Leave the useless horizontal scrollbar in place in order to match the * qperfroll's vertical dimensions. * * ui->namesScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); */ ui->namesScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); ui->namesScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); /* * Replace c_default_perf_zoom with a function to adapt the qperf* zooms * to the PPQN. */ int perfzoom = adapted_perf_zoom(perf().ppqn()); m_perftime = new (std::nothrow) qperftime ( m_mainperf, perfzoom, c_default_snap, this, ui->timeScrollArea ); ui->timeScrollArea->setWidget(m_perftime); ui->timeScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->timeScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_perfroll = new (std::nothrow) qperfroll ( m_mainperf, perfzoom, c_default_snap, m_perfnames, this, ui->rollScrollArea ); ui->rollScrollArea->setWidget(m_perfroll); ui->rollScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); ui->rollScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); /* * Add the various scrollbar pointers to the qscrollmaster object. */ ui->rollScrollArea->add_v_scroll(ui->namesScrollArea->verticalScrollBar()); ui->rollScrollArea->add_h_scroll(ui->timeScrollArea->horizontalScrollBar()); /* * Create the color palette for coloring the patterns. */ m_palette = new QPalette(); /* * #if QT_DEPRECATED_SINCE(5,13) * m_palette->setColor(QPalette::Background, Qt::darkGray); * #else * #endif */ m_palette->setColor(QPalette::Window, Qt::darkGray); /* * Undo and Redo buttons. * * tooltip_with_keystroke(ui->btnUndo, "Ctrl-Z"); * tooltip_with_keystroke(ui->btnUndo, "Shift-Ctrl-Z"); */ connect(ui->btnUndo, SIGNAL(clicked(bool)), m_perfroll, SLOT(undo())); qt_set_icon(undo_xpm, ui->btnUndo); connect(ui->btnRedo, SIGNAL(clicked(bool)), m_perfroll, SLOT(redo())); qt_set_icon(redo_xpm, ui->btnRedo); /* * Follow Progress Button. Qt::NoFocus is the default focus policy. */ std::string keyname = perf().automation_key(automation::slot::follow_transport); tooltip_with_keystroke(ui->m_toggle_follow, keyname); qt_set_icon(follow_xpm, ui->m_toggle_follow); /* * Now specifiable in the 'usr' file. * * ui->m_toggle_follow->setEnabled(true); */ bool followprogress = usr().follow_progress(); ui->m_toggle_follow->setEnabled(true); ui->m_toggle_follow->setCheckable(true); ui->m_toggle_follow->setChecked(followprogress); ui->m_toggle_follow->setAutoDefault(false); // ??? /* * * if (perf().song_mode()) * m_perfroll->progress_follow(true); * * ui->m_toggle_follow->setChecked(m_perfroll->progress_follow()); */ follow(followprogress); connect ( ui->m_toggle_follow, SIGNAL(toggled(bool)), this, SLOT(follow(bool)) ); set_zoom(perfzoom); /* * Zoom-In and Zoom-Out buttons. */ connect(ui->btnZoomIn, SIGNAL(clicked(bool)), this, SLOT(slot_zoom_in())); qt_set_icon(zoom_in_xpm, ui->btnZoomIn); connect(ui->btnZoomOut, SIGNAL(clicked(bool)), this, SLOT(slot_zoom_out())); qt_set_icon(zoom_out_xpm, ui->btnZoomOut); /* * Tiny vertical zoom keys */ connect ( ui->btnKeyVZoomIn, SIGNAL(clicked(bool)), this, SLOT(v_zoom_in()) ); connect ( ui->btnKeyVZoomReset, SIGNAL(clicked(bool)), this, SLOT(reset_v_zoom()) ); connect ( ui->btnKeyVZoomOut, SIGNAL(clicked(bool)), this, SLOT(v_zoom_out()) ); /* * Transpose button and combo-box. */ connect ( ui->btnTranspose, SIGNAL(clicked(bool)), this, SLOT(reset_transpose()) ); qt_set_icon(transpose_xpm, ui->btnTranspose); char num[16]; for (int t = -c_octave_size; t <= c_octave_size; ++t) { int index = t + c_octave_size; if (t != 0) { const char * cit = interval_name_ptr(t); /* see scales.hpp */ snprintf(num, sizeof num, "%+d [%s]", t, cit); } else snprintf(num, sizeof num, "0 [normal]"); ui->comboTranspose->insertItem(index, num); } ui->comboTranspose->setCurrentIndex(c_octave_size); connect ( ui->comboTranspose, SIGNAL(currentIndexChanged(int)), this, SLOT(update_transpose(int)) ); /* * Collapse, Expand, Expand-Copy, Grow, and Loop buttons. */ connect ( ui->btnCollapse, SIGNAL(clicked(bool)), this, SLOT(marker_collapse()) ); qt_set_icon(collapse_xpm, ui->btnCollapse); connect(ui->btnExpand, SIGNAL(clicked(bool)), this, SLOT(marker_expand())); qt_set_icon(expand_xpm, ui->btnExpand); connect ( ui->btnExpandCopy, SIGNAL(clicked(bool)), this, SLOT(marker_expand_copy()) ); qt_set_icon(copy_xpm, ui->btnExpandCopy); if (m_is_external) { std::string keyname = perf().automation_key(automation::slot::loop_LR); tooltip_with_keystroke(ui->btnLoop, keyname); connect ( ui->btnLoop, SIGNAL(clicked(bool)), this, SLOT(marker_loop(bool)) ); qt_set_icon(loop_xpm, ui->btnLoop); } else ui->btnLoop->hide(); /* * The width of the qperfroll is based on its sizeHint(), which is based * on the maximum trigger in all of the sequences in all sets. */ connect(ui->btnGrow, SIGNAL(clicked(bool)), this, SLOT(grow())); qt_set_icon(expandgrid_xpm, ui->btnGrow); /* * Trigger transpose button and spin-box. */ connect ( ui->btnResetTT, SIGNAL(clicked(bool)), this, SLOT(reset_trigger_transpose(bool)) ); ui->spinBoxTT->setRange(c_trigger_transpose_min, c_trigger_transpose_max); ui->spinBoxTT->setSingleStep(1); ui->spinBoxTT->setValue(m_trigger_transpose); ui->spinBoxTT->setReadOnly(false); connect ( ui->spinBoxTT, SIGNAL(valueChanged(int)), this, SLOT(set_trigger_transpose(int)) ); /* * Entry mode */ qt_set_icon(finger_xpm, ui->btnEntryMode); ui->btnEntryMode->setCheckable(true); ui->btnEntryMode->setAutoDefault(false); ui->btnEntryMode->setChecked(false); connect ( ui->btnEntryMode, SIGNAL(toggled(bool)), this, SLOT(entry_mode(bool)) ); /* * Song-record snap button */ ui->btnSnap->setCheckable(true); ui->btnSnap->setChecked(perf().song_record_snap()); connect ( ui->btnSnap, &QPushButton::toggled, [=] { perf().toggle_record_snap(); ui->btnSnap->setChecked(perf().song_record_snap()); } ); /* * Final settings. For snap, 8 is too small. 4, which is actually "1/4", * is better at normal zoom, and also represents a single beat. But * let's use the actual beat length. */ set_snap(perf().get_beat_width()); /* (4) */ set_beats_per_measure(perf().get_beats_per_bar()); set_beat_width(perf().get_beat_width()); std::string dur = perf().duration(m_duration_mode); std::string css = usr().time_colors_css(); if (! css.empty()) ui->btnDuration->setStyleSheet(qt(css)); ui->btnDuration->setText(qt(dur)); connect ( ui->btnDuration, SIGNAL(clicked(bool)), this, SLOT(slot_duration(bool)) ); m_perfroll->setFocus(); } qperfeditframe64::~qperfeditframe64 () { delete ui; if (not_nullptr(m_palette)) delete m_palette; } void qperfeditframe64::scroll_by_step (qscrollmaster::dir d) { switch (d) { case qscrollmaster::dir::left: case qscrollmaster::dir::right: ui->rollScrollArea->scroll_x_by_step(d); break; case qscrollmaster::dir::up: case qscrollmaster::dir::down: ui->rollScrollArea->scroll_y_by_step(d); break; } } /** * Passes the Follow status to the qperfroll object. */ void qperfeditframe64::follow (bool ischecked) { m_perfroll->progress_follow(ischecked); } /** * Checks the position of the tick, and, if it is in a different piano-roll * "page" than the last page, moves the page to the next page. */ void qperfeditframe64::follow_progress () { int w = ui->rollScrollArea->width() - c_progress_page_overlap; if (w > 0) { QScrollBar * hadjust = ui->rollScrollArea->h_scroll(); midipulse progtick = perf().get_tick(); if (progtick > 0 && m_perfroll->progress_follow()) { int progx = m_perfroll->z().tix_to_pix(progtick); int page = progx / w; int oldpage = m_perfroll->scroll_page(); bool newpage = page != oldpage; if (newpage) { m_perfroll->scroll_page(page); // scrollmaster updates the rest hadjust->setValue(progx); } } } } void qperfeditframe64::scroll_to_tick (midipulse tick) { int w = ui->rollScrollArea->width(); if (w > 0) /* w is constant, e.g. around 742 by default */ { int x = m_perfroll->z().tix_to_pix(tick); ui->rollScrollArea->scroll_to_x(x); } } /** * Sets the snap value per the given index. * * \param snapindex * The order of the value in the menu. For 0 to 5, this is essentially * the exponent of 2 that yields the snap value. The default is 4, which * sets snap to 16. */ void qperfeditframe64::update_grid_snap (int snapindex) { if (snapindex >= 0 && snapindex < snap_list().count()) { m_snap = snap_list().ctoi(snapindex); set_guides(); } } void qperfeditframe64::set_snap (midipulse s) { if (s > 0) { char b[16]; snprintf(b, sizeof b, "1/%d", int(s)); ui->cmbGridSnap->setCurrentText(b); m_snap = int(s); } else { ui->cmbGridSnap->setCurrentText("Length"); m_snap = 0; } set_guides(); } /** * These values are ticks, but passed as integers. The guides are set to 4 * measures by default. */ void qperfeditframe64::set_guides () { if (m_beat_width > 0 && m_snap >= 0) { midipulse pp = perf().ppqn() * 4; midipulse measticks = pp * m_beats_per_measure / m_beat_width; midipulse snapticks = m_snap == 0 ? 0 : measticks / m_snap ; midipulse beatticks = pp / m_beat_width; m_perfroll->set_guides(snapticks, measticks, beatticks); m_perftime->set_guides(snapticks, measticks, beatticks); perf().record_snap_length(snapticks); } } /** * Pass-along function for zooming in. Calls the same function for qperftime * and qperfroll. */ void qperfeditframe64::slot_zoom_in () { (void) zoom_in(); } /** * Pass-along function for zooming out. Calls the same function for qperftime * and qperfroll. */ void qperfeditframe64::slot_zoom_out () { (void) zoom_out(); } void qperfeditframe64::adjust_for_zoom (int zprevious) { int znew = m_perfroll->zoom(); float factor = float(zprevious) / float(znew); ui->rollScrollArea->scroll_x_by_factor(factor); set_dirty(); } bool qperfeditframe64::zoom_in () { int zprevious = m_perfroll->zoom(); bool result = m_perftime->zoom_in(); if (result) result = m_perfroll->zoom_in(); if (result) { adjust_for_zoom(zprevious); update_sizes(); } return result; } bool qperfeditframe64::zoom_out () { int zprevious = m_perfroll->zoom(); bool result = m_perftime->zoom_out(); if (result) result = m_perfroll->zoom_out(); if (result) { adjust_for_zoom(zprevious); update_sizes(); } return result; } bool qperfeditframe64::set_zoom (int z) { int zprevious = m_perfroll->zoom(); bool result = m_perftime->set_zoom(z); if (result) result = m_perfroll->set_zoom(z); if (result) adjust_for_zoom(zprevious); return result; } /** * Pass-along function for zoom reset. Calls the same function for qperftime * and qperfroll. */ bool qperfeditframe64::reset_zoom () { int ppq = perf().ppqn(); int zprevious = m_perfroll->zoom(); bool result = m_perftime->reset_zoom(ppq); if (result) result = m_perfroll->reset_zoom(ppq); if (result) adjust_for_zoom(zprevious); return result; } bool qperfeditframe64::v_zoom_in () { return m_perfroll->v_zoom_in(); } bool qperfeditframe64::v_zoom_out () { return m_perfroll->v_zoom_out(); } bool qperfeditframe64::reset_v_zoom () { return m_perfroll->reset_v_zoom(); } /** * The button callback for transposition for this window. Unlike the * Gtkmm-2.4 version, this version just toggles whether it is used or not, * for now. We will add a combo-box selector soon. */ void qperfeditframe64::reset_transpose () { #ifdef THIS_IS_BETTER if (perf().get_transpose() != 0) set_transpose(0); #else ui->comboTranspose->setCurrentIndex(c_octave_size); #endif } /** * Handles updates to the transposition value. This value can be used to * transpose the whole song, or just one trigger, depending on the action * selected. */ void qperfeditframe64::update_transpose (int index) { int transpose = index - c_octave_size; if (transpose >= -c_octave_size && transpose <= c_octave_size) { if (perf().get_transpose() != transpose) set_transpose(transpose); } } /** * Sets the value of transposition for this window. * * \param transpose * The amount to transpose the transposable sequences. * We need to add validation at some point, if the widget does not * enforce that. */ void qperfeditframe64::set_transpose (int transpose) { perf().all_notes_off(); perf().set_transpose(transpose); } void qperfeditframe64::entry_mode (bool ischecked) { if (not_nullptr(m_perfroll)) m_perfroll->set_adding(ischecked); } void qperfeditframe64::slot_duration (bool /* ischecked */ ) { m_duration_mode = ! m_duration_mode; std::string dur = perf().duration(m_duration_mode); ui->btnDuration->setText(qt(dur)); } void qperfeditframe64::update_entry_mode (bool on) { ui->btnEntryMode->setChecked(on); } /** * Calls updateGeometry() on child elements to react to changes in MIDI file * sizes when a MIDI file is opened. * * For the resize() calls, see qperfbase::force_resize(). */ void qperfeditframe64::update_sizes () { std::string dur = perf().duration(m_duration_mode); ui->btnDuration->setText(qt(dur)); set_guides(); m_perfnames->resize(); m_perfnames->updateGeometry(); m_perfroll->resize(); m_perfroll->updateGeometry(); m_perftime->updateGeometry(); } /** * Calls set_dirty() on child element to react to zoom actions. But * qperfnames has no timer, so we update it directly. */ void qperfeditframe64::set_dirty () { std::string dur = perf().duration(m_duration_mode); ui->btnDuration->setText(qt(dur)); m_perfnames->reupdate(); m_perfroll->set_dirty(); m_perftime->set_dirty(); } void qperfeditframe64::marker_collapse () { perf().collapse(); set_dirty(); } void qperfeditframe64::marker_expand () { perf().expand(); set_dirty(); } void qperfeditframe64::marker_expand_copy () { perf().copy(); set_dirty(); } void qperfeditframe64::grow () { m_perfroll->increment_width(); m_perftime->increment_width(); update_sizes(); } void qperfeditframe64::reset_trigger_transpose (bool /*ischecked*/) { ui->spinBoxTT->setValue(0); } void qperfeditframe64::set_trigger_transpose (int tpose) { if (tpose >= c_trigger_transpose_min && tpose <= c_trigger_transpose_max) { ui->spinBoxTT->setValue(tpose); m_perfroll->set_trigger_transpose(tpose); } } /** * There is a button with the same functionality in the main window. */ void qperfeditframe64::marker_loop (bool loop) { perf().looping(loop); } void qperfeditframe64::set_loop_button (bool looping) { ui->btnLoop->setChecked(looping); } /* * We must accept() the key-event, otherwise even key-events in the QLineEdit * items are propagated to the parent, where they then get passed to the * performer as if they were keyboards controls (such as a pattern-toggle * hot-key). */ void qperfeditframe64::keyPressEvent (QKeyEvent * event) { int key = event->key(); bool isctrl = bool(event->modifiers() & Qt::ControlModifier); if (! isctrl) { bool isshift = bool(event->modifiers() & Qt::ShiftModifier); if (! zoom_key_press(isshift, key)) { if (isshift) { if (key == Qt::Key_L) { m_perftime->setFocus(); m_perftime->m_move_L_marker = true; } else if (key == Qt::Key_R) { m_perftime->setFocus(); m_perftime->m_move_L_marker = false; } else event->accept(); } else { /* * vi-style scrolling keystrokes */ if (key == Qt::Key_J) scroll_by_step(qscrollmaster::dir::down); else if (key == Qt::Key_K) scroll_by_step(qscrollmaster::dir::up); else if (key == Qt::Key_H) scroll_by_step(qscrollmaster::dir::left); else if (key == Qt::Key_L) scroll_by_step(qscrollmaster::dir::right); else event->accept(); } } } else event->accept(); } void qperfeditframe64::keyReleaseEvent (QKeyEvent * event) { event->accept(); } bool qperfeditframe64::zoom_key_press (bool shifted, int key) { bool result = false; if (shifted) { if (key == Qt::Key_Z) { result = zoom_in(); } else if (key == Qt::Key_V) { result = v_zoom_in(); } } else { if (key == Qt::Key_Z) { result = zoom_out(); } else if (key == Qt::Key_0) { result = reset_v_zoom(); if (result) result = reset_zoom(); } else if (key == Qt::Key_V) { result = v_zoom_out(); } } return result; } } // namespace seq66 /* * qperfeditframe64.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qperfnames.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qperfnames.cpp * * This module declares/defines the base class for performance names. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2026-01-23 * \license GNU GPLv2 or above * * This module is almost exclusively user-interface code. There are some * pointers yet that could be replaced by references, and a number of minor * issues that could be fixed. * * Adjustments to the performance window can be made with the highlighting * option. Sequences that don't have events show up as black-on-yellow. * This feature is enabled by default. To disable this feature, configure * the build with the "--disable-highlight" option. */ #include #include #include #include "cfg/settings.hpp" /* seq66::usr() config functions */ #include "play/performer.hpp" /* seq66::performer class */ #include "gui_palette_qt5.hpp" /* seq66::gui_palette_qt5 class */ #include "qperfnames.hpp" /* seq66::qperfnames panel class */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ namespace seq66 { /** * Alpha for coloring the names brightly, or not. */ const int s_alpha_bright = 255; const int s_alpha_normal = 100; /** * Default font size. */ const int s_pointsize = 7; /** * Sequence labels for the side of the song editor. This constructor * uses the default zoom, snap, unit height, and total height values of * qperfbase. */ qperfnames::qperfnames (performer & p, QWidget * parent) : QWidget (parent), qperfbase (p), m_font ("Monospace"), m_nametext_x (6 * 2 + 6 * 20), /* see name_x() */ m_preview_color (progress_paint()), m_is_previewing (false), m_preview_row (-1), m_name_grad (0, 0, 0, 1), m_muted_name_grad (0, 0, 0, 1), m_use_gradient (gui_use_gradient_brush()) { /* * This policy is necessary in order to allow the vertical scrollbar to * work. */ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding); setFocusPolicy(Qt::StrongFocus); m_font.setStyleHint(QFont::Monospace); m_font.setLetterSpacing(QFont::AbsoluteSpacing, 1); m_font.setBold(true); m_font.setPointSize(s_pointsize); m_preview_color.setAlpha(s_alpha_normal); if (use_gradient()) { m_name_grad.setCoordinateMode(QGradient::ObjectMode); Color backcolor = grey_color(); m_muted_name_grad.setColorAt(0.01, backcolor.darker(150)); m_muted_name_grad.setColorAt(0.5, backcolor.lighter()); m_muted_name_grad.setColorAt(0.99, backcolor.darker(150)); m_muted_name_grad.setCoordinateMode(QGradient::ObjectMode); } } /** * A pass-along function for the parent frame to call. */ void qperfnames::reupdate () { update(); } /** * Draws the sequence names down the side of the performance roll. */ void qperfnames::paintEvent (QPaintEvent *) { int h = track_height(); int y_s = 0; int y_f = height() / h; int set_count = setmaster::Size(); /* number of rows */ QPainter painter(this); QPen pen(text_paint()); /* fore_color() */ QBrush brush(backnames_paint(), Qt::SolidPattern); pen.setWidth(horiz_pen_width()); painter.setBrush(brush); painter.setFont(m_font); /* * Do we really need this? * * pen.setStyle(Qt::SolidLine); * painter.setPen(pen); * painter.drawRect(0, 0, width(), height() - 1); // rectangle border */ int set_y = h * set_count / 2; for (int y = y_s; y <= y_f; ++y) { bool start_of_set = (y % set_size()) == 0; int seq_id = y; if (seq_id < int(perf().sequence_max())) // or use set_count? { int rect_x = 6 * 2 + 2; int rect_y = h * seq_id; int rect_w = c_names_x - 15; if ((seq_id % set_count) == 0) // 1st seq in bank? { char ss[16]; int bank_id = seq_id / set_count; snprintf(ss, sizeof ss, "%2d", bank_id); pen.setColor(fore_color()); // black bank boxes brush.setColor(back_color()); // Qt::black brush.setStyle(Qt::SolidPattern); painter.setPen(pen); painter.setBrush(brush); painter.drawRect(1, name_y(seq_id) + 1, 13, h - 1); int text_y = rect_y + set_y; QString bankss(ss); pen.setColor(text_names_paint()); // for bank number painter.setPen(pen); painter.drawText(1, rect_y + 10, bankss); painter.save(); // { QString bank(qt(perf().set_name(bank_id))); painter.translate(12, text_y + bank.length() * 4); painter.rotate(270); painter.drawText(0, 0, bank); painter.restore(); // } } if (perf().is_seq_active(seq_id)) { std::string seq_name = perf().sequence_label(seq_id); seq::pointer s = perf().get_sequence(seq_id); bool muted = s->get_song_mute(); // ! s->armed() char name[64]; snprintf ( name, sizeof name, "%-14.14s %3d", s->name().c_str(), s->measures() ); QString chinfo(name); if (use_gradient()) { if (muted) { painter.fillRect ( rect_x + 2 , rect_y + 1, rect_w - 2, h - 1, m_muted_name_grad ); } else { int c = s->color(); Color backcolor = get_color_fix(PaletteColor(c)); int alpha = seq_id == m_preview_row ? s_alpha_bright : s_alpha_normal ; backcolor.setAlpha(alpha); m_name_grad.setColorAt(0.01, backcolor.darker(150)); m_name_grad.setColorAt(0.5, backcolor.lighter()); m_name_grad.setColorAt(0.99, backcolor.darker(150)); painter.fillRect ( rect_x + 2 , rect_y + 1, rect_w - 2, h - 1, m_name_grad ); } /* * Draw a rectangle around the gradient. */ pen.setColor(fore_color()); pen.setStyle(Qt::SolidLine); pen.setColor(fore_color()); brush.setStyle(Qt::NoBrush); painter.setBrush(brush); painter.drawRect(rect_x, rect_y, rect_w, h); } else { if (muted) { brush.setColor(grey_color()); brush.setStyle(Qt::SolidPattern); painter.setBrush(brush); painter.drawRect(rect_x, rect_y, rect_w, h); pen.setColor(fore_color()); } else { int c = s->color(); Color backcolor = get_color_fix(PaletteColor(c)); int alpha = seq_id == m_preview_row ? s_alpha_bright : s_alpha_normal ; backcolor.setAlpha(alpha); brush.setColor(backcolor); brush.setStyle(Qt::SolidPattern); painter.setBrush(brush); painter.drawRect(rect_x, rect_y, rect_w, h); pen.setColor(fore_color()); } } painter.setPen(text_names_paint()); painter.drawText(18, rect_y + 9, chinfo); if (! track_thin()) { char temp[8]; snprintf(temp, sizeof temp, "%3d", s->trigger_count()); painter.drawText(18, rect_y + 19, qt(seq_name)); painter.drawText(114, rect_y + 19, temp); } if (use_gradient()) { if (muted) { brush.setColor(grey_color()); brush.setStyle(Qt::SolidPattern); painter.setBrush(brush); } else { int c = s->color(); Color backcolor = get_color_fix(PaletteColor(c)); int alpha = seq_id == m_preview_row ? s_alpha_bright : s_alpha_normal ; backcolor.setAlpha(alpha); brush.setColor(backcolor); brush.setStyle(Qt::SolidPattern); painter.setBrush(brush); } } painter.drawRect(name_x(2), name_y(seq_id), 9, h); painter.drawText(name_x(4), name_y(seq_id) + 9, QString("M")); } else { pen.setStyle(Qt::SolidLine); pen.setColor(fore_color()); brush.setColor(backnames_paint()); painter.setPen(pen); /* fill background */ painter.setBrush(brush); painter.drawRect(rect_x, rect_y, rect_w, h); } if (start_of_set) { pen.setWidth(horiz_pen_width() * 2); painter.setPen(pen); painter.drawLine ( rect_x, rect_y, rect_x + rect_w + 1, rect_y ); pen.setWidth(horiz_pen_width()); painter.setPen(pen); } } } } QSize qperfnames::sizeHint () const { int count = perf().sequences_in_sets(); int height = track_height() * count; return QSize(c_names_x, height); } /** * Converts a y-value into a sequence number and returns it. Used in * figuring out which sequence to mute/unmute in the performance editor. * * \param y * The y value (within the vertical limits of the perfnames column to the * left of the performance editor's piano roll. * * \return * Returns the sequence number corresponding to the y value. */ int qperfnames::convert_y (int y) { int seqlimit = perf().sequences_in_sets(); int seq = y / track_height(); if (seq >= seqlimit) seq = seqlimit - 1; else if (seq < 0) seq = 0; return seq; } void qperfnames::keyPressEvent (QKeyEvent * event) { bool ignored = false; bool isctrl = bool(event->modifiers() & Qt::ControlModifier); /* Ctrl */ if (isctrl) { /* no code yet */ } else { int keyvalue = event->key(); if ( keyvalue == Qt::Key_Left || keyvalue == Qt::Key_Right || keyvalue == Qt::Key_Up || keyvalue == Qt::Key_Down ) { ignored = true; } } if (ignored) event->accept(); else QWidget::keyPressEvent(event); } void qperfnames::mousePressEvent (QMouseEvent * ev) { int y = qt_mouse_y(ev); int seqno = convert_y(y); if (ev->button() == Qt::LeftButton) { bool isshiftkey = (ev->modifiers() & Qt::ShiftModifier) != 0; (void) perf().toggle_sequences(seqno, isshiftkey); update(); } else if (ev->button() == Qt::RightButton) { (void) perf().sequence_playing_toggle(seqno); update(); } } /** * One issue is that a double-click yields a mouse-press and an * mouse-double-click event, in that order. Note the krufty solution * down at the end of this function. */ void qperfnames::mouseDoubleClickEvent (QMouseEvent * ev) { if (rc().allow_click_edit()) { int seqno = convert_y(qt_mouse_y(ev)); bool active = perf().is_seq_active(seqno); emit signal_call_editor_ex(seqno, active); (void) perf().toggle_sequences(seqno, false); /* krufty ! */ } } void qperfnames::mouseReleaseEvent (QMouseEvent * /*ev*/) { // no code; add a call to update() if a change is made } void qperfnames::mouseMoveEvent (QMouseEvent * /*event*/) { // no code; add a call to update() if a change is made } void qperfnames::set_preview_row (int row) { m_is_previewing = row >= 0; m_preview_row = row; update(); } /** * Prevent qperfnames from scrolling on its own via the scroll-wheel event; * this override does nothing but accept() the event. * * ignore() just let's the parent handle the event, which allows scrolling to * occur. For issue #3, we have enabled the scroll wheel in the piano roll * [see qscrollmaster::wheelEvent()], but we disable it here. So this is a * partial solution to the issue. */ void qperfnames::wheelEvent (QWheelEvent * ev) { ev->accept(); } } // namespace seq66 /* * qperfnames.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qperfroll.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qperfroll.cpp * * This module declares/defines the base class for the Qt 5 version of the * Performance window piano roll. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2026-01-23 * \license GNU GPLv2 or above * * This class represents the central piano-roll user-interface area of the * performance/song editor. * * Seq24's song editor does not snap when first drawing in the pattern * extent. It does snap when moving an existing pattern extent. It does * snap when enlarging or shrinking an existing pattern extent via the * handle. That is, if moving or growing, snap the tick. */ #include #include #include #include #include "cfg/settings.hpp" /* seq66::usr() config functions */ #include "play/performer.hpp" /* seq66::performer class */ #include "util/rect.hpp" /* seq66::rect::xy_to_rect_get() */ #include "gui_palette_qt5.hpp" #include "qperfeditframe64.hpp" #include "qperfnames.hpp" #include "qperfroll.hpp" #include "qt5_helpers.hpp" /* seq66::qt_timer() */ namespace seq66 { /** * Alpha values for various states, not yet members, not yet configurable. */ static const int c_alpha_playing = 255; static const int c_alpha_muted = 100; /** * Initial sizing for the perf-roll. The baseline PPQN is defined in * usrsettings. */ static const int c_ycorrection = 1; /* horizontal grid line fix */ static const int c_pen_width = 2; static const int c_size_box_w = 8; static const int c_size_box_click_w = c_size_box_w + 1 ; #if defined THIS_CODE_ADDS_VALUE static const int c_background_x = (c_base_ppqn * 4 * 16) / c_perf_scale_x; static const int c_border_width = 2; #endif /** * Font sizes for small, normal, and expanded vertical zoom */ static const int s_vfont_size_small = 8; static const int s_vfont_size_normal = 12; static const int s_vfont_size_large = 16; /** * Principal constructor. */ qperfroll::qperfroll ( performer & p, int zoom, int snap, qperfnames * seqnames, qperfeditframe64 * frame, QWidget * parent ) : QWidget (parent), qperfbase ( p, zoom, snap, c_names_y, /* unit height of tracks */ c_names_y * p.sequences_in_sets() /* p.sequence_max() */ ), m_parent_frame (frame), /* frame64() accessor */ m_perf_names (seqnames), m_back_grad (0, 0, 0, 1), m_sel_grad (0, 0, 0, 1), m_timer (nullptr), m_font ("Monospace"), m_trigger_transpose (0), m_tick_s (0), m_tick_f (0), m_seq_h (-1), m_seq_l (-1), m_drop_track (-1), m_drop_tick (0), m_drop_tick_offset (0), m_last_tick (0), m_box_select (false), m_grow_direction (false), m_adding_pressed (false) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); /* track mouse movement without a click */ m_font.setStyleHint(QFont::Monospace); m_font.setLetterSpacing(QFont::AbsoluteSpacing, 1); m_font.setBold(true); m_font.setPointSize(s_vfont_size_normal); if (use_gradient()) { m_back_grad.setCoordinateMode(QGradient::ObjectMode); m_sel_grad.setColorAt(0.01, sel_color().darker()); m_sel_grad.setColorAt(0.5, sel_color().lighter()); m_sel_grad.setColorAt(0.99, sel_color().darker()); m_sel_grad.setCoordinateMode(QGradient::ObjectMode); } m_timer = qt_timer(this, "qperfroll", 2, SLOT(conditional_update())); } /** * This virtual destructor stops the timer. */ qperfroll::~qperfroll () { if (not_nullptr(m_timer)) m_timer->stop(); } /** * Calls update() if needed, and also implements follow-progress. */ void qperfroll::conditional_update () { if (perf().needs_update() || check_dirty()) { if (perf().follow_progress()) follow_progress(); /* keep up with progress */ update(); } } /** * Checks the position of the tick, and, if it is in a different piano-roll * "page" than the last page, moves the page to the next page. */ void qperfroll::follow_progress () { if (not_nullptr(frame64())) frame64()->follow_progress(); } /** * Draws and redraws the performance roll. */ void qperfroll::paintEvent (QPaintEvent * /*qpep*/) { QPainter painter(this); QRect r(0, 0, width(), height()); QBrush brush(Qt::white, Qt::NoBrush); QPen pen(fore_color()); pen.setStyle(Qt::SolidLine); painter.setPen(pen); painter.setBrush(brush); painter.drawRect(0, 0, width(), height()); if (! is_initialized()) set_initialized(); draw_grid(painter, r); draw_triggers(painter, r); /* * Draw selections, if applicable. Currently, only one box can be selected. * Kepler34 has a feature called "box select" that we've had trouble * implementing. */ if (m_box_select) { int x, y, w, h; rect::xy_to_rect_get ( drop_x(), drop_y(), current_x(), current_y(), x, y, w, h ); old_rect().set(x, y, w, h + track_height()); brush.setStyle(Qt::SolidPattern); brush.setColor(grey_color()); pen.setStyle(Qt::SolidLine); pen.setColor(sel_color()); pen.setWidth(c_pen_width); painter.setBrush(brush); painter.setPen(pen); painter.drawRect(x, y, w, h + track_height()); } else { brush.setStyle(Qt::NoBrush); // painter reset painter.setBrush(brush); } #if defined THIS_CODE_ADDS_VALUE int xwidth = r.width(); int yheight = r.height() - 1; pen.setStyle(Qt::SolidLine); // draw border pen.setColor(Qt::black); pen.setWidth(c_border_width); painter.setPen(pen); painter.drawRect(0, 0, xwidth, yheight); #endif midipulse tick = perf().get_tick(); /* draw progress playhead */ int progress_x = z().tix_to_pix(tick); pen.setColor(progress_color()); pen.setStyle(Qt::SolidLine); pen.setWidth(progress_bar_width()); painter.setPen(pen); if (progress_x == 0) progress_x = 3; painter.drawLine(progress_x, 1, progress_x, height() - 2); } bool qperfroll::v_zoom_in () { bool result = not_nullptr(perf_names()); if (result) { m_font.setPointSize(s_vfont_size_large); set_thick(); perf_names()->set_thick(); set_dirty(); frame64()->set_dirty(); } return result; } bool qperfroll::v_zoom_out () { bool result = not_nullptr(perf_names()); if (result) { m_font.setPointSize(s_vfont_size_small); set_thin(); perf_names()->set_thin(); set_dirty(); frame64()->set_dirty(); } return result; } bool qperfroll::reset_v_zoom () { bool result = not_nullptr(perf_names()); if (result) { m_font.setPointSize(s_vfont_size_normal); set_normal(); perf_names()->set_normal(); frame64()->reset_zoom(); set_dirty(); frame64()->set_dirty(); } return result; } bool qperfroll::zoom_in () { bool result = qperfbase::zoom_in(); if (result) set_dirty(); return result; } bool qperfroll::zoom_out () { bool result = qperfbase::zoom_out(); if (result) set_dirty(); return result; } bool qperfroll::reset_zoom (int ppq) { bool result = qperfbase::reset_zoom(ppq); if (result) set_dirty(); return result; } /** * The scale_zoom() function is the zoom value times the scale value. At some * point, might override horizSizeHint() a la qperfbase. */ QSize qperfroll::sizeHint () const { int count = perf().sequences_in_sets(); int height = track_height() * count; int width = horizSizeHint(); int w = frame64()->width(); if (width < w) width = w; width *= width_factor(); return QSize(width, height); } bool qperfroll::in_selection_area (midipulse tick) { return ( m_drop_track >= 0 && m_drop_track >= m_seq_l && m_drop_track <= m_seq_h && tick >= m_tick_s && tick <= m_tick_f ); } void qperfroll::mousePressEvent(QMouseEvent * ev) { bool isctrl = bool(ev->modifiers() & Qt::ControlModifier); bool isshift = bool(ev->modifiers() & Qt::ShiftModifier); bool lbutton = ev->button() == Qt::LeftButton; bool rbutton = ev->button() == Qt::RightButton; bool mbutton = ev->button() == Qt::MiddleButton || (lbutton && isctrl); drop_x(qt_mouse_x(ev)); drop_y(qt_mouse_y(ev)); convert_xy(drop_x(), drop_y(), m_drop_tick, m_drop_track); seq::pointer dropseq = perf().get_sequence(m_drop_track); bool on_pattern = not_nullptr(dropseq); if (mbutton) /* split loop at cursor */ { if (on_pattern) { bool state = perf().get_trigger_state(m_drop_track, m_drop_tick); if (state) { /* * Determine how and where the split should occur. * Need to support an exact split at some point. */ midipulse tick = m_drop_tick; trigger::splitpoint sp = trigger::splitpoint::middle; if (rc().allow_snap_split()) { sp = trigger::splitpoint::snap; tick -= m_drop_tick_offset; if (snap() > 0) tick -= tick % snap(); } (void) perf().split_trigger(m_drop_track, tick, sp); } } } else if (rbutton) { if (isctrl) { /* * See Shift-Left-Click instead. * if (on_pattern) * perf().transpose_trigger(...); */ } else { set_adding(true); perf().unselect_all_triggers(); m_box_select = false; } } else if (lbutton) { /* * Add a new seq instance if we didn't select anything, and are holding * the right mouse button (or the finger button is active). */ if (isshift) { if (on_pattern) { perf().transpose_trigger ( m_drop_track, m_drop_tick, m_trigger_transpose ); } } else { midipulse tick = m_drop_tick; if (adding()) { m_adding_pressed = true; if (on_pattern) { bool trigger_state = perf().get_trigger_state ( m_drop_track, tick ); if (trigger_state) delete_trigger(m_drop_track, tick); else add_trigger(m_drop_track, tick); } } else /* not in paint mode */ { bool selected = false; if (on_pattern) { /* * ISSUE: Just clicking in the perf roll gets us here, * and this ends up setting the perf modify flag. And at * best we are only selecting. * * perf().push_trigger_undo(); * * If the current loop is not in selection range, bin it. */ if (! in_selection_area(tick)) { perf().unselect_all_triggers(); m_seq_h = m_seq_l = m_drop_track; } perf().select_trigger(m_drop_track, tick); midipulse start_tick, end_tick; (void) perf().selected_trigger ( m_drop_track, tick, start_tick, end_tick ); /* * Check for corner drag to grow sequence start. */ int clickminus = c_size_box_click_w - 1; int clickbox = c_size_box_click_w * scale_zoom(); if ( tick >= start_tick && tick <= start_tick + clickbox && (drop_y() % track_height()) <= clickminus ) { growing(true); m_grow_direction = true; selected = true; m_drop_tick_offset = m_drop_tick - start_tick; } else if // check for corner drag to grow sequence end ( tick >= (end_tick - clickbox) && tick <= end_tick && (drop_y() % track_height()) >= (track_height() - clickminus) ) { growing(true); selected = true; m_grow_direction = false; m_drop_tick_offset = m_drop_tick - end_tick; } else if (tick <= end_tick && tick >= start_tick) { moving(true); /* we're moving the seq */ selected = true; m_drop_tick_offset = m_drop_tick - start_tick; } } if (! selected) /* select with a box */ { perf().unselect_all_triggers(); snap_drop_y(); /* always snap to rows */ current_x(drop_x()); current_y(drop_y()); m_box_select = true; } } } } set_dirty(); /* force a redraw */ } void qperfroll::mouseReleaseEvent (QMouseEvent * ev) { bool lbutton = ev->button() == Qt::LeftButton; bool rbutton = ev->button() == Qt::RightButton; if (rbutton) { m_adding_pressed = false; set_adding(false); } else if (lbutton) { if (adding()) { /* * A quandary. This turns off trigger insertion. But the user may * have "permanently" turned on insertion via "p" or the "Entry * mode" button, and expect that to stay in force, and then * forgetfully click on an existing pattern. May have to let user * feedback settle this one. * * set_adding(false); */ m_adding_pressed = false; } if (m_box_select) /* calculate selected seqs in box */ { int x, y, w, h; current_x(qt_mouse_x(ev)); current_y(qt_mouse_y(ev)); snap_current_y(); rect::xy_to_rect_get ( drop_x(), drop_y(), current_x(), current_y(), x, y, w, h ); convert_xy(x, y, m_tick_s, m_seq_l); convert_xy(x + w, y + h, m_tick_f, m_seq_h); perf().select_triggers_in_range(m_seq_l, m_seq_h, m_tick_s, m_tick_f); } } clear_action_flags(); m_box_select = false; m_last_tick = 0; set_dirty(); /* force a redraw */ } void qperfroll::mouseMoveEvent (QMouseEvent * ev) { seq::pointer dropseq = perf().get_sequence(m_drop_track); int x = qt_mouse_x(ev); int y = qt_mouse_y(ev); int row; midipulse t, tick = 0; convert_xy(x, y, t, row); if (row >= 0) perf_names()->set_preview_row(row); if (is_nullptr(dropseq)) return; if (adding() && m_adding_pressed) { midipulse seqlength = dropseq->get_length(); midipulse s = snap() == 0 ? seqlength : snap(); convert_x(x, tick); if (perf().song_record_snap()) tick -= tick % s; (void) perf().grow_trigger ( m_drop_track, m_drop_tick, tick, seqlength ); } else if (moving() || growing()) { convert_x(x, tick); tick -= m_drop_tick_offset; if (perf().song_record_snap()) /* apply to move/grow too */ if (snap() > 0) /* compare Seq64 issue #171 */ tick -= tick % snap(); if (moving()) /* move selected triggers */ { #if defined USE_SONG_BOX_SELECT if (m_last_tick != 0) perf().box_move_triggers(tick - m_last_tick); #else perf().move_triggers(m_drop_track, tick, true); #endif } if (growing()) { if (m_last_tick != 0) { midipulse lastoffset = tick - m_last_tick; triggers::grow ts = m_grow_direction ? triggers::grow::start : triggers::grow::end ; (void) perf().offset_triggers(ts, m_seq_l, m_seq_h, lastoffset); } #if USE_OLD_CODE if (m_grow_direction) /* grow start & trigger(s) */ { triggers::grow ts = triggers::grow::start; for (int seqid = m_seq_l; seqid <= m_seq_h; ++seqid) { seq::pointer seq = perf().get_sequence(seqid); if (not_nullptr(seq)) { if (m_last_tick != 0) seq->offset_triggers(lastoffset, ts); } } } else /* grow end & trigger(s) */ { triggers::grow te = triggers::grow::end; for (int seqid = m_seq_l; seqid <= m_seq_h; ++seqid) { seq::pointer seq = perf().get_sequence(seqid); if (not_nullptr(seq)) { if (m_last_tick != 0) seq->offset_triggers(lastoffset - 1, te); } } } #endif } } else if (m_box_select) { current_x(qt_mouse_x(ev)); current_y(qt_mouse_y(ev)); snap_current_y(); convert_xy(0, current_y(), tick, m_drop_track); } m_last_tick = tick; set_dirty(); /* force a redraw */ frame64()->set_dirty(); } /** * One issue is that a double-click yields a mouse-press and an * mouse-double-click event, in that order. */ void qperfroll::mouseDoubleClickEvent (QMouseEvent * ev) { if (rc().allow_click_edit()) { int seqno = seq_id_from_xy(qt_mouse_x(ev), qt_mouse_y(ev)); bool active = perf().is_seq_active(seqno); emit signal_call_editor_ex(seqno, active); } } /** * Converts the (x, y) coordinates of a click into a sequence/pattern ID. * Compare this function to qslivegrid::seq_id_from_xy(). * * \param click_x * The x-coordinate of the mouse click. At present, this value * is not checked to see if there is a trigger at that location. * Thus, a pattern can be opened/created anywhere in the track * line. * * \param click_y * The y-coordinate of the mouse click. * * \return * Returns the sequence/pattern number. If not found, then a -1 (the * value seq::unassigned) is returned. */ int qperfroll::seq_id_from_xy (int click_x, int click_y) { int result = seq::unassigned(); midipulse tick = 0; convert_xy(click_x, click_y, tick, result); return result; } void qperfroll::keyPressEvent (QKeyEvent * ev) { bool handled = false; bool dirty = false; seq::pointer dropseq = perf().get_sequence(m_drop_track); bool on_pattern = not_nullptr(dropseq); bool isshift = ev->modifiers() & Qt::ShiftModifier; bool isctrl = ev->modifiers() & Qt::ControlModifier; if (perf().is_pattern_playing()) { if (ev->key() == Qt::Key_Space) { handled = true; stop_playing(); } else if (ev->key() == Qt::Key_Escape) { handled = true; stop_playing(); } else if (ev->key() == Qt::Key_Period) { handled = true; pause_playing(); } } else { if (ev->key() == Qt::Key_Space || ev->key() == Qt::Key_Period) { handled = true; start_playing(); } else if (ev->key() == Qt::Key_Escape) { if (adding()) { handled = true; set_adding(false); } else { if (usr().escape_pattern()) { if (not_nullptr(frame64())) { if (frame64()->is_external()) frame64()->parentWidget()->close(); } } } } else if (ev->key() == Qt::Key_I) { handled = true; set_adding(true); } else if (ev->key() == Qt::Key_P) { handled = true; set_adding(true); } else if (ev->key() == Qt::Key_X) { handled = true; set_adding(false); } else if ( ev->key() == Qt::Key_Delete || ev->key() == Qt::Key_Backspace ) { handled = true; perf().push_trigger_undo(); /* delete selected trigs */ for (int seqid = m_seq_l; seqid <= m_seq_h; seqid++) { if (perf().is_seq_active(seqid)) { if (perf().delete_triggers(seqid)) dirty = true; } } } else if ( ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right ) { if (on_pattern && ! isctrl) handled = move_by_key(ev->key() == Qt::Key_Right); } if (isctrl) { switch (ev->key()) { case Qt::Key_X: handled = true; if (on_pattern) { if (perf().cut_triggers(m_drop_track)) dirty = true; } break; case Qt::Key_C: handled = true; if (on_pattern) perf().copy_triggers(m_drop_track); break; case Qt::Key_V: handled = true; if (on_pattern) { if (perf().paste_trigger(m_drop_track)) dirty = true; } break; case Qt::Key_Z: handled = dirty = true; if (ev->modifiers() & Qt::ShiftModifier) redo(); /* perf().pop_trigger_redo() */ else undo(); /* perf().pop_trigger_undo() */ break; case Qt::Key_Home: handled = true; if (not_nullptr(frame64())) frame64()->scroll_to_tick(0); break; case Qt::Key_End: /* go to the bitter end of the song */ handled = true; if (not_nullptr(frame64())) frame64()->scroll_to_tick(perf().get_max_extent()); break; case Qt::Key_Left: case Qt::Key_Right: /* * Redundant with non-Ctrl versions. */ handled = move_by_key(ev->key() == Qt::Key_Right, false); break; } } else { /* * These can be done in playback or not. The grid is "continually" * redrawn. */ if (isshift) { if (ev->key() == Qt::Key_Z) { handled = frame64()->zoom_in(); } else if (ev->key() == Qt::Key_V) { handled = true; v_zoom_in(); } } else { if (ev->key() == Qt::Key_Z) { handled = frame64()->zoom_out(); } else if (ev->key() == Qt::Key_V) { handled = v_zoom_out(); } else if (ev->key() == Qt::Key_0) { handled = reset_v_zoom(); /* also resets horiz zoom */ } else if (ev->key() == Qt::Key_Home) { handled = true; if (not_nullptr(frame64())) frame64()->scroll_to_tick(0); } else if (ev->key() == Qt::Key_End) { handled = true; if (not_nullptr(frame64())) /* go to end minus a bit */ { midipulse mx = perf().get_max_extent() - 4 * measure_length(); frame64()->scroll_to_tick(mx); } } } } } if (handled) { frame64()->set_dirty(); if (dirty) set_dirty(); } else QWidget::keyPressEvent(ev); } void qperfroll::keyReleaseEvent (QKeyEvent * /* ev */) { // no code } bool qperfroll::move_by_key (bool forward, bool single) { trigger t = perf().find_trigger(m_drop_track, m_drop_tick); bool result = t.is_valid(); if (result) { m_drop_tick = t.tick_start(); bool ok = perf().move_trigger ( m_drop_track, m_drop_tick, snap(), forward, single ); if (ok) { if (forward) m_drop_tick += snap(); else m_drop_tick -= snap(); } } else m_drop_tick = 0; return result; } void qperfroll::add_trigger(int seq, midipulse tick) { (void) perf().add_trigger(seq, tick, snap()); } void qperfroll::delete_trigger(int seq, midipulse tick) { (void) perf().delete_trigger(seq, tick); } /* * Sets the snap, measure-length, and beat-length members. */ void qperfroll::set_guides (midipulse snap, midipulse measure, midipulse beat) { set_snap(snap); m_measure_length = measure; m_beat_length = beat; if (is_initialized()) set_dirty(); } void qperfroll::set_adding (bool a) { adding(a); if (a) setCursor(Qt::PointingHandCursor); /* Qt doesn't have a pencil */ else setCursor(Qt::ArrowCursor); frame64()->update_entry_mode(a); /* updates checkable button */ set_dirty(); } void qperfroll::undo () { perf().pop_trigger_undo(); } void qperfroll::redo () { perf().pop_trigger_redo(); } void qperfroll::draw_grid (QPainter & painter, const QRect & r) { int xwidth = r.width(); int yheight = r.height(); int linecounter = 0; QBrush brush(back_color()); /* Qt::NoBrush */ QPen pen(fore_color()); /* Qt::black */ pen.setStyle(Qt::SolidLine); pen.setWidth(horiz_pen_width()); painter.setPen(pen); painter.setBrush(brush); painter.drawRect(0, 0, width(), height()); /* full width */ for (int i = 0; i < height(); i += track_height()) /* horizontal lines */ { bool start_of_set = (linecounter++ % set_size()) == 0; int y = i + c_ycorrection; /* - 2 */ if (start_of_set) pen.setWidth(horiz_pen_width() * 2); painter.setPen(pen); painter.drawLine(0, y, xwidth, y); if (start_of_set) pen.setWidth(horiz_pen_width()); } /* * Draw the vertical lines for the measures and the beats. Incrementing by * the beat-length (PPQN) makes drawing go faster. */ midipulse tick0 = scroll_offset(); midipulse windowticks = z().pix_to_tix(xwidth); midipulse tick1 = tick0 + windowticks; midipulse tickstep = beat_length(); /* versus 1 */ int penwidth = 1; for (midipulse tick = tick0; tick < tick1; tick += tickstep) { int x_pos = xoffset(tick); if (tick % measure_length() == 0) /* measure */ { pen.setColor(beat_paint()); /* fore_color() */ pen.setStyle(measure_pen_style()); penwidth = measure_pen_width(); } else if (tick % beat_length() == 0) /* beat */ { pen.setColor(beat_color()); pen.setStyle(beat_pen_style()); penwidth = beat_pen_width(); } pen.setWidth(penwidth); painter.setPen(pen); painter.drawLine(x_pos, 0, x_pos, yheight); } int x_pos = xoffset(perf().get_max_trigger()); pen.setWidth(3); painter.setPen(pen); painter.drawLine(x_pos, 0, x_pos, yheight); } void qperfroll::draw_triggers (QPainter & painter, const QRect & r) { int y_s = 0; int y_f = r.height() / track_height(); int cbw = c_size_box_w; /* copied for readability */ QBrush brush(Qt::NoBrush); QPen pen(fore_color()); pen.setStyle(Qt::SolidLine); painter.setPen(pen); painter.setBrush(brush); pen.setWidth(1); /* * No real need to enlarge the corner handles, IMHO. * * if (track_thick()) * cbw *= 2; */ for (int y = y_s; y <= y_f; ++y) { int seqid = y; if (perf().is_seq_active(seqid)) { seq::pointer s = perf().get_sequence(seqid); midipulse lens = s->get_length(); /* * Get the seq's assigned colour and beautify it. Reduce the * strength of this color. Not sure why get_color_fix() doesn't * do it here. We can make this a user setting later. */ int c = perf().color(seqid); Color backcolor = get_color_fix(PaletteColor(c)); if (s->armed()) backcolor.setAlpha(c_alpha_playing); else backcolor.setAlpha(c_alpha_muted); int lenw = z().tix_to_pix(lens); int h = track_height() - 1; int cbwoffset = cbw + h / 2 - 2; trigger trig; painter.setFont(m_font); s->reset_draw_trigger_marker(); while (s->next_trigger(trig)) /* side-effect */ { if (trig.tick_end() > 0) { int x_on = z().tix_to_pix(trig.tick_start()); int x_off = z().tix_to_pix(trig.tick_end()); int w = x_off - x_on + 1; int x = x_on; int xmax = x_off + 1; /* same as x + w */ int y = track_height() * seqid - 1; if (use_gradient()) { QLinearGradient grad(x, y, x, y + h + 1); if (trig.selected()) { /* * Nothing to do. */ } else { m_back_grad.setColorAt(0.01, backcolor.darker(150)); m_back_grad.setColorAt(0.5, backcolor.lighter()); m_back_grad.setColorAt(0.99, backcolor.darker(150)); } QLinearGradient & gradref = trig.selected() ? m_sel_grad : m_back_grad ; pen.setStyle(Qt::SolidLine); /* seq trigger box */ pen.setWidth(measure_pen_width()); pen.setColor(fore_color()); /* use box color */ painter.setPen(pen); if (usr().progress_bar_thick()) { painter.fillRect ( x + 1, y + 3, w - 2, h - 1, gradref ); painter.drawRect(x + 1, y + 3, w - 2, h - 1); } else { painter.fillRect ( x + 1, y + 2, w - 2, h - 1, gradref ); painter.drawRect(x + 1, y + 2, w - 2, h - 1); } } else { if (trig.selected()) { pen.setColor(sel_color()); brush.setColor(grey_color()); } else { pen.setColor(fore_color()); brush.setColor(backcolor); } pen.setStyle(Qt::SolidLine); /* seq trigger box */ pen.setWidth(measure_pen_width()); brush.setStyle(Qt::SolidPattern); painter.setBrush(brush); painter.setPen(pen); painter.drawRect(x + 1, y + 1, w - 2, h - 1); } brush.setStyle(Qt::NoBrush); /* grab handle L */ painter.setBrush(brush); pen.setColor(fore_color()); painter.setPen(pen); painter.drawRect(x + 1, y + 2, cbw, cbw); painter.drawRect /* grab handle R */ ( xmax - cbw - 1, y + h - cbw + 2, cbw, cbw ); pen.setColor(fore_color()); pen.setWidth(1); painter.setPen(pen); if (trig.transposed()) { char temp[16]; int t = trig.transpose(); if (t > 0) snprintf(temp, sizeof temp, "+%d", t); else snprintf(temp, sizeof temp, "-%d", -t); int tx = x + 6; if (track_thin()) tx += 6; painter.drawText(tx, y + cbwoffset, temp); } midipulse t = trig.trigger_marker(lens); /* offset */ while (t < trig.tick_end()) { int note0, note1; (void) s->minmax_notes(note0, note1); int height = note1 - note0; height += 2; if (s->transposable()) pen.setColor(fore_color()); else pen.setColor(drum_color()); painter.setPen(pen); int cny = track_height() - 6; int marker_x = z().tix_to_pix(t); for (auto cev = s->cbegin(); ! s->cend(cev); ++cev) { sequence::note_info ni; sequence::draw dt = s->get_next_note(ni, cev); if (dt == sequence::draw::finish) break; midipulse tick_s = ni.start(); int sx = tick_s * lenw / lens + marker_x; if (dt == sequence::draw::tempo) { midibpm max = usr().midi_bpm_maximum(); midibpm min = usr().midi_bpm_minimum(); double tempo = double(ni.velocity()); int yt = int(cny * (max-tempo) / (max-min)) + y; if (sx < x) sx = x; if (sx <= xmax) painter.drawEllipse(sx, yt, 3, 3); } else { midipulse tick_f = ni.finish(); int note_y = ( cny - (cny * (ni.note() - note0)) / height ) + 1 + y; int fx = tick_f * lenw / lens + marker_x; if (sequence::is_draw_note_onoff(dt)) fx = sx + 1; if (fx <= sx) fx = sx + 1; if (sx < x) sx = x; if (fx > xmax) fx = xmax; if (fx >= x && sx <= xmax) painter.drawLine(sx, note_y, fx, note_y); } } t += lens; } } } } } } } // namespace seq66 /* * qperfroll.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qperftime.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qperftime.cpp * * The time bar shows markers and numbers for the measures of the song, * and also depicts the left and right markers. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2026-04-30 * \license GNU GPLv2 or above * * Compare to perftime, the Gtkmm-2.4 implementation of this class. */ #include #include #include #include "cfg/settings.hpp" #include "play/performer.hpp" /* seq66::performer class */ #include "qperfeditframe64.hpp" #include "qperftime.hpp" #include "qt5_helpers.hpp" /* seq66::qt_timer() */ namespace seq66 { /* * Tweaks. The presence of corrections means we need to coordinate * between GUI elements better. */ static const int s_time_fix = 2; /* adjust position of lines */ static const int s_L_fix = 2; /* adjust position of "L" box */ static const int s_end_fix = 22; /* adjust position of "END" box */ static const int s_font_size = 6; static const int s_font_size_large = 9; static const int s_text_y_offset = 22; /** * Principal constructor. */ qperftime::qperftime ( performer & p, int zoom, int snap, qperfeditframe64 * frame, QWidget * parent ) : QWidget (parent), qperfbase (p, zoom, snap, 1, 1 * 1), m_parent_frame (frame), /* frame64() accessor */ m_timer (nullptr), /* refresh/redraw timer */ m_font (), m_move_L_marker (false) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); setFocusPolicy(Qt::StrongFocus); m_font.setBold(true); /* * This affects L/R too, and also requires adding to the y offset. */ int pointsize = usr().progress_bar_thick() ? s_font_size_large : s_font_size ; m_font.setPointSize(pointsize); setMouseTracking(true); /* track mouse movement without a click */ m_timer = qt_timer(this, "qperftime", 2, SLOT(conditional_update())); } /** * This virtual destructor stops the timer. */ qperftime::~qperftime () { if (not_nullptr(m_timer)) m_timer->stop(); } /** * A timer callback/slot that updates the window only if it needs it. * Without the check for needing to update, it is always called and increase * the CPU load. */ void qperftime::conditional_update () { if (perf().needs_update() || check_dirty() ) update(); } void qperftime::paintEvent (QPaintEvent * /*qpep*/) { if (measure_length() == 0) { info_message("qperftime measure-length is 0, cannot draw"); return; } int xwidth = width(); int yheight = height(); QPainter painter(this); QBrush brush(backtime_paint(), Qt::SolidPattern); QPen pen(fore_color()); painter.setBrush(brush); painter.setFont(m_font); painter.drawRect(0, 0, xwidth, yheight); if (! is_initialized()) set_initialized(); /* * Draw the vertical lines for the measures and the beats. */ midipulse tick0 = scroll_offset(); midipulse windowticks = z().pix_to_tix(xwidth); midipulse tick1 = tick0 + windowticks; midipulse tickstep = beat_length(); /* versus 1 */ int measure = 0; for (midipulse tick = tick0; tick < tick1; tick += tickstep) { int x_pos = xoffset(tick) - scroll_offset_x() - s_time_fix; if (tick % measure_length() == 0) { pen.setColor(beat_color()); /* measure */ pen.setWidth(measure_pen_width()); pen.setStyle(measure_pen_style()); painter.setPen(pen); painter.drawLine(x_pos, 2, x_pos, yheight); /* * Draw the measure numbers if they will fit. Determined * currently by trial and error. */ if (zoom() >= c_minimum_zoom && zoom() <= c_maximum_zoom) { QString bar(QString::number(measure + 1)); pen.setColor(text_time_paint()); /* Qt::black */ painter.setPen(pen); painter.drawText(x_pos + 2, 10, bar); } ++measure; } else if (tick % beat_length() == 0) /* beat */ { pen.setColor(beat_color()); pen.setWidth(beat_pen_width()); pen.setStyle(beat_pen_style()); /* * We should match the solid line of the piano roll. * pen.setStyle(Qt::DotLine); */ painter.setPen(pen); painter.drawLine(x_pos, 2, x_pos, yheight); } } int xoff_left = scroll_offset_x(); int xoff_right = scroll_offset_x() + xwidth; int end = xoffset(perf().get_max_trigger()) - s_end_fix; int left = xoffset(perf().get_left_tick()) + s_L_fix; int right = xoffset(perf().get_right_tick()); int now = xoffset(perf().get_tick()); if (! perf().is_pattern_playing() && (now != left) && (now != right)) { if (now >= xoff_left && now <= xoff_right) { pen.setColor(progress_color()); painter.setPen(pen); painter.drawText(now - 2, 18, "o"); } } if (left >= xoff_left && left <= xoff_right) { pen.setColor(Qt::black); brush.setColor(Qt::black); painter.setBrush(brush); painter.setPen(pen); painter.drawRect(left, yheight - 12, 7, 10); pen.setColor(Qt::white); painter.setPen(pen); painter.drawText(left + 2, s_text_y_offset, "L"); } if (right >= xoff_left && right <= xoff_right) { int r = right - 10; pen.setColor(Qt::black); brush.setColor(Qt::black); painter.setBrush(brush); painter.setPen(pen); painter.drawRect(r, yheight - 12, 7, 10); pen.setColor(Qt::white); painter.setPen(pen); painter.drawText(r, s_text_y_offset, "R"); } if (end > right) { pen.setColor(Qt::black); brush.setColor(Qt::black); painter.setBrush(brush); painter.setPen(pen); painter.drawRect(end, yheight - 12, 21, 10); pen.setColor(Qt::white); painter.setPen(pen); painter.drawText(end - 3, s_text_y_offset, "END"); } } QSize qperftime::sizeHint () const { int height = 24; int width = horizSizeHint(); int w = frame64()->width(); if (width < w) width = w; width *= width_factor(); return QSize(width, height); } /** * We don't want the scroll wheel to accidentally scroll the time * horizontally, so this override does nothing but accept() the event. * * ignore() just let's the parent handle the event, which allows scrolling to * occur. For issue #3, we have enabled the scroll wheel in the piano roll * [see qscrollmaster::wheelEvent()], but we disable it here. So this is a * partial solution to the issue. */ void qperftime::wheelEvent (QWheelEvent * qwep) { qwep->accept(); } void qperftime::keyPressEvent (QKeyEvent * event) { bool isctrl = bool(event->modifiers() & Qt::ControlModifier); if (isctrl) { /* no code yet */ } else { midipulse s = snap() > 0 ? snap() : 1 ; if (event->key() == Qt::Key_Left) { if (m_move_L_marker) { midipulse tick = perf().get_left_tick() - s; perf().set_left_tick(tick); } else { midipulse tick = perf().get_right_tick() - s; perf().set_right_tick(tick); } set_dirty(); event->accept(); } else if (event->key() == Qt::Key_Right) { if (m_move_L_marker) { midipulse tick = perf().get_left_tick() + s; perf().set_left_tick(tick); } else { midipulse tick = perf().get_right_tick() + s; perf().set_right_tick(tick); } set_dirty(); event->accept(); } else if (event->key() == Qt::Key_L) { m_move_L_marker = true; /* case doesn't matter */ } else if (event->key() == Qt::Key_R) { m_move_L_marker = false; /* case doesn't matter */ } } } /** * There is a trick to this function that to document and take further * advantage of. If clicking in the top half of the time-bar, whether and * L- or R-click, the time is set to that value, and the effect can be * seen in the main window's beat-indicator. * * If clicking in the bottom * half, of course, the L or R marker is set, depending on which button * is pressed. * * We need to figure out the difference between setting start-tick and * left-tick. It might be better to make the two represent the same concept. */ void qperftime::mousePressEvent (QMouseEvent * ev) { midipulse tick = z().pix_to_tix(qt_mouse_x(ev)); if (snap() > 0) tick -= (tick % snap()); if (qt_mouse_y(ev) > height() / 2) /* see banner note */ { bool isctrl = bool(ev->modifiers() & Qt::ControlModifier); if (ev->button() == Qt::LeftButton) /* move L/R markers */ { if (isctrl) perf().set_tick(tick, true); /* set_start_tick() */ else { perf().set_left_tick_snap(tick, snap()); /* * ca 2025-07-03. Do we need this? See qseqtime :: * mousePressEvent(). * * perf().set_last_ticks(get_left_tick() */ } set_dirty(); } else if (ev->button() == Qt::MiddleButton) /* set start tick */ { perf().set_tick(tick, true); /* set_start_tick() */ set_dirty(); } else if (ev->button() == Qt::RightButton) { perf().set_right_tick(tick + snap()); set_dirty(); } } else { perf().set_tick(tick, true); /* reposition time */ set_dirty(); } if (is_dirty()) frame64()->set_dirty(); } void qperftime::mouseReleaseEvent (QMouseEvent *) { // no code } void qperftime::mouseMoveEvent (QMouseEvent * ev) { setCursor ( qt_mouse_y(ev) > height() / 2 ? Qt::PointingHandCursor : Qt::UpArrowCursor ); } void qperftime::set_guides (midipulse snap, midipulse measure, midipulse beat) { set_snap(snap); m_measure_length = measure; m_beat_length = beat; if (is_initialized()) set_dirty(); } } // namespace seq66 /* * qperftime.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qplaylistframe.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qplaylistframe.cpp * * This module declares/defines the base class for plastering * pattern/sequence data information in the data area of the pattern * editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-09-04 * \updates 2023-09-07 * \license GNU GPLv2 or above * */ #include /* QErrorMessage */ #include /* Needed for QKeyEvent::accept() */ #include #include "cfg/settings.hpp" /* seq66::rc() and seq66::usr() */ #include "play/performer.hpp" /* seq66::performer */ #include "util/filefunctions.hpp" /* seq66::filename_split() */ #include "util/strfunctions.hpp" /* seq66::string_to_int() */ #include "qplaylistframe.hpp" /* seq66::qplaylistframe child */ #include "qsmainwnd.hpp" /* seq66::qsmainwnd, a parent */ #include "qt5_helpers.hpp" /* seq66::qt_set_icon() etc. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qplaylistframe.h" #else #include "forms/qplaylistframe.ui.h" #endif namespace seq66 { /** * For correcting the width of the play-list tables. It tries to account for * the width of the vertical scroll-bar, plus a bit more. */ static const int c_playlist_table_fix = 24; // 48 /** * Specifies the current hardwired value for set_row_heights(). */ static const int c_playlist_row_height = 18; /** * Principal constructor. */ qplaylistframe::qplaylistframe ( performer & p, qsmainwnd * window, QWidget * frameparent ) : QFrame (frameparent), ui (new Ui::qplaylistframe), m_timer (nullptr), m_performer (p), m_parent (window), m_current_list_index (0), m_current_song_index (0) { ui->setupUi(this); QStringList playcolumns; playcolumns << "#" << "Playlist Names"; ui->tablePlaylistSections->setHorizontalHeaderLabels(playcolumns); ui->tablePlaylistSections->setSelectionBehavior ( QAbstractItemView::SelectRows /* QAbstractItemView::SelectItems */ ); ui->tablePlaylistSections->setSelectionMode ( QAbstractItemView::SingleSelection ); QStringList songcolumns; songcolumns << "#" << "Song Files in List"; ui->tablePlaylistSongs->setHorizontalHeaderLabels(songcolumns); ui->tablePlaylistSongs->setSelectionBehavior(QAbstractItemView::SelectRows); ui->tablePlaylistSongs->setSelectionMode ( QAbstractItemView::SingleSelection ); set_row_heights(c_playlist_row_height); set_column_widths(); connect ( ui->tablePlaylistSections, SIGNAL(currentCellChanged(int, int, int, int)), this, SLOT(slot_list_click_ex(int, int, int, int)) ); connect ( ui->tablePlaylistSongs, SIGNAL(currentCellChanged(int, int, int, int)), this, SLOT(slot_song_click_ex(int, int, int, int)) ); connect ( ui->buttonPlaylistCreate, SIGNAL(clicked(bool)), this, SLOT(slot_file_create_click()) ); connect ( ui->buttonSelectSongDir, SIGNAL(clicked(bool)), this, SLOT(slot_list_dir_click()) ); connect ( ui->buttonSelectPlaylist, SIGNAL(clicked(bool)), this, SLOT(slot_list_load_click()) ); connect ( ui->buttonPlaylistAdd, SIGNAL(clicked(bool)), this, SLOT(slot_list_add_click()) ); connect ( ui->buttonPlaylistModify, SIGNAL(clicked(bool)), this, SLOT(slot_list_modify_click()) ); connect ( ui->buttonPlaylistRemove, SIGNAL(clicked(bool)), this, SLOT(slot_list_remove_click()) ); if (not_nullptr(parent())) { ui->buttonPlaylistSave->setText("Save Lists"); if (parent()->use_nsm()) { ui->buttonPlaylistSave->setToolTip ( "Save playlists/MIDI files in NSM session." ); ui->editPlaylistPath->setReadOnly(true); ui->editPlaylistPath->setEnabled(false); ui->editSongPath->setReadOnly(true); ui->editSongPath->setEnabled(false); ui->entry_playlist_file->setReadOnly(true); ui->entry_playlist_file->setEnabled(false); } else { ui->buttonPlaylistSave->setToolTip ( "Save playlists (only) to current directory." ); connect ( ui->editPlaylistPath, SIGNAL(textEdited(QString)), this, SLOT(list_modify(QString)) ); connect ( ui->editSongPath, SIGNAL(textEdited(QString)), this, SLOT(song_modify(QString)) ); } } connect ( ui->buttonPlaylistSave, SIGNAL(clicked(bool)), this, SLOT(slot_list_save_click()) ); #if defined USE_REDUNDANT_SONGLOAD_BUTTON connect ( ui->buttonSongLoad, SIGNAL(clicked(bool)), this, SLOT(slot_song_load_click()) ); #else ui->buttonSongLoad->setText(" "); ui->buttonSongLoad->setEnabled(false); ui->buttonSongLoad->hide(); #endif connect ( ui->buttonSelectSong, SIGNAL(clicked(bool)), this, SLOT(slot_song_select_click()) ); connect ( ui->buttonSongAdd, SIGNAL(clicked(bool)), this, SLOT(slot_song_add_click()) ); connect ( ui->buttonSongModify, SIGNAL(clicked(bool)), this, SLOT(slot_song_modify_click()) ); connect ( ui->buttonSongRemove, SIGNAL(clicked(bool)), this, SLOT(slot_song_remove_click()) ); connect ( ui->checkBoxPlaylistActive, SIGNAL(clicked(bool)), this, SLOT(slot_playlist_active_click()) ); connect ( ui->checkBoxAutoArm, SIGNAL(clicked(bool)), this, SLOT(slot_auto_arm_click()) ); connect ( ui->checkBoxAutoPlay, SIGNAL(clicked(bool)), this, SLOT(slot_auto_play_click()) ); connect ( ui->checkBoxAutoAdvance, SIGNAL(clicked(bool)), this, SLOT(slot_auto_advance_click()) ); connect ( ui->entry_playlist_file, SIGNAL(textEdited(QString)), this, SLOT(list_modify(QString)) ); #if defined USE_PLAYLIST_NUMBER_TEXTEDIT connect ( ui->editPlaylistNumber, SIGNAL(textEdited(QString)), this, SLOT(list_modify(QString)) ); #else ui->editPlaylistNumber->hide(); set_spin_value(ui->spinPlaylistNumber, 0); connect ( ui->spinPlaylistNumber, SIGNAL(valueChanged(int)), this, SLOT(list_modify(int)) ); connect ( ui->spinPlaylistNumber, SIGNAL(editingFinished()), this, SLOT(list_modify()) ); #endif #if defined USE_SONG_NUMBER_TEXTEDIT connect ( ui->editSongNumber, SIGNAL(textEdited(QString)), this, SLOT(song_modify(QString)) ); #else ui->editSongNumber->hide(); set_spin_value(ui->spinSongNumber, 0); connect ( ui->spinSongNumber, SIGNAL(valueChanged(int)), this, SLOT(song_modify(int)) ); connect ( ui->spinSongNumber, SIGNAL(editingFinished()), this, SLOT(song_modify()) ); #endif /* * This field is now read-only, as is the base MIDI file name. * The new "load song" button (buttonSelectSong) must be used. */ #if defined SEQ66_ALLOW_FILENAME_EDIT connect ( ui->editSongFilename, SIGNAL(textEdited(QString)), this, SLOT(song_modify(QString)) ); #else ui->editSongFilename->setEnabled(false); /* now stays disabled */ #endif enable_midi_widgets(false); ui->midiBaseDirText->setReadOnly(true); ui->midiBaseDirText->setEnabled(false); ui->currentSongPath->setReadOnly(true); ui->currentSongPath->setEnabled(false); reset_playlist(); /* if (perf().playlist_mode()) */ m_timer = qt_timer(this, "qplaylistframe", 4, SLOT(conditional_update())); } /** * Stops the timer and deletes the user interface. */ qplaylistframe::~qplaylistframe () { if (not_nullptr(m_timer)) m_timer->stop(); delete ui; } /** * In an effort to reduce CPU usage when simply idling, this function calls * update() only if necessary. See qseqbase::needs_update(). We * must check all sequences. */ void qplaylistframe::conditional_update () { if (perf().needs_update()) /* potentially checks all sequences */ update(); } /** * Sets the height of the rows in the list and song tables. */ void qplaylistframe::set_row_heights (int height) { const int prows = ui->tablePlaylistSections->rowCount(); for (int pr = 0; pr < prows; ++pr) ui->tablePlaylistSections->setRowHeight(pr, height); const int rows = ui->tablePlaylistSongs->rowCount(); for (int sr = 0; sr < rows; ++sr) ui->tablePlaylistSongs->setRowHeight(sr, height); } /** * Scales the columns against the provided window width. */ void qplaylistframe::set_column_widths () { int w = ui->tablePlaylistSections->width() - c_playlist_table_fix; ui->tablePlaylistSections->setColumnWidth(0, int(0.20f * w)); ui->tablePlaylistSections->setColumnWidth(1, int(0.80f * w)); w = ui->tablePlaylistSongs->width() - c_playlist_table_fix; ui->tablePlaylistSongs->setColumnWidth(0, int(0.20f * w)); ui->tablePlaylistSongs->setColumnWidth(1, int(0.80f * w)); } /** * Enables/disables the MIDI-file buttons and edit controls to prevent * trying to add a MIDI file when there is no playlist selected. */ void qplaylistframe::enable_midi_widgets (bool enable) { ui->editSongPath->setEnabled(enable); #if defined USE_SONG_NUMBER_TEXTEDIT ui->editSongNumber->setEnabled(enable); #else ui->spinSongNumber->setEnabled(enable); #endif ui->buttonSelectSong->setEnabled(enable); ui->buttonSongLoad->setEnabled(enable); } /** * Resets the play-list. First, resets to the first (0th) play-list and the * first (0th) song. Then fills the play-list items and resets again. Then * fills in the play-list and song items for the current selection. * * \param listindex * The play-list to reset to, defaulting to the 0th list. However, when * deleting a song, we want to reset to the current playlist. */ void qplaylistframe::reset_playlist (int listindex) { if (listindex >= 0) { bool usable = perf().playlist_reset(); fill_playlists(); fill_songs(); if (listindex > 0) (void) perf().playlist_reset(listindex); set_current_playlist(); ui->tablePlaylistSections->selectRow(listindex); ui->tablePlaylistSongs->selectRow(0); ui->buttonPlaylistRemove->setEnabled(usable); ui->buttonSongRemove->setEnabled(usable); } else { ui->buttonPlaylistRemove->setEnabled(false); ui->buttonSongRemove->setEnabled(false); } } void qplaylistframe::reset_playlist_file_name () { QString fname = qt(rc().playlist_filespec()); } void qplaylistframe::set_current_playlist () { std::string temp; ui->checkBoxPlaylistActive->setChecked(perf().playlist_active()); ui->checkBoxAutoArm->setChecked(perf().playlist_auto_arm()); ui->checkBoxAutoPlay->setChecked(perf().playlist_auto_play()); ui->checkBoxAutoAdvance->setChecked(perf().playlist_auto_advance()); temp = perf().playlist_filename(); ui->entry_playlist_file->setText(qt(temp)); temp = perf().file_directory(); ui->editPlaylistPath->setText(qt(temp)); int midinumber = perf().playlist_midi_number(); if (midinumber < 0) midinumber = 0; #if defined USE_PLAYLIST_NUMBER_TEXTEDIT temp = std::to_string(midinumber); if (temp.empty()) temp = "0"; ui->editPlaylistNumber->setText(qt(temp)); #else set_spin_value(ui->spinPlaylistNumber, midinumber); #endif temp = perf().playlist_midi_base(); ui->midiBaseDirText->setText(qt(temp)); temp = perf().playlist_name(); ui->editPlaylistName->setText(qt(temp)); set_current_song(); } void qplaylistframe::set_current_song () { int rows = perf().song_count(); if (rows > 0) { #if defined USE_SONG_NUMBER_TEXTEDIT std::string temp = std::to_string(perf().song_midi_number()); ui->editSongNumber->setText(qt(temp)); temp = perf().song_directory(); if (temp.empty()) temp = "None"; #else int midinumber = perf().song_midi_number(); set_spin_value(ui->spinSongNumber, midinumber); std::string temp = perf().song_directory(); #endif ui->editSongPath->setText(qt(temp)); bool embedded = perf().is_own_song_directory(); temp = embedded ? "*" : " " ; ui->labelDirEmbedded->setText(qt(temp)); temp = perf().song_filename(); ui->editSongFilename->setText(qt(temp)); temp = perf().song_filepath(); ui->currentSongPath->setText(qt(temp)); } else { ui->labelDirEmbedded->setText(" "); } } /** * Retrieves the table cell at the given row and column. * * \param isplaylist * If true, this call affects the play-list table. Otherwise, it affects * the song-list table. * * \param row * The row number, which should be in range. * * \param col * The column enumeration value, which will be in range. * * \return * Returns a pointer the table widget-item for the given row and column. * If out-of-range, a null pointer is returned. */ QTableWidgetItem * qplaylistframe::cell (bool isplaylist, int row, column_id_t col) { int column = int(col); QTableWidget * listptr = isplaylist ? ui->tablePlaylistSections : ui->tablePlaylistSongs ; QTableWidgetItem * result = listptr->item(row, column); if (is_nullptr(result)) { /* * Will test row/column and maybe add rows on the fly later. */ result = new QTableWidgetItem; listptr->setItem(row, column, result); } return result; } /** * Adds the list of playlists to the tablePlaylistSections table-widget. It * does not select a play-list or select a song. To be called only when * loading a new play-list, as in reset_playlist(). Only acts if the list is * non-empty. */ void qplaylistframe::fill_playlists () { int rows = perf().playlist_count(); if (rows > 0) { ui->tablePlaylistSections->clearContents(); ui->tablePlaylistSections->setRowCount(rows); for (int r = 0; r < rows; ++r) { std::string temp; QTableWidgetItem * qtip = cell(true, r, CID_MIDI_NUMBER); ui->tablePlaylistSections->setRowHeight(r, c_playlist_row_height); if (not_nullptr(qtip)) { int midinumber = perf().playlist_midi_number(); temp = std::to_string(midinumber); qtip->setText(qt(temp)); } qtip = cell(true, r, CID_ITEM_NAME); if (not_nullptr(qtip)) { temp = perf().playlist_name(); qtip->setText(qt(temp)); } /* * Load the next list. The false means "don't load the song", and * the true means "we're loading the playlist, so go to the next * playlist even if not active." */ if (! perf().open_next_list(false, true)) break; } /* * Only when list selected: ui->buttonPlaylistRemove->setEnabled(true); * Only when song selected: ui->buttonSongRemove->setEnabled(true); */ } else { /* * This call cleans out the grid. * * ui->tablePlaylistSections->setRowCount(0); */ ui->tablePlaylistSections->clearContents(); ui->buttonPlaylistRemove->setEnabled(false); ui->buttonSongRemove->setEnabled(false); } } /** * Adds the songs of the current playlist to the tablePlaylistSongs * table-widget. It does not select any song. */ void qplaylistframe::fill_songs () { int rows = perf().song_count(); if (rows > 0) { ui->tablePlaylistSongs->clearContents(); ui->tablePlaylistSongs->setRowCount(rows); for (int r = 0; r < rows; ++r) { std::string temp; if (perf().open_select_song_by_index(r, false)) { QTableWidgetItem * qtip = cell(false, r, CID_MIDI_NUMBER); ui->tablePlaylistSongs->setRowHeight ( r, c_playlist_row_height ); if (not_nullptr(qtip)) { int midinumber = perf().song_midi_number(); temp = std::to_string(midinumber); qtip->setText(qt(temp)); } qtip = cell(false, r, CID_ITEM_NAME); if (not_nullptr(qtip)) { temp = perf().song_filename(); qtip->setText(qt(temp)); } } else break; } } else { ui->tablePlaylistSongs->clearContents(); ui->editSongPath->setText(""); #if defined USE_SONG_NUMBER_TEXTEDIT ui->editSongNumber->setText("0"); #else ui->spinSongNumber->setValue(0); #endif ui->editSongFilename->setText(""); ui->buttonSongRemove->setEnabled(false); } } bool qplaylistframe::load_playlist (const std::string & fullfilespec) { bool result = ! fullfilespec.empty(); if (result) { bool opened = perf().open_playlist(fullfilespec); if (opened) { opened = perf().open_current_song(); reset_playlist(); } else { reset_playlist(); // reset_playlist_file_name(); } update(); /* refresh the user-interface */ } return result; } /** * Weird, this function sometimes receives a row of -1, even when * ui->tablePlaylistSections->clearContents() is called. We ignore this * happenstance without showing a message. */ void qplaylistframe::slot_list_click_ex ( int row, int /*column*/, int /*prevrow*/, int /*prevcolumn*/ ) { if (row >= 0) { m_current_list_index = row; if (perf().open_select_list_by_index(row, false)) { fill_songs(); set_current_playlist(); ui->tablePlaylistSongs->selectRow(0); ui->buttonPlaylistRemove->setEnabled(true); enable_midi_widgets(true); } } } /** * Weird, this function sometimes receives a row of -1, even when * ui->tablePlaylistSongs->clearContents() is called. We ignore this * happenstance without showing a message. */ void qplaylistframe::slot_song_click_ex ( int row, int /*column*/, int /*prevrow*/, int /*prevcolumn*/ ) { if (row >= 0) { m_current_song_index = row; if (perf().open_select_song_by_index(row, true)) { set_current_song(); if (not_nullptr(parent())) parent()->recreate_all_slots(); ui->buttonSongRemove->setEnabled(true); } } } void qplaylistframe::slot_file_create_click () { if (not_nullptr(parent())) { if (parent()->specify_playlist()) ui->buttonPlaylistSave->setEnabled(false); else ui->entry_playlist_file->setText(""); } } /** * Allows the user to select a directory to serve as the location for * the MIDI files of the currently-selected play-list sub-list. */ void qplaylistframe::slot_list_dir_click () { if (not_nullptr(parent())) { std::string folder = parent()->specify_playlist_folder(); if (! folder.empty()) { std::string listname; auto spos = folder.find_last_of("/"); if (spos != std::string::npos) { listname = folder.substr(spos + 1); listname += " "; } listname += "MIDI Files"; ui->editPlaylistPath->setText(qt(folder)); ui->editPlaylistName->setText(qt(listname)); ui->buttonPlaylistAdd->setEnabled(true); int highnumber = perf().next_available_list_number(); ui->spinPlaylistNumber->setValue(highnumber); list_modify(); } } } /** * Calls qsmainwnd::open_playlist(), which then opens the file dialog and * perform::open_playlist(), which resets the play-list pointer and opens the * playlist. It is reset to the first song and performer calls * set_needs_update(). */ void qplaylistframe::slot_list_load_click () { if (not_nullptr(parent())) { if (parent()->open_playlist()) { ui->buttonPlaylistSave->setEnabled(false); rc().auto_rc_save(true); parent()->enable_reload_button(true); } } } /** * This function adds an empty list to the set of playlists in the playlist * object. The user is prompted for the characteristics of the list: * * - The MIDI control number that selects this list. It must not match * any existing MIDI control number for list selection. This number * is supplied by editing the left "Current" field, * ui->editPlaylistNumber. * - The human-friendly name of the list. This name is supplied in the * left unnamed field, ui->editPlaylistName. * - The optional directory where the contents (MIDI files) of the list * are located. The default is the same directory as the playlist. * It is supplied in the left "Directory" field, * ui->editPlaylistPath. If in an NSM session, it is recommended to * leave it as the session's "midi" directory. * * Once this is done, then songs can be added. */ void qplaylistframe::slot_list_add_click () { if (not_nullptr(parent())) { QString temp = ui->editPlaylistPath->text(); std::string listpath = temp.toStdString(); std::string listname; int index = perf().playlist_count(); /* useful number? */ if (index < 127) { int midinumber = index; /* useful number? */ temp = ui->editPlaylistName->text(); listname = temp.toStdString(); #if defined USE_PLAYLIST_NUMBER_TEXTEDIT temp = ui->editPlaylistNumber->text(); midinumber = string_to_int(temp.toStdString(), index); #else midinumber = ui->spinPlaylistNumber->value(); #endif if (perf().add_list(index, midinumber, listname, listpath)) { reset_playlist(); fill_songs(); /* just clears list */ ui->buttonPlaylistSave->setEnabled(true); } else { /* * TODO: report error */ } } } } void qplaylistframe::slot_list_modify_click () { if (not_nullptr(parent())) { QString temp = ui->editPlaylistPath->text(); std::string listpath = temp.toStdString(); std::string listname; int index = m_current_list_index; int midinumber; temp = ui->editPlaylistName->text(); listname = temp.toStdString(); #if defined USE_PLAYLIST_NUMBER_TEXTEDIT temp = ui->editPlaylistNumber->text(); midinumber = string_to_int(temp.toStdString(), index); #else midinumber = ui->spinPlaylistNumber->value(); #endif if (perf().modify_list(index, midinumber, listname, listpath)) { reset_playlist(index); fill_songs(); ui->buttonPlaylistSave->setEnabled(true); } else { /* * TODO: report error */ } } } void qplaylistframe::slot_list_remove_click () { if (not_nullptr(parent())) { int index = m_current_list_index; if (perf().remove_list(index)) { reset_playlist(); parent()->recreate_all_slots(); parent()->enable_reload_button(true); ui->buttonPlaylistSave->setEnabled(true); } } } void qplaylistframe::slot_list_save_click () { if (not_nullptr(parent())) { if (parent()->save_playlist()) { rc().auto_rc_save(true); list_unmodify(); song_unmodify(); parent()->enable_reload_button(true); } } } /** * This function supports loading a song from a file-selection dialog. * Compare it to slot_song_add_click(). */ void qplaylistframe::slot_song_load_click () { if (not_nullptr(parent())) { std::string selectedfile = ui->editSongPath->text().toStdString(); if (parent()->use_nsm()) { bool ok = parent()->load_into_session(selectedfile); if (ok) { ok = perf().add_song(selectedfile); if (ok) { fill_songs(); /* too much: reset_playlist(); */ parent()->recreate_all_slots(); parent()->enable_reload_button(true); ui->buttonPlaylistSave->setEnabled(true); } } } else { if (show_open_midi_file_dialog(this, selectedfile)) { bool ok = perf().add_song(selectedfile); if (ok) { fill_songs(); /* too much: reset_playlist(); */ parent()->recreate_all_slots(); parent()->enable_reload_button(true); ui->buttonPlaylistSave->setEnabled(true); } } } } } /** * Lets one select a MIDI file from a file dialog to fill in the MIDI * information. */ void qplaylistframe::slot_song_select_click () { if (not_nullptr(parent())) { std::string selectedfile = ui->editSongPath->text().toStdString(); if (show_open_midi_file_dialog(this, selectedfile)) { std::string path; std::string basename; if (filename_split(selectedfile, path, basename)) { ui->editSongPath->setText(qt(path)); ui->editSongFilename->setText(qt(basename)); #if defined USE_SONG_NUMBER_TEXTEDIT ui->editSongNumber->setText("Unique MIDI #"); #else /* * Find the highest song number and add one to it. * Base it on playlist::select_song(). * Should this be done in fill_songs()??? * If so, what about fill_playlists() or */ int highnumber = perf().next_available_song_number(); ui->spinSongNumber->setValue(highnumber); #endif song_modify(); } } } } /** * These values depend on correct information edited into the Song text * fields. We support loading a song from a file-selection dialog, so that * is the preferred method; see slot_song_load_click(). */ void qplaylistframe::slot_song_add_click () { if (not_nullptr(parent())) { std::string name = ui->editSongFilename->text().toStdString(); std::string directory = ui->editSongPath->text().toStdString(); name = filename_concatenate(directory, name); bool loadedfile = ! name.empty(); if (loadedfile) { std::string directory; std::string basename; bool ok = filename_split(name, directory, basename); if (ok) { #if defined USE_SONG_NUMBER_TEXTEDIT std::string nstr = ui->editSongNumber->text().toStdString(); int midinumber = string_to_int(nstr); #else int midinumber = ui->spinSongNumber->value(); #endif int index = perf().song_count() + 1; if (perf().add_song(index, midinumber, basename, directory)) { int listindex = m_current_list_index; reset_playlist(listindex); parent()->recreate_all_slots(); parent()->enable_reload_button(true); song_unmodify(); } else { QErrorMessage * errbox = new QErrorMessage(this); errbox->showMessage("Error adding song, fix MIDI #"); errbox->exec(); } } } } } void qplaylistframe::slot_song_modify_click () { if (not_nullptr(parent())) { std::string name = ui->editSongFilename->text().toStdString(); std::string directory = ui->editSongPath->text().toStdString(); #if defined USE_SONG_NUMBER_TEXTEDIT std::string nstr = ui->editSongNumber->text().toStdString(); int midinumber = string_to_int(nstr); #else int midinumber = ui->spinSongNumber->value(); #endif int songindex = m_current_song_index; if (perf().modify_song(songindex, midinumber, name, directory)) { int listindex = m_current_list_index; reset_playlist(listindex); parent()->recreate_all_slots(); parent()->enable_reload_button(true); } else { /* * TODO: report error */ } } } void qplaylistframe::slot_song_remove_click () { if (not_nullptr(parent())) { int listindex = m_current_list_index; int songindex = m_current_song_index; if (perf().remove_song_by_index(songindex)) { reset_playlist(listindex); parent()->recreate_all_slots(); parent()->enable_reload_button(true); ui->buttonPlaylistSave->setEnabled(true); } else { /* * TODO: report error */ } } } void qplaylistframe::slot_playlist_active_click () { if (not_nullptr(parent())) { bool on = ui->checkBoxPlaylistActive->isChecked(); bool success = perf().playlist_activate(on); if (success) { rc().auto_rc_save(true); if (on) /* leave patterns in if off */ parent()->recreate_all_slots(); /* why? */ parent()->enable_reload_button(true); } ui->checkBoxPlaylistActive->setChecked(perf().playlist_active()); ui->buttonPlaylistSave->setEnabled(true); } } void qplaylistframe::slot_auto_arm_click () { if (not_nullptr(parent())) { bool on = ui->checkBoxAutoArm->isChecked(); perf().playlist_auto_arm(on); ui->buttonPlaylistSave->setEnabled(true); parent()->enable_reload_button(true); } } void qplaylistframe::slot_auto_play_click () { if (not_nullptr(parent())) { bool on = ui->checkBoxAutoPlay->isChecked(); perf().playlist_auto_play(on); ui->buttonPlaylistSave->setEnabled(true); ui->checkBoxAutoArm->setChecked(true); /* tricky */ slot_auto_arm_click(); } } void qplaylistframe::slot_auto_advance_click () { if (not_nullptr(parent())) { bool on = ui->checkBoxAutoAdvance->isChecked(); perf().playlist_auto_advance(on); ui->buttonPlaylistSave->setEnabled(true); ui->checkBoxAutoPlay->setChecked(true); /* tricky */ slot_auto_play_click(); } } void qplaylistframe::list_modify (const QString &) { list_modify(); } void qplaylistframe::list_unmodify () { ui->buttonPlaylistAdd->setEnabled(false); ui->buttonPlaylistModify->setEnabled(false); ui->buttonPlaylistSave->setEnabled(false); } void qplaylistframe::list_modify (int) { list_modify(); } void qplaylistframe::list_modify () { ui->buttonPlaylistAdd->setEnabled(true); ui->buttonPlaylistModify->setEnabled(true); ui->buttonPlaylistSave->setEnabled(true); parent()->enable_reload_button(true); } void qplaylistframe::song_modify (const QString &) { song_modify(); } void qplaylistframe::song_unmodify () { ui->buttonSongAdd->setEnabled(false); ui->buttonSongModify->setEnabled(false); ui->buttonPlaylistSave->setEnabled(true); } void qplaylistframe::song_modify (int) { song_modify(); } void qplaylistframe::song_modify () { /* * Why this check? Makes no sense. * * bool addable = ! rc().midi_filename().empty(); * if (addable) */ ui->buttonSongAdd->setEnabled(true); ui->buttonSongModify->setEnabled(true); ui->buttonPlaylistSave->setEnabled(true); parent()->enable_reload_button(true); } /* * We must accept() the key-event, otherwise even key-events in the QLineEdit * items are propagated to the parent, where they then get passed to the * performer as if they were keyboards controls (such as a pattern-toggle * hot-key). */ void qplaylistframe::keyPressEvent (QKeyEvent * event) { event->accept(); } void qplaylistframe::keyReleaseEvent (QKeyEvent * event) { event->accept(); } } // namespace seq66 /* * qplaylistframe.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qportwidget.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qportwidget.cpp * * This base class supports qclocklayout and qinputcheckbox. * * \library seq66 application * \author Chris Ahlstrom * \date 2022-01-20 * \updates 2022-01-20 * \license GNU GPLv2 or above * * This class represents a user-interface for output (clock) or input ports. */ #include "play/performer.hpp" /* seq66::performer class */ #include "qportwidget.hpp" /* seq66::qportwidget class */ #include "qseditoptions.hpp" /* seq66::qseditoptions class */ namespace seq66 { /** * Creates a single line in the MIDI Clocks "Clock" group-box or in the MIDI * Inputs group-box. It is the base class for qclocklayout and * qinputcheckbox. We use the words "clock", "input", or "port" for the MIDI * input/output ports represented by this widget. Here are the jobs we have * to do: * * -# Get the label for the port and set it. * -# Add the tooltips for the clock radio-buttons. * -# Add the clock radio-buttons to m_horizlayout_clocklive. * -# Connect to the radio-button slots: * - clock_callback_disable(). * - clock_callback_off(). * - clock_callback_on(). * - clock_callback_mod(). */ qportwidget::qportwidget (QWidget * parent, performer & p, int bus) : QWidget (parent), m_performance (p), m_bus (bus), m_parent_widget (dynamic_cast(parent)) { // setup_ui(); } } // namespace seq66 /* * qportwidget.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qsabout.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsabout.cpp * * The time bar shows markers and numbers for the measures of the song, * and also depicts the left and right markers. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2021-10-21 * \license GNU GPLv2 or above * */ #include "seq66-config.h" /* needed to check Qt environment */ #include "seq66_features.hpp" /* version information functions */ #include "qsabout.hpp" /* your basic developer spoor */ #include "qt5_helpers.hpp" /* seq66::qt(), qt_set_icon() etc. */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qsabout.h" #else #include "forms/qsabout.ui.h" #endif namespace seq66 { /** * Principal constructor. Now sets the links to be opened by clicking on * them. */ qsabout::qsabout (QWidget * parent) : QDialog (parent), ui (new Ui::qsabout) { ui->setupUi(this); std::string apptag = seq_app_name() + " " + seq_version(); std::string vertag = seq_version_text(); ui->topLabel->setText(qt(apptag)); ui->versionLabel->setText(qt(vertag)); ui->label_filter24_seq24->setOpenExternalLinks(true); ui->label_github_seq66->setOpenExternalLinks(true); ui->label_github_seq32->setOpenExternalLinks(true); ui->label_github_kepler34->setOpenExternalLinks(true); ui->label_gmail_ahlstromcj->setOpenExternalLinks(true); } qsabout::~qsabout() { delete ui; } } // namespace seq66 /* * qsabout.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qsappinfo.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsappinfo.cpp * * The time bar shows markers and numbers for the measures of the song, * and also depicts the left and right markers. * * \library seq66 application * \author Chris Ahlstrom * \date 2023-08-21 * \updates 2025-01-25 * \license GNU GPLv2 or above * * This module supports a task similar to that of the Help / Tutorial menu * entry. The qsappinfo dialog is similar to the qsbuildinfo dialog. There * are a couple of differences: * * - The dialog uses a rich-text browser widget, QTextBrowser. * - The data to be shown will reside in small files stored in * the build directory seq66/data/share/doc/info, and installed to * - /usr/local/share/doc/seq66-0.99/info * - C:/Program Files/Seq66/data/share/doc/info * * The plan is to generalize the tutorial_folder_list() function to more * flexibly select the document folder, and then write a function * to read the desired file into a string. * */ #include "cfg/settings.hpp" /* seq66::open_share_doc_file() */ #include "qsappinfo.hpp" /* seq66::qsappinfo dialog class */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qsappinfo.h" #else #include "forms/qsappinfo.ui.h" #endif namespace seq66 { static const std::string s_error_html { "" "" "" "" "" "" "Could not find the HTML file in installation directories" "" "" }; /** * Principal constructor. */ qsappinfo::qsappinfo (QWidget * parent) : QDialog (parent), ui (new Ui::qsappinfo) { ui->setupUi(this); ui->buttonReserved_1->hide(); ui->buttonReserved_2->hide(); slot_common_keys(); connect ( ui->buttonCommonKeys, SIGNAL(clicked(bool)), this, SLOT(slot_common_keys()) ); connect ( ui->buttonAutomation, SIGNAL(clicked(bool)), this, SLOT(slot_automation_keys()) ); connect ( ui->buttonSeqroll, SIGNAL(clicked(bool)), this, SLOT(slot_seqroll_keys()) ); connect ( ui->buttonPerfroll, SIGNAL(clicked(bool)), this, SLOT(slot_songroll_keys()) ); connect ( ui->buttonHotKeys, SIGNAL(clicked(bool)), this, SLOT(slot_hot_keys()) ); connect ( ui->buttonMutesKeys, SIGNAL(clicked(bool)), this, SLOT(slot_mutes_keys()) ); } qsappinfo::~qsappinfo () { delete ui; } void qsappinfo::open_html ( const std::string & basename, const std::string & comment ) { std::string filename = basename + ".html"; std::string html = open_share_doc_file(filename, "info"); if (html.empty()) html = s_error_html; ui->textBrowser->setHtml(qt(html)); ui->appPanelLabel->setText(qt(comment)); } void qsappinfo::slot_common_keys () { open_html("common_keys", "Common Piano Roll Keys"); } void qsappinfo::slot_automation_keys () { open_html("automation_keys", "Automation Defaults"); } void qsappinfo::slot_seqroll_keys () { open_html("seqroll_keys", "Pattern Editor Keys"); } void qsappinfo::slot_songroll_keys () { open_html("songroll_keys", "Song Editor Keys"); } void qsappinfo::slot_hot_keys () { open_html("pattern_hotkeys", "Grid Hot Keys"); } void qsappinfo::slot_mutes_keys () { open_html("mute_group_keys", "Grid Mute-Group Keys"); } } // namespace seq66 /* * qsappinfo.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qsbuildinfo.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsbuildinfo.cpp * * The time bar shows markers and numbers for the measures of the song, * and also depicts the left and right markers. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-05-30 * \updates 2021-10-20 * \license GNU GPLv2 or above * */ #include "cfg/cmdlineopts.hpp" /* for build info function */ #include "qsbuildinfo.hpp" /* seq66::qsbuildinfo dialog class */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qsbuildinfo.h" #else #include "forms/qsbuildinfo.ui.h" #endif namespace seq66 { /** * Principal constructor. */ qsbuildinfo::qsbuildinfo (QWidget * parent) : QDialog (parent), ui (new Ui::qsbuildinfo) { ui->setupUi(this); QString name(SEQ66_PACKAGE_NAME); QString version(SEQ66_VERSION); QString comment = qt(seq_build_details()); ui->buildProgramLabel->setText(name); ui->buildVersionLabel->setText(version); ui->buildInfoTextEdit->setPlainText(comment); } qsbuildinfo::~qsbuildinfo () { delete ui; } } // namespace seq66 /* * qsbuildinfo.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qscrollmaster.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ /** * \file qscrollmaster.cpp * * This module declares/defines a class for controlling other QScrollAreas * from this one. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-06-18 * \updates 2024-12-27 * \license GNU GPLv2 or above * * When inheriting QAbstractScrollArea, you need to do the following: * * -# Control the scroll bars by setting their range, value, page step, and * tracking their movements. * -# Draw the contents of the area in the viewport according to the values * of the scroll bars. * -# Handle events received by the viewport in viewportEvent() - notably * resize events. * -# Use viewport->update() to update the contents of the viewport instead * of update() as all painting operations take place on the viewport. * * In order to track scroll bar movements, reimplement the virtual function * scrollContentsBy(). * * For convenience, QAbstractScrollArea makes all viewport events available * in the virtual viewportEvent() handler. QWidget's specialized handlers are * remapped to viewport events in the cases where this makes sense. The * remapped specialized handlers are: * * - paintEvent() * - mousePressEvent() * - mouseReleaseEvent() * - mouseDoubleClickEvent() * - mouseMoveEvent() * - wheelEvent() * - dragEnterEvent() * - dragMoveEvent() * - dragLeaveEvent() * - dropEvent() * - contextMenuEvent() * - resizeEvent() * * Other useful QScrollBar functions: * * - setPageStep() * - setSingleStep() */ #include #include #include #include "qscrollmaster.h" /* ::qscrollmaster class */ #include "util/basic_macros.hpp" /* nullptr and other macros */ /* * Note that there is no namespace; the Qt uic specification does not seem to * support them. */ /** * Constructor. * * \param qf * Provides the "parent" of this object. */ qscrollmaster::qscrollmaster (QWidget * qf) : QScrollArea (qf), m_v_scrollbars (), m_h_scrollbars (), m_self_v_scrollbar (verticalScrollBar()), m_self_h_scrollbar (horizontalScrollBar()) { // Done! } qscrollmaster::~qscrollmaster () { // no code } /** * This override of a QScrollArea virtual member function modifies any * attached/listed scrollbars and then calls the base-class version of this * function. * * \param dx * The change in the x position value of the scrollbar. Simply passed * on to the base-class version of this function. * * \param dy * The change in the y position value of the scrollbar. Simply passed * on to the base-class version of this function. */ void qscrollmaster::scrollContentsBy (int dx, int dy) { if (! m_v_scrollbars.empty()) { int vvalue = m_self_v_scrollbar->value(); scroll_to_y(vvalue); } if (! m_h_scrollbars.empty()) { int hvalue = m_self_h_scrollbar->value(); scroll_to_x(hvalue); } QScrollArea::scrollContentsBy(dx, dy); } void qscrollmaster::scroll_to_x (int x) { if (! m_h_scrollbars.empty()) { for (auto hit : m_h_scrollbars) hit->setValue(x); m_self_h_scrollbar->setValue(x); } } void qscrollmaster::scroll_x_to_factor (float f) { if (! m_h_scrollbars.empty()) { int hmin = m_self_h_scrollbar->minimum(); int hmax = m_self_h_scrollbar->maximum(); int newh = int((hmax - hmin) * f) + hmin; int dx = hmax - newh; scroll_to_x(newh); QScrollArea::scrollContentsBy(dx, 0); } } void qscrollmaster::scroll_x_by_factor (float f) { if (! m_h_scrollbars.empty()) { int hvalue = m_self_h_scrollbar->value(); int newh = int(hvalue * f); int dx = hvalue - newh; scroll_to_x(newh); QScrollArea::scrollContentsBy(dx, 0); } } void qscrollmaster::scroll_x_by_step (dir d) { if (! m_h_scrollbars.empty()) { int dx = m_self_h_scrollbar->singleStep(); if (d == dir::left) /* else right assumed */ dx = -dx; int hvalue = m_self_h_scrollbar->value(); int newh = hvalue + dx; scroll_to_x(newh); QScrollArea::scrollContentsBy(dx, 0); } } void qscrollmaster::scroll_to_y (int y) { if (! m_v_scrollbars.empty()) { for (auto vit : m_v_scrollbars) vit->setValue(y); m_self_v_scrollbar->setValue(y); } } /** * Scrolls vertically by a factor, plus a bit to try to center in the roll. * TODO! * * Used in qseqeditframe64::scroll_to_note(). * * \param f * Provides the "average" value of the note(s) found at the beginning * of the pattern. */ void qscrollmaster::scroll_y_to_factor (float f) { if (! m_v_scrollbars.empty() && f > 0.1) { int vmin = m_self_v_scrollbar->minimum(); int vmax = m_self_v_scrollbar->maximum(); int newv = int((vmax - vmin) * f) + vmin; scroll_to_y(newv); /* * int dy = newv - vmax; * QScrollArea::scrollContentsBy(0, dy); */ } } void qscrollmaster::scroll_y_by_factor (float f) { if (! m_v_scrollbars.empty()) { int vvalue = m_self_v_scrollbar->value(); int newv = int(vvalue * f); int dy = vvalue - newv; scroll_to_y(newv); QScrollArea::scrollContentsBy(0, dy); } } void qscrollmaster::scroll_y_by_step (dir d) { if (! m_v_scrollbars.empty()) { int dy = m_self_v_scrollbar->singleStep(); if (d == dir::up) /* else down assumed */ dy = -dy; int vvalue = m_self_v_scrollbar->value(); int newv = vvalue + dy; scroll_to_y(newv); QScrollArea::scrollContentsBy(dy, 0); } } void qscrollmaster::show_values () const { int xv = m_self_h_scrollbar->value(); int yv = m_self_v_scrollbar->value(); printf("Scrollbars at (%d, %d)\n", xv, yv); } void qscrollmaster::wheelEvent (QWheelEvent * qwep) { QScrollArea::wheelEvent(qwep); } /* * qscrollmaster.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qscrollslave.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ /** * \file qscrollslave.cpp * * This module declares/defines a class for ignoring arrow events. * * \library seq66 application * \author Chris Ahlstrom * \date 2023-05-23 * \updates 2026-01-23 * \license GNU GPLv2 or above * * Compare using qscrollslave for a QScrollArea versus the method used in * qperfnames::keyPressEvent(). */ #include #include #include #include #include "qscrollslave.h" /* ::qscrollslave class */ #include "qscrollmaster.h" /* ::qscrollmaster class */ #include "util/basic_macros.h" /* not_nullptr() test macro */ /* * Note that there is no namespace; the Qt uic specification does not seem to * support them. */ /** * Constructor. * * \param qf * Provides the "parent" of this object. */ qscrollslave::qscrollslave (QWidget * qf) : QScrollArea (qf), m_master (nullptr) { // Done! } qscrollslave::~qscrollslave () { // no code } void qscrollslave::attach_master (qscrollmaster * qsm) { m_master = qsm; } static bool is_direction_key (int key) { return ( key == Qt::Key_Down || key == Qt::Key_Up || key == Qt::Key_Left || key == Qt::Key_Right || key == Qt::Key_PageUp || key == Qt::Key_PageDown ); } /** * Arrow and Page events are accepted so they don't go to the QScrollArea, * and then forwarded to the qscrollmaster (e.g. the piano roll pane in * the pattern editor. */ void qscrollslave::keyPressEvent (QKeyEvent * ev) { int key = ev->key(); bool isarrow = is_direction_key(key); if (isarrow) { ev->accept(); // ev->ignore(); if (not_nullptr(m_master)) { QKeyEvent * keyevent { ev }; QApplication::sendEvent(m_master, keyevent); } } else QScrollArea::keyPressEvent(ev); // ev->accept(); } void qscrollslave::keyReleaseEvent (QKeyEvent * ev) { int key = ev->key(); bool isarrow = is_direction_key(key); if (isarrow) ev->accept(); // ev->ignore(); else QScrollArea::keyReleaseEvent(ev); // event->accept(); } /** * The event forward here does not work, not sure why. */ void qscrollslave::wheelEvent (QWheelEvent * qwep) { qwep->accept(); // qwep->ignore(); if (not_nullptr(m_master)) { QWheelEvent * wheelevent { qwep }; QApplication::sendEvent(m_master, wheelevent); } } /* * qscrollslave.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qseditoptions.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseditoptions.cpp * * The Qt version of the Options editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2025-08-18 * \license GNU GPLv2 or above * * This version is located in Edit / Preferences. * * Reset/Apply/Cancel/Ok and Restart-Session handling. The symbol 'x' means * disabled, and 'o' means enabled. * \verbatim Reset Apply Cancel Ok Restart First run: x x o x x Change: o o x o x Reset: x x o x x Apply: x x o x o \endverbatim * * When is a restart (reload) of the application needed? When the Apply or OK * buttons are clicked, or the port statuses have changed. * * When an 'rc' or 'usr' change is made, do we want to wait until the Apply/OK * are clicked? No, they directly affect rc() and usr() and a restart is * needed/useful. */ #include #include "cfg/patchesfile.hpp" /* seq66::patchesfile class */ #include "cfg/rcfile.hpp" /* seq66::rcfile class */ #include "midi/jack_assistant.hpp" /* seq66::jack_assistant statics */ #include "os/daemonize.hpp" /* seq66::signal_for_restart() */ #include "play/performer.hpp" /* seq66::performer class */ #include "util/filefunctions.hpp" /* seq66::filename_base() */ #include "util/strfunctions.hpp" /* seq66::string_to_int() */ #include "gui_palette_qt5.hpp" /* seq66::global_palette() */ #include "palettefile.hpp" /* seq66::palettefile class */ #include "qclocklayout.hpp" /* seq66::qclocklayout class */ #include "qinputcheckbox.hpp" /* seq66::qinputcheckbox class */ #include "qseditoptions.hpp" /* seq66::qseditoptions dialog */ #include "qsmainwnd.hpp" /* seq66::qsmainwnd master class */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ #if defined SEQ66_NSM_SUPPORT #include "nsm/nsmbase.hpp" /* seq66::nsmbase's get_url() */ #endif /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qseditoptions.h" #else #include "forms/qseditoptions.ui.h" #endif namespace seq66 { const int Tab_MIDI_Clock = 0; const int Tab_MIDI_Input = 1; const int Tab_Display = 2; const int Tab_JACK = 3; const int Tab_Play_Options = 4; const int Tab_Metronome = 5; const int Tab_Pattern = 6; const int Tab_Session = 7; /** * Button numbering for JACK Start Mode radio-buttons. */ enum playmode_button_t { playmode_button_live, playmode_button_song }; /** * Button number for the Set Mode radio-buttons. */ enum setsmode_button_t { setsmode_button_normal, setsmode_button_autoarm, setsmode_button_additive, setsmode_button_allsets, }; /** * Button number for the Song Start Mode radio-buttons. */ enum startmode_button_t { startmode_button_live, startmode_button_song, startmode_button_auto }; /** * The main constructor. */ qseditoptions::qseditoptions (performer & p, QWidget * parent) : QDialog (parent), ui (new Ui::qseditoptions), m_live_song_buttons (nullptr), m_parent_widget (dynamic_cast(parent)), m_perf (p), m_ppqn_list (supported_ppqns(), true), /* add blank slot */ m_buffer_size_list (jack_buffer_size_list()), m_is_initialized (false), m_inbus_count (0), m_outbus_count (0), m_backup_rc (), m_backup_usr (), m_reload_needed (false) { ui->setupUi(this); backup(); setup_tab_midi_clock(); setup_tab_midi_input(); setup_tab_display(); setup_tab_jack(); setup_tab_play_options(); setup_tab_metronome(); setup_tab_pattern(); setup_tab_session(); /* * Reload/restart button. Replaced by Apply --> Restart! */ ui->pushButtonReload->setEnabled(false); ui->pushButtonReload->hide(); /* * OK/Cancel Buttons */ connect ( ui->buttonBoxOptionsDialog->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(okay()) ); connect ( ui->buttonBoxOptionsDialog->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(cancel()) ); /* * Apply/Reset buttons. */ if (usr().in_nsm_session()) { set_text(QDialogButtonBox::Apply, "NSM"); } else { set_text(QDialogButtonBox::Apply, "Restart Seq66!"); connect ( ui->buttonBoxOptionsDialog->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(apply()) ); } connect ( ui->buttonBoxOptionsDialog->button(QDialogButtonBox::Reset), SIGNAL(clicked()), this, SLOT(reset()) ); sync(); state_unchanged(); ui->tabWidget->setCurrentIndex(Tab_MIDI_Clock); m_is_initialized = true; } /** * A stock Qt GUI destructor. */ qseditoptions::~qseditoptions () { delete ui; } /* *--------------------------------------------------------------------- * MIDI Clock tab. *--------------------------------------------------------------------- */ /* * Output MIDI control buss combo-box population. */ void qseditoptions::setup_clock_combo_box (int buses) { QComboBox * out = ui->comboBoxMidiOutBuss; out->clear(); for (int bus = 0; bus < buses; ++bus) { std::string busname; e_clock ec; bool good = perf().ui_get_clock(bussbyte(bus), ec, busname); if (good) { bool enabled = ec != e_clock::disabled; out->addItem(qt(busname)); enable_combobox_item(out, bus, enabled); } } /* * The performer class might have disabled MIDI control/display * due to the "Midi Through" issue. * * bool active = perf().midi_control_out().configure_enabled(); */ bool active = perf().midi_control_out().is_enabled(); int buss = perf().midi_control_out().configured_buss(); out->setCurrentIndex(buss); ui->checkBoxMidiOutBuss->setChecked(active); m_outbus_count = buses; } void qseditoptions::refresh_clock_combo_box () { QComboBox * out = ui->comboBoxMidiOutBuss; bool active = perf().midi_control_out().configure_enabled(); bool out_enabled = ui->checkBoxMidiOutBuss->isChecked(); int buses = m_outbus_count; for (int bus = 0; bus < buses; ++bus) { std::string busname; e_clock ec; bool good = perf().ui_get_clock(bussbyte(bus), ec, busname); if (good) { bool enabled = ec != e_clock::disabled && out_enabled; enable_combobox_item(out, bus, enabled); } } int buss = perf().midi_control_out().configured_buss(); out->setCurrentIndex(buss); ui->checkBoxMidiOutBuss->setChecked(active); } /* * Input MIDI control buss combo-box population. Note the (new) check * for the check-box being checked before enabling the items inside. */ void qseditoptions::setup_input_combo_box (int buses) { QComboBox * in = ui->comboBoxMidiInBuss; /* * The performer class might have disabled MIDI control/display * due to the "Midi Through" issue. * * bool active = perf().midi_control_in().configure_enabled(); */ bool active = perf().midi_control_out().is_enabled(); in->clear(); for (int bus = 0; bus < buses; ++bus) { std::string busname; bool inputing; bool good = perf().ui_get_input(bussbyte(bus), inputing, busname); if (good) { bool enabled = ! perf().is_input_system_port(bus); enabled = enabled && active; in->addItem(qt(busname)); enable_combobox_item(in, bus, enabled); } } int buss = perf().midi_control_in().configured_buss(); in->setCurrentIndex(buss); ui->checkBoxMidiInBuss->setChecked(active); m_inbus_count = buses; } void qseditoptions::refresh_input_combo_box () { QComboBox * in = ui->comboBoxMidiInBuss; bool active = perf().midi_control_in().configure_enabled(); bool in_enabled = ui->checkBoxMidiInBuss->isChecked(); int buses = m_inbus_count; for (int bus = 0; bus < buses; ++bus) { std::string busname; bool inputing; bool good = perf().ui_get_input(bussbyte(bus), inputing, busname); if (good) { bool enabled = ! perf().is_input_system_port(bus); /* announce */ enabled = enabled && in_enabled; enable_combobox_item(in, bus, enabled); } } int buss = perf().midi_control_in().configured_buss(); in->setCurrentIndex(buss); ui->checkBoxMidiInBuss->setChecked(active); } void qseditoptions::setup_tab_midi_clock () { ui->tabWidget->setTabToolTip ( Tab_MIDI_Clock, "Output ports, port-mapping, and clock settings" ); /* * Set up the MIDI Clock tab. We use the qclocklayout class to hold * most of the complex code needed to handle the list of output ports and * clock radio-buttons. */ QWidget * central = new QWidget; QVBoxLayout * vboxclocks = new QVBoxLayout(central); mastermidibus * mmb = perf().master_bus(); const clockslist & opm = output_port_map(); bool outportmap = opm.active(); QComboBox * out = ui->comboBoxMidiOutBuss; if (not_nullptr(mmb)) { int buses = outportmap ? opm.count() : mmb->get_num_out_buses() ; ui->clocksScrollArea->setWidget(central); ui->clocksScrollArea->setWidgetResizable(true); for (int bus = 0; bus < buses; ++bus) { qclocklayout * tempqc = new qclocklayout(this, perf(), bus); vboxclocks->addLayout(tempqc->layout()); } /* * Output MIDI control buss combo-box population. */ setup_clock_combo_box(buses); connect ( out, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_output_bus(int)) ); connect ( ui->checkBoxMidiOutBuss, SIGNAL(clicked(bool)), this, SLOT(slot_output_bus_enable()) ); refresh_clock_combo_box(); } QSpacerItem * spacer = new QSpacerItem ( 40, 20, QSizePolicy::Expanding, QSizePolicy::Expanding ); vboxclocks->addItem(spacer); connect ( ui->spinBoxClockStartModulo, SIGNAL(valueChanged(int)), this, SLOT(slot_clock_start_modulo(int)) ); ui->lineEditTempoTrack->setEnabled(true); ui->lineEditTempoTrack->setReadOnly(false); std::string tn = std::to_string(rc().tempo_track_number()); ui->lineEditTempoTrack->setText(qt(tn)); connect ( ui->lineEditTempoTrack, SIGNAL(editingFinished()), this, SLOT(slot_tempo_track()) ); /* * Buss override (simplistic for now) */ int buss_override = usr().midi_buss_override(); std::string botext = std::to_string(buss_override); ui->lineEditBussOverride->setText(qt(botext)); connect ( ui->lineEditBussOverride, SIGNAL(editingFinished()), this, SLOT(slot_buss_override()) ); /* * Tempo precision combo-box. */ for (int i = 0; i <= 2; ++i) /* c_max_bpm_precision */ { QString s = QString::number(i); ui->comboBoxBpmPrecision->insertItem(i, s); } ui->comboBoxBpmPrecision->setCurrentIndex(usr().bpm_precision()); connect ( ui->comboBoxBpmPrecision, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_bpm_precision(int)) ); ui->pushButtonTempoTrack->setEnabled(false); connect ( ui->pushButtonTempoTrack, SIGNAL(clicked(bool)), this, SLOT(slot_tempo_track_set()) ); connect ( ui->pushButtonStoreMap, SIGNAL(clicked(bool)), this, SLOT(slot_io_maps()) ); #if defined SEQ66_ALLOW_PORTMAP_CLEAR connect ( ui->pushButtonRemoveMap, SIGNAL(clicked(bool)), this, SLOT(slot_remove_io_maps()) ); #else ui->pushButtonRemoveMap->hide(); #endif /* * The output and input port-map active check-boxes are both in the * Clocks tab. */ bool inportmap = input_port_map().active(); ui->ioPortsMappedCheck->setChecked(inportmap && outportmap); connect ( ui->ioPortsMappedCheck, SIGNAL(clicked(bool)), this, SLOT(slot_activate_io_maps()) ); std::string clid = perf().client_id_string(); ui->lineEditClientId->setText(qt(clid)); } /* *--------------------------------------------------------------------- * MIDI Input tab. *--------------------------------------------------------------------- */ void qseditoptions::setup_tab_midi_input () { ui->tabWidget->setTabToolTip ( Tab_MIDI_Input, "Input ports and I/O options" ); /* * Set up the MIDI Input tab. It is simpler, just a list of check-boxes * in the groupBoxInputs widget. No need for a separate class. However, * note that our qinputcheckbox class controls the activation, in the GUI, * of each input port, based on its buss status. */ QWidget * central2 = new QWidget; QVBoxLayout * vboxinputs = new QVBoxLayout(central2); mastermidibus * mmb = perf().master_bus(); const inputslist & ipm = input_port_map(); bool inportmap = ipm.active(); QComboBox * in = ui->comboBoxMidiInBuss; if (not_nullptr(mmb)) { int buses = inportmap ? ipm.count() : mmb->get_num_in_buses() ; ui->inputsScrollArea->setWidget(central2); ui->inputsScrollArea->setWidgetResizable(true); for (int bus = 0; bus < buses; ++bus) { qinputcheckbox * tempqi = new qinputcheckbox(this, perf(), bus); vboxinputs->addWidget(tempqi->input_checkbox()); } /* * Input MIDI control buss combo-box population. */ setup_input_combo_box(buses); connect ( in, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_input_bus(int)) ); connect ( ui->checkBoxMidiInBuss, SIGNAL(clicked(bool)), this, SLOT(slot_input_bus_enable()) ); refresh_input_combo_box(); } /* * I/O Port Boolean options. */ connect ( ui->checkBoxRecordByBuss, SIGNAL(clicked(bool)), this, SLOT(slot_record_by_buss()) ); connect ( ui->checkBoxRecordByChannel, SIGNAL(clicked(bool)), this, SLOT(slot_record_by_channel()) ); bool isvirtualports = rc().manual_ports(); ui->checkBoxVirtualPorts->setChecked(isvirtualports); connect ( ui->checkBoxVirtualPorts, SIGNAL(clicked(bool)), this, SLOT(slot_virtual_ports()) ); bool enablevirtualports = rc().manual_auto_enable(); ui->checkBoxAutoEnableVirtual->setChecked(enablevirtualports); connect ( ui->checkBoxAutoEnableVirtual, SIGNAL(clicked(bool)), this, SLOT(slot_enable_virtual_ports()) ); /* * The virtual port counts for input and output. */ ui->lineEditOutputCount->setEnabled(isvirtualports); connect ( ui->lineEditOutputCount, SIGNAL(editingFinished()), this, SLOT(slot_virtual_out_count()) ); ui->lineEditInputCount->setEnabled(isvirtualports); connect ( ui->lineEditInputCount, SIGNAL(editingFinished()), this, SLOT(slot_virtual_in_count()) ); QSpacerItem * spacer2 = new QSpacerItem ( 40, 20, QSizePolicy::Expanding, QSizePolicy::Expanding ); vboxinputs->addItem(spacer2); } /* *--------------------------------------------------------------------- * Display tab. *--------------------------------------------------------------------- */ void qseditoptions::setup_tab_display () { ui->tabWidget->setTabToolTip ( Tab_Display, "User-interface tweaks and boolean options" ); ui->spinKeyHeight->setMinimum(usr().min_key_height()); ui->spinKeyHeight->setMaximum(usr().max_key_height()); connect ( ui->spinKeyHeight, SIGNAL(valueChanged(int)), this, SLOT(slot_key_height()) ); connect ( ui->lineEditUiScaling, SIGNAL(editingFinished()), this, SLOT(slot_ui_scaling()) ); connect ( ui->lineEditUiScalingHeight, SIGNAL(editingFinished()), this, SLOT(slot_ui_scaling()) ); std::string spacingtext = int_to_string(usr().mainwnd_spacing()); ui->lineEditGridSpacing->setText(qt(spacingtext)); connect ( ui->lineEditGridSpacing, SIGNAL(editingFinished()), this, SLOT(slot_grid_spacing()) ); connect ( ui->lineEditSetSize, SIGNAL(editingFinished()), this, SLOT(slot_set_size_rows()) ); connect ( ui->lineEditSetSizeColumns, SIGNAL(editingFinished()), this, SLOT(slot_set_size_columns()) ); connect ( ui->lineEditProgressBox, SIGNAL(editingFinished()), this, SLOT(slot_progress_box_width()) ); connect ( ui->lineEditProgressBoxHeight, SIGNAL(editingFinished()), this, SLOT(slot_progress_box_height()) ); connect ( ui->checkBoxProgressBoxShown, SIGNAL(clicked(bool)), this, SLOT(slot_progress_box_shown()) ); connect ( ui->lineEditFingerprintSize, SIGNAL(editingFinished()), this, SLOT(slot_fingerprint_size()) ); #if defined USE_VERBOSE_CHECKBOX /* * Verbosity is meant to be only a temporary setting for one run. * It is set via --verbose or by editing the 'rc' file, but is * set to false at application exit. This control is read-only. */ ui->checkBoxVerbose->setEnabled(true); connect ( ui->checkBoxVerbose, SIGNAL(clicked(bool)), this, SLOT(slot_verbose_active_click()) ); #endif ui->checkBoxQuiet->setChecked(rc().quiet()); connect ( ui->checkBoxQuiet, SIGNAL(clicked(bool)), this, SLOT(slot_quiet_active_click()) ); connect ( ui->checkBoxLoadMostRecent, SIGNAL(clicked(bool)), this, SLOT(slot_load_most_recent_click()) ); connect ( ui->checkBoxShowFullRecentPaths, SIGNAL(clicked(bool)), this, SLOT(slot_show_full_paths_click()) ); connect ( ui->checkBoxLongBussNames, SIGNAL(clicked(bool)), this, SLOT(slot_long_buss_names_click()) ); connect ( ui->checkBoxPairBussNames, SIGNAL(clicked(bool)), this, SLOT(slot_pair_buss_names_click()) ); connect ( ui->checkBoxLockMainWindow, SIGNAL(clicked(bool)), this, SLOT(slot_lock_main_window_click()) ); ui->checkBoxDarkTheme->setChecked(usr().dark_theme()); connect ( ui->checkBoxDarkTheme, SIGNAL(clicked(bool)), this, SLOT(slot_dark_theme_click()) ); connect ( ui->checkBoxSwapCoordinates, SIGNAL(clicked(bool)), this, SLOT(slot_swap_coordinates_click()) ); connect ( ui->checkBoxBoldGridSlots, SIGNAL(clicked(bool)), this, SLOT(slot_bold_grid_slots_click()) ); ui->checkBoxGridlinesThick->setChecked(usr().gridlines_thick()); connect ( ui->checkBoxGridlinesThick, SIGNAL(clicked(bool)), this, SLOT(slot_gridlines_thick_click()) ); ui->checkBoxElliptical->setChecked(usr().progress_box_elliptical()); connect ( ui->checkBoxElliptical, SIGNAL(clicked(bool)), this, SLOT(slot_elliptical_click()) ); ui->checkBoxFollowProgress->setChecked(usr().follow_progress()); connect ( ui->checkBoxFollowProgress, SIGNAL(clicked(bool)), this, SLOT(slot_follow_progress_click()) ); connect ( ui->checkBoxDoubleClickEdit, SIGNAL(clicked(bool)), this, SLOT(slot_double_click_edit()) ); connect ( ui->checkBoxGlobalSeqFeature, SIGNAL(clicked(bool)), this, SLOT(slot_global_seq_feature()) ); } /* *--------------------------------------------------------------------- * Jack Sync tab. *--------------------------------------------------------------------- */ void qseditoptions::setup_tab_jack () { #if defined SEQ66_JACK_SUPPORT ui->tabWidget->setTabToolTip ( Tab_JACK, "JACK transport and server options" ); /* * JACK Transport Connect/Disconnect buttons. */ connect ( ui->btnJackConnect, SIGNAL(clicked(bool)), this, SLOT(slot_jack_connect()) ); connect ( ui->btnJackDisconnect, SIGNAL(clicked(bool)), this, SLOT(slot_jack_disconnect()) ); /* * JACK Transport/MIDI tab. */ connect ( ui->chkJackTransport, SIGNAL(stateChanged(int)), this, SLOT(slot_transport_support()) ); connect ( ui->chkJackConditional, SIGNAL(stateChanged(int)), this, SLOT(slot_master_cond()) ); connect ( ui->chkJackMaster, SIGNAL(stateChanged(int)), this, SLOT(slot_time_master()) ); connect ( ui->chkJackNative, SIGNAL(stateChanged(int)), this, SLOT(slot_jack_midi()) ); connect ( ui->chkJackAutoConnect, SIGNAL(stateChanged(int)), this, SLOT(slot_jack_auto_connect()) ); /* * Display of current JACK Server settings. */ jack_assistant::parameters pos = jack_assistant::get_jack_parameters(); std::string temp = std::to_string(int(pos.position.frame_rate)); ui->lineEditFrameRate->setText(qt(temp)); temp = std::to_string(pos.period_size); /* int */ ui->lineEditPeriod->setText(qt(temp)); /* * Need a way to determine this. * temp = std::to_string(pos.alsa_nperiod); // int // ui->lineEditNperiod->setText(qt(temp)); */ ui->lineEditNperiod->setText("?"); char tmp[32]; snprintf(tmp, sizeof tmp, "%.2f", pos.position.ticks_per_beat); ui->lineEditTicksPerBeat->setText(tmp); snprintf(tmp, sizeof tmp, "%.2f", pos.position.beats_per_minute); ui->lineEditBeatsPerMinute->setText(tmp); /* * Create a button group to manage the mutual status of the JACK Live and * Song Mode buttons. */ m_live_song_buttons = new QButtonGroup(this); m_live_song_buttons->addButton(ui->radio_live_mode, playmode_button_live); m_live_song_buttons->addButton(ui->radio_song_mode, playmode_button_song); connect ( m_live_song_buttons, SIGNAL(buttonClicked(int)), this, SLOT(slot_jack_mode(int)) ); QString midiengine = rc().with_jack_midi() ? "JACK active" : "?" ; if (rc().with_alsa_midi()) midiengine = "ALSA active"; ui->alsaJackButton->setText(midiengine); ui->alsaJackButton->setStyleSheet("text-align: left;"); #else ui->tabWidget->setTabToolTip ( Tab_JACK, "JACK not supported for this system" ); ui->btnJackConnect->setEnabled(false); ui->btnJackDisconnect->setEnabled(false); ui->chkJackTransport->setEnabled(false); ui->chkJackConditional->setEnabled(false); ui->chkJackMaster->setEnabled(false); ui->chkJackNative->setEnabled(false); ui->chkJackAutoConnect->setEnabled(false); ui->tabWidget->setTabEnabled(Tab_JACK, false); #if defined SEQ66_PORTMIDI_SUPPORT ui->alsaJackButton->setText("PortMidi"); #endif #endif // defined SEQ66_JACK_SUPPORT } /* *--------------------------------------------------------------------- * Play Options tab. *--------------------------------------------------------------------- */ void qseditoptions::setup_tab_play_options () { ui->tabWidget->setTabToolTip ( Tab_Play_Options, "PPQN and playback options" ); connect ( ui->chkNoteResume, SIGNAL(stateChanged(int)), this, SLOT(slot_note_resume()) ); connect ( ui->chkUseFilesPPQN, SIGNAL(stateChanged(int)), this, SLOT(slot_use_file_ppqn()) ); connect ( ui->chkSongRecordSnap, SIGNAL(stateChanged(int)), this, SLOT(slot_song_record_snap()) ); connect ( ui->chkAddTimeSig, SIGNAL(stateChanged(int)), this, SLOT(slot_add_time_sig()) ); /* * Combo-box for changing the default PPQN. */ (void) set_ppqn_combo(); connect ( ui->combo_box_ppqn, SIGNAL(currentTextChanged(const QString &)), this, SLOT(slot_ppqn_by_text(const QString &)) ); (void) set_buffer_size_combo(); connect ( ui->combo_box_buffer_size, SIGNAL(currentTextChanged(const QString &)), this, SLOT(slot_buffer_size_by_text(const QString &)) ); /* * Group-box for changing the "sets mode". */ QButtonGroup * rgroup = new QButtonGroup(this); rgroup->addButton(ui->radio_setsmode_normal, setsmode_button_normal); rgroup->addButton(ui->radio_setsmode_autoarm, setsmode_button_autoarm); rgroup->addButton(ui->radio_setsmode_additive, setsmode_button_additive); rgroup->addButton(ui->radio_setsmode_allsets, setsmode_button_allsets); connect ( rgroup, SIGNAL(buttonClicked(int)), this, SLOT(slot_sets_mode(int)) ); /* * Group-box for changing the "start mode". */ QButtonGroup * rgroup2 = new QButtonGroup(this); rgroup2->addButton(ui->radio_startmode_live, startmode_button_live); rgroup2->addButton(ui->radio_startmode_song, startmode_button_song); rgroup2->addButton(ui->radio_startmode_auto, startmode_button_auto); connect ( rgroup2, SIGNAL(buttonClicked(int)), this, SLOT(slot_start_mode(int)) ); } /* *--------------------------------------------------------------------- * Metronome tab. *--------------------------------------------------------------------- */ void qseditoptions::setup_tab_metronome () { ui->tabWidget->setTabToolTip ( Tab_Metronome, "Options for metronome and count-in" ); int metrotemp = rc().metro_settings().beats_per_bar(); QString qmetrotemp = qt(std::to_string(metrotemp)); ui->lineedit_metro_beats_per_bar->setText(qmetrotemp); connect ( ui->lineedit_metro_beats_per_bar, SIGNAL(editingFinished()), this, SLOT(slot_metro_beats_per_bar()) ); metrotemp = rc().metro_settings().beat_width(); qmetrotemp = qt(std::to_string(metrotemp)); ui->lineedit_metro_beat_width->setText(qmetrotemp); connect ( ui->lineedit_metro_beat_width, SIGNAL(editingFinished()), this, SLOT(slot_metro_beat_width()) ); metrotemp = rc().metro_settings().main_patch(); qmetrotemp = qt(std::to_string(metrotemp)); ui->lineedit_metro_main_patch->setText(qmetrotemp); connect ( ui->lineedit_metro_main_patch, SIGNAL(editingFinished()), this, SLOT(slot_metro_main_patch()) ); metrotemp = rc().metro_settings().main_note(); qmetrotemp = qt(std::to_string(metrotemp)); ui->lineedit_metro_main_note->setText(qmetrotemp); connect ( ui->lineedit_metro_main_note, SIGNAL(editingFinished()), this, SLOT(slot_metro_main_note()) ); metrotemp = rc().metro_settings().main_note_velocity(); qmetrotemp = qt(std::to_string(metrotemp)); ui->lineedit_metro_main_velocity->setText(qmetrotemp); connect ( ui->lineedit_metro_main_velocity, SIGNAL(editingFinished()), this, SLOT(slot_metro_main_velocity()) ); float metrofrac = rc().metro_settings().main_note_fraction(); QString qmetrofrac = qt(double_to_string(metrofrac, 3)); ui->lineedit_metro_main_fraction->setText(qmetrofrac); connect ( ui->lineedit_metro_main_fraction, SIGNAL(editingFinished()), this, SLOT(slot_metro_main_fraction()) ); metrotemp = rc().metro_settings().sub_patch(); qmetrotemp = qt(std::to_string(metrotemp)); ui->lineedit_metro_sub_patch->setText(qmetrotemp); connect ( ui->lineedit_metro_sub_patch, SIGNAL(editingFinished()), this, SLOT(slot_metro_sub_patch()) ); metrotemp = rc().metro_settings().sub_note(); qmetrotemp = qt(std::to_string(metrotemp)); ui->lineedit_metro_sub_note->setText(qmetrotemp); connect ( ui->lineedit_metro_sub_note, SIGNAL(editingFinished()), this, SLOT(slot_metro_sub_note()) ); metrotemp = rc().metro_settings().sub_note_velocity(); qmetrotemp = qt(std::to_string(metrotemp)); ui->lineedit_metro_sub_velocity->setText(qmetrotemp); connect ( ui->lineedit_metro_sub_velocity, SIGNAL(editingFinished()), this, SLOT(slot_metro_sub_velocity()) ); metrofrac = rc().metro_settings().sub_note_fraction(); qmetrofrac = qt(double_to_string(metrofrac, 3)); ui->lineedit_metro_sub_fraction->setText(qmetrofrac); connect ( ui->lineedit_metro_sub_fraction, SIGNAL(editingFinished()), this, SLOT(slot_metro_sub_fraction()) ); ui->button_metro_reload->setEnabled(false); connect ( ui->button_metro_reload, SIGNAL(clicked(bool)), this, SLOT(slot_metro_reload()) ); /* * combobox_metro_buss: * * Code similar to that in qsmainwnd. Output MIDI control * buss combo-box population. */ const clockslist & opm = output_port_map(); mastermidibus * mmb = perf().master_bus(); QComboBox * out = ui->combobox_metro_buss; out->clear(); if (not_nullptr(mmb)) { int metrobus = int(rc().metro_settings().buss()); int buses = opm.active() ? opm.count() : mmb->get_num_out_buses() ; for (int bus = 0; bus < buses; ++bus) { e_clock ec; std::string busname; if (perf().ui_get_clock(bussbyte(bus), ec, busname)) { bool enabled = ec != e_clock::disabled; out->addItem(qt(busname)); enable_combobox_item(out, bus, enabled); } } ui->combobox_metro_buss->setCurrentIndex(metrobus); connect ( ui->combobox_metro_buss, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_metro_buss(int)) ); } /* * combobox_metro_channel */ repopulate_channel_menu(int(rc().metro_settings().buss())); /* * Count-in and recorder settings. */ bool count_in_active = rc().metro_settings().count_in_active(); ui->checkbox_metro_count_in->setChecked(count_in_active); connect ( ui->checkbox_metro_count_in, SIGNAL(clicked(bool)), this, SLOT(slot_metro_count_in()) ); metrotemp = rc().metro_settings().count_in_measures(); qmetrotemp = qt(std::to_string(metrotemp)); ui->lineedit_metro_count_in->setText(qmetrotemp); connect ( ui->lineedit_metro_count_in, SIGNAL(editingFinished()), this, SLOT(slot_metro_count_in_measures()) ); count_in_active = rc().metro_settings().count_in_recording(); ui->checkbox_metro_recording->setChecked(count_in_active); connect ( ui->checkbox_metro_recording, SIGNAL(clicked(bool)), this, SLOT(slot_metro_recording()) ); metrotemp = rc().metro_settings().recording_measures(); qmetrotemp = qt(std::to_string(metrotemp)); ui->lineedit_metro_recording_measures->setText(qmetrotemp); connect ( ui->lineedit_metro_recording_measures, SIGNAL(editingFinished()), this, SLOT(slot_metro_recording_measures()) ); /* * combobox_metro_record_buss: * * Code similar to that in qsmainwnd. Output MIDI control * buss combo-box population. */ const inputslist & ipm = input_port_map(); QComboBox * in = ui->combobox_metro_record_buss; in->clear(); if (not_nullptr(mmb)) { int enabled_count = 0; int last_input = (-1); int buses = ipm.active() ? ipm.count() : mmb->get_num_in_buses() ; for (int bus = 0; bus < buses; ++bus) { std::string busname; bool inputing; bool good = perf().ui_get_input(bus, inputing, busname); if (good) { /* * For this dialog, we want to allow the selection of * a buss that is enabled for input, except for system * ports, which should never be used. */ bool enabled = inputing && ! perf().is_input_system_port(bus); in->addItem(qt(busname)); enable_combobox_item(in, bus, enabled); if (enabled) { ++enabled_count; last_input = bus; } } } ui->combobox_metro_record_buss->setCurrentIndex(last_input); if (enabled_count == 1) rc().metro_settings().recording_buss(midibyte(last_input)); connect ( ui->combobox_metro_record_buss, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_metro_record_buss(int)) ); } /* * combobox_metro_thru_buss */ out = ui->combobox_metro_thru_buss; out->clear(); if (not_nullptr(mmb)) { int thrubus = int(rc().metro_settings().thru_buss()); int buses = opm.active() ? opm.count() : mmb->get_num_out_buses() ; for (int bus = 0; bus < buses; ++bus) { e_clock ec; std::string busname; if (perf().ui_get_clock(bussbyte(bus), ec, busname)) { bool enabled = ec != e_clock::disabled; out->addItem(qt(busname)); enable_combobox_item(out, bus, enabled); } } out->setCurrentIndex(thrubus); connect ( out, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_metro_thru_buss(int)) ); } /* * combobox_metro_thru_channel */ repopulate_thru_channel_menu(int(rc().metro_settings().thru_buss())); } void qseditoptions::repopulate_channel_menu (int buss) { disconnect ( ui->combobox_metro_channel, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_metro_channel(int)) ); ui->combobox_metro_channel->clear(); for (int channel = 0; channel < c_midichannel_max; ++channel) { char b[4]; /* 2 digits or less */ snprintf(b, sizeof b, "%2d", channel + 1); /* user-style no. */ std::string name = std::string(b); std::string s = usr().instrument_name(buss, channel); if (! s.empty()) { name += " ["; name += s; name += "]"; } QString combo_text(qt(name)); ui->combobox_metro_channel->insertItem(channel, combo_text); } int ch = rc().metro_settings().channel(); if (is_null_channel(ch)) ch = 0; ui->combobox_metro_channel->setCurrentIndex(ch); connect ( ui->combobox_metro_channel, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_metro_channel(int)) ); } void qseditoptions::repopulate_thru_channel_menu (int buss) { disconnect ( ui->combobox_metro_thru_channel, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_metro_thru_channel(int)) ); ui->combobox_metro_thru_channel->clear(); for (int channel = 0; channel < c_midichannel_max; ++channel) { char b[4]; /* 2 digits or less */ snprintf(b, sizeof b, "%2d", channel + 1); /* user-style no. */ std::string name = std::string(b); std::string s = usr().instrument_name(buss, channel); if (! s.empty()) { name += " ["; name += s; name += "]"; } QString combo_text(qt(name)); ui->combobox_metro_thru_channel->insertItem(channel, combo_text); } int ch = rc().metro_settings().thru_channel(); if (is_null_channel(ch)) ch = 0; ui->combobox_metro_thru_channel->setCurrentIndex(ch); connect ( ui->combobox_metro_thru_channel, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_metro_thru_channel(int)) ); } void qseditoptions::modify_metronome (bool enablereload) { if (enablereload) ui->button_metro_reload->setEnabled(true); modify_rc(); } void qseditoptions::slot_metro_beats_per_bar () { QString text = ui->lineedit_metro_beats_per_bar->text(); std::string b = text.toStdString(); int bpb = string_to_int(b); if (bpb != rc().metro_settings().beats_per_bar()) { rc().metro_settings().beats_per_bar(bpb); modify_metronome(); } } void qseditoptions::slot_metro_beat_width () { QString text = ui->lineedit_metro_beat_width->text(); std::string b = text.toStdString(); int bw = string_to_int(b); if (bw != rc().metro_settings().beat_width()) { rc().metro_settings().beat_width(bw); modify_metronome(); } } void qseditoptions::slot_metro_main_patch () { QString text = ui->lineedit_metro_main_patch->text(); std::string p = text.toStdString(); int patch = string_to_int(p); if (patch != rc().metro_settings().main_patch()) { rc().metro_settings().main_patch(patch); modify_metronome(); } } void qseditoptions::slot_metro_main_note () { QString text = ui->lineedit_metro_main_note->text(); std::string n = text.toStdString(); int note = string_to_int(n); if (note != rc().metro_settings().main_note()) { rc().metro_settings().main_note(note); modify_metronome(); } } void qseditoptions::slot_metro_main_velocity () { QString text = ui->lineedit_metro_main_velocity->text(); std::string v = text.toStdString(); int vel = string_to_int(v); if (vel != rc().metro_settings().main_note_velocity()) { rc().metro_settings().main_note_velocity(vel); modify_metronome(); } } void qseditoptions::slot_metro_main_fraction () { QString text = ui->lineedit_metro_main_fraction->text(); std::string f = text.toStdString(); float fraction = string_to_double(f); if (fraction != rc().metro_settings().main_note_fraction()) { rc().metro_settings().main_note_fraction(fraction); modify_metronome(); } } void qseditoptions::slot_metro_sub_patch () { QString text = ui->lineedit_metro_sub_patch->text(); std::string p = text.toStdString(); int patch = string_to_int(p); if (patch != rc().metro_settings().sub_patch()) { rc().metro_settings().sub_patch(patch); modify_metronome(); } } void qseditoptions::slot_metro_sub_note () { QString text = ui->lineedit_metro_sub_note->text(); std::string n = text.toStdString(); int note = string_to_int(n); if (note != rc().metro_settings().sub_note()) { rc().metro_settings().sub_note(note); modify_metronome(); } } void qseditoptions::slot_metro_sub_velocity () { QString text = ui->lineedit_metro_sub_velocity->text(); std::string v = text.toStdString(); int vel = string_to_int(v); if (vel != rc().metro_settings().sub_note_velocity()) { rc().metro_settings().sub_note_velocity(vel); modify_metronome(); } } void qseditoptions::slot_metro_sub_fraction () { QString text = ui->lineedit_metro_sub_fraction->text(); std::string f = text.toStdString(); float fraction = string_to_double(f); if (fraction != rc().metro_settings().sub_note_fraction()) { rc().metro_settings().sub_note_fraction(fraction); modify_metronome(); } } void qseditoptions::slot_metro_reload () { ui->button_metro_reload->setEnabled(false); perf().reload_metronome(); } void qseditoptions::slot_metro_buss (int index) { int b = int(rc().metro_settings().buss()); if (index != b) { rc().metro_settings().buss(midibyte(index)); repopulate_channel_menu(int(rc().metro_settings().buss())); modify_metronome(); } } void qseditoptions::slot_metro_channel (int index) { int c = int(rc().metro_settings().channel()); if (index != c) { rc().metro_settings().channel(midibyte(index)); modify_metronome(); } } void qseditoptions::slot_metro_count_in () { bool on = ui->checkbox_metro_count_in->isChecked(); rc().metro_settings().count_in_active(on); modify_metronome(); } void qseditoptions::slot_metro_count_in_measures () { QString text = ui->lineedit_metro_count_in->text(); std::string m = text.toStdString(); int measures = string_to_int(m); if (measures != rc().metro_settings().count_in_measures()) { rc().metro_settings().count_in_measures(measures); modify_metronome(); } } void qseditoptions::slot_metro_recording () { bool on = ui->checkbox_metro_recording->isChecked(); rc().metro_settings().count_in_recording(on); modify_metronome(true); /* no reload button */ } void qseditoptions::slot_metro_recording_measures () { QString text = ui->lineedit_metro_recording_measures->text(); std::string m = text.toStdString(); int measures = string_to_int(m); if (measures != rc().metro_settings().recording_measures()) { rc().metro_settings().recording_measures(measures); modify_metronome(true); /* no reload button */ } } void qseditoptions::slot_metro_record_buss (int index) { int b = int(rc().metro_settings().recording_buss()); if (index != b) { rc().metro_settings().recording_buss(midibyte(index)); modify_metronome(true); /* no reload button */ } } void qseditoptions::slot_metro_thru_buss (int index) { int b = int(rc().metro_settings().thru_buss()); if (index != b) { rc().metro_settings().thru_buss(midibyte(index)); repopulate_thru_channel_menu(int(rc().metro_settings().thru_buss())); modify_metronome(); } } void qseditoptions::slot_metro_thru_channel (int index) { int c = int(rc().metro_settings().thru_channel()); if (index != c) { rc().metro_settings().thru_channel(midibyte(index)); modify_metronome(); } } /* *--------------------------------------------------------------------- * Pattern tab. *--------------------------------------------------------------------- */ /* * Duty now for the Future! * * checkBoxEscapePattern (for issue #117) * checkBoxNewPatternArm * checkBoxNewPatternRecord * checkBoxNewPatternTighten * checkBoxNewPatternQRecord * checkBoxNewPatternNoteMap * checkBoxNewPatternThru * checkBoxNewPatternWrapAround * comboBoxRecordStyle * * spinBoxJitter * spinBoxAmplitude */ void qseditoptions::setup_tab_pattern () { ui->tabWidget->setTabToolTip ( Tab_Pattern, "Options for new patterns and randomization" ); /* * Addition 'usr' boolean options. */ ui->checkBoxEscapePattern->setChecked(usr().escape_pattern()); connect ( ui->checkBoxEscapePattern, SIGNAL(clicked(bool)), this, SLOT(slot_escape_pattern()) ); ui->checkBoxSMF0_to_1->setChecked(usr().convert_to_smf_1()); connect ( ui->checkBoxSMF0_to_1, SIGNAL(clicked(bool)), this, SLOT(slot_convert_to_smf_1()) ); /* * New-pattern boolean options. */ ui->checkBoxNewPatternArm->setChecked(usr().pattern_armed()); connect ( ui->checkBoxNewPatternArm, SIGNAL(clicked(bool)), this, SLOT(slot_pattern_arm()) ); ui->checkBoxNewPatternTighten->setChecked(usr().pattern_tighten()); connect ( ui->checkBoxNewPatternTighten, SIGNAL(clicked(bool)), this, SLOT(slot_pattern_tighten()) ); ui->checkBoxNewPatternQRecord->setChecked(usr().pattern_qrecord()); connect ( ui->checkBoxNewPatternQRecord, SIGNAL(clicked(bool)), this, SLOT(slot_pattern_qrecord()) ); ui->checkBoxNewPatternNoteMap->setChecked(usr().pattern_notemap()); connect ( ui->checkBoxNewPatternNoteMap, SIGNAL(clicked(bool)), this, SLOT(slot_pattern_notemap()) ); ui->checkBoxNewApply->setChecked(usr().pattern_new_only()); connect ( ui->checkBoxNewApply, SIGNAL(clicked(bool)), this, SLOT(slot_pattern_new_only()) ); ui->checkBoxNewPatternRecord->setChecked(usr().pattern_record()); connect ( ui->checkBoxNewPatternRecord, SIGNAL(clicked(bool)), this, SLOT(slot_pattern_record()) ); ui->checkBoxNewPatternThru->setChecked(usr().pattern_thru()); connect ( ui->checkBoxNewPatternThru, SIGNAL(clicked(bool)), this, SLOT(slot_pattern_thru()) ); ui->checkBoxNewPatternWrapAround->setChecked ( usr().pattern_wraparound() ); connect ( ui->checkBoxNewPatternWrapAround, SIGNAL(clicked(bool)), this, SLOT(slot_pattern_wraparound()) ); /* * New-pattern record-style options. These should be an array/function * in the settings module! Note the handling of this list is different * from PPQN and Buffer-size list handling. */ const tokenization & items = rec_style_items(); // settings module ui->comboBoxRecordStyle->addItem(qt(items[0])); // "Merge" ui->comboBoxRecordStyle->addItem(qt(items[1])); // "Overwrite" ui->comboBoxRecordStyle->addItem(qt(items[2])); // "Expand" ui->comboBoxRecordStyle->addItem(qt(items[3])); // "Oneshot" ui->comboBoxRecordStyle->addItem(qt(items[4])); // "Oneshot Reset" int r = usr().pattern_record_code(); ui->comboBoxRecordStyle->setCurrentIndex(r); connect ( ui->comboBoxRecordStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_record_style(int)) ); /* * Randomization options. */ ui->spinBoxJitter->setValue(usr().jitter_divisor()); connect ( ui->spinBoxJitter, SIGNAL(valueChanged(int)), this, SLOT(slot_jitter(int)) ); ui->spinBoxAmplitude->setValue(usr().randomization_amount()); connect ( ui->spinBoxAmplitude, SIGNAL(valueChanged(int)), this, SLOT(slot_amplitude(int)) ); } void qseditoptions::slot_escape_pattern () { bool enable = ui->checkBoxEscapePattern->isChecked(); usr().escape_pattern(enable); modify_usr(); } void qseditoptions::slot_convert_to_smf_1 () { bool enable = ui->checkBoxSMF0_to_1->isChecked(); usr().convert_to_smf_1(enable); modify_usr(); } void qseditoptions::slot_pattern_arm () { bool enable = ui->checkBoxNewPatternArm->isChecked(); usr().pattern_armed(enable); modify_usr(); } /** * Note: currently tightening, quantizing, and note-mapping are mutually * exclusive. This GUI needs to enforce that. Or we need to allow * note-mapping to occur along with one of the others. */ void qseditoptions::slot_pattern_tighten () { bool enable = ui->checkBoxNewPatternTighten->isChecked(); usr().pattern_tighten(enable); if (enable) { ui->checkBoxNewPatternQRecord->setChecked(false); ui->checkBoxNewPatternNoteMap->setChecked(false); usr().pattern_qrecord(false); usr().pattern_notemap(false); } modify_usr(); } void qseditoptions::slot_pattern_qrecord () { bool enable = ui->checkBoxNewPatternQRecord->isChecked(); usr().pattern_qrecord(enable); if (enable) { ui->checkBoxNewPatternTighten->setChecked(false); ui->checkBoxNewPatternNoteMap->setChecked(false); usr().pattern_tighten(false); usr().pattern_notemap(false); } modify_usr(); } void qseditoptions::slot_pattern_notemap () { bool enable = ui->checkBoxNewPatternNoteMap->isChecked(); usr().pattern_notemap(enable); if (enable) { ui->checkBoxNewPatternTighten->setChecked(false); ui->checkBoxNewPatternQRecord->setChecked(false); usr().pattern_tighten(false); usr().pattern_qrecord(false); } modify_usr(); } void qseditoptions::slot_pattern_new_only () { bool enable = ui->checkBoxNewApply->isChecked(); usr().pattern_new_only(enable); if (enable) { // nothing yet to do } modify_usr(); } void qseditoptions::slot_pattern_record () { bool enable = ui->checkBoxNewPatternRecord->isChecked(); usr().pattern_record(enable); modify_usr(); } void qseditoptions::slot_pattern_thru () { bool enable = ui->checkBoxNewPatternThru->isChecked(); usr().pattern_thru(enable); modify_usr(); } void qseditoptions::slot_pattern_wraparound () { bool enable = ui->checkBoxNewPatternWrapAround->isChecked(); usr().pattern_wraparound(enable); modify_usr(); } void qseditoptions::slot_record_style (int index) { usr().set_pattern_record_style(index); modify_usr(); } void qseditoptions::slot_jitter (int jitr) { usr().jitter_divisor(jitr); modify_usr(); } void qseditoptions::slot_amplitude (int amp) { usr().randomization_amount(amp); modify_usr(); } /* *--------------------------------------------------------------------- * Session tab. *--------------------------------------------------------------------- */ void qseditoptions::setup_tab_session () { ui->tabWidget->setTabToolTip ( Tab_Session, "Options for session configuration files" ); /* * Group-box for changing the session. */ QButtonGroup * sgroup = new QButtonGroup(this); sgroup->addButton ( ui->radio_session_none, static_cast(usrsettings::session::none) ); sgroup->addButton ( ui->radio_session_nsm, static_cast(usrsettings::session::nsm) ); sgroup->addButton ( ui->radio_session_jack, static_cast(usrsettings::session::jack) ); connect ( sgroup, SIGNAL(buttonClicked(int)), this, SLOT(slot_session(int)) ); #if ! defined SEQ66_JACK_SESSION ui->radio_session_jack->setEnabled(false); #endif #if ! defined SEQ66_NSM_SUPPORT ui->radio_session_nsm->setEnabled(false); #endif /* * The URL text-edit. */ connect ( ui->lineEditNsmUrl, SIGNAL(editingFinished()), this, SLOT(slot_nsm_url()) ); /* * The Configuration Files section. */ /* * 'rc' file. This file is ALWAYS active, so that check-box is read-only. */ connect ( ui->checkBoxSaveRc, SIGNAL(clicked(bool)), this, SLOT(slot_rc_save_click()) ); #if defined USE_RC_NAME_CHANGE /* * There's no real way to change the 'rc' from the UI and have it * stick. It can be specified by the --config option only. * We could eventually support an environment variablie like * SEQ66_RC_FILE. */ connect ( ui->lineEditRc, SIGNAL(editingFinished()), this, SLOT(slot_rc_filename()) ); connect ( ui->pushButtonLoadRc, SIGNAL(clicked(bool)), this, SLOT(slot_load_rc_filename()) ); #else ui->lineEditRc->setReadOnly(true); ui->lineEditRc->setEnabled(false); ui->pushButtonLoadRc->hide(); #endif /* * 'usr' file. Making 'usr' inactive is no longer permitted, even * experimentally. */ #if defined SEQ66_CAN_DEACTIVATE_USR connect ( ui->checkBoxActiveUsr, SIGNAL(clicked(bool)), this, SLOT(slot_usr_active_click()) ); #else ui->checkBoxActiveUsr->setEnabled(false); #endif connect ( ui->checkBoxSaveUsr, SIGNAL(clicked(bool)), this, SLOT(slot_usr_save_click()) ); connect ( ui->lineEditUsr, SIGNAL(editingFinished()), this, SLOT(slot_usr_filename()) ); /* * 'mutes' file */ connect ( ui->checkBoxActiveMutes, SIGNAL(clicked(bool)), this, SLOT(slot_mutes_active_click()) ); connect ( ui->checkBoxSaveMutes, SIGNAL(clicked(bool)), this, SLOT(slot_mutes_save_click()) ); connect ( ui->lineEditMutes, SIGNAL(editingFinished()), this, SLOT(slot_mutes_filename()) ); connect ( ui->pushButtonLoadMutes, SIGNAL(clicked(bool)), this, SLOT(slot_load_mutes_filename()) ); /* * 'playlist' file */ connect ( ui->checkBoxActivePlaylist, SIGNAL(clicked(bool)), this, SLOT(slot_playlist_active_click()) ); connect ( ui->checkBoxSavePlaylist, SIGNAL(clicked(bool)), this, SLOT(slot_playlist_save_click()) ); connect ( ui->lineEditPlaylist, SIGNAL(editingFinished()), this, SLOT(slot_playlist_filename()) ); ui->pushButtonLoadPlaylist->hide(); /* hmmmmmm */ connect ( ui->pushButtonLoadPlaylist, SIGNAL(clicked(bool)), this, SLOT(slot_load_playlist_filename()) ); /* * 'ctrl' file. Since this configuration is not editable while the * application is running, the "auto-save" check-box is read-only. */ connect ( ui->checkBoxActiveCtrl, SIGNAL(clicked(bool)), this, SLOT(slot_ctrl_active_click()) ); #if defined SEQ66_CAN_SAVE_CTRL connect ( ui->checkBoxSaveCtrl, SIGNAL(clicked(bool)), this, SLOT(slot_ctrl_save_click()) ); #else ui->checkBoxSaveCtrl->setChecked(false); ui->checkBoxSaveCtrl->setEnabled(false); #endif connect ( ui->lineEditCtrl, SIGNAL(editingFinished()), this, SLOT(slot_ctrl_filename()) ); connect ( ui->pushButtonLoadCtrl, SIGNAL(clicked(bool)), this, SLOT(slot_load_ctrl_filename()) ); /* * 'drums' file. Since this configuration is not editable while the * application is running, the "auto-save" check-box is read-only. */ connect ( ui->checkBoxActiveDrums, SIGNAL(clicked(bool)), this, SLOT(slot_drums_active_click()) ); #if defined SEQ66_CAN_SAVE_DRUMS connect ( ui->checkBoxSaveDrums, SIGNAL(clicked(bool)), this, SLOT(slot_drums_save_click()) ); #endif connect ( ui->lineEditDrums, SIGNAL(editingFinished()), this, SLOT(slot_drums_filename()) ); connect ( ui->pushButtonLoadDrums, SIGNAL(clicked(bool)), this, SLOT(slot_load_drums_filename()) ); /* * 'patches' file. */ connect ( ui->checkBoxActivePatches, SIGNAL(clicked(bool)), this, SLOT(slot_patches_active_click()) ); connect ( ui->lineEditPatches, SIGNAL(editingFinished()), this, SLOT(slot_patches_filename()) ); connect ( ui->pushButtonLoadPatches, SIGNAL(clicked(bool)), this, SLOT(slot_load_patches_filename()) ); connect ( ui->pushButtonSavePatches, SIGNAL(clicked(bool)), this, SLOT(slot_patches_save_now_click()) ); /* * 'palette' file. */ connect ( ui->checkBoxActivePalette, SIGNAL(clicked(bool)), this, SLOT(slot_palette_active_click()) ); connect ( ui->lineEditPalette, SIGNAL(editingFinished()), this, SLOT(slot_palette_filename()) ); connect ( ui->pushButtonLoadPalette, SIGNAL(clicked(bool)), this, SLOT(slot_load_palette_filename()) ); connect ( ui->pushButtonSavePalette, SIGNAL(clicked(bool)), this, SLOT(slot_palette_save_now_click()) ); /* * A 'palette' extra. Immediate saving of the palette. */ #if defined SEQ66_PROVIDE_AUTO_COLOR_INVERSION ui->pushButtonSavePalette->setText("Store ~Palette"); connect ( ui->pushButtonSavePalette, SIGNAL(clicked(bool)), this, SLOT(slot_palette_save_inverse()) ); #endif /* * 'qss' file. Since this configuration is not editable while the * application is running, the "auto-save" check-box is read-only. * Also note that this refers to a Qt Style Sheet. */ connect ( ui->checkBoxActiveStyleSheet, SIGNAL(clicked(bool)), this, SLOT(slot_stylesheet_active_click()) ); connect ( ui->lineEditStyleSheet, SIGNAL(editingFinished()), this, SLOT(slot_stylesheet_filename()) ); connect ( ui->pushButtonLoadStyleSheet, SIGNAL(clicked(bool)), this, SLOT(slot_load_stylesheet_filename()) ); /* * Viewers. */ std::string viewer = usr().user_browser(); if (viewer.empty()) viewer = "Default"; ui->lineEditBrowser->setText(qt(viewer)); connect ( ui->lineEditBrowser, SIGNAL(editingFinished()), this, SLOT(slot_browser_executable()) ); connect ( ui->pushButtonSetBrowser, SIGNAL(clicked(bool)), this, SLOT(slot_load_browser_executable()) ); viewer = usr().user_pdf_viewer(); if (viewer.empty()) viewer = "Default"; ui->lineEditPdfViewer->setText(qt(viewer)); connect ( ui->lineEditPdfViewer, SIGNAL(editingFinished()), this, SLOT(slot_pdf_executable()) ); connect ( ui->pushButtonSetPdfViewer, SIGNAL(clicked(bool)), this, SLOT(slot_load_pdf_viewer_executable()) ); /* * This items is shown when certain files are changed. */ ui->label_exit_required->hide(); } void qseditoptions::exit_required () { ui->label_exit_required->show(); } bool qseditoptions::set_ppqn_combo () { std::string p = std::to_string(int(perf().ppqn())); return fill_combobox(ui->combo_box_ppqn, ppqn_list(), p); } bool qseditoptions::set_buffer_size_combo () { std::string sz = std::to_string(rc().jack_buffer_size()); return fill_combobox(ui->combo_box_buffer_size, buffer_size_list(), sz); } void qseditoptions::show_sets_mode (rcsettings::setsmode sm) { switch (sm) { case rcsettings::setsmode::normal: ui->radio_setsmode_normal->setChecked(true); break; case rcsettings::setsmode::autoarm: ui->radio_setsmode_autoarm->setChecked(true); break; case rcsettings::setsmode::additive: ui->radio_setsmode_additive->setChecked(true); break; case rcsettings::setsmode::allsets: ui->radio_setsmode_allsets->setChecked(true); break; default: break; } } void qseditoptions::show_start_mode (sequence::playback sm) { switch (sm) { case sequence::playback::live: ui->radio_startmode_live->setChecked(true); break; case sequence::playback::song: ui->radio_startmode_song->setChecked(true); break; case sequence::playback::automatic: ui->radio_startmode_auto->setChecked(true); break; default: break; } } void qseditoptions::slot_sets_mode (int buttonno) { rcsettings::setsmode previous = rc().sets_mode(); if (buttonno == setsmode_button_autoarm) rc().sets_mode(rcsettings::setsmode::autoarm); else if (buttonno == setsmode_button_additive) rc().sets_mode(rcsettings::setsmode::additive); else if (buttonno == setsmode_button_allsets) rc().sets_mode(rcsettings::setsmode::allsets); else rc().sets_mode(rcsettings::setsmode::normal); if (rc().sets_mode() != previous) modify_rc(); } void qseditoptions::slot_start_mode (int buttonno) { sequence::playback previous = rc().get_song_start_mode(); if (buttonno == startmode_button_live) rc().song_start_mode_by_string("live"); else if (buttonno == startmode_button_song) rc().song_start_mode_by_string("song"); else rc().song_start_mode_by_string("auto"); if (rc().get_song_start_mode() != previous) modify_rc(); } /** * A run-time-only setting. No configuration modification. */ void qseditoptions::slot_jack_mode (int buttonno) { if (buttonno == playmode_button_live) perf().song_mode(false); else if (buttonno == playmode_button_song) perf().song_mode(true); } /** * A run-time-only setting. No configuration modification. */ void qseditoptions::slot_jack_connect () { perf().set_jack_mode(true); } /** * A run-time-only setting. No configuration modification. */ void qseditoptions::slot_jack_disconnect () { perf().set_jack_mode(false); } void qseditoptions::slot_master_cond () { bool use_master_cond = ui->chkJackConditional->isChecked(); rc().with_jack_master_cond(use_master_cond); if (use_master_cond) { ui->chkJackMaster->setChecked(false); rc().with_jack_master(false); } modify_rc(); sync(); } void qseditoptions::slot_time_master () { bool use_time_master = ui->chkJackMaster->isChecked(); rc().with_jack_master(use_time_master); if (use_time_master) { ui->chkJackConditional->setChecked(false); rc().with_jack_master_cond(false); } modify_rc(); sync(); } void qseditoptions::slot_transport_support () { bool use_jack_transport = ui->chkJackTransport->isChecked(); rc().with_jack_transport(use_jack_transport); if (! use_jack_transport) { ui->chkJackConditional->setChecked(false); ui->chkJackMaster->setChecked(false); rc().with_jack_master_cond(false); rc().with_jack_master(false); } modify_rc(); sync(); } void qseditoptions::slot_jack_midi () { rc().with_jack_midi(ui->chkJackNative->isChecked()); modify_rc(); sync(); } void qseditoptions::slot_jack_auto_connect () { rc().jack_auto_connect(ui->chkJackAutoConnect->isChecked()); modify_rc(); sync(); } void qseditoptions::reload_needed (bool flag) { if (flag) state_changed(); else state_unchanged(); sync(); } /** * State of buttons at startup and after Reset is clicked. See the table * at the top of this module. */ void qseditoptions::state_unchanged () { set_enabled(QDialogButtonBox::Apply, false); set_enabled(QDialogButtonBox::Cancel, true); set_enabled(QDialogButtonBox::Reset, false); set_enabled(QDialogButtonBox::Ok, false); if (m_is_initialized) enable_reload_button(false); } void qseditoptions::state_changed () { if (! usr().in_nsm_session()) set_enabled(QDialogButtonBox::Apply, true); set_enabled(QDialogButtonBox::Cancel, false); set_enabled(QDialogButtonBox::Reset, true); set_enabled(QDialogButtonBox::Ok, true); enable_reload_button(true); } void qseditoptions::state_applied () { set_enabled(QDialogButtonBox::Apply, false); set_enabled(QDialogButtonBox::Cancel, true); set_enabled(QDialogButtonBox::Reset, false); set_enabled(QDialogButtonBox::Ok, false); enable_reload_button(true); } void qseditoptions::slot_io_maps () { bool ok = perf().store_io_maps(); /* sets 'rc' auto-save */ if (ok) { bool active = perf().port_maps_active(); ui->ioPortsMappedCheck->setChecked(active); rc().portmaps_active(active); modify_rc(); } else { ui->ioPortsMappedCheck->setChecked(false); rc().portmaps_active(false); modify_rc(); } } #if defined SEQ66_ALLOW_PORTMAP_CLEAR void qseditoptions::slot_remove_io_maps () { perf().clear_io_maps(); /* sets save, inactive */ ui->ioPortsMappedCheck->setChecked(false); rc().portmaps_active(false); modify_rc(); } #endif void qseditoptions::slot_activate_io_maps () { bool active = ui->ioPortsMappedCheck->isChecked(); perf().activate_io_maps(active); /* sets 'rc' auto-save */ active = perf().port_maps_active(); rc().portmaps_active(active); modify_rc(); } void qseditoptions::show_session (usrsettings::session sm) { bool url_modifiable = false; std::string tenturl; #if defined SEQ66_NSM_SUPPORT tenturl = nsm::get_url(); #else ui->radio_session_nsm->setChecked(false); ui->radio_session_nsm->setEnabled(false); #endif if (tenturl.empty()) { #if defined SEQ66_NSM_SUPPORT if (usr().want_nsm_session()) { url_modifiable = true; tenturl = usr().session_url(); } #endif #if defined SEQ66_JACK_SESSION if (usr().want_jack_session()) { tenturl = rc().jack_session(); /* JACK session UUID */ } #endif } ui->label_nsm_url->setEnabled(false); if (usr().in_nsm_session()) { ui->radio_session_none->setChecked(false); ui->radio_session_none->setEnabled(false); ui->radio_session_jack->setChecked(false); ui->radio_session_jack->setEnabled(false); #if defined SEQ66_NSM_SUPPORT ui->radio_session_nsm->setChecked(true); ui->radio_session_nsm->setEnabled(true); ui->label_nsm_url->setEnabled(true); ui->label_nsm_url->setText("NSM URL"); ui->lineEditNsmUrl->setText(qt(tenturl)); #endif } else { switch (sm) { case usrsettings::session::none: ui->radio_session_none->setChecked(true); ui->label_nsm_url->setText("UUID N/A"); break; case usrsettings::session::nsm: #if defined SEQ66_NSM_SUPPORT ui->radio_session_nsm->setChecked(true); ui->label_nsm_url->setEnabled(true); ui->label_nsm_url->setText("NSM URL"); ui->lineEditNsmUrl->setText(qt(tenturl)); #endif break; case usrsettings::session::jack: #if defined SEQ66_JACK_SESSION ui->radio_session_jack->setChecked(true); ui->label_nsm_url->setEnabled(true); ui->label_nsm_url->setText("JACK UUID"); ui->lineEditNsmUrl->setText(qt(tenturl)); #endif break; default: break; } } ui->lineEditNsmUrl->setEnabled(url_modifiable); } void qseditoptions::slot_session (int buttonno) { usrsettings::session current = usr().session_manager(); if (buttonno == static_cast(usrsettings::session::nsm)) { usr().clear_option_bits(); /* see usrsettings::option_bits */ usr().session_manager("nsm"); ui->label_nsm_url->setEnabled(true); ui->label_nsm_url->setText("NSM URL"); ui->lineEditNsmUrl->setEnabled(true); } else if (buttonno == static_cast(usrsettings::session::jack)) { usr().clear_option_bits(); /* see usrsettings::option_bits */ usr().session_manager("jack"); ui->label_nsm_url->setEnabled(true); ui->label_nsm_url->setText("JACK UUID"); } else { usr().clear_option_bits(); /* see usrsettings::option_bits */ usr().session_manager("none"); ui->label_nsm_url->setEnabled(false); ui->label_nsm_url->setText("N/A"); ui->lineEditNsmUrl->setEnabled(false); } if (usr().session_manager() != current) modify_usr(); } void qseditoptions::slot_ui_scaling () { QString qs = ui->lineEditUiScaling->text(); /* w */ QString qheight = ui->lineEditUiScalingHeight->text(); /* h */ ui_scaling_helper(qs, qheight); modify_usr(); } void qseditoptions::slot_grid_spacing () { QString qs = ui->lineEditGridSpacing->text(); std::string spacingtext = qs.toStdString(); int spacing = string_to_int(spacingtext, 2); if (spacing != usr().mainwnd_spacing()) { usr().mainwnd_spacing(spacing); modify_usr(); } } /** * Just closes. Any modifications are already in the rc() and usr() objects. * Also sets the reload buttons, but only the one in the main-window Session * tab will be available. */ void qseditoptions::okay () { enable_reload_button(true); close(); } /** * Restores the settings from the "backup" variables when the user cancels * the edit. * * Not necessary here: sync(); */ void qseditoptions::cancel () { reset(); state_unchanged(); close(); } /** * The Apply button now shows "Restart Seq66!" and serves to restart the whole * application when clicked. * * This button shows "NSM" and is always disabled when running under a * session manager. * * Aren't the backup calls useless at this point??? */ void qseditoptions::apply () { /* * backup() useless at this point, 'cause we're outa here! */ state_unchanged(); signal_for_restart(); /* warnprint("Session reload request"); */ } void qseditoptions::reset () { rc() = m_backup_rc; usr() = m_backup_usr; reload_needed(false); } void qseditoptions::set_enabled (QDialogButtonBox::StandardButton bcode, bool on) { QPushButton * button = ui->buttonBoxOptionsDialog->button(bcode); button->setEnabled(on); } void qseditoptions::set_text ( QDialogButtonBox::StandardButton bcode, const std::string & text ) { QPushButton * button = ui->buttonBoxOptionsDialog->button(bcode); button->setText(qt(text)); if (text == "NSM") button->setEnabled(false); } void qseditoptions::show_button (QDialogButtonBox::StandardButton bcode, bool show) { QPushButton * button = ui->buttonBoxOptionsDialog->button(bcode); if (show) button->show(); else button->hide(); } /** * Backs up the JACK, Time, Key-height, and Note-Resume settings in case the * user cancels. In that case, the cancel() function will put these settings * back into the various settings objects. */ void qseditoptions::backup () { m_backup_rc = rc(); m_backup_usr = usr(); } /** * Sync with preferences. In other words, the current values in the various * settings objects are used to set the user-interface elements in this * dialog. */ void qseditoptions::sync () { sync_rc(); sync_usr(); /* * Other items to sync. */ ui->checkBoxSaveMutes->setChecked(rc().auto_mutes_save()); ui->checkBoxActiveMutes->setChecked(rc().mute_group_file_active()); } /* * Get all the settings from the 'rc' file and make the GUI controls match * them. Note that "foreach" is a Qt-specific keyword, not a C++ keyword. */ void qseditoptions::sync_rc () { ui->chkJackTransport->setChecked(rc().with_jack_transport()); ui->chkJackMaster->setChecked(rc().with_jack_master()); ui->chkJackConditional->setChecked(rc().with_jack_master_cond()); ui->chkJackNative->setChecked(rc().with_jack_midi()); #if defined SEQ66_JACK_SUPPORT ui->chkJackAutoConnect->setChecked(rc().jack_auto_connect()); #else rc().jack_auto_connect(false); ui->chkJackAutoConnect->setChecked(false); #endif ui->chkJackMaster->setDisabled(! rc().with_jack_transport()); ui->chkJackConditional->setDisabled(! rc().with_jack_transport()); show_sets_mode(rc().sets_mode()); show_start_mode(rc().get_song_start_mode()); #if defined SEQ66_JACK_SUPPORT int rbid = perf().song_mode() ? playmode_button_song : playmode_button_live ; foreach (QAbstractButton * button, m_live_song_buttons->buttons()) { int bid = m_live_song_buttons->id(button); button->setChecked(bid == rbid); } #endif /* * Sync all the settings for "UI Boolean Options. */ ui->checkBoxVerbose->setChecked(rc().verbose()); ui->checkBoxLoadMostRecent->setChecked(rc().load_most_recent()); ui->checkBoxShowFullRecentPaths->setChecked(rc().full_recent_paths()); ui->checkBoxLongBussNames->setChecked(rc().port_naming() == portname::full); ui->checkBoxPairBussNames->setChecked(rc().port_naming() == portname::pair); ui->checkBoxLockMainWindow->setChecked(usr().lock_main_window()); ui->checkBoxSwapCoordinates->setChecked(usr().swap_coordinates()); ui->checkBoxBoldGridSlots->setChecked(usr().progress_bar_thick()); ui->checkBoxDoubleClickEdit->setChecked(rc().allow_click_edit()); std::string filespec = rc().config_filespec(); ui->checkBoxSaveRc->setChecked(rc().auto_rc_save()); ui->checkBoxActiveRc->setChecked(true); /* ALWAYS active */ tooltip_for_filename(ui->lineEditRc, filespec); filespec = rc().user_filespec(); ui->checkBoxSaveUsr->setChecked(rc().auto_usr_save()); ui->checkBoxActiveUsr->setChecked(rc().user_file_active()); tooltip_for_filename(ui->lineEditUsr, filespec); filespec = rc().mute_group_filespec(); ui->checkBoxSaveMutes->setChecked(rc().auto_mutes_save()); ui->checkBoxActiveMutes->setChecked(rc().mute_group_file_active()); tooltip_for_filename(ui->lineEditMutes, filespec); filespec = rc().playlist_filespec(); ui->checkBoxSavePlaylist->setChecked(rc().auto_playlist_save()); ui->checkBoxActivePlaylist->setChecked(rc().playlist_active()); tooltip_for_filename(ui->lineEditPlaylist, filespec); filespec = rc().midi_control_filespec(); ui->checkBoxSaveCtrl->setChecked(rc().auto_ctrl_save()); /* readonly */ ui->checkBoxActiveCtrl->setChecked(rc().midi_control_active()); tooltip_for_filename(ui->lineEditCtrl, filespec); filespec = rc().notemap_filespec(); #if defined SEQ66_CAN_SAVE_DRUMS /* * No way to edit drums, so no need to save them. This also applies * to patches, palette, and style-sheets. */ ui->checkBoxSaveDrums->setChecked(rc().auto_drums_save()); /* read-only */ #endif ui->checkBoxActiveDrums->setChecked(rc().notemap_active()); tooltip_for_filename(ui->lineEditDrums, filespec); filespec = rc().patches_filespec(); ui->checkBoxActivePatches->setChecked(rc().patches_active()); tooltip_for_filename(ui->lineEditPatches, filespec); filespec = rc().palette_filespec(); ui->checkBoxActivePalette->setChecked(rc().palette_active()); tooltip_for_filename(ui->lineEditPalette, filespec); /* * We will never save the style sheet from within Seq66. We leave the * checkbox visible, unchecked, and disabled. * * ui->checkBoxSaveStyleSheet->hide(); */ filespec = rc().style_sheet_filespec(); ui->checkBoxSaveStyleSheet->setChecked(false); ui->checkBoxActiveStyleSheet->setChecked(rc().style_sheet_active()); tooltip_for_filename(ui->lineEditStyleSheet, filespec); bool outportmap = output_port_map().active(); bool inportmap = input_port_map().active(); ui->ioPortsMappedCheck->setChecked(outportmap && inportmap); ui->checkBoxVirtualPorts->setChecked(rc().manual_ports()); std::string value = std::to_string(rc().manual_port_count()); ui->lineEditOutputCount->setText(qt(value)); value = std::to_string(rc().manual_in_port_count()); ui->lineEditInputCount->setText(qt(value)); bool bybuss = rc().record_by_buss(); ui->checkBoxRecordByBuss->setChecked(bybuss); if (bybuss) ui->checkBoxRecordByChannel->setChecked(false); else ui->checkBoxRecordByChannel->setChecked(rc().record_by_channel()); } void qseditoptions::sync_usr () { char tmp[32]; snprintf(tmp, sizeof tmp, "%.2f", usr().window_scale()); ui->lineEditUiScaling->setText(tmp); snprintf(tmp, sizeof tmp, "%.2f", usr().window_scale_y()); ui->lineEditUiScalingHeight->setText(tmp); ui->chkNoteResume->setChecked(usr().resume_note_ons()); ui->chkUseFilesPPQN->setChecked(usr().use_file_ppqn()); ui->chkSongRecordSnap->setChecked(perf().song_record_snap()); ui->spinKeyHeight->setValue(usr().key_height()); /* * New-pattern items */ ui->checkBoxEscapePattern->setChecked(usr().escape_pattern()); ui->checkBoxNewPatternArm->setChecked(usr().pattern_armed()); ui->checkBoxNewPatternTighten->setChecked(usr().pattern_tighten()); ui->checkBoxNewPatternQRecord->setChecked(usr().pattern_qrecord()); ui->checkBoxNewPatternNoteMap->setChecked(usr().pattern_notemap()); ui->checkBoxNewPatternRecord->setChecked(usr().pattern_record()); ui->checkBoxNewPatternThru->setChecked(usr().pattern_thru()); ui->checkBoxNewPatternWrapAround->setChecked ( usr().pattern_wraparound() ); ui->chkAddTimeSig->setChecked(usr().auto_add_time_sig()); int r = usr().pattern_record_code(); ui->comboBoxRecordStyle->setCurrentIndex(r); show_session(usr().session_manager()); set_scaling_fields(); set_set_size_fields(); set_progress_box_fields(); snprintf(tmp, sizeof tmp, "%i", usr().fingerprint_size()); ui->lineEditFingerprintSize->setText(tmp); ui->checkBoxGlobalSeqFeature->setChecked(usr().global_seq_feature()); } /** * Instead of this sequence of calls, we could send a Qt signal from * qclocklayout to eventually call the qsmainwnd slot. We need to make this * item cause immediate action. * * We also need to reconstruct the Control/Display combo-boxes. The * code is a bit much, but seems to work well enough. The alternative * is to keep even disabled devices enabled in the combo-box. */ void qseditoptions::enable_bus_item (int bus, bool enabled) { m_parent_widget->enable_bus_item(bus, enabled); mastermidibus * mmb = perf().master_bus(); const clockslist & opm = output_port_map(); bool outportmap = opm.active(); int buses = outportmap ? opm.count() : mmb->get_num_out_buses() ; setup_clock_combo_box(buses); const inputslist & ipm = input_port_map(); bool inportmap = ipm.active(); buses = inportmap ? ipm.count() : mmb->get_num_in_buses() ; setup_input_combo_box(buses); reload_needed(true); /* immediate action */ } void qseditoptions::enable_reload_button (bool flag) { m_parent_widget->enable_reload_button(flag); ui->pushButtonReload->setEnabled(flag); } /** * Updates the performer::result_note_ons() setting in accord with the * user-interface, and then calls sync(), perhaps needlessly, to * make sure the user-interface items correctly represent the settings. * Resolves issue #5. */ void qseditoptions::slot_note_resume () { if (m_is_initialized) { bool resumenotes = ui->chkNoteResume->isChecked(); if (perf().resume_note_ons() != resumenotes) { usr().resume_note_ons(resumenotes); perf().resume_note_ons(resumenotes); modify_usr(); } } } void qseditoptions::slot_ppqn_by_text (const QString & text) { std::string temp = text.toStdString(); if (ppqn_list().valid(temp)) { int p = string_to_int(temp); if (perf().change_ppqn(p)) { ppqn_list().current(temp); m_parent_widget->set_ppqn_text(temp); m_parent_widget->enable_save(); ui->combo_box_ppqn->setItemText(0, text); usr().clear_option_bits(); /* see usrsettings::option_bits */ usr().default_ppqn(p); modify_usr(); } } } void qseditoptions::slot_use_file_ppqn () { if (m_is_initialized) { bool ufppqn = ui->chkUseFilesPPQN->isChecked(); bool status = usr().use_file_ppqn(); if (ufppqn != status) { usr().clear_option_bits(); /* see usrsettings::option_bits */ usr().use_file_ppqn(ufppqn); modify_usr(); } } } void qseditoptions::slot_buffer_size_by_text (const QString & text) { std::string temp = text.toStdString(); if (! temp.empty()) { int bsize = string_to_int(temp); int oldsize = rc().jack_buffer_size(); if (bsize != oldsize) { buffer_size_list().current(temp); rc().jack_buffer_size(bsize); if (rc().jack_buffer_size() == bsize) modify_rc(); } } } /** * Fixed issue #44 "Record live sequence changes" functionality, which * was already in place, but had no runtime user-interface setting. */ void qseditoptions::slot_song_record_snap () { if (m_is_initialized) { bool snappit = ui->chkSongRecordSnap->isChecked(); perf().song_record_snap(snappit); set_enabled(QDialogButtonBox::Ok, true); /* for appearances only */ } } /** * Re issue #137 "xxxx". */ void qseditoptions::slot_add_time_sig () { if (m_is_initialized) { bool addit = ui->chkAddTimeSig->isChecked(); usr().auto_add_time_sig(addit); set_enabled(QDialogButtonBox::Ok, true); /* for appearances only */ modify_usr(); } } /** * Updates the usrsettings::key_height() setting in accord with the * user-interface, and then calls sync(), perhaps needlessly, to * make sure the user-interface items correctly represent the settings. * Also turns on the user-save setting, so that the settings will be written to * the "usr" file upon exit. */ void qseditoptions::slot_key_height () { usr().key_height(ui->spinKeyHeight->value()); if (m_is_initialized) { modify_usr(); sync(); } } void qseditoptions::set_scaling_fields () { char tmp[32]; snprintf(tmp, sizeof tmp, "%.2f", usr().window_scale()); ui->lineEditUiScaling->setText(tmp); snprintf(tmp, sizeof tmp, "%.2f", usr().window_scale_y()); ui->lineEditUiScalingHeight->setText(tmp); } /* * The next function is weird. That's because it uses the same parsing * method as the command-line option. */ void qseditoptions::ui_scaling_helper ( const QString & widthtext, const QString & heighttext ) { std::string wtext = widthtext.toStdString(); std::string htext = heighttext.toStdString(); if (! wtext.empty() && ! htext.empty()) { std::string tuple = wtext + "x" + htext; usr().clear_option_bit(usrsettings::option_bits::option_scale); usr().clear_option_bits(); /* see usrsettings::option_bits */ if (usr().parse_window_scale(tuple)) modify_usr(); } } void qseditoptions::slot_nsm_url () { QString url = ui->lineEditNsmUrl->text(); usr().session_url(url.toStdString()); modify_usr(); } void qseditoptions::set_set_size_fields () { char tmp[32]; snprintf(tmp, sizeof tmp, "%i", usr().mainwnd_rows()); ui->lineEditSetSize->setText(tmp); snprintf(tmp, sizeof tmp, "%i", usr().mainwnd_cols()); ui->lineEditSetSizeColumns->setText(tmp); } void qseditoptions::slot_set_size_rows () { const QString qs = ui->lineEditSetSize->text(); const std::string valuetext = qs.toStdString(); if (! valuetext.empty()) { int rows = string_to_int(valuetext); usr().clear_option_bits(); /* see usrsettings::option_bits */ if (usr().mainwnd_rows(rows)) modify_usr(); else set_set_size_fields(); } } void qseditoptions::slot_set_size_columns () { const QString qs = ui->lineEditSetSizeColumns->text(); const std::string valuetext = qs.toStdString(); if (! valuetext.empty()) { int columns = string_to_int(valuetext); usr().clear_option_bits(); /* see usrsettings::option_bits */ if (usr().mainwnd_cols(columns)) modify_usr(); else set_set_size_fields(); } } void qseditoptions::set_progress_box_fields () { char tmp[32]; snprintf(tmp, sizeof tmp, "%.2f", usr().progress_box_width()); ui->lineEditProgressBox->setText(tmp); snprintf(tmp, sizeof tmp, "%.2f", usr().progress_box_height()); ui->lineEditProgressBoxHeight->setText(tmp); ui->checkBoxProgressBoxShown->setChecked(usr().progress_box_shown()); } void qseditoptions::slot_progress_box_width () { const QString qs = ui->lineEditProgressBox->text(); const std::string wtext = qs.toStdString(); if (! wtext.empty()) { double w = string_to_double(wtext, 0.0, 2); double h = usr().progress_box_height(); if (usr().progress_box_size(w, h)) modify_usr(); else set_progress_box_fields(); } } void qseditoptions::slot_progress_box_height () { const QString qs = ui->lineEditProgressBoxHeight->text(); const std::string htext = qs.toStdString(); if (! htext.empty()) { double w = usr().progress_box_width(); double h = string_to_double(htext, 0.0, 2); if (usr().progress_box_size(w, h)) modify_usr(); else set_progress_box_fields(); } } void qseditoptions::slot_progress_box_shown () { bool on = ui->checkBoxProgressBoxShown->isChecked(); usr().progress_box_shown(on); modify_usr(); } void qseditoptions::slot_fingerprint_size () { const QString qs = ui->lineEditFingerprintSize->text(); std::string text = qs.toStdString(); if (! text.empty()) { double sz = string_to_int(text); if (usr().fingerprint_size(sz)) { modify_usr(); } else { char tmp[32]; snprintf(tmp, sizeof tmp, "%i", usr().fingerprint_size()); ui->lineEditFingerprintSize->setText(tmp); } } } /** * Given a file-name, possibly with a path, get the base name and load it * into the QLineEdit, and set the file-tooltip. * * Currently useful for all files specified in the 'rc' file, but not * for the style-sheet, which can have a full path and is also specified * in the 'usr' file. * * \param lineedit * The QLineEdit that must be updated with the new base-name and the * full filespec tooltip. * * \param fileextension * The desired file-extension for filtering in the file dialog. * If empty, all files will be shown in the dialog. * * \return * Returns true if the path/file selection succeeded. */ bool qseditoptions::load_file_name ( QLineEdit * lineedit, const std::string & fileextension ) { std::string selecteddir; std::string selectedfile; bool result = show_file_select_dialog /* a simplified lookup dialog */ ( this, fileextension, selecteddir, selectedfile ); if (result) { tooltip_for_filename(lineedit, selecteddir); lineedit->setText(qt(selectedfile)); } return result; } /** * Opens a dialog to look for executables, starting with the filename * provided. */ bool qseditoptions::load_executable_name ( QLineEdit * lineedit, const std::string & fname ) { std::string ncfname = fname; bool result = show_exe_file_dialog(lineedit, ncfname); if (result) { tooltip_for_filename(lineedit, ncfname); lineedit->setText(qt(ncfname)); } return result; } void qseditoptions::slot_patches_active_click () { bool on = ui->checkBoxActivePatches->isChecked(); if (rc().patches_filename().empty()) { on = false; ui->checkBoxActivePatches->setChecked(false); } rc().patches_active(on); if (! on) exit_required(); modify_rc(); } void qseditoptions::slot_patches_save_now_click () { QString qs = ui->lineEditPatches->text(); std::string patfile = qs.toStdString(); if (! patfile.empty()) { patfile = filename_base(patfile); rc().patches_filename(patfile); } patfile = rc().patches_filespec(); if (! patfile.empty()) { if (save_patches(patfile)) { /* * TMI: file_message("Saved", patfile); * exit_required(); */ } else file_error("Save failed", patfile); } } void qseditoptions::slot_patches_filename () { const QString qs = ui->lineEditPatches->text(); std::string text = qs.toStdString(); if (text != rc().patches_filename()) { if (text.empty()) { rc().patches_filename(text); ui->checkBoxActivePatches->setChecked(false); ui->lineEditPatches->setToolTip("No file"); } else { std::string pathname = text; if (name_has_path(text)) text = filename_base(text); rc().patches_filename(text); tooltip_for_filename(ui->lineEditPatches, rc().patches_filespec()); } exit_required(); modify_rc(); /* * No saving the patches except via the Store Patches button. * * rc().auto_patches_save(true); * ui->checkBoxSavePatches->setChecked(true); */ } } void qseditoptions::slot_load_patches_filename () { if (load_file_name(ui->lineEditPatches, "patches")) { const QString qs = ui->lineEditPatches->text(); std::string text = qs.toStdString(); rc().patches_filename(text); if (! text.empty()) { ui->checkBoxActivePatches->setChecked(true); rc().patches_active(true); } modify_rc(); } } /** * The user can edit the filename instead of pressing the "..." button * to get the name from the dialog. If a path is provided, it is stripped * and the "home" configuration directory is used. */ void qseditoptions::slot_palette_filename () { const QString qs = ui->lineEditPalette->text(); std::string text = qs.toStdString(); if (text != rc().palette_filename()) { if (text.empty()) { rc().palette_filename(text); ui->checkBoxActivePalette->setChecked(false); ui->lineEditPalette->setToolTip("No file"); } else { std::string pathname = text; if (name_has_path(text)) text = filename_base(text); rc().palette_filename(text); tooltip_for_filename(ui->lineEditPalette, rc().palette_filespec()); } exit_required(); modify_rc(); /* * No saving the palette except via the Store Palette button. * * rc().auto_palette_save(true); * ui->checkBoxSavePalette->setChecked(true); */ } } /** * Note that load_file_name() sets the tooltip to the full path and * the text to the base-name of the file selected. */ void qseditoptions::slot_load_palette_filename () { if (load_file_name(ui->lineEditPalette, "palette")) { const QString qs = ui->lineEditPalette->text(); std::string text = qs.toStdString(); rc().palette_filename(text); if (! text.empty()) { ui->checkBoxActivePalette->setChecked(true); rc().palette_active(true); } modify_rc(); } } /** * Shows an alternative way to handle an empty file-name. */ void qseditoptions::slot_palette_active_click () { bool on = ui->checkBoxActivePalette->isChecked(); if (rc().palette_filename().empty()) { on = false; ui->checkBoxActivePalette->setChecked(false); } rc().palette_active(on); if (! on) exit_required(); modify_rc(); } void qseditoptions::slot_palette_save_now_click () { QString qs = ui->lineEditPalette->text(); std::string palfile = qs.toStdString(); if (! palfile.empty()) { palfile = filename_base(palfile); rc().palette_filename(palfile); } palfile = rc().palette_filespec(); if (! palfile.empty()) { if (save_palette(global_palette(), palfile)) { /* * TMI: file_message("Saved", palfile); * exit_required(); */ } else file_error("Save failed", palfile); } } void qseditoptions::slot_palette_save_inverse () { #if defined SEQ66_PROVIDE_AUTO_COLOR_INVERSION global_palette().fill_inverse_colors(); #endif /* * slot_palette_save_now_click(); */ } #if defined USE_VERBOSE_CHECKBOX void qseditoptions::slot_verbose_active_click () { bool on = ui->checkBoxVerbose->isChecked(); rc().verbose(on); modify_rc(); } #endif void qseditoptions::slot_quiet_active_click () { bool on = ui->checkBoxQuiet->isChecked(); rc().quiet(on); modify_rc(); } void qseditoptions::slot_load_most_recent_click () { bool on = ui->checkBoxLoadMostRecent->isChecked(); rc().load_most_recent(on); modify_rc(); } void qseditoptions::slot_show_full_paths_click () { bool on = ui->checkBoxShowFullRecentPaths->isChecked(); rc().full_recent_paths(on); modify_rc(); } void qseditoptions::slot_long_buss_names_click () { bool on = ui->checkBoxLongBussNames->isChecked(); rc().port_naming(on ? "long" : "short"); modify_rc(); } void qseditoptions::slot_pair_buss_names_click () { bool on = ui->checkBoxPairBussNames->isChecked(); rc().port_naming(on ? "pair" : "short"); modify_rc(); } void qseditoptions::slot_lock_main_window_click () { bool on = ui->checkBoxLockMainWindow->isChecked(); usr().lock_main_window(on); modify_usr(); m_parent_widget->lock_main_window(on); } void qseditoptions::slot_dark_theme_click () { bool on = ui->checkBoxDarkTheme->isChecked(); usr().dark_theme(on); modify_usr(); } void qseditoptions::slot_swap_coordinates_click () { bool on = ui->checkBoxSwapCoordinates->isChecked(); usr().swap_coordinates(on); modify_usr(); } void qseditoptions::slot_bold_grid_slots_click () { bool on = ui->checkBoxBoldGridSlots->isChecked(); usr().progress_bar_thick(on); modify_usr(); } void qseditoptions::slot_gridlines_thick_click () { bool on = ui->checkBoxGridlinesThick->isChecked(); usr().gridlines_thick(on); modify_usr(); } void qseditoptions::slot_elliptical_click () { bool on = ui->checkBoxElliptical->isChecked(); usr().progress_box_elliptical(on); modify_usr(); } void qseditoptions::slot_follow_progress_click () { bool on = ui->checkBoxFollowProgress->isChecked(); usr().follow_progress(on); modify_usr(); } void qseditoptions::slot_double_click_edit () { bool on = ui->checkBoxDoubleClickEdit->isChecked(); rc().allow_click_edit(on); modify_rc(); } void qseditoptions::slot_global_seq_feature () { bool on = ui->checkBoxGlobalSeqFeature->isChecked(); usr().global_seq_feature(on); modify_usr(); } /** * In the following functions, turning of the "auto" flag and the "modify" * flag is somewhat redundant. * * Also, we don't need to process the "active" check-box. It is always * checked and is read-only. The name of the file can be changed, of course. */ void qseditoptions::slot_rc_save_click () { bool on = ui->checkBoxSaveRc->isChecked(); rc().auto_rc_save(on); reload_needed(true); /* * ca 2025-05-25. Save the 'rc' file immediately. */ const QString qs = ui->lineEditRc->text(); std::string text = qs.toStdString(); if (write_rc_file(text)) rc().auto_rc_save(false); } #if defined USE_RC_NAME_CHANGE /** * The previous file-name has either the "HOME" path, no path, or a different * path specified the the 'rc' file, In the first two cases, the plain file * base-name appears in the control. In the last one, the specified path * appears. If there is no path, it can be obtained from the tooltip. * If there is a path, it is stripped. Preserve the original path. */ void qseditoptions::slot_rc_filename () { const QString qs = ui->lineEditRc->text(); std::string text = qs.toStdString(); if (text != rc().config_filename() && ! text.empty()) { text = filename_base(text); rc().config_filename(text); modify_rc(); } } void qseditoptions::slot_load_rc_filename () { if (load_file_name(ui->lineEditRc, "rc")) modify_rc(); } #endif void qseditoptions::modify_rc () { if (m_is_initialized) { ui->checkBoxSaveRc->setChecked(true); rc().auto_rc_save(true); rc().modify(); reload_needed(true); } } void qseditoptions::modify_ctrl () { if (m_is_initialized) { ui->checkBoxSaveCtrl->setChecked(true); rc().auto_ctrl_save(true); reload_needed(true); } } void qseditoptions::modify_usr () { if (m_is_initialized) { ui->checkBoxSaveUsr->setChecked(true); rc().auto_usr_save(true); usr().modify(); reload_needed(true); } } void qseditoptions::slot_usr_save_click () { bool on = ui->checkBoxSaveUsr->isChecked(); if (on) modify_usr(); else rc().auto_usr_save(on); } #if defined SEQ66_CAN_DEACTIVATE_USR void qseditoptions::slot_usr_active_click () { bool on = ui->checkBoxActiveUsr->isChecked(); /* * usr().modify(); */ rc().user_file_active(on); rc().auto_rc_save(true); } #endif /** * There must always be a 'usr' file-name specified. If empty, that is * ignored. */ void qseditoptions::slot_usr_filename () { const QString qs = ui->lineEditUsr->text(); std::string text = qs.toStdString(); if (text != rc().user_filename()) { if (text.empty()) { text = rc().user_filename(); ui->lineEditUsr->setText(qt(text)); /* restore previous value */ } else { if (name_has_path(text)) text = filename_base(text); rc().user_filename(text); std::string pathname = rc().user_filespec(); ui->checkBoxSaveUsr->setChecked(true); tooltip_for_filename(ui->lineEditUsr, pathname); modify_rc(); modify_usr(); /* guarantee saving this file */ } } } void qseditoptions::slot_load_usr_filename () { if (load_file_name(ui->lineEditUsr, "usr")) { const QString qs = ui->lineEditPalette->text(); std::string text = qs.toStdString(); rc().user_filename(text); modify_rc(); } } /** * We could replace these is_empty(QLineEdit) checks with * rc().xxxx_filename().empty() checks. Hmmmmmm. */ void qseditoptions::slot_mutes_save_click () { if (is_empty(ui->lineEditMutes)) { ui->checkBoxSaveMutes->setChecked(false); rc().auto_mutes_save(false); } else { bool on = ui->checkBoxSaveMutes->isChecked(); rc().auto_mutes_save(on); } modify_rc(); } void qseditoptions::slot_mutes_active_click () { if (is_empty(ui->lineEditMutes)) { ui->checkBoxActiveMutes->setChecked(false); rc().mute_group_file_active(false); } else { bool on = ui->checkBoxActiveMutes->isChecked(); rc().mute_group_file_active(on); } modify_rc(); } void qseditoptions::slot_mutes_filename () { const QString qs = ui->lineEditMutes->text(); std::string text = qs.toStdString(); if (text != rc().mute_group_filename()) { if (text.empty()) { ui->checkBoxSaveMutes->setChecked(false); ui->checkBoxActiveMutes->setChecked(false); ui->lineEditMutes->setToolTip("No file"); rc().mute_group_filename(text); } else { if (name_has_path(text)) text = filename_base(text); rc().mute_group_filename(text); std::string pathname = rc().mute_group_filespec(); ui->checkBoxSaveMutes->setChecked(true); tooltip_for_filename(ui->lineEditMutes, pathname); rc().auto_mutes_save(true); } modify_rc(); } } void qseditoptions::slot_load_mutes_filename () { if (load_file_name(ui->lineEditMutes, "mutes")) { const QString qs = ui->lineEditMutes->text(); /* ca 2024-10-14 */ std::string text = qs.toStdString(); rc().mute_group_filename(text); modify_rc(); } } void qseditoptions::slot_playlist_save_click () { if (is_empty(ui->lineEditPlaylist)) { ui->checkBoxSavePlaylist->setChecked(false); rc().auto_playlist_save(false); } else { bool on = ui->checkBoxSavePlaylist->isChecked(); rc().auto_playlist_save(on); } modify_rc(); } void qseditoptions::slot_playlist_active_click () { if (is_empty(ui->lineEditPlaylist)) { ui->checkBoxActivePlaylist->setChecked(false); (void) perf().playlist_activate(false); rc().playlist_active(false); } else { bool on = ui->checkBoxActivePlaylist->isChecked(); if (perf().playlist_activate(on)) rc().playlist_active(on); } modify_rc(); } void qseditoptions::slot_playlist_filename () { const QString qs = ui->lineEditPlaylist->text(); std::string text = qs.toStdString(); if (text != rc().playlist_filename()) { if (text.empty()) { ui->checkBoxActivePlaylist->setChecked(false); ui->lineEditPlaylist->setToolTip("No file"); perf().playlist_filename(text); } else { if (name_has_path(text)) text = filename_base(text); perf().playlist_filename(text); /* rc().playlist_filename(text) */ std::string pathname = rc().playlist_filespec(); ui->checkBoxSavePlaylist->setChecked(true); tooltip_for_filename(ui->lineEditPlaylist, pathname); rc().auto_playlist_save(true); } modify_rc(); } } void qseditoptions::slot_load_playlist_filename () { if (load_file_name(ui->lineEditPlaylist, "playlist")) modify_rc(); } #if defined SEQ66_CAN_SAVE_CTRL void qseditoptions::slot_ctrl_save_click () { if (is_empty(ui->lineEditCtrl)) { ui->checkBoxSaveCtrl->setChecked(false); rc().auto_ctrl_save(false); } else { bool on = ui->checkBoxSaveCtrl->isChecked(); rc().auto_ctrl_save(on); } modify_rc(); } #endif void qseditoptions::slot_ctrl_active_click () { if (is_empty(ui->lineEditCtrl)) { ui->checkBoxActiveCtrl->setChecked(false); rc().midi_control_active(false); } else { bool on = ui->checkBoxActiveCtrl->isChecked(); rc().midi_control_active(on); } modify_rc(); } void qseditoptions::slot_ctrl_filename () { const QString qs = ui->lineEditCtrl->text(); std::string text = qs.toStdString(); if (text != rc().midi_control_filename()) { if (text.empty()) { ui->checkBoxActiveCtrl->setChecked(false); ui->lineEditCtrl->setToolTip("No file"); rc().midi_control_filename(text); } else { if (name_has_path(text)) text = filename_base(text); rc().midi_control_filename(text); std::string pathname = rc().midi_control_filespec(); ui->checkBoxSaveCtrl->setChecked(true); tooltip_for_filename(ui->lineEditCtrl, pathname); rc().auto_ctrl_save(true); modify_ctrl(); } modify_rc(); } } void qseditoptions::slot_load_ctrl_filename () { if (load_file_name(ui->lineEditCtrl, "ctrl")) { const QString qs = ui->lineEditCtrl->text(); std::string text = qs.toStdString(); rc().midi_control_filename(text); modify_rc(); } } #if defined SEQ66_CAN_SAVE_DRUMS void qseditoptions::slot_drums_save_click () { bool on = ui->checkBoxSaveDrums->isChecked(); rc().auto_drums_save(on); modify_rc(); } #endif void qseditoptions::slot_drums_active_click () { if (is_empty(ui->lineEditDrums)) { ui->checkBoxActiveDrums->setChecked(false); rc().notemap_active(false); } else { bool on = ui->checkBoxActiveDrums->isChecked(); rc().notemap_active(on); } modify_rc(); } void qseditoptions::slot_drums_filename () { const QString qs = ui->lineEditDrums->text(); std::string text = qs.toStdString(); if (text != rc().notemap_filename()) { if (text.empty()) { ui->checkBoxActiveDrums->setChecked(false); ui->lineEditDrums->setToolTip("No file"); rc().notemap_filename(text); } else { if (name_has_path(text)) text = filename_base(text); rc().notemap_filename(text); std::string pathname = rc().notemap_filespec(); tooltip_for_filename(ui->lineEditDrums, pathname); ui->checkBoxSaveDrums->setChecked(true); } modify_rc(); } } void qseditoptions::slot_load_drums_filename () { if (load_file_name(ui->lineEditDrums, "drums")) { QString qtdrum = ui->lineEditDrums->text(); std::string drum = qtdrum.toStdString(); rc().notemap_filename(drum); if (! drum.empty()) { ui->checkBoxActiveDrums->setChecked(true); rc().notemap_active(true); } modify_rc(); } } void qseditoptions::slot_stylesheet_active_click () { if (is_empty(ui->lineEditStyleSheet)) { ui->checkBoxActiveStyleSheet->setChecked(false); rc().style_sheet_active(false); exit_required(); } else { bool on = ui->checkBoxActiveStyleSheet->isChecked(); rc().style_sheet_active(on); if (! on) exit_required(); } modify_rc(); } /** * Here, we allow style-sheet files from anywhere in the directory tree. * We should check for the existence of the file. */ void qseditoptions::slot_stylesheet_filename () { const QString qs = ui->lineEditStyleSheet->text(); std::string text = qs.toStdString(); if (text != rc().style_sheet_filename()) { rc().style_sheet_filename(text); if (text.empty()) { ui->checkBoxActiveStyleSheet->setChecked(false); ui->lineEditStyleSheet->setToolTip("No file"); rc().style_sheet_active(false); } else { ui->checkBoxActiveStyleSheet->setChecked(true); tooltip_for_filename(ui->lineEditStyleSheet, text); } exit_required(); modify_rc(); /* 'rc' now sets style-sheet */ } } void qseditoptions::slot_load_stylesheet_filename () { if (load_file_name(ui->lineEditStyleSheet, "qss")) { QString qtqss = ui->lineEditStyleSheet->text(); std::string qss = qtqss.toStdString(); rc().style_sheet_filename(qss); if (! qss.empty()) { ui->checkBoxActiveStyleSheet->setChecked(true); rc().style_sheet_active(true); } /* * exit_required(); // why not required? */ modify_rc(); } } void qseditoptions::slot_browser_executable () { QString qviewer = ui->lineEditBrowser->text(); std::string viewer = qviewer.toStdString(); if (viewer != usr().user_browser()) { /* * viewer = strip_quotes(viewer); */ usr().user_browser(viewer); /* we could check existence */ modify_usr(); } } void qseditoptions::slot_load_browser_executable () { std::string current = usr().user_browser(); if (load_executable_name(ui->lineEditBrowser, current)) { const QString qs = ui->lineEditBrowser->text(); std::string viewer = qs.toStdString(); if (viewer != usr().user_browser()) { usr().user_browser(viewer); modify_usr(); } } } void qseditoptions::slot_pdf_executable () { QString qviewer = ui->lineEditPdfViewer->text(); std::string viewer = qviewer.toStdString(); if (viewer != usr().user_pdf_viewer()) { /* * viewer = strip_quotes(viewer); */ usr().user_pdf_viewer(viewer); /* we could check existence */ modify_usr(); } } void qseditoptions::slot_load_pdf_viewer_executable () { if (load_executable_name(ui->lineEditPdfViewer, "")) { const QString qs = ui->lineEditPdfViewer->text(); std::string viewer = qs.toStdString(); if (viewer != usr().user_pdf_viewer()) { usr().user_pdf_viewer(viewer); modify_usr(); } } } void qseditoptions::slot_clock_start_modulo (int ticks) { rc().set_clock_mod(ticks); modify_rc(); } void qseditoptions::midi_through_check () { if (! perf().alsa_midi_through_check()) { qt_info_box ( this, "Warning: Using MIDI Through ports for both\n" "control & display/macros can cause serious problems,\n" "including crashing via input overrun. Change\n" "to another port." ); } } void qseditoptions::slot_output_bus (int index) { int oldindex = perf().midi_control_out().configured_buss(); if (index != oldindex) { bool enable = index >= 0; if (enable) { /* * Don't call is_enabled(), as this will disable the control * display at exit. * * perf().midi_control_out().is_enabled(enable); * perf().midi_control_out().nominal_buss(index); * perf().midi_control_out().configure_enabled(true); */ ui->checkBoxSaveCtrl->setChecked(true); perf().midi_control_out().configured_buss(index); perf().ui_set_clock(index, e_clock::none); /* enabled! */ rc().midi_control_active(true); reload_needed(true); midi_through_check(); } modify_ctrl(); } } void qseditoptions::activate_ctrl_file () { bool inenable = ui->checkBoxMidiInBuss->isChecked(); bool outenable = ui->checkBoxMidiOutBuss->isChecked(); bool active = inenable || outenable; rc().midi_control_active(active); ui->checkBoxActiveCtrl->setChecked(rc().midi_control_active()); } /** * The issue here is that disabling the display MIDI means it won't * be acted on at exit/restart. We have rc().midi_control_active(), but * may need settings outside the midi_control_in/out classes for in and out * activity in the 'ctrl' file. */ void qseditoptions::slot_output_bus_enable () { bool enable = ui->checkBoxMidiOutBuss->isChecked(); modify_ctrl(); perf().midi_control_out().configure_enabled(enable); activate_ctrl_file(); refresh_clock_combo_box(); if (enable) midi_through_check(); /* * Does not work, or gets undone somehow. * * ui->comboBoxMidiOutBuss->setEnabled(enable); */ } void qseditoptions::slot_input_bus (int index) { int oldindex = perf().midi_control_in().configured_buss(); if (index != oldindex) { bool enable = index >= 0; if (enable) { ui->checkBoxSaveCtrl->setChecked(true); perf().midi_control_in().configured_buss(index); perf().ui_set_input(index, true); /* auto-enable the input */ rc().midi_control_active(true); reload_needed(true); midi_through_check(); } } modify_ctrl(); } void qseditoptions::slot_input_bus_enable () { bool enable = ui->checkBoxMidiInBuss->isChecked(); modify_ctrl(); perf().midi_control_in().configure_enabled(enable); activate_ctrl_file(); refresh_input_combo_box(); if (enable) midi_through_check(); /* * Does not work, or gets undone somehow. * * ui->comboBoxMidiInBuss->setEnabled(enable); */ } void qseditoptions::slot_tempo_track () { QString text = ui->lineEditTempoTrack->text(); std::string t = text.toStdString(); if (t.empty()) ui->pushButtonTempoTrack->setEnabled(false); else ui->pushButtonTempoTrack->setEnabled(true); } void qseditoptions::slot_buss_override () { QString text = ui->lineEditBussOverride->text(); std::string t = text.toStdString(); if (! t.empty()) { bussbyte buss_override = bussbyte(string_to_int(t)); if (is_valid_buss(buss_override)) { usr().midi_buss_override(buss_override); modify_usr(); } } } void qseditoptions::slot_bpm_precision (int index) { usr().bpm_precision(index); modify_usr(); } void qseditoptions::slot_tempo_track_set () { QString text = ui->lineEditTempoTrack->text(); std::string t = text.toStdString(); if (! t.empty()) { int ttrack = string_to_int(t); bool ok = ttrack >= 0 && ttrack < seq::maximum(); if (ok) { rc().tempo_track_number(ttrack); ui->pushButtonTempoTrack->setEnabled(false); modify_rc(); /* * new ca 2024-05-20 * Whether the new tempo track exists, it will be logged * in the last (Seqspec) track of the song. */ m_perf.modify(); m_perf.notify_sequence_change(ttrack); } } } void qseditoptions::slot_record_by_buss () { bool on = ui->checkBoxRecordByBuss->isChecked(); rc().record_by_buss(on); perf().record_by_buss(on); if (on) ui->checkBoxRecordByChannel->setChecked(false); modify_rc(); } void qseditoptions::slot_record_by_channel () { bool on = ui->checkBoxRecordByChannel->isChecked(); rc().record_by_channel(on); perf().record_by_channel(on); if (on) ui->checkBoxRecordByBuss->setChecked(false); modify_rc(); } void qseditoptions::slot_virtual_ports () { bool on = ui->checkBoxVirtualPorts->isChecked(); ui->lineEditOutputCount->setEnabled(on); ui->lineEditInputCount->setEnabled(on); rc().manual_ports(on); if (! on) { rc().default_manual_port_counts(); sync_rc(); /* too much, but awright */ } modify_rc(); } void qseditoptions::slot_enable_virtual_ports () { bool on = ui->checkBoxAutoEnableVirtual->isChecked(); if (on) { ui->checkBoxVirtualPorts->setChecked(true); ui->lineEditOutputCount->setEnabled(true); ui->lineEditInputCount->setEnabled(true); rc().manual_ports(true); } rc().manual_auto_enable(on); modify_rc(); } void qseditoptions::slot_virtual_out_count () { QString text = ui->lineEditOutputCount->text(); int count = string_to_int(text.toStdString()); rc().manual_port_count(count); modify_rc(); std::string value = std::to_string(rc().manual_port_count()); ui->lineEditOutputCount->setText(qt(value)); } void qseditoptions::slot_virtual_in_count () { QString text = ui->lineEditInputCount->text(); int count = string_to_int(text.toStdString()); rc().manual_in_port_count(count); modify_rc(); std::string value = std::to_string(rc().manual_in_port_count()); ui->lineEditInputCount->setText(qt(value)); } } // namespace seq66 /* * qseditoptions.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qseqbase.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqbase.cpp * * This module declares/defines the base class for drawing on the piano * roll of the patterns editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2025-07-07 * \license GNU GPLv2 or above * * We are currently moving toward making this class a base class. * * User jean-emmanual added support for disabling the following of the * progress bar during playback. See the seqroll::m_progress_follow member. */ #include "play/performer.hpp" /* seq66::performer class */ #include "qseqbase.hpp" /* seq66::qseqbase class */ #include "qseqeditframe64.hpp" /* seq66::qseqeditframe64 class */ namespace seq66 { /** * We need to square this away. This should be the configurable usr() * key-height. But it is not used, and we already have made it configurable. * * static const int c_key_y = 8; */ /** * Primary constructor */ qseqbase::qseqbase ( performer & p, sequence & s, qseqeditframe64 * frame, int zoom, int snap, int unitheight, int totalheight ) : qeditbase ( p, zoom, snap, 1, c_keyboard_padding_x, unitheight, totalheight ), m_parent_frame (frame), m_seq (s), m_move_delta_x (0), m_move_delta_y (0), m_move_snap_offset_x (0) { set_snap(track().snap()); /* * four_pen_style(Qt::SolidLine); // Qt::DotLine */ if (usr().gridlines_thick()) /* otherise use defaults */ measure_pen_width(3); } /** * Checks for the dirtiness of the user-interface or the current sequence. * This function is an override of the qbase version. * * \return * Returns true if an update is needed. */ bool qseqbase::check_dirty () const { bool dirty = qeditbase::check_dirty(); if (! dirty) { performer & ncp = const_cast(perf()); dirty = ncp.needs_update(track().seq_number()); } return dirty; } bool qseqbase::mark_unmodified () { if (not_nullptr(frame64())) frame64()->set_external_frame_title(false); return true; } bool qseqbase::mark_modified () { if (not_nullptr(frame64())) frame64()->set_external_frame_title(true); return true; } /** * Set the measures value, using the given parameter, and some internal * values passed to apply_length(). * * \param len * Provides the sequence length, in measures. */ void qseqbase::set_measures (int len) { track().apply_length(len); set_dirty(); } int qseqbase::get_measures () { return track().get_measures(); } void qseqbase::convert_xy (int x, int y, midipulse & tick, int & note) { tick = z().pix_to_tix(x); note = (m_total_height - y - 2) / m_unit_height; if (note >= c_note_max) note = c_note_max; else if (note < 0) note = 0; } /** * Also see pulses_per_pixel() in the zoomer module. Here we are * doing the inverse calculation, sort of. Note the zoom is * ticks per pixel. * * ca 2025-07-07. * Removed the PPQN elements. * * x = ticks * usr().base_ppqn() / ppqn() / zoom(); */ void qseqbase::convert_tn (midipulse ticks, int note, int & x, int & y) { if (note >= 0 && note <= c_note_max) { x = ticks / zoom(); y = m_total_height - ((note + 1) * m_unit_height) - 1; } else x = y = 0; } /** * See seqroll::convert_sel_box_to_rect() for a potential upgrade. * * \param tick_s * The starting tick of the rectangle. * * \param tick_f * The finishing tick of the rectangle. * * \param note_h * The high note of the rectangle. * * \param note_l * The low note of the rectangle. * * \param [out] r * The destination rectangle for the calculations. */ void qseqbase::convert_tn_box_to_rect ( midipulse tick_s, midipulse tick_f, int note_h, int note_l, seq66::rect & r ) { int x1, y1, x2, y2; convert_tn(tick_s, note_h, x1, y1); /* convert box to X,Y values */ convert_tn(tick_f, note_l, x2, y2); rect::xy_to_rect(x1, y1, x2, y2, r); r.height_incr(m_unit_height); } } // namespace seq66 /* * qseqbase.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qseqdata.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqdata.cpp * * This module declares/defines the base class for plastering * pattern/sequence data information in the data area of the pattern * editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2026-04-30 * \license GNU GPLv2 or above * * The data pane is the drawing-area below the seqedit's event area, and * contains vertical lines whose height matches the value of each data event. * The height of the vertical lines is editable via the mouse. */ #include "cfg/settings.hpp" /* seq66::usr() config functions */ #include "midi/drums.hpp" /* seq66::drums class and functions */ #include "play/performer.hpp" /* seq66::performer class */ #include "qseqdata.hpp" /* seq66::qseqdata class */ #include "qseqeditframe64.hpp" /* seq66::qseqeditframe64 class */ #include "qt5_helpers.hpp" /* seq66::qt_timer() */ #if defined SEQ66_SHOW_GM_PROGRAM_NAME #include "midi/patches.hpp" /* seq66::program_name() */ #endif /* * The fixes for issue #90 cause a lot of redrawing during mouse movement while * using a line to edit (for example) velocity. But we still call * change_event_data_range() or change_event_data_relative() during mouse * movement, if SEQ66_TRACK_DATA_EDITING_MOVEMENTS is defined. * * We also disabled the "relative adjust" feature we copped from Kepler34. * However, we have never been able to get that feature to turn on. In Kepler34, * all it seems to do is allow modifying a single event by moving the mouse up * and down. Obviously, our implementation is buggy, but does it matter? We will * still see if we can get it to work at some point. * * To revert to the old behavior, define this macro. We leave the old behavior in * since we figured out that constant title-dirtying in qsmainwnd :: * enable_save() was causing continuous flickering during editing mouse * movements. */ #define SEQ66_TRACK_DATA_EDITING_MOVEMENTS #undef SEQ66_ALLOW_RELATIVE_VELOCITY_CHANGE namespace seq66 { /** * The height of the data-entry area for velocity, aftertouch, and other * controllers, as well as note on and off velocity. This value is pixels; * one pixel per MIDI value, which ranges from 0 to 127. We start with a * hardwired constant for this variable, but it can be halved to help fit the * pattern editor into a tab. */ static const int sc_dataarea_y = 156; static const int sc_dataarea_y_effective = 128; static const int sc_dataarea_y_offset = 12; static const int sc_dataarea_y_sub = 48; /* * Tweaks. */ static const int sc_x_data_fix = -6; /* adjusts x-value for the events */ static const int sc_key_padding = 8; /* adjusts x for keyboard padding */ static const int sc_circle_d = 6; /* diameter of tempo/prog. dots */ static const int sc_handle_d = 10; /* diameter of grab handle */ static const int sc_handle_r = sc_handle_d / 2; /* grab handle radius */ static const int sc_handle_delta = 2; /* delta of mouse-pixels */ /** * Base font size in points, and the y increment to use to avoid text * overwrite. */ static const int sc_font_size = 10; static const int sc_text_spacing = sc_font_size + 4; static const int sc_1 = sc_font_size + 1; static const int sc_2 = sc_1 * 2; static const int sc_name = sc_circle_d + 12; #if defined SEQ55_SHOW_REDUNDANT_TIME_SIG static const int sc_time_spacing = sc_font_size + 18; #endif /** * Principal constructor. */ qseqdata::qseqdata ( performer & p, sequence & s, qseqeditframe64 * frame, /* frame64() accessor */ int zoom, int snap, QWidget * parent, /* ui->dataScrollArea */ int height ) : QWidget (parent), qseqbase (p, s, frame, zoom, snap), performer::callbacks (p), m_timer (nullptr), m_font ("Monospace"), m_keyboard_padding_x (sc_key_padding), m_short_dataarea (height == 64), m_dataarea_y ( m_short_dataarea ? height : sc_dataarea_y_effective /* 64 or 128 */ ), m_data_type (type::note), /* replaces booleans */ m_edit_mode (sequence::editmode::note), /* mode parameter? */ m_status (EVENT_NOTE_ON), m_cc (1), /* modulation */ m_show_hex_values (false), m_show_level_numbers (true), m_line_adjust (false), m_relative_adjust (false), m_drag_handle (false), m_mouse_tick (-1), m_handle_delta (z().pix_to_tix(sc_handle_delta)), m_dragging (false) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); setMouseTracking(true); /* no click needed */ m_font.setPointSize(sc_font_size); m_font.setBold(true); #if defined SEQ66_PLATFORM_DEBUG_TMI set_initialized(); #endif cb_perf().enregister(this); /* set for notification */ m_timer = qt_timer(this, "qseqdata", 2, SLOT(conditional_update())); } /** * This virtual destructor stops the timer. */ qseqdata::~qseqdata () { if (not_nullptr(m_timer)) m_timer->stop(); cb_perf().unregister(this); } /** * In an effort to reduce CPU usage when simply idling, this function calls * update() only if necessary. See qseqbase::dirty(). */ void qseqdata::conditional_update () { if (check_dirty()) update(); } bool qseqdata::on_ui_change (seq::number seqno) { if (seqno == track().seq_number()) update(); return true; } QSize qseqdata::sizeHint () const { int w = frame64()->width(); int h = sc_dataarea_y; int len = z().tix_to_pix(track().get_length()); if (len < w) len = w; len += c_keyboard_padding_x; if (m_short_dataarea) h /= 2; return QSize(len, h); } /** * We don't want the scroll wheel to accidentally scroll this pane * horizontally, so this override does nothing but accept() the event. * * ignore() just let's the parent handle the event, which allows scrolling to * occur. For issue #3, we have enabled the scroll wheel in the piano roll * [see qscrollmaster::wheelEvent()], but we disable it here. So this is a * partial solution to the issue. */ void qseqdata::wheelEvent (QWheelEvent * qwep) { qwep->accept(); } /** * First, we get the data height, which ranges from 0 to 128, where * 128 is highest in the data pane. This is a y of 0 plus the offset * we use to pad the top and bottom of the pane, so that grab handles * are always visible. * * We should be able to get rid of the static function data_height(). * * \param value * Provides a data value, which must range from 0 to 127. * * \return * Returns the y coordinate that represents the data value. */ int qseqdata::data_y (midibyte value) const { int result = m_dataarea_y - byte_height(m_dataarea_y, value); result += sc_dataarea_y_offset; return result; } /** * We create an iterator and use sequence::get_next_event_match(). */ void qseqdata::paintEvent (QPaintEvent * qpep) { QRect r = qpep->rect(); QPainter painter(this); QBrush brush(backdata_paint(), Qt::SolidPattern); QPen pen(Qt::black); pen.setWidth(1); painter.setPen(pen); painter.setBrush(brush); painter.setFont(m_font); m_edit_mode = perf().edit_mode(track().seq_number()); char digits[8]; midipulse start_tick = z().pix_to_tix(r.x()); midipulse end_tick = start_tick + z().pix_to_tix(r.width()); int text_y = sc_text_spacing; /* * Draw mid-lines and top and bottom lines. */ int maxline = data_y(128); int topline = data_y(96); int midline = data_y(64); int botline = data_y(32); int minline = data_y(0); /* * Show line labels. TODO: for pitchbend, show -2, -1, 0, 1, and 2. */ pen.setColor(sel_color()); painter.setPen(pen); if (! is_tempo()) { if (show_level_numbers()) { const char * maxnumber = "128"; const char * topnumber = " 96"; const char * midnumber = " 64"; const char * botnumber = " 32"; const char * minnumber = " 0"; if (is_pitchbend()) { maxnumber = " 2"; topnumber = " 1"; midnumber = " 0"; botnumber = "-1"; minnumber = "-2"; } else if (show_hex_values()) { maxnumber = " 0x80"; topnumber = " 0x60"; midnumber = " 0x40"; botnumber = " 0x20"; minnumber = " 0x00"; } painter.drawText(0, maxline - 1, maxnumber); /* tiny correction */ painter.drawText(0, topline, topnumber); painter.drawText(0, midline, midnumber); painter.drawText(0, botline, botnumber); painter.drawText(0, minline, minnumber); } } pen.setColor(fore_color()); pen.setStyle(Qt::SolidLine); painter.setPen(pen); painter.drawLine(0, maxline, width() - 1, maxline); pen.setStyle(Qt::DotLine); painter.setPen(pen); painter.drawLine(0, topline, width() - 1, topline); if (m_data_type == type::pitchbend) { pen.setWidth(2); painter.setPen(pen); } painter.drawLine(0, midline, width() - 1, midline); if (m_data_type == type::pitchbend) { pen.setWidth(1); painter.setPen(pen); } painter.drawLine(0, botline, width() - 1, botline); pen.setStyle(Qt::SolidLine); painter.setPen(pen); painter.drawLine(0, minline, width() - 1, minline); /* * Now draw the event data. */ pen.setStyle(Qt::SolidLine); pen.setWidth(2); track().draw_lock(); for (auto cev = track().cbegin(); ! track().cend(cev); ++cev) { if (! track().get_next_event_match(m_status, m_cc, cev)) break; midipulse tick = cev->timestamp(); if (tick >= start_tick && tick <= end_tick) { bool data_event = cev->is_continuous_event(); /* can draw line */ bool selected = cev->is_selected(); int event_x = z().tix_to_pix(tick) + m_keyboard_padding_x; int x_offset = event_x + sc_x_data_fix; int y_offset = m_dataarea_y - sc_dataarea_y_sub; midibyte d0, d1; cev->get_data(d0, d1); int event_value = event::is_one_byte_msg(m_status) ? d0 : d1 ; int event_height = event_value; if (cev->is_pitchbend()) event_height = pitch_value_scaled(d0, d1); event_height = data_y(event_height); bool its_close = false; if (! selected && m_mouse_tick >= 0) { midipulse delta = std::labs(tick - m_mouse_tick); if (delta <= m_handle_delta) its_close = true; } if (data_event) { pen.setColor(selected ? sel_color() : fore_color()); painter.setPen(pen); event_x -= 3; if (is_pitchbend()) { #if defined SEQ66_DRAW_PITCHBEND_AS_DOT painter.drawPoint(event_x, event_height); #else /* * Draw from 64 (the middle) to the value, rather than * from 0 (the bottom) to the value. */ int middle = data_y(64); painter.drawLine(event_x, event_height, event_x, middle); #endif } else { painter.drawLine(event_x, event_height, event_x, bottom()); if (show_hex_values()) snprintf(digits, sizeof digits, "0x%02x", d1); else snprintf(digits, sizeof digits, "%3d", d1); if (selected || its_close) { painter.drawEllipse ( event_x - sc_handle_r, event_height - sc_handle_r, sc_handle_d, sc_handle_d ); if (is_drum_mode()) { std::string drumname { drum_name(event_value) }; painter.drawText ( x_offset, y_offset - sc_1, drumname.c_str() ); } } #if defined SEQ66_SHOW_GM_DRUM_NAME /* * Enabling this code shows too many overlapping * names to be readable. */ if (is_drum_mode()) { std::string drumname { drum_name(event_value) }; painter.drawText ( x_offset, y_offset - sc_name, drumname.c_str() ); } else { #endif QString val = digits; pen.setColor(text_data_paint()); /* fore_color()) */ painter.setPen(pen); x_offset += 6; if (show_hex_values()) { painter.drawText(x_offset, y_offset + sc_2, val); } else { painter.drawText ( x_offset, y_offset, val.at(0) ); painter.drawText ( x_offset, y_offset + sc_1, val.at(1) ); painter.drawText ( x_offset, y_offset + sc_2, val.at(2) ); } } #if defined SEQ66_SHOW_GM_DRUM_NAME } #endif } else if (is_tempo() && cev->is_tempo()) { d1 = bottom() - tempo_to_note_value(cev->tempo()) - (sc_circle_d / 2); if (d1 < 4) d1 = 4; /* avoid overlap with top */ snprintf(digits, sizeof digits, "%3d", int(cev->tempo())); brush.setColor(selected ? sel_color() : tempo_color()); if (selected) pen.setColor(sel_color()); else if (its_close) pen.setColor(near_paint()); /* near_color()? */ else pen.setColor(text_data_paint()); /* fore_color()) */ painter.setBrush(brush); painter.setPen(pen); painter.drawEllipse ( event_x - sc_handle_r, d1 - sc_handle_r, sc_handle_d, sc_handle_d ); painter.drawText(x_offset + sc_text_spacing, d1 + 4, digits); brush.setColor(grey_color()); painter.setBrush(brush); } else if (is_program_change() && cev->is_program_change()) { int patch = int(cev->d0()); #if defined SEQ66_SHOW_GM_PROGRAM_NAME std::string p = program_name(patch); d1 = bottom() - midi_data_adjust(patch, sc_name); #else d1 = bottom() - patch - (sc_circle_d / 2); if (d1 < 4) d1 = 4; /* avoid overlap with top */ d1 -= sc_circle_d; snprintf(digits, sizeof digits, "%3d", patch); #endif brush.setColor(selected ? sel_color() : drum_color()); /* ! */ if (selected) /* issue #136 */ pen.setColor(sel_color()); else if (its_close) pen.setColor(near_paint()); /* near_color()? */ else pen.setColor(text_data_paint()); /* fore_color()) */ painter.setBrush(brush); painter.setPen(pen); painter.drawEllipse ( event_x - sc_handle_r, d1 - sc_handle_r, sc_handle_d, sc_handle_d ); #if defined SEQ66_SHOW_GM_PROGRAM_NAME painter.drawText(x_offset + 14, d1 + 4, p.c_str()); #else painter.drawText(x_offset + 6, d1 + 6, digits); #endif brush.setColor(grey_color()); painter.setBrush(brush); } else if (is_text() && cev->is_meta_text()) { std::string text = cev->get_text(); painter.drawText(x_offset + 6, text_y, qt(text)); if (text.length() > 16) { text_y += sc_text_spacing; if (text_y > m_dataarea_y) text_y = sc_text_spacing; } } } } #if defined SEQ55_SHOW_REDUNDANT_TIME_SIG /* * All time-sigs can be seen in the seqtime pane or in the * event editor. */ if (is_time_signature()) { const int y_offset = sc_time_spacing; int count = track().time_signature_count(); for (int tscount = 0; tscount < count; ++tscount) { const sequence::timesig & ts = track().get_time_signature(tscount); if (ts.sig_beat_width == 0) break; midipulse start = ts.sig_start_tick; int pos = xoffset(start); int n = ts.sig_beats_per_bar; int d = ts.sig_beat_width; std::string text = std::to_string(n); text += "/"; text += std::to_string(d); pen.setColor(Qt::white); painter.setPen(pen); painter.drawText(pos, y_offset, qt(text)); } } #endif track().draw_unlock(); if (m_line_adjust) /* draw edit line */ { int x, y, w, h; pen.setColor(sel_color()); pen.setStyle(Qt::DashLine); pen.setWidth(1); painter.setPen(pen); rect::xy_to_rect_get ( drop_x(), drop_y(), current_x(), current_y(), x, y, w, h ); old_rect().set(x, y, w, h); painter.drawLine ( current_x() + c_keyboard_padding_x, current_y(), drop_x() + c_keyboard_padding_x, drop_y() ); pen.setWidth(2); painter.setPen(pen); } } void qseqdata::resizeEvent (QResizeEvent * qrep) { qrep->ignore(); /* QWidget::resizeEvent(qrep) */ } void qseqdata::mousePressEvent (QMouseEvent * ev) { int mouse_x = qt_mouse_x(ev) - c_keyboard_padding_x + scroll_offset_x(); int mouse_y = qt_mouse_y(ev); midipulse tick_start = z().pix_to_tix(mouse_x - 8); midipulse tick_finish = z().pix_to_tix(mouse_x + 8); bool isctrl = bool(ev->modifiers() & Qt::ControlModifier); if (ev->button() == Qt::RightButton) { m_dragging = m_line_adjust = false; return; } drop_x(mouse_x); /* set values for line */ drop_y(mouse_y); #if defined SEQ66_ALLOW_RELATIVE_VELOCITY_CHANGE bool would_select = set_adjustment(tick_start, tick_finish); if (! would_select) { m_dragging = m_line_adjust = true; /* set ev values under line */ } #endif /* * This value is either 0 (128) or 64 as called in qseqeditframe64's * constructor. If this changes, we could change the factor of 2 to * sc_dataarea_y / height. A little be tricky. */ midibyte dataval = m_dataarea_y - drop_y() + sc_dataarea_y_offset; if (m_short_dataarea) dataval *= 2; int count = track().select_event_handle /* check for handle grab */ ( tick_start, tick_finish, m_status, m_cc, dataval ); m_drag_handle = count > 0; if (m_drag_handle) { track().push_undo(); m_dragging = false; } else { int currcount = track().get_num_selected_events(m_status, m_cc); if (currcount > 0 && ! isctrl) track().unselect(); /* unselect all of selected */ int evcount = track().select_events /* select clicked event */ ( tick_start, tick_finish, m_status, m_cc, eventlist::select::select_one /* adds to the selections */ ); bool selected = evcount > 0; if (selected) { flag_dirty(); } else { track().unselect(); /* unselect all of selected */ m_dragging = m_line_adjust = true; flag_dirty(); } } /* * Check if this tick range would select an event. */ track().push_undo(); old_rect().clear(); /* reset dirty redraw box */ } /** * Convert x,y to ticks, then set events in range */ void qseqdata::mouseReleaseEvent (QMouseEvent * ev) { bool ismodded = bool(ev->modifiers() & Qt::ControlModifier) || bool(ev->modifiers() & Qt::ShiftModifier); current_x(qt_mouse_x(ev) - c_keyboard_padding_x + scroll_offset_x()); current_y(qt_mouse_y(ev)); if (m_line_adjust) { if (current_x() < drop_x()) { swap_x(); swap_y(); } midipulse tick_s = z().pix_to_tix(drop_x()); midipulse tick_f = z().pix_to_tix(current_x()); int ds = byte_value(m_dataarea_y, m_dataarea_y - drop_y()); int df = byte_value(m_dataarea_y, m_dataarea_y - current_y() - 1); if (ismodded) { if (is_tempo()) { bool ok = track().add_tempos(tick_s, tick_f, ds, df); if (ok) flag_dirty(); } else if (is_program_change()) { /* * anything to do for issue #136? */ } /* * We could set m_line_adjust = false here, but the effect * seems useful for tempo as well as program-change. */ } else { bool ok = track().change_event_data_range ( tick_s, tick_f, m_status, m_cc, ds, df, true /* finalize */ ); m_line_adjust = false; if (ok) set_dirty(); } } else if (m_drag_handle) { track().unselect(); track().set_dirty(); } m_drag_handle = m_relative_adjust = m_dragging = false; /* * Should we call update()? * Should we call track().push_undo(true)? */ } void qseqdata::mouseMoveEvent (QMouseEvent * ev) { #if defined SEQ66_TRACK_DATA_EDITING_MOVEMENTS midipulse tick_s, tick_f; #endif bool ismodded = bool(ev->modifiers() & Qt::ControlModifier) || bool(ev->modifiers() & Qt::ShiftModifier); current_x(qt_mouse_x(ev) - c_keyboard_padding_x); current_y(qt_mouse_y(ev)); m_mouse_tick = -1; if (m_drag_handle) { track().adjust_event_handle(m_status, m_dataarea_y - current_y()); mark_modified(); flag_dirty(); } else if (m_line_adjust) { #if defined SEQ66_TRACK_DATA_EDITING_MOVEMENTS int adj_x_min, adj_x_max, adj_y_min, adj_y_max; if (current_x() < drop_x()) { adj_x_min = current_x(); adj_y_min = current_y(); adj_x_max = drop_x(); adj_y_max = drop_y(); } else { adj_x_max = current_x(); adj_y_max = current_y(); adj_x_min = drop_x(); adj_y_min = drop_y(); } tick_s = z().pix_to_tix(adj_x_min); tick_f = z().pix_to_tix(adj_x_max); if (ismodded) { /* * This shows the line, but the new events are not shown until * mouse-release. */ flag_dirty(); } else { int ds = byte_value(m_dataarea_y, m_dataarea_y - adj_y_min - 1); int df = byte_value(m_dataarea_y, m_dataarea_y - adj_y_max - 1); bool ok = track().change_event_data_range ( tick_s, tick_f, m_status, m_cc, ds, df ); if (ok) { (void) mark_modified(); flag_dirty(); set_dirty(); /* just a flag setting */ } } #else (void) mark_modified(); set_dirty(); #endif } #if defined SEQ66_ALLOW_RELATIVE_VELOCITY_CHANGE else if (m_relative_adjust) /* currently DISABLED */ { #if defined SEQ66_TRACK_DATA_EDITING_MOVEMENTS int adjy = byte_value(m_dataarea_y, drop_y() - current_y()); tick_s = z().pix_to_tix(drop_x() - 2); tick_f = z().pix_to_tix(drop_x() + 2); bool ok = track().change_event_data_relative ( tick_s, tick_f, m_status, m_cc, adjy ); if (ok) { (void) mark_modified(); set_dirty(); /* just a flag setting */ } #else (void) mark_modified(); set_dirty(); #endif /* * Move the drop location so we increment properly on next mouse move. */ drop_y(current_y()); } #endif else if (! m_dragging) { m_mouse_tick = z().pix_to_tix(current_x()); update(); /* force a paintEvent() */ } } /** * Sets the drum/note mode status. * * \param mode * The drum or note mode status. */ void qseqdata::update_edit_mode (sequence::editmode mode) { m_edit_mode = mode; } #if defined SEQ66_ALLOW_RELATIVE_VELOCITY_CHANGE /** * If near an event (8 px), do relative adjustment. Do we need to * push-undo here? Not sure, won't change for now. Disable for now * because it either doesn't work or causes velocity changes to not * occur. Also, it's an issue with tracks densely packed with note * events. * * The sequence::select_events() call checks if this tick range would * select an event. * * \return * Returns true if the mouse location would select events. */ bool qseqdata::set_adjustment (midipulse tick_start, midipulse tick_finish) { int count = track().select_events ( tick_start, tick_finish, m_status, m_cc, eventlist::select::would_select ); bool result = count > 0; if (result) { m_dragging = m_relative_adjust = true; } } #endif // defined SEQ66_ALLOW_RELATIVE_VELOCITY_CHANGE void qseqdata::set_data_type (midibyte status, midibyte control) { if (event::is_tempo_status(status)) { m_data_type = type::tempo; m_status = EVENT_MIDI_META; /* tricky */ m_cc = status; } else if (event::is_time_signature_status(status)) { m_data_type = type::time_signature; m_status = EVENT_MIDI_META; /* tricky */ m_cc = status; } else if (event::is_program_change_msg(status)) { m_data_type = type::program_change; m_status = status; m_cc = 0; } else if (event::is_pitchbend_msg(status)) { m_data_type = type::pitchbend; m_status = status; m_cc = 0; } else if (event::is_meta_text_msg(status)) { m_data_type = type::text; m_status = EVENT_MIDI_META; /* tricky */ m_cc = status; } else { m_data_type = type::note; /* "other" */ m_status = event::normalized_status(status); m_cc = control; } update(); } /** * Similar to qstriggereditor::flag_dirty(). */ void qseqdata::flag_dirty () { track().set_dirty(); frame64()->set_dirty(); } } // namespace seq66 /* * qseqdata.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qseqeditex.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqeditex.cpp * * This module declares/defines the base class for the external sequence edit * window. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-06-15 * \updates 2026-04-22 * \license GNU GPLv2 or above * */ #include #include "play/performer.hpp" /* seq66::performer class */ #include "qseqeditex.hpp" /* seq66::qseqeditex container */ #include "qseqeditframe64.hpp" /* seq66::qseqeditframe64 editor */ #include "qsmainwnd.hpp" /* seq66::qsmainwnd master class */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qseqeditex.h" #else #include "forms/qseqeditex.ui.h" #endif /* * Don't document the namespace. */ namespace seq66 { /** * Creates the window holding the external edit frame, and creates that frame * [qseqeditframe64] as well. * * \param p * Provides the performer object to use for interacting with this sequence. * * \param seqid * Provides the sequence number. The corresponding seq::pointer is * obtained from the performer and the reference is pass to the seqedit * constructor. * * \param parent * Provides the parent window/widget for this container window. Defaults * to null. Note that this parameter does not link this class to the * parent as a QWidget, because that would make this class appear inside * the qsmainwnd user-interface. */ qseqeditex::qseqeditex ( performer & p, int seqid, qsmainwnd * parent ) : QWidget (nullptr), ui (new Ui::qseqeditex), m_performer (p), m_seq_id (seqid), m_edit_parent (parent), m_edit_frame (nullptr) { ui->setupUi(this); QGridLayout * layout = new QGridLayout(this); seq::pointer s = perf().get_sequence(seqid); if (s) { m_edit_frame = new (std::nothrow) qseqeditframe64(p, *s, this); layout->addWidget(m_edit_frame); std::string title = "Pattern #"; title += std::to_string(seqid); title += ": "; title += s->name(); setWindowTitle(qt(title)); show(); m_edit_frame->show(); } } /** * Deletes the user interface, then tells the editor parent to remove * this object. */ qseqeditex::~qseqeditex() { delete ui; if (not_nullptr(m_edit_frame)) delete m_edit_frame; } /** * First tell the enclosed seqedit frame to close (which then should cause * that to tell it's LFO and Pattern-Fix windows to close. Then * tells the parent window to close (and automatically remove this container * frame when the user closes it. * * Hopefully there's no race condition here. * * ca 2026-04-22: * * Yes, there is, and it is hit if opening the next MIDI file will * cause a port remap or port unavailable prompt. Stopping the * update timer seems to fix it. */ void qseqeditex::closeEvent (QCloseEvent *) { if (not_nullptr(m_edit_parent)) { if (not_nullptr(m_edit_frame)) { m_edit_frame->stop_timer(); /* stop the update timer now */ m_edit_frame->close(); } m_edit_parent->remove_editor(m_seq_id); } } /** * Tells the contained edit frame that it needs to update itself. */ void qseqeditex::update_draw_geometry () { if (not_nullptr(m_edit_frame)) m_edit_frame->update_draw_geometry(); } void qseqeditex::set_title (bool modified) { seq::pointer s = perf().get_sequence(m_seq_id); if (s) { std::string title = "Pattern #"; title += std::to_string(m_seq_id); title += ": "; title += s->name(); if (modified) title += " *"; setWindowTitle(qt(title)); } } } // namespace seq66 /* * qseqeditex.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qseqeditframe64.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqeditframe64.cpp * * This module declares/defines the base class for plastering pattern / * sequence data information in the pattern editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-06-15 * \updates 2026-04-23 * \license GNU GPLv2 or above * * The data pane is the drawing-area below the seqedit's event area, and * contains vertical lines whose height matches the value of each data event. * The height of the vertical lines is editable via the mouse. * * https://stackoverflow.com/questions/1982986/ * scrolling-different-widgets-at-the-same-time * * ...Try to make it so that each of the items that needs to scroll in * concert is inside its own QScrollArea. I would then put all those * widgets into one widget, with a QScrollBar underneath (and/or to the * side, if needed). * * Designate one of the interior scrolled widgets as the "master", * probably the plot widget. Then do this: * * Set every QScrollArea's horizontal scroll bar policy to never show the * scroll bars. (Set) the master QScrollArea's horizontalScrollBar()'s * rangeChanged(int min, int max) signal to a slot that sets the main * widget's horizontal QScrollBar to the same range. Additionally, it * should set the same range for the other scroll area widget's * horizontal scroll bars. * * The horizontal QScrollBar's valueChanged(int value) signal should be * connected to every scroll area widget's horizontal scroll bar's * setValue(int value) slot. Repeat for vertical scroll bars, if doing * vertical scrolling. * * The layout of this frame is depicted here: * \verbatim ----------------------------------------------------------- QHBoxLayout | seqname : gridsnap : notelength : seqlength : ... | ----------------------------------------------------------- QHBoxLayout | undo : redo : tools : zoomin : zoomout : scale : ... | QVBoxLayout ----------------------------------------------------------- QScrollArea | | qseqtime (0, 1, 1, 1) Scroll horiz only | | |-- |---------------------------------------------------|---| | q | | v | | s | | e | | e | | r | QScrollArea | q | qseqroll (1, 1, 1, 1) Scroll h/v both | t | | k | | s | | e | | b | | y | | a | | s | | r | |---|---------------------------------------------------|---| QScrollArea | | qtriggeredit (2, 1, 1, 1) Scroll horiz only | | | |---------------------------------------------------| | | | | | QScrollArea | | qseqdata (3, 1, 1, 1) Scroll horiz only | | | | | | | | [Note: the height can be reduced to fit in tab.] | | ----------------------------------------------------------- | | Horizontal scroll bar for QWidget container | | ----------------------------------------------------------- QHBoxLayout | Events : ... | ----------------------------------------------------------- \endverbatim * */ #include #include #include #include "midi/controllers.hpp" /* seq66::controller_name() */ #include "play/performer.hpp" /* seq66::performer reference */ #include "util/strfunctions.hpp" /* seq66::string_to_int() */ #include "qlfoframe.hpp" /* seq66::qlfoframe dialog class */ #include "qpatternfix.hpp" /* seq66::qpatternfix dialog class */ #include "qseqdata.hpp" /* seq66::qseqdata panel */ #include "qseqeditex.hpp" /* seq66::qseqeditex class */ #include "qseqeditframe64.hpp" /* seq66::qseqeditframe64 class */ #include "qseqkeys.hpp" /* seq66::qseqkeys panel */ #include "qseqroll.hpp" /* seq66::qseqroll panel */ #include "qseqtime.hpp" /* seq66::qseqtime panel */ #include "qstriggereditor.hpp" /* seq66::qstriggereditor events */ #include "qt5_helpers.hpp" /* seq66::qt(), qt_set_icon() */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qseqeditframe64.h" #else #include "forms/qseqeditframe64.ui.h" #endif /* * We prefer to load the pixmaps on the fly, rather than deal with those * friggin' resource files. * * #include "pixmaps/down.xpm" // replaced by up.xpm and its inverse */ #include "pixmaps/bus.xpm" #include "pixmaps/drum.xpm" #include "pixmaps/exp_rec_on.xpm" #include "pixmaps/filter_off.xpm" #include "pixmaps/filter_on.xpm" #include "pixmaps/finger.xpm" #include "pixmaps/follow.xpm" #include "pixmaps/hex_off.xpm" #include "pixmaps/hex_on.xpm" #include "pixmaps/key.xpm" #include "pixmaps/loop.xpm" #include "pixmaps/length_red.xpm" #include "pixmaps/length_short.xpm" /* not length.xpm, it is too long */ #include "pixmaps/length_short_inv.xpm" #include "pixmaps/menu_empty.xpm" #include "pixmaps/menu_empty_inv.xpm" #include "pixmaps/menu_full.xpm" #include "pixmaps/menu_full_inv.xpm" #include "pixmaps/midi.xpm" #include "pixmaps/numbers_off.xpm" #include "pixmaps/numbers_on.xpm" #include "pixmaps/n_rec_on.xpm" #include "pixmaps/note_length.xpm" #include "pixmaps/note_length_inv.xpm" #include "pixmaps/play.xpm" #include "pixmaps/play_on.xpm" #include "pixmaps/q_rec.xpm" #include "pixmaps/q_rec_on.xpm" #include "pixmaps/quantize.xpm" #include "pixmaps/quantize_inv.xpm" #include "pixmaps/rec.xpm" #include "pixmaps/rec_on.xpm" #include "pixmaps/redo.xpm" #include "pixmaps/scale.xpm" #include "pixmaps/sequences.xpm" #include "pixmaps/show_bars_off.xpm" #include "pixmaps/show_bars_on.xpm" #include "pixmaps/snap.xpm" #include "pixmaps/t_rec_on.xpm" #include "pixmaps/thru.xpm" #include "pixmaps/thru_on.xpm" #include "pixmaps/tools.xpm" #include "pixmaps/transpose.xpm" #include "pixmaps/undo.xpm" #include "pixmaps/up.xpm" #include "pixmaps/up_inv.xpm" #include "pixmaps/zoom.xpm" /* zoom_in/_out combo-box */ #include "pixmaps/chord3-inv.xpm" /** * Rather than repopulate combos upon any sequence change, which causes * issues, let's populate them only with the user asks for one of them. * Luckily, they use a QPushButton which executes free-standing popup * menus. The MIDI channel is a combo-box, but no new events in the * pattern requires a change there; it is filled at startup based on * optional user-channel information in the 'usr' file, or filled when * changing the MIDI buss. * * So far, this seems to work; we will make it permanent at some point. * Now permanent: USE_LAZY_REPOPULATE_USR_COMBOS */ namespace seq66 { /* * We have an issue that using the new (and larger) qseqeditframe64 class in * the Edit tab causes the whole main window to increase in size, which * stretches the Live frame's pattern slots too much vertically. So let's * try to shrink the seq-edit's piano roll to compensate. Nope. However, we * now get around that issue by halving the height of the qseqdata pane and * removing the "Map" button. */ /** * Static data members. These items apply to all of the instances of * seqedit. * * The snap and note-length defaults would be good to write to the "user" * configuration file. The scale and key would be nice to write to the * proprietary section of the MIDI song. Or, even more flexibly, to each * sequence, if that makes sense to do, since all tracks would generally be * in the same key. Right, Charles Ives? * * Note that, currently, that some of these "initial values" are modified, so * that they are "contagious". That is, the next sequence to be opened in * the sequence editor will adopt these values. This is a long-standing * feature of Seq24, but strikes us as a bit surprising. * * If we just double the PPQN, then the snap divisor becomes 32, and the snap * interval is a 32nd note. We would like to keep it at a 16th note. We * correct the snap ticks to the actual PPQN ratio. */ int qseqeditframe64::sm_initial_snap = c_base_ppqn / 4; int qseqeditframe64::sm_initial_note_length = c_base_ppqn / 4; int qseqeditframe64::sm_initial_chord = 0; /** * To reduce the amount of written code, we use the following count to cover * beats/measure ranging from 1 to 16, plus an additional value of 32. The user * can always manually edit odd beats/measure values. Unused. * * static const int s_beat_measure_count = 16; */ /** * These static items are used to fill in and select the proper zoom values for * the grids. Note that they are not members, though they could be. * Also note the features of these zoom numbers: * * -# The lowest zoom value is defined in qeditbase and usrsettings. * -# The highest zoom value is defined in qeditbase and usrsettings. * -# The zoom values are all powers of 2. * -# The zoom values are further constrained by the configured values * of usr().min_zoom() and usr().max_zoom(). * -# The default zoom is specified in the user's "usr" file, and * the default value of this default zoom is 2. * * \todo * We still need to figure out what to do with a zoom of 0, which * is supposed to tell Seq66 to auto-adjust to the current PPQN. * * Please note that the list of zooms is now maintained in the settings * module. */ /* * For set_event_entry calls. */ using event_popup_pair = struct { std::string epp_name; midibyte epp_status; }; /* * This array must exactly match the qseqeditframe64::event_index enumeration. */ event_popup_pair s_event_items [] = { { "Note On Velocity", EVENT_NOTE_ON }, { "Note Off Velocity", EVENT_NOTE_OFF }, { "Aftertouch", EVENT_AFTERTOUCH }, { "Control Change", EVENT_CONTROL_CHANGE }, { "Program Change", EVENT_PROGRAM_CHANGE }, { "Channel Pressure", EVENT_CHANNEL_PRESSURE }, { "Pitch Wheel", EVENT_PITCH_WHEEL }, { "Tempo", EVENT_META_SET_TEMPO }, /* special */ { "Time Signature", EVENT_META_TIME_SIGNATURE }, /* ditto */ { "Text", EVENT_META_TEXT_EVENT } /* tricky */ }; /** * * Note that a shorter window has a QTabWidget as a parent, while the normal * window has a qsmainwnd (QMainWindow) as a parent. * * \param p * Provides the performer object to use for interacting with this * sequence. Among other things, this object provides the active PPQN. * * \param s * Provides the reference to the sequence represented by this seqedit. * * \param parent * Provides the parent window/widget for this container window. Defaults * to null. Normally a pointer to a qseditex frame or the qsmainwnd's * EditTab is passed in this parameter. If the former, we want to be * able to change it's title. * * \param shorter * If true, the data window is halved in size, and some controls are * hidden, to compact the editor for use a tab. If false, we assume * that the \a parent parameter is a qseqeditex frame. */ qseqeditframe64::qseqeditframe64 ( performer & p, sequence & s, QWidget * parent, bool shorter ) : qseqframe (p, s, adapted_seq_zoom(p.ppqn()), parent), performer::callbacks (p), ui (new Ui::qseqeditframe64), m_qseqeditex_frame (nullptr), m_edit_tab_widget (nullptr), m_short_version (shorter), /* short_version() */ m_is_looping (false), m_lfo_wnd (nullptr), m_patternfix_wnd (nullptr), m_tools_popup (nullptr), m_tools_harmonic (nullptr), m_tools_pitch (nullptr), m_tools_timing (nullptr), m_sequences_popup (nullptr), m_events_popup (nullptr), m_minidata_popup (nullptr), m_measures_list (measure_items()), /* see settings module */ m_measures (0), m_beats_per_bar_list (beats_per_bar_items()), m_beats_per_bar (0), /* set in ctor body */ m_beats_per_bar_to_log (0), /* set in ctor body */ m_beatwidth_list (beatwidth_items()), /* see settings module */ m_beat_width (0), /* set in ctor body */ m_beat_width_to_log (0), /* set in ctor body */ m_snap_list (snap_items()), /* see settings module */ m_snap (sm_initial_snap), m_zoom_list (zoom_items()), /* see settings module */ m_rec_vol_list (rec_vol_items()), /* see settings module */ m_note_length (sm_initial_note_length), m_scale (0), /* set in ctor body */ m_chord (0), m_key (usr().seqedit_key()), m_bgsequence (-1), /* set in ctor body */ m_edit_bus (0), m_edit_in_bus (0), /* TODO */ m_edit_channel (0), /* 0-15, null */ m_first_event (max_midibyte()), m_first_event_name ("(no events)"), m_have_focus (false), m_edit_mode (perf().edit_mode(s.seq_number())), m_last_record_style (perf().record_style()), m_armed_status (false), m_show_hex_values (false), m_show_level_numbers (true), m_timer (nullptr) { std::string seqname = "No sequence!"; int loopcountmax = 0; ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); /* part of issue #4 */ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); if (s.snap() > 0) m_snap = s.snap(); m_armed_status = track().armed(); if (m_short_version) { /* * Currently this seems to have position 0, 0, which does not work * for showing note tooltips. */ #if defined SEQ66_SHOW_GENERIC_TOOLTIPS /* not defined */ m_edit_tab_widget = parent; #endif } else m_qseqeditex_frame = dynamic_cast(parent); /* * These indirect settings have side-effects, so we just assign the * values to the members. * * set_beats_per_bar(track().get_beats_per_bar(), qbase::status::startup); * set_beat_width(track().get_beat_width(), qbase::status::startup); * set_measures(track().get_measures(), qbase::status::startup); */ m_beats_per_bar_to_log = m_beats_per_bar = track().get_beats_per_bar(); m_beat_width_to_log = m_beat_width = track().get_beat_width(); m_measures = track().get_measures(); m_scale = track().musical_scale(); m_chord = track().musical_chord(); m_edit_bus = track().seq_midi_bus(); m_edit_in_bus = track().seq_midi_in_bus(); m_edit_channel = track().midi_channel(); /* 0-15, null */ seqname = track().name(); loopcountmax = track().loop_count_max(); initialize_panels(); /* uses seq_pointer() */ if (usr().global_seq_feature()) { set_scale(usr().seqedit_scale(), qbase::status::startup); set_key(usr().seqedit_key(), qbase::status::startup); set_background_sequence ( usr().seqedit_bgsequence(), qbase::status::startup ); track().musical_scale(usr().seqedit_scale()); track().musical_key(usr().seqedit_key()); track().background_sequence(usr().seqedit_bgsequence()); } else { set_scale(track().musical_scale(), qbase::status::startup); set_key(track().musical_key(), qbase::status::startup); set_background_sequence ( track().background_sequence(), qbase::status::startup ); } bool expandrec = track().expanded_recording(); setup_record_styles(); setup_alterations(); /* * Sequence Number Label */ std::string seqtext = std::to_string(track().seq_number()); QString labeltext = qt(seqtext); ui->m_label_seqnumber->setText(labeltext); /* * Sequence Title. Related to issue #133, we connect to editingFinished() * instead. */ ui->m_entry_name->setText(qt(seqname)); connect ( ui->m_entry_name, SIGNAL(editingFinished()), this, SLOT(update_seq_name()) ); /* * Beats Per Bar, preliminary (global) value. Fill options for the beats * per measure combo-box, and set the default. These range from 1 to 16, * plus a value of 32. * * How to handle values outside the combobox? Pass the string as a * parameter to fill_combobox(). * * We also set up the preliminary values of beat-width here to avoid doing * it while connected. */ bool got_timesig = detect_time_signature(); /* find beats/bar & width */ std::string bstring = std::to_string(m_beats_per_bar); (void) fill_combobox(ui->m_combo_bpm, beats_per_bar_list(), bstring); /* * Didn't seem to be needed, as changing the index also changes the text. * However, now that editingFinished() is the signal, even a selection * of a number requires a Tab out or an Enter key, which is not expected * by the user. So this connection makes the change immediate upon * selection. * * Related to issue #133, we need to connect the editable combo-box's * line-edit. */ const char ** up_pixmap = usr().dark_theme() ? up_inv_xpm : up_xpm ; qt_set_icon(up_pixmap, ui->m_button_bpm); connect ( ui->m_combo_bpm, SIGNAL(currentIndexChanged(int)), this, SLOT(update_beats_per_bar(int)) ); connect ( ui->m_combo_bpm->lineEdit(), SIGNAL(editingFinished()), this, SLOT(text_beats_per_bar()) ); connect ( ui->m_button_bpm, SIGNAL(clicked(bool)), this, SLOT(reset_beats_per_bar()) ); /* * Beat Width (denominator of time signature). Fill the options for * the beats per measure combo-box, and set the default. */ bstring = std::to_string(m_beat_width); (void) fill_combobox(ui->m_combo_bw, beatwidth_list(), bstring); qt_set_icon(up_pixmap, ui->m_button_bw); connect ( ui->m_combo_bw, SIGNAL(currentIndexChanged(int)), this, SLOT(update_beat_width(int)) ); connect ( ui->m_combo_bw->lineEdit(), SIGNAL(editingFinished()), this, SLOT(text_beat_width()) ); connect ( ui->m_button_bw, SIGNAL(clicked(bool)), this, SLOT(reset_beat_width()) ); /* * Button to log the current time signature as a Meta event at the * current "L" marker. */ set_log_timesig_text(m_beats_per_bar_to_log, m_beat_width_to_log); set_log_timesig_status(false); connect ( ui->m_button_log_timesig, SIGNAL(clicked(bool)), this, SLOT(slot_log_timesig()) ); /* * Pattern Length in Measures. Fill the options for the beats per measure * combo-box, and set the default. */ std::string mstring = std::to_string(m_measures); (void) fill_combobox(ui->m_combo_length, measures_list(), mstring); /* * Didn't seem to be needed, as changing the index also changes the text. * But now that we're connecting to editingFinished(), we need the * following connection so that length/measures selection ushers in * an immediate change. * * For issue #133, we need to connect the editable combo-box's line-edit * control to the editingFinished signal, so that the number(s) being * entered will not take effect until either Tab or Enter is struck. */ qt_set_icon ( usr().dark_theme() ? length_short_inv_xpm : length_short_xpm, ui->m_button_length ); #if defined USE_LEGACY_MEASURES_ADJUSTMENT connect ( ui->m_combo_length, SIGNAL(currentTextChanged(const QString &)), this, SLOT(text_measures(const QString &)) ); #else connect ( ui->m_combo_length, SIGNAL(currentIndexChanged(int)), this, SLOT(update_measures(int)) ); connect ( ui->m_combo_length->lineEdit(), SIGNAL(editingFinished()), this, SLOT(text_measures_edit()) ); #endif connect ( ui->m_button_length, SIGNAL(clicked(bool)), this, SLOT(reset_measures()) ); /* * Transposable button. */ bool can_transpose = track().transposable(); /* * Hide some vertically- and horizontally-oriented widgets to fit in a * tighter space. * * resize(880, height()); // does not work here */ if (shorter) { ui->m_toggle_drum->hide(); /* ui->m_toggle_transpose->hide() */ ui->m_map_notes->hide(); ui->btn_filter_painted_notes->hide(); ui->btn_show_hex->hide(); ui->btn_show_scale_or_chords->hide(); ui->btn_spare_0->hide(); ui->btn_level_numbers->hide(); ui->btn_pattern_fix->hide(); ui->m_button_loop->hide(); ui->m_button_lfo->hide(); } else { /* * Drum-Mode Button. Qt::NoFocus is the default focus policy. * Also includes the new loop button, which does the same function * as in the main window. */ ui->m_toggle_drum->setAutoDefault(false); ui->m_toggle_drum->setCheckable(true); qt_set_icon(drum_xpm, ui->m_toggle_drum); qt_set_icon(loop_xpm, ui->m_button_loop); connect ( ui->m_toggle_drum, SIGNAL(toggled(bool)), this, SLOT(editor_mode(bool)) ); std::string keyname = perf().automation_key(automation::slot::loop_LR); tooltip_with_keystroke(ui->m_button_loop, keyname); connect ( ui->m_button_loop, SIGNAL(toggled(bool)), this, SLOT(loop_mode(bool)) ); connect ( ui->m_map_notes, SIGNAL(clicked(bool)), this, SLOT(remap_notes()) ); ui->m_map_notes->setEnabled(can_transpose); /* * Thin pattern-fix button. */ connect ( ui->btn_pattern_fix, SIGNAL(clicked()), this, SLOT(slot_pattern_fix()) ); } /* * Transpose button. Qt::NoFocus is the default focus policy. * When implement, add tooltip extension for mod_transpose_song. (???) */ qt_set_icon(transpose_xpm, ui->m_toggle_transpose); ui->m_toggle_transpose->setCheckable(true); ui->m_toggle_transpose->setChecked(can_transpose); ui->m_toggle_transpose->setAutoDefault(false); connect ( ui->m_toggle_transpose, SIGNAL(toggled(bool)), this, SLOT(transpose(bool)) ); set_transpose_image(can_transpose); /* * Chord button and combox-box. See chord_name_ptr() in the scales.hpp * header file. */ qt_set_icon(chord3_inv_xpm, ui->m_button_chord); connect ( ui->m_button_chord, SIGNAL(clicked(bool)), this, SLOT(reset_chord()) ); for (int chord = 0; /* condition in loop */ ; ++chord) { const char * cn = chord_name_ptr(chord); if (strlen(cn) > 0) { QString combo_text = cn; ui->m_combo_chord->insertItem(chord, combo_text); } else break; } ui->m_combo_chord->setCurrentIndex(m_chord); connect ( ui->m_combo_chord, SIGNAL(currentIndexChanged(int)), this, SLOT(update_chord(int)) ); set_chord(m_chord); /* * MIDI buss items discovered at startup-time. Not sure if we want to * use the button to reset the buss, or increment to the next buss. */ qt_set_icon(bus_xpm, ui->m_button_bus); connect ( ui->m_button_bus, SIGNAL(clicked(bool)), this, SLOT(reset_midi_bus()) ); const clockslist & opm = output_port_map(); mastermidibus * mmb = perf().master_bus(); if (not_nullptr(mmb)) { bussbyte seqbuss = m_edit_bus; /* seq_pointer()->seq_midi_bus() */ int buses = opm.active() ? opm.count() : mmb->get_num_out_buses() ; for (int bus = 0; bus < buses; ++bus) { e_clock ec; std::string busname; if (perf().ui_get_clock(bussbyte(bus), ec, busname)) { bool unavailable = perf().is_port_unavailable ( bussbyte(bus), midibase::io::output ); bool disabled = ec == e_clock::disabled || unavailable; ui->m_combo_bus->addItem(qt(busname)); if (disabled) enable_combobox_item(ui->m_combo_bus, bus, false); } } ui->m_combo_bus->setCurrentIndex(int(seqbuss)); } connect ( ui->m_combo_bus, SIGNAL(currentIndexChanged(int)), this, SLOT(update_midi_bus(int)) ); /* * MIDI channels. Not sure if we want to use the button to reset the * channel, or increment to the next channel. */ qt_set_icon(midi_xpm, ui->m_button_channel); connect ( ui->m_button_channel, SIGNAL(clicked(bool)), this, SLOT(reset_midi_channel()) ); /* * Undo and Redo Buttons. */ qt_set_icon(undo_xpm, ui->m_button_undo); connect(ui->m_button_undo, SIGNAL(clicked(bool)), this, SLOT(undo())); qt_set_icon(redo_xpm, ui->m_button_redo); connect(ui->m_button_redo, SIGNAL(clicked(bool)), this, SLOT(redo())); /* * Quantize Button. This is the "Q" button, and indicates to quantize * (just?) notes. Compare it to the Quantize menu entry, which quantizes * events. Note the usage of std::bind()... this feature requires C++11. * Also see q_record_change(), which handles on-the-fly quantization while * recording. */ tooltip_with_keystroke(ui->m_button_undo, "q"); qt_set_icon ( usr().dark_theme() ? quantize_inv_xpm : quantize_xpm, ui->m_button_quantize ); connect ( ui->m_button_quantize, &QPushButton::clicked, std::bind ( &qseqeditframe64::do_action, this, eventlist::edit::quantize_notes, 0 ) ); /* * Follow Progress Button. If expanded recording is in force for this * track, then force progress-follow. */ std::string keyname = perf().automation_key ( automation::slot::follow_transport ); tooltip_with_keystroke(ui->m_toggle_follow, keyname); qt_set_icon(follow_xpm, ui->m_toggle_follow); ui->m_toggle_follow->setCheckable(true); if (usr().follow_progress() || expandrec) { /* * For issue #94, both here and in the perf-edit frame, we may need to * allocate more width. To be determined. See: * * qseqroll::scroll_offset() * qperfroll::sizeHint() */ int seqwidth = m_seqroll->width(); int scrollwidth = ui->rollScrollArea->width(); bool followit = expandrec || (seqwidth > scrollwidth); m_seqroll->progress_follow(followit); ui->m_toggle_follow->setChecked(m_seqroll->progress_follow()); update_draw_geometry(); } else { m_seqroll->progress_follow(false); ui->m_toggle_follow->setChecked(false); } /* * Qt::NoFocus is the default focus policy. */ ui->m_toggle_follow->setAutoDefault(false); connect ( ui->m_toggle_follow, SIGNAL(toggled(bool)), this, SLOT(slot_follow(bool)) ); /** * Fill "Snap" and "Note" Combo Boxes: To reduce the amount of written * code, we use a static array to initialize some of these menu entries. * 0 denotes the separator. This same setup is used to set up both the * snap and note menu, since they are exactly the same. Saves a *lot* of * code. This code was copped from the Gtkmm 2.4 seqedit class and * adapted to Qt 5. */ (void) fill_combobox(ui->m_combo_snap, snap_list(), "16", "1/"); /* 1/16th */ (void) fill_combobox(ui->m_combo_note, snap_list(), "16", "1/"); /* ditto */ connect ( ui->m_combo_snap, SIGNAL(currentIndexChanged(int)), this, SLOT(update_grid_snap(int)) ); connect ( ui->m_combo_note, SIGNAL(currentIndexChanged(int)), this, SLOT(update_note_length(int)) ); qt_set_icon(snap_xpm, ui->m_button_snap); connect ( ui->m_button_snap, SIGNAL(clicked(bool)), this, SLOT(reset_grid_snap()) ); midipulse scaledtick = rescale_tick ( sm_initial_snap, perf().ppqn(), usr().base_ppqn() ); set_snap(scaledtick); set_note_length(scaledtick); qt_set_icon ( usr().dark_theme() ? note_length_inv_xpm : note_length_xpm, ui->m_button_note ); connect ( ui->m_button_note, SIGNAL(clicked(bool)), this, SLOT(reset_note_length()) ); /* * Zoom In and Zoom Out: Rather than two buttons, we use one and * a combo-box. */ qt_set_icon(zoom_xpm, ui->m_button_zoom); connect ( ui->m_button_zoom, SIGNAL(clicked(bool)), this, SLOT(slot_reset_zoom()) ); (void) fill_combobox(ui->m_combo_zoom, zoom_list(), "2", "1:"); connect ( ui->m_combo_zoom, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_update_zoom(int)) ); int zoom = usr().base_zoom(); if (usr().adapt_zoom()) zoom = zoom_power_of_2(perf().ppqn()); set_zoom(zoom); /* * Musical Keys Button and Combo-Box. See the scales.hpp header file. */ qt_set_icon(key_xpm, ui->m_button_key); connect ( ui->m_button_key, SIGNAL(clicked(bool)), this, SLOT(reset_key()) ); for (int key = 0; key < c_octave_size; ++key) { QString combo_text = qt(musical_key_name(int_to_key(key))); ui->m_combo_key->insertItem(key, combo_text); } ui->m_combo_key->setCurrentIndex(m_key); connect ( ui->m_combo_key, SIGNAL(currentIndexChanged(int)), this, SLOT(update_key(int)) ); /* * Musical Scales Button and Combo-Box. See c_scales_text[] in the * scales.hpp header file. */ qt_set_icon(scale_xpm, ui->m_button_scale); connect ( ui->m_button_scale, SIGNAL(clicked(bool)), this, SLOT(reset_scale()) ); for (int scale = c_scales_off; scale < c_scales_max; ++scale) { QString combo_text = qt(musical_scale_name(int_to_scale(scale))); ui->m_combo_scale->insertItem(scale, combo_text); } ui->m_combo_scale->setCurrentIndex(m_scale); connect ( ui->m_combo_scale, SIGNAL(currentIndexChanged(int)), this, SLOT(update_scale(int)) ); /* * Tools Pop-up Menu Button. Must come after the scale has been * determined. */ qt_set_icon(tools_xpm, ui->m_button_tools); connect(ui->m_button_tools, SIGNAL(clicked(bool)), this, SLOT(tools())); popup_tool_menu(); /* * Background Sequence/Pattern Selectors. */ qt_set_icon(sequences_xpm, ui->m_button_sequence); connect ( ui->m_button_sequence, SIGNAL(clicked(bool)), this, SLOT(sequences()) ); popup_sequence_menu(); /* create the initial popup menu */ /* * Tiny vertical zoom keys */ connect ( ui->btnKeyVZoomIn, SIGNAL(clicked(bool)), this, SLOT(v_zoom_in()) ); connect ( ui->btnKeyVZoomReset, SIGNAL(clicked(bool)), this, SLOT(reset_v_zoom()) ); connect ( ui->btnKeyVZoomOut, SIGNAL(clicked(bool)), this, SLOT(v_zoom_out()) ); /* * Tool-tip mode */ ui->tooltip_button->setCheckable(true); ui->tooltip_button->setChecked(false); connect ( ui->tooltip_button, SIGNAL(toggled(bool)), this, SLOT(tooltip_mode(bool)) ); /* * Note-entry mode */ qt_set_icon(finger_xpm, ui->m_button_note_entry); ui->m_button_note_entry->setCheckable(true); ui->m_button_note_entry->setAutoDefault(false); ui->m_button_note_entry->setChecked(false); connect ( ui->m_button_note_entry, SIGNAL(toggled(bool)), this, SLOT(note_entry(bool)) ); /* * Event Selection Button and Popup Menu for qseqdata. */ connect ( ui->m_button_event, SIGNAL(clicked(bool)), this, SLOT(events()) ); if (got_timesig) set_data_type(EVENT_META_TIME_SIGNATURE); else set_data_type(EVENT_NOTE_ON); /* * Event Data Presence-Indicator Button and Popup Menu. */ connect ( ui->m_button_data, SIGNAL(clicked(bool)), this, SLOT(data()) ); if (! shorter) { /* * LFO Button. */ ui->m_button_lfo->setEnabled(true); connect ( ui->m_button_lfo, SIGNAL(clicked(bool)), this, SLOT(show_lfo_frame()) ); } /* * Loop-count Spin Box. */ ui->m_spin_loop_count->setValue(loopcountmax); ui->m_spin_loop_count->setReadOnly(false); connect ( ui->m_spin_loop_count, SIGNAL(valueChanged(int)), this, SLOT(slot_loop_count(int)) ); /* * Enable (unmute) Play Button. It's not the triangular play button, it's * the box-to-MIDI-port button. */ qt_set_icon(play_xpm, ui->m_toggle_play); ui->m_toggle_play->setCheckable(true); connect ( ui->m_toggle_play, SIGNAL(toggled(bool)), this, SLOT(slot_play_change(bool)) ); /* * MIDI Thru Button. */ qt_set_icon(thru_xpm, ui->m_toggle_thru); ui->m_toggle_thru->setCheckable(true); connect ( ui->m_toggle_thru, SIGNAL(toggled(bool)), this, SLOT(slot_thru_change(bool)) ); /* * MIDI Record Button. */ qt_set_icon(rec_xpm, ui->m_toggle_record); ui->m_toggle_record->setCheckable(true); connect ( ui->m_toggle_record, SIGNAL(toggled(bool)), this, SLOT(slot_record_change(bool)) ); /* * MIDI Quantized Record Button. For recording, we can set red-letter * icons for ">", "Q", "T", and "N". */ ui->m_toggle_qrecord->setCheckable(true); s.set_recording_style(p.record_style()); set_toggle_qrecord_button(); connect ( ui->m_toggle_qrecord, SIGNAL(toggled(bool)), this, SLOT(slot_q_record_change(bool)) ); connect ( ui->m_combo_rec_type, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_record_style(int)) ); /* * Recording Volume Button and Combo Box */ connect ( ui->m_button_rec_vol, SIGNAL(clicked(bool)), this, SLOT(reset_recording_volume()) ); (void) fill_combobox(ui->m_combo_rec_vol, rec_vol_list()); connect ( ui->m_combo_rec_vol, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_recording_volume(int)) ); /* * This little button is a workaround for Qt's lack of a signal * when the window manager's "X" button in the title bar of a non-main * window is clicked. Doesn't work right, leaves a ghost window and * a seqfault at application exit. * * connect(ui->btn_close, SIGNAL(clicked()), this, SLOT(close())); */ set_recording_volume(usr().velocity_override()); /* * Set up the events, mini-events, and channel selectors based on the * current buss and channel and (if activated) the 'usr' file's * entries for MIDI buss and instrument definitions. * * Each of the following set calls call repopulate_usr_combos() only * when the user makes a change. */ repopulate_usr_combos(m_edit_bus, m_edit_channel); set_midi_bus(m_edit_bus, qbase::status::startup); /* * set_midi_in_bus(m_edit_bus, qbase::status::startup); */ set_midi_channel(m_edit_channel, qbase::status::startup); /* 0-15/0x80 */ /* * Old location. Avoid calling setChecked(), as the Qt 5 documentation * seems to be incorrect in saying setChecked() doesn't trigger a slot. */ update_midi_buttons(); set_initialized(); cb_perf().enregister(this); /* notification */ m_seqroll->setFocus(); m_timer = qt_timer(this, "qseqeditframe64", 2, SLOT(conditional_update())); } qseqeditframe64::~qseqeditframe64 () { if (not_nullptr(m_timer)) m_timer->stop(); cb_perf().unregister(this); delete ui; } void qseqeditframe64::stop_timer () { if (not_nullptr(m_timer)) m_timer->stop(); } void qseqeditframe64::set_show_scale_or_chords () { bool active = m_seqroll->show_scale_or_chords(); if (! usr().dark_theme()) { qt_set_icon ( active ? show_bars_on_xpm : show_bars_off_xpm, ui->btn_show_scale_or_chords ); } ui->btn_show_scale_or_chords->setChecked(active); } void qseqeditframe64::set_filter_painted_notes () { bool active = m_seqroll->filter_painted_notes(); if (! usr().dark_theme()) { qt_set_icon ( active ? filter_on_xpm : filter_off_xpm, ui->btn_filter_painted_notes ); } ui->btn_filter_painted_notes->setChecked(active); } void qseqeditframe64::scroll_by_step (qscrollmaster::dir d) { switch (d) { case qscrollmaster::dir::left: case qscrollmaster::dir::right: ui->rollScrollArea->scroll_x_by_step(d); break; case qscrollmaster::dir::up: case qscrollmaster::dir::down: ui->rollScrollArea->scroll_y_by_step(d); break; } } /** * Odd, when this window has focus, this function is called roughly every 1/2 * second! The type of event is always qpep->type() == 12. */ void qseqeditframe64::paintEvent (QPaintEvent * qpep) { qpep->ignore(); /* QFrame::paintEvent(qpep) */ } /** * Here, we will need to recreate the current viewport, if not the whole damn * seqroll. */ void qseqeditframe64::resizeEvent (QResizeEvent * qrep) { update_draw_geometry(); qrep->ignore(); /* qseqframe::resizeEvent(qrep) */ } /** * This event is not called when moving the roll-scroll area horizontally or * vertically. However, paintEvent() calls do occur then for seqtime, seqdata, * seqevent (qstriggereditor), and seqkey. */ void qseqeditframe64::wheelEvent (QWheelEvent * qwep) { qwep->ignore(); /* qseqframe::resizeEvent(qwep) */ } /** * https://www.informit.com/articles/article.aspx?p=1405544&seqNum=2 * * The edit frame is the monitor of what events go into the * various scroll areas. A follow-on to issue #3. * * This does not work, so see the new qscrollslave class, a replacement * for QScrollArea that disables the arrow and paging keys. */ bool qseqeditframe64::eventFilter (QObject * target, QEvent * event) { return qseqframe::eventFilter(target, event); } /** * Once the user has clicked on the qseqroll, the Space key can be used to * start, stop, and restart playback. The Period key can be used to pause * and start (at the same position) playback. * * We could simplify this a bit by creating a keystroke object. * See qseqroll. */ void qseqeditframe64::keyPressEvent (QKeyEvent * event) { int key = event->key(); bool isctrl = bool(event->modifiers() & Qt::ControlModifier); if (perf().is_pattern_playing()) { if (key == Qt::Key_Space) stop_playing(); else if (key == Qt::Key_Escape) stop_playing(); else if (key == Qt::Key_Period) pause_playing(); } else { if (key == Qt::Key_Space || key == Qt::Key_Period) { start_playing(); } else if (key == Qt::Key_Escape) { /* * If the parent is a qseqeditex, and if enabled, Esc can * close the pattern editor. A concession for issue #117. */ if (m_seqroll->adding()) { m_seqroll->set_adding(false); } else if (not_nullptr(m_qseqeditex_frame)) { if (usr().escape_pattern() && ! short_version()) m_qseqeditex_frame->close(); } } else if (isctrl) { bool isshift = bool(event->modifiers() & Qt::ShiftModifier); if (key == Qt::Key_Z) { if (isshift) redo(); // track().pop_redo(); else undo(); // #110 track().pop_undo() } } } if (! isctrl) { bool isshift = bool(event->modifiers() & Qt::ShiftModifier); if (! zoom_key_press(isshift, key)) { if (isshift) { if (key == Qt::Key_L) { m_seqtime->setFocus(); m_seqtime->m_move_L_marker = true; } else if (key == Qt::Key_R) { m_seqtime->setFocus(); m_seqtime->m_move_L_marker = false; } else event->accept(); } else { /* * vi-style scrolling keystrokes */ if (key == Qt::Key_J) scroll_by_step(qscrollmaster::dir::down); else if (key == Qt::Key_K) scroll_by_step(qscrollmaster::dir::up); else if (key == Qt::Key_H) scroll_by_step(qscrollmaster::dir::left); else if (key == Qt::Key_L) scroll_by_step(qscrollmaster::dir::right); else event->accept(); } } } else event->accept(); } void qseqeditframe64::keyReleaseEvent (QKeyEvent *) { // no code } /** * Supplemental zoom support, for horizontal zoom only, so that when * focus is not on the seqroll, zoom will still work. */ bool qseqeditframe64::zoom_key_press (bool shifted, int key) { bool result = false; if (shifted) { if (key == Qt::Key_Z) result = zoom_in(); } else { if (key == Qt::Key_Z) result = zoom_out(); else if (key == Qt::Key_0) result = reset_zoom(); } return result; } /** * Passes along the signal to close the windows. */ void qseqeditframe64::closeEvent (QCloseEvent * event) { /* * ca 2026-04-23 Let's stop the timer first. */ if (not_nullptr(m_timer)) m_timer->stop(); remove_lfo_frame(); remove_patternfix_frame(); event->accept(); } /** * This function now seems to be called. Yay! Note that these functions * do not take action when changing the item number from the live grid: * * set_midi_bus(bus, qbase::status::edit); * set_midi_channel(channel, qbase::status::edit); * * Therefore we extract the necessary functionality from them and put * it here. * * The call to repopulate_usr_combos() calls these functions: * * repopulate_midich_combo(buss); * repopulate_event_menu(m_edit_bus, m_edit_channel); * repopulate_mini_event_menu(m_edit_bus, m_edit_channel); * * Since USE_LAZY_REPOPULATE_USR_COMBOS is now permanent, we'd prefer * to not have that over during sequence changes. * * These are needed in case the change involved adding new kinds of events * to the pattern. Also, set_track_change() will call sequence::modify(), * which is unnecessary because sequence and performer already have the * change. [ca 2024-12-21] */ bool qseqeditframe64::on_sequence_change ( seq::number seqno, performer::change ctype ) { seq::number trackno = track().seq_number(); bool result = seqno == trackno; if (result) { bool modification = false; if (ctype == performer::change::yes) { int bus = track().seq_midi_bus(); int channel = track().midi_channel(); int inbus = track().seq_midi_in_bus(); bool slotchanged = bus != m_edit_bus || channel != m_edit_channel || inbus != m_edit_in_bus; if (slotchanged) { m_edit_bus = bus; m_edit_channel = channel; m_edit_in_bus = inbus; modification = true; repopulate_usr_combos(bus, channel); } } set_external_frame_title(modification); set_dirty(); update_midi_buttons(); /* mirror current states */ } return result; } /** * Added this function to handle simple changes in sequence status, * including recording changes. Can be conflated with on_sequence_change(). */ bool qseqeditframe64::on_trigger_change (seq::number seqno, performer::change mod) { seq::number trackno = track().seq_number(); bool result = seqno == trackno; if (result && performer::callbacks::true_change(mod)) set_track_change(true); /* also calls set_dirty() */ update_midi_buttons(); /* mirror current states */ return result; } /** * More redraws? Data and event panes? */ bool qseqeditframe64::on_automation_change (automation::slot s) { if (s == automation::slot::start || s == automation::slot::stop) m_seqroll->set_redraw(); else if (s == automation::slot::mod_undo) undo(); else if (s == automation::slot::mod_redo) redo(); else if (s == automation::slot::record_style) /* issue #128 */ slot_record_style(usr().pattern_record_code()); return true; } /** * Recording loop style Overdub (merge), Overwrite (replace), Expand * (extend), and One-Shot button. Provides a button to cycle the * recording style between "merge" (when looping, merge new incoming events * into the pattern), "overwrite" (replace events with incoming events), * "expand" (increase the size of the loop to accomodate new events), and * "oneshot" (record one loop and stop playing). * * Setting the current index will not call the slot function because * ui->m_combo_rec_type is not yet connected. So we call it manually. */ void qseqeditframe64::setup_record_styles () { int lrmerge = usr().pattern_record_code(recordstyle::merge); int lrreplace = usr().pattern_record_code(recordstyle::overwrite); int lrexpand = usr().pattern_record_code(recordstyle::expand); int lroneshot = usr().pattern_record_code(recordstyle::oneshot); int lrreset = usr().pattern_record_code(recordstyle::oneshot_reset); const tokenization & items = rec_style_items(); // settings ui->m_combo_rec_type->insertItem(lrmerge, qt(items[0])); // "Overdub" ui->m_combo_rec_type->insertItem(lrreplace, qt(items[1])); // "Overwrite" ui->m_combo_rec_type->insertItem(lrexpand, qt(items[2])); // "Expand" ui->m_combo_rec_type->insertItem(lroneshot, qt(items[3])); // "One-shot" ui->m_combo_rec_type->insertItem(lrreset, qt(items[4])); // "1-shot reset" int npc = usr().pattern_record_code(); ui->m_combo_rec_type->setCurrentIndex(npc); m_last_record_style = perf().record_style(); /* * TODO: work this out */ bool resetenabled = m_last_record_style == recordstyle::oneshot; enable_combobox_item(ui->m_combo_rec_type, lrreset, resetenabled); slot_record_style(npc); } /** * This check looks only for "Untitled" and no events. Causes * opening this window to unmute patterns in generic *.mid files. * * Indirectly handled: * * usr().pattern_tighten() * usr().pattern_notemap() */ void qseqeditframe64::setup_alterations () { bool alter = track().is_new_pattern() || ! usr().pattern_new_only(); if (alter) { alteration alt = alteration::none; toggler t = toggler::off; bool altered_recording = usr().pattern_alter_recording(); if (altered_recording) { alt = usr().pattern_alteration(); t = toggler::on; } slot_play_change(usr().pattern_armed()); /* set track arming */ slot_thru_change(usr().pattern_thru()); /* set track thru */ slot_record_change(usr().pattern_record()); /* set track record */ q_record_change(alt, t); /* trickier */ } else { alteration alt = alteration::none; toggler t = toggler::off; bool altered_recording = usr().alter_recording(); if (altered_recording) { alt = usr().record_alteration(); t = toggler::on; } q_record_change(alt, t); } } void qseqeditframe64::get_position (int & x, int & y) { if (not_nullptr(m_qseqeditex_frame)) { x = m_qseqeditex_frame->x(); y = m_qseqeditex_frame->x(); } #if defined SEQ66_SHOW_GENERIC_TOOLTIPS else if (not_nullptr(m_edit_tab_widget)) { x = m_edit_tab_widget->x(); y = m_edit_tab_widget->y(); } #else else { x = 0; y = 0; } #endif } /** * Instantiates the various editable areas (panels) of the seqedit * user-interface. Note that m_seqkeys and the other panel pointers are * protected members of the qseqframe base class. We could move * qseqframe's members into this class, now that we no * longer provide the old qseqeditframe. */ void qseqeditframe64::initialize_panels () { int noteheight = usr().key_height(); int height = noteheight * c_notes_count + 1; m_seqkeys = new (std::nothrow) qseqkeys ( perf(), track(), this, ui->keysScrollArea, noteheight, height ); ui->keysScrollArea->setWidget(m_seqkeys); ui->keysScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->keysScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->keysScrollArea->verticalScrollBar()->setRange(0, height); m_seqtime = new (std::nothrow) qseqtime ( perf(), track(), this, zoom(), ui->timeScrollArea ); ui->timeScrollArea->setWidget(m_seqtime); ui->timeScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->timeScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); /* * qseqroll. Note the "this" parameter is not really a Qt parent * parameter. It simply gives qseqroll access to the qseqeditframe64 :: * follow_progress() function. */ m_seqroll = new (std::nothrow) qseqroll ( perf(), track(), this, m_seqkeys, zoom(), m_snap, sequence::editmode::note, noteheight, height ); ui->rollScrollArea->setWidget(m_seqroll); ui->rollScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); ui->rollScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); m_seqroll->update_edit_mode(m_edit_mode); /** * New buttons to show/hide the scale or chord bars, and * to turn on/off the painting of notes not part of the * selected scale or the selected chord. */ set_show_scale_or_chords(); connect ( ui->btn_show_scale_or_chords, SIGNAL(clicked()), this, SLOT(slot_show_scale_or_chords()) ); set_filter_painted_notes(); connect ( ui->btn_filter_painted_notes, SIGNAL(clicked()), this, SLOT(slot_filter_painted_notes()) ); ui->btn_show_hex->setChecked(m_show_hex_values); qt_set_icon ( m_show_hex_values ? hex_on_xpm : hex_off_xpm, ui->btn_show_hex ); connect ( ui->btn_show_hex, SIGNAL(clicked()), this, SLOT(slot_show_hex()) ); ui->btn_level_numbers->setChecked(m_show_level_numbers); qt_set_icon ( m_show_level_numbers ? numbers_on_xpm : numbers_off_xpm, ui->btn_level_numbers ); connect ( ui->btn_level_numbers, SIGNAL(clicked()), this, SLOT(slot_show_level_numbers()) ); /* * qseqdata */ m_seqdata = new (std::nothrow) qseqdata ( perf(), track(), this, zoom(), m_snap, ui->dataScrollArea, short_version() ? 64 : 0 /* 0 means "normal height" */ ); m_seqdata->update_edit_mode(m_edit_mode); /* * dataScrollArea is now a qscrollslave object. */ ui->dataScrollArea->setWidget(m_seqdata); ui->dataScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->dataScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_seqevent = new (std::nothrow) qstriggereditor ( perf(), track(), this, zoom(), m_snap, noteheight, ui->eventScrollArea, 0 ); ui->eventScrollArea->setWidget(m_seqevent); ui->eventScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->eventScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); /* * Add the various scrollbar points to the qscrollmaster object, * ui->rollScrollArea. */ ui->rollScrollArea->add_v_scroll(ui->keysScrollArea->verticalScrollBar()); ui->rollScrollArea->add_h_scroll(ui->timeScrollArea->horizontalScrollBar()); ui->rollScrollArea->add_h_scroll(ui->dataScrollArea->horizontalScrollBar()); ui->rollScrollArea->add_h_scroll(ui->eventScrollArea->horizontalScrollBar()); /* * Attach the qscrollmaster to each qscrollslave so that they can * forward the arrow and page keys to the qscrollmaster. This avoids * having to click on the seqroll to get focus to enable the direction * keys. However, this doesn't seem to work with wheel events. See * the qscrollslave module. */ ui->dataScrollArea->attach_master(ui->rollScrollArea); ui->eventScrollArea->attach_master(ui->rollScrollArea); ui->keysScrollArea->attach_master(ui->rollScrollArea); ui->timeScrollArea->attach_master(ui->rollScrollArea); /* * Here, we want to position the notes in the piano roll so * that the user doesn't have to scroll up and down to find them. */ midipulse ts; int n; bool gotnotes = track().first_notes(ts, n); /* get average note value */ if (gotnotes) { scroll_to_tick(ts); scroll_to_note(n); } else { int minimum = ui->rollScrollArea->verticalScrollBar()->minimum(); int maximum = ui->rollScrollArea->verticalScrollBar()->maximum(); ui->rollScrollArea->verticalScrollBar()->setValue ( (minimum + maximum) / 2 ); } } /** * We need to set the dirty state while the sequence has been changed. */ void qseqeditframe64::conditional_update () { bool expandrec = track().expand_recording(); /* time to expand? */ if (expandrec) { /* * Question: how can the expansion work here without constantly * increasing the length before the end of the measures is reached??? * We have moved this operation out of the user-interface and into * sequence::stream_event() * * set_measures(track().get_measures() + 1); */ expandrec = follow_progress(expandrec); /* keep up with it */ } else if (not_nullptr(m_seqroll) && m_seqroll->progress_follow()) { follow_progress(); } /* * Can we make one function to deal with a member, the track's * value, and the combobox, and add to it to deal with external * changes to the input channel and buss and the output channel? */ int m = track().measures(); if (m_measures != m) { std::string mstring = std::to_string(m); int lenindex = measures_list().index(m); if (lenindex >= 0) { int curindex = ui->m_combo_length->currentIndex(); if (lenindex != curindex) ui->m_combo_length->setCurrentIndex(lenindex); } ui->m_combo_length->setEditText(qt(mstring)); } if (m_beats_per_bar != track().get_beats_per_bar()) { std::string mstring = std::to_string(m_beats_per_bar); int lenindex = beats_per_bar_list().index(m_beats_per_bar); m_beats_per_bar = track().get_beats_per_bar(); if (lenindex >= 0) { int curindex = ui->m_combo_bpm->currentIndex(); if (lenindex != curindex) ui->m_combo_bpm->setCurrentIndex(lenindex); } ui->m_combo_bpm->setEditText(qt(mstring)); } if (m_beat_width != track().get_beat_width()) { std::string mstring = std::to_string(m_beat_width); int lenindex = beatwidth_list().index(m_beat_width); m_beat_width = track().get_beat_width(); if (lenindex >= 0) { int curindex = ui->m_combo_bw->currentIndex(); if (lenindex != curindex) ui->m_combo_bw->setCurrentIndex(lenindex); } ui->m_combo_bw->setEditText(qt(mstring)); } if (m_is_looping != perf().looping()) { m_is_looping = perf().looping(); ui->m_button_loop->setChecked(m_is_looping); } bool armchange = m_armed_status != track().armed(); if (armchange) m_armed_status = track().armed(); if (track().check_loop_reset() || expandrec || armchange) { /* * Now we need to update the event and data panes. The notes update * during the next pass through the loop only if more notes come in on * the input buss. */ set_dirty(); /* modified for issue #90 */ update_midi_buttons(); /* mirror current states */ } } #if defined USE_WOULD_TRUNCATE_BPB_BW /* undefined */ /** * Checks if the new time-signature results in a dropping events. * However, this check is now not needed under recent fixes which increase * the number of measures if necessary, so we just return false, indicating * no truncation. * * Interesting: divide by 0 here yields the value "inf", not an exception. */ bool qseqeditframe64::would_truncate (int bpb, int bw) { bool result = false; /* no problem by default */ if (bpb > 0 && bw > 0) /* do only after start-up */ { double fraction = double(bpb) / double(bw); midipulse max = track().get_max_timestamp(); midipulse newlength = midipulse(track().get_length() * fraction); result = newlength < max; if (result) { result = ! qt_prompt_ok /* Cancel == ack the danger */ ( "This time-signature might drop events.", "Cancel to avoid that." ); } } return result; } #endif // defined USE_WOULD_TRUNCATE_BPB_BW /** * Checks if a direct change in measures would truncate events. */ bool qseqeditframe64::would_truncate (int m) { bool result = false; /* no problem by default */ if (m > 0 && m <= 999999) /* a sanity check only */ { midipulse max = track().get_max_timestamp(); midipulse newlength = midipulse(track().measures_to_ticks(m)); result = newlength < max; if (result) { result = ! qt_prompt_ok /* Cancel == ack the danger */ ( "This change will drop events.", "Cancel to avoid that." ); } } return result; } /** * Handles edits of the sequence title. * * We need to revisit sequence::set_name() in light of issue #90. */ void qseqeditframe64::update_seq_name () { std::string name = ui->m_entry_name->text().toStdString(); if (perf().set_sequence_name(track(), name)) set_track_change(); /* to solve issue #90 */ } /** * Reacts to text changes. */ void qseqeditframe64::set_log_timesig_text (int bpb, int bw) { std::string text = int_to_string(bpb); std::string bwstr = int_to_string(bw); text += "/"; text += bwstr; ui->m_button_log_timesig->setText(qt(text)); } void qseqeditframe64::set_log_timesig_status (bool flag) { midipulse tick = perf().get_tick(); /* perf().get_left_tick() */ if (tick > 0) ui->m_button_log_timesig->setEnabled(flag); else ui->m_button_log_timesig->setEnabled(false); } /** * Helper function */ bool qseqeditframe64::log_timesig (bool islogbutton) { midipulse tick = perf().get_tick(); /* perf().get_left_tick() */ midipulse tstamp; int n, d; bool found = track().detect_time_signature(tstamp, n, d, tick); if (found) { found = std::labs(tick - tstamp) < (track().snap() / 2); if (found) (void) track().delete_time_signature(tstamp); } int bpb = islogbutton ? m_beats_per_bar_to_log : m_beats_per_bar ; int bw = islogbutton ? m_beat_width_to_log : m_beat_width ; bool result = track().log_time_signature(tick, bpb, bw); if (result) { (void) track().analyze_time_signatures(); set_data_type(EVENT_META_TIME_SIGNATURE); set_log_timesig_text(bpb, bw); set_log_timesig_status(false); } return result; } /** * Given the current positions as set by clicking in the top half of the * seqtime bar [performer::get_tick() versus performer::get_left_tick()], * this function removes any existing time-signature at that point, and adds * a new one at that point. * * Note that there's some slop, 1/2 the event-snap value. Also note that this * function will not change to stored values of m_beats_per_bar and * m_beat_width. */ void qseqeditframe64::slot_log_timesig () { if (log_timesig(true)) { /* * How to get the modify flag set??? * * m_beats_per_bar = m_beats_per_bar_to_log; * m_beat_width = m_beat_width_to_log; */ set_track_change(); } } /** * The spacer-button has developer-dependent tasks, not documented. */ void qseqeditframe64::slot_show_scale_or_chords () { m_seqroll->toggle_show_scale_or_chords(); set_show_scale_or_chords(); } void qseqeditframe64::slot_filter_painted_notes () { m_seqroll->toggle_filter_painted_notes(); set_filter_painted_notes(); } void qseqeditframe64::slot_show_hex () { m_show_hex_values = ! m_show_hex_values; m_seqdata->show_hex_values(m_show_hex_values); qt_set_icon ( m_show_hex_values ? hex_on_xpm : hex_off_xpm, ui->btn_show_hex ); set_dirty(); /* refreshes data pane */ } void qseqeditframe64::slot_show_level_numbers () { m_show_level_numbers = ! m_show_level_numbers; m_seqdata->show_level_numbers(m_show_level_numbers); qt_set_icon ( m_show_level_numbers ? numbers_on_xpm : numbers_off_xpm, ui->btn_level_numbers ); set_dirty(); /* refreshes data pane */ } /** * Handles updates to the beats/measure for only the current sequences. * See the similar function in qsmainwnd. */ void qseqeditframe64::update_beats_per_bar (int index) { int bpb = beats_per_bar_list().ctoi(index); set_beats_per_bar(bpb); set_log_timesig_status(true); } void qseqeditframe64::text_beats_per_bar () { QString text = ui->m_combo_bpm->currentText(); std::string temp = text.toStdString(); if (temp.empty()) { // no code } else { int bpb = string_to_int(temp); set_beats_per_bar(bpb); set_log_timesig_status(true); } } void qseqeditframe64::reset_beats_per_bar () { int index = beats_per_bar_list().index(usr().bpb_default()); ui->m_combo_bpm->setCurrentIndex(index); set_log_timesig_status(true); } /** * Applies the new beats/bar (beats/measure) value to the sequence and the * user interface. * * \param bpb * The desired beats/measure value. * * \param qs * Set to start or edit. Basically, the same as user_change == * false or true. */ void qseqeditframe64::set_beats_per_bar (int bpb, qbase::status qs) { if (usr().bpb_is_valid(bpb)) { bool loggable = perf().get_tick() > track().snap() / 2; bool doable = loggable ? (bpb != m_beats_per_bar_to_log) : (bpb != m_beats_per_bar) ; if (doable) { bool user_change = qs == qbase::status::edit; if (loggable) { m_beats_per_bar_to_log = bpb; set_log_timesig_text ( m_beats_per_bar_to_log, m_beat_width_to_log ); } else /* get_left_tick() */ { m_beats_per_bar = m_beats_per_bar_to_log = bpb; track().set_beats_per_bar(bpb, user_change); (void) track().apply_length(bpb, 0, 0); /* no measures */ if (log_timesig(false)) set_track_change(); } } } } /** * Set the measures value, using the given parameter, and some internal * values passed to apply_length(). * * \param m * Provides the sequence length, in measures. */ void qseqeditframe64::set_measures (int m, qbase::status qs) { if (m > 0 && m <= 999999) /* a sanity check only */ { bool reset = false; if (qs == qbase::status::edit) { if (! perf().is_pattern_playing()) /* ca 2022-08-20 */ reset = would_truncate(m); } if (reset) { /* * reset_measures(); simply ignore? No, reset to previous value. */ QString text = qt(std::to_string(m_measures)); ui->m_combo_length->lineEdit()->setText(text); } else if (qs == qbase::status::undo) { // do nothing } else { if (track().apply_length(m, true)) /* always a user change */ { m_measures = m; track().remove_orphaned_events(); set_track_change(); /* to solve issue #90 */ } } } } /** * Handles updates to the beat width for only the current sequences. * See the similar function in qsmainwnd. */ void qseqeditframe64::update_beat_width (int index) { int bw = beatwidth_list().ctoi(index); set_beat_width(bw); set_log_timesig_status(true); } void qseqeditframe64::text_beat_width () { QString text = ui->m_combo_bw->currentText(); std::string temp = text.toStdString(); if (temp.empty()) { // no code } else { int bw = string_to_int(temp); set_beat_width(bw); set_log_timesig_status(true); } } /** * Resets the beat-width combo-box to its default value. */ void qseqeditframe64::reset_beat_width () { int index = beatwidth_list().index(usr().bw_default()); ui->m_combo_bw->setCurrentIndex(index); set_log_timesig_text(m_beats_per_bar, m_beat_width); set_log_timesig_status(true); } /** * Sets the beat-width value and then dirties the user-interface so that it * will be repainted. usr().bw_is_valid() is only a gross range check. */ void qseqeditframe64::set_beat_width (int bw, qbase::status qs) { if (usr().bw_is_valid(bw)) { bool loggable = perf().get_tick() > track().snap() / 2; bool doable = loggable ? (bw != m_beat_width_to_log) : (bw != m_beat_width) ; if (doable) { /* * If not a power of 2, then just set for a c_timesig SeqSpec. */ bool user_change = qs == qbase::status::edit; bool rational = is_power_of_2(bw); if (rational) /* use OK'ed it */ { if (loggable) { m_beat_width_to_log = bw; set_log_timesig_text ( m_beats_per_bar_to_log, m_beat_width_to_log ); } else /* get_left_tick() */ { m_beat_width = m_beat_width_to_log = bw; track().set_beat_width(bw, user_change); (void) track().apply_length(0, 0, bw); if (log_timesig(false)) set_track_change(); } } else { bool allow_odd_beat_width = qt_prompt_ok ( "MIDI supports only powers of 2 for beat-width.", "Thus, saved as a global Seq66-specific MIDI event, " "not a time-signature event. " "Overriden by existing time-signature events." ); if (allow_odd_beat_width) { track().set_beat_width(bw, user_change); (void) track().apply_length(0, 0, bw); } } } } } /** * This code is meant to change beats/bar and beat-width at the same * time, to avoid adding two time-signature events. * * In addition, it sets the data type to Note Ons. This is meant to * work with the fix-pattern time-signature operation. */ void qseqeditframe64::set_bpb_and_bw (int bpb, int bw, qbase::status qs) { if (usr().bpb_is_valid(bpb) && usr().bw_is_valid(bw)) { bool loggable = perf().get_tick() > track().snap() / 2; bool doable; if (loggable) { doable = (bw != m_beat_width_to_log) || (bpb != m_beats_per_bar_to_log); } else { doable = (bpb != m_beats_per_bar) || (bw != m_beat_width); } if (doable) { /* * If not a power of 2, then just set for a c_timesig SeqSpec. */ bool user_change = qs == qbase::status::edit; bool rational = is_power_of_2(bw); if (rational) /* use OK'ed it */ { if (loggable) { m_beats_per_bar_to_log = bpb; m_beat_width_to_log = bw; set_log_timesig_text ( m_beats_per_bar_to_log, m_beat_width_to_log ); } else /* get_left_tick() */ { m_beats_per_bar = m_beats_per_bar_to_log = bpb; m_beat_width = m_beat_width_to_log = bw; track().set_beats_per_bar(bpb, user_change); track().set_beat_width(bw, user_change); (void) track().apply_length(bpb, 0, bw); int bpbindex = beats_per_bar_list().index(bpb); int bwindex = beatwidth_list().index(bw); ui->m_combo_bpm->setCurrentIndex(bpbindex); ui->m_combo_bw->setCurrentIndex(bwindex); if (log_timesig(false)) set_track_change(); } set_data_type(EVENT_NOTE_ON); } } } } /** * This function looks for a time-signature, and if it is within the first * half-snap interval of the start of of the pattern, sets the beats value * accordingly. * * Time signature life-cycle: * * -# Set seqedit time-signature to the global default (usually 4/4). * -# If an initial time-signature is detected, set it here. * -# If the numerator or denominator is changed in the GUI, and * there is a time-signature at the beginning (tick == 0), then * replace it. * -# Otherwise, require the "L" marker to be set and then log the * time-signature at that point. See slot_log_timesig(). */ bool qseqeditframe64::detect_time_signature () { midipulse tstamp; int n, d; bool result = track().detect_time_signature ( tstamp, n, d, 0, track().snap() / 2 ); (void) track().analyze_time_signatures(); /* log some or none */ if (result) { set_beats_per_bar(n, qbase::status::startup); set_beat_width(d, qbase::status::startup); } return result; } #if defined USE_LEGACY_MEASURES_ADJUSTMENT void qseqeditframe64::text_measures (const QString & text) { std::string temp = text.toStdString(); if (! temp.empty()) { int measures = string_to_int(temp); set_measures(measures); } } #else void qseqeditframe64::text_measures_edit () { QString text = ui->m_combo_length->currentText(); std::string temp = text.toStdString(); if (! temp.empty()) { int measures = string_to_int(temp); if (measures != m_measures) set_measures(measures); } } void qseqeditframe64::update_measures (int index) { int measures = measures_list().ctoi(index); set_measures(measures); } #endif /** * Resets the pattern-length to 1 in its combo-box. */ void qseqeditframe64::reset_measures () { int index = 0; /* beatwidth_list().index(m_measures) */ m_measures = 1; ui->m_combo_length->setCurrentIndex(index); set_measures(m_measures); } /** * Passes the transpose status to the sequence object. */ void qseqeditframe64::transpose (bool ischecked) { track().set_transposable(ischecked, true); /* it's a user change */ set_transpose_image(ischecked); ui->m_map_notes->setEnabled(ischecked); } /** * Changes the image used for the transpose button. Actually, we have two * drum icons next to each other now, so we won't change this one to a drum. * * qt_set_icon(transpose_xpm, ui->m_toggle_transpose); * qt_set_icon(drum_xpm, ui->m_toggle_transpose); * * \param istransposable * Indicates what state we're in. */ void qseqeditframe64::set_transpose_image (bool istransposable) { QString text = istransposable ? "Pattern is transposable" : "Pattern not transposable" ; ui->m_toggle_transpose->setToolTip(text); } /** * Handles updates to the beats/measure for only the current sequences. * See the similar function in qsmainwnd. */ void qseqeditframe64::update_chord (int index) { if (index != m_chord && chord_number_valid(index)) { set_chord(index); set_dirty(); /* modified for issue #90 */ } } /** * Resets the chord-setting to the initial value in the chord combo-box. */ void qseqeditframe64::reset_chord () { ui->m_combo_chord->setCurrentIndex(0); if (not_nullptr(m_seqroll)) m_seqroll->set_chord(0); set_dirty(); /* modified for issue #90 */ } void qseqeditframe64::set_chord (int chord, qbase::status qs) { if (chord_number_valid(chord)) { ui->m_combo_chord->setCurrentIndex(chord); m_chord = sm_initial_chord = chord; if (not_nullptr(m_seqroll)) m_seqroll->set_chord(chord); bool user_change = qs == qbase::status::edit; track().musical_chord(midibyte(chord), user_change); set_track_change(); } } /** * Sets the MIDI bus to use for output. * * \param index * The index into the list of available MIDI buses. */ void qseqeditframe64::update_midi_bus (int index) { set_midi_bus(index); } /** * Resets the MIDI bus value to its default. */ void qseqeditframe64::reset_midi_bus () { set_midi_bus(0, qbase::status::startup); /* indirect user-change */ } /** * Selects the given MIDI buss parameter in the main sequence object, * so that it will use that buss. Currently called only in the constructor. * Otherwise, see the update_midi_bus() function. * * Should this change set the is-modified flag? Where should validation * against the ALSA or JACK buss limits occur? * * Also, it would be nice to be able to update this display of the MIDI bus * in the field if we set it from the seqmenu. * * \param bus * The nominal buss value to set. If this value changes the selected * buss, then the MIDI channel popup menu is repopulated. * * \param qs * Indicates if the changes was made at startup, or by a user-edit. Set * to qbase::status::startup in the former case, and qbase::status::edit * in the latter case. If the user made this change, they've potentially * modified the song. If the bus number has changed, then the MIDI * channel and event menus are repopulated to reflect the new bus. This * parameter is "startup" in the constructor because those items have not * been set up at that time. The default value is "edit". */ void qseqeditframe64::set_midi_bus (int bus, qbase::status qs) { bussbyte initialbus = track().seq_midi_bus(); bussbyte b = bussbyte(bus); if (b != initialbus) { bool user_change = qs == qbase::status::edit; track().set_midi_bus(b, user_change); m_edit_bus = bus; if (user_change) { repopulate_usr_combos(m_edit_bus, m_edit_channel); /* bug-fix! */ set_track_change(); /* to solve issue #90 */ } else ui->m_combo_bus->setCurrentIndex(bus); } } /** * Populates the MIDI Channel combo-box with the number and names of each * channel. This action is needed at startup of the seqedit window, and when * the user changes the active buss for the sequence. * * When the output buss or channel are changed, we get the 16 "channels" from * the new buss's definition, get the corresponding instrument, and load its * name into this midich popup. Then we need to go to the instrument/channel * that has been selected, and repopulate the event menu with that item's * controller values/names. * * \param buss * The new value for the buss from which to get the [user-instrument-N] * settings in the [user-instrument-definitions] section. */ void qseqeditframe64::repopulate_midich_combo (int buss) { int ch { int(track().seq_midi_channel()) }; /* track().midi_channel() */ if (populate_midich_combo(ui->m_combo_channel, buss, ch)) { /* * if (is_null_channel(ch)) * ch = c_midichannel_max; * * ui->m_combo_channel->setCurrentIndex(ch); */ } } /** * Note that c_midichannel_max (16) is a legal value. It is remapped in * sequence::set_midi_channel() to null_channel(). */ void qseqeditframe64::update_midi_channel (int index) { set_midi_channel(index); } void qseqeditframe64::reset_midi_channel () { set_midi_channel(0); } /** * Selects the given MIDI channel parameter in the main sequence object, so * that it will use that channel. If "Free" is selected, all that happens is * that the pattern is set to "no-channel" mode for MIDI output. * * \param ch * The MIDI channel value to set. * * \param user_change * True if the user made this change, and thus has potentially modified * the song. The default is false. * \param qs * Indicates if the changes was made at startup, or by a user-edit. Set * to qbase::status::startup in the former case, and qbase::status::edit * in the latter case. If the user made this change, they've potentially * modified the song. If the bus number has changed, then the MIDI * channel and event menus are repopulated to reflect the new bus. This * parameter is "startup" in the constructor because those items have not * been set up at that time. The default value is "edit". */ void qseqeditframe64::set_midi_channel (int ch, qbase::status qs) { int initialchan = track().seq_midi_channel(); bool user_change = qs == qbase::status::edit; if (ch != initialchan || ! user_change) { int chindex = ch; midibyte channel = midibyte(ch); if (! is_good_channel(channel)) { chindex = c_midichannel_max; /* "Free" */ channel = null_channel(); } if (track().set_midi_channel(channel, user_change)) { m_edit_channel = channel; if (is_null_channel(channel)) { ui->m_combo_channel->setCurrentIndex(chindex); } else { if (user_change) set_track_change(); /* to solve issue #90 */ else ui->m_combo_channel->setCurrentIndex(chindex); } } } } /** * Does a pop-undo on the sequence object and then sets the dirty flag. */ void qseqeditframe64::undo () { track().pop_undo(); set_dirty(); /* for issue #110 */ } /** * Does a pop-redo on the sequence object and then sets the dirty flag. * We can't reliably call this a track change, however. */ void qseqeditframe64::redo () { track().pop_redo(); set_dirty(); } /** * Popup menu over button. */ void qseqeditframe64::tools () { if (not_nullptr(m_tools_popup)) { enable_note_menus(); m_tools_popup->exec ( ui->m_button_tools->mapToGlobal ( QPoint(ui->m_button_tools->width() - 2, ui->m_button_tools->height() - 2) ) ); } } void qseqeditframe64::insert_macro (const std::string & macroname) { const midimacro & macro = perf().get_macro(macroname); midipulse tstamp = track().get_last_tick(); bool ok = track().add_macro(tstamp, macro); if (ok) { msgprintf ( msglevel::info, "Macro '%s' inserted at %ld", macroname.c_str(), long(tstamp) ); } else error_message("Could not insert macro '%s'", macroname); } /** * Builds the Tools popup menu on the fly. */ void qseqeditframe64::popup_tool_menu () { m_tools_popup = new_qmenu("", this); if (not_nullptr(m_tools_popup)) { /* * Shall we look up macros by name or number? What's best for these * QActions? We can get the action text via text(). * * ca 2025-07-25 * Since we're not sending macros here, it does not matter * if they're active via the 'ctrl' file. As long as macros * exist, they can be used here. * * bool macrosactive = perf().macros_active(); */ QMenu * menumacros = nullptr; tokenization names = perf().macro_names(); bool macrosactive = ! names.empty(); if (macrosactive) { menumacros = new_qmenu("&Insert macro at \"L\"", m_tools_popup); if (not_nullptr(menumacros)) { for (const auto & name : names) { QAction * tmp = new_qaction(name, m_tools_popup); midibytes mbytes = perf().macro_bytes(name); bool enabled = ! mbytes.empty(); if (enabled) enabled = name != "header" && name != "footer"; tmp->setEnabled(enabled); menumacros->addAction(tmp); } connect ( menumacros, &QMenu::triggered, [this] (QAction * qact) { insert_macro(qact->text().toStdString()); } ); } else macrosactive = false; } QMenu * menuselect = new_qmenu("&Select notes...", m_tools_popup); QMenu * menutiming = new_qmenu ( "Note &timing/velocity...", m_tools_popup ); QMenu * menupitch = new_qmenu("&Pitch transpose...", m_tools_popup); QMenu * menuharmonic = new_qmenu ( "&Harmonic transpose...", m_tools_popup ); #if defined USE_MORE_TOOLS /* * Can enable this if we get much more than the 2 current extra tools. */ QMenu * menumore = new_qmenu("&More tools...", m_tools_popup); #endif QAction * selectall = new_qaction("Select &all", m_tools_popup); connect ( selectall, SIGNAL(triggered(bool)), this, SLOT(select_all_notes()) ); menuselect->addAction(selectall); QAction * selectinverse = new_qaction ( "&Invert selection", m_tools_popup ); connect ( selectinverse, SIGNAL(triggered(bool)), this, SLOT(inverse_note_selection()) ); menuselect->addAction(selectinverse); QAction * selectpitches = new_qaction ( "Select &pitch range...", m_tools_popup ); connect ( selectpitches, SIGNAL(triggered(bool)), this, SLOT(note_pitch_selection()) ); menuselect->addAction(selectpitches); QAction * quantize = new_qaction("&Quantize", m_tools_popup); connect ( quantize, SIGNAL(triggered(bool)), this, SLOT(quantize_notes()) ); menutiming->addAction(quantize); QAction * tighten = new_qaction("&Tighten", m_tools_popup); connect ( tighten, SIGNAL(triggered(bool)), this, SLOT(tighten_notes()) ); menutiming->addAction(tighten); QAction * jitter = new_qaction("&Jitter", m_tools_popup); connect(jitter, SIGNAL(triggered(bool)), this, SLOT(jitter_notes())); menutiming->addAction(jitter); QAction * rando = new_qaction("&Randomize velocity", m_tools_popup); connect ( rando, SIGNAL(triggered(bool)), this, SLOT(randomize_note_velocities()) ); menutiming->addAction(rando); QAction * lfobox = new_qaction("&LFO...", m_tools_popup); connect ( lfobox, SIGNAL(triggered(bool)), this, SLOT(show_lfo_frame()) ); QAction * fixbox = new_qaction("Pattern &fix...", m_tools_popup); connect ( fixbox, SIGNAL(triggered(bool)), this, SLOT(show_pattern_fix()) ); QAction * transpose[2 * c_octave_size]; /* pitch transpose */ QAction * harmonic[2 * c_harmonic_size]; /* harmonic transpose */ for (int t = -c_octave_size; t <= c_octave_size; ++t) { if (t != 0) { /* * Pitch transpose menu entries. Note the interval_name_ptr() and * harmonic_interval_name_ptr() functions from the scales module. */ char num[32]; int index = t + c_octave_size; snprintf ( num, sizeof num, "%+d [%s]", t, interval_name_ptr(t) ); transpose[index] = new_qaction(num, m_tools_popup); transpose[index]->setData(t); menupitch->addAction(transpose[index]); connect ( transpose[index], SIGNAL(triggered(bool)), this, SLOT(transpose_notes()) ); if (harmonic_number_valid(t)) { /* * Harmonic transpose menu entries. */ int tn = t < 0 ? (t - 1) : (t + 1); index = t + c_harmonic_size; snprintf ( num, sizeof num, "%+d [%s]", tn, harmonic_interval_name_ptr(t) ); harmonic[index] = new_qaction(num, m_tools_popup); harmonic[index]->setData(t); menuharmonic->addAction(harmonic[index]); connect ( harmonic[index], SIGNAL(triggered(bool)), this, SLOT(transpose_harmonic()) ); } } else { menupitch->addSeparator(); menuharmonic->addSeparator(); } } if (macrosactive) m_tools_popup->addMenu(menumacros); m_tools_popup->addMenu(menuselect); m_tools_popup->addMenu(menutiming); m_tools_popup->addMenu(menupitch); m_tools_popup->addMenu(menuharmonic); #if defined USE_MORE_TOOLS m_tools_popup->addMenu(menumore); #else m_tools_popup->addAction(lfobox); m_tools_popup->addAction(fixbox); #endif m_tools_harmonic = menuharmonic; m_tools_timing = menutiming; m_tools_pitch = menupitch; enable_note_menus(); } } void qseqeditframe64::enable_note_menus () { bool gotselection = track().any_selected_notes(); if (not_nullptr(m_tools_harmonic)) m_tools_harmonic->setEnabled(gotselection && m_scale > 0); if (not_nullptr(m_tools_pitch)) m_tools_pitch->setEnabled(gotselection); if (not_nullptr(m_tools_timing)) m_tools_timing->setEnabled(gotselection); } /** * Aftertouch events are associated with notes. */ void qseqeditframe64::select_all_notes () { (void) track().select_events(EVENT_NOTE_ON, 0); (void) track().select_events(EVENT_NOTE_OFF, 0); (void) track().select_events(EVENT_AFTERTOUCH, 0); enable_note_menus(); set_dirty(); } /** * Aftertouch events are associated with notes. */ void qseqeditframe64::inverse_note_selection () { (void) track().select_events(EVENT_NOTE_ON, 0, true); (void) track().select_events(EVENT_NOTE_OFF, 0, true); (void) track().select_events(EVENT_AFTERTOUCH, 0, true); enable_note_menus(); set_dirty(); } /** * Select notes via a range of pitches. */ void qseqeditframe64::note_pitch_selection () { std::string range { qt_get_string ( this, "Enter Range", "Low-High Pitches", "0-127 or C4-C6" ) }; if (! range.empty()) { tokenization numbers { tokenize(range, " -") }; #if defined USE_OLD_CODE int lowest { string_to_int(numbers[0]) }; int highest { 127 }; if (numbers.size() > 1) highest = string_to_int(numbers[1]); bool ok = track().select_notes_by_pitch(highest, lowest) > 0; #else int lowest, highest; bool ok = get_pitch_range(numbers, lowest, highest); if (ok) ok = track().select_notes_by_pitch(highest, lowest) > 0; #endif if (ok) { enable_note_menus(); set_dirty(); } } } /** * This function acts on Notes On, Notes Off, and Aftertouch events. */ void qseqeditframe64::quantize_notes () { track().push_quantize_notes(1); } /** * This function acts on Notes On, Notes Off, and Aftertouch events. */ void qseqeditframe64::tighten_notes () { track().push_quantize_notes(2); } /** * Jitter the selected notes, preceded by pushing the current events * onto the undo stack. * * Includes Aftertouch events. */ void qseqeditframe64::jitter_notes () { int j = usr().jitter_range(m_seqroll->snap()); /* a calculation */ track().push_jitter_notes(j); } void qseqeditframe64::randomize_note_velocities () { int r = usr().randomization_amount(); track().randomize_note_velocities(r); } /** * Consider adding Aftertouch events. Done in sequence::transpose_notes(). * Also note that this function does a push-undo operation first. */ void qseqeditframe64::transpose_notes () { QAction * senderAction = (QAction *) sender(); int transposeval = senderAction->data().toInt(); track().transpose_notes(transposeval, 0); } void qseqeditframe64::transpose_harmonic () { QAction * senderAction = (QAction *) sender(); int transposeval = senderAction->data().toInt(); track().transpose_notes(transposeval, m_scale, m_key); } /** * Here, we need to get the filespec, create a notemapper, fill it from the * notemapfile, and iterate through the notes, converting them. */ void qseqeditframe64::remap_notes () { if (repitch_all()) ui->m_map_notes->setEnabled(false); } /** * Popup menu sequences button. */ void qseqeditframe64::sequences () { if (not_nullptr(m_sequences_popup)) { int w = ui->m_button_sequence->width() - 2; int h = ui->m_button_sequence->height() - 2; m_sequences_popup->exec ( ui->m_button_sequence->mapToGlobal(QPoint(w, h)) ); } } /** * A case where a macro makes the code easier to read. The first parameter is * the sequence number, and the second indicates that the user made the * setting. */ #define SET_BG_SEQ(seq, change) \ std::bind(&qseqeditframe64::set_background_sequence, this, seq, change) /** * Builds the menu for the Background Sequences button on the fly. Analogous * to seqedit :: popup_sequence_menu(). */ void qseqeditframe64::popup_sequence_menu () { if (is_nullptr(m_sequences_popup)) { m_sequences_popup = new_qmenu("", this); } QAction * off = new_qaction("Off", m_sequences_popup); connect ( off, &QAction::triggered, SET_BG_SEQ(seq::limit(), qbase::status::edit) ); (void) m_sequences_popup->addAction(off); (void) m_sequences_popup->addSeparator(); int seqsinset = perf().screenset_size(); int maxset = perf().screenset_max(); for (int sset = 0; sset < maxset; ++sset) { QMenu * menusset = nullptr; if (perf().is_screenset_active(sset)) { char number[16]; snprintf(number, sizeof number, "Set %d", sset); menusset = m_sequences_popup->addMenu(number); for (int seq = 0; seq < seqsinset; ++seq) { char name[32]; int s = sset * seqsinset + seq; seq::pointer sp = perf().get_sequence(s); if (not_nullptr(sp)) { const char * nameptr = sp->name().c_str(); snprintf(name, sizeof name, "[%d] %.13s", s, nameptr); QAction * item = new_qaction(name, menusset); menusset->addAction(item); connect ( item, &QAction::triggered, SET_BG_SEQ(s, qbase::status::edit) ); } } } } } /** * Pass the status of showing note tooltips to the seqroll object. * * If the --investigate option is present, though, we will do alternate * processing for testing/trouble-shooting. For example, we find that if * we try to test a combo-box menu, when the breakpoint is hit, that disables * the rest of the desktop, and we cannot step through the debugger. */ void qseqeditframe64::tooltip_mode (bool ischecked) { if (not_nullptr(m_seqroll)) m_seqroll->set_tooltip_mode(ischecked); } /** * Passes the transpose status to the seqroll object. */ void qseqeditframe64::note_entry (bool ischecked) { if (not_nullptr(m_seqroll)) m_seqroll->set_adding(ischecked); if (not_nullptr(m_seqevent)) m_seqevent->set_adding(ischecked); /* re issue #136 */ } void qseqeditframe64::update_note_entry (bool on) { ui->m_button_note_entry->setChecked(on); } /** * Sets the given background sequence for the Pattern editor so that the * musician has something to see that can be played against. As a new * feature, it is also passed to the sequence, so that it can be saved as * part of the sequence data, but only if less or equal to the maximum * single-byte MIDI value, 127. * * Note that the "initial value" for this parameter is a static variable that * gets set to the new value, so that opening up another sequence causes the * sequence to take on the new "initial value" as well. A feature, but * should it be optional? Now it is, based on the setting of * usr().global_seq_feature(). */ void qseqeditframe64::set_background_sequence (int seqnum, qbase::status qs) { if (! seq::legal(seqnum)) /* includes seq::limit() */ return; seq::pointer s = perf().get_sequence(seqnum); if (not_nullptr(s)) { if (seqnum < usr().max_sequence()) /* even more restrictive */ { if (seq::disabled(seqnum) || ! perf().is_seq_active(seqnum)) { ui->m_entry_sequence->setText("Off"); } else { char n[32]; snprintf(n, sizeof n, "[%d] %.13s", seqnum, s->name().c_str()); m_bgsequence = seqnum; ui->m_entry_sequence->setText(n); if (usr().global_seq_feature()) usr().seqedit_bgsequence(seqnum); bool user_change = qs == qbase::status::edit; if (track().background_sequence(seqnum, user_change)) { if (not_nullptr(m_seqroll)) m_seqroll->set_background_sequence(true, seqnum); set_track_change(); /* to solve issue #90 */ } } } } else { if (seqnum == seq::limit()) { ui->m_entry_sequence->setText("Off"); if (usr().global_seq_feature()) usr().seqedit_bgsequence(seqnum); bool user_change = qs == qbase::status::edit; if (track().background_sequence(seqnum, user_change)) { if (not_nullptr(m_seqroll)) m_seqroll->set_background_sequence(true, seqnum); set_track_change(); /* to solve issue #90 */ } } else msgprintf(msglevel::error, "null pattern %d", seqnum); } } /** * Sets the data type based on the given parameters. This function uses the * controller_name() function defined in the controllers.cpp module. * * See editable_event.cpp's s_channel_event_names[] for the list of names. * We should centralizze them at some point. * * \param status * The current editing status. * * \param control * The control value. Validated in the controller_name() function. */ void qseqeditframe64::set_data_type (midibyte status, midibyte control) { if (event::is_tempo_status(status)) { m_seqevent->set_data_type(status, control); m_seqdata->set_data_type(status, control); ui->m_entry_data->setText("Tempo"); // s_event_items[i].epp_name } else if (event::is_time_signature_status(status)) { m_seqevent->set_data_type(status, control); m_seqdata->set_data_type(status, control); ui->m_entry_data->setText("Time Signature"); // s_event_items[i].epp_name } else if (event::is_meta_text_msg(status)) { m_seqevent->set_data_type(status, control); m_seqdata->set_data_type(status, control); ui->m_entry_data->setText("Text"); // s_event_items[i].epp_name } else { char hexa[8]; char type[32]; snprintf(hexa, sizeof hexa, "[0x%02X]", status); status = event::normalized_status(status); m_seqevent->set_data_type(status, control); /* qstriggereditor */ m_seqdata->set_data_type(status, control); if (status == EVENT_NOTE_OFF) snprintf(type, sizeof type, "Note Off"); else if (status == EVENT_NOTE_ON) snprintf(type, sizeof type, "Note On"); else if (status == EVENT_AFTERTOUCH) snprintf(type, sizeof type, "Aftertouch"); else if (status == EVENT_CONTROL_CHANGE) { int bus = int(track().seq_midi_bus()); int channel = int(track().seq_midi_channel()); std::string ccname = usr().controller_active(bus, channel, control) ? usr().controller_name(bus, channel, control) : controller_name(control) ; snprintf(type, sizeof type, "CC%s", ccname.c_str()); } else if (status == EVENT_PROGRAM_CHANGE) snprintf(type, sizeof type, "Program"); else if (status == EVENT_CHANNEL_PRESSURE) snprintf(type, sizeof type, "Ch Pressure"); else if (status == EVENT_PITCH_WHEEL) snprintf(type, sizeof type, "Pitch Wheel"); else if (event::is_meta_status(status)) snprintf(type, sizeof type, "Meta Event"); else snprintf(type, sizeof type, "Unknown"); char text[80]; snprintf(text, sizeof text, "%s %s", hexa, type); ui->m_entry_data->setText(text); } } /** * Follow-progress callback. Passes the Follow status to the qseqroll object. */ void qseqeditframe64::slot_follow (bool ischecked) { if (not_nullptr(m_seqroll)) m_seqroll->progress_follow(ischecked); } /** * Checks the position of the tick, and, if it is in a different piano-roll * "page" than the last page, moves the page to the next page. * * We don't want to do any of this if the length of the sequence fits in the * window, but for now it doesn't hurt; the progress bar just never meets the * criterion for moving to the next page. * * \todo * - If playback is disabled (such as by a trigger), then do not update * the page; * - When it comes back, make sure we're on the correct page; * - When it stops, put the window back to the beginning, even if the * beginning is not defined as "0". * * int w = m_seqroll->window_width(): 781, what we set. * int w = m_seqroll->width(): Dependent on seq length. * * \param expand * If true (the default is false), then add a measure to the pattern * while recording new notes. */ bool qseqeditframe64::follow_progress (bool expand) { bool result = false; if (track().expanded_recording()) { result = m_seqroll->follow_progress(ui->rollScrollArea, expand); if (result && expand) qt_set_icon(length_red_xpm, ui->m_button_length); } else result = m_seqroll->follow_progress(ui->rollScrollArea); return result; } void qseqeditframe64::scroll_to_tick (midipulse tick) { int w = ui->rollScrollArea->width(); if (w > 0) /* w is constant */ { float fraction = float(tick) / float(track().get_length()); ui->rollScrollArea->scroll_x_to_factor(fraction); } } /** * How can we add a little bit to move the note value more to the middle of * the piano roll, rather than to the top? The easiest approach is to * add an octave to the note, which moves the seqroll window down by about * half at normal zoom. * * \param note * Either a single note or the average of note values (e.g. from a * chord) within the first snap distance of the first note. */ void qseqeditframe64::scroll_to_note (int note) { int h = ui->rollScrollArea->height(); if (h > 0) /* h is constant */ { if (is_good_data_byte(midibyte(note))) { float fraction = (127 - (note + 12)) / 128.0F; ui->rollScrollArea->scroll_y_to_factor(fraction); } } } /** * Updates the grid-snap values and control based on the index. The value is * passed to the set_snap() function for final setting. * * \param index * Provides the index selected from the combo-box. */ void qseqeditframe64::update_grid_snap (int index) { if (index >= 0 && index < snap_list().count()) { int qnfactor = perf().ppqn() * 4; int item = snap_list().ctoi(index); int v = qnfactor / item; set_grid_snap(v); /* set_snap(v) */ set_snap(v); /* pass along to sub-panes */ } } /** * Selects the given snap value, which is the number of ticks in a snap-sized * interval. It is passed to the seqroll, seqevent, and sequence objects, as * well. * * The default initial snap is the default PPQN divided by 4, or the * equivalent of a 16th note (48 ticks). The snap divisor is 192 * 4 / 48 or * 16. * * \param s * The prospective snap value to set. It is checked to make * sure it is greater than 0, to avoid a numeric exception. */ void qseqeditframe64::set_snap (midipulse s) { if (s > 0 && s != m_snap) { m_snap = int(s); track().snap(s); /* set it for pattern */ if (not_nullptr(m_seqroll)) m_seqroll->set_snap(s); if (not_nullptr(m_seqevent)) m_seqevent->set_snap(s); /* qstriggereditor */ if (not_nullptr(m_seqtime)) m_seqtime->set_snap(s); } } void qseqeditframe64::reset_grid_snap () { ui->m_combo_snap->setCurrentIndex(4); set_dirty(); /* modified for issue #90 */ } /** * Updates the note-length values and control based on the index. It is * passed to the set_note_length() function for processing. * * \param index * Provides the index selected from the combo-box. */ void qseqeditframe64::update_note_length (int index) { if (index >= 0 && index < snap_list().count()) { int qnfactor = perf().ppqn() * 4; int item = snap_list().ctoi(index); int v = qnfactor / item; set_note_length(v); } } /** * Selects the given note-length value. * * \warning * Currently, we don't handle changes in the global PPQN after the * creation of the menu. The creation of the menu hard-wires the values * of note-length. To adjust for a new global PQN, we will need to store * the original PPQN (m_original_ppqn = m_ppqn), and then adjust the * notelength based on the new PPQN. For example if the new PPQN is * twice as high as 192, then the notelength should double, though the * text displayed in the "Note length" field should remain the same. * However, we do adjust for a non-default PPQN at startup time. * * \param notelength * Provides the note length in units of MIDI pulses. */ void qseqeditframe64::set_note_length (int notelength) { #if defined CAN_MODIFY_GLOBAL_PPQN if (perf().ppqn() != m_original_ppqn) { double factor = double(perf().ppqn()) / double(m_original_ppqn); notelength = int(notelength * factor + 0.5); m_original_ppqn = perf().ppqn(); } #endif m_note_length = notelength; sm_initial_note_length = notelength; track().step_edit_note_length(notelength); /* set it for pattern */ if (not_nullptr(m_seqroll)) m_seqroll->set_note_length(notelength); } void qseqeditframe64::reset_note_length () { ui->m_combo_note->setCurrentIndex(4); set_dirty(); /* modified for issue #90 */ } bool qseqeditframe64::on_resolution_change ( int ppqn, midibpm bpm, performer::change /*ch*/ ) { bool result = change_ppqn(ppqn); if (result) { if (rc().verbose()) msgprintf(msglevel::info, "PPQN = %d; BPM = %d", ppqn, int(bpm)); } return result; } bool qseqeditframe64::change_ppqn (int ppqn) { int zoom = usr().base_zoom(); midipulse scaledtick = rescale_tick ( sm_initial_snap, ppqn, usr().base_ppqn() ); set_snap(scaledtick); set_note_length(scaledtick); if (usr().adapt_zoom()) zoom = zoom_power_of_2(ppqn); set_zoom(zoom); return true; } /** * Updates the grid-zoom values and control based on the index. The value is * passed to the set_zoom() function for processing. * * \param index * Provides the index selected from the combo-box. */ void qseqeditframe64::slot_update_zoom (int index) { int z = zoom_list().ctoi(index); (void) set_zoom(z); set_dirty(); /* modified for issue #90 */ } void qseqeditframe64::adjust_for_zoom (int zprevious) { int znew = zoom(); float factor = float(zprevious) / float(znew); int index = zoom_list().index(znew); ui->m_combo_zoom->setCurrentIndex(index); if (expanded_zoom()) { factor = zoom_expansion(); ui->rollScrollArea->scroll_x_by_step(qscrollmaster::dir::right); } ui->rollScrollArea->scroll_x_by_factor(factor); set_dirty(); } bool qseqeditframe64::zoom_in () { int zprevious = qseqframe::zoom(); bool result = qseqframe::zoom_in(); if (result) adjust_for_zoom(zprevious); return result; } bool qseqeditframe64::zoom_out () { int zprevious = qseqframe::zoom(); bool result = qseqframe::zoom_out(); if (result) adjust_for_zoom(zprevious); return result; } bool qseqeditframe64::set_zoom (int z) { int zprevious = qseqframe::zoom(); bool result = qseqframe::set_zoom(z); if (result) adjust_for_zoom(zprevious); return result; } bool qseqeditframe64::reset_zoom (int ppq) { int zprevious = qseqframe::zoom(); bool result = qseqframe::reset_zoom(ppq); if (result) adjust_for_zoom(zprevious); return result; } void qseqeditframe64::v_zoom_in () { m_seqroll->v_zoom_in(); } void qseqeditframe64::v_zoom_out () { m_seqroll->v_zoom_out(); } void qseqeditframe64::reset_v_zoom () { m_seqroll->reset_v_zoom(); } /** * This override just reset the current index of the zoom combo-box. * That then triggers the callback that sets the current index. */ void qseqeditframe64::slot_reset_zoom () { (void) reset_zoom(); /* TODO? */ } /** * Handles updates to the key selection. */ void qseqeditframe64::update_key (int index) { if (index != m_key && legal_key(index)) set_key(index); } void qseqeditframe64::set_key (int key, qbase::status qs) { if (legal_key(key)) { m_key = key; ui->m_combo_key->setCurrentIndex(key); if (not_nullptr(m_seqroll)) m_seqroll->set_key(key); if (not_nullptr(m_seqkeys)) m_seqkeys->set_key(key); if (usr().global_seq_feature()) usr().seqedit_key(key); bool user_change = qs == qbase::status::edit; track().musical_key(midibyte(key), user_change); set_track_change(); /* to solve issue #90 */ } else errprint("null pattern"); } void qseqeditframe64::reset_key () { set_key(c_key_of_C); } /** * Handles updates to the scale selection. */ void qseqeditframe64::update_scale (int index) { set_scale(index); } /** * For internal use only. */ void qseqeditframe64::set_scale (int scale, qbase::status qs) { if (legal_scale(scale)) { m_scale = scale; ui->m_combo_scale->setCurrentIndex(scale); if (not_nullptr(m_seqroll)) m_seqroll->set_scale(scale); if (usr().global_seq_feature()) usr().seqedit_scale(scale); bool user_change = qs == qbase::status::edit; track().musical_scale(midibyte(scale), user_change); enable_note_menus(); set_track_change(); /* to solve issue #90 */ } else errprint("null pattern"); } void qseqeditframe64::reset_scale () { set_scale(c_scales_off); } void qseqeditframe64::editor_mode (bool ischecked) { set_editor_mode ( ischecked ? sequence::editmode::drum : sequence::editmode::note ); set_dirty(); /* modified for issue #90 */ } void qseqeditframe64::loop_mode (bool ischecked) { perf().looping(ischecked); set_dirty(); } void qseqeditframe64::set_editor_mode (sequence::editmode mode) { if (mode != m_edit_mode) { m_edit_mode = mode; perf().edit_mode(track().seq_number(), mode); if (not_nullptr(m_seqroll)) { m_seqroll->update_edit_mode(mode); m_seqdata->update_edit_mode(mode); } set_dirty(); /* modified for issue #90 */ } } /** * Popup menu events button. * * The dependencies of menus on events changes in the pattern: * * - repopulate_event_menu (buss, channel). * - repopulate_mini_event_menu (buss, channel). */ void qseqeditframe64::events () { repopulate_event_menu(m_edit_bus, m_edit_channel); repopulate_mini_event_menu(m_edit_bus, m_edit_channel); if (not_nullptr(m_events_popup)) { int w = ui->m_button_event->width() - 2; int h = ui->m_button_event->height() - 2; m_events_popup->exec(ui->m_button_event->mapToGlobal(QPoint(w, h))); } } /** * Sets the menu pixmap depending on the given state, where true is a * full menu (black background), and empty menu (gray background). * * \param state * If true, the full-menu image will be created. Otherwise, the * empty-menu image will be created. * * \return * Returns a pointer to the created image. */ QIcon * qseqeditframe64::create_menu_image (bool state) { if (usr().dark_theme()) { QPixmap p(state ? menu_full_inv_xpm : menu_empty_inv_xpm); return new QIcon(p); } else { QPixmap p(state ? menu_full_xpm : menu_empty_xpm); return new QIcon(p); } } /** * Functions to create event menu entries. The first overload handles * CC events, and the section handles the other kinds of events. */ void qseqeditframe64::set_event_entry ( QMenu * menu, const std::string & text, bool present, midibyte status, midibyte control ) { QAction * item = new_qaction(text, *create_menu_image(present)); menu->addAction(item); connect ( item, &QAction::triggered, std::bind(&qseqeditframe64::set_data_type, this, status, control) ); if (present && m_first_event == max_midibyte()) { m_first_event = status; m_first_event_name = text; set_data_type(status, control); // need m_first_control value? } } void qseqeditframe64::set_event_entry ( QMenu * menu, bool present, event_index ei ) { int index = static_cast(ei); std::string text = s_event_items[index].epp_name; midibyte status = s_event_items[index].epp_status; QAction * item = new_qaction(text, *create_menu_image(present)); menu->addAction(item); connect ( item, &QAction::triggered, std::bind(&qseqeditframe64::set_data_type, this, status, 0) ); if (present && m_first_event == max_midibyte()) { m_first_event = status; m_first_event_name = text; set_data_type(status); } } /** * Populates the event-selection menu that drops from the "Event" button in * the bottom row of the Pattern editor. * * This menu has a large number of items. They are filled in by code, but * can also be loaded from seq66.usr, qpseq66.usr, etc. * * This function first loops through all of the existing events in the * sequence in order to determine what events exist in it. If any of the * following events are found, their entry in the menu is marked by a filled * square, rather than a hollow square: * * - Note On * - Note off * - Aftertouch * - Program Change * - Channel Pressure * - Pitch Wheel * - Control Changes from 0 to 127 * * Compare this function to repopulate_mini_event_menu(). * * \param buss * The selected bus number. * * \param channel * The selected channel number. */ void qseqeditframe64::repopulate_event_menu (int buss, int channel) { bool ccs[c_midibyte_data_max]; bool note_on = false; bool note_off = false; bool aftertouch = false; bool program_change = false; bool channel_pressure = false; bool pitch_wheel = false; bool tempo = false; bool timesig = false; bool text = false; midibyte status = 0, cc = 0; memset(ccs, false, sizeof(bool) * c_midibyte_data_max); for (auto cev = track().cbegin(); ! track().cend(cev); ++cev) { if (! track().get_next_event(status, cc, cev)) break; /* * Tempo and time-signature are handled after this loop. */ status = event::normalized_status(status); /* mask off channel */ switch (status) /* see event_index */ { case EVENT_NOTE_OFF: note_off = true; break; case EVENT_NOTE_ON: note_on = true; break; case EVENT_AFTERTOUCH: aftertouch = true; break; case EVENT_CONTROL_CHANGE: ccs[cc] = true; break; case EVENT_PROGRAM_CHANGE: program_change = true; break; case EVENT_PITCH_WHEEL: pitch_wheel = true; break; case EVENT_CHANNEL_PRESSURE: channel_pressure = true; break; case EVENT_MIDI_META: /* handled below */ break; } } /* * Currently the only meta events that can be edited here are tempo and * time signature. The meta-match function keeps going until it finds * these meta events, or it ends. All we care here is if one exists. */ auto cev = track().cbegin(); /* scan whole container */ if (! track().cend(cev)) { if (track().get_next_meta_match(EVENT_META_SET_TEMPO, cev)) tempo = true; cev = track().cbegin(); /* start over! */ if (track().get_next_meta_match(EVENT_META_TIME_SIGNATURE, cev)) timesig = true; cev = track().cbegin(); /* start over! */ if (track().get_next_meta_match(EVENT_META_TEXT_EVENT, cev)) text = true; } if (not_nullptr(m_events_popup)) delete m_events_popup; m_events_popup = new_qmenu("", this); set_event_entry(m_events_popup, note_on, event_index::note_on); set_event_entry(m_events_popup, note_off, event_index::note_off); m_events_popup->addSeparator(); set_event_entry(m_events_popup, aftertouch, event_index::aftertouch); /* * Control changes are handled in submenus constructed below. */ set_event_entry(m_events_popup, program_change, event_index::program_change); set_event_entry ( m_events_popup, channel_pressure, event_index::channel_pressure ); set_event_entry(m_events_popup, pitch_wheel, event_index::pitch_wheel); set_event_entry(m_events_popup, tempo, event_index::tempo); set_event_entry(m_events_popup, timesig, event_index::time_signature); set_event_entry(m_events_popup, text, event_index::text); m_events_popup->addSeparator(); /** * Create the 8 sub-menus for the various ranges of controller * changes, shown 16 per sub-menu. */ const bool usehex = m_show_hex_values; const int menucount = 8; const int itemcount = 16; char b[32]; for (int submenu = 0; submenu < menucount; ++submenu) { int offset = submenu * itemcount; const char * fmt { usehex ? "Controls 0x%02x-%02x" : "Controls %d-%d" }; snprintf(b, sizeof b, fmt, offset, offset + itemcount - 1); QMenu * menucc = new_qmenu(b, m_events_popup); for (int item = 0; item < itemcount; ++item) { /* * Do we really want the default controller name to start? There * was a bug in Seq24 where the instrument number was use re 1 to * get the proper instrument... it needs to be decremented to be * re 0. */ std::string cname { controller_name(offset + item, usehex) }; const usermidibus & umb { usr().bus(buss) }; int inst { umb.instrument(channel) }; const userinstrument & uin { usr().instrument(inst) }; if (uin.is_valid()) // redundant check { if (uin.controller_active(offset + item)) cname = uin.controller_name(offset + item); } set_event_entry ( menucc, cname, ccs[offset+item], EVENT_CONTROL_CHANGE, offset + item ); } m_events_popup->addMenu(menucc); } } /** * Popup menu data button. */ void qseqeditframe64::data () { repopulate_mini_event_menu(m_edit_bus, m_edit_channel); if (not_nullptr(m_minidata_popup)) { QPoint bwh(ui->m_button_data->width()-2, ui->m_button_data->height()-2); m_minidata_popup->exec(ui->m_button_data->mapToGlobal(bwh)); } } /** * Rebuilds the event, mini-event, and midi-channel popups and combo-boxes. * We're going to see how it works to repopulate this menu popup only * when requested, rather than when a sequence-change occurs. * * The status dependencies of menus on events: * * - repopulate_event_menu (buss, channel). Depends on the events * in the pattern and the MIDI buss. * - repopulate_mini_event_menu (buss, channel). Depends on the events * in the pattern and the MIDI buss. * - repopulate_midich_combo (buss). Depends only on the MIDI buss. * * The repopulate_usr_combos() function calls all three, and really needs * to be called only when the pattern editor is opened. */ void qseqeditframe64::repopulate_usr_combos (int buss, int channel) { disconnect ( ui->m_combo_bus, SIGNAL(currentIndexChanged(int)), this, SLOT(update_midi_bus(int)) ); disconnect ( ui->m_combo_channel, SIGNAL(currentIndexChanged(int)), this, SLOT(update_midi_channel(int)) ); ui->m_combo_bus->setCurrentIndex(buss); repopulate_midich_combo(buss); repopulate_event_menu(buss, channel); repopulate_mini_event_menu(buss, channel); connect ( ui->m_combo_bus, SIGNAL(currentIndexChanged(int)), this, SLOT(update_midi_bus(int)) ); connect ( ui->m_combo_channel, SIGNAL(currentIndexChanged(int)), this, SLOT(update_midi_channel(int)) ); } /** * Populates the mini event-selection menu that drops from the mini-"Event" * button in the bottom row of the Pattern editor. This menu has a much * smaller number of items, only the ones that actually exist in the * track/pattern/loop/sequence. * * Compare this function to repopulate_event_menu(). * * \param buss * The selected bus number. * * \param channel * The selected channel number. */ void qseqeditframe64::repopulate_mini_event_menu (int buss, int channel) { bool ccs[c_midibyte_data_max]; bool note_on = false; bool note_off = false; bool aftertouch = false; bool program_change = false; bool channel_pressure = false; bool pitch_wheel = false; bool tempo = false; bool timesig = false; bool text = false; bool any_events = false; midibyte status = 0, cc = 0; memset(ccs, false, sizeof(bool) * c_midibyte_data_max); for (auto cev = track().cbegin(); ! track().cend(cev); ++cev) { if (! track().get_next_event(status, cc, cev)) break; /* * Tempo and time-signatures are detected after this loop ends. */ status = event::normalized_status(status); /* mask off channel */ switch (status) { case EVENT_NOTE_OFF: any_events = note_off = true; break; case EVENT_NOTE_ON: any_events = note_on = true; break; case EVENT_AFTERTOUCH: any_events = aftertouch = true; break; case EVENT_CONTROL_CHANGE: any_events = ccs[cc] = true; break; case EVENT_PROGRAM_CHANGE: any_events = program_change = true; break; case EVENT_PITCH_WHEEL: any_events = pitch_wheel = true; break; case EVENT_CHANNEL_PRESSURE: any_events = channel_pressure = true; break; case EVENT_MIDI_META: /* handled below */ break; } } auto cev = track().cbegin(); /* scan whole container */ if (! track().cend(cev)) { if (track().get_next_meta_match(EVENT_META_SET_TEMPO, cev)) tempo = any_events = true; cev = track().cbegin(); /* start over! */ if (track().get_next_meta_match(EVENT_META_TIME_SIGNATURE, cev)) timesig = any_events = true; cev = track().cbegin(); /* start over! */ if (track().get_next_meta_match(EVENT_META_TEXT_EVENT, cev)) text = any_events = true; } if (not_nullptr(m_minidata_popup)) delete m_minidata_popup; m_minidata_popup = new_qmenu("", this); set_event_entry(m_minidata_popup, note_on, event_index::note_on); set_event_entry(m_minidata_popup, note_off, event_index::note_off); m_minidata_popup->addSeparator(); set_event_entry(m_minidata_popup, aftertouch, event_index::aftertouch); /* * Control changes are handled in submenus constructed below. */ set_event_entry ( m_minidata_popup, program_change, event_index::program_change ); set_event_entry ( m_minidata_popup, channel_pressure, event_index::channel_pressure ); set_event_entry(m_minidata_popup, pitch_wheel, event_index::pitch_wheel); set_event_entry(m_minidata_popup, tempo, event_index::tempo); set_event_entry(m_minidata_popup, timesig, event_index::time_signature); set_event_entry(m_minidata_popup, text, event_index::text); if (any_events) m_minidata_popup->addSeparator(); /** * Create the menu for the controller changes that actually exist in * the track, if any. */ const bool usehex = m_show_hex_values; const int itemcount = c_midibyte_data_max; /* 128 */ for (int item = 0; item < itemcount; ++item) { std::string cname { controller_name(item, usehex) }; const usermidibus & umb { usr().bus(buss) }; int inst { umb.instrument(channel) }; const userinstrument & uin { usr().instrument(inst) }; if (uin.is_valid()) /* redundant */ { if (uin.controller_active(item)) cname = uin.controller_name(item); } if (ccs[item]) { set_event_entry ( m_minidata_popup, cname, true, EVENT_CONTROL_CHANGE, item ); } } if (any_events) { /* * Here, we'd like to pre-select the first kind of event found, * somehow. */ qt_set_icon ( usr().dark_theme() ? menu_full_inv_xpm : menu_full_xpm, ui->m_button_data ); } else { set_event_entry(m_minidata_popup, "(no events)", false, 0); qt_set_icon ( usr().dark_theme() ? menu_empty_inv_xpm : menu_empty_xpm, ui->m_button_data ); } } /* * Warning: * * In the next 2 functions, not providing "this" means we have to delete * both dialogs in the destructor. Should we fix this? */ void qseqeditframe64::show_lfo_frame () { if (is_nullptr(m_lfo_wnd)) { bool ok = track().playable_count() > 0; if (ok) { m_lfo_wnd = new (std::nothrow) qlfoframe(perf(), track(), *m_seqdata); if (not_nullptr(m_lfo_wnd)) m_lfo_wnd->show(); } else { qt_info_box ( this, "No playable events to modulate. " "Add notes or controllers first." ); } } else m_lfo_wnd->show(); } void qseqeditframe64::slot_pattern_fix () { show_pattern_fix(); } void qseqeditframe64::show_pattern_fix () { if (is_nullptr(m_patternfix_wnd)) { m_patternfix_wnd = new (std::nothrow) qpatternfix(perf(), track(), this); if (not_nullptr(m_patternfix_wnd)) m_patternfix_wnd->show(); } else m_patternfix_wnd->show(); } /** * Duplicative code. See slot_record_change(), slot_thru_change(), * slot_q_record_change(). Should add text for Tightened and Note-Mapped * active at some point. */ static const char * const s_thru_on = "MIDI Thru Active"; static const char * const s_thru_off = "MIDI Thru Inactive"; static const char * const s_rec_on = "Record Active"; static const char * const s_rec_off = "Record Inactive"; static const char * const s_rec_on_fmt = "%s Active"; static const char * const s_rec_off_fmt = "%s Inactive"; void qseqeditframe64::update_midi_buttons () { bool thru_active = track().thru(); ui->m_toggle_thru->blockSignals(true); ui->m_toggle_thru->setChecked(thru_active); ui->m_toggle_thru->setToolTip(thru_active ? s_thru_on : s_thru_off); qt_set_icon(thru_active ? thru_on_xpm : thru_xpm, ui->m_toggle_thru); ui->m_toggle_thru->blockSignals(false); bool record_active = track().recording(); ui->m_toggle_record->blockSignals(true); ui->m_toggle_record->setChecked(record_active); ui->m_toggle_record->setToolTip(record_active ? s_rec_on : s_rec_off); qt_set_icon(record_active ? rec_on_xpm : rec_xpm, ui->m_toggle_record); ui->m_toggle_record->blockSignals(false); /* * Need to be able to affect the track() alteration in this window! * * bool alteration_active = track().is_new_pattern() ? * usr().pattern_alter_recording() : usr().alter_recording() ; */ bool alteration_active = track().alter_recording(); ui->m_toggle_qrecord->blockSignals(true); ui->m_toggle_qrecord->setChecked(alteration_active); set_toggle_qrecord_button(); ui->m_toggle_qrecord->blockSignals(false); bool playing = track().armed(); ui->m_toggle_play->blockSignals(true); ui->m_toggle_play->setChecked(playing); ui->m_toggle_play->setToolTip(playing ? "Armed" : "Muted"); qt_set_icon(playing ? play_on_xpm : play_xpm, ui->m_toggle_play); ui->m_toggle_play->blockSignals(false); } /** * Passes the play status to the sequence object. */ void qseqeditframe64::slot_play_change (bool ischecked) { if (track().set_armed(ischecked)) { /* * No need to refresh all the buttons: update_midi_buttons(); */ bool playing = track().armed(); ui->m_toggle_play->setToolTip(playing ? "Armed" : "Muted"); qt_set_icon(playing ? play_on_xpm : play_xpm, ui->m_toggle_play); } } /** * Passes the recording status to performer. */ void qseqeditframe64::slot_record_change (bool ischecked) { toggler t = ischecked ? toggler::on : toggler::off ; if (perf().set_recording(track(), t)) { /* * No need to refresh all the buttons: update_midi_buttons(); */ bool record_active = track().recording(); ui->m_toggle_record->setToolTip(record_active ? s_rec_on : s_rec_off); qt_set_icon(record_active ? rec_on_xpm : rec_xpm, ui->m_toggle_record); } } /** * Passes the quantized-recording status to the performer object. Tricky * code. Turn off reqular recording first. This allows the Q record button * to get set, and turn both Q and regular recording on. * * Stazed fix: * * If we set Quantized recording, then also set recording, but do not * unset recording if we unset Quantized recording. * * This is not necessarily the most intuitive thing to do. */ #if defined USE_QUAN_ON_OFF_ONLY /* old stuff */ void qseqeditframe64::slot_q_record_change (bool ischecked) { toggler t = ischecked ? toggler::on : toggler::off ; alteration mode = alteration::none; if (ischecked) { mode = track().record_alteration(); if (mode == alteration::none) mode = alteration::quantize; /* legacy setting */ } q_record_change(mode, t); } #else void qseqeditframe64::slot_q_record_change (bool /* ischecked */) { cb_perf().next_record_alteration(); alteration mode = cb_perf().record_alteration(); track().record_alteration(mode); bool ischecked = track().alter_recording(); toggler t = ischecked ? toggler::on : toggler::off ; q_record_change(mode, t); } #endif void qseqeditframe64::q_record_change (alteration mode, toggler t) { if (perf().set_recording(track(), mode, t)) { bool record_active = track().recording(); ui->m_toggle_record->setToolTip(record_active ? s_rec_on : s_rec_off); qt_set_icon(record_active ? rec_on_xpm : rec_xpm, ui->m_toggle_record); set_toggle_qrecord_button(); } } /** * Using track().alter_recording() currently, but when first opening this * editor, we want to show the setting of alteration usr().record_alteration() * and usr().alter_recording(). * * TODO: elsewhere show recordstyle usr().pattern_record_style(). */ void qseqeditframe64::set_toggle_qrecord_button () { bool alter_record_active = track().alter_recording(); char qtnlabel[48]; (void) snprintf ( qtnlabel, sizeof qtnlabel, alter_record_active ? s_rec_on_fmt : s_rec_off_fmt, usr().record_alteration_label().c_str() ); ui->m_toggle_qrecord->setToolTip(qtnlabel); if (alter_record_active) { ui->m_toggle_qrecord->setChecked(true); if (perf().record_style() == recordstyle::expand) qt_set_icon(exp_rec_on_xpm, ui->m_toggle_record); else qt_set_icon(rec_on_xpm, ui->m_toggle_record); alteration a = perf().record_alteration(); if (a == alteration::tighten) qt_set_icon(t_rec_on_xpm, ui->m_toggle_qrecord); else if (a == alteration::notemap) qt_set_icon(n_rec_on_xpm, ui->m_toggle_qrecord); else qt_set_icon(q_rec_on_xpm, ui->m_toggle_qrecord); } else qt_set_icon(q_rec_xpm, ui->m_toggle_qrecord); } /** * Passes the MIDI Thru status to performer. */ void qseqeditframe64::slot_thru_change (bool ischecked) { if (perf().set_thru(track(), ischecked, false)) { /* * No need to refresh all the buttons: update_midi_buttons(); */ bool thru_active = track().thru(); ui->m_toggle_thru->setToolTip(thru_active ? s_thru_on : s_thru_off); qt_set_icon(thru_active ? thru_on_xpm : thru_xpm, ui->m_toggle_thru); } } /** * If the last recording style is oneshot we can select reset. If we then * select oneshot_reset, we reset the sequence and reselect oneshot. */ void qseqeditframe64::slot_record_style (int index) { recordstyle newstyle = usr().pattern_record_style(index); if (newstyle != m_last_record_style) /* see issue #128 */ { int lroneshot = usr().pattern_record_code(recordstyle::oneshot); int lrreset = usr().pattern_record_code(recordstyle::oneshot_reset); bool ok = track().update_recording(index); if (ok) { enable_combobox_item ( ui->m_combo_rec_type, lrreset, index == lroneshot ); if (index == lrreset) /* oneshot reset */ { if (m_last_record_style == recordstyle::oneshot) { ui->m_combo_rec_type->setCurrentIndex(lroneshot); newstyle = recordstyle::oneshot; index = lroneshot; } } m_last_record_style = newstyle; m_seqtime->set_END_marker(newstyle == recordstyle::expand); ui->m_combo_rec_type->setCurrentIndex(index); perf().set_record_style(newstyle); set_toggle_qrecord_button(); set_dirty(); /* see issue #90 */ } } } void qseqeditframe64::slot_recording_volume (int index) { if (index >= 0 && index < rec_vol_list().count()) { int recvol = rec_vol_list().ctoi(index); if (index == 0) recvol = (-1); /* force preserve-velocity */ set_recording_volume(recvol); set_dirty(); /* modified for issue #90 */ } } void qseqeditframe64::slot_loop_count (int value) { if (track().loop_count_max(value, true)) /* it is a user-change */ set_track_change(); /* added for issue #90 */ } void qseqeditframe64::reset_recording_volume () { ui->m_combo_rec_vol->setCurrentIndex(0); set_dirty(); /* modified for issue #90 */ } /** * Passes the given parameter to sequence::set_rec_vol(). This function also * changes the button's text to match the selection, and also changes the * global velocity-override setting in usrsettings. Note that the setting * will not be saved to the "usr" configuration file unless Seq66 was * run with the "--user-save" option. * * \param recvol * The setting to be made, obtained from the recording-volume ("Vol") * menu. */ void qseqeditframe64::set_recording_volume (int recvol) { track().set_rec_vol(recvol); /* save to the sequence settings */ usr().velocity_override(recvol); /* save to the "usr" config file */ set_dirty(); /* modified for issue #90 */ } void qseqeditframe64::set_dirty () { if (is_initialized()) { qseqframe::set_dirty(); /* roll, time, date & event panes */ m_seqroll->set_redraw(); /* also calls set_dirty() */ /* * These are done in qseqframe::set_dirty(). * * m_seqdata->set_dirty(); // update(); neither cause refresh // * m_seqevent->set_dirty(); // how about this? same! // * m_seqtime->set_dirty(); // any comment? // * * Make each location of change call this, if the action is indeed * a sequence change. Too tricky! See the new set_track_change() * function. * * track().modify(false); // do NOT do notify-change! // */ } update_draw_geometry(); } /** * For issue #90, we had to remove the track-change marking from * set_dirty(). But some of the locations where that function was * called need to mark the sequence as modified. This function * replaces set_dirty() for those places. * * \param modified * Normally true (and that's the default), this parameter can be set to * false for changes such as muting. See on_sequence_change(). */ void qseqeditframe64::set_track_change (bool modified) { set_dirty(); if (is_initialized()) /* start-up changes? */ { set_external_frame_title(modified); if (modified) track().modify(false); /* do not change-notify */ } } void qseqeditframe64::set_external_frame_title (bool modified) { if (not_nullptr(m_qseqeditex_frame)) m_qseqeditex_frame->set_title(modified); /* show an asterisk? */ } /** * Causes all of the panels to be updated. */ void qseqeditframe64::update_draw_geometry() { if (not_nullptr(m_seqroll)) m_seqroll->updateGeometry(); if (not_nullptr(m_seqtime)) m_seqtime->updateGeometry(); if (not_nullptr(m_seqdata)) m_seqdata->updateGeometry(); if (not_nullptr(m_seqevent)) m_seqevent->updateGeometry(); } /** * Implements the actions brought forth from the Tools (hammer) button. * Note that the push_undo(), called internally, pushes all of the current * events (in sequence::m_events) onto the stack (as a single entry). */ void qseqeditframe64::do_action (eventlist::edit action, int var) { track().handle_edit_action(action, var); set_dirty(); /* modified for issue #90 */ } /** * Removes the LFO editor frame. For 0.98.7, we had the issue where closing * the seqedit frame would leave this window and the pattern-fix window * open. So now, instead of deleting them, we signal them to close. */ void qseqeditframe64::remove_lfo_frame () { if (not_nullptr(m_lfo_wnd)) { /* * ca 2026-04-23 This can cause a segfault eventlist::operator = () * if this dialog is open when opening another MIDI file. */ #if defined USE_RESET_HERE m_lfo_wnd->reset(); /* cancel modifications */ #endif m_lfo_wnd->close(); } } /** * Removes the pattern-fix frame. */ void qseqeditframe64::remove_patternfix_frame () { if (not_nullptr(m_patternfix_wnd)) m_patternfix_wnd->close(); } QWidget * qseqeditframe64::rollview () { return ui->rollScrollArea->viewport(); } QWidget * qseqeditframe64::rollwidget () const { return ui->rollScrollArea->widget(); } } // namespace seq66 /* * qseqeditframe64.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qseqeventframe.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqeventframe.cpp * * This module declares/defines the base class for plastering * pattern/sequence data information into a table widget for potential event * editing. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-08-13 * \updates 2026-04-20 * \license GNU GPLv2 or above * * This class is the "Event Editor". */ #include /* Needed for QKeyEvent::accept() */ #include /* for usage with select_button */ #include "cfg/settings.hpp" /* SEQ66_QMAKE_RULES indirectly */ #include "midi/controllers.hpp" /* seq66::controller_name() etc. */ #include "midi/patches.hpp" /* seq66::program_name() etc. */ #include "play/sequence.hpp" /* seq66::sequence */ #include "util/filefunctions.hpp" /* seq66::filename_split() */ #include "util/strfunctions.hpp" /* seq66::string_to_midi_bytes() */ #include "qseqeventframe.hpp" /* seq66::qseqeventframe */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qseqeventframe.h" #else #include "forms/qseqeventframe.ui.h" #endif namespace seq66 { /** * For correcting the width of the event table. It tries to account for the * width of the vertical scroll-bar, plus a bit more. */ static const int sc_event_table_fix { 48 }; /** * Specifies the current hardwired value for set_row_heights(). */ static const int sc_event_row_height { 18 }; /** * * \param p * Provides the performer object to use for interacting with this sequence. * Among other things, this object provides the active PPQN. * * \param s * Provides the reference to the sequence represented by this seqedit. * * \param parent * Provides the parent window/widget for this container window. Defaults * to null. * */ qseqeventframe::qseqeventframe ( performer & p, sequence & s, QWidget * parent ) : QFrame (parent), performer::callbacks (p), ui (new Ui::qseqeventframe), m_seq (s), m_eventslots (new qseventslots(p, *this, s)), m_linked_selection (false), m_show_data_as_hex (false), m_show_time_as_pulses (false), m_initialized (false), m_in_control (false), m_in_program (false), m_is_dirty (false), m_no_channel_index (c_midichannel_max), m_select_popup (nullptr) { ui->setupUi(this); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); /* * Sequence Title. */ connect ( ui->m_entry_name, SIGNAL(textChanged(const QString &)), this, SLOT(update_seq_name()) ); set_seq_title(make_seq_title()); QString seqnolabel = qt(std::to_string(int(track().seq_number()))); ui->label_seq_number->setText(seqnolabel); (void) track().analyze_time_signatures(); int b { int(track().seq_midi_bus()) }; int c { int(track().seq_midi_channel()) }; std::string buss { std::to_string(b) }; std::string chan { is_null_channel(c) ? "Free" : std::to_string(c + 1) }; std::string bpb { std::to_string(track().get_beats_per_bar()) }; std::string bw { std::to_string(track().get_beat_width()) }; std::string ppq { std::to_string(track().get_ppqn()) }; std::string tsetc { "Bus " + buss + " Ch " + chan + ": " + bpb + "/" + bw + " " + ppq + " PPQN" }; set_seq_time_sig_and_ppqn(tsetc); set_seq_channel(""); set_seq_lengths(get_lengths()); /* * Event Table and scroll area. Some setup is already done in the * setupUi() function as configured via Qt Creator. * * "This" object is the parent of eventScrollArea. The scroll-area * contains scrollAreaWidgetContents, which is the parent of * eventTableWidget. * * Note that setRowHeight() will need to be called for any new rows that * get added to the table. However, there is no function for that! * * ui->eventTableWidget->setRowHeight(16); */ QStringList columns; columns << "Time" << "Event" << "Bus" << "Ch" << "D 0" << "D 1" << "Link"; ui->eventTableWidget->setHorizontalHeaderLabels(columns); ui->eventTableWidget->setSelectionBehavior ( QAbstractItemView::SelectRows /* SelectItems */ ); set_selection_multi(false); /* SingleSelection */ set_row_heights(sc_event_row_height); set_column_widths(ui->eventTableWidget->width() - sc_event_table_fix); /* * Doesn't make the table read-only. We want that for now, until we can *kj get time to modify events in-place. * * ui->eventTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); */ connect ( ui->eventTableWidget, SIGNAL(currentCellChanged(int, int, int, int)), this, SLOT(slot_table_click_ex(int, int, int, int)) ); connect ( ui->eventTableWidget, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slot_row_selected()) ); ui->button_link->setChecked(m_linked_selection); connect ( ui->button_link, SIGNAL(clicked()), this, SLOT(slot_link_status()) ); ui->button_blank->setEnabled(false); /* not yet in use */ /* * The (new) "Category" combo-box. This contains the following entries: * * - 0. Channel Message * - 1. System Message * - 2. Meta Event * - 3. SeqSpec Event (disabled) */ populate_category_combo(); connect ( ui->combo_ev_category, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_event_category(int)) ); /* * The "Event" combo-box is used for various purposes depending on which * "Category" item is selected. See these additional populator functions: * * - populate_system_combo() * - populate_meta_combo() * - populate_seqspec_combo() [currently disabled] */ populate_status_combo(); connect ( ui->combo_ev_name, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_event_name(int)) ); /* * Channel selection and (new) Event selection. */ repopulate_midich_combo(); connect ( ui->channel_combo_box, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_midi_channel(int)) ); /* * Time and data format check-boxes. */ connect ( ui->hex_data_check_box, SIGNAL(stateChanged(int)), this, SLOT(slot_hex_data_state(int)) ); connect ( ui->pulse_time_check_box, SIGNAL(stateChanged(int)), this, SLOT(slot_pulse_time_state(int)) ); /* * Experimental. Monitor the D0 field for changes via user edit. */ connect ( ui->entry_ev_data_0, SIGNAL(textEdited(QString)), this, SLOT(slot_ev_data_0_edit(QString)) ); /* * Plain-text edit control for Meta messages involving text. */ ui->data_text_edit->clear(); ui->data_text_edit->setEnabled(true); connect ( ui->data_text_edit, SIGNAL(textChanged()), this, SLOT(slot_meta_text_change()) ); /* * Delete button. Will set to enabled/disabled once fully initialized. */ connect ( ui->button_del, SIGNAL(clicked(bool)), this, SLOT(slot_delete()) ); ui->button_del->setEnabled(false); /* * Insert button. */ connect ( ui->button_ins, SIGNAL(clicked(bool)), this, SLOT(slot_insert()) ); ui->button_ins->setEnabled(true); /* * Modify button. */ connect ( ui->button_modify, SIGNAL(clicked(bool)), this, SLOT(slot_modify()) ); ui->button_modify->setEnabled(false); /* * Save button, now labelled less misleadingly as "Store". */ ui->button_save->setEnabled(false); connect ( ui->button_save, SIGNAL(clicked(bool)), this, SLOT(slot_save()) ); /* * Clear button. */ connect ( ui->button_clear, SIGNAL(clicked(bool)), this, SLOT(slot_clear()) ); ui->button_clear->setEnabled(true); /* * Dump button. */ connect ( ui->button_dump, SIGNAL(clicked(bool)), this, SLOT(slot_dump()) ); ui->button_dump->setEnabled(true); /* * Select button for control/program popup menus. */ connect ( ui->select_button, SIGNAL(clicked(bool)), this, SLOT(slot_event_popup()) ); /* * Load the data. */ initialize_table(); track().set_dirty_mp(); set_dirty(false); cb_perf().enregister(this); } qseqeventframe::~qseqeventframe() { cb_perf().unregister(this); delete ui; } /** * Populates the (new) Category combo-box that replaces the text-label * with a more functional interface item. */ void qseqeventframe::populate_category_combo () { ui->combo_ev_category->clear(); for (int counter = 0; /* counter value */ ; ++counter) { std::string name = editable_event::category_name(counter); if (name.empty()) { break; } else { QString combotext(qt(name)); ui->combo_ev_category->insertItem(counter, combotext); } } /* * Currently, we can't directly display Seq66 SeqSpec items because * they are not in the event list. There are trigger containers that * could be displayed. The rest of the SeqSpecs are properties of the * pattern or the song. */ enable_combobox_item(ui->combo_ev_category, 3, false); } /** * Populates combo_ev_name with the status values of Channel events. * See s_channel_event_names[] in the editable_event module. */ void qseqeventframe::populate_status_combo () { ui->combo_ev_name->clear(); for (int counter = 0; /* counter value */ ; ++counter) { std::string name = editable_event::channel_event_name(counter); if (name.empty()) { break; } else { QString combotext(qt(name)); ui->combo_ev_name->insertItem(counter, combotext); } } ui->combo_ev_name->setCurrentIndex(0); } /** * Populates combo_ev_name with the name of supported System events. */ void qseqeventframe::populate_system_combo () { ui->combo_ev_name->clear(); int counter = 0; for ( ; /* counter value */ ; ++counter) { std::string name = editable_event::system_event_name(counter); if (name.empty()) { break; } else { QString combotext(qt(name)); ui->combo_ev_name->insertItem(counter, combotext); } } ui->combo_ev_name->setCurrentIndex(0); } /** * Populates combo_ev_name with the name of supported Meta events. */ void qseqeventframe::populate_meta_combo () { ui->combo_ev_name->clear(); int counter = 0; for ( ; /* counter value */ ; ++counter) { std::string name = editable_event::meta_event_name(counter); if (name.empty()) { break; } else { QString combotext(qt(name)); ui->combo_ev_name->insertItem(counter, combotext); } } /* * Currently, the "Track Name" is editable by the ui->m_entry_name * line-edit, and is not inserted into the pattern's event list (though * it is saved as a track-name event in the MIDI file. */ enable_combobox_item(ui->combo_ev_name, 2, false); ui->combo_ev_name->setCurrentIndex(0); } /** * Populates combo_ev_name with the name of supported SeqSpec events. */ void qseqeventframe::populate_seqspec_combo () { ui->combo_ev_name->clear(); int counter = 0; for ( ; /* counter value */ ; ++counter) { std::string name = editable_event::seqspec_event_name(counter); if (name.empty()) { break; } else { QString combotext(qt(name)); ui->combo_ev_name->insertItem(counter, combotext); } } ui->combo_ev_name->setCurrentIndex(0); } /** * Inserts channel text items from 1 to 16, with indices from 0 to 15. * Also added is a "None" entry at index 16, for display with * non-channel events. */ void qseqeventframe::repopulate_midich_combo () { int buss { int(track().seq_midi_bus()) }; int ch { int(track().seq_midi_channel()) }; /* track().midi_channel() */ if (populate_midich_combo(ui->channel_combo_box, buss, ch)) { /* * if (is_null_channel(ch)) * ch = c_midichannel_max; * * ui->channel_combo_box->setCurrentIndex(ch); */ } } void qseqeventframe::set_selection_multi (bool multi) { QAbstractItemView::SelectionMode sm = multi ? QAbstractItemView::MultiSelection : QAbstractItemView::SingleSelection ; ui->eventTableWidget->setSelectionMode(sm); } void qseqeventframe::slot_midi_channel (int /*index*/) { // Anything to do? We just need the text. } /** * Checks the given index (from the Channel Message-enabled drop-down) * in order to set a couple of flags. */ void qseqeventframe::check_channel_msg_index (int index) { if (index == control_change) // 3 { m_in_control = true; m_in_program = false; ui->select_button->setText("Ctrl"); ui->select_button->setEnabled(true); } else if (index == program_change) // 4 { m_in_control = false; m_in_program = true; ui->select_button->setText("Prog"); ui->select_button->setEnabled(true); } else { m_in_control = false; m_in_program = false; ui->select_button->setText("Sel"); ui->select_button->setEnabled(false); } } /** * Called when the user clicks a row. The following is disabled as * it is always called near the end of the process so that the text * does not show up. * * Here, we clear the text. It will be filled in either by selecting * a Meta event in the table, or by the user preparing to insert a new * Meta event. Note that clearing the text inserts the placeholder * text. * * For reference during trouble shooting, here are the indices: * * - (-1). Empty string. * - 0. Note Off * - 1. Note On * - 2. Aftertouch * - 3. Control * - 4. Program * - 5. Ch Pressure * - 7. Pitchwheel * * For Control and Program, we would like to show the name of the item * selected, by number, in the D0 field as data is entered. */ void qseqeventframe::slot_event_name (int index) { /* * ui->data_text_edit->clear(); */ #if defined SEQ66_PLATFORM_DEBUG_TMI QString eqs = ui->combo_ev_name->currentText(); std::string es = eqs.toStdString(); printf("slot_event_name(%d) == %s\n", index, CSTR(es)); #endif check_channel_msg_index(index); } /** * We'll tighten this up later. * See the s_category_names [] array. */ void qseqeventframe::slot_event_category (int index) { switch (index) { case channel_message: populate_status_combo(); break; case system_message: populate_system_combo(); break; case meta_event: populate_meta_combo(); break; case seqspec_event: populate_seqspec_combo(); break; default: break; } } void qseqeventframe::slot_hex_data_state (int state) { bool is_true = state != Qt::Unchecked; m_show_data_as_hex = is_true; m_eventslots->hexadecimal(is_true); initialize_table(); } void qseqeventframe::slot_pulse_time_state (int state) { bool is_true = state != Qt::Unchecked; m_show_time_as_pulses = is_true; m_eventslots->pulses(is_true); initialize_table(); } /** * Also gets the characters remaining after translation to encoded * MIDI bytes. Too slow? No. * * How can we easily detect an actual meta-text change??? * * QString qtex = ui->data_text_edit->toPlainText(); * * There's actually nothing to do here. And the pattern is not * dirty until the Modify or Insert button is pressed. * * We let the user enter any amount of text here. We might * have a ton of SysEx bytes, for example. * * set_dirty(); * std::string text = string_to_midi_bytes(qtex.toStdString()); * size_t remainder = c_meta_text_limit - text.size(); * std::string rem = int_to_string(int(remainder)); * ui->labelCharactersRemaining->setText(qt(rem)); * * This function is called whenever the text changes. How can we * manage this when editing Control or Program names (instead of Meta * text or Tempo)? */ void qseqeventframe::slot_meta_text_change () { #if defined USE_THIS_CODE /* * We wanted to be able to type in control or program names to * translate them to D0 values. But this is all but unworkable. */ if (m_in_control) { std::string text = ui->data_text_edit->toPlainText().toStdString(); printf("Control change %s\n", CSTR(text)); } else if (m_in_program) { std::string text = ui->data_text_edit->toPlainText().toStdString(); printf("Program change %s\n", CSTR(text)); } #endif } /** * Helper for slot_ev_data_0_edit() and set_event_data_0(). * The controller code is similar to that in handle_control_popup(). */ void qseqeventframe::data_0_helper (int d0) { if (m_in_control) { int bs { int(track().seq_midi_bus()) }; int ch { int(track().seq_midi_channel()) }; /* no channel here */ const usermidibus & umb { usr().bus(bs) }; int inst { umb.instrument(ch) }; const userinstrument & uin { usr().instrument(inst) }; std::string cname { controller_name(d0, m_show_data_as_hex) }; if (uin.is_valid()) { if (uin.controller_active(d0)) cname = uin.controller_name(d0); /* no hex parameter */ } ui->data_text_edit->document()->setPlainText(qt(cname)); } else if (m_in_program) /* printf("Program edit %d\n", d0) */ { std::string pname { program_name(d0) }; ui->data_text_edit->document()->setPlainText(qt(pname)); } } /** * We could upgrade set_event_plaintext() & qseventslots::set_current_event() * to do this. */ void qseqeventframe::slot_ev_data_0_edit (const QString & qs) { bool success; int d0 = qs.toInt(&success, 0); /* convert by C rules (0n, 0xn, etc */ if (success) data_0_helper(d0); /* can provide a string for d0 */ } /** * Check for dirtiness (perhaps), clear the table and settings, and reload as * if starting again. */ bool qseqeventframe::on_sequence_change ( seq::number seqno, performer::change ctype ) { bool result = seqno == track().seq_number(); if (result) { if (m_is_dirty) { result = false; /* ignore and pop up a warning dialog? */ } else { bool recreate = ctype == performer::change::yes; if (recreate) initialize_table(); } } return result; } void qseqeventframe::set_row_heights (int height) { const int rows = ui->eventTableWidget->rowCount(); for (int r = 0; r < rows; ++r) ui->eventTableWidget->setRowHeight(r, height); /* set_row_height() */ } /** * Sets the height of each row in the table. */ void qseqeventframe::set_row_height (int row, int height) { ui->eventTableWidget->setRowHeight(row, height); } /** * Scales the columns against the provided window width. */ void qseqeventframe::set_column_widths (int total_width) { static float s_w [] = { /* * Time Event Bus Ch D0 D 1 Link */ 0.20f, 0.25f, 0.1f, 0.1f, 0.125f, 0.110f, 0.25f }; ui->eventTableWidget->setColumnWidth(0, int(s_w[0] * total_width)); ui->eventTableWidget->setColumnWidth(1, int(s_w[1] * total_width)); ui->eventTableWidget->setColumnWidth(2, int(s_w[2] * total_width)); ui->eventTableWidget->setColumnWidth(3, int(s_w[3] * total_width)); ui->eventTableWidget->setColumnWidth(4, int(s_w[4] * total_width)); ui->eventTableWidget->setColumnWidth(5, int(s_w[5] * total_width)); ui->eventTableWidget->setColumnWidth(6, int(s_w[6] * total_width)); } /** * Clears, then refills the event table from the qseventslots object. */ bool qseqeventframe::initialize_table () { static const int s_default_rows = 28; bool result = false; if (m_eventslots) { int rows = m_eventslots->count(); if (rows > 0) { ui->eventTableWidget->clearContents(); ui->eventTableWidget->setRowCount(rows); set_row_heights(sc_event_row_height); if (m_eventslots->load_table()) m_eventslots->select_event(0); /* first row */ ui->button_clear->setEnabled(true); } else { ui->eventTableWidget->clearContents(); ui->eventTableWidget->setRowCount(s_default_rows); set_row_heights(sc_event_row_height); ui->button_clear->setEnabled(false); ui->button_del->setEnabled(false); ui->button_modify->setEnabled(false); } ui->eventTableWidget->clearSelection(); } return result; } std::string qseqeventframe::make_seq_title () { return track().name(); } /** * Sets ui->m_entry_name to the title. * * \param title * The name of the sequence. */ void qseqeventframe::set_seq_title (const std::string & title) { ui->m_entry_name->setText(qt(title)); } /** * Handles edits of the sequence title. This immediately dirties the * pattern. */ void qseqeventframe::update_seq_name () { std::string name = ui->m_entry_name->text().toStdString(); if (cb_perf().set_sequence_name(track(), name)) set_dirty(); } /** * Sets ui->label_time_sig to the time-signature string. Also adds the * parts-per-quarter-note string, and now also the length in ticks. * * Combines the set_seq_time_sig() and set_seq_ppqn() from the old * user-interface. * * \param sig * The time signature of the sequence. */ void qseqeventframe::set_seq_time_sig_and_ppqn (const std::string & sig) { ui->label_time_sig->setText(qt(sig)); } /** * If USE_QCHANNELPOPUP_CODE is defined, this combo-box is under * the control of the qchannelpopup object. */ void qseqeventframe::set_seq_channel (const std::string & /*ch*/) { ui->channel_combo_box->setCurrentIndex(0); } /** * Sets the number of measure and the number of events string. * * m_eventslots->track().calculate_measures() * * Combines set_seq_count() and set_length() into one function. */ void qseqeventframe::set_seq_lengths (const std::string & mevents) { ui->label_measures_ev_count->setText(qt(mevents)); } /** * Sets ui->label_category to the category string. * * \param c * The category string for the current event. */ void qseqeventframe::set_event_category (const std::string & c) { QString category = qt(c); /* ui->label_category->setText(qt(c)) */ ui->combo_ev_category->setCurrentText(category); int index = ui->combo_ev_category->currentIndex(); slot_event_category(index); } /** * Sets ui->entry_ev_timestamp to the time-stamp string. * * \param ts * The time-stamp string for the current event. */ void qseqeventframe::set_event_timestamp (const std::string & ts) { ui->entry_ev_timestamp->setText(qt(ts)); } /** * Sets ui->combo_ev_name to the name-of-event string. Oops, now * (2021-04-11), we used it to select the entry in the editable combo-box for * event-status names. * * \param n * The name-of-event string for the current event. */ void qseqeventframe::set_event_name (const std::string & n) { QString name = qt(n); ui->combo_ev_name->setCurrentText(name); /* * Need to call this because slot_event_name does not get called. * Code duplicated in slot_event_name(). * * FIXME */ int index = editable_event::channel_event_index(n); check_channel_msg_index(index); } /** * If USE_QCHANNELPOPUP_CODE is defined, this combo-box is under * the control of the qchannelpopup object. */ void qseqeventframe::set_event_channel (int channel) { if (is_null_channel(midibyte(channel))) channel = c_midichannel_max; ui->channel_combo_box->setCurrentIndex(channel); } /** * Sets ui->entry_ev_data_0 to the first data byte string. We have to call a * helper function because this setting is not treated as a user edit. * * \param d0 * The first data byte string for the current event. */ void qseqeventframe::set_event_data_0 (const std::string & d0) { ui->entry_ev_data_0->setText(qt(d0)); data_0_helper(string_to_int(d0)); } /** * Sets ui->entry_data_1 to the second data byte string. * * \param d * The second data byte string for the current event. */ void qseqeventframe::set_event_data_1 (const std::string & d) { ui->entry_ev_data_1->setText(qt(d)); } /** * Also, we use data_0_helper() for program change and controller * events to display text. The rest are handled in * qseventslots::set_current_event(). * * If USE_QCHANNELPOPUP_CODE is defined, this combo-box is under * the control of the qchannelpopup object. */ void qseqeventframe::set_event_plaintext (const std::string & t) { std::string temp = string_to_midi_bytes(t); QString text = qt(temp); populate_meta_combo(); ui->channel_combo_box->setCurrentIndex(m_no_channel_index); ui->data_text_edit->document()->setPlainText(text); } void qseqeventframe::set_event_system (const std::string & t) { std::string temp = string_to_midi_bytes(t); QString text = qt(temp); // convert to hex bytes? populate_system_combo(); ui->channel_combo_box->setCurrentIndex(m_no_channel_index); ui->data_text_edit->document()->setPlainText(text); } void qseqeventframe::set_event_seqspec (const std::string & t) { QString text = qt(t); // convert to hex bytes? populate_system_combo(); ui->channel_combo_box->setCurrentIndex(m_no_channel_index); ui->data_text_edit->document()->setPlainText(text); } /** * Retrieve the table cell at the given row and column. * * \param row * The row number, which should be in range. * * \param col * The column enumeration value, which will be in range. * * \return * Returns a pointer the table widget-item for the given row and column. * If out-of-range, a null pointer is returned. */ QTableWidgetItem * qseqeventframe::cell (int row, column_id col) { int column = int(col); QTableWidgetItem * result = ui->eventTableWidget->item(row, column); if (is_nullptr(result)) { /* * Will test row/column and maybe add rows on the fly later. */ result = new QTableWidgetItem; ui->eventTableWidget->setItem(row, column, result); } return result; } void qseqeventframe::set_event_line ( int row, const std::string & evtimestamp, const std::string & evname, const std::string & busno, const std::string & evchannel, const std::string & evdata0, const std::string & evdata1, const std::string & linktime ) { static QBrush s_red_brush(Qt::red); static QBrush s_black_brush(Qt::black); QTableWidgetItem * qtip = cell(row, column_id::timestamp); if (not_nullptr(qtip)) { qtip->setText(qt(evtimestamp)); qtip = cell(row, column_id::eventname); qtip->setText(qt(evname)); qtip = cell(row, column_id::buss); qtip->setText(qt(busno)); if (busno[0] != '<') /* see qseventslots */ qtip->setForeground(s_black_brush); /* the event has a bus */ else qtip->setForeground(s_red_brush); /* the pattern's in bus */ qtip = cell(row, column_id::channel); qtip->setText(qt(evchannel)); qtip = cell(row, column_id::data_0); qtip->setText(qt(evdata0)); qtip = cell(row, column_id::data_1); qtip->setText(qt(evdata1)); qtip = cell(row, column_id::link); qtip->setText(qt(linktime)); } } void qseqeventframe::set_event_line (int row, const editable_event & ev) { const editable_event & ev2 = m_eventslots->lookup_link(ev); std::string linktime = ev2.timestamp_string(); std::string evtimestamp = m_eventslots->time_string(ev.timestamp()); std::string evname = ev.status_string(); int buss = int(ev.input_bus()); std::string busno = std::to_string(buss); if (is_null_buss(buss)) busno = "-"; std::string evchannel = ev.channel_string(); midibyte d0, d1; ev.get_data(d0, d1); std::string evdata0 = m_eventslots->data_string(d0); std::string evdata1 = m_eventslots->data_string(d1); set_event_line ( row, evtimestamp, evname, busno, evchannel, evdata0, evdata1, linktime ); } void qseqeventframe::set_event_line (int row) { if (not_nullptr(m_eventslots)) { const editable_event & ev = m_eventslots->current_event(); set_event_line(row, ev); } } /** * Sets the "modified" status of the user-interface. This includes changing * a label, enabling/disabling the Save button, and modifying the event count * and sequence length (in measures). * * \param flag * If true, the modified status is indicated, otherwise it is cleared. * The default value is true. */ void qseqeventframe::set_dirty (bool flag) { static std::string s_initial_color; static bool s_uninitialized = true; if (s_uninitialized) { s_uninitialized = false; s_initial_color = qt_get_color(ui->button_save); } if (flag) { ui->button_save->setEnabled(true); (void) qt_set_color("#AAAA00", ui->button_save); m_is_dirty = true; } else { (void) qt_set_color(s_initial_color, ui->button_save); ui->button_save->setEnabled(false); m_is_dirty = false; } } /** * Needs to be defined in cpp file due to being an incomplete type in the * header. */ int qseqeventframe::current_row () const { return m_eventslots->current_row(); } /** * Needs to be defined in cpp file due to being an incomplete type in the * header. */ void qseqeventframe::current_row (int row) { m_eventslots->current_row(row); } /** * The first slot for handling a click in the event table. */ void qseqeventframe::slot_table_click_ex ( int row, int /*column*/, int /*prevrow*/, int /*prevcolumn*/ ) { if (row >= 0) { m_eventslots->select_event(row); current_row(row); ui->button_del->setEnabled(true); ui->button_modify->setEnabled(true); if (ui->eventTableWidget->selectionModel()->selectedRows().count() > 1) ui->eventTableWidget->clearSelection(); } } /** * The second slot for handling a click in the event table. * * Alternative to check-button: * * QApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier); */ void qseqeventframe::slot_row_selected () { QModelIndex index = ui->eventTableWidget->currentIndex(); bool multi = m_linked_selection = ui->button_link->isChecked(); int row = index.row(); m_eventslots->select_event(row); /* also done by click */ const editable_event & ev0 = m_eventslots->current_event(); if (multi && ev0.is_linked()) { editable_event & ev1 = m_eventslots->lookup_link(ev0); if (ev1.valid_status()) { int row1 = m_eventslots->count_to_link(ev0); if (row1 >= 0) { set_selection_multi(true); ui->eventTableWidget->selectRow(row1); } } } else set_selection_multi(false); } void qseqeventframe::slot_link_status () { m_linked_selection = ui->button_link->isChecked(); if (! m_linked_selection) set_selection_multi(false); } std::string qseqeventframe::get_lengths () { std::string measure { std::to_string(m_eventslots->calculate_measures()) }; std::string tlength { std::to_string(track().get_length()) }; std::string evcount { std::to_string(m_eventslots->count()) }; std::string result { measure + " measure(s) " + tlength + " ticks " + evcount + " events" }; return result; } /** * Initiates the deletion of the current editable event. We call both of the * following. Though they seem redundant, the first call is needed to * hightlight the row visually, and the second makes the actual selection. * * - ui->eventTableWidget->setCurrentIndex(next); * - ui->eventTableWidget->selectionModel()->select(next, ...); * * These are alternatives we tried, FYI only: * * - QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Rows * - ui->eventTableWidget->viewport().update(); */ void qseqeventframe::slot_delete () { if (m_eventslots) { editable_event & current = m_eventslots->current_event(); int row0 = m_eventslots->current_row(); int row1 = m_eventslots->count_to_link(current); bool islinked = row1 >= 0; if (islinked && row0 > row1) std::swap(row0, row1); else row1 = row0; if (islinked) m_eventslots->select_event(row1, false); /* only/last row */ bool was_removed = m_eventslots->delete_current_event(); if (was_removed) { int cr = row1; ui->eventTableWidget->removeRow(row1); if (islinked) { m_eventslots->select_event(row0, false); /* first row */ was_removed = m_eventslots->delete_current_event(); if (was_removed) { cr = row0 - 1; ui->eventTableWidget->removeRow(row0); } } if (! m_eventslots->empty()) { QModelIndex next = ui->eventTableWidget->model()->index(cr, 0); ui->eventTableWidget->setCurrentIndex(next); ui->eventTableWidget->selectionModel()->select ( next, QItemSelectionModel::Rows ); m_eventslots->select_event(cr); current_row(cr); } set_dirty(); } if (m_eventslots->empty()) (void) initialize_table(); set_seq_lengths(get_lengths()); } set_selection_multi(false); } /** * Initiates the insertion of a new editable event. The event's location * will be determined by the timestamp and existing events. Note that we * have to recalibrate the scroll-bar when we insert/delete events. * * As a feature, we will allow events to extend the official length of the * sequence. * * We have to figure out where the new event goes, its new index into * the container, and add the new table row in the corresponding place. * * If USE_QCHANNELPOPUP_CODE is defined, this combo-box is under * the control of the qchannelpopup object. */ void qseqeventframe::slot_insert () { if (m_eventslots) { std::string ts = ui->entry_ev_timestamp->text().toStdString(); std::string name = ui->combo_ev_name->currentText().toStdString(); std::string d0 = ui->entry_ev_data_0->text().toStdString(); std::string d1 = ui->entry_ev_data_1->text().toStdString(); std::string busno = "-"; std::string ch = ui->channel_combo_box->currentText().toStdString(); std::string text = ui->data_text_edit->toPlainText().toStdString(); text = string_to_midi_bytes(text); /* encode for ext ASCII */ std::string linktime; /* empty, no link time yet */ bool has_events = m_eventslots->insert_event ( ts, name, d0, d1, ch, text ); set_seq_lengths(get_lengths()); if (has_events) { std::string chan = m_eventslots->current_event().channel_string(); int cr = m_eventslots->current_row(); ui->eventTableWidget->insertRow(cr); set_row_height(cr, sc_event_row_height); if (text.empty()) set_event_line(cr, ts, name, busno, chan, d0, d1, linktime); else set_event_line(cr, ts, name, busno, chan, text, d1, linktime); ui->button_del->setEnabled(true); ui->button_modify->setEnabled(true); set_dirty(); } } set_selection_multi(false); } /** * Passes the edited fields to the current editable event in the eventslot. * Note that there are two cases to worry about. If the timestamp has not * changed, then we can simply modify the existing current event in place. * Otherwise, we need to delete the old event and insert the new one. * But that is done for us by eventslots::modify_current_event(). * * If USE_QCHANNELPOPUP_CODE is defined, this combo-box is under * the control of the qchannelpopup object. */ void qseqeventframe::slot_modify () { if (m_eventslots) { int row0 = current_row(); const editable_event & ev0 = m_eventslots->current_event(); std::string ts = ui->entry_ev_timestamp->text().toStdString(); std::string name = ui->combo_ev_name->currentText().toStdString(); std::string ch = ev0.channel_string(); std::string d0 = ui->entry_ev_data_0->text().toStdString(); std::string d1 = ui->entry_ev_data_1->text().toStdString(); std::string chan = ui->channel_combo_box->currentText().toStdString(); std::string text = ui->data_text_edit->toPlainText().toStdString(); text = string_to_midi_bytes(text); /* encode for ext ASCII */ midipulse lt = c_null_midipulse; bool reload = true; if (ev0.is_linked()) { editable_event & ev1 = m_eventslots->lookup_link(ev0); if (ev1.valid_status()) { int row1 = m_eventslots->count_to_link(ev0); if (row1 >= 0) { lt = ev0.link_time(); m_eventslots->select_event(row1, false); m_eventslots->modify_current_channel_event(row1, d0, d1, ch); set_event_line(row1); } } else reload = false; } std::string ltstr = m_eventslots->time_string(lt); int buss = int(ev0.input_bus()); std::string busno = std::to_string(buss); if (is_null_buss(buss)) busno = "-"; m_eventslots->select_event(row0, false); if (reload) { reload = m_eventslots->modify_current_event ( row0, ts, name, d0, d1, ch, text ); } set_seq_lengths(get_lengths()); set_event_line(row0, ts, name, busno, chan, d0, d1, ltstr); if (reload) initialize_table(); /* this is very stilted, Milton */ set_dirty(reload); } set_selection_multi(false); } /** * Handles saving the edited data back to the original sequence, now called * "Store". * * The event list in the original sequence is cleared, and the editable * events are converted to plain events, and added to the container, one by * one. * * Also tells performer to notify its subscribers. This will cause * qseqeventframe::on_sequence_change() to be called, so be careful of loops! * * \todo * Could also support writing the events to a new sequence, for added * flexibility. * * \todo * Make sure that performer now calls the notification apparatus! */ void qseqeventframe::slot_save () { if (m_eventslots) { bool ok = m_eventslots->save_events(); if (ok) { seq::number seqno = track().seq_number(); cb_perf().notify_sequence_change(seqno); set_dirty(false); } } set_selection_multi(false); (void) track().analyze_time_signatures(); } void qseqeventframe::slot_clear () { if (m_eventslots) { bool hasevents = ! m_eventslots->empty(); m_eventslots->clear(); initialize_table(); if (hasevents) set_dirty(); } set_selection_multi(false); } std::string qseqeventframe::filename_prompt ( const std::string & prompt, const std::string & file ) { std::string result = file; bool ok = show_file_dialog ( this, result, prompt, "Text files (*.text *.txt);;All files (*)", SavingFile, NormalFile, ".text", false ); if (ok) { // nothing yet } else result.clear(); return result; } void qseqeventframe::slot_dump () { if (m_eventslots) { std::string dump = m_eventslots->events_to_string(); if (! dump.empty()) { std::string fspec = rc().midi_filename(); std::string directory; std::string basename; bool ok = filename_split(fspec, directory, basename); if (ok) { basename = file_extension_set(basename); /* strip .ext */ basename += "-pattern-"; basename += std::to_string(track().seq_number()); basename = file_extension_set(basename, ".text"); fspec = filename_concatenate(directory, basename); /* * Before writing, give the user a chance to save it * elsewhere, or at least see where it will be saved. */ std::string prompt = "Dump events to text file"; std::string filename = filename_prompt(prompt, fspec); if (filename.empty()) { // no code, the user cancelled } else { if (! file_write_string(filename, dump)) msgprintf(msglevel::status, "%s", dump.c_str()); } } else msgprintf(msglevel::status, "%s", dump.c_str()); } } set_selection_multi(false); } /** * Cancels the edits and closes the dialog box. In order for removing the * current-highlighting in the mainwd or perfedit windows, some of the work * of slot_close() needs to be done here as well. */ void qseqeventframe::slot_cancel () { set_selection_multi(false); } /** * NEW. ------------------------------------------------------------------- */ void qseqeventframe::slot_event_popup () { if (m_in_control) handle_control_popup(); else if (m_in_program) handle_program_popup(); } /** * Functions to create event menu entries. The first overload handles * CC events. */ void qseqeventframe::set_controller_entry ( QMenu * menu, const std::string & text, midibyte status, midibyte control ) { QAction * item = new_qaction(text, this); // hmmmmmmmmmm menu->addAction(item); connect ( item, &QAction::triggered, std::bind(&qseqeventframe::set_control_type, this, status, control) ); } void qseqeventframe::set_control_type (midibyte /* status */, midibyte control) { std::string controlstr { std::to_string(unsigned(control)) }; set_event_data_0(controlstr); if (m_in_control) set_event_data_1("64"); else if (m_in_program) set_event_data_1("0"); data_0_helper(control); /* fill in the text field */ } /** * This function is similar to that used in qseqeditframe64. * However, it does not need the code to draw a used/unused square * pixmap. */ void qseqeventframe::handle_control_popup () { m_select_popup = new_qmenu("", this); /** * Create the 8 sub-menus for the various ranges of controller * changes, shown 16 per sub-menu. */ const int menucount { 8 }; const int itemcount { 16 }; int bs { int(track().seq_midi_bus()) }; int ch { int(track().seq_midi_channel()) }; // no channel here const usermidibus & umb { usr().bus(bs) }; int inst { umb.instrument(ch) }; const userinstrument & uin { usr().instrument(inst) }; char b[32]; for (int submenu = 0; submenu < menucount; ++submenu) { int offset { submenu * itemcount }; const char * fmt { m_show_data_as_hex ? "Controls 0x%02x-%02x" : "Controls %d-%d" }; snprintf(b, sizeof b, fmt, offset, offset + itemcount - 1); QMenu * menucc { new_qmenu(b, m_select_popup) }; for (int item = 0; item < itemcount; ++item) { std::string cname { controller_name(offset + item, m_show_data_as_hex) }; if (uin.is_valid()) { if (uin.controller_active(offset + item)) { cname = uin.controller_name(offset + item); /* no hex */ } } set_controller_entry ( menucc, cname, EVENT_CONTROL_CHANGE, offset + item ); } m_select_popup->addMenu(menucc); } if (not_nullptr(m_select_popup)) { int w { ui->select_button->width() - 2 }; int h { ui->select_button->height() - 2 }; m_select_popup->exec(ui->select_button->mapToGlobal(QPoint(w, h))); delete m_select_popup; m_select_popup = nullptr; } } void qseqeventframe::handle_program_popup () { m_select_popup = new_qmenu("", this); /** * Create the 8 sub-menus for the various ranges of patches, * shown 16 per sub-menu. */ const int menucount { 8 }; const int itemcount { 16 }; char b[32]; for (int submenu = 0; submenu < menucount; ++submenu) { int offset { submenu * itemcount }; snprintf ( b, sizeof b, "Programs %d-%d", offset, offset + itemcount - 1 ); QMenu * menupr { new_qmenu(b, m_select_popup) }; for (int item = 0; item < itemcount; ++item) { std::string pname { program_name(offset + item) }; set_controller_entry ( menupr, pname, EVENT_PROGRAM_CHANGE, offset + item ); } m_select_popup->addMenu(menupr); } if (not_nullptr(m_select_popup)) { int w { ui->select_button->width() - 2 }; int h { ui->select_button->height() - 2 }; m_select_popup->exec(ui->select_button->mapToGlobal(QPoint(w, h))); delete m_select_popup; m_select_popup = nullptr; } } /*------------------------------------------------------------------------*/ /* * We must accept() the key-event, otherwise even key-events in the QLineEdit * items are propagated to the parent, where they then get passed to the * performer as if they were keyboards controls (such as a pattern-toggle * hot-key). * * Plus, here, we have no real purpose for the code, so we macro it out. * What's up with that, Spunky? */ void qseqeventframe::keyPressEvent (QKeyEvent * event) { event->accept(); } void qseqeventframe::keyReleaseEvent (QKeyEvent * event) { event->accept(); } } // namespace seq66 /* * qseqeventframe.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qseqframe.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqframe.cpp * * This module declares/defines the base class for managing the editing of * sequences. * * \library seq66 application * \author Oli Kester; modifications by Chris Ahlstrom * \date 2018-07-27 * \updates 2025-07-18 * \license GNU GPLv2 or above * * Seq66 (Qt version) has two different pattern editor frames to * support: * * - New. This pattern-editor frame is used in its own window. It is * larger and has a lot of functionality. Furthermore, it * keeps the time, event, and data views in full view at all times * when scrolling, just like the Gtkmm-2.4 version of the pattern * editor. * - Kepler34. This frame is not as functional, but it does fit in the * tab better, and it scrolls the time, event, keys, and roll panels * as if they were one object. */ #include "cfg/settings.hpp" /* seq66::usr() */ #include "qseqdata.hpp" #include "qseqframe.hpp" #include "qseqkeys.hpp" #include "qseqroll.hpp" #include "qseqtime.hpp" #include "qstriggereditor.hpp" /* * Do not document the name space. */ namespace seq66 { /** * Principal constructor. * * \param p * Provides the performer object to use for interacting with this sequence. * Among other things, this object provides the active PPQN. * * \param s * Provides the reference to the sequence represented by this seqedit. * * \param basezoom * Provides the base or starting zoom, so that it can be reset properly * by the "0" key. * * \param parent * Provides the parent window/widget for this container window. Defaults * to null. */ qseqframe::qseqframe ( performer & p, sequence & s, int basezoom, QWidget * parent ) : QFrame (parent), qeditbase (p, basezoom), /* c_default_seq_zoom */ m_seq (s), m_seqkeys (nullptr), m_seqtime (nullptr), m_seqroll (nullptr), m_seqdata (nullptr), m_seqevent (nullptr) { // No code needed } qseqframe::~qseqframe () { // No code needed } bool qseqframe::repitch_all () { std::string filename = rc().notemap_filespec(); bool result = perf().repitch_all(filename, track()); if (result) { set_dirty(); } else { // need to display error message somehow } return result; } bool qseqframe::repitch_selected () { std::string filename = rc().notemap_filespec(); bool result = perf().repitch_selected(filename, track()); if (result) { set_dirty(); } else { // need to display error message somehow } return result; } /** * Forwards the zoom changes to the child panes. */ bool qseqframe::zoom_in () { bool result = qeditbase::zoom_in(); if (result) { if (not_nullptr(m_seqroll)) m_seqroll->zoom_in(); if (not_nullptr(m_seqtime)) m_seqtime->zoom_in(); if (not_nullptr(m_seqdata)) m_seqdata->zoom_in(); if (not_nullptr(m_seqevent)) m_seqevent->zoom_in(); } return result; } bool qseqframe::zoom_out () { bool result = qeditbase::zoom_out(); if (result) { if (not_nullptr(m_seqroll)) m_seqroll->zoom_out(); if (not_nullptr(m_seqtime)) m_seqtime->zoom_out(); if (not_nullptr(m_seqdata)) m_seqdata->zoom_out(); if (not_nullptr(m_seqevent)) m_seqevent->zoom_out(); } return result; } /** * Sets the horizontal (time) zoom parameter, z. If valid, then the m_zoom * member is set. The new setting is passed to the roll, time, data, and * event panels [which each call their own set_dirty() functions]. * * \param z * The desired zoom value, which is checked for validity. * * \return * Returns true if the zoom value was changed. */ bool qseqframe::set_zoom (int z) { bool result = qeditbase::set_zoom(z); if (result) { if (not_nullptr(m_seqroll)) m_seqroll->set_zoom(z); if (not_nullptr(m_seqtime)) m_seqtime->set_zoom(z); if (not_nullptr(m_seqdata)) m_seqdata->set_zoom(z); if (not_nullptr(m_seqevent)) m_seqevent->set_zoom(z); } return result; } bool qseqframe::reset_zoom (int ppq) { bool result = qeditbase::reset_zoom(ppq); if (result) { if (not_nullptr(m_seqroll)) m_seqroll->reset_zoom(ppq); if (not_nullptr(m_seqtime)) m_seqtime->reset_zoom(ppq); if (not_nullptr(m_seqdata)) m_seqdata->reset_zoom(ppq); if (not_nullptr(m_seqevent)) m_seqevent->reset_zoom(ppq); } return result; } /** * Sets the dirty status of all of the panels. However, note that in the * case of zoom, for example, it also sets dirtiness, via qseqbase. * * Also be sure that the performer calls the notification apparatus when * changes in the data in the sequence occur. */ void qseqframe::set_dirty () { qeditbase::set_dirty(); if (not_nullptr(m_seqroll)) m_seqroll->set_dirty(); if (not_nullptr(m_seqtime)) m_seqtime->set_dirty(); if (not_nullptr(m_seqdata)) m_seqdata->set_dirty(); if (not_nullptr(m_seqevent)) m_seqevent->set_dirty(); } } // namespace seq66 /* * qseqframe.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qseqkeys.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqkeys.cpp * * This module declares/defines the base class for the left-side piano of * the pattern/sequence panel. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2026-04-30 * \license GNU GPLv2 or above * * We've added the feature of a right-click toggling between showing the main * octave values (e.g. "C1" or "C#1") versus the numerical MIDI values of the * keys. */ #include #include /* QPainter, QPen, QBrush, ... */ #include #include "cfg/settings.hpp" /* seq66::usr().key_height(), etc. */ #include "play/performer.hpp" /* seq66::performer class */ #include "qseqeditframe64.hpp" /* seq66::qseqeditframe64 class */ #include "qseqkeys.hpp" /* seq66::qseqkeys panel */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ namespace seq66 { class performer; /** * The width and thickness of the keys drawn on the GUI. * * Unused: * * static const int sc_key_y = 8; */ static const int sc_key_x = 22; /** * The dimensions and offset of the virtual keyboard at the left of the piano * roll. We also add a y offset to line up the keys and the piano roll grid. */ static const int sc_keyoffset_x = 20; static const int sc_keyarea_x = sc_key_x + sc_keyoffset_x + 2; /** * Manifest constant. */ static const int sc_null_key = (-1); static const int sc_border_width = 2; /** * Principal constructor. */ qseqkeys::qseqkeys ( performer & p, sequence & s, qseqeditframe64 * frame, QWidget * parent, int keyheight, int keyareaheight ) : QWidget (parent), qseqbase ( p, s, frame, c_default_seq_zoom, c_default_snap, keyheight, keyareaheight ), m_font (), m_show_key_names (usr().key_view()), /* initial default */ m_key (0), /* i.e. "C" */ m_key_y (keyheight), /* note_height() */ m_key_area_y (keyareaheight), /* total_height() */ m_preview_color (progress_paint()), /* preview_color() */ m_white_key_color (white_key_paint()), /* white_color() */ m_black_key_color (black_key_paint()), /* black_color() */ m_is_previewing (false), /* previewing() */ m_preview_on (false), /* preview_on() */ m_preview_key (sc_null_key) /* preview_key() */ { /* * This policy is necessary in order to allow the vertical scrollbar to * work. */ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding); setMouseTracking(true); m_font.setPointSize(9); /* 6, 7, 8 are tiny; 10 big */ } /* * Provides additional vertical padding to adjust y values for notes, then * subtract the built-in key offset. */ void qseqkeys::paintEvent (QPaintEvent *) { QPainter painter(this); QPen pen(Qt::black); QBrush brush (Qt::SolidPattern); const int keyx = sc_keyoffset_x + 1; const int numx = 0; /* was 2 then 1 */ int keyy = 0; int numy = 8; const int nh = note_height(); const int nh_1 = nh - 1; const int nh_4 = nh - 4; const int nh_3 = nh - 3; const int key_x_3 = sc_key_x - 3; const int key_x_6 = sc_key_x - 6; pen.setStyle(Qt::SolidLine); pen.setWidth(sc_border_width); brush.setColor(backkeys_paint()); painter.setPen(pen); painter.setBrush(brush); painter.drawRect(0, 0, sc_keyarea_x, total_height()); /* border */ pen.setWidth(1); for (int i = 0; i < c_notes_count; ++i, keyy += nh, numy += nh) { int keyvalue = c_notes_count - i - 1; int key = keyvalue % c_octave_size; pen.setStyle(Qt::SolidLine); pen.setColor(Qt::black); brush.setStyle(Qt::SolidPattern); if (is_black_key(key)) /* black keys */ { /* * Inverting the keys is confusing, so alway use black. * brush.setColor(black_color()); */ brush.setColor(Qt::black); painter.setPen(pen); painter.setBrush(brush); painter.drawRect(keyx, keyy + 1, key_x_6, nh_3); } else { /* * Inverting the keys is confusing, so alway use white. * brush.setColor(white_color()); */ brush.setColor(Qt::white); painter.setPen(pen); painter.setBrush(brush); painter.drawRect(keyx, keyy, sc_key_x, nh_1); } if (is_preview_key(keyvalue)) /* preview note */ { brush.setColor(preview_color()); /* Qt::red */ pen.setStyle(Qt::NoPen); painter.setPen(pen); painter.setBrush(brush); painter.drawRect(keyx + 2, keyy + 2, key_x_3, nh_4); } std::string note; char notebuf[8]; m_font.setBold(key == m_key); /* octave label */ painter.setFont(m_font); pen.setColor(text_keys_paint()); /* Qt::black) */ painter.setPen(pen); switch (m_show_key_names) { case showkeys::octave_letters: if (key == m_key) { note = musical_note_name(keyvalue); painter.drawText(numx, numy, qt(note)); } break; case showkeys::even_letters: if ((keyvalue % 2) == 0) { note = musical_note_name(keyvalue); painter.drawText(numx, numy, qt(note)); } break; case showkeys::all_letters: note = musical_note_name(keyvalue); painter.drawText(numx, numy, qt(note)); break; case showkeys::even_numbers: if ((keyvalue % 2) == 0) { snprintf(notebuf, sizeof notebuf, "%3d", keyvalue); painter.drawText(numx, numy, notebuf); } break; case showkeys::all_numbers: snprintf(notebuf, sizeof notebuf, "%3d", keyvalue); painter.drawText(numx, numy, notebuf); break; } } } void qseqkeys::mousePressEvent (QMouseEvent * ev) { if (ev->button() == Qt::LeftButton) { int note; int y = qt_mouse_y(ev); convert_y(y, note); preview_key(note); preview_on(true); track().play_note_on(note); } else if (ev->button() == Qt::RightButton) { preview_key(sc_null_key); switch (m_show_key_names) { case showkeys::octave_letters: m_show_key_names = showkeys::even_letters; break; case showkeys::even_letters: m_show_key_names = showkeys::all_letters; break; case showkeys::all_letters: m_show_key_names = showkeys::even_numbers; break; case showkeys::even_numbers: m_show_key_names = showkeys::all_numbers; break; case showkeys::all_numbers: m_show_key_names = showkeys::octave_letters; break; } } update(); } void qseqkeys::mouseReleaseEvent (QMouseEvent * ev) { if (ev->button() == Qt::LeftButton) { if (previewing()) { if (preview_on()) { track().play_note_off(preview_key()); preview_on(false); } preview_key(sc_null_key); } } update(); } void qseqkeys::mouseMoveEvent (QMouseEvent * /* ev */) { if (previewing()) { if (preview_on()) { track().play_note_off(preview_key()); preview_on(false); } preview_key(sc_null_key); } update(); } QSize qseqkeys::sizeHint () const { int w = sc_keyarea_x; int h = total_height(); return QSize(w, h); } void qseqkeys::convert_y (int y, int & note) { note = (total_height() - y - 2) / note_height(); } /** * We don't want the scroll wheel to accidentally scroll the piano keys * vertically, so this override does nothing but accept() the event. * * ignore() just let's the parent handle the event, which allows scrolling to * occur. For issue #3, we have enabled the scroll wheel in the piano roll * [see qscrollmaster::wheelEvent()], but we disable it here. So this is a * partial solution to the issue. * * The best solution would be to pass the event along to the qscrollmaster. * But this class doesn't have access to the scroll-master. */ void qseqkeys::wheelEvent (QWheelEvent * qwep) { qwep->accept(); } void qseqkeys::preview_key (int key) { m_is_previewing = key >= 0; m_preview_key = key; update(); } bool qseqkeys::set_note_height (int h) { bool result = usr().valid_key_height(h) && h != note_height(); if (result) { note_height(h); total_height(h * c_notes_count); resize(sc_keyarea_x, total_height()); update(); } return result; } bool qseqkeys::v_zoom_in () { return set_note_height(note_height() + 1); } bool qseqkeys::v_zoom_out () { return set_note_height(note_height() - 1); } bool qseqkeys::reset_v_zoom () { return set_note_height(usr().key_height()); } void qseqkeys::set_key (int k) { #if defined SEQ66_SHOW_SELECTED_KEY_OCTAVE if (legal_key(k)) { m_key = k; update(); } #else (void) k; #endif } } // namespace seq66 /* * qseqkeys.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qseqroll.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqroll.cpp * * This module declares/defines the base class for drawing on the piano * roll of the patterns editor for the Qt 5 implementation. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2026-04-30 * \license GNU GPLv2 or above * * Please see the additional notes for the Gtkmm-2.4 version of this panel, * seqroll. */ #include /* QApplication keyboardModifiers() */ #include /* base class for seqedit frame(s) */ #include /* used as a tool-tip for notes */ #include #include #include #include /* for recoloring the tool-tip */ #include #include /* used in scrolling for progress */ #include #include "cfg/settings.hpp" /* seq66::usr().key_height(), etc. */ #include "play/performer.hpp" /* seq66::performer class */ #include "qseqeditframe64.hpp" /* seq66::qseqeditframe64 class */ #include "qseqkeys.hpp" /* seq66::qseqkeys class */ #include "qseqroll.hpp" /* seq66::qseqroll class */ #include "qscrollmaster.h" /* used in scrolling for progress */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ /** * We've had an issue where adding wrapped-but-truncated notes would * alter the look of some other notes until the play/record was stopped. * So this macro enables sequence::verify_and_link() every time. * This iterates through all events, but acts only for unlinked notes, * so it doesn't appear to add a noticeable amount to the CPU load. */ #define SEQ66_ALWAYS_VERIFY_AND_LINK namespace seq66 { /** * Default value for randomization. Currently the only value supported. * * Unused: * * static const int c_randomize_range = 4; // randomize range in ticks */ static const int c_border_width = 2; static const int c_pen_width = 1; /** * Principal constructor. */ qseqroll::qseqroll ( performer & p, sequence & s, qseqeditframe64 * frame, qseqkeys * seqkeys_wid, int zoom, int snap, sequence::editmode mode, int unith, int totalh ) : QWidget (frame), qseqbase (p, s, frame, zoom, snap, unith, totalh), m_note_grad (0, 0, 0, 1), m_wrap_grad (0, 0, 0, 1), m_sel_grad (0, 0, 0, 1), m_analysis_msg (nullptr), m_font ("Monospace"), m_backseq_brush (gui_backseq_brush()), m_chord_brush (gui_chord_brush()), m_seqkeys_wid (seqkeys_wid), m_timer (nullptr), m_scale (scales::off), m_pos (0), m_chord (chords::none), m_key (keys::C), m_show_note_info (false), m_note_tooltip (nullptr), m_note_length (p.ppqn() / 4), m_note_off_margin (2), m_background_sequence (seq::unassigned()), m_draw_background_seq (false), m_show_scale_or_chords (true), m_filter_painted_notes (false), m_status (0), m_cc (0), m_edit_mode (mode), m_draw_whole_grid (true), m_t0 (0), m_t1 (0), m_frame_ticks (0), m_note_x (0), m_note_width (0), m_note_y (0), m_keypadding_x (c_keyboard_padding_x), m_v_zooming (false), m_selection (), m_sel_offset_x (0), m_sel_offset_y (0), m_last_base_note (-1), m_link_wraparound (usr().pattern_wraparound()) { setAttribute(Qt::WA_StaticContents); setAttribute(Qt::WA_OpaquePaintEvent); /* no erase on repaint */ setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); /* track w/out a click */ m_font.setStyleHint(QFont::Monospace); m_font.setLetterSpacing(QFont::AbsoluteSpacing, 1); m_font.setBold(false); m_font.setPointSize(6); /* 8 is too obtrusive */ set_snap(track().snap()); if (use_gradient()) { m_note_grad.setColorAt(0.05, fore_color()); m_note_grad.setColorAt(0.5, note_in_color()); m_note_grad.setColorAt(0.95, fore_color()); m_note_grad.setCoordinateMode(QGradient::ObjectMode); m_wrap_grad.setColorAt(0.05, fore_color()); m_wrap_grad.setColorAt(0.5, Qt::magenta); m_wrap_grad.setColorAt(0.95, fore_color()); m_wrap_grad.setCoordinateMode(QGradient::ObjectMode); m_sel_grad.setColorAt(0.05, fore_color()); m_sel_grad.setColorAt(0.5, sel_color()); m_sel_grad.setColorAt(0.95, fore_color()); m_sel_grad.setCoordinateMode(QGradient::ObjectMode); } /* * A kludge to start out with a reasonable zoom. For some reason, * calling zoom_power_of_2() in qseqeditframe64 does not work. */ if (p.ppqn() < 192) (void) frame64()->zoom_in(); if (p.ppqn() <= 48) (void) frame64()->zoom_in(); show(); m_timer = qt_timer(this, "qseqroll", 1, SLOT(conditional_update())); } /** * This virtual destructor stops the timer. */ qseqroll::~qseqroll () { if (not_nullptr(m_timer)) m_timer->stop(); } /** * In an effort to reduce CPU usage when simply idling, this function calls * update() only if necessary. See qseqbase::check_dirty(). * * bool ok = track().playing(); * if (m_draw_whole_grid) */ void qseqroll::conditional_update () { bool ok = perf().needs_update() || check_dirty(); if (ok) { #if defined SEQ66_ALWAYS_VERIFY_AND_LINK /* defined */ if (track().recording()) (void) track().verify_and_link(); /* refresh before update */ #endif update(); } } void qseqroll::set_dirty () { /* * Recursion unto segfault! * * frame64()->set_track_change(); * * This function is actually for drawing. * * frame64()->set_external_frame_title(); */ /* * Try to get refresh of data pane to work. * qseqbase::set_dirty() doesn't do the trick. * frame64()->set_dirty() yields a segfault because it calls * qseqframe::set_dirty() which then calls the set_dirty() functions * of the four pattern editor frames including this one. */ qseqbase::set_dirty(); } /** * Similar to qstriggereditor::flag_dirty(). */ void qseqroll::flag_dirty () { track().set_dirty(); frame64()->set_dirty(); } /** * Zooms in, first calling the base-class version of this function, then * passing along the message to the parent edit frame, so that it can change * the zoom on the other panels of the parent edit frame. */ bool qseqroll::zoom_in () { bool result = qseqbase::zoom_in(); if (result) set_dirty(); return result; } /** * Zooms out, first calling the base-class version of this function, then * passing along the message to the parent edit frame, so that it can change * the zoom on the other panels of the parent edit frame. */ bool qseqroll::zoom_out () { bool result = qseqbase::zoom_out(); if (result) set_dirty(); return result; } /** * Tells the parent frame to reset our zoom. */ bool qseqroll::reset_zoom (int ppq) { bool result = qseqbase::reset_zoom(ppq); if (result) set_dirty(); return result; } bool qseqroll::v_zoom_in () { bool result = m_seqkeys_wid->v_zoom_in(); if (result) { int h = m_seqkeys_wid->note_height(); unit_height(h); total_height(m_seqkeys_wid->total_height()); m_v_zooming = true; set_dirty(); frame64()->set_dirty(); } return result; } bool qseqroll::v_zoom_out () { bool result = m_seqkeys_wid->v_zoom_out(); if (result) { int h = m_seqkeys_wid->note_height(); unit_height(h); total_height(m_seqkeys_wid->total_height()); m_v_zooming = true; set_dirty(); frame64()->set_dirty(); } return result; } bool qseqroll::reset_v_zoom () { bool result = m_seqkeys_wid->reset_v_zoom(); if (result) { int h = m_seqkeys_wid->note_height(); unit_height(h); total_height(m_seqkeys_wid->total_height()); set_dirty(); frame64()->set_dirty(); } m_v_zooming = false; return result; } int qseqroll::note_height () const { return m_seqkeys_wid->note_height(); } /** * Override. * * Issue 2024-09-07: If PPQN == 120, then pulses_per_substep() returns 7.5 * truncated. This (in part) screws up the display. * * \param v * The value of the scrollbar in pixels. */ void qseqroll::scroll_offset (int x) { midipulse ticks = z().pix_to_tix(x); midipulse ticks_per_step = z().pulses_per_substep(); m_t0 = ticks - (ticks % ticks_per_step); m_frame_ticks = z().pix_to_tix(frame64()->width()); m_t1 = ticks + m_frame_ticks; qseqbase::scroll_offset(x); } /** * This function sets the given sequence onto the piano roll of the pattern * editor, so that the musician can have another pattern to play against. * The state parameter sets the boolean m_draw_background_seq. * * The first check in this statement disabled further changes! * * if (state != m_draw_background_seq && m_background_sequence != seq) * * \param state * If true, the background sequence will be drawn. * * \param seq * Provides the sequence number, which is checked against seq::legal() * before being used. This macro allows the value seq::limit (2048), * which disables the background sequence. */ void qseqroll::set_background_sequence (bool state, int seq) { if (m_background_sequence != seq) { if (seq::legal(seq)) { m_draw_background_seq = state; m_background_sequence = seq; } if (is_initialized()) set_dirty(); } } void qseqroll::toggle_show_scale_or_chords () { m_show_scale_or_chords = ! m_show_scale_or_chords; if (is_initialized()) set_dirty(); } void qseqroll::toggle_filter_painted_notes () { m_filter_painted_notes = ! m_filter_painted_notes; if (is_initialized()) set_dirty(); } /** * Does anybody use this one? qseqeditframe64::on_automation_change(). */ void qseqroll::set_redraw () { m_draw_whole_grid = true; set_dirty(); } /** * Draws the piano roll. * * In later usage, the width() function [and height() as well?], returns a * humongous value (38800+). So we store the current values to use, via * window_width() and window_height(), in follow_progress(). * * We have to decide how to handle repaints after the initialization. Do we * use the QRect of the paint-event? * * Here, we could choose black instead white for "inverse" mode. */ void qseqroll::paintEvent (QPaintEvent * qpep) { QRect r = qpep->rect(); QRect view(0, 0, width(), height()); QPainter painter(this); QBrush brush(blank_brush()); // QBrush brush(Qt::white, Qt::NoBrush); QPen pen(Qt::lightGray); pen.setStyle(Qt::SolidLine); pen.setColor(Qt::lightGray); painter.setPen(pen); painter.setFont(m_font); m_frame_ticks = z().pix_to_tix(r.width()); m_edit_mode = perf().edit_mode(track().seq_number()); /* * Draw the border and grid. See the banner notes about width and height. * Doesn't seem to be needed: painter.drawRect(0, 0, ww, wh); */ draw_grid(painter, view); set_initialized(); /* * Draw the events. This currently draws all of them. Drawing all them * only needs to be drawn once. */ call_draw_notes(painter, view); pen.setWidth(c_pen_width); /* * Draw the playhead. */ pen.setColor(progress_color()); pen.setStyle(Qt::SolidLine); pen.setWidth(progress_bar_width()); painter.setPen(pen); old_progress_x(progress_x()); progress_x(xoffset(track().get_tick())); painter.drawLine(progress_x(), r.y(), progress_x(), r.y() + r.height()); /* * End of draw_progress_on_window(). The next step is to restore * the "empty" brush style in case the user draws a selection box. */ brush.setStyle(Qt::NoBrush); /* painter reset */ painter.setBrush(brush); if (select_action()) /* select/move/paste/grow */ pen.setStyle(Qt::SolidLine); int x, y, w, h; /* draw selections */ if (selecting()) { rect::xy_to_rect_get ( drop_x(), drop_y(), current_x(), current_y(), x, y, w, h ); old_rect().set(x, y, w, h + unit_height()); pen.setColor(sel_color()); painter.setPen(pen); painter.drawRect(x, y, w, h); } else if (paste()) /* issue #97, draw a paste box */ { pen.setColor(sel_color()); painter.setPen(pen); draw_ghost_notes(painter, m_selection); } int selw = selection().width(); int selh = selection().height(); if (drop_action()) { int delta_x = current_x() - drop_x(); int delta_y = current_y() - drop_y(); x = selection().x() + delta_x; y = selection().y() + delta_y; pen.setColor(sel_color()); painter.setPen(pen); if (is_drum_mode()) { int drumx = x - unit_height() * 0.5 + m_keypadding_x; painter.drawRect(drumx, y + 2, selw + unit_height(), selh); } else { draw_ghost_notes(painter, m_selection); /* *not* selection()!! */ } old_rect().set(x, y, selw, selh); } if (growing()) { int delta_x = current_x() - drop_x(); selw += delta_x; if (selw < 1) selw = 1; x = selection().x(); y = selection().y(); pen.setColor(sel_color()); /* fore_color() Qt::black */ painter.setPen(pen); painter.drawRect(x + m_keypadding_x, y, selw, selh); old_rect().set(x, y, selw, selh); } } void qseqroll::call_draw_notes (QPainter & painter, const QRect & view) { if (m_draw_background_seq) draw_notes(painter, view, true); if (is_drum_mode()) draw_drum_notes(painter, view, false); else draw_notes(painter, view, false); } /** * First, we clear the rectangle before drawing. At this point, we could * choose black instead white for "inverse" mode. * * The brush. The following really looks good only with the default * "SolidPattern" style: * * QBrush brush(back_color(), blank_brush().style()); * * Drawing the horizontal grid lines depends on the vertical zoom. * Draw horizontal grid lines differently depending on editing mode. * Set line color dependent on the note row we're on. * * Drawing vertical grid lines. Incrementing by ticks_per_step only works * for PPQN of certain multiples or for certain time offsets. Therefore, need * to check every darn tick!!!! No, that causes aliasing in drawing horizontal * lines. :-D * * for (int tick = starttick; tick < endtick; ++tick) * * The ticks_per_step value needs to be figured out. Why 6 * zoom()? 6 is * the number of pixels in the smallest divisions in the default seqroll * background. This code needs to be put into a function. * * For odd beat widths, use 1 as ticks_per_substep. * * If the time-sig is further along, it won't work. We do make sure there * is a time-sig at the beginning, if nothing else. Also, the time-sig ends * at the end of the pattern, but we generally need to paint beyond that * point. * * midipulse starttick = pix_to_tix(r.x()); // the old way * midipulse tsendtick = ts.sig_end_tick != 0 ? * ts.sig_end_tick : pix_to_tix(r.x() + r.width()); */ void qseqroll::draw_grid (QPainter & painter, const QRect & r) { QBrush bbrush(back_color()); /* brush(Qt::NoBrush) */ QPen pen(grey_color()); /* pen(Qt::lightGray) */ pen.setStyle(Qt::SolidLine); /* Qt::DotLine */ pen.setWidth(c_border_width); /* border thickness */ painter.fillRect(r, bbrush); /* blank the viewport */ painter.setBrush(bbrush); painter.setPen(pen); painter.drawRect(r); pen.setWidth(horiz_pen_width()); painter.drawLine(r.x(), 1, r.x() + r.width(), 1); /* * Horizontal (note) lines. Note that scroll_offset_v() is always 0 * and has been removed here. */ for (int note = 0; note < c_notes_count; ++note) /* each note row */ { int modkey = c_note_max - note; /* actual note value */ /* * This cause drawing to be one height to low on the screen. * Also see the corresponding correction in the scales_policy() * overload. * * int y = note * unit_height() + 2; */ int y = note * unit_height() + 2; if ((modkey % c_octave_size) == 0) pen.setColor(octave_color()); /* fore_color() */ else pen.setColor(step_color()); painter.setPen(pen); painter.drawLine(r.x(), y, r.x() + r.width(), y); if (show_scale_or_chords()) { /* * We could display both the scale and chord, but this looks * confusing. For now, keep the "else". */ if (m_scale != scales::off) { if (scales_policy(m_scale, m_key, modkey)) { continue; } else { painter.setBrush(scale_brush()); painter.drawRect(0, y + 1, r.width(), unit_height() - 1); } } else if (m_chord != chords::none) { if (note_in_chord(m_chord, m_key, modkey)) { continue; } else { painter.setBrush(chord_brush()); painter.drawRect(0, y + 1, r.width(), unit_height() - 1); } } } } int count = track().time_signature_count(); midipulse ticks_per_step = z().pulses_per_substep(); midipulse endtick = z().pix_to_tix(r.x() + r.width()); for (int tscount = 0; tscount < count; ++tscount) { const sequence::timesig & ts = track().get_time_signature(tscount); if (ts.sig_beat_width == 0) break; int bpbar = ts.sig_beats_per_bar; int bwidth = ts.sig_beat_width; bool valid_bw = is_power_of_2(bwidth); midipulse ticks_per_four = z().pulses_per_partial_beat(bpbar, bwidth); midipulse ticks_per_beat = midipulse(z().pulses_per_beat(bwidth)); midipulse ticks_per_bar = z().pulses_per_bar(bpbar, bwidth); midipulse starttick = ts.sig_start_tick; starttick -= starttick % ticks_per_step; for (midipulse tick = starttick; tick < endtick; tick += ticks_per_step) { int x_offset = xoffset(tick) - scroll_offset_x(); int penwidth = 1; enum Qt::PenStyle penstyle = Qt::SolidLine; if (tick % ticks_per_bar == 0) /* solid line every bar */ { penstyle = measure_pen_style(); penwidth = measure_pen_width(); pen.setColor(beat_color()); } else if (tick % ticks_per_beat == 0) /* light line each beat */ { penstyle = beat_pen_style(); penwidth = beat_pen_width(); pen.setColor(beat_color()); } else if (tick % ticks_per_four == 0 && valid_bw) { /* * Adding this line is problematic if the beat-width is * not the power-of-two that MIDI requires. Keep the * display cleaner, if incomplete. */ penstyle = fourth_pen_style(); /* Qt::DashDotLine */ pen.setColor(extra_color()); /* beat_color()) */ } else { pen.setColor(step_color()); /* faint step lines */ penstyle = step_pen_style(); /* Qt::DotLine */ } pen.setWidth(penwidth); pen.setStyle(penstyle); painter.setPen(pen); painter.drawLine(x_offset, 0, x_offset, total_height()); } } } /** * Draw the current pixmap frame. Note that, if the width and height change, * we will have to reevaluate. Draw the events. This currently draws all of * them. Drawing all them only needs to be drawn once. */ void qseqroll::draw_notes ( QPainter & painter, const QRect & r, bool background ) { QBrush brush(note_brush()); QBrush error_brush(Qt::magenta); /* for unlinked notes */ QPen pen(fore_color()); QPen error_pen(Qt::magenta); pen.setStyle(Qt::SolidLine); pen.setWidth(c_pen_width); painter.setPen(pen); painter.setBrush(brush); midipulse seqlength = track().get_length(); midipulse start_tick = z().pix_to_tix(r.x()); midipulse end_tick = start_tick + z().pix_to_tix(r.width()); sequence * b = perf().get_sequence(m_background_sequence).get(); sequence * s = background ? b : &track(); if (is_nullptr(s)) return; int noteheight = unit_height() - 2; /* was "- 3" */ s->draw_lock(); for (auto cev = s->cbegin(); ! s->cend(cev); ++cev) { sequence::note_info ni; sequence::draw dt = s->get_next_note(ni, cev); if (dt == sequence::draw::finish) break; if (ni.non_note()) continue; bool start_in = ni.start() >= start_tick && ni.start() <= end_tick; bool end_in = ni.finish() >= start_tick && ni.finish() <= end_tick; bool not_wrapped = ni.finish() >= ni.start(); bool linkedin = dt == sequence::draw::linked && end_in; bool bad = false; if (start_in || linkedin) { int in_shift = 0; int length_add = 0; m_note_x = xoffset(ni.start()); m_note_y = note_to_pix(ni.note()); if (dt == sequence::draw::linked) { if (not_wrapped) { m_note_width = z().tix_to_pix(ni.finish() - ni.start()); if (m_note_width < 1) m_note_width = 1; } else m_note_width = z().tix_to_pix(seqlength - ni.start()); } else m_note_width = z().tix_to_pix(16); if (dt == sequence::draw::note_on) /* means it's unlinked */ { in_shift = 0; length_add = 2; bad = true; painter.setBrush(error_brush); } else if (dt == sequence::draw::note_off) { in_shift = -1; length_add = 1; bad = true; painter.setBrush(error_brush); } if (background) /* draw background note */ { length_add = 1; painter.setBrush(backseq_brush()); } else painter.setBrush(note_brush()); painter.drawRect(m_note_x, m_note_y, m_note_width, noteheight); if (use_gradient()) { if (background) { length_add = 1; painter.setBrush(backseq_brush()); painter.drawRect ( m_note_x, m_note_y, m_note_width, noteheight ); } else { painter.fillRect ( m_note_x + 1, m_note_y + 1, m_note_width - 1, noteheight - 1, m_note_grad ); } } if (m_link_wraparound && ! not_wrapped) { int len = z().tix_to_pix(ni.finish()) - m_note_off_margin; if (use_gradient()) { painter.fillRect ( m_keypadding_x, m_note_y, len + 1, noteheight + 1, m_wrap_grad ); } else { painter.setPen(error_pen); painter.drawRect ( m_keypadding_x, m_note_y, len, noteheight ); painter.setPen(pen); } } /* * Draw note highlight if there's room. Orange note if selected, * red if drum mode, otherwise plain white. */ if (m_note_width > 3) { if (! background) { int x_shift = m_note_x + in_shift; int h_minus = noteheight - 1; if (use_gradient()) { if (ni.selected()) { painter.fillRect ( x_shift, m_note_y, m_note_width + length_add, h_minus, m_sel_grad ); } } else { if (ni.selected()) brush.setColor(sel_color()); /* "orange" */ else brush.setColor(note_in_color()); /* Qt::white */ if (bad) painter.setBrush(error_brush); else painter.setBrush(brush); if (not_wrapped) /* note highlight */ { painter.drawRect ( x_shift, m_note_y, m_note_width + length_add - 1, h_minus ); } else { int w = z().tix_to_pix(ni.finish()) + length_add - 3; painter.drawRect ( x_shift, m_note_y, m_note_width, h_minus ); painter.drawRect ( m_keypadding_x, m_note_y, w, h_minus ); } } } } } } s->draw_unlock(); } /** * Originally, once Ctrl-V (paste) as hit, an empty rectangle the size * of the original selection range would appear, and the top left corner * would be the approximate location of the paste, upon the mouse release. * Now we want to get the tick range and note range of the selected notes * and map that to the smallest rectangle enclosing the notes. * * (x0, y0) and (xo, yo) * ----------________ --------------------- * n1 .....|........>|________| | * | ________ ________ | * horizontal | ||________| |________| ni | * pixels | | ^ (xi, yi) ________ | * v | : |________|<|..... n0 * -------------------------------------^-- * : vertical pixels ---> : (x1, y1) * : : * t0 t1 * * - (x0, y0) is the current (x, y) of the mouse in screen coordinates. * - (x1, y1) is the other corner in screen coordinates. * - (xi, yi) is the desired coordinate of note i. * * Let t be ticks, n be note numbers, and x and y be pixels. * * Here are the mappings: * * - t0 == tick_start ---> x0 == current x * - t1 == tick_finish ---> x1 * - n1 == note_high ---> y0 == current y * - n0 == note_low ---> y1 * * We can let (x0, y0) be (0, 0) and add current (x, y) later. * Might deduct the height of the note, later, as well. * * (ti - t0)(x1 - x0) * xi = -------------------- + x0 * t1 - t0 * * (n1 - ni)(y1 - y0) * yi = -------------------- + y0 * n1 - n0 * * For yi, we have to add the height of the selection box, plus a couple * of pixels for looks. * * \param painter * The object used for painting. * * \param selection * Holds the range from leftmost/highest note to rightmost/lowest * note. The selection provides * * - rect::x0() --> tick_start * - rect::y0() --> note_high * - rect::x1() --> tick_finish * - rect::y1() --> note_low * * (x0, y0) and (x1, y1) in units of * pixels. The 0th coordinate is (0, 0). */ void qseqroll::draw_ghost_notes ( QPainter & painter, const seq66::rect & selection ) { int xo = current_x() - m_sel_offset_x + 6; /* leftmost */ int yo = current_y() - m_sel_offset_y; /* top */ int t0 = selection.x0(); /* tick_start */ int t1 = selection.x1(); /* tick_finish */ int n0 = selection.y0(); /* note_low */ int n1 = selection.y1(); /* note_high */ int x0 = z().tix_to_pix(midipulse(t0)); int x1 = z().tix_to_pix(midipulse(t1)); int y0 = note_to_pix(n1); int y1 = note_to_pix(n0); int wbox = x1 - x0; int hbox = y1 - y0; int ndiff = n1 - n0; if (ndiff == 0) ndiff = 1; if (hbox == 0) hbox = unit_height(); float widthslope = wbox / float(t1 - t0); /* (x1-x0) / (t1-t0) */ float hieghtslope = hbox / float(ndiff); /* (y1-y0) / (n1-n0) */ /* * ca 2025-07-06. Not really necessary to draw an outline box. * * painter.drawRect(x0, y0 + 1, wbox, hbox); */ track().draw_lock(); for (auto cev = track().cbegin(); ! track().cend(cev); ++cev) { sequence::note_info ninfo; sequence::draw dt = track().get_next_note(ninfo, cev); if (dt == sequence::draw::finish) break; if (ninfo.selected()) { int ti = int(ninfo.start()); int ni = ninfo.note(); int xi = (ti - t0) * widthslope + xo + 1; int yi = (n1 - ni) * hieghtslope + yo + 4; if (dt == sequence::draw::linked) { m_note_width = z().tix_to_pix(ninfo.length()); if (m_note_width < 1) m_note_width = 1; painter.drawRect(xi, yi, m_note_width, unit_height() - 2); } } } track().draw_unlock(); } /* * Why floating point; just divide by 2. Also, the polygon seems to be offset * downward by half the note height. * \verbatim x0 x x1 y 1 . / \ / \ y0 0 . . 2 \ / \ / y1 . 3 \endverbatim */ void qseqroll::draw_drum_note (QPainter & painter, int x, int y) { int noteheight = unit_height(); int h2 = noteheight / 2; int x0 = x - h2; int x1 = x + h2; int y0 = y + h2; int y1 = y + noteheight; QPointF points[4] = { QPointF(x0, y0), // 0 QPointF(x, y), // 1 QPointF(x1, y0), // 2 QPointF(x, y1) // 3 }; painter.drawPolygon(points, 4); /* * Draw note highlight. Not really useful, save time by ignoring. * * if (ni.selected()) * brush.setColor("orange"); // Qt::red * else if (is_drum_mode()) * brush.setColor(Qt::red); */ } #if defined SEQ66_SHOW_TEMPO_IN_PIANO_ROLL void qseqroll::draw_tempo (QPainter & painter, int x, int y, int velocity) { QString v = qt(std::to_string(velocity)); int h = int(0.75 * unit_height()); painter.drawEllipse(x, y, h, h); painter.drawText(x, y - 2, v); } #endif void qseqroll::draw_drum_notes ( QPainter & painter, const QRect & r, bool background ) { QBrush brush(Qt::NoBrush); QPen pen(drum_color()); /* draw red boxes from drum loop */ pen.setStyle(Qt::SolidLine); pen.setWidth(c_pen_width); brush.setStyle(Qt::SolidPattern); painter.setPen(pen); painter.setBrush(brush); m_edit_mode = perf().edit_mode(track().seq_number()); midipulse start_tick = z().pix_to_tix(r.x()); midipulse end_tick = start_tick + z().pix_to_tix(r.width()); sequence * b = perf().get_sequence(m_background_sequence).get(); sequence * s = background ? b : &track(); if (is_nullptr(s)) return; s->draw_lock(); for (auto cev = s->cbegin(); ! s->cend(cev); ++cev) { sequence::note_info ni; sequence::draw dt = s->get_next_note(ni, cev); if (dt == sequence::draw::finish) break; if (ni.non_note()) continue; bool start_in = ni.start() >= start_tick && ni.start() <= end_tick; bool end_in = ni.finish() >= start_tick && ni.finish() <= end_tick; bool linkedin = dt == sequence::draw::linked && end_in; if (start_in || linkedin) { m_note_x = xoffset(ni.start()); m_note_y = note_to_pix(ni.note()); /* * Orange note if selected, red for drum mode. */ if (ni.selected()) brush.setColor(sel_color()); else brush.setColor(drum_paint()); pen.setColor(fore_color()); painter.setPen(pen); painter.setBrush(brush); draw_drum_note(painter, m_note_x, m_note_y); } } s->draw_unlock(); } /** * Encapsulates a common calculation. */ int qseqroll::note_to_pix (int n) const { return total_height() - (n + 1) * unit_height() + 2; } int qseqroll::note_off_length () const { return m_note_length - m_note_off_margin; } /** * Convenience wrapper for sequence::add_note() and sequence::add_chord(). * The length parameters is obtained from the note_off_length() function. * This sets the note length at a little less than the snap value. * * We no longer support single-note undo of painted notes; they all get * undone. * * if (m_chord > 0) * result = track().push_add_chord(m_chord, tick, n, note); * else * result = track().push_add_note(tick, n, note, true); * * Scenarios: * * - Normal (no scale, no chord): Just add the note. * - Scale on * - Ignore the chord status. * - Filtering off: Just add the note. * - Filtering on: If the note is on scale, add the note. * - Chord on (scales off): * - Filtering off: Add the chord at the base note. * - Filtering on: If the note is part of the chord, just * add the single note. * * \param tick * The time destination of the new note, in pulses. * * \param note * The pitch destination of the new note. * * \param first * True if the call was made via a mouse press, rather than * mouse movement. Defaults to false. * * \return * Returns true if the painting succeeded. */ bool qseqroll::add_painted_note (midipulse tick, int note, bool first) { bool result = false; bool can_paint = true; bool scales_on = m_scale != scales::off; bool chords_on = m_chord != chords::none; bool filter = filter_painted_notes(); if (filter) { if (scales_on) { can_paint = scales_policy(m_scale, m_key, note); chords_on = false; } else if (chords_on) can_paint = note_in_chord(m_chord, m_key, note); } if (can_paint) { bool can_add_chords = chords_on && ! filter; int n = note_off_length(); if (first) track().push_undo(); /* multiple-note undo only */ /* * Using a down-snap makes the painting look and feel better. * * This, however, causes issue #144. Let's try adding * half the note length to the tick. A bit disconcerting * is some cases. So now try adding 1/4 the note length. * Seems better but awful with long notes. Use snap()! * * tick = closest_snap(snap(), tick); * tick = down_snap(snap(), tick); * tick = down_snap(snap(), tick + m_note_length / 4); * tick = down_snap(snap(), tick + snap() / 4); * * However, still an issue in drum mode, so add a whole snap(). */ if (is_drum_mode()) tick = down_snap(snap(), tick + snap()); else tick = down_snap(snap(), tick + snap() / 4); if (can_add_chords) /* add chords if not filtering */ { result = track().add_chord ( static_cast(m_chord), tick, n, note ); } else { result = track().add_painted_note ( tick, n, note, true /* paint */ ); } if (result) { result = mark_modified(); set_dirty(); } } return result; } void qseqroll::resizeEvent (QResizeEvent * qrep) { QWidget::resizeEvent(qrep); } /** * If it was a button press, set values for dragging. */ void qseqroll::mousePressEvent (QMouseEvent * ev) { midipulse tick_s, tick_f; int note, note_l, norm_x, norm_y, snapped_x, snapped_y; /* * The key-padding messes with snap_x(), we think. Instead use * the progress-bar's initial location. * * snapped_x = norm_x = ev->x() - m_keypadding_x; */ snapped_x = norm_x = qt_mouse_x(ev) - xoffset(0); if (norm_x < 0) return; snapped_y = norm_y = qt_mouse_y(ev); snap_x(snapped_x); snap_y(snapped_y); current_y(snapped_y); drop_y(snapped_y); /* y is always snapped */ if (paste()) { convert_xy(snapped_x, snapped_y, tick_s, note); if (ev->button() == Qt::LeftButton) track().paste_selected(tick_s, note); paste(false); setCursor(Qt::ArrowCursor); flag_dirty(); } else { bool isctrl = bool(ev->modifiers() & Qt::ControlModifier); bool lbutton = ev->button() == Qt::LeftButton; bool rbutton = ev->button() == Qt::RightButton; bool mbutton = ev->button() == Qt::MiddleButton || (lbutton && isctrl); if (lbutton) { current_x(norm_x); drop_x(norm_x); /* select non-snapped x */ if (is_drum_mode()) { int dropxadj = drop_x() - unit_height() / 2; /* padding */ convert_xy(dropxadj, drop_y(), tick_s, note); } else { convert_xy(drop_x(), drop_y(), tick_s, note); tick_f = tick_s; } m_last_base_note = note; if (adding()) /* painting new notes */ { eventlist::select selmode = eventlist::select::would_select; painting(true); /* start paint job */ current_x(snapped_x); drop_x(snapped_x); /* adding, snapped x */ /* * ca 2025-07-21. Already done above. * * convert_xy(drop_x(), drop_y(), tick_s, note); */ /* * Test if a note is already there. Fake select, if so, don't * add, else add a note, length = little less than snap. */ bool wont_select = track().select_note_events ( tick_s, note, tick_s, note, selmode ) == 0; if (wont_select) { if (add_painted_note(tick_s, note, true)) set_dirty(); } } else /* we're selecting anew */ { /* * In drum mode, we were using "is_onset", but this breaks * moving the selected drum events. So we leave it at * "selected". */ eventlist::select selmode = eventlist::select::selected; int selcount = track().select_note_events ( tick_s, note, tick_f, note, selmode ); bool is_selected = selcount > 0; if (is_selected) { if (! isctrl) { moving_init(true); /* moving; L-click only */ /* * ca 2025-07-07 * Store the selection to show the single-note * "ghost note" while moving. */ (void) get_selected_box(); flag_dirty(); if (is_drum_mode()) { track().onsets_selected_box ( tick_s, note, tick_f, note_l ); } else track().selected_box(tick_s, note, tick_f, note_l); convert_tn_box_to_rect ( tick_s, tick_f, note, note_l, selection() ); int adj_selected_x = selection().x(); snap_x(adj_selected_x); move_snap_offset_x(selection().x() - adj_selected_x); current_x(snapped_x); drop_x(snapped_x); /* * EXPERIMENTAL. Get pixel coordinates of selected notes. * Subtract them from the current mouse location. if (selcount > 1) { m_sel_offset_x = current_x() - selection().x(); m_sel_offset_y = current_y() - selection().y(); } else m_sel_offset_x = m_sel_offset_y = 0; * */ m_sel_offset_x = current_x() - selection().x(); m_sel_offset_y = current_y() - selection().y(); } /* * Middle mouse button or left-ctrl click. */ bool can_grow = mbutton && ! is_drum_mode(); if (can_grow) { growing(true); track().selected_box(tick_s, note, tick_f, note_l); convert_tn_box_to_rect ( tick_s, tick_f, note, note_l, selection() ); } } else { if (! isctrl) { track().unselect(); flag_dirty(); } selmode = is_drum_mode() ? eventlist::select::onset : eventlist::select::select_one ; int numsel = track().select_note_events ( tick_s, note, tick_f, note, selmode ); if (numsel == 0) /* none selected, start selection box */ selecting(true); else flag_dirty(); } } } if (rbutton) set_adding(true); } } /** * This function gets the rectangle m_selection with its ranges * based on the selected notes (whether selected with the mouse or via * functions such as select_all(). */ bool qseqroll::get_selected_box () { midipulse tick_s, tick_f; /* start and end of tick window */ int note_h, note_l; /* high and low notes in window */ bool result = track().selected_box(tick_s, note_h, tick_f, note_l); if (result) { m_selection.xy_to_rect ( int(tick_s), note_h, int(tick_f), note_l ); } return result; } void qseqroll::mouseReleaseEvent (QMouseEvent * ev) { /* * The key-padding messes with snap_x(), we think. Instead use * the progress-bar's initial location. * * current_x(int(ev->x()) - m_keypadding_x); */ current_x(qt_mouse_x(ev) - xoffset(0)); current_y(qt_mouse_y(ev)); (void) snap_current_y(); if (moving()) (void) snap_current_x(); int delta_x = current_x() - drop_x(); int delta_y = current_y() - drop_y(); midipulse delta_tick; int delta_note; bool lbutton = ev->button() == Qt::LeftButton; bool rbutton = ev->button() == Qt::RightButton; bool isctrl = bool(ev->modifiers() & Qt::ControlModifier); /* Ctrl */ bool mbutton = ev->button() == Qt::MiddleButton || (lbutton && isctrl); if (lbutton) { if (selecting()) { midipulse tick_s, tick_f; /* start and end of tick window */ int note_h, note_l; /* high and low notes in window */ int x, y, w, h; /* window dimensions */ eventlist::select selmode = eventlist::select::selecting; rect::xy_to_rect_get /* copy drop dimensions to xywh */ ( drop_x(), drop_y(), current_x(), current_y(), x, y, w, h ); convert_xy(x, y, tick_s, note_h); convert_xy(x + w, y + h, tick_f, note_l); /* * This breaks the selection of events in drum mode. * if (is_drum_mode()) selmode = eventlist::select::onset; */ int numsel = track().select_note_events ( tick_s, note_h, tick_f, note_l, selmode ); if (numsel > 0) { (void) get_selected_box(); flag_dirty(); } } if (moving()) { /* * Move the notes to the same location as the ghost-notes * selection box. The ghost notes are drawn based on the * current mouse (x, y) location, with the top-left corner * at the current mouse location. * * Adjust delta x for snap, convert deltas into screen * coordinates. Since delta_note and delta_y are of opposite * sign, we flip the final result. delta_y[0] = note[127]. * This code suffers from the defect that the drop location * depends on where in the selection the mouse grabbed it. */ int note; delta_x -= move_snap_offset_x(); convert_xy(delta_x, current_y(), delta_tick, note); if (m_last_base_note >= 0) { delta_note = note - m_last_base_note; } else { convert_xy(delta_x, delta_y, delta_tick, delta_note); delta_note = delta_note - (c_notes_count - 1); } m_last_base_note = (-1); if (delta_tick != 0 || delta_note != 0) { track().move_selected_notes(delta_tick, delta_note); flag_dirty(); /* * Make the moved notes the new selection. */ (void) get_selected_box(); } } } if (lbutton || mbutton) { if (growing()) { convert_xy(delta_x, delta_y, delta_tick, delta_note); if (ev->modifiers() & Qt::ShiftModifier) track().stretch_selected(delta_tick); else track().grow_selected(delta_tick); (void) mark_modified(); flag_dirty(); } } if (rbutton) { if (! QApplication::queryKeyboardModifiers().testFlag(Qt::MetaModifier)) { set_adding(false); flag_dirty(); } } clear_action_flags(); /* turn off all the action flags */ track().unpaint_all(); if (is_dirty()) /* if clicked, something changed */ track().set_dirty(); } /** * Snaps the given x coordinate to the next snap value. Note that the * m_snap value already takes into account a PPQN different from the * base PPQN of 192. So here we only want to account for the current * zoom value. Also see the seq66::snapped<>() template function from * the midi/calculations module. */ int qseqroll::snapped_x (int x) { int result = 0; int sczoom = scale_zoom(); if (sczoom > 0) { int snap = m_snap / sczoom; result = snapped(snapper::up, snap, x); } return result; } /** * Handles a mouse movement, including selection and note-painting. */ void qseqroll::mouseMoveEvent (QMouseEvent * ev) { /* * The key-padding messes with snap_x(), we think. Instead use * the progress-bar's initial location. * * current_x(int(ev->x()) - m_keypadding_x); */ current_x(qt_mouse_x(ev) - xoffset(0)); current_y(qt_mouse_y(ev)); if (moving_init()) { moving_init(false); moving(true); } (void) snap_current_y(); int x = current_x(); int y = current_y() + 2; int note; midipulse tick; convert_xy(0, y, tick, note); m_seqkeys_wid->preview_key(note); if (m_show_note_info) show_note_tooltip(x, y); if (select_action()) { if (drop_action()) (void) snap_current_x(); set_dirty(); } if (painting()) { /* * This change broke note-painted during mouse movement, * causing notes to be added at the next snap, not the current * snap: * * x = snapped_x(current_x()); * * However, using "if (snap_current_x())" broke drawing at * PPQN > 192. * * ca 2025-10-23 Gets snapped properly in add_painted_note(). * Same issue as above, doh! * * x = snapped_x(x); */ convert_xy(x, y, tick, note); if (add_painted_note(tick, note)) { set_dirty(); if (track().expanded_recording()) frame64()->follow_progress(true); } } if (paste()) flag_dirty(); } bool qseqroll::zoom_key_press (bool shifted, int key) { bool result = false; if (shifted) { if (key == Qt::Key_Z) { result = frame64()->zoom_in(); } else if (key == Qt::Key_V) { result = v_zoom_in(); } } else { if (key == Qt::Key_Z) { result = frame64()->zoom_out(); } else if (key == Qt::Key_0) { if (m_v_zooming) result = reset_v_zoom(); else result = frame64()->reset_zoom(); } else if (key == Qt::Key_V) { result = v_zoom_out(); } } return result; } /** * Handles keystrokes for note movement, zoom, and more. These key names are * located in /usr/include/x86_64-linux-gnu/qt5/QtCore/qnamespace.h (for * Debian/Ubuntu Linux). * * We could simplify this a bit by creating a keystroke object. * * At first, qseqeditframe gets the keystrokes. But giving focus to the * qseqroll causes both to get the keystrokes. */ void qseqroll::keyPressEvent (QKeyEvent * ev) { int key = ev->key(); bool isctrl = bool(ev->modifiers() & Qt::ControlModifier); bool isshift = bool(ev->modifiers() & Qt::ShiftModifier); bool ismeta = bool(ev->modifiers() & Qt::MetaModifier); bool done = false; if (key == Qt::Key_Delete || key == Qt::Key_Backspace) { if (track().remove_selected()) done = mark_modified(); } else { if (perf().is_pattern_playing()) { /* * The space and period keystrokes are handled at the top of * qseqeditframe64::keyPressEvent(). The zoom keys are repeated * here and below as well. 2021-06-29 let's try allowing note * movement during playback. */ if (! isctrl && ! ismeta) { done = movement_key_press(key); if (done) done = mark_modified(); else done = zoom_key_press(isshift, key); } } else { done = movement_key_press(key); if (! done && ! isctrl && ! ismeta) done = zoom_key_press(isshift, key); if (done) { done = mark_modified(); } else { if (isshift) { std::string filename; switch (key) { case Qt::Key_J: /* * No alteration setting, as the human will jitter the * timing quite naturally. */ track().push_jitter_notes(usr().jitter_range(snap())); done = true; break; case Qt::Key_N: /* note-map while recording */ done = perf().set_recording ( track(), alteration::notemap, toggler::flip ); break; case Qt::Key_Q: /* set quantized recording */ done = perf().set_recording ( track(), alteration::quantize, toggler::flip ); break; case Qt::Key_R: if (track().set_recording(toggler::flip)) done = true; break; } } else if (isctrl) { midipulse tick = perf().get_tick(); midipulse len = track().get_length(); switch (key) { /* * See movement_key_press(). Can we reconcile that * with this? */ case Qt::Key_Left: done = true; perf().set_tick(tick - snap(), true); /* no reset */ track().set_last_tick(tick - snap()); break; case Qt::Key_Right: done = true; perf().set_tick(tick + snap(), true); /* no reset */ track().set_last_tick(tick + snap()); break; case Qt::Key_Home: done = true; track().set_last_tick(0); /* sets it to beginning */ if (not_nullptr(frame64())) frame64()->scroll_to_tick(0); break; case Qt::Key_End: done = true; track().set_last_tick(); /* sets it to length */ if (not_nullptr(frame64())) frame64()->scroll_to_tick(len); break; case Qt::Key_A: done = true; track().select_all(); (void) get_selected_box(); break; case Qt::Key_C: done = true; track().copy_selected(); break; case Qt::Key_D: done = true; sequence::clear_clipboard(); /* drop clipboard */ break; case Qt::Key_E: done = true; track().select_by_channel(frame64()->edit_channel()); (void) get_selected_box(); break; case Qt::Key_K: done = true; analyze_seq_notes(); break; case Qt::Key_N: done = true; track().select_notes_by_channel ( frame64()->edit_channel() ); (void) get_selected_box(); break; case Qt::Key_V: done = true; start_paste(); setCursor(Qt::CrossCursor); break; case Qt::Key_X: if (track().cut_selected()) done = true; break; case Qt::Key_Z: done = true; track().pop_undo(); break; } } else if (ismeta) { // nothing yet } else { midipulse len = track().get_length(); switch (key) { case Qt::Key_Home: done = true; track().set_last_tick(0); /* sets it to beginning */ if (not_nullptr(frame64())) frame64()->scroll_to_tick(0); break; case Qt::Key_End: done = true; track().set_last_tick(); /* sets it to length */ if (not_nullptr(frame64())) frame64()->scroll_to_tick(len); break; case Qt::Key_C: if (frame64()->repitch_selected()) done = mark_modified(); break; case Qt::Key_F: if (track().edge_fix()) done = mark_modified(); break; case Qt::Key_I: done = true; set_adding(true); break; case Qt::Key_P: done = true; set_adding(true); break; case Qt::Key_Q: /* quantize selected notes */ if (track().push_quantize_notes(1)) done = mark_modified(); break; case Qt::Key_R: /* default random == 8 */ /* * No alteration setting, as the human will randomize * the note velocity quite naturally. This function * pushes undo as well. */ if (track().randomize_note_velocities()) done = mark_modified(); break; case Qt::Key_T: /* tighten selected notes */ if (track().push_quantize_notes(2)) done = mark_modified(); break; case Qt::Key_U: if (track().remove_unlinked_notes()) done = mark_modified(); break; case Qt::Key_X: set_adding(false); done = true; break; case Qt::Key_Equal: set_adding(false); (void) track().verify_and_link(true); /* w/wrap */ done = true; break; } } } } } if (done) { set_dirty(); flag_dirty(); /* \tricky ca 2023-08-20 */ } else QWidget::keyPressEvent(ev); } bool qseqroll::movement_key_press (int key) { bool result = false; if (track().any_selected_notes()) { if (key == Qt::Key_Left) { move_selected_notes(-1, 0); result = mark_modified(); } else if (key == Qt::Key_Right) { move_selected_notes(1, 0); result = mark_modified(); } else if (key == Qt::Key_Down) { move_selected_notes(0, 1); result = mark_modified(); } else if (key == Qt::Key_Up) { move_selected_notes(0, -1); result = mark_modified(); } } return result; } /** * Proposed new function to encapsulate the movement of selections even * more fully. Works with the four arrow keys. * * Note that the movement vertically is different for the selection box versus * the notes. While the movement values are -1, 0, or 1, the differences are * as follows: * * - Selection box vertical movement: * - -1 is up one note snap. * - 0 is no vertical movement. * - +1 is down one note snap. * - Note vertical movement: * - -1 is down one note. * - 0 is no note vertical movement. * - +1 is up one note. * * \param dx * The amount to move the selection box or the selection horizontally. * Values are -1 (left one time snap), 0 (no movement), and +1 (right one * snap). Obviously values other than +-1 can be used for larger * movement, but the GUI doesn't yet support that ... we could implement * movement by "pages" some day. * * \param dy * The amount to move the selection box or the selection vertically. See * the notes above. */ void qseqroll::move_selected_notes (int dx, int dy) { if (paste()) { /* move_selection_box(dx, dy); */ } else { int snap_x = dx * snap(); /* time-stamp snap */ if (track().any_selected_notes()) /* redundant! */ { int snap_y = -dy; /* note pitch snap */ track().move_selected_notes(snap_x, snap_y); } else if (snap_x != 0) { track().set_last_tick(track().get_last_tick() + snap_x); } } } #if defined USE_GROW_SELECTED_NOTES_FUNCTION /** * Proposed new function to encapsulate the movement of selections even * more fully. Currently no one calls this function! * * \param dx * The amount to grow the selection horizontally. Values are -1 (left one * time snap), 0 (no stretching), and +1 (right one snap). Obviously * values other than +-1 can be used for larger stretching, but the GUI * doesn't yet support that. */ void qseqroll::grow_selected_notes (int dx) { if (! paste()) { int snap_x = dx * snap(); /* time-stamp snap */ growing(true); track().grow_selected(snap_x); } } #endif /** * Provides the base sizing of the piano roll. If less than the width of the * parent frame, it is increased to that, so that the roll covers the whole * scrolling area. */ QSize qseqroll::sizeHint () const { int w = frame64()->width(); int h = total_height(); int len = z().tix_to_pix(track().get_length_plus()); /* was get_length()) */ if (len > 0 && len < w) len = w; len += m_keypadding_x; /* c_keyboard_padding */ return QSize(len, h); } /** * Snaps the y pixel to the height of a piano key. Do we want to snap upward * or downward? Let's try upward snap for awhile. * * \param [in,out] y * The vertical pixel value to be snapped. */ void qseqroll::snap_y (int & y) { y -= y % unit_height(); /* y += y % unit_height() */ } /** * Provides an override to change the mouse "cursor" based on whether adding * notes is active, or not. * * \param a * The value of the status of adding (e.g. a note). */ void qseqroll::set_adding (bool a) { qseqbase::set_adding(a); if (a) setCursor(Qt::PointingHandCursor); /* Qt doesn't have a pencil */ else setCursor(Qt::ArrowCursor); frame64()->update_note_entry(a); /* updates checkable button */ set_dirty(); } /** * The current (x, y) drop points are snapped, and the pasting flag is set to * true. Then this function Gets the box that selected elements are in, then * adjusts for the clipboard being shifted to tick 0. */ void qseqroll::start_paste () { (void) snap_current_x(); (void) snap_current_y(); drop_x(current_x()); drop_y(current_y()); paste(true); midipulse tick_s, tick_f; int note_h, note_l; track().clipboard_box(tick_s, note_h, tick_f, note_l); convert_tn_box_to_rect(tick_s, tick_f, note_h, note_l, selection()); selection().xy_incr(drop_x(), drop_y() - selection().y()); m_sel_offset_x = 0; m_sel_offset_y = 0; } /** * Sets the drum/note mode status. * * \param mode * The drum or note mode status. */ void qseqroll::update_edit_mode (sequence::editmode mode) { m_edit_mode = mode; } /** * Sets the current chord to the given value. * * \param chord * The desired chord value. */ void qseqroll::set_chord (int chord) { chords c = int_to_chord(chord); if (m_chord != c) { m_chord = c; if (is_initialized()) set_dirty(); } } void qseqroll::set_key (int key) { keys k = int_to_key(key); if (m_key != k) { m_key = k; if (is_initialized()) set_dirty(); } } void qseqroll::set_scale (int scale) { scales s = int_to_scale(scale); if (m_scale != s) { m_scale = s; if (is_initialized()) set_dirty(); } } void qseqroll::analyze_seq_notes () { std::vector outkeys; std::vector outscales; int results = analyze_notes(track().events(), outkeys, outscales); if (results > 0) { std::string message; for (int r = 0; r < results; ++r) { int k = static_cast(outkeys[r]); int s = static_cast(outscales[r]); char temp[80]; snprintf ( temp, sizeof temp, "Analysis %d: Key %s, Scale '%s'\n", r + 1, musical_key_name(int_to_key(k)).c_str(), musical_scale_name(int_to_scale(s)).c_str() ); message += temp; } if (not_nullptr(m_analysis_msg)) delete m_analysis_msg; m_analysis_msg = new QMessageBox(this); m_analysis_msg->setWindowTitle("Estimated Scale(s)"); m_analysis_msg->setText(qt(message)); m_analysis_msg->setModal(false); m_analysis_msg->show(); } } /** * This function is called in qseqeditframe64 :: conditional_update(). * * For expansion, here, we need to set some kind of flag for clearing the * viewport before repainting. */ bool qseqroll::follow_progress (qscrollmaster * qsm, bool expand) { bool result = false; QScrollBar * hadjust = qsm->h_scroll(); if (expand) { midipulse progtick = track().expand_value(); if (progtick != 0) { int newx = z().tix_to_pix(progtick); hadjust->setValue(newx); result = true; } } else { int w = qsm->width(); midipulse progtick = track().get_last_tick(); int progx = z().tix_to_pix(progtick); int page = progx / w; int oldpage = scroll_page(); bool newpage = page != oldpage; if (newpage) { scroll_page(page); scroll_offset(progx); hadjust->setValue(progx); result = true; } } return result; } /** * Creates a label to show the note information. We cannot get the * generic_tooltip() function to work properly. * * The odd thing is that the font is bold in the main window, but * regular in the external window. */ void qseqroll::show_note_tooltip (int mx, int my) { int note; midipulse tick; convert_xy(mx, my, tick, note); sequence::note_info ni = track().find_note(tick, note); if (ni.valid()) { std::string s = perf().pulses_to_measure_string(ni.start()); std::string f = perf().pulses_to_measure_string(ni.finish()); std::string temp = "#"; temp += std::to_string(ni.note()); temp += ": "; temp += s; temp += "-"; temp += f; temp += " Vel "; temp += std::to_string(ni.velocity()); #if defined SEQ66_SHOW_GENERIC_TOOLTIPS generic_tooltip(this, temp, mx, my); #else if (not_nullptr(m_note_tooltip)) delete m_note_tooltip; m_note_tooltip = new QLabel(qt(temp), this); /* * Using the existing style might render the text the same color * as the background, so we need to set the colors as per our * Seq66 palette. */ QPalette & p = const_cast(m_note_tooltip->palette()); p.setColor(m_note_tooltip->backgroundRole(), back_color()); p.setColor(m_note_tooltip->foregroundRole(), fore_color()); m_note_tooltip->setPalette(p); m_note_tooltip->show(); m_note_tooltip->move(mx + 3, my - note_height() - 3); #endif } else { #if defined SEQ66_SHOW_GENERIC_TOOLTIPS generic_tooltip(this, "", mx, my); #else delete m_note_tooltip; m_note_tooltip = nullptr; #endif } } } // namespace seq66 /* * qseqroll.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qseqtime.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseqtime.cpp * * This module declares/defines the base class for drawing the * time/measures bar at the top of the patterns/sequence editor. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2026-04-30 * \license GNU GPLv2 or above * */ #include #include /* std::strcat() */ #include "cfg/settings.hpp" /* seq66::usr() config functions */ #include "play/performer.hpp" /* seq66::performer class */ #include "qseqeditframe64.hpp" /* seq66::qseqeditframe64 class */ #include "qseqtime.hpp" /* seq66::qseqtime class */ #include "qt5_helpers.hpp" /* seq66::qt_timer() */ namespace seq66 { /** * Base font size in points. */ static const int sc_font_size = 10; /* * Marker/label tweaks. The presence of these corrections means we need to * coordinate between GUI elements better :-(. Also note we increased the font * size and had to change locations and size of the boxes for the markers. */ static const int s_x_tick_fix = 2; /* adjusts vertical grid lines (2) */ static const int s_time_fix = 1; /* seqtime offset from seqroll (9) */ static const int s_timesig_fix = 8; /* time-sig offset from seqroll (18)*/ static const int s_L_timesig_fix = 8; /* time-sig offset from "L" marker */ static const int s_L_fix = 3; /* adjust position of "o" mark (6) */ static const int s_o_fix = 0; /* adjust position of "o" mark (6) */ static const int s_LR_box_y = 10; static const int s_LR_box_w = sc_font_size; static const int s_LR_box_h = 24; static const int s_END_fix = 26; /* adjust position of "END" box (18)*/ static const int s_END_box_w = 27; static const int s_END_box_h = 24; static const int s_END_y = 20; /* keep the same as s_text_y */ static const int s_text_y = 20; static const int s_ts_text_y = 19; /** * Principal constructor. */ qseqtime::qseqtime ( performer & p, sequence & s, qseqeditframe64 * frame, int zoom, QWidget * /* parent // QScrollArea */ ) : QWidget (frame), qseqbase (p, s, frame, zoom, c_default_snap), m_timer (nullptr), m_font (), m_move_L_marker (false), m_expanding (s.expanded_recording()), m_R_marker () { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); m_font.setBold(true); m_font.setPointSize(sc_font_size); setMouseTracking(true); /* track mouse movement without a click */ set_snap(track().snap()); m_L_marker[0] = 'L'; m_L_marker[1] = 0; m_R_marker[0] = 'R'; m_R_marker[1] = 0; set_END_marker(s.expanded_recording()); m_timer = qt_timer(this, "qseqtime", 4, SLOT(conditional_update())); // 2 } /** * This virtual destructor stops the timer. */ qseqtime::~qseqtime () { if (not_nullptr(m_timer)) m_timer->stop(); } void qseqtime::set_END_marker (bool expanding) { m_expanding = expanding; (void) snprintf(m_END_marker, sizeof m_END_marker, "END"); if (expanding) std::strcat(m_END_marker, ">"); } /** * In an effort to reduce CPU usage when simply idling, this function calls * update() only if necessary. See qseqbase::check_dirty(). */ void qseqtime::conditional_update () { if (perf().needs_update() || check_dirty()) update(); } /** * Draws the time panel. */ void qseqtime::paintEvent (QPaintEvent * qpep) { QRect r = qpep->rect(); QPainter painter(this); QBrush brush(backtime_paint(), Qt::SolidPattern); QPen pen(Qt::black); pen.setStyle(Qt::SolidLine); painter.setPen(pen); painter.setBrush(brush); painter.setFont(m_font); painter.drawRect /* draw the border */ ( c_keyboard_padding_x + 1, 0, width(), height() - 1 ); /* * The ticks_per_step value needs to be figured out. Why 6 * m_zoom? 6 * is the number of pixels in the smallest divisions in the default * seqroll background. This code needs to be put into a function. * Actually, odd beats are not allowed in MIDI. */ draw_grid(painter, r); draw_markers(painter); } /** * Code fixed to make sure the whole rectangle is filled with vertical * bars. See qseqroll for more notes. * * midipulse endtick = ts.sig_end_tick != 0 ? * ts.sig_end_tick : pix_to_tix(r.x() + r.width()); * * Vertical line at each measure; number each measure. * * Here we could speed things up by avoiding extra calculations if * the time signature has not changed. */ void qseqtime::draw_grid (QPainter & painter, const QRect & r) { QBrush brush(Qt::lightGray, Qt::SolidPattern); QPen pen(Qt::black); int count = track().time_signature_count(); midipulse ticks_per_step = z().pulses_per_substep(); midipulse endtick = z().pix_to_tix(r.x() + r.width()); int sizeheight = size().height(); for (int tscount = 0; tscount < count; ++tscount) { const sequence::timesig & ts = track().get_time_signature(tscount); if (ts.sig_beat_width == 0) break; bool skip_substep = false; int bpbar = ts.sig_beats_per_bar; int bwidth = ts.sig_beat_width; bool valid_bw = is_power_of_2(bwidth); midipulse ticks_per_four = z().pulses_per_partial_beat(bpbar, bwidth); midipulse ticks_per_beat = midipulse(z().pulses_per_beat(bwidth)); midipulse ticks_per_bar = z().pulses_per_bar(bpbar, bwidth); midipulse starttick = ts.sig_start_tick; starttick -= starttick % ticks_per_step; for (midipulse tick = starttick; tick < endtick; tick += ticks_per_step) { int x_offset = xoffset(tick) - scroll_offset_x() + s_x_tick_fix; int penwidth; enum Qt::PenStyle penstyle; if (tick % ticks_per_bar == 0) /* thick solid line */ { char bar[32]; int measure = track().measure_number(tick); snprintf(bar, sizeof bar, "%d", measure); QString qbar(bar); pen.setColor(text_time_paint()); painter.setPen(pen); painter.drawText(x_offset + 3, 10, qbar); penwidth = measure_pen_width(); penstyle = measure_pen_style(); pen.setColor(beat_color()); skip_substep = true; /* don't mess meas. */ } else if (tick % ticks_per_beat == 0) /* thin every beat */ { penwidth = beat_pen_width(); penstyle = beat_pen_style(); pen.setColor(beat_color()); } else if (tick % ticks_per_four == 0 && valid_bw) { /* * Adding this line is problematic if the beat-width is * not the power-of-two that MIDI requires. Keep the * display clean, if incomplete. */ penwidth = 1; penstyle = fourth_pen_style(); /* Qt::DashDotLine */ pen.setColor(extra_color()); /* beat_color()) */ } else { if (skip_substep) /* hmmmmmmmm */ { penwidth = 0; skip_substep = false; } else { penwidth = 1; penstyle = step_pen_style(); /* Qt::DotLine */ pen.setColor(step_color()); } } if (penwidth > 0) { pen.setWidth(penwidth); pen.setStyle(penstyle); painter.setPen(pen); painter.drawLine(x_offset, 0, x_offset, sizeheight); } } } } void qseqtime::draw_markers (QPainter & painter /* , const QRect & r */ ) { int xoff_left = scroll_offset_x(); int xoff_right = scroll_offset_x() + width(); midipulse length = track().get_length(); int end = xoffset(length) - s_END_fix; int left = xoffset(perf().get_left_tick()) + s_L_fix; int right = xoffset(perf().get_right_tick()) + s_time_fix - s_LR_box_w; int now = xoffset(perf().get_tick() % length) + s_o_fix; QBrush brush(Qt::lightGray, Qt::SolidPattern); QPen pen(Qt::black); painter.setPen(pen); /* * Draw end of seq label, label background. */ if (! perf().is_pattern_playing() && (now != left) && (now != right)) { if (now >= xoff_left && now <= xoff_right) { pen.setColor(progress_color()); painter.setPen(pen); painter.drawText(now, 18, "o"); } } pen.setColor(Qt::black); brush.setColor(Qt::black); brush.setStyle(Qt::SolidPattern); painter.setBrush(brush); painter.setPen(pen); if (left >= xoff_left && left <= xoff_right) { painter.setBrush(brush); painter.drawRect(left, s_LR_box_y, s_LR_box_w, s_LR_box_h); pen.setColor(Qt::white); painter.setPen(pen); painter.drawText(left + 1, s_text_y, m_L_marker); /* "L" */ } pen.setColor(Qt::black); painter.setPen(pen); painter.drawRect(end - 1, s_LR_box_y, s_END_box_w, s_END_box_h); pen.setColor(Qt::white); painter.setPen(pen); painter.drawText(end + 1, s_END_y, m_END_marker); /* "END" */ if (right >= xoff_left && right <= xoff_right) { int drend = std::abs(end - right); if (drend > s_END_fix) { pen.setColor(Qt::black); painter.setBrush(brush); painter.setPen(pen); painter.drawRect(right - 1, s_LR_box_y, s_LR_box_w, s_LR_box_h); pen.setColor(Qt::white); /* text */ painter.setPen(pen); painter.drawText(right, s_text_y, m_R_marker); /* "R" */ } } int count = track().time_signature_count(); for (int tscount = 0; tscount < count; ++tscount) { const sequence::timesig & ts = track().get_time_signature(tscount); if (ts.sig_beat_width == 0) break; int n = ts.sig_beats_per_bar; int d = ts.sig_beat_width; midipulse start = ts.sig_start_tick; int pos = xoffset(start) + s_timesig_fix;; int dlstart = std::abs(pos - left); std::string text = std::to_string(n); text += "/"; text += std::to_string(d); if (dlstart < 8) pos += s_L_timesig_fix; pen.setColor(text_time_paint()); /* Qt::black */ painter.setPen(pen); painter.drawText(pos + 1, s_ts_text_y, qt(text)); } } void qseqtime::resizeEvent (QResizeEvent * qrep) { QWidget::resizeEvent(qrep); /* qrep->ignore() */ } /** * There is a trick to this function that to document and take further * advantage of. * * Top half: If clicking in the top half of the time-bar, whether and * L- or R-click, the time is set to that value, and the effect can be * seen in the main window's beat-indicator. * * Bottom half: If clicking in the bottom * half, of course, the L or R marker is set, depending on which button * is pressed. See the qperftime versoin of this function. */ void qseqtime::mousePressEvent (QMouseEvent * ev) { midipulse tick = z().pix_to_tix(qt_mouse_x(ev)); if (snap() > 0) tick -= (tick % snap()); if (qt_mouse_y(ev) > height() / 2) /* bottom half */ { bool isctrl = bool(ev->modifiers() & Qt::ControlModifier); if (ev->button() == Qt::LeftButton) /* move L/R markers */ { if (isctrl) { perf().set_tick(tick, true); /* set_start_tick() */ } else { /* * ca 2025-07-03. Related to issue #138, allow auto-step * to start at the selected L/playhead marker. * * Too global? * * perf().set_left_tick_snap(tick, snap()); * perf().set_last_ticks(perf().get_left_tick()); */ perf().set_last_tick_seq(track(), tick, snap()); } set_dirty(); } else if (ev->button() == Qt::MiddleButton) /* set start tick */ { perf().set_tick(tick, true); /* set_start_tick() */ set_dirty(); } else if (ev->button() == Qt::RightButton) { perf().set_right_tick_snap(tick, snap()); set_dirty(); } } else /* top half */ { perf().set_tick(tick, true); /* reposition time */ set_dirty(); } if (is_dirty()) frame64()->set_dirty(); } void qseqtime::mouseReleaseEvent (QMouseEvent *) { // no code } void qseqtime::mouseMoveEvent(QMouseEvent * ev) { setCursor ( qt_mouse_y(ev) > height() / 2 ? Qt::PointingHandCursor : Qt::UpArrowCursor ); } void qseqtime::keyPressEvent (QKeyEvent * ev) { bool isctrl = bool(ev->modifiers() & Qt::ControlModifier); if (isctrl) { /* no code yet */ } else { bool isshift = bool(ev->modifiers() & Qt::ShiftModifier); midipulse s = snap() > 0 ? snap() : 1 ; if (ev->key() == Qt::Key_Left) { if (m_move_L_marker) /* set by Shift-L, unset by Shift-R */ { /* * perf().set_left_tick_snap(tick, snap()); */ midipulse tick = perf().get_left_tick() - s; perf().set_last_tick_seq(track(), tick, snap()); } else { midipulse tick = perf().get_right_tick() - s; perf().set_right_tick_snap(tick, snap()); } set_dirty(); ev->accept(); } else if (ev->key() == Qt::Key_Right) { if (m_move_L_marker) /* set by Shift-L, unset by Shift-R */ { /* * perf().set_left_tick_snap(tick, snap()); */ midipulse tick = perf().get_left_tick() + s; perf().set_last_tick_seq(track(), tick, snap()); } else { midipulse tick = perf().get_right_tick() + s; perf().set_right_tick_snap(tick, snap()); } set_dirty(); ev->accept(); } else if (ev->key() == Qt::Key_L) { if (isshift) { m_move_L_marker = true; ev->accept(); } } else if (ev->key() == Qt::Key_R) { if (isshift) { m_move_L_marker = false; ev->accept(); } } } } QSize qseqtime::sizeHint () const { int w = frame64()->width(); int len = z().tix_to_pix(track().get_length_plus()); /* get_length()); */ if (len < w) len = w; len += c_keyboard_padding_x; return QSize(len, 22); } /** * We don't want the scroll wheel to accidentally scroll the time * horizontally, so this override does nothing but accept() the event. * * ignore() just let's the parent handle the event, which allows scrolling to * occur. For issue #3, we have enabled the scroll wheel in the piano roll * [see qscrollmaster::wheelEvent()], but we disable it here. So this is a * partial solution to the issue. */ void qseqtime::wheelEvent (QWheelEvent * qwep) { qwep->accept(); } } // namespace seq66 /* * qseqtime.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qsessionframe.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsessionframe.cpp * * This module declares/defines the base class for the main window. * * \library seq66 application * \author Chris Ahlstrom * \date 2020-08-24 * \updates 2025-07-09 * \license GNU GPLv2 or above * */ #include /* Needed for QKeyEvent::accept() */ #include "seq66-config.h" /* defines SEQ66_QMAKE_RULES */ #include "os/daemonize.hpp" /* seq66::signal_for_restart() */ #include "play/performer.hpp" /* seq66::performer */ #include "util/strfunctions.hpp" /* seq66::int_to_string() */ #include "qsessionframe.hpp" /* seq66::qsessionframe, this class */ #include "qsmainwnd.hpp" /* seq66::qsmainwnd */ #include "qt5_helpers.hpp" /* seq66::qt(), qt_set_icon() etc. */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qsessionframe.h" #else #include "forms/qsessionframe.ui.h" #endif namespace seq66 { /** * Limit for showing macro bytes in the combo-box. */ static const int c_macro_byte_max = 18; /** * Principle constructor. */ qsessionframe::qsessionframe ( performer & p, qsmainwnd * mainparent, QWidget * parent ) : QFrame (parent), ui (new Ui::qsessionframe), m_main_window (mainparent), m_performer (p), m_current_track (0), m_current_text_number (0), m_track_high (p.sequence_high()) { ui->setupUi(this); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); ui->sessionManagerNameText->setEnabled(false); ui->sessionNameText->setEnabled(false); ui->sessionUrlText->setEnabled(false); ui->displayNameText->setEnabled(false); ui->clientIdText->setEnabled(false); ui->songPathText->setEnabled(false); ui->lineEditLastUsedDir->setEnabled(false); ui->lineEditLastUsedDir->setText(qt(rc().last_used_dir())); session_log_file(usr().option_logfile()); connect ( ui->lineEditLogFile, SIGNAL(editingFinished()), this, SLOT(slot_log_file()) ); QIcon icon = QIcon::fromTheme("edit-clear", QIcon(":/images/icon")); ui->pushButtonLogFileClear->setIcon(icon); connect ( ui->pushButtonLogFileClear, SIGNAL(clicked(bool)), this, SLOT(slot_log_file_clear()) ); ui->pushButtonReload->setEnabled(false); connect ( ui->pushButtonReload, SIGNAL(clicked(bool)), this, SLOT(slot_flag_reload()) ); populate_macro_combo(); reload_song_info(); connect ( ui->plainTextSongInfo, SIGNAL(textChanged()), this, SLOT(slot_songinfo_change()) ); ui->pushButtonSaveInfo->setEnabled(false); connect ( ui->pushButtonSaveInfo, SIGNAL(clicked(bool)), this, SLOT(slot_save_info()) ); sync_track_label(); /* * Track (pattern) spin-box. */ ui->spinBoxTrackNumber->setEnabled(true); ui->spinBoxTrackNumber->setReadOnly(false); ui->spinBoxTrackNumber->setRange(0, m_track_high - 1); ui->spinBoxTrackNumber->setValue(0); connect ( ui->spinBoxTrackNumber, SIGNAL(valueChanged(int)), this, SLOT(slot_track_number(int)) ); #if defined ALLOW_TRACK_NUMBER_EDIT // maybe later connect ( ui->spinBoxTrackNumber, SIGNAL(textChanged(const QString &)), this, SLOT(slot_edit_track_number()) ); #endif } qsessionframe::~qsessionframe() { delete ui; } void qsessionframe::sync_track_label () { std::string tlabel = "Song Info "; if (m_current_track > 0) { tlabel = "Track Info "; tlabel += std::to_string(int(m_current_track)); } ui->labelTrackInfo->setText(qt(tlabel)); } void qsessionframe::sync_track_high () { int high = int(perf().sequence_high()); if (m_track_high != high) { bool reduced = m_track_high < high; m_track_high = high; if (reduced) { m_current_track = high - 1; ui->spinBoxTrackNumber->setValue(high - 1); sync_track_label(); } ui->spinBoxTrackNumber->setMaximum(high - 1); } } void qsessionframe::enable_reload_button (bool flag) { ui->pushButtonReload->setEnabled(flag); } void qsessionframe::slot_flag_reload () { signal_for_restart(); /* warnprint("Session reload request"); */ } /** * Also gets the characters remaining after translation to encoded * MIDI bytes. Too slow? */ void qsessionframe::slot_songinfo_change () { QString qtex = ui->plainTextSongInfo->toPlainText(); std::string text = string_to_midi_bytes(qtex.toStdString()); size_t remainder = c_meta_text_limit - text.size(); std::string rem = int_to_string(int(remainder)); ui->labelCharactersRemaining->setText(qt(rem)); ui->pushButtonSaveInfo->setEnabled(true); } void qsessionframe::slot_save_info () { QString qtex = ui->plainTextSongInfo->toPlainText(); std::string text = string_to_midi_bytes ( qtex.toStdString(), c_meta_text_limit ); perf().song_info(text, m_current_track); ui->pushButtonSaveInfo->setEnabled(false); } void qsessionframe::slot_track_number (int trk) { sync_track_high(); /* adjust the maximum value */ if (trk != m_current_track) { if (trk == 0) { reload_song_info(); /* track zero is treated specially */ } else { bool nextmatch = false; seq66::event e; std::string trkinfo; if (trk == 0) { e = perf().get_track_info_event(trk, nextmatch); trkinfo = e.get_text(); } else { trkinfo = perf().get_all_track_text(trk); } if (trkinfo.empty()) { ui->plainTextSongInfo->document()->setPlainText("*No text*"); ui->pushButtonSaveInfo->setEnabled(false); } else { size_t remainder = c_meta_text_limit - trkinfo.size(); std::string rem = int_to_string(int(remainder)); ui->plainTextSongInfo->document()->setPlainText(qt(trkinfo)); ui->labelCharactersRemaining->setText(qt(rem)); ui->pushButtonSaveInfo->setEnabled(false); } } } m_current_track = trk; sync_track_label(); } /* * New song-info edit control and the characters-remaining label. * Tricky, when getting the song info from the performer, it is already * in normal string format. * * std::string songinfo = midi_bytes_to_string(perf().song_info()); */ void qsessionframe::reload_song_info () { std::string songinfo = perf().song_info(); size_t remainder = c_meta_text_limit - songinfo.size(); std::string rem = int_to_string(int(remainder)); ui->plainTextSongInfo->document()->setPlainText(qt(songinfo)); ui->labelCharactersRemaining->setText(qt(rem)); ui->pushButtonSaveInfo->setEnabled(false); } void qsessionframe::populate_macro_combo () { tokenization t = perf().macro_names(); bool macrosactive = perf().macros_active(); if (macrosactive) macrosactive = ! t.empty(); if (! t.empty()) { int counter = 0; ui->macroComboBox->clear(); for (const auto & name : t) { if (name.empty()) { break; } else { midibytes bytes = perf().macro_bytes(name); std::string bs = midi_bytes_string(bytes, c_macro_byte_max); std::string combined = name; combined += ": "; combined += bs; QString combotext(qt(combined)); ui->macroComboBox->insertItem(counter++, combotext); } } connect ( ui->macroComboBox, SIGNAL(currentTextChanged(const QString &)), this, SLOT(slot_macro_pick(const QString &)) ); } if (macrosactive) { ui->checkBoxMacrosActive->setChecked(true); connect ( ui->checkBoxMacrosActive, SIGNAL(clicked(bool)), this, SLOT(slot_macros_active()) ); } else { ui->checkBoxMacrosActive->setChecked(false); ui->macroComboBox->setEnabled(false); if (t.empty()) ui->checkBoxMacrosActive->setEnabled(false); } /* * The user should not have run-time control over this. */ ui->checkBoxMacrosActive->setEnabled(false); } void qsessionframe::slot_macros_active() { bool active = ui->checkBoxMacrosActive->isChecked(); perf().macros_active(active); ui->macroComboBox->setEnabled(active); rc().auto_ctrl_save(true); ui->pushButtonReload->setEnabled(true); } void qsessionframe::slot_macro_pick (const QString & name) { if (! name.isEmpty()) { std::string line = name.toStdString(); size_t pos = line.find_first_of(":"); line = line.substr(0, pos); perf().send_macro(line); } } void qsessionframe::session_manager (const std::string & text) { ui->sessionManagerNameText->setText(qt(text)); } void qsessionframe::session_path (const std::string & text) { ui->sessionNameText->setText(qt(text)); } void qsessionframe::session_display_name (const std::string & text) { ui->displayNameText->setText(qt(text)); } void qsessionframe::session_client_id (const std::string & text) { ui->clientIdText->setText(qt(text)); } void qsessionframe::session_URL (const std::string & text) { ui->sessionUrlText->setText(qt(text)); } void qsessionframe::session_log_file (const std::string & text) { ui->lineEditLogFile->setText(qt(text)); } void qsessionframe::slot_log_file () { QString text = ui->lineEditLogFile->text(); std::string temp = text.toStdString(); if (temp != usr().option_logfile()) { usr().option_logfile(temp); /* sets usr().option_use_logfile() */ session_log_file(usr().option_logfile()); rc().auto_usr_save(true); usr().modify(); enable_reload_button(true); ui->pushButtonLogFileClear->setEnabled(! temp.empty()); } } void qsessionframe::slot_log_file_clear() { ui->lineEditLogFile->clear(); slot_log_file(); } void qsessionframe::song_path (const std::string & text) { ui->songPathText->setText(qt(text)); } void qsessionframe::last_used_dir (const std::string & text) { ui->lineEditLastUsedDir->setText(qt(text)); } /* * We must accept() the key-event, otherwise even key-events in the QLineEdit * items are propagated to the parent, where they then get passed to the * performer as if they were keyboards controls (such as a pattern-toggle * hot-key). */ void qsessionframe::keyPressEvent (QKeyEvent * event) { event->accept(); } void qsessionframe::keyReleaseEvent (QKeyEvent * event) { event->accept(); } } // namespace seq66 /* * qsessionframe.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qsetmaster.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsetmaster.cpp * * This module declares/defines the base class for the set-master tab. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2026-04-23 * \license GNU GPLv2 or above * * The set-master controls the existence and usage of all sets. For control * of the play-screen (playing set), see the setmapper class. */ #include /* Needed for QKeyEvent::accept() */ #include #include #include #include "seq66-config.h" /* defines SEQ66_QMAKE_RULES */ #include "ctrl/keystroke.hpp" /* seq66::keystroke class */ #include "util/strfunctions.hpp" /* seq66::string_to_int() */ #include "qsetmaster.hpp" /* seq66::qsetmaster tab class */ #include "qsmainwnd.hpp" /* seq66::qsmainwnd main window */ #include "qt5_helpers.hpp" /* seq66::qt_keystroke() etc. */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qsetmaster.h" #else #include "forms/qsetmaster.ui.h" #endif /** * For correcting the width of the set table. Otherwise clicking on the * set-name obscures the first column. Weird. */ static const int c_set_table_fix = 24; // 0; // -12; /** * Specifies the current hardwired value for set_row_heights(). */ static const int c_table_row_height = 18; /* * Don't document the namespace. */ namespace seq66 { /** * Principal constructor. * * \param p * Provides the performer object to use for interacting with this frame. * * \param mainparent * Provides the parent window, which will call this frame up and also * needs to be notified if it goes away. * * \param parent * Provides the parent window/widget for this container window. Defaults * to null. * */ qsetmaster::qsetmaster ( performer & p, qsmainwnd * mainparent, QWidget * parent ) : QFrame (parent), performer::callbacks (p), ui (new Ui::qsetmaster), m_operations ("Set Master Operations"), m_timer (nullptr), m_main_window (mainparent), m_set_buttons (setmaster::Size(), nullptr), m_current_set (seq::unassigned()), m_current_row (seq::unassigned()), m_current_row_count (cb_perf().screenset_count()), m_needs_update (true), m_trigger_mode (false), m_table_initializing (true) { ui->setupUi(this); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); connect ( ui->m_set_name_text, SIGNAL(textEdited(const QString &)), this, SLOT(slot_set_name()) ); ui->m_button_down->setEnabled(false); ui->m_button_up->setEnabled(false); ui->m_button_delete->setEnabled(false); /* * This button is no longer available. The set-master is always available * in its tab. * * if (m_is_permanent) * ui->m_button_close->hide(); * else * connect(ui->m_button_close, SIGNAL(clicked()), this, SLOT(close())); */ connect(ui->m_button_show, SIGNAL(clicked()), this, SLOT(slot_show_sets())); connect(ui->m_button_down, SIGNAL(clicked()), this, SLOT(slot_move_down())); connect(ui->m_button_up, SIGNAL(clicked()), this, SLOT(slot_move_up())); connect(ui->m_button_delete, SIGNAL(clicked()), this, SLOT(slot_delete())); create_set_buttons(); setup_table(); /* row and column sizing */ (void) initialize_table(); /* fill with sets */ (void) populate_default_ops(); /* load key-automation support */ handle_set(0); /* guaranteed to be present */ cb_perf().enregister(this); /* register this for notifications */ m_timer = qt_timer(this, "qsetmaster", 3, SLOT(conditional_update())); } qsetmaster::~qsetmaster() { if (not_nullptr(m_timer)) m_timer->stop(); cb_perf().unregister(this); delete ui; } void qsetmaster::conditional_update () { if (needs_update()) /* perf().needs_update() too iffy */ { for (int s = 0; s < setmaster::Size(); ++s) /* s is the set number */ { bool enabled = cb_perf().is_screenset_available(s); bool checked = s == m_current_set; /* s'set::unassigned() */ m_set_buttons[s]->setEnabled(enabled); m_set_buttons[s]->setChecked(checked); } update(); m_needs_update = false; } } void qsetmaster::setup_table () { QStringList columns; columns << "Set #" << "Seqs" << "Set Name"; /* * This advice is bs. The actual answer is to set the horizontal header to * be visible. * * ui->m_set_table->insertColumn(0), and 1 and 2;; */ ui->m_set_table->setHorizontalHeaderLabels(columns); ui->m_set_table->setSelectionBehavior(QAbstractItemView::SelectRows); /* * ui->m_set_table->setSelectionMode(QAbstractItemView::SingleSelection); */ const int rows = ui->m_set_table->rowCount(); for (int r = 0; r < rows; ++r) ui->m_set_table->setRowHeight(r, c_table_row_height); int w = ui->m_set_table->width(); set_column_widths(w + c_set_table_fix); connect ( ui->m_set_table, SIGNAL(currentCellChanged(int, int, int, int)), this, SLOT(slot_table_click_ex(int, int, int, int)) ); connect ( ui->m_set_table, SIGNAL(cellChanged(int, int)), this, SLOT(slot_cell_changed(int, int)) ); ui->m_button_toggle_trigger_mode->setCheckable(true); ui->m_button_toggle_trigger_mode->setChecked(false); connect ( ui->m_button_toggle_trigger_mode, SIGNAL(clicked()), this, SLOT(slot_toggle_trigger_mode()) ); ui->m_button_set_0->setEnabled(false); connect ( ui->m_button_set_0, SIGNAL(clicked()), this, SLOT(slot_set_0()) ); } /** * Scales the columns against the provided window width. The width factors * should add up to 1. */ void qsetmaster::set_column_widths (int total_width) { ui->m_set_table->setColumnWidth(0, int(0.15f * total_width)); ui->m_set_table->setColumnWidth(1, int(0.15f * total_width)); ui->m_set_table->setColumnWidth(2, int(0.70f * total_width)); } void qsetmaster::current_row (int row) { m_current_row = row; } /** * Do we want to skip non-existent sets? */ bool qsetmaster::initialize_table () { bool result = false; m_table_initializing = true; int rows = setmaster::Size(); /* cb_perf().screenset_count() */ if (rows > 0) { ui->m_set_table->clearContents(); for (int r = 0; r < rows; ++r) ui->m_set_table->setRowHeight(r, c_table_row_height); int active = cb_perf().screenset_count(); if (active > 0) { screenset::sethandler setfunc = ( std::bind ( &qsetmaster::set_line, this, std::placeholders::_1, std::placeholders::_2 ) ); (void) cb_perf().exec_set_function(setfunc); } } m_table_initializing = false; return result; } /** * Retrieve the table cell at the given row and column. * * Issue: * * When this is first called [via initialize_table()], the * QWidgetTableItem does not exist. So we make a new one, which has no * text in it, and insert it. This then sends the cellChanged(int, int) * signal, which calls slot_cell_changed(), which sets the set's name to * empty! This issue must also be checked/fixed in qplaylistframe :: * cell(), qseqeventframe :: cell(), and qmutemaster :: cell(). * * \param row * The row number, which should be in the range of 0 to 32. * * \param col * The column enumeration value, which will be in range. * * \return * Returns a pointer the table widget-item for the given row and column. * If out-of-range, a null pointer is returned. */ QTableWidgetItem * qsetmaster::cell (screenset::number row, column_id col) { int column = int(col); QTableWidgetItem * result = ui->m_set_table->item(row, column); if (is_nullptr(result)) { result = new (std::nothrow) QTableWidgetItem; if (not_nullptr(result)) { ui->m_set_table->setItem(row, column, result); if (col != column_id::set_name) result->setFlags(result->flags() ^ Qt::ItemIsEditable); } } return result; } bool qsetmaster::set_line (screenset & sset, screenset::number row) { bool result = false; QTableWidgetItem * qtip = cell(row, column_id::set_number); if (not_nullptr(qtip)) { int setno = int(row); // int(sset.set_number()); std::string setnostr = std::to_string(setno); qtip->setText(qt(setnostr)); qtip = cell(row, column_id::set_name); // clears sset.name()! if (not_nullptr(qtip)) { const std::string & setname = sset.name(); qtip->setText(qt(setname)); qtip = cell(row, column_id::set_seq_count); if (not_nullptr(qtip)) { int seqcount = int(sset.active_count()); std::string seqcountstr = std::to_string(seqcount); qtip->setText(qt(seqcountstr)); result = true; } } } return result; } /** * Indicates that the MIDI file needs to be updated (saved) and * the GUI needs to be refreshed. */ void qsetmaster::set_needs_update () { m_needs_update = true; m_main_window->enable_save(); } /** * The Delete button is always disabled for row 0. The 0th set must always * exist. */ void qsetmaster::slot_table_click_ex ( int row, int /*column*/, int /*prevrow*/, int /*prevcolumn*/ ) { int rows = cb_perf().screenset_count(); if (rows > 0 && row >= 0 && row < rows) { current_row(row); ui->m_button_down->setEnabled(true); ui->m_button_up->setEnabled(true); ui->m_button_delete->setEnabled(row > 0); ui->m_set_number_text->setText(qt(std::to_string(row))); ui->m_set_name_text->setText(qt(cb_perf().set_name(row))); } } void qsetmaster::slot_cell_changed (int row, int column) { if (! m_table_initializing) { column_id cid = static_cast(column); if (cid == column_id::set_name) { screenset::number s = screenset::number(row); QTableWidgetItem * c = cell(s, cid); QString qtext = c->text(); std::string name = qtext.toStdString(); std::string oldname = cb_perf().set_name(s); if (name != oldname) { cb_perf().screenset_name(s, name); set_needs_update(); } } } } void qsetmaster::slot_toggle_trigger_mode () { m_trigger_mode = ! m_trigger_mode; ui->m_button_set_0->setEnabled(m_trigger_mode); } void qsetmaster::slot_set_0 () { if (m_trigger_mode) m_main_window->slot_set_home(); } void qsetmaster::closeEvent (QCloseEvent * event) { /* * ca 2026-04-23 Let's stop the timer first. */ if (not_nullptr(m_timer)) m_timer->stop(); cb_perf().unregister(this); /* unregister this immediately */ if (not_nullptr(m_main_window)) m_main_window->remove_set_master(); event->accept(); } /** * Creates a grid of buttons in the grid layout. This grid is always * 4 x 8, as discussed in the setmapper::grid_to_set() function, but if a * smaller set number (count) is used, some buttons will be unlabelled and * disabled. * * Note that the largest number of sets is 4 x 8 = 32. This limitation is * necessary because there are only so many available keys on the keyboard for * pattern, mute-group, and set control. * * A set button is valid if the set number falls within the true set count, * which might be less than 32 (e.g. it will be 16 sets with an 8x8 grid). * But for now we will allow all buttons to remain enabled. A set button is * enabled if the set has at least one sequence in it. */ void qsetmaster::create_set_buttons () { const QSize btnsize = QSize(32, 32); for (int s = 0; s < setmaster::Size(); ++s) /* s is set # */ { bool enabled = cb_perf().is_screenset_available(s); int row, column; bool valid = cb_perf().master_index_to_grid(s, row, column); if (valid) { std::string snstring = std::to_string(s); QPushButton * temp = new QPushButton(qt(snstring)); ui->setGridLayout->addWidget(temp, row, column); temp->setFixedSize(btnsize); temp->show(); temp->setEnabled(enabled); temp->setCheckable(true); connect(temp, &QPushButton::released, [=] { handle_set(s); }); m_set_buttons[s] = temp; } } } void qsetmaster::handle_set (int setno) { if (setno != m_current_set) { bool firstset = m_current_set == (-1); // unassigned() (void) cb_perf().set_playing_screenset(setno); ui->m_set_number_text->setText(qt(std::to_string(setno))); ui->m_set_name_text->setText(qt(cb_perf().set_name(setno))); m_current_set = setno; /* * Highlight the current set in the list. Find the row based on set * number. Also show its patterns. */ ui->m_set_table->selectRow(cb_perf().screenset_index(setno)); ui->m_set_contents_text->setPlainText ( qt(cb_perf().set_to_string(setno)) ); /* * If trigger-mode is active, then update the playscreen to that * screenset. */ if (m_trigger_mode) m_main_window->update_bank(setno); if (! firstset) set_needs_update(); } } void qsetmaster::slot_set_name () { if (m_current_set != screenset::unassigned()) { std::string name = ui->m_set_name_text->text().toStdString(); cb_perf().screenset_name(m_current_set, name); (void) initialize_table(); /* refill with sets */ } } void qsetmaster::slot_show_sets () { ui->m_set_contents_text->setPlainText(qt(cb_perf().sets_to_string())); } /** * If this is slow, we will try something else. It is only 32 rows right now. * * int rowcount = ui->m_set_table->rowCount() > 0; */ void qsetmaster::slot_move_down () { int rows = cb_perf().screenset_count(); if (rows > 1) /* cannot move if 1 row */ { int row = current_row(); /* last row clicked */ if (row >= 0 && row < (rows - 1)) /* moveable down? */ move_helper(row, row + 1); } } void qsetmaster::slot_move_up () { int rows = cb_perf().screenset_count(); if (rows > 1) /* cannot move if 1 row */ { int row = current_row(); /* last row clicked */ if (row > 0 && row < rows) /* moveable up? */ move_helper(row, row - 1); } } /** * Provides common code for slot_move_down() and slot_move_up(). Note that * there is a trick here. We cannot swap by set number, but by key value. */ void qsetmaster::move_helper (int oldrow, int newrow) { QTableWidgetItem * c0 = cell(oldrow, column_id::set_number); if (not_nullptr(c0)) { std::string snstring = c0->text().toStdString(); int set0 = string_to_int(snstring); QTableWidgetItem * c1 = cell(newrow, column_id::set_number); if (not_nullptr(c1)) { std::string snstring = c1->text().toStdString(); int set1 = string_to_int(snstring); if (cb_perf().swap_sets(set0, set1)) /* a modify action */ { (void) initialize_table(); /* refill with sets */ ui->m_set_table->selectRow(newrow); set_needs_update(); } } } } /** * Handles the "Delete" button. We do not allow deleting of set 0. This * causes too many issues. * * We're going to make this just clear the screenset, by replacing it with a * new (empty) screenset. */ void qsetmaster::slot_delete () { int rows = cb_perf().screenset_count(); if (rows > 1) /* cannot move if 1 row */ { int row = current_row(); /* last row clicked */ if (row >= 0 && row < rows) /* deleteable? */ { QTableWidgetItem * qtip = cell(row, column_id::set_number); if (not_nullptr(qtip)) { std::string snstr = qtip->text().toStdString(); int setno = string_to_int(snstr); if (setno > 0) { /* * if (cb_perf().remove_set(setno)) */ if (cb_perf().clear_set(setno)) { if (setno == m_current_set) m_current_set = seq::unassigned(); set_needs_update(); } } } } } } /** * Handles set changes from other dialogs. We have a quandary: now * we don't necessarily change the current row, even when "removing" * (actually just clearing now) a screenset.. */ bool qsetmaster::on_set_change (screenset::number setno, performer::change modtype) { int rows = cb_perf().screenset_count(); bool result = m_current_set != setno || rows != m_current_row_count; if (result) { m_current_row_count = rows; if (modtype != performer::change::removed) m_current_set = setno; (void) initialize_table(); /* redraw the set-list (set-table) */ set_needs_update(); /* cause set-buttons to redraw */ } else { result = initialize_table(); /* redraw the set-list (set-table) */ set_needs_update(); /* cause set-buttons to redraw */ } return result; } /* * We must accept() the key-event, otherwise even key-events in the QLineEdit * items are propagated to the parent, where they then get passed to the * performer as if they were keyboards controls (such as a pattern-toggle * hot-key). * * Plus, here, we have no real purpose for the code, so we macro it out. * What's up with that, Spunky? */ void qsetmaster::keyPressEvent (QKeyEvent * event) { #if defined PASS_KEYSTROKES_TO_PARENT keystroke kkey = qt_keystroke(event, keystroke::action::press); bool done = handle_key_press(kkey); if (done) set_needs_update(); else QWidget::keyPressEvent(event); /* event->ignore() */ #else event->accept(); #endif } void qsetmaster::keyReleaseEvent (QKeyEvent * event) { #if defined PASS_KEYSTROKES_TO_PARENT keystroke kkey = qt_keystroke(event, keystroke::action::release); bool done = handle_key_release(kkey); if (done) update(); else QWidget::keyReleaseEvent(event); /* event->ignore() */ #else event->accept(); #endif } #if defined PASS_KEYSTROKES_TO_PARENT bool qsetmaster::handle_key_press (const keystroke & k) { ctrlkey ordinal = k.key(); const keycontrol & kc = cb_perf().key_controls().control(ordinal); bool result = kc.is_usable(); if (result) { automation::slot s = kc.slot_number(); const midioperation & mop = m_operations.operation(s); if (mop.is_usable()) { /* * Note that the "inverse" parameter is based on key press versus * release. Not all automation functions care about this setting. */ automation::action a = kc.action_code(); bool invert = ! k.is_press(); int d0 = 0; int index = kc.control_code(); result = mop.call(a, d0, index, invert); } } return result; } bool qsetmaster::handle_key_release (const keystroke & k) { bool done = cb_perf().midi_control_keystroke(k); if (! done) { // no useful code yet } return done; } #endif // defined PASS_KEYSTROKES_TO_PARENT /** * This is not called when focus changes. */ void qsetmaster::changeEvent (QEvent * event) { QWidget::changeEvent(event); if (event->type() == QEvent::ActivationChange) { // no useful code yet } } bool qsetmaster::set_control ( automation::action a, int /*d0*/, int /*d1*/, int index, bool inverse ) { bool result = a == automation::action::toggle; if (result && ! inverse) handle_set(index); /* index == set number */ return result; } /** * Add a "loop" operation. In this window, it will simply select the * active set. */ bool qsetmaster::populate_default_ops () { midioperation patmop ( opcontrol::category_name(automation::category::loop), /* name */ automation::category::loop, /* category */ automation::slot::loop, /* opnumber */ [this] ( automation::action a, int d0, int d1, int index, bool inverse ) { return set_control(a, d0, d1, index, inverse); } ); return m_operations.add(patmop); } } // namespace seq66 /* * qsetmaster.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qseventslots.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qseventslots.cpp * * This module declares/defines the base class for displaying events in their * editing slots. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-08-13 * \updates 2025-05-09 * \license GNU GPLv2 or above * * Also note that, currently, the editable_events container does not support * a verify_and_link() function like that of the eventlist container. * Eventually, we want to support that feature, so that we can delete a Note * On event and have the corresponding Note Off event automatically deleted * as well. */ #include "play/performer.hpp" /* seq66::performer class */ #include "util/strfunctions.hpp" /* seq66::strings_match() */ #include "qseqeventframe.hpp" #include "qseventslots.hpp" namespace seq66 { /** * Principal constructor for this user-interface object. * * \param p * The parent performer object. * * \param parent * The parent event-editor object. * * \param seq * Provides the sequence represented by this event-editing object. */ qseventslots::qseventslots ( performer & p, qseqeventframe & parent, sequence & s ) : m_parent (parent), m_seq (s), m_event_container (s, p.get_beats_per_minute()), m_current_event (m_event_container), m_event_count (0), m_last_max_timestamp (0), m_measures (0), m_line_count (0), m_line_maximum (999999), /* don't need with Qt table widget */ m_line_overlap (5), m_top_index (0), m_current_index (SEQ66_NULL_EVENT_INDEX), /* -1 */ m_current_row (0), m_top_iterator (), m_bottom_iterator (), m_current_iterator (), m_pager_index (0), m_show_data_as_hex (false), /* hexadecimal() */ m_show_time_as_pulses (false) /* pulses() */ { load_events(); } /** * Grabs the event list from the sequence and uses it to fill the * editable-event list. Determines how many events can be shown in the * GUI [later] and adjusts the top and bottom editable-event iterators to * show the first page of events. * * Note that, for this QWdigetTable support class, the line_maximum() is more * of a sanity check, since the table can grow indefinitely and has no * viewport in the sense the Gtkmm-2.4 version had. * * \return * Returns true if the event iterators were able to be set up as valid. */ bool qseventslots::load_events () { bool result = m_event_container.load_events(); if (result) { m_event_count = m_event_container.count(); if (m_event_count > 0) { if (m_event_count < m_line_count) m_line_count = m_event_count; else m_line_count = line_maximum(); m_current_iterator = m_bottom_iterator = m_top_iterator = m_event_container.begin(); for (int i = 0; i < m_line_count - 1; ++i) { if (increment_bottom() == SEQ66_NULL_EVENT_INDEX) break; } for (auto & ei : m_event_container) ei.second.analyze(); /* creates the event strings */ } else result = false; } if (! result) { m_line_count = 0; m_current_iterator = m_bottom_iterator = m_top_iterator = m_event_container.end(); } return result; } bool qseventslots::load_table () { bool result = m_event_container.count() > 0; if (m_event_count > 0) { int row = 0; for (auto & ei : m_event_container) { set_table_event(ei.second, row); ++row; } } return result; } /** * Any way to easily add the link indexes or add arrows to the linked * note? * * "Link-time L> 0) { int row = 0; result += " No. Ticks Timestamp Event Status Ch. -- D0 D1 Link-time Length Rank\n" ; for (const auto & ei : m_event_container) { result += event_to_string(ei.second, row); ++row; } } return result; } /** * Provides the "printf()" format statement for a data value for both the * data columns in the event table and the data field in the right-hand * editable area of the event editor. */ #define SEQ66_EVENT_DATA_FMT_DEC "%d" #define SEQ66_EVENT_DATA_FMT_HEX "0x%02x" /** * Set the current event, which is the event that is highlighted. Note in * the snprintf() calls that the first digit is part of the data byte, so * that translation is easier. * * \param ei * The iterator that points to the event. * * \param index * The index (re 0) of the event, starting at the top line of the frame. * It is a frame index, not a container index. (NOT TRUE using Qt!) * * \param full_redraw * If true (the default) does a full redraw of the frame. Otherwise, * only the current event is drawn. Generally, the only time a single * event (actually, two adjacent events) is convenient to draw is when * using the arrow keys, where the speed of keystroke auto-repeats makes * the full-frame update scrolling very flickery and disconcerting. */ void qseventslots::set_current_event ( const editable_events::iterator ei, int index, bool /* full_redraw */ ) { int channel = null_channel(); std::string data_0; std::string data_1; const editable_event /* & */ ev = editable_events::cdref(ei); if (ev.is_meta()) { /* * This may not be suitable for all meta events. But we will * add support for using the "sysex" (text) data for storing * data for these events. */ std::string text = ev.get_text(); m_parent.set_event_plaintext(text); } else if (ev.is_sysex()) { std::string text = ev.get_text(); m_parent.set_event_plaintext(text); } else if (ev.is_ex_data()) { data_0 = ev.ex_data_string(); m_parent.set_event_plaintext("Ex data"); } else if (ev.is_system()) { m_parent.set_event_system("System data"); m_parent.set_event_plaintext("System data: To do!"); } else { /* * qseqeventframe::data_0_helper() is used to display program change and * controller names. We could use it here. Also note that setting * the plaintext data to empty will show its place-holder text, * if set. */ midibyte d0, d1; ev.get_data(d0, d1); data_0 = data_string(d0); data_1 = data_string(d1); channel = int(ev.channel()); if (ev.is_pitchbend()) /* TODO: use RPN setting */ { double semis = pitch_value_semitones(d0, d1); std::string s = double_to_string(semis, 3); /* number of digits */ std::string msg = "Pitch bend "; msg += s; msg += " semitones"; m_parent.set_event_plaintext(msg); } else m_parent.set_event_plaintext(""); /* no plaintext data here */ } set_event_text ( ev.category_string(), ev.timestamp_string(), ev.status_string(), data_0, data_1, channel ); m_current_row = m_current_index = index; m_current_iterator = ei; m_current_event = ev; } std::string qseventslots::data_string (midibyte d) { char tmp[32]; const char * format = SEQ66_EVENT_DATA_FMT_DEC; if (m_show_data_as_hex) format = SEQ66_EVENT_DATA_FMT_HEX; (void) snprintf(tmp, sizeof tmp, format, int(d)); return std::string(tmp); } /** * Similar to set_current_event(), but fills in the table row with data, * rather than filling the side fields for the current event. */ void qseventslots::set_table_event (editable_event & ev, int row) { std::string data_0; std::string data_1; std::string linktime; std::string tstring = m_show_time_as_pulses ? std::to_string(long(ev.timestamp())) : ev.timestamp_string() ; int buss = int(ev.input_bus()); std::string busno; if (is_null_buss(buss)) { if (m_seq.has_in_bus()) busno = "<" + std::to_string(int(m_seq.seq_midi_in_bus())) + ">"; else busno = "-"; } else busno = std::to_string(buss); if (ev.is_meta_text()) { data_0 = ev.ex_text_string(); } else if (ev.is_ex_data()) { data_0 = ev.ex_data_string(); } else { midibyte d0, d1; ev.get_data(d0, d1); data_0 = data_string(d0); data_1 = data_string(d1); if (ev.is_linked()) { midipulse lt = ev.link_time(); if (m_show_time_as_pulses) { linktime = std::to_string(long(lt)); } else { linktime = pulses_to_measurestring ( lt, m_event_container.timing() ); } } else linktime = "None"; } m_parent.set_event_line ( row, tstring, ev.status_string(), busno, ev.channel_string(), data_0, data_1, linktime ); } std::string qseventslots::time_string (midipulse lt) { std::string result = "None"; if (! is_null_midipulse(lt)) result = pulses_to_measurestring(lt, m_event_container.timing()); return result; } std::string qseventslots::event_to_string ( const editable_event & ev, int index, bool usehex ) const { char line[132]; if (ev.is_ex_data()) { std::string data_0 = ev.ex_data_string(); snprintf ( line, sizeof line, "%4d %6ld %-9s %-9s %-30s 0x%04x\n", index, long(ev.timestamp()), ev.timestamp_string().c_str(), ev.status_string().c_str(), data_0.c_str(), ev.get_rank() ); } else { std::string data_0; std::string data_1; std::string linktime; std::string lenstring = "--"; const char * fmt = usehex ? "0x%02x" : "%5d"; char tmp[32]; midibyte d0, d1; midibyte rawstatus = ev.get_status(); ev.get_data(d0, d1); snprintf(tmp, sizeof tmp, fmt, int(d0)); data_0 = tmp; snprintf(tmp, sizeof tmp, fmt, int(d1)); data_1 = tmp; if (ev.is_linked()) { midipulse lt = ev.link_time(); linktime = pulses_to_measurestring(lt, m_event_container.timing()); if (ev.is_note_on()) lenstring = std::to_string(long(lt) - long(ev.timestamp())); } else linktime = "None"; snprintf ( line, sizeof line, "%4d %6ld %-9s %-9s 0x%02x Ch %2s %3s %3s %-9s %6s 0x%04x\n", index, long(ev.timestamp()), ev.timestamp_string().c_str(), ev.status_string().c_str(), rawstatus, ev.channel_string().c_str(), data_0.c_str(), data_1.c_str(), linktime.c_str(), lenstring.c_str(), ev.get_rank() ); } return std::string(line); } /** * Sets the text in the parent dialog, qseqeventframe. * * \param evcategory * The category of event to be set in the parent. * * \param evts * The event time-stamp to be set in the parent. * * \param evname * The event name to be set in the parent. * * \param evdata0 * The first event data byte to be set in the parent. * * \param evdata1 * The second event data byte to be set in the parent. */ void qseventslots::set_event_text ( const std::string & evcategory, const std::string & evts, const std::string & evname, const std::string & evdata0, const std::string & evdata1, int channel ) { m_parent.set_event_timestamp(evts); m_parent.set_event_category(evcategory); m_parent.set_event_name(evname); m_parent.set_event_channel(channel); m_parent.set_event_data_0(evdata0); m_parent.set_event_data_1(evdata1); } midibyte qseventslots::string_to_channel (const std::string & channel) { midibyte result = track().seq_midi_channel(); if (! channel.empty()) result = midibyte(string_to_int(channel) - 1); return result; } /** * Inserts an event. What actually happens here depends if the new event is * before the frame, within the frame, or after the frame, based on the * timestamp. Also, we want to allow the lengthening of a sequence by * inserting an event past its current length. Especially useful for the * tempo track. * * If before the frame: To keep the previous events visible, we do not need * to increment the iterators (insertion does not affect multimap iterators), * but we do need to increment their indices. The contents shown in the frame * should not change. * * If at the frame top: The new timestamp equals the top timestamp. We don't * know exactly where the new event goes in the multimap, but we do have an * new event. * * If after the frame: No action needed if the bottom event is actually at * the bottom of the frame. But if the frame is not yet filled, we need to * increment the bottom iterator, and its index. * * \note * Actually, it is far easier to just adjust all the counts and iterators * and redraw the screen, as done by the page_topper() function. * * \param edev * The event to insert, prebuilt. * * \return * Returns true if the event was inserted. */ bool qseventslots::insert_event (editable_event ev) // Q: where called? { bool result = m_event_container.add(ev); if (result) { m_event_count = m_event_container.count(); if (m_event_count == 1) { m_line_count = 1; /* used in drawing in a bit */ m_top_index = 0; m_current_index = 0; m_top_iterator = m_current_iterator = m_bottom_iterator = m_event_container.begin(); select_event(m_current_index); } else { /* * This iterator is a short-lived [changed after the next add() * call] pointer to the added event. This check somehow breaks * the redisplay of the modified event list in the Gtkmm-2.4 * user-interface. * * if (m_event_container.is_valid_iterator(nev)) */ auto nev = m_event_container.current_event(); page_topper(nev); m_parent.set_dirty(); } /* * Now see if the timestamp of the new event is past the end of the * sequence. In this case, we will allow the sequence to increase in * length. We also need to account for any change in length. */ if (get_length() > m_last_max_timestamp) { m_last_max_timestamp = get_length(); } } return result; } /** * Inserts an event based on the setting provided, which the qseqeventframe * object gets from its Entry fields. It calls the other insert_event() * overload. * * Note that we need to qualify the temporary event class object we create * below, with the seq66 namespace, otherwise the compiler thinks we're * trying to access some Gtkmm thing. * * \param evts * The time-stamp of the new event, as obtained from the event-edit * timestamp field. * * \param evname * The type name (status name) of the new event, as obtained from the * event-edit event-name field. * * \param evdata0 * The first data byte of the new event, as obtained from the event-edit * data 1 field. * * \param evdata1 * The second data byte of the new event, as obtained from the event-edit * data 2 field. Used only for two-parameter events. * * \param channel * Provides the channel string. Defaults to "", which means the * sequence's internal MIDI channel (most often it is 0) is used. * * \return * Returns true if the event was inserted. */ bool qseventslots::insert_event ( const std::string & evts, const std::string & evname, const std::string & evdata0, const std::string & evdata1, const std::string & channel, const std::string & text ) { seq66::event e; /* new default event */ editable_event edev(m_event_container, e); edev.set_status_from_string ( evts, evname, evdata0, evdata1, channel, text ); m_current_event = edev; return insert_event(edev); } /** * Deletes the current event, and makes adjustments due to that deletion. * Also deletes the linked note for Note Offs and Ons. * * To delete the current event, this function moves the current iterator to * the next event, deletes the previously-current iterator, adjusts the event * count and the bottom iterator, and redraws the pixmap. The exact changes * depend upon whether the deleted event was at the top of the visible frame, * within the visible frame, or at the bottom the visible frame. Note that * only visible events can be the current event, and thus get deleted. * \verbatim Event Index 0 1 2 Top 3 <--------- Top case: The new top iterator, index becomes 2 4 . . Inside of Visible Frame . 43 44 Bottom 45 <--------- Top case: The new bottom iterator, index becomes 44 46 Bottom case: Same result \endverbatim * * Basically, when an event is deleted, the frame (delimited by the * event-index members) stays in place, while the frame iterators move to the * previous event. If the top of the frame would move to before the first * event, then the frame must shrink. * * Top case: If the current iterator is the top (of the frame) iterator, then * the top iterator needs to be incremented. The new top event has the same * index as the now-gone top event. The index of the bottom event is * decremented, since an event before it is now gone. The bottom iterator * moves to the next event, which is now at the bottom of the frame. The * current event is treated like the top event. * * Inside case: If the current iterator is in the middle of the frame, the top * iterator and index remain unchanged. The current iterator is incremented, * but its index is now the same as the old bottom index. Same for the bottom * iterator. * * Bottom case: If the current iterator (and bottom iterator) point to the * last event in the frame, then both of them need to be * decremented. The frame needs to be moved up by one event, so that the * current event remains at the bottom (it's just simpler to manage that way). * * If there is no event after the bottom of the frame, the iterators that now * point to end() must backtrack one event. If the container becomes empty, * then everything is invalidated. * * \return * Returns true if the delete was possible. If the container was empty * then false is returned. It is the caller's responsibility to check if * the container is empty after this operation, via a call to * qseventslots::empty(). */ bool qseventslots::delete_current_event () { bool result = m_event_count > 0; if (result) result = m_current_iterator != m_event_container.end(); if (result) { auto oldcurrent = m_current_iterator; int oldcount = m_event_container.count(); if (oldcount > 1) { if (m_current_index == 0) { (void) increment_top(); /* bypass to-delete event */ (void) increment_current(); /* ditto */ (void) increment_bottom(); /* next event up to bottom */ } else if (m_current_index == (m_line_count - 1)) { /* * If we are before the last event in the event container, we * can increment the iterators. Otherwise, we have to back * off by one so we are above the last event, which will be * deleted. */ if (m_current_index < (m_event_count - 1)) { (void) increment_current(); (void) increment_bottom(); } else { /* * The frame must shrink. */ m_current_index = decrement_current(); (void) decrement_bottom(); if (m_line_count > 0) --m_line_count; } } else { /* * \change ca 2016-05-18 * Try to avoid a crash deleting the last item again * (since it is still visible). * * (void) increment_current(); * (void) increment_bottom(); */ if (increment_current() != SEQ66_NULL_EVENT_INDEX) { (void) increment_bottom(); m_bottom_iterator = m_event_container.end(); } else // if (m_current_index >= 0) /* issues/26 */ --m_current_index; } } /* * Has to be done after the adjustment, otherwise iterators are * invalid and cannot be adjusted. The remove() function validates * the iterator before trying to delete it. */ m_event_container.remove(oldcurrent); /* wrapper for erase() */ int newcount = m_event_container.count(); if (newcount == 0) { m_top_index = m_current_index = 0; m_top_iterator = m_current_iterator = m_bottom_iterator = m_event_container.end(); } bool ok = newcount == (oldcount - 1); if (ok) { m_parent.set_dirty(); m_event_count = newcount; result = true; /* an event was deleted */ if (newcount > 0) select_event(m_current_index); else select_event(SEQ66_NULL_EVENT_INDEX); } } return result; } /** * Modifies the data in the currently-selected event. If the timestamp has * changed, however, we can't just modify the event in place. Instead, we * finish modifying the event, but tell the caller to delete and reinsert the * new event (in its proper new location based on timestamp). * * This function always copies the original event, modifiies the copy, * deletes the original event, and inserts the "new" event into the * editable-event container. The insertion takes care of updating any length * increase of the sequence. * * \param evts * Provides the new event time-stamp as edited by the user. * * \param evname * Provides the event name as edited by the user. * * \param evdata0 * Provides the first data byte as edited by the user. * * \param evdata1 * Provides the second data byte as edited by the user. * * \param channel * Provides the channel string. Defaults to "", which means the * * \param text * For ex-data events (meta, sysex, and system), this provides * data to be converted into storable bytes. * * \return * Returns true simply if the event-count is greater than 0. */ bool qseventslots::modify_current_event ( int row, const std::string & evts, const std::string & evname, const std::string & evdata0, const std::string & evdata1, const std::string & channel, const std::string & text ) { bool result = m_event_count > 0; if (result) result = m_current_iterator != m_event_container.end(); if (result) { editable_event & evref = editable_events::dref(m_current_iterator); bool isnoteevent = evref.is_note(); midibyte channelbyte = string_to_channel(channel); if (isnoteevent) { evref.set_status_from_string ( evts, evname, evdata0, evdata1, channel ); if (row >= 0) set_table_event(evref, row); } else { editable_event ev = editable_events::dref(m_current_iterator); ev.set_status_from_string ( evts, evname, evdata0, evdata1, channel, text ); if (! ev.is_ex_data()) ev.set_channel(channelbyte); result = delete_current_event(); /* full karaoke del */ if (result) result = insert_event(ev); /* full karaoke add */ } } return result; } /** * This function assumes it is called only for channel events. */ bool qseventslots::modify_current_channel_event ( int row, const std::string & evdata0, const std::string & evdata1, const std::string & channel ) { bool result = m_event_count > 0; if (result) result = m_current_iterator != m_event_container.end(); if (result) { editable_event & ev = editable_events::dref(m_current_iterator); ev.modify_channel_status_from_string(evdata0, evdata1, channel); if (row >= 0) set_table_event(ev, row); } return result; } /** * Writes the events back to the sequence. Added a copy_events() function in * the sequence class for the purpose of reconstructing the events container * for the sequence. It is locked by a mutex, and so will not draw until all * is done, preventing a segfault. * * We create a new plain event container here, and then passing it to the new * locked/threadsafe sequence::copy_events() function that clears the * sequence container and copies the events from the parameter container. * Note that this code will operate event if all events were deleted. * * \return * Returns true if the operations succeeded. */ bool qseventslots::save_events () { bool result = m_event_count >= 0 && m_event_count == m_event_container.count(); if (result) { eventlist newevents; for (auto & ei : m_event_container) { result = newevents.add(ei.second); if (! result) break; } if (result) result = newevents.count() == m_event_count; if (result) { track().copy_events(newevents); result = track().event_count() == m_event_count; if (result && m_last_max_timestamp > track().get_length()) track().set_length(m_last_max_timestamp); } } return result; } #if defined QSEVENTSLOTS_FUNCTION_USED /** * Adjusts the vertical position of the frame according to the given new * scrollbar/vadjust value. The adjustment is done via movement from the * current position. * * Do we even need a way to detect excess movement? The scrollbar, if * properly set up, should never move the frame too high or too low. * Verified by testing. * * \param new_value * Provides the new value of the scrollbar position. */ void qseventslots::page_movement (int new_value) { if ((new_value >= 0) && (new_value < m_event_count)) { int movement = new_value - m_pager_index; /* can be negative */ int absmovement = movement >= 0 ? movement : -movement; m_pager_index = new_value; if (movement != 0) { /* * @change ca 2016-10-08 * Issue #38. We see a double-increment when moving the * scroll down one slot. But if we don't do this, the event * indices at the left always start at 0 at the top of the * view, no matter what the page. Weird! */ m_top_index += movement; if (movement > 0) { for (int i = 0; i < movement; ++i) { (void) increment_top(); (void) increment_bottom(); } } else if (movement < 0) { for (int i = 0; i < absmovement; ++i) { (void) decrement_top(); (void) decrement_bottom(); } } /* * Don't move the current event (highlighted) unless * we move more than one event. Annoying to the user. */ if (absmovement > 1) { set_current_event(m_top_iterator, 0); } else { int newindex = m_current_index + movement; set_current_event(m_current_iterator, newindex); } } } } #endif // defined QSEVENTSLOTS_FUNCTION_USED /** * Adjusts the vertical position of the frame according to the given new * bottom iterator. The adjustment is done "from scratch". We've found page * movement to be an insoluable problem in some editing circumstances. So * now we move to the inserted event, and make it the top event. * * However, always moving an inserted event to the top is a bit annoying. So * now we backtrack so that the inserted event is at the bottom. * * \param newcurrent * Provides the iterator to the event to be shown at the bottom of the * frame. */ void qseventslots::page_topper (editable_events::iterator newcurrent) { bool ok = newcurrent != m_event_container.end(); if (ok) ok = m_event_count > 0; if (ok) { auto ei = m_event_container.begin(); int botindex = 0; while (ei != newcurrent) { ++botindex; ++ei; if (botindex == m_event_count) { ok = false; /* never found the event! */ break; } } if (m_event_count <= line_maximum()) /* fewer events than lines */ { if (ok) { m_pager_index = m_top_index = 0; m_top_iterator = m_event_container.begin(); m_line_count = m_event_count; m_current_iterator = newcurrent; m_current_index = botindex; } } else { m_line_count = line_maximum(); if (ok) { /* * Backtrack by up to line_maximum() events so that the new * event is shown at the bottom or at its natural location; it * also needs to be the current event, for highlighting. * Count carefully! */ auto ei = m_event_container.begin(); int pageup = botindex - line_maximum(); if (pageup < 0) { m_top_index = m_pager_index = pageup = 0; } else { int countdown = pageup; while (countdown-- > 0) ++ei; m_top_index = m_pager_index = pageup + 1; /* re map */ } m_top_iterator = ei; m_current_iterator = newcurrent; m_current_index = botindex - m_top_index; /* re frame */ } } if (ok) select_event(m_current_index); } } /** * Selects and highlights the event that is located in the frame at the given * event index. The event index is provided by the QtTable Widget. * Note that, if the event index is negative, then we just queue up a draw * operation, which should paint an empty frame -- the event container is * empty. * * \param event_index * Provides the numeric index of the event in the event frame, or * SEQ66_NULL_EVENT if there is no event to draw. * * \param full_redraw * Defaulting to true, this parameter can be set to false in some case to * reduce the flickering of the frame under fast movement. Doesn't * really make sense yet in the Qt 5 version. */ void qseventslots::select_event (int event_index, bool full_redraw) { bool ok = event_index != SEQ66_NULL_EVENT_INDEX; if (ok) ok = (event_index < m_line_count); if (ok) { auto ei = m_event_container.begin(); /* not m_top_iterator */ ok = ei != m_event_container.end(); if (ok && (event_index > 0)) { int i = 0; while (i++ < event_index) { ++ei; ok = ei != m_event_container.end(); if (! ok) break; } } if (ok) set_current_event(ei, event_index, full_redraw); } } /** * Decrements the top iterator, if possible. * * \return * Returns 0, or SEQ66_NULL_EVENT_INDEX if the iterator could not be * decremented. */ int qseventslots::decrement_top () { if (m_top_iterator != m_event_container.begin()) { --m_top_iterator; return m_top_index - 1; } else return SEQ66_NULL_EVENT_INDEX; } /** * Increments the top iterator, if possible. Also handles the top-event * index, so that the GUI can display the proper event numbers. * * \return * Returns the top index, or SEQ66_NULL_EVENT_INDEX if the iterator could * not be incremented, or would increment to the end of the container. */ int qseventslots::increment_top () { auto ei = m_top_iterator; if (ei != m_event_container.end()) { ++ei; if (ei != m_event_container.end()) { m_top_iterator = ei; return m_top_index + 1; } else return SEQ66_NULL_EVENT_INDEX; } else return SEQ66_NULL_EVENT_INDEX; } /** * Decrements the current iterator, if possible. * * \return * Returns the decremented index, or SEQ66_NULL_EVENT_INDEX if the * iterator could not be decremented. Remember that the index ranges * only from 0 to m_line_count-1, and that is enforced here. */ int qseventslots::decrement_current () { if (m_current_iterator != m_event_container.begin()) { --m_current_iterator; int result = m_current_index - 1; if (result < 0) result = 0; return result; } else return SEQ66_NULL_EVENT_INDEX; } /** * Increments the current iterator, if possible. * * \return * Returns the incremented index, or SEQ66_NULL_EVENT_INDEX if the * iterator could not be incremented. Remember that the index ranges * only from 0 to m_line_count-1, and that is enforced here. */ int qseventslots::increment_current () { auto ei = m_current_iterator; if (ei != m_event_container.end()) { int result = SEQ66_NULL_EVENT_INDEX; ++ei; if (ei != m_event_container.end()) { m_current_iterator = ei; result = m_current_index + 1; if (result >= m_line_count) result = m_line_count - 1; } return result; } else return SEQ66_NULL_EVENT_INDEX; } /** * Decrements the bottom iterator, if possible. * * \return * Returns 0, or SEQ66_NULL_EVENT_INDEX if the iterator could not be * decremented. */ int qseventslots::decrement_bottom () { if (m_bottom_iterator != m_event_container.begin()) { --m_bottom_iterator; return 0; } else return SEQ66_NULL_EVENT_INDEX; } /** * Increments the bottom iterator, if possible. There is an issue in paging * down using the scrollbar where, at the bottom of the scrolling, the bottom * iterator ends up bad. Not yet sure how this happens, so for now we * backtrack one event if this happens. * * \return * Returns the incremented index, or SEQ66_NULL_EVENT_INDEX if the * iterator could not be incremented. */ int qseventslots::increment_bottom () { int result = SEQ66_NULL_EVENT_INDEX; if (m_bottom_iterator != m_event_container.end()) { auto old = m_bottom_iterator++; if (m_bottom_iterator != m_event_container.end()) result = 0; else m_bottom_iterator = old; /* backtrack to initial value */ } return result; } /** * This function basically duplicates sequence::calculate_measures(). * * \return * Returns the number of measures needed to cover the full length of the * current events in the container. The lowest valid measure is 1. */ int qseventslots::calculate_measures () const { return track().calculate_measures(); } } // namespace seq66 /* * qseventslots.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qslivebase.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qslivebase.cpp * * This module declares/defines the base class for holding pattern slots. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-06-22 * \updates 2025-09-17 * \license GNU GPLv2 or above * * This class is the Qt counterpart to the old mainwid class. */ #include "cfg/settings.hpp" /* seq66::usr().mainwnd_spacing() */ #include "ctrl/keystroke.hpp" /* seq66::keystroke class */ #include "qslivebase.hpp" /* seq66:qslivebase class, this one */ #include "qsmainwnd.hpp" /* the parent class of this window */ namespace seq66 { /** * Principal constructor. * * \param p * Provides the performer object to use for interacting with this sequence. * * \param window * Provides the functional parent of this live frame. * * \param bank * Indicates the screenset number to use for the live grid. If unassigned, * then the performer's active screenset number is used. * * \param parent * Provides the Qt-parent window/widget for this container window. * Defaults to null. Normally, this is a pointer to the tab-widget * containing this frame. If null, there is no parent, and this frame is * in an external window. */ qslivebase::qslivebase ( performer & p, qsmainwnd * window, screenset::number bank, QWidget * parent ) : QFrame (parent), m_performer (p), m_parent (window), m_font (), m_bank_id ( screenset::is_unassigned(bank) ? p.playscreen_number() : bank ), m_bank_name (), m_mainwnd_spacing (usr().mainwnd_spacing()), /* spacing() */ m_space_rows (m_mainwnd_spacing * p.rows()), m_space_cols (m_mainwnd_spacing * p.columns()), m_screenset_slots (p.rows() * p.columns()), m_slot_w (0), m_slot_h (0), m_last_metro (0), m_alpha (0), m_current_seq (seq::unassigned()), /* current_seq() */ m_hover_seq (seq::unassigned()), /* hover_seq() */ m_source_seq (seq::unassigned()), m_button_down (false), m_moving (false), m_adding_new (false), m_can_paste (perf().can_paste()), m_has_focus (false), m_is_external (is_nullptr(parent)), m_needs_update (false) { /* * Stupid hack to avoid the BBT/HMS tooltip showing up when the mouse * is over the live grid. */ setToolTip(" "); setToolTipDuration(1); } /** * Virtual destructor. */ qslivebase::~qslivebase() { // No code needed } /** * Sets the bank number retrieved from performer. */ void qslivebase::set_bank () { set_bank(m_bank_id); /* set_bank(int(perf().playscreen_number())) */ } /** * Roughly similar to log_screenset(). Note that, for import, we will have * already set the bank to be filled in, and so must do the work even if the * bank ID has not changed. * * \return * Returns true if the bank was successfully changed. */ bool qslivebase::set_bank (int bankid, bool hasfocus) { bool result = perf().is_screenset_valid(bankid); if (result) { m_bank_id = bankid; if (hasfocus) { std::string bankname = perf().set_name(bankid); if (! is_external()) /* show_per_bank() */ (void) perf().set_playing_screenset(bankid); set_bank_values(bankname, bankid); /* update the GUI */ } } return result; } /** * We rely on the caller (the parent window) to call this function only when * the bank ID has actually changed. */ void qslivebase::update_bank (int bank) { m_has_focus = true; /* widget active */ set_bank(bank, true); } /** * We should ultimately use setmaster rather than usrsettings here. */ seq::number qslivebase::seq_offset () const { seq::number result = ! is_external() ? perf().playscreen_offset() : usr().set_offset(bank_id()) ; return result; } void qslivebase::color_by_number (int i) { perf().set_color(m_current_seq, i); } void qslivebase::set_midi_bus (int b) { (void) perf().set_midi_bus(m_current_seq, b); } void qslivebase::set_midi_channel (int channel) { (void) perf().set_midi_channel(m_current_seq, channel); } void qslivebase::set_midi_in_bus (int b) { (void) perf().set_midi_in_bus(m_current_seq, b); } } // namespace seq66 /* * qslivebase.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qslivegrid.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qslivegrid.cpp * * This module declares/defines the base class for holding pattern slots. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-06-21 * \updates 2025-10-19 * \license GNU GPLv2 or above * * This class is the Qt counterpart to the mainwid class. This version is * replaces qsliveframe, and uses buttons and draws faster. However, we had * to disable button callbacks and use keypresses directly in order to * implement double-click and drag-and-drop functionality. * * A two-dimensional vector of buttons containing a vector of rows, each * row being a vector of columns. * * Screen view and loop-grid slot numbers: * \verbatim Column 0 1 2 3 4 5 6 7 Row 0 0 4 8 12 16 20 24 28 Row 1 1 5 9 13 17 21 25 29 Row 2 2 6 10 14 18 22 26 30 Row 3 3 7 11 15 19 23 27 31 \endverbatim * * Button/Gridrow (m_loop_buttons[column][row]) view slot numbers: * \verbatim Row 0 1 2 3 [one gridrow, a vector of slotbuttons] Column 0 0 1 2 3 1 4 5 6 7 2 8 9 10 11 3 12 13 14 15 4 16 17 18 19 5 20 21 22 23 6 24 25 26 27 7 28 29 30 31 \endverbatim * * The fastest varying index is the row: m_loop_buttons[column][row]. * * Issue #106: "Mark the selected MIDI bus and channel in the pattern dropdown * menu." Code supplied by phuel 2023-02-26. */ #include #include #include #include #include #include #include "cfg/settings.hpp" /* seq66::usr() config functions */ #include "ctrl/keystroke.hpp" /* seq66::keystroke class */ #include "gui_palette_qt5.hpp" /* seq66::gui_palette_qt5 class */ #include "os/timing.hpp" /* seq66::millisleep() */ #include "play/performer.hpp" /* seq66::performer class */ #include "qloopbutton.hpp" /* seq66::qloopbutton (qslotbutton) */ #include "qslivegrid.hpp" /* seq66::qslivegrid */ #include "qsmainwnd.hpp" /* the true parent of this class */ #include "qt5_helpers.hpp" /* seq66::qt_keystroke() etc. */ #include "util/filefunctions.hpp" /* seq66::get_full_path() */ #include "pixmaps/metro.xpm" /* a metroname icon */ #include "pixmaps/metro_on.xpm" /* metroname-is-active icon */ #include "pixmaps/rec.xpm" /* recording off */ #include "pixmaps/rec_on.xpm" /* recording on */ #if defined SEQ66_PLATFORM_DEBUG #include "util/strfunctions.hpp" /* seq66::pointer_to_string() */ #endif /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qslivegrid.h" #else #include "forms/qslivegrid.ui.h" #endif /* * EXPERIMENTAL. */ #define SEQ66_TRACK_LIVE_GRID_MOVEMENT namespace seq66 { /* * Provides size restrictions. */ static const int c_minimum_width = 300; static const int c_minimum_height = 180; /** * The Qt 5 version of mainwid. * * \param p * Provides the performer object to use for interacting with this sequence. * * \param window * Provides the functional parent of this live frame. * * \param bank * Indicates the screenset number to use for the live grid. If unassigned, * then the performer's active screenset number is used. * * \param parent * Provides the Qt-parent window/widget for this container window. * Defaults to null. Normally, this is a pointer to the tab-widget * containing this frame. If null, there is no parent, and this frame is * in an external window. */ qslivegrid::qslivegrid ( performer & p, qsmainwnd * window, screenset::number setno, QWidget * parent ) : qslivebase (p, window, setno, parent), performer::callbacks (p), ui (new Ui::qslivegrid), m_popup (nullptr), m_timer (nullptr), m_msg_box (nullptr), m_redraw_buttons (true), m_loop_buttons (), m_x_min (0), m_x_max (0), m_y_min (0), m_y_max (0) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setFocusPolicy(Qt::StrongFocus); ui->setupUi(this); #if defined SEQ66_TRACK_LIVE_GRID_MOVEMENT /* * The next two lines of code allow the live grid to track the * mouse position in the mouseMoveEvent() function. We want the * Menu key (if present) to bring up the popup-menu of the slot * underneath the cursor. One still has to click once to give * the grid mouse focus. */ ui->frame->setAttribute(Qt::WA_TransparentForMouseEvents); setMouseTracking(true); #endif m_msg_box = new QMessageBox(this); m_msg_box->setText(tr("A pattern is present.")); m_msg_box->setInformativeText(tr("Overwrite with a blank pattern?")); m_msg_box->setStandardButtons(QMessageBox::Yes | QMessageBox::No); m_msg_box->setDefaultButton(QMessageBox::No); int w = usr().scale_size(c_minimum_width); int h = usr().scale_size_y(c_minimum_height); ui->frame->setMinimumSize(QSize(w, h)); if (is_external()) { QString bname = qt(perf().set_name(bank_id())); ui->txtBankName->setText(bname); connect ( ui->txtBankName, SIGNAL(editingFinished()), this, SLOT(slot_set_bank_name()) ); ui->buttonActivate->setEnabled(true); connect ( ui->buttonActivate, SIGNAL(clicked(bool)), this, SLOT(slot_activate_bank(bool)) ); ui->labelArmMode->hide(); ui->labelPlaylistSong->hide(); ui->buttonLoopMode->hide(); ui->buttonRecordMode->hide(); ui->buttonMetronome->hide(); ui->buttonBackgroundRecord->hide(); ui->comboGridMode->hide(); } else { ui->setNameLabel->hide(); ui->txtBankName->hide(); ui->buttonActivate->hide(); ui->labelArmMode->hide(); ui->buttonLoopMode->setEnabled(true); ui->buttonRecordMode->setEnabled(true); qt_set_icon(metro_xpm, ui->buttonMetronome); ui->buttonMetronome->setEnabled(true); qt_set_icon(rec_xpm, ui->buttonBackgroundRecord); bool background_record = rc().metro_settings().count_in_recording(); ui->buttonBackgroundRecord->setEnabled(background_record); show_record_style(); /* * Loop mode: merge, overwrite, one-shot, etc. Also called, * confusingly, grid record-style. */ connect ( ui->buttonLoopMode, SIGNAL(clicked(bool)), this, SLOT(slot_record_style(bool)) ); std::string keyname = cb_perf().automation_key(automation::slot::record_style); /* * Record mode: none, tighten, quantize, and note-map. */ tooltip_with_keystroke(ui->buttonRecordMode, keyname); show_record_alteration(); connect ( ui->buttonRecordMode, SIGNAL(clicked(bool)), this, SLOT(slot_record_alteration(bool)) ); connect ( ui->buttonMetronome, SIGNAL(clicked(bool)), this, SLOT(slot_toggle_metronome(bool)) ); connect ( ui->buttonBackgroundRecord, SIGNAL(clicked(bool)), this, SLOT(slot_toggle_background_record(bool)) ); /* * Live grid mode settings. */ populate_grid_mode(); set_grid_mode(); connect ( ui->comboGridMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slot_grid_mode(int)) ); } ui->labelPlaylistSong->setText(""); qloopbutton::progress_box_size ( usr().progress_box_width(), usr().progress_box_height() ); /* * Drag-n-drop support. Much easier than I thought! */ setAcceptDrops(true); /* * Redundant, so disabled. */ if (! is_external()) enable_solo(false); /* * Register to be notified by the performer. */ perf().enregister(this); /* notification */ m_timer = qt_timer(this, "qslivegrid", 3, SLOT(conditional_update())); } /** * Virtual destructor, deletes the user-interface objects and the message * box. */ qslivegrid::~qslivegrid() { if (not_nullptr(m_timer)) m_timer->stop(); perf().unregister(this); clear_loop_buttons(); /* currently we use raw pointers */ delete ui; } void qslivegrid::set_grid_mode () { int gcode = usr().grid_mode_code(); ui->comboGridMode->setCurrentIndex(gcode); } void qslivegrid::enable_solo (bool enable) { int index = usr().grid_mode_code(gridmode::solo); enable_combobox_item(ui->comboGridMode, index, enable); #if THIS_CODE_WORKS std::string recordcolor{"Record"}; index = usr().grid_mode_code(gridmode::record); set_combobox_item(ui->comboGridMode, index, recordcolor); #endif } /** * Populate the combo box from grid_loop, grid_record, and, as implemented * more automation::slot::grid_xxxxx values up to grid_double. * * Added a new grid mode, grid_mutes, which is in proper order in the * gridmode enumeration, but implemented by an automation slot replacing * reserved_46. */ void qslivegrid::populate_grid_mode () { ui->comboGridMode->clear(); int ending = usr().grid_mode_code(gridmode::double_length); for (int counter = 0; counter <= ending; ++counter) { gridmode gm = usr().grid_mode(counter); bool enabled = true; /* gm != gridmode::double_length */ std::string modename = usr().grid_mode_label(gm); QString combotext(qt(modename)); ui->comboGridMode->insertItem(counter, combotext); enable_combobox_item(ui->comboGridMode, counter, enabled); } } void qslivegrid::slot_grid_mode (int index) { gridmode gm = usr().grid_mode(index); perf().set_grid_mode(gm); } /** * At some point we might add coloring to this button to show a status of * recording or not. */ void qslivegrid::show_grid_mode () { set_grid_mode(); } /** * Sets the name of the play-list. */ void qslivegrid::set_playlist_name (const std::string & plname, bool modified) { #if defined USE_OLD_CODE std::string fullname = get_full_path(plname); std::string path; std::string basename; if (fullname.empty()) { fullname = filename_base(plname); if (fullname.empty()) fullname = rc().no_name(); basename = fullname; } else { bool ok = filename_split(fullname, path, basename); if (! ok) basename = fullname; } #else std::string basename = shorten_file_spec(plname, 64); std::string path = plname; #endif if (modified) basename += " *"; QString name = qt(basename); ui->labelPlaylistSong->setText(name); if (! path.empty()) { QString p = qt(path); ui->labelPlaylistSong->setToolTip(p); } (void) recreate_all_slots(); } /** * In an effort to reduce CPU usage when simply idling, this function calls * update() only if necessary. See qlivebase::check_dirty(). All * sequences are potentially checked. * * Actually, we need a way to update only the loop slots in the grid layout, * and only update the progress area. * * TODO: unhide and show if the following are active: * * - One-shot * - Solo * - Replace * - Queue * - Keep queue * - Snapshot (snapshot stored) */ void qslivegrid::conditional_update () { if (m_loop_buttons.empty()) return; sequence_key_check(); if (perf().needs_update() || check_needs_update()) { /* * Going to get rid of Solo, since it can offer no functionality * not already offered by automation::slot::solo. Also disabled * in qsmainwnd::set_song_mode(). * * enable_solo(! perf().song_mode()); */ show_record_style(); show_record_alteration(); show_grid_mode(); if (perf().has_ctrl_status()) { QString ctrlstatus(qt(perf().ctrl_status_string())); ui->labelArmMode->show(); ui->labelArmMode->setText(ctrlstatus); } else ui->labelArmMode->hide(); update_state(); } } /** * Grab frame dimensions for scaled drawing. Note that the frame size can be * modified by the user dragging a corner, in some window managers. * * The slot-sizes are provisional sizes, will be adjusted according to the * size of the layout grid. * * Note that this function is called only in paintEvent(), and only when * m_redraw_buttons is true. * * Note that m_space_rows and m_space_cols are defined in qslivebase, and are * based ultimately on the usr().mainwnd_spacing() value times the number of * rows or columns in the grid. */ void qslivegrid::create_loop_buttons () { int fw = ui->frame->width(); int fh = ui->frame->height(); m_slot_w = (fw - m_space_cols - 1) / columns(); m_slot_h = (fh - m_space_rows - 1) / rows() - 1; for (int row = 0; row < rows(); ++row) ui->loopGridLayout->setRowMinimumHeight(row, m_slot_h + spacing()); for (int column = 0; column < columns(); ++column) ui->loopGridLayout->setColumnMinimumWidth(column, m_slot_w + spacing()); int setsize = perf().screenset_size(); int offset = seq_offset(); for (int seqno = 0; seqno < setsize; ++seqno) { /* * This does old behavior (external live grid shows the desired set, * and so does the main window. Using seqno + offset yields buttons * squashed to the right, and a blank main live grid! * * // int s = is_external() ? (seqno + offset) : seqno ; */ int s = seqno + offset; /* provides old behavior */ qslotbutton * pb = create_one_button(s); if (not_nullptr(pb)) { m_loop_buttons.push_back(pb); } else { (void) error_message("create button failed", std::to_string(s)); break; } } measure_loop_buttons(); /* always do this */ } /** * This function just deletes all the pointers in our "2-D" array and then * clears the array completely, so that it has a size of 0 x 0. This * function is meant to be used when completely recreating the button-layout * grid. Note that we leave the original sequence/pattern along. */ void qslivegrid::clear_loop_buttons () { if (! m_loop_buttons.empty()) { int setsize = perf().screenset_size(); if (setsize <= int(m_loop_buttons.size())) { for (int seqno = 0; seqno < setsize; ++seqno) { qslotbutton * pb = m_loop_buttons[seqno]; if (not_nullptr(pb)) delete pb; } } m_loop_buttons.clear(); } } /** * Very brute force, but done rarely! */ void qslivegrid::measure_loop_buttons () { m_x_max = m_y_max = 0; m_x_min = m_y_min = 99999; int setsize = perf().screenset_size(); for (int seqno = 0; seqno < setsize; ++seqno) { qslotbutton * pb = loop_button(seqno); if (not_nullptr(pb)) { QRect r = pb->geometry(); if (m_slot_w == 0) { m_slot_w = r.width(); /* all buttons same size */ m_slot_h = r.height(); } int x0 = r.x(); int x1 = x0 + m_slot_w; /* r.width() */ int y0 = r.y(); int y1 = y0 + m_slot_h; /* r.height() */ if (x0 < m_x_min) m_x_min = x0; if (x1 > m_x_max) m_x_max = x1; if (y0 < m_y_min) m_y_min = y0; if (y1 > m_y_max) m_y_max = y1; } else break; } } /** * Given the (x, y) coordinate of a mouse click, calculates the row and * column of the button over which the click occurred. * * \warning * Note that this function currently does not check for being completely * within the button, so that clicks in the space between the buttons cause * a true result. * * \return * Returns true if the calculation could be made. Otherwise, the results * are meaningless. */ bool qslivegrid::get_slot_coordinate (int x, int y, int & row, int & column) { bool result = m_x_max > 0; if (result) { int xslotsize = (m_x_max - m_x_min) / columns(); int yslotsize = (m_y_max - m_y_min) / rows(); row = (y - m_y_min) / yslotsize; column = (x - m_x_min) / xslotsize; } else row = column = 0; return result; } /** * Creates a single button, either empty (qslotbutton) or representing a * sequence (qloopbutton). * * Uses pointers. Beware of reconnecting to changed or new pointers! */ qslotbutton * qslivegrid::create_one_button (seq::number seqno) { qslotbutton * result = nullptr; int row, column; bool valid = perf().seq_to_grid(seqno, row, column, is_external()); if (valid) { bool enabled = perf().is_screenset_active(seqno); const QSize btnsize = QSize(m_slot_w, m_slot_h); std::string snstring; if (valid) snstring = std::to_string(seqno); std::string hotkey = perf().lookup_slot_key(seqno); seq::pointer pattern = perf().loop(seqno); /* can be null */ if (pattern) { result = new (std::nothrow) qloopbutton ( this, seqno, snstring, hotkey, pattern ); } else { result = new (std::nothrow) qslotbutton ( this, seqno, snstring, hotkey ); } ui->loopGridLayout->addWidget(result, row, column); result->setFixedSize(btnsize); result->show(); result->setEnabled(enabled); setup_button(result); } return result; } /** * This override simply calls creates the slot/loop buttons. We have to do * it here, because only here do we know the final size of the grid. This * function is called about a half-dozen times at startup, which really * messes with "redraw" flags in the buttons. We need call the internal * code here just once at startup. */ void qslivegrid::paintEvent (QPaintEvent * /*qpep*/) { if (m_redraw_buttons) { create_loop_buttons(); /* refresh_all_slots() */ m_redraw_buttons = false; } } /** * This function removes the slot-buttons from the layout and then deletes * them. Then it recreates them at the new size. Works pretty fast on an i7! */ void qslivegrid::resizeEvent (QResizeEvent * /*qrep*/) { (void) recreate_all_slots(); /* sets m_redraw_buttons to true */ } /** * A helper function for create_one_button(). */ void qslivegrid::setup_button (qslotbutton * pb) { if (not_nullptr(pb)) { pb->setup(); pb->reupdate(true); } } /** * Tell performer to set the current pattern's color and then call * recreate_all_slots(). Simply calling a refresh or reupdate doesn't work * to show the new color. Luckily, this goes fast. */ void qslivegrid::color_by_number (int i) { qslivebase::color_by_number(i); /* perf().color(current_seq(), ...) */ (void) recreate_all_slots(); } /** * A virtual function to set the bank name in the user-interface. Called by * qslivebase::set_bank(). */ void qslivegrid::set_bank_values (const std::string & name, int id) { qslivebase::set_bank_values(name, id); ui->txtBankName->setText(qt(name)); } /** * A brute-force function to get the desired slot-button pointer. * Currently similar to the grid_to_seq() functions. */ qslotbutton * qslivegrid::button (int row, int column) { qslotbutton * result = nullptr; if (! m_loop_buttons.empty()) { int index = perf().grid_to_index(row, column); if (index < int(m_loop_buttons.size())) result = m_loop_buttons[index]; } return result; } /** * Does a brute force lookup calculation for a button based on its sequence * number. * * \param seqno * The number of the sequence to be looked up. This determines the row and * column by a simple calculation. * * \return * Returns a pointer to a qslotbutton or qloopbutton. Returns a null * pointer if a failure occurs. */ qslotbutton * qslivegrid::loop_button (seq::number seqno) { seq::number sz = seq::number(m_loop_buttons.size()); return seqno < sz ? m_loop_buttons[seqno] : nullptr ; } /** * This function finds the desired layout-item in the grid, and then removes * it. The caller is responsible for deleting the slot-button. */ bool qslivegrid::delete_slot (int row, int column) { qslotbutton * pb = button(row, column); bool result = not_nullptr(pb); if (result) { QLayoutItem * item = ui->loopGridLayout->itemAtPosition(row, column); if (not_nullptr(item)) ui->loopGridLayout->removeWidget(item->widget()); } return result; } bool qslivegrid::delete_slot (seq::number seqno) { int row, column; bool result = perf().index_to_grid(seqno, row, column); if (result) result = delete_slot(row, column); return result; } /** * Generally, after calling this function, clear_loop_buttons() needs to be * called to delete the slot-button pointers. */ bool qslivegrid::delete_all_slots () { bool result = ! m_loop_buttons.empty(); if (result) { bool failed = false; int setsize = perf().screenset_size(); if (setsize <= int(m_loop_buttons.size())) { for (int seqno = 0; seqno < setsize; ++seqno) { result = delete_slot(seqno); if (! result) failed = true; } } else failed = true; if (failed) result = false; } return result; } /** * Deletes all the slot-buttons, and recreates them from scratch. This is * done by setting m_redraw_buttons and letting create_loop_buttons() be * called in paintEvent(). */ bool qslivegrid::recreate_all_slots () { bool result = delete_all_slots(); qloopbutton::boxes_initialized(true); /* actually sets it false i */ qloopbutton::progress_box_size /* in case user changed it */ ( usr().progress_box_width(), usr().progress_box_height() ); if (result) { clear_loop_buttons(); m_redraw_buttons = true; set_needs_update(); } return result; } /** * This function helps the caller (usually qsmainwnd) tell all the buttons to * show their current state. One big use case is when the command is given * to mute, unmute, or toggle all of the patterns. * * We still need to be able to refresh individual buttons when MIDI control * or Song playback changes the state of a pattern. */ bool qslivegrid::refresh_all_slots () { bool result = ! m_loop_buttons.empty(); if (result) { seq::number offset = perf().playscreen_offset(); for (auto pb : m_loop_buttons) { seq::pointer s = perf().get_sequence(offset); if (not_nullptr(s)) { pb->set_checked(s->armed()); pb->reupdate(true); } ++offset; } } return result; } bool qslivegrid::modify_slot (qslotbutton * newslot, int row, int column) { bool result = delete_slot(row, column); if (result) { ui->loopGridLayout->addWidget(newslot, row, column); int index = perf().grid_to_index(row, column); m_loop_buttons[index] = newslot; } return result; } /** * We have found we need to pause the timer when switching banks while * playing, otherwise there is a high probability of a seqfault, due to * updating the user-interface (which deletes and rebuilts the slot buttons). */ void qslivegrid::update_bank (int bankid) { m_timer->stop(); qslivebase::update_bank(bankid); (void) recreate_all_slots(); /* sets m_redraw_buttons to true */ m_timer->start(); } void qslivegrid::update_bank () { m_timer->stop(); (void) recreate_all_slots(); /* sets m_redraw_buttons to true */ m_timer->start(); } /** * Used to grab the std::string bank name and convert it to QString for * display. Let performer set the modify flag, it knows when to do it. * Otherwise, just scrolling to the next screen-set causes a spurious * modification and an annoying prompt to a user exiting the application. */ void qslivegrid::update_bank_name (const std::string & name) { if (is_external()) ui->txtBankName->setText(qt(name)); perf().screenset_name(bank_id(), name, is_external()); } /** * This function causes all slots to ultimately be deleted and flagged for * reconstruction. That's a lot of work, and only occurs if a sequence slot * is created, deleted, or pasted. * * \param seqno * Provides the number of the pattern being created, deleted, or pasted. */ void qslivegrid::update_sequence (seq::number seqno, bool redo) { if (redo) { alter_sequence(seqno); /* similar to below, but recreates */ } else { int row, column; if (perf().seq_to_grid(seqno, row, column, is_external())) { qslotbutton * pb = button(row, column); if (not_nullptr(pb)) pb->reupdate(true); } } } /** * Converts the (x, y) coordinates of a click into a sequence/pattern ID. * Normally, these values can range from 0 to 31, representing one of 32 * slots in the live frame. But sets may be larger or smaller. * * Compare this function to setmapper::set_get(). * * \param click_x * The x-coordinate of the mouse click. * * \param click_y * The y-coordinate of the mouse click. * * \return * Returns the sequence/pattern number. If not found, then a -1 (the * value seq::unassigned) is returned. */ int qslivegrid::seq_id_from_xy (int click_x, int click_y) { int result = seq::unassigned(); int row, column; if (get_slot_coordinate(click_x, click_y, row, column)) result = int(perf().grid_to_seq(bank_id(), row, column)); return result; } /** * Sets current_seq() based on the position of the mouse over the live frame. * For the left button: * * - No modifier. The button is looked up by the position of the mouse. * If it has a sequence pointer, then the button's toggle_checked() * function is called, which does what it says, plus toggles the * sequence. The sequence tells the performer to notify all subscribers * of the on_trigger_change() function. * - Control key. Create a new sequence. * - Shift key. Create a new live frame with a screen-set offset by the * button number times the size of a screenset. A bit tricky. * * One issue is that a double-click yields a mouse-press and an * mouse-double-click event, in that order. We log the current state of * the pattern so that we can restore it in the double-click handler. * * \param event * Provides the mouse event. */ void qslivegrid::mousePressEvent (QMouseEvent * event) { current_seq(seq_id_from_xy(event->x(), event->y())); bool ok = current_seq() != seq::unassigned(); bool nonblankslot = perf().set_current_sequence(current_seq()); if (ok) { if (event->button() == Qt::LeftButton) { if (event->modifiers() & Qt::ControlModifier) { new_sequence(); } else if (event->modifiers() & Qt::ShiftModifier) { new_live_frame(); } else if (event->modifiers() & Qt::AltModifier) { /* * Not sure that this really works. Needs investigation. */ if (nonblankslot) (void) perf().replace_for_solo(current_seq()); } else { button_toggle_checked(current_seq()); m_button_down = true; } seq::pointer s = perf().get_sequence(current_seq()); if (not_nullptr(s)) s->set_popup(false); } else if (event->button() == Qt::RightButton) { if (event->modifiers() & Qt::ControlModifier) new_sequence(); else if (event->modifiers() & Qt::ShiftModifier) new_live_frame(); else popup_menu(); } } } /** * Get the sequence number we clicked on. If we're on a valid sequence, hit * the left mouse button, and are not dragging a sequence, then toggle * playing. * * Moving: * * If it's the left mouse button and we're moving a pattern between slots, * then, if the sequence number is valid, inactive, and not in editing, * create a new pattern and copy the data to it. Otherwise, copy the data * to the old sequence. * * Right Click: * * Check for right mouse click; this action launches the popup menu for the * pattern slot underneath the mouse. Can we do this just once and save the * menus for reuse? This is now done in the mouse-press handler. */ void qslivegrid::mouseReleaseEvent (QMouseEvent * event) { current_seq(seq_id_from_xy(event->x(), event->y())); m_button_down = false; if (current_seq() != seq::unassigned()) { if (event->button() == Qt::LeftButton) { if (m_moving) /* see "Moving" above */ { m_moving = false; button_toggle_enabled(m_source_seq); m_source_seq = seq::unassigned(); if (perf().finish_move(current_seq())) (void) recreate_all_slots(); } else { if (perf().is_seq_active(current_seq())) m_adding_new = false; else m_adding_new = true; } } else if /* launch seq editor */ ( event->button() == Qt::MiddleButton && perf().is_seq_active(current_seq()) ) { emit signal_call_editor(current_seq()); } } } /** * Drag a sequence between slots; save the sequence into a "moving sequence" * in the performer, and clear the old slot. But what if the user lets go of * the pattern in the same slot? To DO! Also, this call does a * partial-assign and removal all the time. */ void qslivegrid::mouseMoveEvent (QMouseEvent * event) { seq::number seqno = seq_id_from_xy(event->x(), event->y()); if (seqno != hover_seq()) { if (! seq::unassigned(hover_seq())) { button_toggle_flat(hover_seq()); } button_toggle_flat(seqno); hover_seq(seqno); } if (m_button_down) { if (! perf().is_seq_in_edit(current_seq())) { if (seqno == current_seq()) { if (seq::unassigned(m_source_seq)) { m_source_seq = current_seq(); button_toggle_enabled(m_source_seq); } } else { if (! m_moving) { if (perf().move_sequence(current_seq())) { m_moving = true; update(); } } } } } #if defined SEQ66_TRACK_LIVE_GRID_MOVEMENT else /* ca 2025-10-19 */ { #if defined SEQ66_PLATFORM_DEBUG_TMI printf("x,y = %d,%d --> track #%d\n", event->x(), event->y(), seqno); #endif current_seq(seqno); } #endif } /** * One issue is that a double-click yields a mouse-press and an * mouse-double-click event, in that order. */ void qslivegrid::mouseDoubleClickEvent (QMouseEvent * event) { if (rc().allow_click_edit()) { if (m_adding_new) new_sequence(); current_seq(seq_id_from_xy(event->x(), event->y())); if (perf().is_seq_active(current_seq())) button_toggle_checked(current_seq()); /* undo press-toggle */ emit signal_call_editor_ex(current_seq()); } } void qslivegrid::button_toggle_enabled (seq::number seqno) { bool assigned = seqno != seq::unassigned(); if (assigned) { qslotbutton * pb = loop_button(seqno); if (not_nullptr(pb)) { seq::pointer s = pb->loop(); if (s) (void) pb->toggle_enabled(); } } } /** * Moved control to performer, and now rely upon the full cycle to work, * rather than toggling the button state(s) here. We need to have the * loop_control() function use only index values (e.g. only 0 to 31), * and get the actual pattern number via the current play-set. */ void qslivegrid::button_toggle_checked (seq::number seqno) { bool assigned = seqno != seq::unassigned(); if (assigned) { (void) perf().loop_control ( automation::action::toggle, (-1), (-1), int(seqno), false ); } } void qslivegrid::button_toggle_flat (seq::number seqno) { bool assigned = seqno != seq::unassigned(); if (assigned) { qslotbutton * pb = loop_button(seqno); if (not_nullptr(pb)) { bool isflat = pb->isFlat(); pb->setFlat(! isflat); } } } void qslivegrid::new_sequence () { bool createseq = true; seq::number current = current_seq(); if (perf().is_seq_active(current)) { int choice = m_msg_box->exec(); if (choice == QMessageBox::Yes) createseq = perf().remove_sequence(current); else createseq = false; } if (createseq) { if (perf().request_sequence(current)) { msgprintf(msglevel::status, "New Pattern %d", int(current)); alter_sequence(current); /* * ca 2022-09-04 An additional fix for issue #93. */ parent()->remove_editor(current_seq()); } } } /** * We need to see if there is an external live-frame window already existing * for the current sequence number (which is used as a screen-set number). * If not, we can create a new one and add it to the list. */ void qslivegrid::new_live_frame () { emit signal_live_frame(current_seq()); } /** * Emits the signal_call_editor() signal. In qsmainwnd, this signal is * connected to the load_editor() slot. */ void qslivegrid::edit_sequence () { emit signal_call_editor(current_seq()); } /** * Emits the signal_call_editor_ex() signal. In qsmainwnd, this signal is * connected to the load_qseqedit() slot. */ void qslivegrid::edit_sequence_ex () { emit signal_call_editor_ex(current_seq()); } void qslivegrid::edit_events () { emit signal_call_edit_events(current_seq()); } /** * Handles any existing pattern-key statuses. Used in the timer callback. */ void qslivegrid::sequence_key_check () { seq::number seqno; bool ok = perf().got_seqno(seqno); if (perf().seq_edit_pending()) { if (ok) { seq::pointer s = perf().get_sequence(seqno); current_seq(seqno); if (is_nullptr(s)) new_sequence(); edit_sequence_ex(); perf().clear_seq_edits(); } } else if (perf().event_edit_pending()) { if (ok) { current_seq(seqno); edit_events(); perf().clear_seq_edits(); } } } /** * Functionality moved to qsmainwnd::handle_key_press(). * The handling of seq-edit and event-edit is done via setting flags in * performer and responding to them in the timer function. */ bool qslivegrid::handle_key_press (const keystroke & k) { if (k.is_menu()) { popup_menu(); return true; } else return k.is_good() ? parent()->handle_key_press(k) : false ; } bool qslivegrid::handle_key_release (const keystroke & k) { return k.is_good() ? parent()->handle_key_press(k) : false ; } /** * The Gtkmm 2.4 version calls performer::mainwnd_key_event(). We have * broken that function into pieces (smaller functions) that we can use here. * An important point is that keys that affect the GUI directly need to be * handled here in the GUI. Another important point is that other events are * offloaded to the performer object, and we need to let that object handle * as much as possible. The logic here is an admixture of events that we * will have to sort out. * * Note that the QKeyEvent::key() function does not distinguish between * capital and non-capital letters, so we use the text() function (returning * the Unicode text the key generated) for this purpose. * * Weird. After the first keystroke, for, say 'o' (ascii 111) == k, we get k * == 0, presumably a terminator character that we have to ignore. Also, we * can't intercept the Esc key. Qt grabbing it? * * \param event Provides a pointer to the key event. */ void qslivegrid::keyPressEvent (QKeyEvent * event) { bool show = rc().verbose(); keystroke k = qt_keystroke(event, keystroke::action::press, show); bool done = handle_key_press(k); if (done) update(); else QWidget::keyPressEvent(event); } void qslivegrid::keyReleaseEvent (QKeyEvent * event) { bool show = rc().verbose(); keystroke k = qt_keystroke(event, keystroke::action::release, show); bool done = handle_key_release(k); if (done) update(); else QWidget::keyReleaseEvent(event); } void qslivegrid::reupdate () { for (auto pb : m_loop_buttons) { if (not_nullptr(pb)) { pb->setup(); pb->reupdate(true); } else break; } } void qslivegrid::update_state () { for (auto pb : m_loop_buttons) /* copy/paste code */ { if (not_nullptr(pb)) { seq::pointer s = pb->loop(); if (s) { pb->set_checked(s->armed()); pb->reupdate(true); } else pb->reupdate(false); } } } void qslivegrid::update_geometry () { updateGeometry(); } /** * This function is a helper for the other xxx_sequence() functions. It * obtains the sequence number, figures out the row and column, and creates a * new button based on whether the slot contains a pattern or not. */ void qslivegrid::alter_sequence (seq::number seqno) { int row, column; if (perf().seq_to_grid(seqno, row, column, is_external())) { qslotbutton * pb = create_one_button(seqno); if (not_nullptr(pb)) { if (modify_slot(pb, row, column)) (void) recreate_all_slots(); } } } /** * Upgraded to apply the loop record-style and alteration specified * in the grid. */ void qslivegrid::record_sequence () { bool ok = false; seq::pointer sp = perf().get_sequence(current_seq()); if (sp) ok = perf().set_recording_flip(*sp); if (! ok) { // todo? } } void qslivegrid::flatten_sequence () { if (perf().flatten_sequence(current_seq())) { // no other code needed here } } void qslivegrid::export_sequence () { std::string prompt = "Export pattern..."; std::string fname = rc().last_used_dir(); bool ok = show_file_dialog /* qsmainwnd::midi_filename_prompt() */ ( this, fname, prompt, "MIDI files (*.midi *.mid);;All files (*)", SavingFile, NormalFile, ".midi" ); if (ok) { if (! perf().export_sequence(current_seq(), fname)) { // show_error_box(perf().error_messages()); } } } void qslivegrid::copy_sequence () { bool ok = perf().copy_sequence(current_seq()); if (ok) can_paste(true); } /** * Need a dialog warning that the editor is the reason this sequence cannot be * cut. Or delete it. For issue #93, we delete the pattern editor. */ void qslivegrid::cut_sequence () { bool ok = perf().cut_sequence(current_seq()); if (ok) { can_paste(true); parent()->remove_editor(current_seq()); alter_sequence(current_seq()); } } /** * If the sequence/pattern is delete-able (valid and not being edited), then * it is deleted via the performer object. Note that in seq66 the * screenset::remove() function makes this check now. * * For issue #93, we delete the pattern editor. */ void qslivegrid::delete_sequence () { bool ok = perf().remove_sequence(current_seq()); if (ok) { perf().notify_sequence_removal ( current_seq(), performer::change::recreate ); parent()->remove_editor(current_seq()); can_paste(false); alter_sequence(current_seq()); } } void qslivegrid::clear_sequence () { bool ok = perf().clear_sequence(current_seq()); if (ok) { can_paste(false); alter_sequence(current_seq()); } } void qslivegrid::paste_sequence () { bool ok = perf().can_paste() && can_paste(); if (ok) { ok = perf().paste_sequence(current_seq()); alter_sequence(current_seq()); } if (! ok) can_paste(false); } void qslivegrid::merge_sequence () { bool ok = perf().can_paste() && can_paste(); if (ok) { ok = perf().merge_sequence(current_seq()); alter_sequence(current_seq()); } if (! ok) can_paste(false); } void qslivegrid::slot_set_bank_name () { QString newname = ui->txtBankName->text(); std::string name = newname.toStdString(); update_bank_name(name); } void qslivegrid::slot_activate_bank (bool /*clicked*/) { (void) perf().set_playing_screenset(bank_id()); } /** * Record style (loop mode) is one of overwrite, expand, one-shot, etc. */ void qslivegrid::slot_record_style (bool /*clicked*/) { perf().next_record_style(); } /** * Record mode is one of none, quantize, tighten, jitter, random, and * note-map. */ void qslivegrid::slot_record_alteration (bool /*clicked*/) { perf().next_record_alteration(); } void qslivegrid::slot_toggle_metronome (bool /*clicked*/) { Qt::KeyboardModifiers qkm = QGuiApplication::keyboardModifiers(); if (qkm & Qt::ControlModifier) { bool on = ui->buttonMetronome->isChecked(); if (on) { ui->buttonMetronome->setChecked(false); qt_set_icon(metro_xpm, ui->buttonMetronome); } emit signal_call_editor_ex(sequence::metronome()); } else if (qkm & Qt::AltModifier) { bool on = ui->buttonMetronome->isChecked(); if (on) { ui->buttonMetronome->setChecked(false); qt_set_icon(metro_xpm, ui->buttonMetronome); } emit signal_call_edit_events(sequence::metronome()); } else { bool on = ui->buttonMetronome->isChecked(); if (on) { (void) perf().install_metronome(); /* arms if already existing */ qt_set_icon(metro_on_xpm, ui->buttonMetronome); } else { /* * This can cause the occasional segfault. * * (void) perf().remove_metronome(); */ (void) perf().arm_metronome(false); /* mutes the metronome */ qt_set_icon(metro_xpm, ui->buttonMetronome); } } } void qslivegrid::slot_toggle_background_record (bool /*clicked*/) { bool on = ui->buttonBackgroundRecord->isChecked(); if (on) { qt_set_icon(rec_on_xpm, ui->buttonBackgroundRecord); (void) perf().install_recorder(); /* arms if already existing */ } else { qt_set_icon(rec_xpm, ui->buttonBackgroundRecord); (void) perf().finish_recorder(); } } /** * Changing the color of the button can yield unreadable text, so let's not * do that anymore. */ void qslivegrid::show_record_style () { #if defined USE_BUTTON_COLORING static bool s_uninitialized = true; static QPalette s_palette; QPushButton * button = ui->buttonLoopMode; if (s_uninitialized) { s_uninitialized = false; s_palette = button->palette(); button->setEnabled(false); } if (usr().no_grid_record()) { button->setPalette(s_palette); button->setEnabled(false); button->update(); } else { QPalette pal = button->palette(); QColor c; switch (usr().record_style()) { case recordstyle::merge: c.setNamedColor("#FF0000"); break; case recordstyle::overwrite: c.setNamedColor("#C00000"); break; case recordstyle::expand: c.setNamedColor("#900000"); break; case recordstyle::oneshot: c.setNamedColor("#600000"); break; default: break; } pal.setColor(QPalette::Button, c); button->setAutoFillBackground(true); button->setPalette(pal); button->setEnabled(true); button->update(); } #endif ui->buttonLoopMode->setText(qt(usr().pattern_record_style_label())); } /** * Changing the color of the button can yield unreadable text, so let's not * do that anymore. */ void qslivegrid::show_record_alteration () { #if defined USE_BUTTON_COLORING static bool s_uninitialized = true; static QPalette s_palette; QPushButton * button = ui->buttonRecordMode; if (s_uninitialized) { s_uninitialized = false; button->setEnabled(false); s_palette = button->palette(); } if (usr().no_grid_record()) { button->setPalette(s_palette); button->setEnabled(false); button->update(); } else { QPalette pal = button->palette(); QColor c; switch (usr().record_alteration()) { case alteration::quantize: c.setNamedColor("#00C0C0"); break; case alteration::tighten: c.setNamedColor("#009090"); break; default: break; } pal.setColor(QPalette::Button, c); button->setAutoFillBackground(true); button->setPalette(pal); button->setEnabled(true); button->update(); } #endif ui->buttonRecordMode->setText(qt(usr().record_alteration_label())); } void qslivegrid::change_event (QEvent * evp) { changeEvent(evp); } /** * This is not called when focus changes. Instead, we have to call this from * qsliveframeex::changeEvent(). See change_event() above. Thus, this * function is called only for the external live frame. */ void qslivegrid::changeEvent (QEvent * event) { QWidget::changeEvent(event); if (event->type() == QEvent::ActivationChange) { if (isActiveWindow()) { m_has_focus = true; /* widget is now active */ /* * We don't want the external frame to change the active * screen-set. * * if (show_perf_bank()) * (void) perf().set_playing_screenset(bank_id()); */ } else { m_has_focus = false; /* widget is now inactive */ } } } void qslivegrid::popup_menu () { m_popup = new_qmenu("", this); QAction * ns = new_qaction("&New pattern", m_popup); QObject::connect(ns, SIGNAL(triggered(bool)), this, SLOT(new_sequence())); m_popup->addAction(ns); m_popup->addSeparator(); /* * Add an action to bring up an external qslivegrid window based * on the sequence number over which the mouse is resting. This is * pretty tricky, but might be reasonable. Note that we want to allow * opening an external live frame only from the Live grid in the main * window, and only if the 0th set is shown... we allow only 32 sets for * this purpose. However, we now allow the external frame menu entry on * any set, but modded to be within the screenset size. */ if (! is_external()) { seq::number mcs = current_seq() % perf().screenset_max(); char temp[48]; snprintf ( temp, sizeof temp, "&Live grid window for set %d", mcs ); QAction * livegrid = new_qaction(temp, m_popup); m_popup->addAction(livegrid); m_popup->addSeparator(); QObject::connect ( livegrid, SIGNAL(triggered(bool)), this, SLOT(new_live_frame()) ); } if (perf().is_seq_active(current_seq())) { seq::pointer s = perf().get_sequence(current_seq()); if (! is_external()) { #if defined SEQ66_USE_COLLAPSED_SLOT_POPUP_MENU QMenu * menuEdit = new_qmenu("&Edit"); QAction * actionRecord = new_qaction("&Record toggle", m_popup); m_popup->addAction(actionRecord); connect ( actionRecord, SIGNAL(triggered(bool)), this, SLOT(record_sequence()) ); QAction * editseqex = new_qaction ( "Pattern in &window", menuEdit ); menuEdit->addAction(editseqex); connect ( editseqex, SIGNAL(triggered(bool)), this, SLOT(edit_sequence_ex()) ); if (perf().is_seq_active(current_seq())) { QAction * editseq = new_qaction ( "Pattern in &tab", menuEdit ); connect ( editseq, SIGNAL(triggered(bool)), this, SLOT(edit_sequence()) ); menuEdit->addAction(editseq); } QAction * editevents = new_qaction ( "&Events in tab", menuEdit ); menuEdit->addAction(editevents); if (perf().is_seq_recording(current_seq())) { editevents->setDisabled(true); } else { editevents->setEnabled(true); connect ( editevents, SIGNAL(triggered(bool)), this, SLOT(edit_events()) ); } m_popup->addMenu(menuEdit); #else QAction * actionRecord = new_qaction("&Record toggle", m_popup); m_popup->addAction(actionRecord); connect ( actionRecord, SIGNAL(triggered(bool)), this, SLOT(record_sequence()) ); QAction * editseqex = new_qaction ( "Edit pattern in &window", m_popup ); m_popup->addAction(editseqex); connect ( editseqex, SIGNAL(triggered(bool)), this, SLOT(edit_sequence_ex()) ); if (perf().is_seq_active(current_seq())) { QAction * editseq = new_qaction("Edit pattern in &tab", m_popup); m_popup->addAction(editseq); connect ( editseq, SIGNAL(triggered(bool)), this, SLOT(edit_sequence()) ); } QAction * editevents = new_qaction ( "Edit e&vents in tab", m_popup ); m_popup->addAction(editevents); if (perf().is_seq_recording(current_seq())) { editevents->setDisabled(true); } else { editevents->setEnabled(true); connect ( editevents, SIGNAL(triggered(bool)), this, SLOT(edit_events()) ); } #endif } /** * Color menus */ QMenu * menuColour = new_qmenu("&Color..."); int firstcolor = palette_to_int(PaletteColor::none); int lastcolor = palette_to_int(PaletteColor::white); for (int c = firstcolor; c <= lastcolor; ++c) { PaletteColor pc = PaletteColor(c); std::string cname = c == firstcolor ? get_color_name(pc) : get_color_name_ex(pc); QAction * a = new_qaction(cname, menuColour); connect ( a, &QAction::triggered, [this, c] { color_by_number(c); } /* lambda */ ); a->setCheckable(true); a->setChecked(s->color() == c); menuColour->addAction(a); } QMenu * menu2Colour = new_qmenu("Dark colors"); firstcolor = palette_to_int(PaletteColor::dk_black); lastcolor = palette_to_int(PaletteColor::dk_white); for (int c = firstcolor; c <= lastcolor; ++c) { PaletteColor pc = PaletteColor(c); std::string cname = get_color_name_ex(pc); QAction * a = new_qaction(cname, menu2Colour); connect ( a, &QAction::triggered, [this, c] { color_by_number(c); } ); a->setCheckable(true); a->setChecked(s->color() == c); menu2Colour->addAction(a); } QMenu * menu3Colour = new_qmenu("Other colors"); firstcolor = palette_to_int(PaletteColor::orange); lastcolor = palette_to_int(PaletteColor::grey); for (int c = firstcolor; c <= lastcolor; ++c) { PaletteColor pc = PaletteColor(c); std::string cname = get_color_name_ex(pc); QAction * a = new_qaction(cname, menu3Colour); connect ( a, &QAction::triggered, [this, c] { color_by_number(c); } ); a->setCheckable(true); a->setChecked(s->color() == c); menu3Colour->addAction(a); } QMenu * menu4Colour = new_qmenu("More colors"); firstcolor = palette_to_int(PaletteColor::dk_orange); lastcolor = palette_to_int(PaletteColor::dk_grey); for (int c = firstcolor; c <= lastcolor; ++c) { PaletteColor pc = PaletteColor(c); std::string cname = get_color_name_ex(pc); QAction * a = new_qaction(cname, menu4Colour); connect ( a, &QAction::triggered, [this, c] { color_by_number(c); } ); a->setCheckable(true); a->setChecked(s->color() == c); menu4Colour->addAction(a); } menuColour->addMenu(menu2Colour); menuColour->addMenu(menu3Colour); menuColour->addMenu(menu4Colour); m_popup->addMenu(menuColour); #if defined SEQ66_USE_COLLAPSED_SLOT_POPUP_MENU QMenu * menuTrack = new_qmenu("&Track"); if (s->trigger_count() > 0) { /** * Flatten menu. This action consolidates the triggers into one * trigger, useful for export. */ QAction * actionFlatten = new_qaction("&Flatten triggers", menuTrack); menuTrack->addAction(actionFlatten); connect ( actionFlatten, SIGNAL(triggered(bool)), this, SLOT(flatten_sequence()) ); } if (s->event_count() > 0) { /** * Export menu. This action writes the track to a new file. */ QAction * actionExport = new_qaction("&Export", menuTrack); menuTrack->addAction(actionExport); connect ( actionExport, SIGNAL(triggered(bool)), this, SLOT(export_sequence()) ); } QAction * actionCopy = new_qaction("&Copy", menuTrack); menuTrack->addAction(actionCopy); connect ( actionCopy, SIGNAL(triggered(bool)), this, SLOT(copy_sequence()) ); QAction * actionCut = new_qaction("Cu&t", menuTrack); menuTrack->addAction(actionCut); connect ( actionCut, SIGNAL(triggered(bool)), this, SLOT(cut_sequence()) ); QAction * actionDelete = new_qaction("&Delete", menuTrack); menuTrack->addAction(actionDelete); connect ( actionDelete, SIGNAL(triggered(bool)), this, SLOT(delete_sequence()) ); if (can_clear()) { QAction * actionClear = new_qaction("Clear e&vents", menuTrack); menuTrack->addAction(actionClear); connect ( actionClear, SIGNAL(triggered(bool)), this, SLOT(clear_sequence()) ); } if (can_paste()) { QAction * actionMerge = new_qaction("&Merge into pattern", menuTrack); menuTrack->addAction(actionMerge); connect ( actionMerge, SIGNAL(triggered(bool)), this, SLOT(merge_sequence()) ); } m_popup->addMenu(menuTrack); #else if (s->trigger_count() > 0) { /** * Flatten menu. This action consolidates the triggers into one * trigger, useful for export. */ QAction * actionFlatten = new_qaction("&Flatten triggers", m_popup); m_popup->addAction(actionFlatten); connect ( actionFlatten, SIGNAL(triggered(bool)), this, SLOT(flatten_sequence()) ); } if (s->event_count() > 0) { /** * Export menu. This action writes the track to a new file. */ QAction * actionExport = new_qaction("&Export", m_popup); m_popup->addAction(actionExport); connect ( actionExport, SIGNAL(triggered(bool)), this, SLOT(export_sequence()) ); } /** * Copy/Cut/Delete/Paste menus */ QAction * actionCopy = new_qaction("Cop&y", m_popup); m_popup->addAction(actionCopy); connect ( actionCopy, SIGNAL(triggered(bool)), this, SLOT(copy_sequence()) ); QAction * actionCut = new_qaction("Cu&t", m_popup); m_popup->addAction(actionCut); connect ( actionCut, SIGNAL(triggered(bool)), this, SLOT(cut_sequence()) ); QAction * actionDelete = new_qaction("&Delete", m_popup); m_popup->addAction(actionDelete); connect ( actionDelete, SIGNAL(triggered(bool)), this, SLOT(delete_sequence()) ); if (can_clear()) { QAction * actionClear = new_qaction("Clear events", m_popup); m_popup->addAction(actionClear); connect ( actionClear, SIGNAL(triggered(bool)), this, SLOT(clear_sequence()) ); } if (can_paste()) { QAction * actionMerge = new_qaction("&Merge into pattern", m_popup); m_popup->addAction(actionMerge); connect ( actionMerge, SIGNAL(triggered(bool)), this, SLOT(merge_sequence()) ); } #endif mastermidibus * mmb = perf().master_bus(); if (not_nullptr(mmb)) { s->set_popup(true); /** * Input buss menu. It is optional. The default is "Free", * which means the mastermidibus uses the active current * pattern for input. */ if (rc().sequence_lookup_support()) { QMenu * menuinbuss = new_qmenu("&Input bus"); const inputslist & ipm = input_port_map(); int inbuses = ipm.active() ? ipm.count() : mmb->get_num_in_buses() ; /* * bussbyte midi_in_bus = s->seq_midi_in_bus(); */ bussbyte midi_in_bus = s->true_in_bus(); for (int bus = 0; bus < inbuses; ++bus) { bool active; std::string busname; if (perf().ui_get_input(bussbyte(bus), active, busname)) { QAction * a = new_qaction(busname, menuinbuss); a->setCheckable(true); if (midi_in_bus == bussbyte(bus)) a->setChecked(true); /* * see qslivebase::set_midi_in_buss(). */ connect ( a, &QAction::triggered, [this, bus] { set_midi_in_bus(bus); } ); menuinbuss->addAction(a); if (! active) a->setEnabled(false); } } QAction * f = new_qaction("&Free", menuinbuss); bussbyte nullbuss = null_buss(); f->setCheckable(true); if (is_null_buss(midi_in_bus)) f->setChecked(true); connect ( f, &QAction::triggered, [this, nullbuss] { set_midi_in_bus(nullbuss); } ); menuinbuss->addAction(f); m_popup->addSeparator(); m_popup->addMenu(menuinbuss); } /* if (rc().with_jack_midi()) */ /** * Output buss menu. See qslivebase; it calls performer :: * set_midi_buschannel */ QMenu * menubuss = new_qmenu("Output &bus"); const clockslist & opm = output_port_map(); int buses = opm.active() ? opm.count() : mmb->get_num_out_buses() ; for (int bus = 0; bus < buses; ++bus) { e_clock ec; std::string busname; if (perf().ui_get_clock(bussbyte(bus), ec, busname)) { bool disabled = ec == e_clock::disabled; QAction * a = new_qaction(busname, menubuss); a->setCheckable(true); /* issue #106 */ a->setChecked(s->true_bus() == bus); connect ( a, &QAction::triggered, [this, bus] { set_midi_bus(bus); } /* in qslivebase */ ); menubuss->addAction(a); if (disabled) a->setEnabled(false); } } m_popup->addMenu(menubuss); /** * Channel menu. See qslivebase; it calls performer :: * set_midi_channel(). */ QMenu * menuchan = new_qmenu("Output &channel"); int buss = s->true_bus(); for (int channel = 0; channel <= c_midichannel_max; ++channel) { char b[4]; /* 2 digits or less */ snprintf(b, sizeof b, "%2d", channel + 1); std::string name = std::string(b); std::string sname = usr().instrument_name(buss, channel); if (! sname.empty()) { name += " ["; name += sname; name += "]"; } if (channel == c_midichannel_max) { QAction * a = new_qaction("Free", menuchan); connect ( a, &QAction::triggered, [this, /*buss,*/ channel] { set_midi_channel(channel); } ); a->setCheckable(true); /* issue #106 */ a->setChecked(s->midi_channel() == channel); menuchan->addAction(a); } else { QAction * a = new_qaction(name, menuchan); connect ( a, &QAction::triggered, [this, /*buss,*/ channel] { set_midi_channel(channel); } ); a->setCheckable(true); /* issue #106 */ a->setChecked(s->midi_channel() == channel); menuchan->addAction(a); } } m_popup->addMenu(menuchan); } } else if (perf().can_paste() && can_paste()) { QAction * actionPaste = new_qaction("&Paste", m_popup); m_popup->addAction(actionPaste); connect ( actionPaste, SIGNAL(triggered(bool)), this, SLOT(paste_sequence()) ); QAction * actionMerge = new_qaction("&Merge", m_popup); m_popup->addAction(actionMerge); connect ( actionMerge, SIGNAL(triggered(bool)), this, SLOT(merge_sequence()) ); } else can_paste(false); m_popup->exec(QCursor::pos()); if (perf().is_seq_active(current_seq())) { seq::pointer s = perf().get_sequence(current_seq()); s->set_popup(false); } /* * This really sucks. Without this sleep, the popup menu, after any * selection, crashes much of the time. It crashes deep in Qt territory, * with no clue as to why. And it doesn't seem to matter what themes * (Qt/Gtk) are in use. */ millisleep(10); } /** * Added this function to handle simple changes in sequence status, * including recording changes. */ bool qslivegrid::on_trigger_change ( seq::number /* seqno */, performer::change /* mod */ ) { update_state(); set_needs_update(); return true; } bool qslivegrid::on_automation_change (automation::slot /* s */) { update_state(); set_needs_update(); return true; } /* * Trial for drag-and-drop onto the Live grid. */ void qslivegrid::dragEnterEvent (QDragEnterEvent * event) { if (event->mimeData()->hasUrls()) event->acceptProposedAction(); } void qslivegrid::dragMoveEvent (QDragMoveEvent * /*event*/) { // no code } void qslivegrid::dragLeaveEvent (QDragLeaveEvent * /*event*/) { // no code } void qslivegrid::dropEvent (QDropEvent * event) { foreach (const QUrl & url, event->mimeData()->urls()) { QString urlasfile = url.toLocalFile(); std::string fname = urlasfile.toStdString(); if (! parent()->open_file(fname)) file_error("Drag-and-drop failed", fname); } } } // namespace seq66 /* * qslivegrid.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qslogview.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qslogview.cpp * * The time bar shows markers and numbers for the measures of the song, * and also depicts the left and right markers. * * \library seq66 application * \author Chris Ahlstrom * \date 2025-01-25 * \updates 2025-01-25 * \license GNU GPLv2 or above * * This module supports a task similar to that of the Help / Tutorial menu * entry. The qslogview dialog is similar to the qsbuildinfo dialog. There * are a couple of differences: * * - The dialog uses a rich-text browser widget, QTextBrowser. * - The data to be shown will reside in small files stored in * the build directory seq66/data/share/doc/info, and installed to * - /usr/local/share/doc/seq66-0.99/info * - C:/Program Files/Seq66/data/share/doc/info * * The plan is to generalize the tutorial_folder_list() function to more * flexibly select the document folder, and then write a function * to read the desired file into a string. * */ #include #include "cfg/settings.hpp" /* seq66::open_share_doc_file() */ #include "util/filefunctions.hpp" /* seq66::file_read_string() */ #include "qslogview.hpp" /* seq66::qslogview dialog class */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qslogview.h" #else #include "forms/qslogview.ui.h" #endif namespace seq66 { static const std::string s_error_text { "No active log. Set it up in main Session tab." }; static const std::string s_empty_text { "No log entries yet. Strange." }; /** * Principal constructor. */ qslogview::qslogview (QWidget * parent) : QDialog (parent), ui (new Ui::qslogview) { ui->setupUi(this); connect ( ui->buttonRefresh, SIGNAL(clicked(bool)), this, SLOT(slot_refresh_log_view()) ); } qslogview::~qslogview () { delete ui; } void qslogview::refresh () { std::string text{s_error_text}; if (usr().option_use_logfile()) { std::string logfilename = usr().option_logfile(); text = file_read_string(logfilename); if (text.empty()) text = s_empty_text; } ui->textBrowser->setText(qt(text)); QScrollBar * sb = ui->textBrowser->verticalScrollBar(); sb->setValue(sb->maximum()); } void qslogview::slot_refresh_log_view () { refresh(); } } // namespace seq66 /* * qslogview.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qslotbutton.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qslotbutton.cpp * * This module declares/defines the base class for drawing a pattern-slot * button. * * \library seq66 application * \author Chris Ahlstrom * \date 2019-06-26 * \updates 2023-10-27 * \license GNU GPLv2 or above * * This object is just a QPushButton with number label. See seq66::qslivegrid * for its usage. * * Getting system background color: * * QWidget tmpWidget; * QColor bgcolor = tmpWidget.palette().color(QPalette::Window); * * Getting system button text color: * * QColor btextcolor = tmpWidget.palette().color(QPalette::ButtonText); */ #include #include "cfg/settings.hpp" /* seq66::usr() config functions */ #include "qslotbutton.hpp" /* seq66::qslotbutton base class */ #include "qt5_helpers.hpp" /* seq66::qt(), qt_set_icon() etc. */ namespace seq66 { /** * Note that we make the button transparent to mouse events. It lets them * through for the parent to handle. */ qslotbutton::qslotbutton ( const qslivegrid * const slotparent, seq::number slotnumber, const std::string & label, const std::string & hotkey, QWidget * parent ) : QPushButton (parent), m_slot_parent (slotparent), m_slot_number (slotnumber), m_label (label), m_hotkey (hotkey), m_drum_color (drum_paint()), /* Qt::red */ #if defined DRAW_TEMPO_LINE m_tempo_color (tempo_paint()), /* Qt::magenta */ #endif m_progress_color (progress_paint()), m_label_color (label_paint()), m_text_color (text_slots_paint()), m_pen_color (foreground_paint()), m_back_color (background_paint()), m_vert_compressed (usr().vertically_compressed()), m_horiz_compressed (usr().horizontally_compressed()), m_is_active (false), m_is_checkable (false), m_is_dirty (true) { setAttribute(Qt::WA_TransparentForMouseEvents); if (is_theme_color(label_paint())) { QWidget tmp; label_color(tmp.palette().color(QPalette::ButtonText)); } } /** * This function is called by qslivegrid::setup_button() for inactive slots, * and it is responsible for presenting a clean button. If not called, then * the original pattern information remains on the button, inactive so that * it looks ghostly. */ void qslotbutton::setup () { setAutoFillBackground(true); setEnabled(true); setCheckable(is_checkable()); /* * Works better here than in paintEvent(). */ std::string snstring = std::to_string(m_slot_number); setText(qt(snstring)); } } // namespace seq66 /* * qslotbutton.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qsmaintime.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsmaintime.cpp * * This module declares/defines the base class for the "time" progress * window. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2021-10-15 * \license GNU GPLv2 or above * * This class implements the strip of boxes at the top of the main window * that highlight each beat in a measure as playback occurs. * */ #include "play/performer.hpp" /* seq66::performer class */ #include "qsmaintime.hpp" #include "gui_palette_qt5.hpp" namespace seq66 { /** * Manifest constants for this module. The maximum box width is kept small * because otherwise the beat-strip is way too long when the application is * maximized horizontally. */ static const int s_max_box_width = 120; static const int s_width_hint = 150; static const double s_height_hint_factor = 2.4; static const double s_height_factor = 0.3; /** * Principal constructor. */ qsmaintime::qsmaintime ( performer & perf, QWidget * parent, int beatspermeasure, int beatwidth ) : QWidget (parent), m_main_perf (perf), m_color (Qt::red), m_font (), m_beats_per_measure (beatspermeasure), m_beat_width (beatwidth), #if defined SEQ66_USE_METRONOME_FADE m_alpha (230), #endif m_last_metro (0) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); m_font.setPointSize(9); m_font.setBold(true); #if defined SEQ66_USE_METRONOME_FADE m_color.setAlpha(m_alpha); #endif } void qsmaintime::beat_width (int bw) { if (bw != m_beat_width) { m_beat_width = bw; update(); } } void qsmaintime::beats_per_measure (int bpm) { if (bpm != m_beats_per_measure) { m_beats_per_measure = bpm; update(); } } /** * Note that the timing of this event is controlled in qsmainwnd::refresh(). */ void qsmaintime::paintEvent (QPaintEvent *) { QPainter painter(this); QPen pen(Qt::darkGray); QBrush brush(Qt::NoBrush); painter.setPen(pen); painter.setBrush(brush); painter.setFont(m_font); midipulse tick = perf().get_tick(); int boxwidth = (width() - 1) / beats_per_measure(); if (boxwidth > s_max_box_width) boxwidth = s_max_box_width; int metro = ticks_to_beats ( tick, perf().ppqn(), beats_per_measure(), beat_width() ); /* * Flash on beats (i.e. if the metronome has changed, or we've just * started playing). We select a color from the palette. */ if (metro != m_last_metro || (tick < 50 && tick > 0)) { #if defined SEQ66_USE_METRONOME_FADE m_alpha = 230; #endif if (metro == 0) m_color = beat_paint(); else m_color = background_paint(); } for (int b = 0; b < beats_per_measure(); ++b) /* draw beat blocks */ { int offset_x = boxwidth * b; int w = pen.width() - 1; if (b == metro && perf().is_running()) /* flash current beat */ { brush.setStyle(Qt::SolidPattern); pen.setColor(Qt::black); } else { brush.setStyle(Qt::NoBrush); pen.setColor(Qt::darkGray); } #if defined SEQ66_USE_METRONOME_FADE m_color.setAlpha(m_alpha); #endif brush.setColor(m_color); painter.setPen(pen); painter.setBrush(brush); painter.drawRect ( offset_x + w, w, boxwidth - pen.width(), height() - pen.width() ); } if (beats_per_measure() < 10) // draw beat number (if there's space) { int x = (metro + 1) * boxwidth - (m_font.pointSize() + 2); int y = height() * s_height_factor + m_font.pointSize(); pen.setColor(Qt::black); pen.setStyle(Qt::SolidLine); painter.setPen(pen); painter.drawText(x, y, QString::number(metro + 1)); } #if defined SEQ66_USE_METRONOME_FADE /* * Lessen alpha on each redraw to have smooth fading done as a factor of * the BPM to get useful fades. However, we have to scale this * differently than 300, because Seq66 allows BPM higher than 300. */ m_alpha *= 0.7 - perf().bpm() / usr().midi_bpm_maximum(); if (m_alpha < 0) m_alpha = 0; #endif m_last_metro = metro; } /** * Need names for both 150 and 2.4!!! */ QSize qsmaintime::sizeHint () const { return QSize(s_width_hint, m_font.pointSize() * s_height_hint_factor); } } // namespace seq66 /* * qsmaintime.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qsmainwnd.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qsmainwnd.cpp * * This module declares/defines the base class for the main window. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2026-04-23 * \license GNU GPLv2 or above * * The main window is known as the "Patterns window" or "Patterns panel". It * holds the "Pattern Editor" or "Sequence Editor". The main window consists * of two object: mainwnd, which provides the user-interface elements that * surround the patterns, and the live-grid, which implements the behavior of * the pattern slots. * * Menu Entries for NSM: * * New MIDI FIle new_session() Clear file/playlist, set new name. * Import [Open] import_midi_into_session() Imports only a MIDI file. * Save session save_session() Save MIDI and configs in session. * Save As HIDDEN See Export from session. * Export from ... save_file_as() Copy MIDI file outside of session. * Close (hidden) quit_session Detach from session management. * * Normal Menu Entries: * * New new_file() Clear file/playlist * Open select_and_load_file() Read file from anywhere * Save save_file() Save file in original location * Save As save_file_as() Save file anywhere, new name * Recent files open_recent_file() Get a particular recent file * * Common Menu Entries: * * Export Song export_song() Export song merging triggers * Export as MIDI export_file_as_midi() Save as regular SMF 1 MIDI file * Export SMF 0 export_file_as_smf_0() Save as regular SMF 0 MIDI file * Export Config: export_project () Save all configuration files * Import MIDI import_midi_into_set() Import MIDI into current set * Import Project import_project() Import a project configuration * Quit/Exit quit() Normal Qt application closing * Help show_qsabout() Show Help About (version info) * show_qsbuildinfo() Show features of the build * show_qsappinfo() Show additional features * show_qslogview() Show the configured log file. */ #include /* QErrorMessage */ #include /* prompt for NSM MIDI file-name */ #include /* used for QScreen geometry() call */ #include /* QMessageBox */ #include /* QResizeEvent */ #include /* QScreen */ #undef USE_QDESKTOPSERVICES #if defined USE_QDESKTOPSERVICES #include /* used for opening a URL */ #else #include "os/shellexecute.hpp" /* seq66::open_pdf(), open_url() */ #endif #include /* std::hex, std::setw() */ #include /* std::ostringstream */ #include /* std::make_pair() */ #include "cfg/cmdlineopts.hpp" /* write_options_files() */ #include "ctrl/keystroke.hpp" /* seq66::keystroke class */ #include "midi/wrkfile.hpp" /* seq66::wrkfile class */ #include "os/daemonize.hpp" /* seq66::signal_for_restart() */ #include "play/songsummary.hpp" /* seq66::write_song_summary() */ #include "util/strfunctions.hpp" /* seq66::string_to_int() */ #include "qliveframeex.hpp" /* seq66::qliveframeex container */ #include "qmutemaster.hpp" /* shows a map of mute-groups */ #include "qperfeditex.hpp" /* seq66::qperfeditex container */ #include "qperfeditframe64.hpp" /* seq66::qperfeditframe64 class */ #include "qperfnames.hpp" /* seq66::qperfnames pointer access */ #include "qperfroll.hpp" /* seq66::qperfroll pointer access */ #include "qplaylistframe.hpp" /* seq66::qplaylistframe class */ #include "qsabout.hpp" /* seq66::qsabout dialog class */ #include "qsappinfo.hpp" /* seq66::qsappinfo dialog class */ #include "qslogview.hpp" /* seq66::qslogview dialog class */ #include "qsbuildinfo.hpp" /* seq66::qsbuildinfo dialog class */ #include "qseditoptions.hpp" /* seq66::qseditoptions dialog */ #include "qseqeditex.hpp" /* seq66::qseqeditex container */ #include "qseqeditframe64.hpp" /* seq66::qseqeditframe64 class */ #include "qseqeventframe.hpp" /* a new event editor for Qt */ #include "qsessionframe.hpp" /* shows session information */ #include "qsetmaster.hpp" /* shows a map of all sets */ #include "qsmaintime.hpp" /* seq66::qsmaintime panel class */ #include "qsmainwnd.hpp" /* seq66::qsmainwnd main window */ #include "qslivegrid.hpp" /* seq66::qslivegrid panel */ #include "qt5_helpers.hpp" /* seq66::qt(), qt_set_icon() etc. */ #include "qt5nsmanager.hpp" /* seq66::qt5nsmanager session mgr. */ #include "util/filefunctions.hpp" /* seq66::file_extension_match() */ /* * A signal handler is defined in daemonize.cpp, used for quick & dirty * signal handling. Thanks due to user falkTX! */ #include "os/daemonize.hpp" /* seq66::session_close(), etc. */ /* * Qt's uic application allows a different output file-name, but not sure * if qmake can change the file-name. */ #if defined SEQ66_QMAKE_RULES #include "forms/ui_qsmainwnd.h" #else #include "forms/qsmainwnd.ui.h" /* generated btnStop, btnPlay, etc. */ #endif #if defined SEQ66_PORTMIDI_SUPPORT #include "icons/route66.xpm" #endif #include "pixmaps/learn.xpm" #include "pixmaps/learn2.xpm" #include "pixmaps/loop.xpm" #include "pixmaps/panic.xpm" /* panic.xpm was too big and fuzzy */ #include "pixmaps/pause.xpm" #include "pixmaps/perfedit.xpm" #include "pixmaps/play2.xpm" #include "pixmaps/song_rec_no_snap.xpm" #include "pixmaps/song_rec_off.xpm" #include "pixmaps/song_rec_on.xpm" /* #include "pixmaps/song_rec.xpm" */ #include "pixmaps/stop.xpm" #define SEQ66_USE_RECORD_EX_BUTTON /* STILL IN PROGRESS */ #if defined SEQ66_USE_RECORD_EX_BUTTON #include "pixmaps/rec_ex_buss.xpm" /* green */ #include "pixmaps/rec_ex_channel.xpm" /* yellow */ #include "pixmaps/rec_ex_normal.xpm" /* red */ #endif /** * This is an option, on by default, in libseq66/include/seq66_features.h. */ #if defined SEQ66_USE_SHOW_HIDE_BUTTON #include "pixmaps/hide.xpm" #include "pixmaps/show.xpm" #endif /** * Used in show_error_box_ex(). */ #define SEQ66_ERROR_BOX_WIDTH 600 namespace seq66 { /** * The default name of the current (if empty) tune. Also refer to the * function rc().session_midi_filename(). */ const std::string s_default_tune = "newtune.midi"; /** * Manifest constant to indicate the location of each main-window tab. */ enum tabs_t { Tab_Live = 0, Tab_Song = 1, Tab_Editor = 2, Tab_Events = 3, Tab_Playlist = 4, Tab_Set_Master = 5, Tab_Mute_Master = 6, Tab_Session = 7 }; /** * Manifest constants for the beat-measure and beat-length combo-boxes. * * Unused: * * static const int s_beat_measure_count = 16; */ /** * Given a display coordinate, looks up the screen and returns its geometry. * If no screen was found, return the primary screen's geometry */ static QRect desktop_rectangle (const QPoint & p) { QList screens = QGuiApplication::screens(); Q_FOREACH(const QScreen *screen, screens) { if (screen->geometry().contains(p)) return screen->geometry(); } return QGuiApplication::primaryScreen()->geometry(); } /** * Provides the main window for the application. * * \param p * Provides the performer object to use for interacting with this * sequence. * * \param midifilename * Provides an optional MIDI file-name. If provided, the file is opened * immediately. * * \param usensm * If true, this changes the menu to be suitable for keeping work * products inside an NSM session directory. * * \param parent * Provides the parent window/widget for this container window. Defaults * to null. */ qsmainwnd::qsmainwnd ( performer & p, const std::string & midifilename, bool usensm, qt5nsmanager * sessionmgr ) : QMainWindow (nullptr), performer::callbacks (p), ui (new Ui::qsmainwnd), m_session_mgr (sessionmgr), m_initial_width (0), m_initial_height (0), m_live_frame (nullptr), m_perfedit (nullptr), m_song_frame64 (nullptr), m_edit_frame (nullptr), m_event_frame (nullptr), m_playlist_frame (nullptr), m_msg_error (nullptr), m_msg_save_changes (nullptr), m_timer (nullptr), m_menu_recent (nullptr), /* QMenu * */ m_recent_action_list (), /* QList */ m_beat_ind (nullptr), m_dialog_prefs (nullptr), m_dialog_about (nullptr), m_dialog_build_info (nullptr), m_dialog_app_info (nullptr), m_dialog_log_view (nullptr), m_session_frame (nullptr), m_set_master (nullptr), m_mute_master (nullptr), m_ppqn_list (supported_ppqns(), true), /* add a blank slot */ m_beatwidth_list (beatwidth_items()), /* see settings module */ m_beats_per_bar_list (beats_per_bar_items()), /* ditto */ m_main_bpm (0.0), m_control_status (automation::ctrlstatus::none), m_song_mode (false), m_is_looping (false), m_use_nsm (usensm), m_is_title_dirty (true), m_tick_time_as_bbt (false), /* toggled in constructor */ m_previous_tick (0), m_is_playing_now (false), m_open_editors (), m_open_live_frames (), m_perf_frame_visible (false), m_current_main_set (0), m_shrunken (usr().shrunken()) { ui->setupUi(this); /* * Does not seem to work. */ setMouseTracking(true); /* NEW ca 2025-09-17 */ #if defined SEQ66_PORTMIDI_SUPPORT /* * Let's try to use a true Windows icon here to override the Linux-allowed * xpm file defined the the qsmainwnd.ui file. That doesn't work, so we * fall back to our reliable #include xpm method. */ #if defined THIS_CODE_WORKS QIcon icon; icon.addFile ( QString::fromUtf8(":/icons/route66.ico"), QSize(), QIcon::Normal, QIcon::On ); setWindowIcon(icon); #else QPixmap pixmap(route66); QIcon icon; icon.addPixmap(pixmap, QIcon::Normal, QIcon::On); setWindowIcon(icon); #endif #endif // defined SEQ66_PORTMIDI_SUPPORT int w = usr().mainwnd_x(); /* normal, maybe scaled */ int h = usr().mainwnd_y(); resize(QSize(w, h)); /* scaled values */ w = usr().mainwnd_x_min(); /* scaled even smaller */ h = usr().mainwnd_y_min(); setMinimumSize(QSize(w, h)); /* minimum values */ QPoint pt; /* default at (0, 0) */ QRect screen = desktop_rectangle(pt); /* avoids deprecated func */ int x = (screen.width() - width()) / 2; /* center on the screen */ int y = (screen.height() - height()) / 2; move(x, y); if (usr().lock_main_window()) setFixedSize(width(), height()); /* * Combo-box for tweaking the PPQN. */ (void) set_ppqn_combo(); /* * Global output buss items. Connected later on in this constructor. */ const clockslist & opm = output_port_map(); mastermidibus * mmb = cb_perf().master_bus(); ui->cmb_global_bus->addItem("None"); if (not_nullptr(mmb)) { int buses = opm.active() ? opm.count() : mmb->get_num_out_buses() ; for (int bus = 0; bus < buses; ++bus) { e_clock ec; std::string busname; if (cb_perf().ui_get_clock(bussbyte(bus), ec, busname)) { bool unavailable = cb_perf().is_port_unavailable ( bus, midibase::io::output ); bool disabled = ec == e_clock::disabled; ui->cmb_global_bus->addItem(qt(busname)); if (disabled || unavailable) enable_bus_item(bus, false); } } /* * Global buss combo-box. If we set the buss override, we have to add * 1 to it to allow for the "None" entry. */ bussbyte buss_override = usr().midi_buss_override(); if (is_good_buss(buss_override) && buss_override < buses) ui->cmb_global_bus->setCurrentIndex(int(buss_override) + 1); else ui->cmb_global_bus->setCurrentIndex(0); connect ( ui->cmb_global_bus, SIGNAL(currentIndexChanged(int)), this, SLOT(update_midi_bus(int)) ); } /* * Fill options for beats per measure in the combo box, and set the * default. For both the beat-measure and beat-length combo-boxes, we * tack on an additional entry for "32". */ (void) fill_combobox(ui->cmb_beat_measure, beats_per_bar_list()); /* * Fill options for beat length (beat width) in the combo box, and set the * default. Note that the actual value is selected via a switch statement * in the update_beat_length() function. See that function for the true * story. */ (void) fill_combobox(ui->cmb_beat_length, beatwidth_list()); m_msg_save_changes = new QMessageBox(this); m_msg_save_changes->setText(tr("Unsaved changes detected.")); m_msg_save_changes->setInformativeText(tr("Do you want to save them?")); m_msg_save_changes->setStandardButtons ( QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel ); m_msg_save_changes->setDefaultButton(QMessageBox::Save); /* * B:B:T vs H:M:S button is now incorporated into the tick/time display. * Set HMS/BBT text and background color. */ std::string css = usr().time_colors_css(); if (! css.empty()) ui->btnBBTHMS->setStyleSheet(qt(css)); toggle_time_format(true); /* slightly tricky */ connect ( ui->btnBBTHMS, SIGNAL(clicked(bool)), this, SLOT(toggle_time_format(bool)) ); /* * Main time bar at top right. */ m_dialog_prefs = new (std::nothrow) qseditoptions(cb_perf(), this); m_beat_ind = new (std::nothrow) qsmaintime ( cb_perf(), ui->verticalWidget /*this*/, 4, 4 ); m_dialog_about = new (std::nothrow) qsabout(this); m_dialog_app_info = new (std::nothrow) qsappinfo(this); m_dialog_log_view = new (std::nothrow) qslogview(this); m_dialog_build_info = new (std::nothrow) qsbuildinfo(this); make_perf_frame_in_tab(); /* create m_song_frame64 pointer */ m_live_frame = new (std::nothrow) qslivegrid ( cb_perf(), this, screenset::unassigned(), ui->LiveTab ); if (not_nullptr(m_live_frame)) { ui->LiveTabLayout->addWidget(m_live_frame); } m_playlist_frame = new (std::nothrow) qplaylistframe ( cb_perf(), this, ui->PlaylistTab ); if (not_nullptr(m_playlist_frame)) ui->PlaylistTabLayout->addWidget(m_playlist_frame); connect ( ui->actionExportProject, SIGNAL(triggered(bool)), this, SLOT(export_project()) ); connect ( ui->actionExportSong, SIGNAL(triggered(bool)), this, SLOT(export_song()) ); connect ( ui->actionExportMIDI, SIGNAL(triggered(bool)), this, SLOT(export_file_as_midi()) ); connect ( ui->actionExportSMF0, SIGNAL(triggered(bool)), this, SLOT(export_file_as_smf_0()) ); if (cb_perf().smf_format() != 0) ui->smf0Button->hide(); else ui->smf0Button->show(); /* * File / Import / Import MIDI to Current Set. This action reads a MIDI * file and inserts it into the currently-selected set. */ connect ( ui->actionImportMIDI, SIGNAL(triggered(bool)), this, SLOT(import_midi_into_set()) ); /** * File / Import / Import Project. */ connect ( ui->actionImportProject, SIGNAL(triggered(bool)), this, SLOT(import_project()) ); /** * File / Import / Import Playlist. This action will import a playlsit * and all of the tunes associated with it. */ connect ( ui->actionImportPlaylist, SIGNAL(triggered(bool)), this, SLOT(import_playlist()) ); if (use_nsm()) { /* * Xfce4 doesn't seem to respect this flag change! */ Qt::WindowFlags f = windowFlags(); Qt::WindowFlags c = Qt::WindowCloseButtonHint; f = f & (~c); setWindowFlags(f); connect_nsm_slots(); } else connect_normal_slots(); connect(ui->actionQuit, SIGNAL(triggered(bool)), this, SLOT(quit())); /* * Help menu. */ connect ( ui->actionAbout, SIGNAL(triggered(bool)), this, SLOT(show_qsabout()) ); connect ( ui->actionBuildInfo, SIGNAL(triggered(bool)), this, SLOT(show_qsbuildinfo()) ); connect ( ui->actionAppInfo, SIGNAL(triggered(bool)), this, SLOT(show_qsappinfo()) ); connect ( ui->actionLogView, SIGNAL(triggered(bool)), this, SLOT(show_qslogview()) ); connect ( ui->actionSongSummary, SIGNAL(triggered(bool)), this, SLOT(slot_summary_save()) ); connect ( ui->actionTutorial, SIGNAL(triggered(bool)), this, SLOT(slot_tutorial()) ); connect ( ui->actionUserManual, SIGNAL(triggered(bool)), this, SLOT(slot_user_manual()) ); /* * Edit Menu. First connect the preferences dialog to the main window's * Edit / Preferences menu entry. Then connect all the new Edit menu * entries. We wire in a slot to allow syncing the Preferences dialog * with the current status. */ if (not_nullptr(m_dialog_prefs)) { connect ( ui->actionPreferences, SIGNAL(triggered(bool)), this, SLOT(slot_open_edit_prefs()) ); } connect ( ui->actionSongEditor, SIGNAL(triggered(bool)), this, SLOT(open_performance_edit()) ); connect ( ui->actionSongTranspose, SIGNAL(triggered(bool)), this, SLOT(apply_song_transpose()) ); connect ( ui->actionClearMuteGroups, SIGNAL(triggered(bool)), this, SLOT(clear_mute_groups()) ); connect ( ui->actionReloadMuteGroups, SIGNAL(triggered(bool)), this, SLOT(reload_mute_groups()) ); connect ( ui->actionMuteAllTracks, SIGNAL(triggered(bool)), this, SLOT(set_song_mute_on()) ); connect ( ui->actionUnmuteAllTracks, SIGNAL(triggered(bool)), this, SLOT(set_song_mute_off()) ); /* * Provide a menu entry to toggle all tracks. We also add a track toggle * button for quicker access. */ connect ( ui->actionToggleAllTracks, SIGNAL(triggered(bool)), this, SLOT(set_song_mute_toggle()) ); std::string keyname = cb_perf().automation_key(automation::slot::toggle_mutes); tooltip_with_keystroke(ui->btnMute, keyname); ui->btnMute->setCheckable(true); /* ok? */ /* * Keep it enabled even in Song mode. * * if (cb_perf().song_mode()) * ui->btnMute->setEnabled(false); */ connect ( ui->btnMute, SIGNAL(clicked(bool)), this, SLOT(set_song_mute_toggle()) ); connect ( ui->actionCopyCurrentSet, SIGNAL(triggered(bool)), this, SLOT(set_playscreen_copy()) ); ui->actionCopyCurrentSet->setEnabled(true); connect ( ui->actionPasteToCurrentSet, SIGNAL(triggered(bool)), this, SLOT(set_playscreen_paste()) ); ui->actionPasteToCurrentSet->setEnabled(false); /* * Stop button. */ ui->btnStop->setCheckable(true); keyname = cb_perf().automation_key(automation::slot::stop); tooltip_with_keystroke(ui->btnStop, keyname); connect(ui->btnStop, SIGNAL(clicked(bool)), this, SLOT(stop_playing())); qt_set_icon(stop_xpm, ui->btnStop); /* * Pause button. */ ui->btnPause->setCheckable(true); keyname = cb_perf().automation_key(automation::slot::playback); tooltip_with_keystroke(ui->btnPause, keyname); connect(ui->btnPause, SIGNAL(clicked(bool)), this, SLOT(pause_playing())); qt_set_icon(pause_xpm, ui->btnPause); /* * Play button. Adding support bit by bit for showing the automation key * in the tool-tips where applicable. Per issue #114. */ ui->btnPlay->setCheckable(true); keyname = cb_perf().automation_key(automation::slot::start); tooltip_with_keystroke(ui->btnPlay, keyname); connect(ui->btnPlay, SIGNAL(clicked(bool)), this, SLOT(start_playing())); qt_set_icon(play2_xpm, ui->btnPlay); ui->btnPlay->setFocus(); /* * L/R Loop button (and Text Underrun button hiding). While working issue * #114, added automation::slot::loop_LR to the mix. */ if (m_shrunken) { ui->btnLoop->hide(); ui->txtUnderrun->hide(); ui->lineEditPpqn->hide(); status_message("GUI is 'shrunken'"); } else { ui->btnLoop->setCheckable(true); ui->btnLoop->setChecked(cb_perf().looping()); keyname = cb_perf().automation_key(automation::slot::loop_LR); tooltip_with_keystroke(ui->btnLoop, keyname); connect(ui->btnLoop, SIGNAL(clicked(bool)), this, SLOT(set_loop(bool))); qt_set_icon(loop_xpm, ui->btnLoop); } /* * Song Play (Live vs Song) button. */ ui->btnSongPlay->setCheckable(false); /* hmmmmmm */ keyname = cb_perf().automation_key(automation::slot::song_mode); tooltip_with_keystroke(ui->btnSongPlay, keyname); connect ( ui->btnSongPlay, SIGNAL(clicked(bool)), this, SLOT(set_song_mode(bool)) ); /* * New Record button to select the current pattern for recording, * all input-bussed patterns, or all patterns that have an output channel * in force. */ connect ( ui->btnRecordEx, SIGNAL(clicked(bool)), this, SLOT(recording_ex(bool)) ); update_record_by_status(); /* * Record-Song button. What about the song_rec_off_xpm file? */ keyname = cb_perf().automation_key(automation::slot::song_record); tooltip_with_keystroke(ui->btnRecord, keyname); connect ( ui->btnRecord, SIGNAL(clicked(bool)), this, SLOT(song_recording(bool)) ); qt_set_icon(song_rec_off_xpm, ui->btnRecord); /* * Performance Editor button. The keystroke shortcut specified in the * ui file. */ if (m_shrunken) { ui->btnPerfEdit->hide(); } else { connect ( ui->btnPerfEdit, SIGNAL(clicked(bool)), this, SLOT(load_qperfedit(bool)) ); qt_set_icon(perfedit_xpm, ui->btnPerfEdit); } /* * Set-Reset button. */ connect ( ui->setResetButton, SIGNAL(clicked(bool)), this, SLOT(reset_sets()) ); /* * Group-learn ("L") button. */ keyname = cb_perf().automation_key(automation::slot::mod_glearn); tooltip_with_keystroke(ui->button_learn, keyname); connect ( ui->button_learn, SIGNAL(clicked(bool)), this, SLOT(learn_toggle()) ); qt_set_icon(learn_xpm, ui->button_learn); /* * Tap BPM button. */ keyname = cb_perf().automation_key(automation::slot::tap_bpm); tooltip_with_keystroke(ui->button_tap_bpm, keyname); connect ( ui->button_tap_bpm, SIGNAL(clicked(bool)), this, SLOT(tap()) ); /* * Keep Queue button. */ keyname = cb_perf().automation_key(automation::slot::keep_queue); tooltip_with_keystroke(ui->button_keep_queue, keyname); connect ( ui->button_keep_queue, SIGNAL(clicked(bool)), this, SLOT(queue_it()) ); ui->button_keep_queue->setCheckable(true); /* * BPM (beats-per-minute) spin-box. */ std::string keysnames = cb_perf().automation_key(automation::slot::bpm_dn); keysnames += " "; keysnames += cb_perf().automation_key(automation::slot::bpm_up); tooltip_with_keystroke(ui->spinBpm, keysnames); m_main_bpm = cb_perf().bpm(); ui->spinBpm->setReadOnly(false); ui->spinBpm->setDecimals(usr().bpm_precision()); ui->spinBpm->setSingleStep(usr().bpm_step_increment()); ui->spinBpm->setValue(m_main_bpm); connect ( ui->spinBpm, SIGNAL(valueChanged(double)), this, SLOT(update_bpm(double)) ); connect ( ui->spinBpm, SIGNAL(editingFinished()), this, SLOT(edit_bpm()) ); /* * Beat Length (Width) combo-box. */ connect ( ui->cmb_beat_length, SIGNAL(currentIndexChanged(int)), this, SLOT(update_beat_length(int)) ); /* * Beats-Per-Measure combo-box. */ connect ( ui->cmb_beat_measure, SIGNAL(currentIndexChanged(int)), this, SLOT(update_beats_per_measure(int)) ); /* * Tab-change callback. */ connect ( ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabWidgetClicked(int)) ); /* * Pattern editor callbacks. One for editing in the tab, and the other * for editing in an external pattern editor window. Also added is a * signal/callback to create an external live-frame window. */ if (not_nullptr(m_live_frame)) { connect_editor_slots(); connect ( m_live_frame, SIGNAL(signal_live_frame(int)), this, SLOT(load_live_frame(int)) ); } /* * Panic button. */ keyname = cb_perf().automation_key(automation::slot::panic); tooltip_with_keystroke(ui->btnPanic, keyname); connect(ui->btnPanic, SIGNAL(clicked(bool)), this, SLOT(panic())); qt_set_icon(panic_xpm, ui->btnPanic); /* * Set Number and Name. */ QString bname = qt(cb_perf().set_name(0)); ui->txtBankName->setText(bname); ui->spinBank->setRange(0, cb_perf().screenset_max() - 1); keysnames = cb_perf().automation_key(automation::slot::ss_dn); keysnames += " "; keysnames += cb_perf().automation_key(automation::slot::ss_up); tooltip_with_keystroke(ui->spinBank, keysnames); connect ( ui->setHomeButton, SIGNAL(clicked(bool)), this, SLOT(slot_set_home()) ); connect ( ui->spinBank, SIGNAL(valueChanged(int)), this, SLOT(update_bank(int)) ); connect ( ui->txtBankName, SIGNAL(editingFinished()), this, SLOT(update_bank_text()) ); connect ( this, SIGNAL(signal_set_change(int)), this, SLOT(update_set_change(int)) ); connect ( this, SIGNAL(signal_song_action(int)), this, SLOT(update_song_action(int)) ); /* * Mute-group name. */ ui->txtMuteName->setText("None"); /* * The MIDI file is now opened and read in the performer before the * creation of this window, to avoid issues with unset PPQN values * causing segfaults. * * open_file(midifilename); */ update_window_title(); if (! midifilename.empty()) enable_save(false); load_set_master(); load_mute_master(); load_session_frame(); ui->tabWidget->setCurrentIndex(Tab_Live); ui->tabWidget->setTabEnabled(Tab_Events, false); /* prevents issues */ connect ( ui->btnSeq66, SIGNAL(clicked(bool)), this, SLOT(slot_close_externals()) ); #if defined SEQ66_USE_SHOW_HIDE_BUTTON /* * Show/Hide button. */ ui->btnShowHide->setCheckable(true); ui->btnShowHide->setChecked(false); /* start w/showing */ ui->btnShowHide->setEnabled(true); ui->btnShowHide->setToolTip("Show/hide many controls to save space"); qt_set_icon(hide_xpm, ui->btnShowHide); connect ( ui->btnShowHide, SIGNAL(clicked(bool)), this, SLOT(slot_show_hide()) ); #else ui->btnShowHide->hide(); #endif /* * Test button. This button supports whatever debugging we need to do at * any particular time. */ if (rc().investigate_disabled()) { ui->testButton->setToolTip("No Test functionality at present."); connect(ui->testButton, SIGNAL(clicked(bool)), this, SLOT(slot_test())); } else { if (m_shrunken) { ui->testButton->hide(); } else { ui->testButton->setEnabled(false); ui->testButton->setToolTip("Developer test button disabled."); } } if (use_nsm()) { std::string tune_name = s_default_tune; std::string fname = rc().midi_filename(); if (! fname.empty()) tune_name = fname; rc().session_midi_filename(tune_name); } #if defined SEQ66_PORTMIDI_SUPPORT ui->alsaJackButton->setText("PortMidi"); ui->jackTransportButton->hide(); #else QString midiengine = rc().with_jack_midi() ? "JACK" : "?" ; if (rc().with_alsa_midi()) midiengine = "ALSA"; if (cb_perf().is_jack_master()) ui->jackTransportButton->setText("Master"); else if (cb_perf().is_jack_slave()) ui->jackTransportButton->setText("Slave"); else ui->jackTransportButton->hide(); ui->alsaJackButton->setText(midiengine); #endif if (! rc().investigate()) ui->label_test->hide(); show(); show_song_mode(m_song_mode); (void) refresh_captions(); cb_perf().enregister(this); m_timer = qt_timer(this, "qsmainwnd", 3, SLOT(conditional_update())); } /** * Destroys the user interface and removes any external qperfeditex that * exists. */ qsmainwnd::~qsmainwnd () { /* * This should be done by Qt since the "this" parameter was used. * * if (not_nullptr(m_msg_error)) * delete m_msg_error; * * if (not_nullptr(m_msg_save_changes)) * delete m_msg_save_changes; */ if (not_nullptr(m_timer)) m_timer->stop(); cb_perf().unregister(this); delete ui; } void qsmainwnd::lock_main_window (bool lockit) { if (lockit) setFixedSize(width(), height()); else setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); } void qsmainwnd::enable_bus_item (int bus, bool enabled) { /* * Why plus one? Changed ca 2025-05-19. * * int index = bus + 1; */ int index = bus; enable_combobox_item(ui->cmb_global_bus, index, enabled); } /** * Handles closing this window by calling check(), and, if it returns false, * ignoring the close event. * * If running under NSM, we need to respect a SIGTERM signal. This can come * from the window manager or from nsmd. To reduce (but not eliminate) the * chance of the user killing the application, we have hidden/disabled the "X" * button and the File / Exit menu entry. Note that sending a SIGTERM must * still work to support NSM, and note that a window manager will likely have * other ways to send a SIGTERM. See issue #41. * * \param event * Provides a pointer to the close event to be checked. */ void qsmainwnd::closeEvent (QCloseEvent * event) { /* * ca 2026-04-23 Let's stop the timer first. */ if (not_nullptr(m_timer)) m_timer->stop(); if (usr().in_nsm_session()) { session_message("Close event with NSM"); remove_everything(); } else { session_message("Close event"); if (check()) remove_everything(); else event->ignore(); } } /** * Pulls defaults from song frame. */ void qsmainwnd::make_perf_frame_in_tab () { m_song_frame64 = new (std::nothrow) qperfeditframe64 ( cb_perf(), ui->SongTab ); if (not_nullptr(m_song_frame64)) { int bpmeasure = m_song_frame64->get_beats_per_measure(); int beatwidth = m_song_frame64->get_beat_width(); ui->SongTabLayout->addWidget(m_song_frame64); ui->cmb_beat_length->setCurrentText(QString::number(beatwidth)); ui->cmb_beat_measure->setCurrentText(QString::number(bpmeasure)); if (not_nullptr(m_beat_ind)) { ui->layout_beat_ind->addWidget(m_beat_ind); m_beat_ind->beat_width(beatwidth); m_beat_ind->beats_per_measure(bpmeasure); } qperfroll * pr = m_song_frame64->perf_roll(); if (not_nullptr(pr)) { /* * This does not make sense; there is no bool * parameter in signal_call_editor_ex(). */ connect // standalone sequence editor ( pr, SIGNAL(signal_call_editor_ex(int, bool)), this, SLOT(load_qseqedit_ex(int, bool)) ); } qperfnames * pn = m_song_frame64->perf_names(); if (not_nullptr(pn)) { connect // standalone sequence editor ( pn, SIGNAL(signal_call_editor_ex(int, bool)), this, SLOT(load_qseqedit_ex(int, bool)) ); } } } /** * Helper to keep buttons in sync. */ void qsmainwnd::stop (bool rewind) { if (rewind) cb_perf().auto_stop(true); /* rewind to 0 */ else cb_perf().auto_stop(); ui->btnPause->setChecked(false); /* force off */ ui->btnPlay->setChecked(false); /* force off */ } /** * Calls performer::stop_key() and unchecks the Play button. */ void qsmainwnd::stop_playing () { Qt::KeyboardModifiers qkm = QGuiApplication::keyboardModifiers(); bool rewind = (qkm & Qt::ShiftModifier) != 0; stop(rewind); ui->btnPlay->setFocus(); } /** * Implements the pause button. Using stop() breaks the pause function. */ void qsmainwnd::pause_playing () { cb_perf().auto_pause(); /* update_play_status() */ } /** * Implements the play button. */ void qsmainwnd::start_playing () { cb_perf().auto_play(); ui->btnPause->setChecked(false); /* force off */ ui->btnStop->setChecked(false); /* force off */ ui->btnStop->setFocus(); } /** * This function handles the status of the Stop, Pause, and Play buttons. * It is needed especially when the user uses the Space bar, Period, or * Escape to change the playing status. */ void qsmainwnd::update_play_status () { bool in_play = cb_perf().is_pattern_playing(); bool in_pause = cb_perf().is_pattern_paused(); bool playstate_change = in_play != m_is_playing_now; if (playstate_change) { m_is_playing_now = in_play; if (in_play) { ui->btnStop->setChecked(false); ui->btnPause->setChecked(false); ui->btnPlay->setChecked(true); } else if (in_pause) /* as opposed to stopped */ { ui->btnStop->setChecked(false); ui->btnPause->setChecked(true); ui->btnPlay->setChecked(false); } else { ui->btnStop->setChecked(true); ui->btnPause->setChecked(false); ui->btnPlay->setChecked(false); } } } void qsmainwnd::update_record_by_status () { #if defined SEQ66_USE_RECORD_EX_BUTTON if (cb_perf().record_by_buss()) { qt_set_icon(rec_ex_buss_xpm, ui->btnRecordEx); /* green */ ui->btnRecordEx->setEnabled(true); } else if (cb_perf().record_by_channel()) { qt_set_icon(rec_ex_channel_xpm, ui->btnRecordEx); /* yellow */ ui->btnRecordEx->setEnabled(true); } else { qt_set_icon(rec_ex_normal_xpm, ui->btnRecordEx); /* red */ ui->btnRecordEx->setEnabled(cb_perf().have_current_seq()); } #else ui->btnRecordEx->hide(); #endif } /** * There is a button with the same functionality in the Song editor. */ void qsmainwnd::set_loop (bool looping) { cb_perf().looping(looping); if (not_nullptr(m_perfedit)) m_perfedit->set_loop_button(looping); } void qsmainwnd::toggle_loop () { bool looping = ! cb_perf().looping(); set_loop(looping); } /** * New, depends on mode of recording. If neither route-by-buss or * route-by-channel is enabled, then the recording status of the performer's * current sequence (if any) is toggled. */ void qsmainwnd::recording_ex (bool record) { bool ok = cb_perf().set_recording_ex(record); if (! ok) { // todo? } } /** * Note the usage of the Ctrl and Shift keys, when clicking, in relation to * issue #44. * * - No modifier key. Turn snap on for recording. * - Control key. Turn snap off for recording. * - Shift. Arm the patterns as soon as play starts. */ void qsmainwnd::song_recording (bool record) { bool dosnap = true; bool atstart = false; const char ** pixmap = song_rec_on_xpm; if (record) { Qt::KeyboardModifiers qkm = QGuiApplication::keyboardModifiers(); if (qkm & Qt::ControlModifier) { dosnap = false; pixmap = song_rec_no_snap_xpm; } if (qkm & Qt::ShiftModifier) atstart = true; } else { pixmap = song_rec_off_xpm; } qt_set_icon(pixmap , ui->btnRecord); cb_perf().song_record_snap(dosnap); cb_perf().song_recording(record, atstart); } void qsmainwnd::show_song_mode (bool songmode) { if (songmode) { ui->btnRecord->setEnabled(true); ui->btnSongPlay->setText("Song"); } else { ui->btnRecord->setChecked(false); ui->btnRecord->setEnabled(false); ui->btnSongPlay->setText("Live"); } } /** * Sets the song mode, which is actually the JACK start mode. If true, we * are in playback/song mode. If false, we are in live mode. * * Removed: song_recording(false) */ void qsmainwnd::set_song_mode (bool /*songmode*/) { bool playmode = cb_perf().toggle_song_mode(); if (playmode) { /* * No longer disabled in Song mode. * * ui->btnMute->setEnabled(true); */ } else { /* * Always enabled now. * * ui->btnMute->setEnabled(true); */ song_recording(false); } show_song_mode(playmode); /* * Disabled. See the comments in qslivegrid. * * if (not_nullptr(m_live_frame)) * m_live_frame->enable_solo(! playmode); */ } void qsmainwnd::set_ppqn_text (const std::string & text) { if (! text.empty()) { QString p = qt(text); ui->lineEditPpqn->setText(p); } } void qsmainwnd::set_ppqn_text (int ppq) { if (ppqn_in_range(ppq)) { std::string temp = std::to_string(ppq); QString ppqntext = qt(temp); ui->cmb_ppqn->setItemText(0, ppqntext); usr().file_ppqn(ppq); ui->cmb_ppqn->setCurrentIndex(0); } } /** * Also restores the BPM (tempo) value. */ void qsmainwnd::reset_ppqn () { usr().reset_ppqn(); /* restore startup PPQN */ cb_perf().ppqn(usr().midi_ppqn()); std::string pstring = std::to_string(usr().midi_ppqn()); set_ppqn_text(pstring); int index = ppqn_list().index(pstring) - 1; ui->cmb_ppqn->setCurrentIndex(index); update_bpm(usr().midi_beats_per_minute()); } bool qsmainwnd::set_ppqn_combo () { int ppq = cb_perf().ppqn(); std::string p = std::to_string(ppq); bool result = fill_combobox(ui->cmb_ppqn, ppqn_list(), p); if (result) { std::string pstring = std::to_string(ppq); ui->lineEditPpqn->setReadOnly(true); set_ppqn_text(pstring); connect ( ui->cmb_ppqn, SIGNAL(currentTextChanged(const QString &)), this, SLOT(update_ppqn_by_text(const QString &)) ); } return result; } /** * This gets called when just typing numbers into the BPM field! It gets * called before edit_bpm(). * * This doesn't make sense, isn't the BPM value definitely different??? */ void qsmainwnd::update_bpm (double bp) { midibpm bpold = m_main_bpm; // ui->spinBpm->value() already changed! if (bp != bpold) { if (cb_perf().set_beats_per_minute(midibpm(bp), true)) { m_main_bpm = bp; enable_save(); } } } void qsmainwnd::edit_bpm () { midibpm bp = ui->spinBpm->value(); if (cb_perf().set_beats_per_minute(bp, true)) enable_save(); } void qsmainwnd::slot_open_edit_prefs () { m_dialog_prefs->show(); m_dialog_prefs->sync(); } void qsmainwnd::slot_summary_save () { std::string fname = rc().midi_filename(); /* a full pathspec */ if (fname.empty()) { // nothing to do yet } else { fname = file_extension_set(fname, ".text"); if (show_text_file_dialog(this, fname)) write_song_summary(cb_perf(), fname); } } void qsmainwnd::slot_tutorial () { #if defined USE_QDESKTOPSERVICES /* currently undefined */ QString link = qt(tutpath); /* "http://www.google.com" */ QDesktopServices::openUrl(QUrl(link)); #else (void) open_tutorial(); #endif } /** * To do: tighten this up a bit. */ void qsmainwnd::slot_user_manual () { #if defined USE_QDESKTOPSERVICES /* currently undefined */ QString link = qt(docpath); QDesktopServices::openUrl(QUrl::fromLocalFile(link)); #else (void) open_user_manual(); #endif } /** * For NSM usage, this function replaces the "Open" operation. It will * create a copy of the file which is then saved at the session path provided * by NSM. The question is, do we want to import configuration files as well * as a MIDI file? Well, no, because, when we first run qseq66 via NSM, we * immediately create the $NSM_HOME/config directory and immediately copy the * configuration files from $HOME/.config/seq66 there. * * So here, we want to ask the user for the MIDI/WRK file, read it in, and * then immediately save it to the $NSM_HOME/midi directory. */ void qsmainwnd::import_midi_into_session () { if (use_nsm()) { std::string selectedfile; (void) load_into_session(selectedfile); } } bool qsmainwnd::load_into_session (const std::string & selectedfile) { bool result = false; std::string filename = selectedfile; if (show_open_file_dialog(filename)) { if (open_file(filename)) { /* * Change the filename to reflect the base NSM directory * and immediately save it. */ std::string basename = filename_base(filename); rc().session_midi_filename(basename); /* make NSM name */ std::string mfilename = rc().midi_filename(); song_path(mfilename); last_used_dir(rc().last_used_dir()); /* hope this is set */ std::string msg = save_file(mfilename, false) ? "Saved: " : "Failed to save: "; msg += rc().midi_filename(); show_error_box(msg); result = true; if (not_nullptr(m_mute_master)) m_mute_master->group_needs_update(); } else { /* open_file() will show the error message. */ } } return result; } /** * Shows the "Open" file dialog. If a file is selected, then the file is * opened. */ void qsmainwnd::select_and_load_file () { std::string selectedfile = rc().last_used_dir(); if (show_open_file_dialog(selectedfile)) { if (open_file(selectedfile)) { if (! usr().is_buss_override()) ui->cmb_global_bus->setCurrentIndex(0); if (not_nullptr(m_mute_master)) m_mute_master->group_needs_update(); } } } /** * Shows the "Open" file dialog, if not within an NSM session. Otherwise, * import_midi_into_session() is called. */ bool qsmainwnd::show_open_file_dialog (std::string & selectedfile) { bool result = false; if (check()) { result = show_open_midi_file_dialog(this, selectedfile); if (result) stop(); } return result; } /** * For importing a playlist file and all of the songs it contains. */ void qsmainwnd::import_playlist () { std::string sourcepath; bool ok = show_playlist_dialog(this, sourcepath, OpeningFile); if (ok) { std::string destdir = rc().home_config_directory(); std::string cfgpath; std::string midipath; std::string midisubdir = "playlists"; std::string midibase = filename_base(sourcepath, true); /* no ext */ midisubdir = pathname_concatenate(midisubdir, midibase); ok = session()->make_path_names(destdir, cfgpath, midipath, midisubdir); if (ok) { ok = cb_perf().import_playlist(sourcepath, cfgpath, midipath); if (ok) { rc().set_imported_playlist(sourcepath, midipath); if (! cmdlineopts::write_options_files()) session_message("Configuration write failed"); if (use_nsm()) { session_message("Restart via NSM UI needed"); } else { session_message("Restarting with imported playlist"); signal_for_restart(); /* "reboot" the application */ } } } } } /** * Opens the dialog to request a playlist. This action should not be allowed * in an NSM session; instead, a playlist can be imported. This is a slot, * which calls a member function that callers can call directly and get a * boolean status, unlike this function. */ void qsmainwnd::show_open_list_dialog () { if (check()) (void) open_list_dialog(); } bool qsmainwnd::open_list_dialog () { std::string fname; bool result = show_playlist_dialog(this, fname, OpeningFile); if (result) { result = not_nullptr(m_playlist_frame); if (result) { cb_perf().playlist_activate(true); /* ca 2023-07-17 */ rc().playlist_active(true); /* ditto */ refresh_captions(); /* ditto */ result = m_playlist_frame->load_playlist(fname); if (! result) show_error_box(cb_perf().playlist_error_message()); } } return result; } /** * This function prompts the user for a directory to be used as the base * directory for a play-list sub-list. */ std::string qsmainwnd::specify_playlist_folder (const std::string & defalt) { std::string result; std::string temp = defalt; std::string prompt = "Select directory for the MIDI files"; bool ok = show_folder_dialog(this, temp, prompt, true); /* force home */ if (ok) result = temp; return result; } /** * This function lets one select a directory and type in a file-name for * a playlist file at the beginning of its creation. */ bool qsmainwnd::specify_list_dialog () { bool result = false; std::string fname = rc().playlist_filespec(); if (use_nsm()) { // TODO } else { std::string prompt = "Select a directory and type a base name for the playlist"; bool ok = show_file_dialog ( this, fname, prompt, "Playlist file (*.playlist);;All files (*)", SavingFile, NormalFile, ".playlist" ); if (ok) { fname = file_extension_set(fname, ".playlist"); rc().playlist_filename(fname); rc().auto_playlist_save(true); rc().auto_rc_save(true); result = not_nullptr(m_playlist_frame); if (result) { result = m_playlist_frame->load_playlist(fname); if (! result) show_error_box(cb_perf().playlist_error_message()); } } } return result; } /** * Opens the dialog to save a playlist file. This action should be allowed * in an NSM session, but defaults to the configuration directory. * * NOT YET CONNECTED. */ void qsmainwnd::show_save_list_dialog () { if (check()) (void) save_list_dialog(); } bool qsmainwnd::save_list_dialog () { std::string fname = rc().playlist_filename(); bool result = show_playlist_dialog(this, fname, SavingFile); if (result) { fname = file_extension_set(fname, ".playlist"); result = cb_perf().save_playlist(fname); if (result) { /* performer will handle rc().playlist_filename(fname) */ } else show_error_box(cb_perf().playlist_error_message()); } return result; } /** * Opens the dialog to request a mutegroups file. * * NOT YET CONNECTED. */ void qsmainwnd::show_open_mutes_dialog () { (void) open_mutes_dialog(); } bool qsmainwnd::open_mutes_dialog () { std::string fname; bool result = show_file_dialog ( this, fname, "Open mute-groups file", "Mutes-groups (*.mutes);;All (*)", OpeningFile, ConfigFile ); if (result) { result = not_nullptr(m_mute_master); if (result) { result = m_mute_master->load_mutegroups(fname); if (result) { fname = filename_base(fname); rc().mute_group_filename(fname); rc().mute_group_file_active(true); m_dialog_prefs->sync(); /* also call apply()? */ } else show_error_box("Mute-groups loading error"); // TODO } else { // what to do? } } return result; } /** * Not yet connected. */ void qsmainwnd::show_save_mutes_dialog () { if (check()) (void) save_mutes_dialog(); } /** * Called by qmutemaster::slot_save(). */ bool qsmainwnd::save_mutes_dialog (const std::string & basename) { std::string fname = basename; bool result = show_file_dialog ( this, fname, "Save mute-groups file", "Mutes-groups (*.mutes);;All (*)", SavingFile, ConfigFile, ".mutes" ); if (result) { result = not_nullptr(m_mute_master); if (result) { /* * Guarantee the '.mutes' extension. */ fname = file_extension_set(fname, ".mutes"); result = m_mute_master->save_mutegroups(fname); if (result) { rc().mute_group_filename(fname); m_dialog_prefs->sync(); /* also call apply()? */ } else show_error_box("Mute-groups saving error"); } else { // what to do? } } return result; } /** * Update all of the children. Doesn't seem to work for the edit frames, may * have to recreate them, or somehow hook in the new sequence objects (as * pointers, not references). Probably an issue to be ignored; the user will * have to close and reopen the pattern editor(s). * * Also sets the current file-name and the last-used directory to the ones * just loaded. */ bool qsmainwnd::open_file (const std::string & fn) { bool result = false; if (check()) { if (not_nullptr(m_mute_master)) m_mute_master->reset(); std::string errmsg; result = cb_perf().read_midi_file(fn, errmsg); if (result) { remove_all_editors(); /* moved from below */ redo_live_frame(); if (not_nullptr(m_song_frame64)) { m_song_frame64->update_sizes(); m_song_frame64->reset_zoom(); } if (not_nullptr(m_perfedit)) m_perfedit->update_sizes(); /* * The tabbed edit frame is automatically removed if no other * seq-edit is open. * * remove_all_editors(); // moved to above // */ set_ppqn_text(cb_perf().ppqn()); if (! use_nsm()) /* does this menu exist? */ { /* * Just because we open a file doesn't mean it needs to be * saved. * * enable_save(file_writable(fn)); */ enable_save(false); update_recent_files_menu(); } if (not_nullptr(m_session_frame)) { m_session_frame->reload_song_info(); song_path(fn); last_used_dir(rc().last_used_dir()); } if (not_nullptr(m_mute_master)) m_mute_master->reload_mute_groups(); if (cb_perf().port_map_error()) /* ca 2023-06-01 */ { std::string msg = "Unavailable port(s) specified in MIDI file. " "Perhaps modify MIDI file to specify available ports. " ; msg += cb_perf().error_messages(); bool yes = show_error_box_ex(msg, false); if (yes) cb_perf().store_io_maps_and_restart(); } cb_perf().unmodify(); m_is_title_dirty = true; } else { show_error_box(errmsg); update_recent_files_menu(); } } return result; } /* * Reinitialize the "Live" frame. Reconnect its signal, as we've made a new * object. */ void qsmainwnd::redo_live_frame () { if (not_nullptr(m_live_frame)) { ui->LiveTabLayout->removeWidget(m_live_frame); delete m_live_frame; } m_live_frame = new (std::nothrow) qslivegrid ( cb_perf(), this, screenset::unassigned(), ui->LiveTab ); if (not_nullptr(m_live_frame)) { ui->LiveTabLayout->addWidget(m_live_frame); connect_editor_slots(); /* external pattern editor window */ connect /* external live frame window */ ( m_live_frame, SIGNAL(signal_live_frame(int)), this, SLOT(load_live_frame(int)) ); m_live_frame->show(); /* * This is not necessary. And it causes copies painter errors * since it bypasses paintEvent(). If we do need to redraw, call * m_live_frame->repaint() instead. * * m_live_frame->redraw(); */ } } void qsmainwnd::update_window_title (const std::string & fn) { std::string itemname = fn.empty() ? cb_perf().main_window_title(fn) : fn ; itemname += " [*]"; /* required by Qt 5 */ QString fname = qt(itemname); setWindowTitle(fname); /* title must come 1st */ setWindowModified(cb_perf().modified()); /* perhaps show the '*' */ } /** * Toggles the recording of the live song control done by the musician. * This functionality currently does not have a key devoted to it, nor is it * a saved setting. But it now has a MIDI automation slot, mod_bbt_hms. */ void qsmainwnd::toggle_time_format (bool /*on*/) { m_tick_time_as_bbt = ! m_tick_time_as_bbt; QString label = m_tick_time_as_bbt ? "B:B:T" : "H:M:S" ; ui->btnBBTHMS->setText(label); update_time(cb_perf().get_tick()); } void qsmainwnd::update_time (midipulse tick) { std::string t = m_tick_time_as_bbt ? cb_perf().pulses_to_measure_string(tick) : cb_perf().pulses_to_time_string(tick) ; ui->btnBBTHMS->setText(qt(t)); } void qsmainwnd::load_session_frame () { if (is_nullptr(m_session_frame)) { qsessionframe * qsf = new (std::nothrow) qsessionframe ( cb_perf(), this, ui->SessionTab ); if (not_nullptr(qsf)) { ui->SessionTabLayout->addWidget(qsf); m_session_frame = qsf; } } } /** * Handles resetting the performer's play-set to the current set. */ void qsmainwnd::reset_sets () { cb_perf().reset_playset(); } void qsmainwnd::remove_set_master () { if (not_nullptr(m_set_master)) { delete m_set_master; m_set_master = nullptr; } } /** * The debug statement shows us that the main-window size starts at * 920 x 680, goes to 800 x 480 (unscaled) briefly, and then back to * 920 x 680. */ void qsmainwnd::conditional_update () { if (session_close()) { m_timer->stop(); close(); return; } if (session_save()) (void) save_session(); int active_screenset = int(cb_perf().playscreen_number()); std::string b = "#"; b += std::to_string(active_screenset); b += " / "; /* * b += std::to_string(cb_perf().screenset_count()); */ b += std::to_string(cb_perf().screenset_active_count()); ui->entry_active_set->setText(qt(b)); if (ui->button_keep_queue->isChecked() != cb_perf().is_keep_queue()) ui->button_keep_queue->setChecked(cb_perf().is_keep_queue()); if (m_song_mode != cb_perf().song_mode()) { m_song_mode = cb_perf().song_mode(); show_song_mode(m_song_mode); } if (m_is_looping != cb_perf().looping()) { m_is_looping = cb_perf().looping(); ui->btnLoop->setChecked(m_is_looping); } midipulse tick = cb_perf().get_tick(); if (tick != m_previous_tick) { /* * Calculate the current time, and display it. Update beat indicator. */ m_previous_tick = tick; if (not_nullptr(m_beat_ind)) { update_time(tick); m_beat_ind->update(); } } if (m_is_title_dirty) { (void) refresh_captions(); update_window_title(); /* puts current MIDI file in title */ } update_play_status(); if (! m_shrunken) { if (m_is_playing_now) { long delta = cb_perf().delta_us(); if (delta != 0) { std::string dus = std::to_string(int(delta)); ui->txtUnderrun->setText(qt(dus)); } } else ui->txtUnderrun->setText("-"); } if (cb_perf().tap_bpm_timeout()) set_tap_button(0); } /** * Prompts the user to save the MIDI file. Check if the file has been * modified. If modified, ask the user whether to save changes. * * \return * Returns true if the file was saved or the changes were "discarded" by * the user. */ bool qsmainwnd::check () { bool result = false; if (cb_perf().modified() && ! use_nsm()) { int choice = m_msg_save_changes->exec(); switch (choice) { case QMessageBox::Save: result = save_file(); break; case QMessageBox::Discard: cb_perf().unmodify(); /* avoid saving in save_session() */ rc().clear_midi_filename(); result = true; break; case QMessageBox::Cancel: default: break; } } else result = true; return result; } /** * Prompts for a MIDI file-name, returning it as, what else, a C++ std::string. * * \param prompt * The prompt to display. * * \return * Returns the name of the file, which will include the path to the file. * If empty, the user cancelled. */ std::string qsmainwnd::midi_filename_prompt ( const std::string & prompt, const std::string & file, bool promptoverwrite ) { std::string result = file.empty() ? rc().last_used_dir() : file ; bool ok = show_file_dialog ( this, result, prompt, "MIDI files (*.midi *.mid);;All files (*)", SavingFile, NormalFile, ".midi", promptoverwrite ); if (ok) { // nothing yet } else result.clear(); return result; } /** * Prompts for an 'rc' file name. * * \param prompt * The prompt to display. * * \return * Returns the name of the file, which will include the path to the file. * If empty, the user cancelled. */ std::string qsmainwnd::project_folder_prompt ( const std::string & prompt ) { std::string result; bool ok = show_folder_dialog( this, result, prompt, true); /* force home */ if (ok) { // nothing yet } else result.clear(); return result; } /** * We were passing true to performer::clear_all() to clear the playlist as * well. However, that doesn't clear the playlist frame, and the user may * want to build a new tune to add to the playlist. */ void qsmainwnd::new_file () { if (check() && cb_perf().clear_all()) /* don't clear playlist */ { stop(); /* ca 2023-12-11 */ enable_save(false); /* no save until change */ redo_live_frame(); remove_all_editors(); if (not_nullptr(m_song_frame64)) m_song_frame64->set_dirty(); /* refresh empty song */ (void) cb_perf().reset_mute_groups(); /* no modify() call */ cb_perf().song_mode(false); m_song_mode = cb_perf().song_mode(); show_song_mode(m_song_mode); if (! usr().is_buss_override()) ui->cmb_global_bus->setCurrentIndex(0); if (not_nullptr(m_mute_master)) m_mute_master->group_needs_update(); std::string nofile; song_path(nofile); rc().clear_midi_filename(); /* no file in force yet */ reset_ppqn(); /* restore startup PPQN */ m_is_title_dirty = true; } } /** * This option will simply empty or reset the current file, after user * confirmation. According to NSM protocol, it cannot allow the user to * create a new project/file in another location. All this function does is * set the file-name, if supplied, to be used later to save the MIDI file to * the current NSM session. * * After setting the file name, the code of the normal new_file() function is * called, but without prompting to save the current MIDI file. Any playlist * loaded is also cleared from memory. * * From the NSM API: * * This option may empty/reset the current file or project (possibly * after user confirmation). UNDER NO CIRCUMSTANCES should it allow the * user to create a new project/file in another location. */ void qsmainwnd::new_session () { if (use_nsm()) { /* * We need show only the base name of the file. */ std::string tune_name = rc().midi_filename(); std::string path, defname; if (tune_name.empty()) tune_name = s_default_tune; (void) filename_split(tune_name, path, defname); #if defined USE_OLD_CODE bool ok; QString text = QInputDialog::getText ( this, tr("Session MIDI File"), /* parent and title */ tr("MIDI FIle Base Name"), /* input field label */ QLineEdit::Normal, qt(defname), &ok ); #else std::string text = qt_get_string ( this, "Session MIDI File", /* parent and title */ "MIDI FIle Base Name", /* input field label */ defname /* default text */ ); bool ok = ! text.empty(); #endif if (ok) { if (cb_perf().clear_all()) /* like new_file() */ { m_is_title_dirty = true; redo_live_frame(); remove_all_editors(); /* * TODO: consolidate */ (void) cb_perf().reset_mute_groups(); /* no modify() call */ cb_perf().song_mode(false); m_song_mode = cb_perf().song_mode(); show_song_mode(m_song_mode); reset_ppqn(); /* restore startup PPQN */ if (not_nullptr(m_mute_master)) m_mute_master->group_needs_update(); } if (text.empty()) { file_message("Session MIDI file", "Cleared"); } else { std::string filenamebase = text; rc().session_midi_filename(filenamebase); file_message("Session MIDI file", rc().midi_filename()); } } } else { // illegal } } /** * This option, for NSM usage, saves the current file in the NSM location * specified by the NSM "open" message. According to NSM protocol, it cannot * offer to play the file in another location. For that purpose, see the * "Export" menu entry. * * The question here is what we want to save. Here are the candidates: * * - The current MIDI file, if any. * - The "rc" file, along with any "ctrl", "mutes", and "playlist" * files it specifies. * - The "usr" file. Normally saved only if --user-save is specified. * * I think this action should unconditionaly save them all. No prompting. */ bool qsmainwnd::save_session () { bool result = false; if (use_nsm()) { if (not_nullptr(session())) { std::string msg; result = session()->save_session(msg); if (result) enable_save_update(false); else show_error_box(msg); } } else { result = save_file(); } return result; } void qsmainwnd::enable_save_update (bool flag) { enable_save(flag); /* disable "File / Save" */ update_all_editors_titles(flag); /* update title bars */ cb_perf().unmodify(); /* avoid saving later */ m_is_title_dirty = true; } bool qsmainwnd::save_file (const std::string & fname, bool updatemenu) { bool result = false; std::string filename = fname.empty() ? rc().midi_filename() : fname ; if (filename.empty()) { result = save_file_as(); } else { bool save_it = true; bool is_wrk = file_extension_match(filename, "wrk"); if (is_wrk) { std::ostringstream os; std::string wrkname = filename; filename = file_extension_set(filename, ".midi"); os << "Saving the Cakewalk WRK file " << wrkname << " in Seq66 format as " << filename ; save_it = report_message(os.str(), true); } if (save_it) { std::string errmsg; result = write_midi_file(cb_perf(), filename, errmsg); if (result) { std::string path = filename_path(filename); if (! path.empty()) { last_used_dir(path); rc().last_used_dir(path); } enable_save_update(false); song_path(filename); if (updatemenu) /* or ! use_nsm() */ update_recent_files_menu(); /* add the recent file-name */ } else show_error_box(errmsg); } } if (result) m_is_title_dirty = true; return result; } bool qsmainwnd::save_file_as () { bool result = false; std::string prompt = use_nsm() ? "Export MIDI file from session as..." : "Save MIDI file as..." ; std::string currentfile = rc().midi_filename(); bool is_wrk = file_extension_match(currentfile, "wrk"); if (is_wrk) currentfile = file_extension_set(currentfile, ".midi"); std::string filename = midi_filename_prompt(prompt, currentfile, true); if (filename.empty()) { // no code, the user cancelled } else { result = save_file(filename); if (result) { enable_save(false); /* disable "File / Save" */ rc().midi_filename(filename); } } return result; } /** * Prompts for a file-name, then exports the current tune as a standard * MIDI file, stripping out the Seq66 SeqSpec information. Does not * update the the current file-name, but does update the recent-file * information at this time. It does not preserve the triggers. * This function is equivalent to export_song(), except it calls midifile :: * write() instead of midifile :: write_song(). * * \param fname * The full path-name to the file to be written. If empty (the default), * then the user is prompted for the file-name. * * \return * Returns true if the file was successfully written. */ bool qsmainwnd::export_file_as_midi (const std::string & fname) { bool result = false; std::string filename; if (fname.empty()) { std::string prompt = "Export file as standard MIDI..."; filename = midi_filename_prompt(prompt, "", true); } else filename = fname; if (filename.empty()) { /* * Maybe later, add some kind of warning dialog. */ } else { midifile f(filename, choose_ppqn()); result = f.write(cb_perf(), false); /* no SeqSpec */ if (! result) show_error_box(f.error_message()); } return result; } bool qsmainwnd::export_project (const std::string & fname) { bool result = false; std::string foldername; if (fname.empty()) { std::string prompt = "Export project configuration..."; foldername = project_folder_prompt(prompt); } else foldername = fname; if (foldername.empty()) { /* * Maybe later, add some kind of warning dialog. */ } else { if (not_nullptr(session())) { std::string selectedfile = rc().config_filename(); bool ok = session()->export_session_configuration ( foldername, selectedfile ); if (ok) { if (use_nsm()) { } else { } } else { std::string msg = "Could not export to " + foldername; show_error_box(msg); } } } return result; } /** * Prompts for a file-name, then exports the current tune as a standard * MIDI file, stripping out the Seq66 SeqSpec information. Does not * update the current file-name, but does update the the recent-file * information at this time. * * This function is ESSENTIALLY EQUIVALENT to export_file_as_midi()!!! * * \param fname * The full path-name to the file to be written. If empty (the default), * then the user is prompted for the file-name. * * \return * Returns true if the file was successfully written. */ bool qsmainwnd::export_song (const std::string & fname) { bool result = false; std::string filename; if (fname.empty()) { std::string prompt = "Export Song..."; filename = midi_filename_prompt(prompt, "", true); } else filename = fname; if (filename.empty()) { /* * Maybe later, add some kind of warning dialog. */ } else { midifile f(filename, choose_ppqn()); bool result = f.write_song(cb_perf()); if (result) { if (rc().add_recent_file(filename)) update_recent_files_menu(); } else show_error_box(f.error_message()); } return result; } void qsmainwnd::import_midi_into_set () { std::string selectedfile; bool selected = show_import_midi_file_dialog(this, selectedfile); if (selected) { QString path = qt(selectedfile); if (! path.isEmpty()) { try { int setno = int(cb_perf().playscreen_number()); std::string fn = path.toStdString(); bool is_wrk = file_extension_match(fn, "wrk"); midifile * f = is_wrk ? new (std::nothrow) wrkfile(fn, choose_ppqn()) : new (std::nothrow) midifile(fn, choose_ppqn()) ; if (f->parse(cb_perf(), setno, true)) /* true-->importing */ { ui->spinBpm->setDecimals(usr().bpm_precision()); ui->spinBpm->setSingleStep(usr().bpm_step_increment()); /* * ui->spinBpm->setValue(cb_perf().bpm()); */ set_beats_per_minute(cb_perf().bpm(), true); update_bank(setno); (void) refresh_captions(); } } catch (...) { std::string p = path.toStdString(); std::string msg = "Error reading MIDI data from file: " + p; show_error_box(msg); } } } } /* * We do not want to save any configuration after the import. We need to * restart the app to load the new configuration; we tell the user that this * needs to be done.. */ /** * This version uses smanager::import_into_session(), which calls, among * other things, copy_configuration(), which depends on rc().config_files() * which will not have the same file-name entries as the import source * directory. */ void qsmainwnd::import_project () { std::string selecteddir; std::string selectedfile; bool selected = show_select_project_dialog(this, selecteddir, selectedfile); if (selected && not_nullptr(session())) { if (session()->import_into_session(selecteddir, selectedfile)) { rc().disable_save_list(); /* save no configs at exit */ if (use_nsm()) { std::string msg = "Stop and then restart Seq66 in NSM GUI"; qt_info_box(this, msg); } else { session_message("Restarting with imported configuration"); signal_for_restart(); /* "reboot" the application */ } } else { std::string msg = "Could not import from " + selecteddir; show_error_box(msg); } } } void qsmainwnd::show_qsabout () { if (not_nullptr(m_dialog_about)) m_dialog_about->show(); } void qsmainwnd::show_qsbuildinfo () { if (not_nullptr(m_dialog_build_info)) m_dialog_build_info->show(); } void qsmainwnd::show_qsappinfo () { if (not_nullptr(m_dialog_app_info)) m_dialog_app_info->show(); } void qsmainwnd::show_qslogview () { if (not_nullptr(m_dialog_log_view)) { m_dialog_log_view->show(); m_dialog_log_view->refresh(); } } /** * Loads a slightly compressed qseqeditframe64 for the selected * sequence into the "Edit" tab. It is compressed by hiding some of * the buttons and by halving the height of the seqdata frame. * * \param seqid * The slot value (0 to 1024) of the sequence to be edited. * * \warning * Somehow, checking for not_nullptr(m_edit_frame) to determine whether * to remove or add that widget causes the edit frame to not get created, * and then the Edit tab is empty. * * There are still some issues here: * * - Making sure that there is only one sequence editor window/tab * per pattern. * - Removing the current song editor window/tab when another MIDI file * is loaded. */ void qsmainwnd::load_editor (int seqid) { if (make_edit_frame(seqid)) update(); } bool qsmainwnd::make_edit_frame (int seqid) { seq::pointer s = cb_perf().get_sequence(seqid); bool result = bool(s); if (result) { if (not_nullptr(m_edit_frame)) { ui->EditTabLayout->removeWidget(m_edit_frame); /* no ptr check */ delete m_edit_frame; /* hmmmmmmmmmmm */ } m_edit_frame = new (std::nothrow) qseqeditframe64 ( cb_perf(), *s, ui->EditTab, true /* short frame */ ); result = not_nullptr(m_edit_frame); if (result) { ui->EditTabLayout->addWidget(m_edit_frame); m_edit_frame->show(); ui->tabWidget->setCurrentIndex(Tab_Editor); } } return result; } void qsmainwnd::remove_edit_tab_frame () { if (not_nullptr(m_edit_frame)) { ui->tabWidget->setCurrentIndex(Tab_Live); ui->EditTabLayout->removeWidget(m_edit_frame); delete m_edit_frame; m_edit_frame = nullptr; ui->tabWidget->setTabEnabled(Tab_Editor, false); } } void qsmainwnd::remove_event_tab_frame () { if (not_nullptr(m_event_frame)) { ui->tabWidget->setCurrentIndex(Tab_Live); ui->EventTabLayout->removeWidget(m_event_frame); delete m_event_frame; m_event_frame = nullptr; ui->tabWidget->setTabEnabled(Tab_Events, false); } } /** * This function first looks to see if a piano roll editor is already open * for this sequence. If so, we will not open the event-editor frame, to * avoid conflicts. */ void qsmainwnd::load_event_editor (int seqid) { seq::pointer seq = cb_perf().get_sequence(seqid); bool ok = bool(seq); #if defined DISALLOW_EDITOR_CONFLICT if (ok) { auto ei = m_open_editors.find(seqid); ok = ei == m_open_editors.end(); /* 1 editor/seq */ } #endif if (ok) { if (make_event_frame(seqid)) { ui->tabWidget->setTabEnabled(Tab_Events, true); ui->tabWidget->setCurrentIndex(Tab_Events); m_event_frame->set_initialized(); update(); } } } void qsmainwnd::load_set_master () { qsetmaster * qsm = new (std::nothrow) qsetmaster ( cb_perf(), this, ui->SetMasterTab ); if (not_nullptr(qsm)) { ui->SetsTabLayout->addWidget(qsm); m_set_master = qsm; } } void qsmainwnd::load_mute_master () { qmutemaster * qsm = new (std::nothrow) qmutemaster ( cb_perf(), this, ui->MuteMasterTab ); if (not_nullptr(qsm)) { ui->MutesTabLayout->addWidget(qsm); m_mute_master = qsm; } } /** * Opens an external window for editing the sequence. This window * (qseqeditex with an embedded qseqeditframe64) is much more like the Gtkmm * seqedit window, and somewhat more functional. It has no parent widget, * otherwise the whole big dialog will appear inside that parent. * * We make sure the sequence exists. We should consider creating it if it * does not exist. So many features, so little time. * * \warning * We have to make sure the pattern ID is valid. Somehow, we can * double-click on the qsmainwnd's set/bank roller and get this function * called! For now, we work around that bug. * * \param seqid * The slot value (0 to 1024) of the sequence to be edited. */ void qsmainwnd::load_qseqedit (int seqid) { bool isactive = cb_perf().is_seq_active(seqid); if (! isactive) isactive = cb_perf().is_metronome(seqid); /* a special case */ if (isactive) { auto ei = m_open_editors.find(seqid); if (ei == m_open_editors.end()) { qseqeditex * ex = new (std::nothrow) qseqeditex ( cb_perf(), seqid, this ); if (not_nullptr(ex)) { auto p = std::make_pair(seqid, ex); m_open_editors.insert(p); ex->show(); } } else { /* * Try to raise the already open pattern-editor. */ qseqeditex * ed { ei->second }; ed->setWindowState ( (windowState() & ~Qt::WindowMinimized) | Qt::WindowActive ); #if defined SEQ66_PLATFORM_WINDOWS ed->activateWindow(); #else ed->raise(); #endif } } } /** * Compare to qslivegrid::new_sequence(). This function is called * from the song window's name column. */ void qsmainwnd::load_qseqedit_ex (int seqno, bool active) { if (active) { load_qseqedit(seqno); } else { if (cb_perf().request_sequence(seqno)) /* new_sequence() */ { msgprintf(msglevel::status, "New Pattern %d", int(seqno)); load_qseqedit(seqno); /* * alter_sequence(seqno); // adds a grid button * m_parent->remove_editor(m_current_seq); // issue #93 */ } } } /** * Removes the editor window from the list. This function is called by the * editor window to tell its parent (this) that it is going away. Note that * this does not delete the editor, it merely removes the pointer to it. * * \param seqid * The sequence number that the editor represented. */ void qsmainwnd::remove_editor (int seqno) { auto ei = m_open_editors.find(seqno); if (ei != m_open_editors.end()) { qseqeditex * qep = ei->second; /* save the pointer */ m_open_editors.erase(ei); if (not_nullptr(qep)) qep->close(); /* just signal to close */ /* * Deleting this pointer makes qseq66 segfault, and valgrind doesn't * seem to show any leak. Commented out. This fixes issue #4. It * also needs to be backported to Sequencer64. * * qseqeditex * qep = ei->second; // save the pointer * if (not_nullptr(qep)) * delete qep; // delete the pointer */ } } /** * Centralizes removal of all windows. This is needed in case of a restart * operation with external live frames and external song editor. Some windows * were left open during a restart, causing a seqfault or other abort. * * See closeEvent(), quit(), and ~qsmainwnd(). */ void qsmainwnd::remove_everything () { remove_all_editors(); remove_qperfedit(); remove_all_live_frames(); remove_set_master(); } /** * Uses the standard "associative-container erase-remove idiom". Otherwise, * the current iterator is invalid, and a segfault results in the top of the * for-loop. Another option with C++11 is "ci = m_open_editors.erase(ei)". */ void qsmainwnd::remove_all_editors () { remove_edit_tab_frame(); remove_event_tab_frame(); remove_ex_editors(); } /** * We need to remove editors when loading a file, as well. */ void qsmainwnd::remove_ex_editors () { for ( auto ei = m_open_editors.begin(); ei != m_open_editors.end(); /* ++ei */ ) { qseqeditex * qep = ei->second; /* save the pointer */ m_open_editors.erase(ei++); /* remove pointer, inc iterator */ if (not_nullptr(qep)) qep->close(); /* just signal to close */ } } void qsmainwnd::update_all_editors_titles (bool modified) { for (auto & qed : m_open_editors) qed.second->set_title(modified); } /** * \param on * Disabled for now. */ void qsmainwnd::load_qperfedit (bool /*on*/) { if (is_nullptr(m_perfedit)) { qperfeditex * ex = new (std::nothrow) qperfeditex(cb_perf(), this); if (not_nullptr(ex)) { m_perfedit = ex; hide_qperfedit(false); /* * Leave it enabled now to do show versus hide to avoid a weird * segfault. * * ui->btnPerfEdit->setEnabled(false); */ qperfroll * pr = m_perfedit->perf_roll(); if (not_nullptr(pr)) { connect // standalone sequence editor ( pr, SIGNAL(signal_call_editor_ex(int, bool)), this, SLOT(load_qseqedit_ex(int, bool)) ); } qperfnames * pn = m_perfedit->perf_names(); if (not_nullptr(pn)) { connect // standalone sequence editor ( pn, SIGNAL(signal_call_editor_ex(int, bool)), this, SLOT(load_qseqedit_ex(int, bool)) ); } } } else hide_qperfedit(); } /** * Shows or hides the external performance editor window. We use to just * delete it, but somehow this started causing a segfault when X-ing * (closing) that window. So now we just keep it around until the * application is exited. * * \param hide * If true, the performance editor is unconditionally hidden. Otherwise, * it is shown if hidden, or hidden if showing. The default value is * false. */ void qsmainwnd::hide_qperfedit (bool hide) { if (not_nullptr(m_perfedit)) { if (hide) { m_perfedit->hide(); m_perf_frame_visible = false; } else { if (m_perf_frame_visible) m_perfedit->hide(); else m_perfedit->show(); m_perf_frame_visible = ! m_perf_frame_visible; } } } /** * Removes the single song editor window. This function is called by the * editor window to tell its parent (this) that it is going away. */ void qsmainwnd::remove_qperfedit () { if (not_nullptr(m_perfedit)) { qperfeditex * tmp = m_perfedit; m_perfedit = nullptr; delete tmp; ui->btnPerfEdit->setEnabled(true); } } /** * Opens an external live frame. It has no parent widget, otherwise the whole * big dialog will appear inside that parent. * * \param ssnum * The screen-set value (0 to 31) of the live-frame to be displayed. */ void qsmainwnd::load_live_frame (int ssnum) { if (ssnum >= 0 && ssnum < cb_perf().screenset_max()) { auto ei = m_open_live_frames.find(ssnum); if (ei == m_open_live_frames.end()) { qliveframeex * ex = new (std::nothrow) qliveframeex ( cb_perf(), ssnum, this ); if (not_nullptr(ex)) { auto p = std::make_pair(ssnum, ex); m_open_live_frames.insert(p); ex->show(); } } } } /** * Removes the live frame window from the list. This function is called by * the live frame window to tell its parent (this) that it is going away. * Note that this does not delete the editor, it merely removes the pointer * to it. * * \param ssnum * The screen-set number that the live frame represented. */ void qsmainwnd::remove_live_frame (int ssnum) { auto ei = m_open_live_frames.find(ssnum); if (ei != m_open_live_frames.end()) m_open_live_frames.erase(ei); } /** * Uses the standard "associative-container erase-remove idiom". Otherwise, * the current iterator is invalid, and a segfault results in the top of the * for-loop. Another option with C++11 is "ci = m_open_editors.erase(ei)". */ void qsmainwnd::remove_all_live_frames () { for (auto i = m_open_live_frames.begin(); i != m_open_live_frames.end() ; ) { qliveframeex * lep = i->second; /* save the pointer */ m_open_live_frames.erase(i++); /* remove pointer, inc iterator */ if (not_nullptr(lep)) delete lep; /* delete the pointer */ } } void qsmainwnd::update_ppqn_by_text (const QString & text) { std::string temp = text.toStdString(); if (ppqn_list().valid(temp)) { int p = string_to_int(temp); if (cb_perf().change_ppqn(p)) { set_ppqn_text(p); if (not_nullptr(m_song_frame64)) m_song_frame64->set_guides(); enable_save(); } } } /** * Sets the MIDI bus to use for output for the current set and its sequences. * A convenience when dealing with one MIDI output device. Must be applied * individually to each set; this allows each set to drive a different buss. * * \param index * The index into the list of available MIDI buses. The drop-down has * already been remapped (if port-mapping is active). */ void qsmainwnd::update_midi_bus (int index) { mastermidibus * mmb = cb_perf().master_bus(); if (not_nullptr(mmb)) { if (index == 0) { usr().midi_buss_override(null_buss()); /* for the "None" entry */ } else { (void) cb_perf().ui_change_set_bus(index - 1); usr().midi_buss_override(bussbyte(index - 1), true); /* user */ enable_save(); } } } /** * We could set the value using pow(2, blindex). */ void qsmainwnd::update_beat_length (int blindex) { int bl = beatwidth_list().ctoi(blindex); if (cb_perf().set_beat_width(bl, true)) /* a user change */ { enable_save(); if (not_nullptr(m_song_frame64)) /* qperfedit in tab */ m_song_frame64->set_beat_width(bl); if (not_nullptr(m_beat_ind)) /* qsmaintime */ m_beat_ind->beat_width(bl); if (not_nullptr(m_edit_frame)) /* qseqedit in tab */ m_edit_frame->update_draw_geometry(); } } /** * Sets the beats-per-measure for the beat indicator and the performer. * Also sets the beat length for all sequences, and resets the number of * measures, causing length to adjust to new b/m. * * \param bmindex * Provides the index into the list of beats. This number is one less * than the actual beats-per-measure represent by that index. */ void qsmainwnd::update_beats_per_measure (int bmindex) { int bm = beats_per_bar_list().ctoi(bmindex); if (cb_perf().set_beats_per_measure(bm, true)) /* a user change */ { enable_save(); if (not_nullptr(m_song_frame64)) /* qperfedit in tab */ m_song_frame64->set_beats_per_measure(bm); if (not_nullptr(m_beat_ind)) /* qsmaintime */ m_beat_ind->beats_per_measure(bm); if (not_nullptr(m_edit_frame)) /* qseqedit in tab */ m_edit_frame->update_draw_geometry(); } } /** * If we've selected the edit tab, make sure it has something to edit. * * \warning * Somehow, checking for not_nullptr(m_edit_frame) to determine whether * to remove or add that widget causes the edit frame to not get created, * and then the Edit tab is empty. */ void qsmainwnd::tabWidgetClicked (int newindex) { bool isnull = is_nullptr(m_edit_frame); seq::number seqid = cb_perf().first_seq(); /* seq in playscreen? */ if (isnull) { if (newindex == Tab_Editor) { bool ignore = false; if (seqid == seq::unassigned()) /* no, make a new one */ { /* * This is too mysterious, and not sure we want to bother to * update the live tab to show a new sequence. * * seqid = cb_perf().playscreen_offset(); * (void) cb_perf().new_sequence(seqid); */ ignore = true; } if (! ignore) { if (make_edit_frame(seqid)) update(); } } else { // No code } } isnull = is_nullptr(m_event_frame); if (isnull) { if (newindex == Tab_Events) { bool ignore = false; if (seqid == seq::unassigned()) /* no, make a new one */ { /* * This is too mysterious, and not sure we want to bother to * update the live tab to show a new sequence. * * seqid = cb_perf().playscreen_offset(); * (void) cb_perf().new_sequence(seqid); */ ignore = true; } if (! ignore) { if (make_event_frame(seqid)) { ui->tabWidget->setTabEnabled(Tab_Events, true); ui->tabWidget->setCurrentIndex(Tab_Events); m_event_frame->set_initialized(); update(); } } } } } /** * First, make sure the sequence exists. Consider creating it if it does not * exist. */ bool qsmainwnd::make_event_frame (int seqid) { seq::pointer seqp = cb_perf().get_sequence(seqid); bool result = bool(seqp); if (result) { if (not_nullptr(m_event_frame)) { ui->EventTabLayout->removeWidget(m_event_frame); delete m_event_frame; } m_event_frame = new (std::nothrow) qseqeventframe ( cb_perf(), *seqp, ui->EventTab ); if (not_nullptr(m_event_frame)) { ui->EventTabLayout->addWidget(m_event_frame); m_event_frame->show(); } } return result; } /** * Let's see if we can distinguish files with the same base name. */ void qsmainwnd::update_recent_files_menu () { int count = rc().recent_file_count(); if (count > 0) { bool ok = true; bool shorten = ! rc().full_recent_paths(); for (int f = 0; f < count; ++f) { std::string shortname = rc().recent_file(f, shorten); if (! shortname.empty()) { std::string longname = rc().recent_file(f, false); m_recent_action_list.at(f)->setText(qt(shortname)); m_recent_action_list.at(f)->setData(qt(longname)); m_recent_action_list.at(f)->setVisible(true); } else { ok = false; break; } } if (ok) { for (int fj = count; fj < rc().recent_file_max(); ++fj) m_recent_action_list.at(fj)->setVisible(false); ui->menuFile->insertMenu(ui->actionSave, m_menu_recent); m_menu_recent->setEnabled(true); } } else m_menu_recent->setEnabled(false); } void qsmainwnd::create_action_connections () { for (int i = 0; i < rc().recent_file_max(); ++i) { QAction * action = new_qaction("", this); if (not_nullptr(action)) { action->setVisible(false); QObject::connect ( action, &QAction::triggered, this, &qsmainwnd::open_recent_file ); m_recent_action_list.append(action); } } } /** * Deletes the recent-files menu and recreates it, inserts it into the File * menu. Not sure why we picked create_action_menu() for the name of this * function. */ void qsmainwnd::create_action_menu () { if (not_nullptr(m_menu_recent) && m_menu_recent->isWidgetType()) delete m_menu_recent; int count = rc().recent_file_max(); m_menu_recent = new_qmenu("&Recent MIDI Files...", this); if (count > 0) { m_menu_recent->setEnabled(true); for (int i = 0; i < rc().recent_file_max(); ++i) m_menu_recent->addAction(m_recent_action_list.at(i)); } else m_menu_recent->setEnabled(false); ui->menuFile->insertMenu(ui->actionSave, m_menu_recent); } /** * Opens the selected recent file. */ void qsmainwnd::open_recent_file () { QAction * action = qobject_cast(sender()); if (not_nullptr(action) && check()) { QString fname = QVariant(action->data()).toString(); std::string actionfile = fname.toStdString(); if (! actionfile.empty()) { if (open_file(actionfile)) { if (rc().add_recent_file(actionfile)) /* ca 2025-05-31 */ update_recent_files_menu(); if (! usr().is_buss_override()) ui->cmb_global_bus->setCurrentIndex(0); if (not_nullptr(m_mute_master)) m_mute_master->group_needs_update(); } } } } void qsmainwnd::enable_reload_button (bool flag) { if (not_nullptr(m_session_frame)) m_session_frame->enable_reload_button(flag); } /** * Calls check(), and if it checks out (heh heh), remove all of the editor * windows and then calls for an exit of the application. */ void qsmainwnd::quit () { if (use_nsm()) { cb_perf().hidden(true); hide(); m_session_mgr->send_visibility(false); } else { if (check()) { remove_everything(); /* remove_all_editors() */ QCoreApplication::exit(); } } } /* EXPERIMENTAL * void qsmainwnd::mouseMoveEvent (QMouseEvent * // event) { printf("qsmainwnd::mouseMoveEvent\n"); } */ /** * By experimenting, we see that the live frame gets all of the keystrokes. * So we moved much of the processing to that class. If the event isn't * handled there, then qsmainwnd::handle_key_press() is called. * * If we reimplement this handler, it is very important to call the base * class implementation if the key is no acted on. * * ca 2020-11-22 Refactoring keystroke handling between the main window and * the live grid/frame. */ void qsmainwnd::keyPressEvent (QKeyEvent * event) { keystroke k = qt_keystroke(event, keystroke::action::press); bool done = handle_key_press(k); if (done) update(); else QWidget::keyPressEvent(event); /* event->ignore()? */ } void qsmainwnd::keyReleaseEvent (QKeyEvent * event) { keystroke k = qt_keystroke(event, keystroke::action::release); bool done = handle_key_release(k); if (done) update(); else QWidget::keyReleaseEvent(event); /* event->ignore()? */ } /** * Handles some hard-wired keystrokes. If they aren't handled, then MIDI * control-key processing is performed. * * The arrow keys support moving forward and backward through the playlists * and the songs they specify. These functions are also supported by the * MIDI automation apparatus, but are not supported by the keystroke * automation apparatus. They are considered dedicated keys. * * Note that changing the playlist name will cause a reupdate of all of the * window, including the pattern buttons in qslivegrid, causing flickering of * all armed pattern buttons. * * Also note that we have switched from direct calls the performer playlist * actions to using signals. We have to use signals with MIDI control * because of thread conflicts, so we might as well use them with the * keystrokes as well. * * \param k * Provides a wrapper for the key event. */ bool qsmainwnd::handle_key_press (const keystroke & k) { bool done = false; if (k.is_press()) { playlist::action act = playlist::action::none; if (k.is_right()) { act = playlist::action::next_song; done = true; } else if (k.is_left()) { act = playlist::action::previous_song; done = true; } else if (k.is_down()) { act = playlist::action::next_list; done = true; } else if (k.is_up()) { act = playlist::action::previous_list; done = true; } int actcode = playlist::action_to_int(act); emit signal_song_action(actcode); } if (! done) { done = cb_perf().midi_control_keystroke(k); if (cb_perf().seq_edit_pending()) { done = true; } else if (cb_perf().event_edit_pending()) { done = true; } } return done; } /** * See qslivegrid::handle_key_release(). */ bool qsmainwnd::handle_key_release (const keystroke & k) { bool result = ! k.is_press(); if (result) result = cb_perf().midi_control_keystroke(k); return result; } /** * Implements the "panic button". */ void qsmainwnd::panic() { if (cb_perf().panic()) { ui->btnStop->setChecked(true); //false); ui->btnPause->setChecked(true); //false); ui->btnPlay->setChecked(true); //false); } } /** * Quickly changes to set 0. */ void qsmainwnd::slot_set_home () { ui->spinBank->setValue(0); // update_bank(0); } /** * The qslivebase::update_bank() function and its overrides do not take care * of changing the performer's playing screenset; the live-frame does that. */ void qsmainwnd::update_bank (int bankid) { if (not_nullptr(m_live_frame)) { m_live_frame->update_bank(bankid); /* * Done in the call above: * * (void) cb_perf().set_playing_screenset(m_live_frame->bank_id()); */ std::string name = cb_perf().set_name(bankid); QString newname = qt(name); ui->txtBankName->setText(newname); } } /** * Used to grab the std::string bank name and convert it to QString for * display. Let performer set the modify flag, it knows when to do it. * Otherwise, just scrolling to the next screen-set causes a spurious * modification and an annoying prompt to a user exiting the application. */ void qsmainwnd::update_bank_text () { QString newname = ui->txtBankName->text(); std::string name = newname.toStdString(); if (not_nullptr(m_live_frame)) m_live_frame->update_bank_name(name); ui->txtBankName->setText(newname); } void qsmainwnd::show_error_box (const std::string & msgtext) { if (! msgtext.empty()) qt_error_box(this, msgtext); } bool qsmainwnd::show_error_box_ex (const std::string & msgtext, bool isporterror) { bool result = false; if (! msgtext.empty()) { if (not_nullptr(m_msg_error)) delete m_msg_error; m_msg_error = new (std::nothrow) QMessageBox(this); if (not_nullptr(m_msg_error)) { QString msg = qt(msgtext); QSpacerItem * hspace = new QSpacerItem ( SEQ66_ERROR_BOX_WIDTH, 0, QSizePolicy::Minimum, QSizePolicy::Expanding ); QGridLayout * lay = (QGridLayout *) m_msg_error->layout(); lay->addItem(hspace, lay->rowCount(), 0, 1, lay->columnCount()); QAbstractButton * yes = nullptr; if (isporterror) { (void) m_msg_error->addButton("OK", QMessageBox::NoRole); if (! usr().in_nsm_session()) { yes = m_msg_error->addButton ( "Remap and restart", QMessageBox::YesRole ); } m_msg_error->setIcon(QMessageBox::Question); } m_msg_error->setText(msg); m_msg_error->exec(); if (isporterror && ! usr().in_nsm_session()) { if (m_msg_error->clickedButton() == yes) result = true; } } } return result; } /** * This varient is meant to be used in an NSM environment. */ bool qsmainwnd::show_timed_error_box ( const std::string & msgtext, int timeout ) { if (! msgtext.empty()) { if (not_nullptr(m_msg_error)) delete m_msg_error; m_msg_error = new (std::nothrow) QMessageBox(this); if (not_nullptr(m_msg_error)) { /* * QTimer::singleShot(timeout, m_msg_error, SLOT(hide)); */ QTimer timer; /* don't use QTimer::singleShot() */ timer.setSingleShot(true); connect ( &timer, &QTimer::timeout, [&] { m_msg_error->accept(); } ); m_msg_error->setText(qt(msgtext)); m_msg_error->setStandardButtons(QMessageBox::NoButton); timer.start(timeout); m_msg_error->exec(); /* m_msg_error->show() */ } } return false; } /** * Check for an actual flag change before dirtying the title, which prevents * a lot of flickering. However, it often prevents dirtying the main title. */ void qsmainwnd::enable_save (bool flag) { bool enabled = ui->actionSave->isEnabled(); if (flag != enabled) { ui->actionSave->setEnabled(flag); m_is_title_dirty = true; } } void qsmainwnd::connect_editor_slots () { connect // connect to sequence-edit signal from the Live tab ( m_live_frame, SIGNAL(signal_call_editor(int)), this, SLOT(load_editor(int)) ); connect // new standalone sequence editor ( m_live_frame, SIGNAL(signal_call_editor_ex(int)), this, SLOT(load_qseqedit(int)) ); /* * Event editor callback. There is only one, for editing in the tab. * The event editor is meant for light use only at this time. */ connect // connect to sequence-edit signal from the Event tab ( m_live_frame, SIGNAL(signal_call_edit_events(int)), this, SLOT(load_event_editor(int)) ); } void qsmainwnd::connect_nsm_slots () { if (rc().verbose()) { infoprint("Connecting NSM File menu entries"); } /* * File / New. NSM version. */ ui->actionNew->setText("&New MIDI file..."); ui->actionNew->setToolTip("Clear and set a new MIDI file in session."); connect ( ui->actionNew, SIGNAL(triggered(bool)), this, SLOT(new_session()) ); /* * File / Open versus File / Import / Import MIDI into session. */ ui->actionOpen->setVisible(false); ui->actionImportMIDIIntoSession->setText("&Import MIDI into session..."); ui->actionImportMIDIIntoSession->setToolTip ( "Import a MIDI/Seq66 file into the current session." ); ui->menuImport->addAction(ui->actionImportMIDIIntoSession); connect ( ui->actionImportMIDIIntoSession, SIGNAL(triggered(bool)), this, SLOT(import_midi_into_session()) ); /* * File / Open Playlist. */ ui->actionOpenPlaylist->setVisible(false); /* * File / Save session. */ ui->actionSave->setText("&Save session"); ui->actionSave->setToolTip ( "Save current MIDI file and configuration in the session." ); connect ( ui->actionSave, SIGNAL(triggered(bool)), this, SLOT(save_session()) ); /* * File / Save As... */ ui->actionSave_As->setVisible(false); /* * File / Export from session */ ui->actionSave_As->setText("&Export from session..."); ui->actionSave_As->setToolTip("Export as a Seq66 MIDI file."); connect ( ui->actionSave_As, SIGNAL(triggered(bool)), this, SLOT(save_file_as()) ); /* * File / Quit ---> File / Hide */ ui->actionQuit->setText("Hide"); ui->actionClose->setVisible(false); // ui->actionClose->hide(); } void qsmainwnd::connect_normal_slots () { if (rc().verbose()) { infoprint("Connecting normal File menu entries"); } /* * File / New. Connect the GUI elements to event handlers. */ ui->actionNew->setText("&New"); ui->actionNew->setToolTip("Clear MIDI data to make a new Seq66 tune."); connect(ui->actionNew, SIGNAL(triggered(bool)), this, SLOT(new_file())); /* * File / Open versus File / Import / Import MIDI into session. */ ui->actionImportMIDIIntoSession->setVisible(false); ui->actionOpen->setText("&Open..."); ui->actionOpen->setVisible(true); ui->actionOpen->setToolTip("Open a standard or Seq66 MIDI file."); connect ( ui->actionOpen, SIGNAL(triggered(bool)), this, SLOT(select_and_load_file()) ); /* * File / Open Playlist. Also see the Load button in qsessionframe. */ ui->actionOpenPlaylist->setVisible(true); connect ( ui->actionOpenPlaylist, SIGNAL(triggered(bool)), this, SLOT(show_open_list_dialog()) ); /* * File / Save */ ui->actionSave->setText("&Save"); ui->actionSave->setToolTip("Save a Seq66 MIDI file."); if (! file_writable(rc().midi_filename())) ui->actionSave->setEnabled(false); connect(ui->actionSave, SIGNAL(triggered(bool)), this, SLOT(save_file())); /* * File / Save As... */ ui->actionSave_As->setText("Save &As..."); ui->actionSave_As->setToolTip("Save as another Seq66 MIDI file."); connect ( ui->actionSave_As, SIGNAL(triggered(bool)), this, SLOT(save_file_as()) ); /* * File / Recent MIDI files */ create_action_connections(); create_action_menu(); update_recent_files_menu(); } /** * Opens the Performance Editor (Song Editor). * * We will let performer keep track of modifications, and not just set an * is-modified flag just because we opened the song editor. We're going to * centralize the modification flag in the performer object, and see if it can * work. */ void qsmainwnd::open_performance_edit () { if (not_nullptr(m_perfedit)) { if (m_perfedit->isVisible()) m_perfedit->hide(); else m_perfedit->show(); } else load_qperfedit(true); } /** * Apply full song transposition, if enabled. Then reset the perfedit * transpose setting to 0. */ void qsmainwnd::apply_song_transpose () { if (cb_perf().get_transpose() != 0) { cb_perf().apply_song_transpose(); } } /** * Reload all mute-group settings from the "rc" file. */ void qsmainwnd::reload_mute_groups () { std::string errmessage; bool result = cb_perf().reload_mute_groups(errmessage); if (! result) show_error_box("Reload of mute-groups failed"); } /** * Clear all mute-group settings. Sets all values to false/zero. Also, * since the intent might be to clean up the MIDI file, the user is prompted * to save. * * Should we use clear_mutes() instead? */ void qsmainwnd::clear_mute_groups () { if (cb_perf().clear_mute_groups()) /* did any mute statuses change? */ { if (check()) { enable_save(); if (cb_perf().is_pattern_playing()) stop(); } } } /** * Sets the song-mute mode. */ void qsmainwnd::set_song_mute_on () { cb_perf().set_song_mute(mutegroups::action::on); if (not_nullptr(m_live_frame)) m_live_frame->refresh(); } /** * Sets the song-mute mode. */ void qsmainwnd::set_song_mute_off () { cb_perf().set_song_mute(mutegroups::action::off); if (not_nullptr(m_live_frame)) m_live_frame->refresh(); } /** * Sets the song-mute mode. */ void qsmainwnd::set_song_mute_toggle () { cb_perf().set_song_mute(mutegroups::action::toggle); if (not_nullptr(m_live_frame)) m_live_frame->refresh(); } void qsmainwnd::set_playscreen_copy () { bool ok = cb_perf().copy_playscreen(); ui->actionPasteToCurrentSet->setEnabled(ok); } /** * We want to allow multiple pastes of the same screenset. It will also * persist between MIDI files during a run. * * if (cb_perf().paste_playscreen(cb_perf().playscreen_number())) * ui->actionPasteToCurrentSet->setEnabled(false); */ void qsmainwnd::set_playscreen_paste () { (void) cb_perf().paste_to_playscreen(); } /** * Toggle the group-learn status. Simply forwards the call to * performer::learn_toggle(). */ void qsmainwnd::learn_toggle () { cb_perf().learn_toggle(); qt_set_icon ( cb_perf().is_group_learn() ? learn2_xpm : learn_xpm, ui->button_learn ); } /** * Implements the Tap button or Tap keystroke (defaults to F9). */ void qsmainwnd::tap () { midibpm bp = cb_perf().update_tap_bpm(); update_tap(bp); } void qsmainwnd::update_tap (midibpm bp) { set_tap_button(cb_perf().current_beats()); if (cb_perf().current_beats() > 1) /* first one is useless */ set_beats_per_minute(bp); /* ui->spinBpm->setValue(bp) */ } /** * Sets the label in the Tap button to the given number of taps. * * \param beats * The current number of times the user has clicked the Tap button/key. */ void qsmainwnd::set_tap_button (int beats) { char temp[8]; snprintf(temp, sizeof temp, "%d", beats); ui->button_tap_bpm->setText(temp); enable_save(); } void qsmainwnd::set_beats_per_minute (double bp, bool blockchange) { if (blockchange) ui->spinBpm->blockSignals(true); ui->spinBpm->setValue(bp); if (blockchange) ui->spinBpm->blockSignals(false); } /** * Implements the keep-queue button. */ void qsmainwnd::queue_it () { bool is_active = ui->button_keep_queue->isChecked(); cb_perf().set_keep_queue(is_active); } /** * We're adapting this control to make the main GUI as small as possible. */ void qsmainwnd::slot_test () { /* * The code here depends only on what we need to investigate at * this time. */ } /** * Clicking on the main Seq66 label (actually a push-button) * closes all open external windows (pattern editor, song editor, * and external live grid. Also performs the hide function of * the perfedit button. */ void qsmainwnd::slot_close_externals() { remove_ex_editors(); remove_all_live_frames(); if (not_nullptr(m_perfedit)) { if (m_perfedit->isVisible()) m_perfedit->hide(); } } /** * We're adapting this control to make the main GUI as small as possible. */ void qsmainwnd::slot_show_hide () { bool hidethem = ui->btnShowHide->isChecked(); if (hidethem) { qt_set_icon(show_xpm, ui->btnShowHide); ui->menuBar->hide(); ui->cmb_global_bus->hide(); ui->cmb_beat_measure->hide(); ui->cmb_beat_length->hide(); qt_set_layout_visibility(ui->hLayoutBottom_1, false); qt_set_layout_visibility(ui->hLayoutBottom_2, false); } else { qt_set_icon(hide_xpm, ui->btnShowHide); ui->menuBar->show(); ui->cmb_global_bus->show(); ui->cmb_beat_measure->show(); ui->cmb_beat_length->show(); qt_set_layout_visibility(ui->hLayoutBottom_1, true); qt_set_layout_visibility(ui->hLayoutBottom_2, true); } } bool qsmainwnd::export_file_as_smf_0 (const std::string & fname) { bool result = false; std::string filename; if (fname.empty()) { std::string prompt = "Convert and export file as SMF 0..."; filename = midi_filename_prompt(prompt, "", true); } else filename = fname; if (! filename.empty()) { if (cb_perf().convert_to_smf_0()) { midifile f(filename, choose_ppqn()); result = f.write(cb_perf(), false); /* no SeqSpec */ if (result) { rc().session_midi_filename(filename); m_is_title_dirty = true; } else show_error_box(f.error_message()); } else { std::string msg = "Could not convert to SMF 0. Verify tracks are active, " "in Live mode, and are flattened." ; show_error_box(msg); } if (cb_perf().smf_format() != 0) ui->smf0Button->hide(); else ui->smf0Button->show(); } return result; } /** * Shows a message. Returns true if the user clicked OK. */ bool qsmainwnd::report_message (const std::string & msg, bool good, bool showcancel) { bool result = false; if (! msg.empty()) { if (good) /* info message */ { QMessageBox * mbox = new QMessageBox(this); mbox->setText(qt(msg)); if (showcancel) { mbox->setInformativeText(tr("Click OK to save, or Cancel")); mbox->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); } else { mbox->setInformativeText(tr("Action complete")); mbox->setStandardButtons(QMessageBox::Ok); } int choice = mbox->exec(); result = choice == QMessageBox::Ok; } else /* error message */ { QErrorMessage * errbox = new QErrorMessage(this); errbox->showMessage(qt(msg)); errbox->exec(); } } return result; } bool qsmainwnd::on_group_learn (bool learning) { qt_set_icon(learning ? learn2_xpm : learn_xpm, ui->button_learn); return true; } bool qsmainwnd::on_group_learn_complete (const keystroke & k, bool good) { std::ostringstream os; if (good) { if (usr().enable_learn_confirmation()) { os << "MIDI mute-group learn success, mute-group key '" << k.name() << "' (code = " << int(k.key()) << " [0x" << std::hex << std::setw(2) << unsigned(k.key()) << "]) mapped." ; } } else { os << "Key '" << k.name() << "' (code = " << int(k.key()) << " [0x" << std::hex << std::setw(2) << unsigned(k.key()) << "]) is not a configured mute-group key. " << "To add it, edit the 'ctrl' file." ; } bool dirty = good && report_message(os.str(), good, false); /* no Cancel */ if (dirty) m_is_title_dirty = true; return good; } bool qsmainwnd::on_automation_change (automation::slot s) { bool result = not_nullptr(m_live_frame); if (s == automation::slot::mod_bbt_hms) { toggle_time_format(true); } else if (s == automation::slot::mod_LR_loop) { toggle_loop(); } else if (s == automation::slot::menu_mode) { bool hide = ! ui->btnShowHide->isChecked(); ui->btnShowHide->setChecked(hide); slot_show_hide(); } if (result) m_live_frame->set_needs_update(); /* brute force */ return result; } bool qsmainwnd::on_sequence_change (seq::number seqno, performer::change ctype) { bool result = not_nullptr(m_live_frame); if (result) { /* * Issue #85: segfault on recording events (not when drawing them). * * This code would result in a recreation and the following error a * few times, then a segfault. * * QObject::setParent: Cannot set parent, new parent is in a * different thread * * Using modification() to fix issue #90 causes flickering as * changes are made that cause performer to notify its clients. */ bool redo = ctype == performer::change::recreate; bool domod = cb_perf().modification(ctype); /* issue #90 */ m_live_frame->update_sequence(seqno, redo); for (auto ip : m_open_live_frames) ip.second->update_sequence(seqno, redo); update_record_by_status(); if (domod) enable_save(cb_perf().modified()); } return result; } bool qsmainwnd::on_trigger_change (seq::number seqno, performer::change /* mod */) { bool result = not_nullptr(m_live_frame); if (result) { bool save = cb_perf().modified(); /* || mod==performer::change::yes */ m_live_frame->refresh(seqno); /* calls on_trigger_change() ! */ enable_save(save); m_is_title_dirty = true; } return result; } bool qsmainwnd::on_set_change (screenset::number setno, performer::change /*ctype*/) { emit signal_set_change(int(setno)); m_is_title_dirty = true; return true; } void qsmainwnd::update_set_change (int setno) { bool ok = not_nullptr(m_live_frame); if (ok) { if (setno != m_live_frame->bank_id()) { QString bname = qt(cb_perf().set_name(setno)); m_live_frame->update_bank(setno); /* updates current bank */ ui->spinBank->setValue(setno); /* shows it in spinbox */ ui->txtBankName->setText(bname); /* show set/bank name */ } else m_live_frame->update_bank(); /* updates current bank */ bool cancopy = cb_perf().playscreen_active_count() > 0; ui->actionCopyCurrentSet->setEnabled(cancopy); if (not_nullptr(m_song_frame64)) m_song_frame64->update_sizes(); } } bool qsmainwnd::on_mutes_change (mutegroup::number group, performer::change mod) { bool result = ! mutegroup::none(group); if (result) { bool ok = mod != performer::change::max; if (ok) { std::string text = cb_perf().group_name(group); ui->txtMuteName->setText(qt(text)); } } return result; } bool qsmainwnd::on_resolution_change (int ppqn, midibpm bp, performer::change ch) { std::string pstring = std::to_string(ppqn); set_ppqn_text(pstring); /* * if (ch == performer::change::yes) * ui->spinBpm->setValue(bp); */ set_beats_per_minute(bp, ch == performer::change::no); m_is_title_dirty = true; return true; } bool qsmainwnd::on_song_action (bool signal, playlist::action act) { if (signal) { int a = playlist::action_to_int(act); emit signal_song_action(a); } else m_is_title_dirty = true; return true; } void qsmainwnd::update_song_action (int playaction) { bool result = false; playlist::action a = playlist::int_to_action(playaction); switch (a) { case playlist::action::next_list: result = cb_perf().open_next_list(); break; case playlist::action::next_song: result = cb_perf().open_next_song(); break; case playlist::action::previous_song: result = cb_perf().open_previous_song(); break; case playlist::action::previous_list: result = cb_perf().open_previous_list(); break; default: break; } if (result) { m_is_title_dirty = true; update_window_title(cb_perf().playlist_song_basename()); } } /** * This is called when focus changes in the main window. */ void qsmainwnd::changeEvent (QEvent * event) { QWidget::changeEvent(event); if (event->type() == QEvent::ActivationChange) { if (isActiveWindow()) { if (not_nullptr(m_live_frame)) { screenset::number bank = m_live_frame->bank_id(); screenset::number setno = cb_perf().playscreen_number(); if (bank != setno) (void) cb_perf().set_playing_screenset(bank); } } else { // widget is now inactive } } } void qsmainwnd::resizeEvent (QResizeEvent * /*r*/ ) { (void) usr().window_rescale(width(), height()); recreate_all_slots(); } bool qsmainwnd::recreate_all_slots () { bool result = refresh_captions(); if (result) result = m_live_frame->recreate_all_slots(); return result; } bool qsmainwnd::refresh_captions () { bool result = not_nullptr(m_live_frame); m_is_title_dirty = false; if (result) { std::string newname; if (cb_perf().playlist_active()) newname = cb_perf().playlist_song(); else newname = rc().midi_filename(); m_live_frame->set_playlist_name(newname, cb_perf().modified()); } if (cb_perf().playlist_active()) { std::string newname = cb_perf().playlist_song_basename(); update_window_title(newname); } return result; } void qsmainwnd::session_manager (const std::string & text) { if (not_nullptr(m_session_frame)) m_session_frame->session_manager(text); } void qsmainwnd::session_path (const std::string & text) { if (not_nullptr(m_session_frame)) m_session_frame->session_path(text); } void qsmainwnd::session_display_name (const std::string & text) { if (not_nullptr(m_session_frame)) m_session_frame->session_display_name(text); } void qsmainwnd::session_client_id (const std::string & text) { if (not_nullptr(m_session_frame)) m_session_frame->session_client_id(text); } void qsmainwnd::session_URL (const std::string & text) { if (not_nullptr(m_session_frame)) m_session_frame->session_URL(text); } void qsmainwnd::session_log_file (const std::string & text) { if (not_nullptr(m_session_frame)) m_session_frame->session_log_file(text); } void qsmainwnd::song_path (const std::string & text) { if (not_nullptr(m_session_frame)) m_session_frame->song_path(text); } void qsmainwnd::last_used_dir (const std::string & text) { if (not_nullptr(m_session_frame)) m_session_frame->last_used_dir(text); } } // namespace seq66 /* * qsmainwnd.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qstriggereditor.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qstriggereditor.cpp * * This module declares/defines the class for the thin event window pane * "events" just below the qseqroll. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 * \updates 2026-01-23 * \license GNU GPLv2 or above * * This class represents the central piano-roll user-interface area of the * performance/song editor. It is not the best name, since the earlier Seq64 * name was seqevent. It is the Qt version of the seqevent class. */ #include /* QApplication keyboardModifiers() */ #include "cfg/settings.hpp" /* seq66::usr().key_height(), etc. */ #include "play/performer.hpp" /* seq66::performer class */ #include "play/sequence.hpp" /* seq66::sequence class */ #include "qseqdata.hpp" /* seq66::qseqdata class */ #include "qseqeditframe64.hpp" /* seq66::qseqeditframe64 class */ #include "qstriggereditor.hpp" /* seq66::qstriggereditor class */ #include "qt5_helpers.hpp" /* seq66::qt_timer() */ namespace seq66 { class performer; /* * Tweaks */ static const int qc_eventarea_y = 16; static const int qc_eventevent_y = 10; static const int qc_eventevent_x = 5; static const int s_x_tick_fix = 2; static std::string s_edit_msg{"Cannot edit note events in the event pane."}; /** * Principal constructor. */ qstriggereditor::qstriggereditor ( performer & p, sequence & s, qseqeditframe64 * frame, int zoom, int snap, int keyheight, QWidget * parent, int xoffset /* 0 */ ) : QWidget (parent), qseqbase (p, s, frame, zoom, snap), m_timer (nullptr), m_x_offset (xoffset + s_x_tick_fix), m_key_y (keyheight), m_is_tempo (false), /* is_tempo() */ m_is_time_signature (false), /* is_time_signature() */ m_is_program_change (false), /* is_program_change() */ m_status (EVENT_NOTE_ON), m_cc (0) /* bank select */ { setAttribute(Qt::WA_StaticContents); setAttribute(Qt::WA_OpaquePaintEvent); /* no erase on repaint */ setFocusPolicy(Qt::StrongFocus); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); setToolTip(qt(s_edit_msg)); m_timer = qt_timer(this, "qstriggereditor", 2, SLOT(conditional_update())); } /** * This virtual destructor stops the timer. */ qstriggereditor::~qstriggereditor () { if (not_nullptr(m_timer)) m_timer->stop(); } /** * A convenience function. * * \return * Returns non-zero if events can be selected (we no longer allow this * for note-related events in this editor, to prevent issues) and are * selected. */ int qstriggereditor::select_events ( eventlist::select selmode, midipulse start, midipulse finish ) { int result = 0; if (! event::is_note_msg(m_status)) result = track().select_events(start, finish, m_status, m_cc, selmode); return result; } /** * In an effort to reduce CPU usage when simply idling, this function calls * update() only if necessary. See qseqbase::check_dirty(). */ void qstriggereditor::conditional_update () { if (perf().needs_update() || check_dirty()) update(); } QSize qstriggereditor::sizeHint () const { int w = frame64()->width(); int len = z().tix_to_pix(track().get_length()); if (len < w) len = w; len += c_keyboard_padding_x; return QSize(len, qc_eventarea_y + 1); } /** * We don't want the scroll wheel to accidentally scroll this pane * horizontally, so this override does nothing but accept() the event. * * ignore() just let's the parent handle the event, which allows scrolling to * occur. For issue #3, we have enabled the scroll wheel in the piano roll * [see qscrollmaster::wheelEvent()], but we disable it here. So this is a * partial solution to the issue. */ void qstriggereditor::wheelEvent (QWheelEvent * qwep) { qwep->accept(); } void qstriggereditor::paintEvent (QPaintEvent * qpep) { QRect r = qpep->rect(); QPainter painter(this); QBrush brush(backevent_paint(), Qt::SolidPattern); QPen pen(Qt::black); pen.setStyle(Qt::SolidLine); painter.setPen(pen); painter.setBrush(brush); painter.drawRect(1, 0, width(), height() - 1); /* draw the background */ draw_grid(painter, r); /* * Draw boxes from sequence. */ midipulse ticks_per_step = z().pulses_per_substep(); midipulse starttick = scroll_offset() - (scroll_offset() % ticks_per_step); midipulse endtick = z().pix_to_tix(width()); pen.setColor(fore_color()); /* Qt::black */ pen.setStyle(Qt::SolidLine); brush.setStyle(Qt::SolidPattern); track().draw_lock(); for (auto cev = track().cbegin(); ! track().cend(cev); ++cev) { if (! track().get_next_event_match(m_status, m_cc, cev)) break; midipulse tick = cev->timestamp(); bool selected = cev->is_selected(); if ((tick >= starttick && tick <= endtick)) { int x = xoffset(tick) + m_x_offset; int y = (qc_eventarea_y - qc_eventevent_y) / 2; pen.setColor(fore_color()); /* Qt::black ev border */ painter.setPen(pen); painter.drawRect(x, y, qc_eventevent_x, qc_eventevent_y); if (selected) brush.setColor(sel_color()); /* "orange" */ else if (cev->is_tempo()) brush.setColor(tempo_color()); else if (cev->is_time_signature()) brush.setColor(grey_color()); else if (cev->is_program_change()) brush.setColor(drum_color()); /* ! */ else brush.setColor(note_event_paint()); /* back_color(), white */ painter.setBrush(brush); /* draw event highlight */ painter.drawRect(x, y, qc_eventevent_x - 1, qc_eventevent_y - 1); } } track().draw_unlock(); int h = qc_eventevent_y; int y = (qc_eventarea_y - h) / 2; /* draw selection */ brush.setStyle(Qt::NoBrush); /* painter reset */ painter.setBrush(brush); if (selecting()) { int x, w; x_to_w(drop_x(), current_x(), x, w); old_rect().x(x); old_rect().width(w); pen.setColor(sel_color()); /* Qt::black */ painter.setPen(pen); painter.drawRect(x + c_keyboard_padding_x, y, w, h); } if (drop_action()) { int delta_x = current_x() - drop_x(); int x = selection().x() + delta_x; pen.setColor(Qt::black); painter.setPen(pen); painter.drawRect(x + c_keyboard_padding_x, y, selection().width(), h); old_rect().x(x); old_rect().width(selection().width()); } } void qstriggereditor::draw_grid (QPainter & painter, const QRect & r) { QBrush brush(Qt::lightGray, Qt::SolidPattern); QPen pen(Qt::black); int count = track().time_signature_count(); midipulse ticks_per_step = z().pulses_per_substep(); midipulse endtick = z().pix_to_tix(r.x() + r.width()); for (int tscount = 0; tscount < count; ++tscount) { const sequence::timesig & ts = track().get_time_signature(tscount); if (ts.sig_beat_width == 0) break; int bpbar = ts.sig_beats_per_bar; int bwidth = ts.sig_beat_width; midipulse ticks_per_beat = midipulse(z().pulses_per_beat(bwidth)); midipulse ticks_per_bar = z().pulses_per_bar(bpbar, bwidth); midipulse starttick = ts.sig_start_tick; for (midipulse tick = starttick; tick < endtick; tick += ticks_per_step) { int x_offset = xoffset(tick) - scroll_offset_x() + m_x_offset; int penwidth = 1; enum Qt::PenStyle penstyle = Qt::SolidLine; if (tick % ticks_per_bar == 0) /* solid line on every beat */ { penstyle = measure_pen_style(); penwidth = measure_pen_width(); pen.setColor(beat_color()); } else if (tick % ticks_per_beat == 0) { penstyle = beat_pen_style(); /* not measure_pen_style() */ penwidth = beat_pen_width(); /* not measure_pen_width() */ pen.setColor(beat_color()); } else /* no ticks/four as yet */ { pen.setColor(step_color()); /* faint step lines */ penstyle = Qt::DotLine; } pen.setWidth(penwidth); pen.setStyle(penstyle); painter.setPen(pen); painter.drawLine(x_offset, 1, x_offset, qc_eventarea_y); } } } void qstriggereditor::resizeEvent (QResizeEvent * qrep) { qrep->ignore(); /* QWidget::resizeEvent(qrep) */ } /** * We cannot call frame64()->set_dirty() without creating an infinite loop * and seqfault. So we make a private function to do that. */ void qstriggereditor::flag_dirty () { track().set_dirty(); frame64()->set_dirty(); } /** * If it was a button press, set values for dragging. */ void qstriggereditor::mousePressEvent (QMouseEvent * ev) { midipulse tick_s, tick_f, tick_w; convert_x(qc_eventevent_x, tick_w); current_x(qt_mouse_x(ev) - c_keyboard_padding_x); drop_x(current_x()); old_rect().clear(); /* reset box holding dirty redraw spot */ if (paste()) { snap_current_x(); convert_x(current_x(), tick_s); track().paste_selected(tick_s, 0); paste(false); setCursor(Qt::ArrowCursor); flag_dirty(); } else { bool isctrl = bool(ev->modifiers() & Qt::ControlModifier); bool lbutton = ev->button() == Qt::LeftButton; bool rbutton = ev->button() == Qt::RightButton; if (lbutton) { convert_x(drop_x(), tick_s); /* turn x,y in to tick/note */ tick_f = tick_s + zoom(); /* shift back a few ticks */ tick_s -= tick_w; if (tick_s < 0) tick_s = 0; if (adding()) /* add note length < snap */ { eventlist::select selmode = eventlist::select::would_select; painting(true); snap_drop_x(); convert_x(drop_x(), tick_s); /* turn x,y in to tick/note */ bool dropit = select_events(selmode, tick_s, tick_f) == 0; if (dropit) drop_event(tick_s); } else /* we're selecting anew */ { eventlist::select selmode = eventlist::select::selected; bool is_selected = select_events(selmode, tick_s, tick_f); if (is_selected) { if (! isctrl) { int note, x, w; moving_init(true); track().selected_box(tick_s, note, tick_f, note); tick_f += tick_w; convert_t(tick_s, x); /* convert box to X,Y values */ convert_t(tick_f, w); w -= x; selection().set ( x, w, (qc_eventarea_y - qc_eventevent_y) / 2, qc_eventevent_y ); int adjusted_selected_x = selection().x(); snap_x(adjusted_selected_x); move_snap_offset_x(selection().x() - adjusted_selected_x); } /* * Align selection for drawing. Save X as a variable so we * can use the snap function. */ int tempSelectedX = selection().x(); snap_x(tempSelectedX); selection().x(tempSelectedX); snap_current_x(); snap_drop_x(); } else { if (! isctrl) { track().unselect(); flag_dirty(); } selmode = eventlist::select::select_one; int numsel = select_events(selmode, tick_s, tick_f); if (numsel == 0) { /* * If we didn't select anything (the user clicked * empty space) unselect all notes, and start the * selection box, unless its a note message in force. */ if (! event::is_note_msg(m_status)) selecting(true); } else flag_dirty(); } } } if (rbutton) set_adding(true); } } void qstriggereditor::mouseReleaseEvent (QMouseEvent * ev) { current_x(qt_mouse_x(ev) - c_keyboard_padding_x); if (moving()) snap_current_x(); int delta_x = current_x() - drop_x(); bool lbutton = ev->button() == Qt::LeftButton; bool rbutton = ev->button() == Qt::RightButton; if (lbutton) { if (selecting()) { midipulse tick_s, tick_f; int x, w; eventlist::select selmode = eventlist::select::selecting; x_to_w(drop_x(), current_x(), x, w); convert_x(x, tick_s); convert_x(x + w, tick_f); int numsel = select_events(selmode, tick_s, tick_f); if (numsel > 0) flag_dirty(); } if (moving()) { /* * Adjust for snap, then convert deltas into screen coordinates. * Move the events, except for notes. Those must be edited in the * piano roll. */ midipulse delta_tick; delta_x -= move_snap_offset_x(); convert_x(delta_x, delta_tick); track().move_selected_events(delta_tick); } set_adding(adding()); } if (rbutton) { if (! QApplication::queryKeyboardModifiers().testFlag(Qt::MetaModifier)) { set_adding(false); flag_dirty(); } } clear_action_flags(); /* turn off */ track().unpaint_all(); if (is_dirty()) /* if clicked, something changed */ flag_dirty(); } void qstriggereditor::mouseMoveEvent (QMouseEvent * ev) { midipulse tick = 0; if (moving_init()) { moving_init(false); moving(true); } if (select_action()) // m_selecting || m_moving || m_paste { current_x(qt_mouse_x(ev) - c_keyboard_padding_x); if (drop_action()) // m_moving || m_paste snap_current_x(); } if (painting()) { current_x(qt_mouse_x(ev)); snap_current_x(); convert_x(current_x(), tick); drop_event(tick); } flag_dirty(); } void qstriggereditor::keyPressEvent (QKeyEvent * ev) { bool ret = false; int key = ev->key(); if (key == Qt::Key_Delete || key == Qt::Key_Backspace) { track().remove_selected(); ret = mark_modified(); } if (ev->modifiers() & Qt::ControlModifier) { switch (key) { case Qt::Key_X: /* cut */ track().cut_selected(); ret = mark_modified(); break; case Qt::Key_C: /* copy */ track().copy_selected(); ret = true; break; case Qt::Key_V: /* paste */ start_paste(); ret = mark_modified(); break; case Qt::Key_Z: /* Undo */ if (ev->modifiers() & Qt::ShiftModifier) track().pop_redo(); else track().pop_undo(); ret = true; break; } } if (! ret) { if (key == Qt::Key_P || key == Qt::Key_I) { set_adding(true); ret = true; } else if (key == Qt::Key_X) { set_adding(false); ret = true; } else if (movement_key_press(key)) { ret = mark_modified(); } else { if (! perf().is_pattern_playing()) { if (key == Qt::Key_Escape) { set_adding(false); ret = true; } } } } if (ret) flag_dirty(); else QWidget::keyPressEvent(ev); } void qstriggereditor::keyReleaseEvent (QKeyEvent *) { // no code } bool qstriggereditor::movement_key_press (int key) { bool result = false; if (track().any_selected_events(m_status, m_cc)) { if (key == Qt::Key_Left) { move_selected_events(-1); result = mark_modified(); } else if (key == Qt::Key_Right) { move_selected_events(1); result = mark_modified(); } } return result; } void qstriggereditor::move_selected_events (midipulse dt) { if (track().any_selected_events(m_status, m_cc)) { midipulse snap_t = dt * snap(); /* time-stamp snap */ track().move_selected_events(snap_t); } } /** * This function checks the minimums and maximums, then fills in the x and w * values. */ void qstriggereditor::x_to_w (int x1, int x2, int & x, int & w) { if (x1 < x2) { x = x1; w = x2 - x1; } else { x = x2; w = x1 - x2; } } void qstriggereditor::start_paste () { snap_current_x(); snap_current_y(); drop_x(current_x()); drop_y(current_y()); paste(true); /* * Get the box that selected elements are in. */ midipulse tick_s, tick_f; int note_h, note_l, x, w; track().clipboard_box(tick_s, note_h, tick_f, note_l); convert_t(tick_s, x); /* convert box to X,Y values */ convert_t(tick_f, w); /* * w is actually coordinates now, so we have to change. * Set the m_selected rectangle to hold the x,y,w,h of our selected events. * Adjust, clipboard shift to tick 0. */ w -= x; selection().set ( x, w, (qc_eventarea_y - qc_eventevent_y) / 2, qc_eventevent_y ); selection().x(selection().x() + drop_x()); } void qstriggereditor::convert_x (int x, midipulse & tick) { tick = z().pix_to_tix(x); } void qstriggereditor::convert_t (midipulse ticks, int & x) { x = z().tix_to_pix(ticks); } void qstriggereditor::drop_event (midipulse tick) { track().push_undo(); if (is_tempo()) { midibpm bpm = perf().bpm(); (void) track().add_tempo(tick, bpm, true); /* repaint true */ } else if (is_time_signature()) { /* ignore */ } else { midibyte d0 = m_cc; midibyte d1 = 0x40; if (m_status == EVENT_AFTERTOUCH) d0 = 0; else if (m_status == EVENT_PROGRAM_CHANGE) d0 = 0x40; /* d0 == new patch */ else if (m_status == EVENT_CHANNEL_PRESSURE) d0 = 0x40; /* d0 == pressure */ else if (m_status == EVENT_PITCH_WHEEL) d0 = 0; if (track().add_event(tick, m_status, d0, d1, true)) /* sorts it */ (void) mark_modified(); } } /** * Does not allow adding for note events: on, off, and aftertouch. * These are too tricky due to the need for linking. */ void qstriggereditor::set_adding (bool a) { if (! event::is_note_msg(m_status)) { adding(a); if (a) setCursor(Qt::PointingHandCursor); else setCursor(Qt::ArrowCursor); } } void qstriggereditor::set_data_type (midibyte status, midibyte control) { if (event::is_tempo_status(status)) { is_tempo(true); is_time_signature(false); is_program_change(false); m_status = EVENT_MIDI_META; /* tricky */ m_cc = status; } else if (event::is_time_signature_status(status)) { is_tempo(false); is_time_signature(true); is_program_change(false); m_status = EVENT_MIDI_META; /* tricky */ m_cc = status; } else if (event::is_program_change_msg(status)) { is_tempo(false); is_time_signature(false); is_program_change(true); m_status = status; m_cc = 0; } else { is_tempo(false); is_time_signature(false); m_status = event::normalized_status(status); m_cc = control; if (event::is_note_msg(status)) setToolTip(qt(s_edit_msg)); else setToolTip(""); } update(); } } // namespace seq66 /* * qstriggereditor.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qt5_helpers.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qt5_helpers.cpp * * This module declares/defines some helpful macros or functions. * * \library seq66 application * \author Chris Ahlstrom * \date 2018-03-14 * \updates 2026-03-13 * \license GNU GPLv2 or above * * The items provided externally are: * * - qt_keystroke(). Returns an "ordinal" for Qt keystroke. * - qt_set_color(). Sets the color of a push-button. * - qt_set_icon(). Sets an icon for a push-button. * - qt_icon_theme(). Returns the name of the icon theme. * - qt_prompt_ok(). Does an OK/Cancel QMessageBox. * - qt(). Converts an std::sring to a QString. * - qt_set_layout_visibility(). Hide/show a layout and its children. * - qt_timer(). Encapsulates creating and starting a timer, with a * callback given by a Qt slot-name. * - enable_combobox_item(). Handles the appearance of a combo box. * - fill_combobox(). Fills a combo box from a combolist. * - new_qaction(). Creates a menu action from text and an icon. Also * could centralize internationalization. * - new_qmenu(). Similar. * - show_open_midi_file_dialog() * - show_import_midi_file_dialog() * - show_select_project_dialog() * - show_playlist_dialog() * - show_text_file_dialog() * - show_file_dialog() * - show_folder_dialog() * - show_file_select_dialog() */ #include #include #include #include #include /* prompt for full MIDI file's path */ #include #include /* prompt for a simple string */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cfg/settings.hpp" /* seq66::rc().home_config_dir...() */ #include "util/filefunctions.hpp" /* seq66 file-name manipulations */ #include "util/strfunctions.hpp" /* seq66::toupper() and tolower */ #include "qt5_helpers.hpp" /* these cool helper functions! */ /** * We need a better option than getExistingDirectory(). We need to * see hidden directories when exporting projects. */ #define SEQ66_SHOW_HIDDEN_DIRECTORIES /* * Don't document the namespace. */ namespace seq66 { /** * This code is used in qslivekeys in order to get the list(s) of extended * characters in the keymap module. */ static keystroke qt_keystroke_test (QKeyEvent * event, keystroke::action act) { unsigned kmods = static_cast(event->modifiers()); eventkey k = event->key(); ctrlkey ordinal = qt_modkey_ordinal(k, kmods); bool press = act == keystroke::action::press; keystroke t = keystroke(ordinal, press); std::string ktext = event->text().toStdString(); std::string kname = t.name(); std::string modifiers = modifier_names(kmods); unsigned scode = unsigned(event->nativeScanCode()); /* scan code */ unsigned kcode = unsigned(event->nativeVirtualKey()); /* key sym */ if (ktext.empty()) ktext = kname; char tmp[128]; snprintf ( tmp, sizeof tmp, "Event key #0x%02x mod %s '%s' %s: scan 0x%x key 0x%x ord 0x%x", k, modifiers.c_str(), ktext.c_str(), press ? "press" : "release", scode, kcode, ordinal ); (void) info_message(tmp); return keystroke(0, press); /* disable the key action */ } /** * Given a keystroke from a Qt 5 GUI, this function returns an "ordinal" * version of the keystroke. Note that there are many keystrokes that can * have the same event key value. For example: Ctrl-a, Shift-a, and a. In * cases like that, we have to check the modifiers. * * But the QKeyEvent::modifiers() function cannot always be trusted. The user * can confuse it by pressing both Shift keys simultaneously and releasing * one of them, for example. * * We can also check the nativeVirtualKey() result for the event. Even that * can be fooled by a change in the keyboard encoding. Yeesh! And on * Windows, the native value is different from that of Linux for the arrow * keys. * * The qt_modkey_ordinal() function in the keymap module can use all these * codes to try to figure out the proper ordinal to return. * * \param event * The putative Qt 5 keystroke event. * * \param act * Indicates if the keystroke action was a press or a release. * * \param testing * If true (the default is false), then the lookup results are shown and * the true keystroke is returned to be acted on. We have a feeling * we'll be looking at a more international key-maps in the future. * To avoid problematic actions getting in the way of the test, call * qt_keystroke_test() directly, as done in the qslivegrid class when * SEQ66_KEY_TESTING is defined. * * \return * Returns an object that makes the key event easier to use. It needs to * hold only the ordinal and whether the key was pressed or released. */ keystroke qt_keystroke (QKeyEvent * event, keystroke::action act, bool testing) { if (testing) (void) qt_keystroke_test(event, act); /* show key details */ unsigned kmods = static_cast(event->modifiers()); eventkey k = event->key(); #if defined SEQ66_PLATFORM_WINDOWS ctrlkey ordinal = qt_modkey_ordinal(k, kmods, 0); #else eventkey v = event->nativeVirtualKey(); ctrlkey ordinal = qt_modkey_ordinal(k, kmods, v); #endif bool press = act == keystroke::action::press; return keystroke(ordinal, press, kmods); } /** * Get the color of a push-button as a string. */ std::string qt_get_color (QPushButton * button) { std::string result; if (not_nullptr(button)) { QColor buttcolor = button->palette().color(QPalette::Button); char temp[16]; int r, g, b, a; buttcolor.getRgb(&r, &g, &b, &a); snprintf(temp, sizeof temp, "#%02X%02X%02X%02X", a, r, g, b); result = temp; } return result; } /** * Sets the color of a push-button. Code (now disabled) from qslivegrid. * * \param colorname * The name of the color, preferably in the form "#AARRGGBB"; "AA" is * quite often "FF". * * \param button * Pointer to the button to color. * * \return * Returns a string of the form "#AARRGGBB" denoting the original color * of the button. */ std::string qt_set_color (const std::string & colorname, QPushButton * button) { std::string result; if (not_nullptr(button) && ! colorname.empty()) { result = qt_get_color(button); QString qcolorname(qt(colorname)); QPalette pal = button->palette(); QColor c(qcolorname); pal.setColor(QPalette::Button, c); button->setAutoFillBackground(true); /* not needed: setFlat(true) */ button->setPalette(pal); button->update(); } return result; } /** * Clears the text of the QPushButton, and sets its icon to the pixmap given * by the pixmap character array. * * \param pixmap_array * Provides the character array representing the XPM pixmap. * * \param button * Provides a pointer to the button to be cleared and to have its icon * set. */ void qt_set_icon (const char * pixmap_array [], QPushButton * button) { if (not_nullptr_2(pixmap_array, button)) { QPixmap pixmap(pixmap_array); QIcon icon; icon.addPixmap(pixmap, QIcon::Normal, QIcon::On); button->setText(""); button->setIcon(icon); } } /** * Gets the icon theme name. */ std::string qt_icon_theme () { QString qs = QIcon::themeName(); return qs.toStdString(); } /** * Encapsulates a common conversion from C++ string to QString. */ QString qt (const std::string & text) { return QString::fromStdString(text); } /** * In Qt6, x() and y() are deprecated for some reason. So we * essentially duplicate the inline code here. */ #if defined QT_VERSION_5 int qt_mouse_x (QMouseEvent * ev) { return ev->x(); } int qt_mouse_y (QMouseEvent * ev) { return ev->y(); } #elif defined QT_VERSION_6 int qt_mouse_x (QMouseEvent * ev) { return qRound(ev->position().x()); } int qt_mouse_y (QMouseEvent * ev) { return qRound(ev->position().y()); } #endif /** * Semi-recursive function to alter the visibility of all sub-items * in a layout item. * * Why not use a reference? */ void qt_set_layout_visibility (QLayoutItem * item, bool visible) { QWidget * widget = item->widget(); if (not_nullptr(widget)) return widget->setVisible(visible); QLayout * layout = item->layout(); if (not_nullptr(layout)) { for (int i = 0; i < layout->count(); ++i) qt_set_layout_visibility(layout->itemAt(i), visible); } } /** * Provide an OK/Cancel prompt and return the result, true if Ok. */ bool qt_prompt_ok ( const std::string & text, const std::string & info ) { QMessageBox temp; temp.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); temp.setText(qt(text)); temp.setInformativeText(qt(info)); int choice = temp.exec(); bool result = choice == QMessageBox::Ok; return result; } /** * Provides an informative message box, no decision necessary. * * Do we need to delete the message box here, or risk them accumulating?` * * \param self * Usually provides the "this" pointer of the calling object. * * \param msg * Provides the text to be shown. */ void qt_info_box (QWidget * self, const std::string & msg) { QMessageBox * mbox = new QMessageBox(self); if (not_nullptr(mbox)) { mbox->setText(qt(msg)); mbox->setStandardButtons(QMessageBox::Ok); (void) mbox->exec(); delete mbox; } } /** * Provides an informative error message box, no decision necessary. * Qt::WA_DeleteOnClose? * * \param self * Usually provides the "this" pointer of the calling object. * * \param msg * Provides the text to be shown. */ void qt_error_box (QWidget * self, const std::string & msg) { QErrorMessage * mbox = new QErrorMessage(self); if (not_nullptr(mbox)) { mbox->showMessage(qt(msg)); (void) mbox->exec(); delete mbox; } } /** * Gets a string from the user. A very simple dialog. * * \param self * Usually provides the "this" pointer of the calling object. * * \param title * Provides the title of the input dialog. * * \param label * Provides a label for the edit line. * * \param text * Provides the default text to be shown in the edit line. * The default value is an empty string. * * \return * Returns the entered string, or an empty string if Cancel is * selected. */ std::string qt_get_string ( QWidget * self, const std::string & title, const std::string & label, const std::string & text ) { QString qtext = QInputDialog::getText /* static function */ ( self, qt(title), qt(label), QLineEdit::Normal, qt(text) ); return qtext.toStdString(); } /** * Creates a QTimer in a consistent manner. */ QTimer * qt_timer ( QObject * self, const std::string & name, int redraw_factor, const char * slotname ) { QTimer * result = new QTimer(self); if (not_nullptr(result)) { int interval = redraw_factor * usr().window_redraw_rate(); #if defined SHOW_TIMER_CREATION /* this has been well vetted, quiet */ if (rc().investigate()) { std::string msg = "Timer '"; msg += name; msg += "' created at rate "; msg += std::to_string(interval); msg += " for slot "; msg += slotname; (void) debug_message(msg); } #endif result->setInterval(interval); QMetaObject::Connection c = QObject::connect(result, SIGNAL(timeout()), self, slotname); if (bool(c)) result->start(); else error_message("Connection invalid"); } else { std::string msg = "Could not create timer '"; msg += name; msg += "'"; error_message(msg); } return result; } /** * Helper for handling enabled/disabled items in a combo-box */ void enable_combobox_item (QComboBox * box, int index, bool enabled) { QStandardItemModel * m = qobject_cast(box->model()); QStandardItem * item = m->item(index); if (not_nullptr(item)) { if (enabled) item->setFlags(item->flags() | Qt::ItemIsEnabled); else item->setFlags(item->flags() & ~Qt::ItemIsEnabled); } else { std::string data = "index "; data += std::to_string(index); error_message("null combobox item", data); } } void set_combobox_item ( QComboBox * box, int index, const std::string & text ) { QStandardItemModel * m = qobject_cast(box->model()); QStandardItem * item = m->item(index); if (not_nullptr(item)) { item->setText(qt(text)); } } /** * Populates the MIDI Channel combo-box with the number and names of each * channel. This action is needed at startup of the seqedit window, and when * the user changes the active buss for the sequence. * * When the output buss or channel are changed, we get the 16 "channels" from * the new buss's definition, get the corresponding instrument, and load its * name into this midich popup. Then we need to go to the instrument/channel * that has been selected, and repopulate the event menu with that item's * controller values/names. * * \param combo * Provides the QComboBox to be filled. * * \param buss * The new value for the buss from which to get the [user-instrument-N] * settings in the [user-instrument-definitions] section. */ bool populate_midich_combo (QComboBox * combo, int buss, int ch) { bool result { false }; tokenization chlist; for (int channel = 0; channel <= c_midichannel_max; ++channel) { char b[4]; /* 2 digits or less */ snprintf(b, sizeof b, "%2d", channel + 1); std::string name = std::string(b); if (channel == c_midichannel_max) /* i.e. 16 */ { name = "Free"; } else { std::string s = usr().instrument_name(buss, channel); if (! s.empty()) { name += " ["; name += s; name += "]"; } } chlist.push_back(name); result = true; } if (result) { combolist cl { chlist }; result = fill_combobox(combo, cl); if (result) { if (is_null_channel(ch)) ch = c_midichannel_max; combo->setCurrentIndex(ch); } } return result; } /** * Helper to fill a combo-box. Also sets the current index, and can bracket * each line-item with optional prefix and suffix text. * * \param box * Provides the QComboBox to be filled. * * \param clist * The list of combo-box string entries. * * \param value * A string value to tack onto each entry. The default is an * empty string. * * \param prefix * A string to prepend to each item in the list. The default is an * empty string. * * \param suffix * A string to append to each item in the list. The default is an * empty string. * * \return * Returns true if the list could be filled. */ bool fill_combobox ( QComboBox * box, const combolist & clist, const std::string & value, const std::string & prefix, const std::string & suffix ) { bool result = false; int count = clist.count(); if (count > 0) { box->clear(); for (int i = 0; i < count; ++i) { std::string item = clist.at(i); if (! item.empty()) { bool nopadding = clist.use_current() && i == 0; bool addpadding = (item != "-") && ! nopadding; if (addpadding) { if (! prefix.empty()) /* for example, "1/" for widths */ item = prefix + item; if (! suffix.empty()) item = item + suffix; } result = true; if (item == "-") { box->insertSeparator(8); } else { QString text = qt(item); box->insertItem(i, text); } } } if (result) { if (! value.empty()) { std::string fullvalue = prefix; fullvalue += value; fullvalue += suffix; clist.current(fullvalue); box->setCurrentText(qt(fullvalue)); } else box->setCurrentIndex(0); } } return result; } /** * Sets the value in an integer spin-box without causing it to trigger * an event. */ void set_spin_value (QSpinBox * spin, int value) { spin->blockSignals(true); spin->setValue(value); spin->blockSignals(false); } /** * Handles versioning issue in creating a menu action. Earlier versions * required a QMenu pointer even if null. * * We could add tr() to the text parameter. */ QAction * new_qaction (const std::string & text, const QIcon & micon) { QString mlabel(qt(text)); #if defined QT_VERSION_L5 QAction * result = new (std::nothrow) QAction(micon, mlabel, nullptr); #else QAction * result = new (std::nothrow) QAction(micon, mlabel); #endif return result; } /** * Prevents cluttering the code with "new (std::nothrow)"; we generally * prefer to check pointers (or let the app crash) in the Qt code. * * We could add tr() to the text parameter. */ QAction * new_qaction (const std::string & text, QObject * parent) { if (text.empty()) { return new (std::nothrow) QAction(parent); } else { QString mlabel(qt(text)); QAction * result = new (std::nothrow) QAction(mlabel, parent); return result; } } QMenu * new_qmenu (const std::string & text, QWidget * parent) { if (text.empty()) { return new (std::nothrow) QMenu(parent); } else { QString mlabel(qt(text)); QMenu * result = new (std::nothrow) QMenu(mlabel, parent); return result; } } #if defined SEQ66_INSTALL_SCROLL_FILTER /** * To be called by the monitoring object. * * QWidget * monitor = parentWidget(); // ui->dataScrollArea * m_scrollArea->viewport()->installEventFilter(monitor); * m_scrollArea->horizontalScrollBar()->installEventFilter(monitor); * if event->type() == QEvent::ShortcutOverride && * obj == myparent->verticalScrollBar() * * QKeyEvent keyEvent = static_cast(event); * if(obj == keyEvent->key() == Qt::Key_Up) * { * m_scrollArea->verticalScrollBar()->setEnabled(false); * std::cout<<" Up "<viewport()->installEventFilter(monitor); target->horizontalScrollBar()->installEventFilter(monitor); target->verticalScrollBar()->installEventFilter(monitor); } return result; } #endif /** * For internal usage. */ static const char * midi_wrk_wildcards () { static const char * const s_wildcards = "MIDI/WRK (*.midi *.mid *.MID *.wrk *.WRK);;" "MIDI (*.midi *.mid *.MID);;WRK (*.wrk *.WRK);;All files (*)" ; return s_wildcards; } /** * Shows the "Open" file dialog. * * \param [inout] selectedfile * A return value for the chosen file and path. If not-empty when the * call is made, show the user that directory instead of the last-used * directory. * * \return * Returns true if the returned path can be used. */ bool show_open_midi_file_dialog (QWidget * parent, std::string & selectedfile) { return show_file_dialog ( parent, selectedfile, "Open MIDI/WRK file", midi_wrk_wildcards(), OpeningFile, NormalFile ); } bool show_import_midi_file_dialog (QWidget * parent, std::string & selectedfile) { std::string caption = "Import MIDI File into Current Set"; return show_file_dialog ( parent, selectedfile, caption, midi_wrk_wildcards(), OpeningFile, NormalFile ); } /** * This function allows one to select an 'rc' file. */ bool show_select_project_dialog ( QWidget * parent, std::string & selecteddir, std::string & selectedfile ) { std::string filter = "Config (*.rc);;All files (*)"; std::string caption = "Select Project Configuration"; std::string selection; bool result = show_file_dialog ( parent, selection, caption, filter, OpeningFile, ConfigFile ); if (result) { std::string dir; std::string file; result = filename_split(selection, dir, file); if (result) { selecteddir = dir; selectedfile = file; } } if (! result) { selecteddir.clear(); selectedfile.clear(); } return result; } /** * Shows the "Open" play-list dialog. * * \param parent * Provides the parent widget of this dialog. * * \param [inout] selectedfile * A return value for the chosen file and path. If not-empty when the * call is made, show the user that directory instead of the home * configuration directory. Callers should just provide the base-name of * the file when saving a file under session management! For opening a * file, not a big deal. * * \param saving * Indicates saving versus opening the file. The two values are: * * - SavingFile = true * - OpeningFile = false * * \return * Returns true if the returned path can be used. */ bool show_playlist_dialog (QWidget * parent, std::string & selectedfile, bool saving) { std::string filter = "Playlist (*.playlist);;All files (*)"; std::string caption = saving ? "Save play-lists file" : "Open play-lists file"; return show_file_dialog ( parent, selectedfile, caption, filter, saving, ConfigFile, ".playlist" ); } bool show_text_file_dialog (QWidget * parent, std::string & selectedfile) { return show_file_dialog ( parent, selectedfile, "Save text file", "Text (*.txt *.text);;All (*)", SavingFile, NormalFile, ".text" ); } bool show_exe_file_dialog (QWidget * parent, std::string & selectedfile) { #if defined SEQ66_PLATFORM_WINDOWS std::string ext = ".exe"; std::string filter = "Executables (*.exe);;All files (*)"; #else std::string ext = ""; std::string filter = "Executables (*);;All files (*)"; #endif return show_file_dialog ( parent, selectedfile, "Open executable file", filter, OpeningFile, NormalFile, ext ); } /** * Meant to handle many more situations. * * QString QFileDialog::getSaveFileName * ( * QWidget * parent = nullptr, * const QString & caption = QString(), * const QString & dir = QString(), * const QString & filter = QString(), * QString * selectedFilter = nullptr, * QFileDialog::Options options = Options() * ) * * Useful QFileDialog::Options: * * QFileDialog::ShowDirsOnly * QFileDialog::DontConfirmOverwrite * * \param parent * The owner of this dialog. * * \param [inout] selectedfile * The initial file option and the final selected file option. * * \param prompt * The string to show in the caption. * * \param filterlist * The types of files (file extensions) that can be selected. * * \param saving * indicates if the dialog is meant for saving a file. The constants * that apply are SavingFile (true) and OpeningFile (false). * If the former, we call QFileDialog::getSaveFileName(). * If the latter, we call QFileDialog::getOpenFileName(). * * \param forceconfig * Indicates if the dialog is meant for a configuration file. The * constants that apply are ConfigFile (true) and NormalFile (false). * forceconfig selects either rc().home_config_directory() or selectedfile. * * \param extension * The main extension that applies. * * \param promptoverwrite * If true, a prompt for an overwrite operation is shown. */ bool show_file_dialog ( QWidget * parent, std::string & selectedfile, const std::string & prompt, const std::string & filterlist, bool saving, bool forceconfig, const std::string & extension, bool promptoverwrite ) { bool result = false; std::string d = forceconfig ? rc().home_config_directory() : selectedfile ; if (selectedfile.empty()) { /* nothing to do (yet) */ } else { if (name_has_path(selectedfile) && forceconfig) { if (file_is_directory(selectedfile)) { /* Keep the home configuration directory */ } else { /* * Pull out the file-name and prepend the home configuration * directory. */ std::string fullpath = selectedfile; std::string path; std::string basename; (void) filename_split(fullpath, path, basename); d = filename_concatenate(d, basename); } } } std::string p = prompt; if (p.empty()) p = saving ? "Save file" : "Open file" ; std::string f = filterlist; if (f.empty()) f = "All files (*)"; QString file; QString folder = qt(d); QString caption = qt(p); QString filters = qt(f); QString * selfilter = nullptr; QFileDialog::Options options; /* what's the default? Options(); */ if (saving) { if (! promptoverwrite) options = QFileDialog::DontConfirmOverwrite; file = QFileDialog::getSaveFileName ( parent, caption, folder, filters, selfilter, options ); } else { file = QFileDialog::getOpenFileName ( parent, caption, folder, filters, selfilter, options ); } result = ! file.isEmpty(); if (result) { selectedfile = file.toStdString(); std::string ext = file_extension(selectedfile); if (saving && ext.empty() && ! extension.empty()) selectedfile = file_extension_set(selectedfile, extension); file_message(saving ? "Save" : "Open", selectedfile); } return result; } /** * To be used simply for getting file-names. It returns both the full path * to the file, including the base-name, plus the base-name of the file. * * Compare to show_select_project_dialog(). * * \param parent * The owner of this dialog. * * \param extension * The file-extension of interest. If empty, all files are shown. * * \param [inout] selecteddir * The initial path and the final selected path. This is the starting * point for the file dialog. * * \param [inout] selectedfile * The base name, nicely laid on a platter for the caller. * * \return * Returns true if the in-out parameters can be used. */ bool show_file_select_dialog ( QWidget * parent, const std::string & extension, std::string & selecteddir, std::string & selectedfile ) { std::string selection; std::string caption = "Select a File"; std::string ext; std::string filter; if (extension.empty()) { filter = "All files (*)"; } else { filter = capitalize(extension); filter += " (*."; filter += tolower(extension); filter += ")"; filter += ";;All files (*)"; } bool result = show_file_dialog ( parent, selection, caption, filter, OpeningFile, ConfigFile ); if (result) { std::string dir; std::string file; result = filename_split(selection, dir, file); if (result) { selecteddir = dir; selectedfile = file; } } /* * Hmmmm, do we really want to clear these? */ if (! result) { selecteddir.clear(); selectedfile.clear(); } return result; } /** * This dialog is meant to allow the user to select a directory instead * of typing it. */ bool show_folder_dialog ( QWidget * parent, std::string & selectedfolder, const std::string & prompt, bool forcehome ) { bool result = false; std::string d = forcehome ? rc().home_config_directory() : selectedfolder ; if (d.empty()) { /* nothing extra to do (yet) */ } else { bool badpath = ! name_has_path(d); if (! badpath) badpath = ! file_is_directory(d); if (badpath) d.clear(); } QString qprompt = qt(prompt); QString folder = qt(d); #if defined SEQ66_SHOW_HIDDEN_DIRECTORIES QFileDialog dialog(parent, qprompt, folder); dialog.setFileMode(QFileDialog::Directory); dialog.setViewMode(QFileDialog::Detail); dialog.setFilter(QDir::AllDirs | QDir::Hidden | QDir::NoDotAndDotDot); folder.clear(); if (dialog.exec()) { QStringList fnames = dialog.selectedFiles(); if (! fnames.isEmpty()) folder = fnames.first(); } #else folder = QFileDialog::getExistingDirectory ( parent, qprompt, folder, QFileDialog::ShowDirsOnly ); #endif result = ! folder.isEmpty(); if (result) { selectedfolder = folder.toStdString(); file_message("Choosing", selectedfolder); } return result; } /** * For issue #114, we want to append the keystroke as configured to the * tool-tip. * * \param widget * The QWidget whose tool-tip we want to modify. * * \param keyname * The name of the key as provided by the caller. (We could change * this parameter to an automation action that can be looked up here * instead.) * * \param duration * Provides the tool-tip duration in milliseconds. If -1 (the default), * then duration depends on the length of the tool-tip (and this is still * a bit too short. */ void tooltip_with_keystroke ( QWidget * widget, const std::string & keyname, int duration ) { if (! keyname.empty()) { QString qtip = widget->toolTip(); std::string tip = qtip.toStdString(); if (tip.empty()) { tip = "Key " + keyname; } else { tip += " [ "; tip += keyname; tip += " ]"; } qtip = qt(tip); widget->setToolTip(qtip); widget->setToolTipDuration(duration); } } void tooltip_for_filename ( QLineEdit * lineedit, const std::string & filespec, int duration ) { if (filespec.empty()) { lineedit->setToolTip("No file"); } else { std::string base = filename_base(filespec); QString filename = qt(filespec); QString basename = qt(base); lineedit->setToolTip(filename); lineedit->setToolTipDuration(duration); lineedit->setText(basename); } } /** * A generic tooltip, meant for use in canvas such as a piano roll. */ void generic_tooltip ( QWidget * widget, const std::string & tiptext, int x, int y, int msduration ) { QPoint p{x, y}; QString qtip{qt(tiptext)}; QRect qr{}; QToolTip::showText(p, qtip, widget, qr, msduration); } /** * Makes it easier to check a QLineEdit for empty text [QString::isEmpty() as * opposed to QString::isNull()]. */ bool is_empty (const QLineEdit * lineedit) { const QString qs = lineedit->text(); std::string text = qs.toStdString(); return text.empty(); } } // namespace seq66 /* * qt5_helpers.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_qt5/src/qt5nsmanager.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file qt5nsmanager.cpp * * This module declares/defines the main module for the Non Session Manager * control of qseq66. * * \library qt5nsmanager application * \author Chris Ahlstrom * \date 2020-03-15 * \updates 2026-04-16 * \license GNU GPLv2 or above * * Duty now for the future! Join the Smart Patrol! */ #include /* QApplication etc. */ #include /* QTimer */ #include #include "cfg/settings.hpp" /* seq66::usr() and seq66::rc() */ #include "util/strfunctions.hpp" /* seq66::string_replace() */ #include "gui_palette_qt5.hpp" /* seq66::gui_palette_qt5 */ #include "os/daemonize.hpp" /* seq66::session_restart() check */ #include "palettefile.hpp" /* seq66::palette_file config file */ #include "qt5nsmanager.hpp" /* seq66::qt5nsmanager */ #include "qsmainwnd.hpp" /* seq66::qsmainwnd (main window) */ #include "qt5_helpers.hpp" /* seq66::qt() string conversion */ #if defined SEQ66_NSM_SUPPORT #include "nsm/nsmmessagesex.hpp" /* seq66::nsm access functions */ #endif namespace seq66 { /* *------------------------------------------------------------------------- * qt5nsmanager *------------------------------------------------------------------------- */ /** * Note that this object is created before there is any chance to get the * configuration, because the smanager base class is what gets the * configuration, well after this constructor. */ qt5nsmanager::qt5nsmanager ( QApplication & app, QObject * parent, const std::string & caps ) : QObject (parent), clinsmanager (caps), m_application (app), m_timer (nullptr), m_window (), m_was_hidden (false) { #if defined QT_VERSION_STR set_qt_version(std::string(QT_VERSION_STR)); #endif } qt5nsmanager::~qt5nsmanager () { if (not_nullptr(m_timer)) m_timer->stop(); if (! is_help()) (void) session_message("Exiting qt5nsmanager"); } /** * Here we will poll to see if the "dirty" status has changed, and tell the * nsmclient to report it. * * For visibility, we have the situation where either NSM or a MIDI control * can toggle the desired visibility of the main window. How can we tell * which one requested the change? The performer will set a "show-hide * pending" variable if it made the request. */ void qt5nsmanager::conditional_update () { if (not_nullptr(perf())) { if (perf()->modified() != last_dirty_status()) set_last_dirty(); if (perf()->show_hide_pending()) handle_show_hide(perf()->hidden()); else client_show_hide(); if (session_restart()) quit(); } } /** * Push the qsmainwnd window onto the stack. Also be sure to pass along the * PPQN value, which might be different than the default (192), and affects * some of the child objects of qsmainwnd. * * Also note the support for NSM; it changes the menus available in the main * Seq66 window. Note that we cannot use the nsm_active() call, because all * that means is that the user wants NSM handling to be active. But it isn't * active until "in session", which means that the user wants NSM *and* NSM * is running. * * This function assumes that create_performer() has already been called. * And this function is called before create_session(). */ bool qt5nsmanager::create_window () { bool result = not_nullptr(perf()); if (result) { performer * p = perf(); std::string mfname = midi_filename(); bool usensm = usr().in_nsm_session(); /* not nsm_active()! */ if (rc().palette_active()) { std::string palfile = rc().palette_filespec(); if (! palfile.empty()) (void) open_palette(global_palette(), palfile); } qsmainwnd * qm = new (std::nothrow) qsmainwnd(*p, mfname, usensm, this); result = not_nullptr(qm); if (result) { m_window.reset(qm); #if defined SEQ66_NSM_SUPPORT if (not_nullptr(nsm_client())) { bool visible = usr().session_visibility(); bool hidden = ! visible; std::string status = "NSM startup "; status += visible ? "visible" : "hidden" ; session_message(status); m_was_hidden = ! hidden; /* force GUI show/hide */ handle_show_hide(hidden); } else #endif show_gui(); /* m_window->show() */ (void) smanager::create_window(); /* just house-keeping */ m_timer = qt_timer ( this, "qt5nsmanager", 5, SLOT(conditional_update()) ); /* * Let the application stay active; let the user decide what to * do about the error here. For example, if a playlist doesn't * load, why just exit? Should delay this message because other * messages are shown later in the process. For that matter, * we should allow otherwise normal operations so that the user * can recover. * * if (error_active()) show_error("", error_message()); else */ std::string path; /* config or session path */ std::string name; /* config or session name */ std::string clid; /* config/session client ID */ if (usensm) { path = manager_path(); /* session manager path */ name = display_name(); /* session display name */ clid = client_id(); /* session client ID */ } else { bool have_uuid = ! rc().jack_session().empty(); path = rc().home_config_directory(); name = rc().config_filename(); clid = rc().app_client_name(); if (rc().jack_session_active()) { session_manager_name("JACK"); if (have_uuid) { clid += ":"; clid += rc().jack_session(); /* UUID alone */ } } if (rc().alt_session()) { std::string tag = "["; tag += rc().session_tag(); tag += "]"; session_manager_name(tag); } if (have_uuid) /* wrong ??? */ { clid += "/"; clid += rc().jack_session(); /* UUID alone */ } } m_window->session_manager(manager_name()); m_window->session_path(path); m_window->session_display_name(name); m_window->session_client_id(clid); m_window->song_path(rc().midi_filename()); m_window->last_used_dir(rc().last_used_dir()); if (rc().investigate_disabled()) { file_message("Session manager", manager_name()); file_message("Session path", path); file_message("Session name", name); file_message("Session ID", clid); file_message("Session song path", rc().midi_filename()); } set_session_url(); } } if (result) { std::string themename = qt_icon_theme(); if (! themename.empty()) file_message("Icon theme", themename); if (rc().style_sheet_active()) { std::string ssname = rc().style_sheet_filespec(); if (! ssname.empty()) { QFile file(qt(ssname)); bool ok = file.open(QFile::ReadOnly); if (ok) { QString ss = QLatin1String(file.readAll()); qApp->setStyleSheet(ss); } } } } return result; } /** * Will do more with this later. Currently we just call the base class. * Note that the auto-palette-save() value will always be false. * Saving the palette should be a manual option, as it is never changed * during run-time. * * bool savepalette = rc().palette_active() || rc().auto_palette_save(); */ bool qt5nsmanager::close_session (std::string & msg, bool ok) { bool saved = true; bool savepalette = rc().auto_palette_save(); if (savepalette) { std::string palfile = rc().palette_filespec(); saved = save_palette(global_palette(), palfile); } /* * if (m_window) * m_window->remove_all_editors(); */ bool closed = clinsmanager::close_session(msg, ok); return saved && closed; } /** * Compare this function to clinsmanager::run(). We need to call * session_setup(), otherwise Ctrl-C terminates the application peremptorily, * potentially leaving the MIDI display device lit up instead of shut down. */ bool qt5nsmanager::run () { bool restart = perf()->port_map_error(); // || perf()->new_ports_available(); if (session_setup(restart)) /* need an early exit? */ { int exit_status = m_application.exec(); /* run main window loop */ /* * status_message("Exit flagged in session"); */ return exit_status == EXIT_SUCCESS; } else return true; /* see main() */ } /* * Using the window's quit provides a prompt to save, and also avoids a * segfault when exiting with changes pending. * * m_application.quit(); */ void qt5nsmanager::quit () { if (nsm_active()) { static int s_count = 0; if (s_count == 0) { ++s_count; session_message("Looping...."); } } else { session_message("Quitting the session"); m_window->quit(); } } void qt5nsmanager::show_message ( const std::string & tag, const std::string & msg ) const { if (m_window && ! msg.empty()) { bool quiet = rc().quiet() || usr().in_nsm_session(); if (quiet) { smanager::show_message(tag, msg); perf()->clear_port_map_error(); } else { std::string text = tag + ": " + msg; bool prompt = perf()->port_map_error() || perf()->new_ports_available(); bool yes = m_window->show_error_box_ex(text, prompt); if (yes) { /* * We get here before the call to session_setup(). * So we check the condition above again in our run() * function. See above. */ perf()->store_io_maps_and_restart(); } else { /* * If the user clicks OK, opens a pattern editor, and then * opens another MIDI file, and this message box pops up, * a segfault can occur. So turn on the quiet option * as well. It's on the user to accept the mismatch. */ perf()->clear_port_map_error(); rc().quiet(true); } } } } /** * Shows the collected messages in the message-box, and recommends the user * exit and check the configuration. */ void qt5nsmanager::show_error ( const std::string & tag, const std::string & msg ) const { if (m_window) { if (msg.empty()) { /* * Consider moving this support to before this if-clause. */ #if defined SEQ66_PORTMIDI_SUPPORT if (Pm_error_present()) { std::string pmerrmsg = std::string(Pm_error_message()); append_error_message(pmerrmsg); } #endif bool quiet = rc().quiet() || usr().in_nsm_session(); if (quiet) { smanager::show_message(tag, msg); } else { std::string html = tag + " "; html += string_replace(error_message(), "\n", "
"); html += "
Please exit and fix the configuration."; m_window->show_error_box(html); } } else { #if defined SEQ66_PORTMIDI_SUPPORT if (Pm_error_present()) { std::string pmerrmsg = std::string(Pm_error_message()); append_error_message(pmerrmsg); } #endif std::string text = tag; if (! tag.empty()) text += " "; text += msg; if (rc().quiet()) { smanager::show_message(tag, msg); perf()->clear_port_map_error(); /* keep going! */ } else { if (usr().in_nsm_session()) { (void) m_window->show_timed_error_box(text); perf()->clear_port_map_error(); } else { bool doit = m_window->show_error_box_ex ( text, perf()->port_map_error() ); if (doit) perf()->store_io_maps_and_restart(); else perf()->clear_port_map_error(); } } } } } void qt5nsmanager::session_manager_name (const std::string & mgrname) { clinsmanager::session_manager_name(mgrname); if (m_window) m_window->session_manager(mgrname.empty() ? "None" : mgrname); } void qt5nsmanager::session_manager_path (const std::string & pathname) { clinsmanager::session_manager_path(pathname); if (m_window) m_window->session_path(pathname.empty() ? "None" : pathname); } void qt5nsmanager::session_display_name (const std::string & dispname) { clinsmanager::session_display_name(dispname); if (m_window) m_window->session_display_name(dispname.empty() ? "None" : dispname); } void qt5nsmanager::session_client_id (const std::string & clid) { clinsmanager::session_client_id(clid); if (m_window) m_window->session_client_id(clid.empty() ? "None" : clid); } /* * Added in version 0.99.16 in order to clear the modified flag and * close editor windows. */ bool qt5nsmanager::save_session (std::string & msg, bool ok) { bool result = clinsmanager::save_session(msg, ok); if (m_window && ok) m_window->enable_save_update(false); return result; } void qt5nsmanager::handle_show_hide (bool hide) { if (hide != m_was_hidden) { #if defined SEQ66_NSM_SUPPORT if (not_nullptr(nsm_client())) /* are we under NSM control? */ { usr().session_visibility(! hide); rc().auto_usr_save(true); } #endif m_was_hidden = hide; if (hide) hide_gui(); else show_gui(); } } void qt5nsmanager::show_gui () { if (m_window) { perf()->hidden(false); /* turns off show-hide pending flag */ m_window->show(); send_visibility(true); } } void qt5nsmanager::hide_gui () { if (m_window) { perf()->hidden(true); /* turns off show-hide pending flag */ m_window->hide(); send_visibility(false); } } void qt5nsmanager::send_visibility (bool visible) { status_message(visible ? "GUI is showing..." : "GUI is hiding..."); #if defined SEQ66_NSM_SUPPORT if (not_nullptr(nsm_client())) nsm_client()->send_visibility(visible); #endif } void qt5nsmanager::set_last_dirty () { last_dirty_status(perf()->modified()); #if defined SEQ66_NSM_SUPPORT if (not_nullptr(nsm_client())) nsm_client()->dirty(last_dirty_status()); #endif } void qt5nsmanager::client_show_hide () { #if defined SEQ66_NSM_SUPPORT if (not_nullptr(nsm_client())) handle_show_hide(nsm_client()->hidden()); #endif } void qt5nsmanager::set_session_url () { #if defined SEQ66_NSM_SUPPORT if (not_nullptr(nsm_client())) { std::string url = nsm_client()->nsm_url(); m_window->session_URL(url); } else m_window->session_URL("None"); #else m_window->session_URL("None"); #endif } } // namespace seq66 /* * qt5nsmanager.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/Makefile.am ================================================ #***************************************************************************** # Makefile.am (seq_rtmidi) #----------------------------------------------------------------------------- ## # \file Makefile.am # \library seq_rtmidi # \author Chris Ahlstrom # \date 2016-11-19 # \updates 2016-11-19 # \version $Revision$ # \license $MIDICVT_SUITE_GPL_LICENSE$ # # This file is a makefile for the libseq66 library project. This # makefile provides the skeleton needed to build the libseq66 project # directory using GNU autotools. # #----------------------------------------------------------------------------- #***************************************************************************** # Packing targets. #----------------------------------------------------------------------------- # # Always use Automake in foreign mode (adding foreign to # AUTOMAKE_OPTIONS in Makefile.am). Otherwise, it requires too many # boilerplate files from the GNU coding standards that aren't useful to # us. # #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #***************************************************************************** # EXTRA_DIST #----------------------------------------------------------------------------- EXTRA_DIST = #***************************************************************************** # SUBDIRS #----------------------------------------------------------------------------- SUBDIRS = include src #***************************************************************************** # DIST_SUBDIRS #----------------------------------------------------------------------------- # # DIST_SUBDIRS is used by targets that need to recurse into /all/ # directories, even those which have been conditionally left out of the # build. # # Precisely, DIST_SUBDIRS is used by: # # - make dist # - make distclean # - make maintainer-clean. # # All other recursive targets use SUBDIRS. # #----------------------------------------------------------------------------- DIST_SUBDIRS = $(SUBDIRS) #***************************************************************************** # all-local #----------------------------------------------------------------------------- all-local: @echo "Top source-directory 'top_srcdir' is $(top_srcdir)" @echo "* * * * * All libseq_rtmidi build items completed * * * * *" #***************************************************************************** # Makefile.am (seq_rtmidi) #----------------------------------------------------------------------------- # vim: ts=3 sw=3 noet ft=automake #----------------------------------------------------------------------------- ================================================ FILE: seq_rtmidi/include/Makefile.am ================================================ #****************************************************************************** # Makefile.am (seq_rtmidi) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library seq_rtmidi library # \author Chris Ahlstrom # \date 2016-11-19 # \update 2022-02-22 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the seq66 C/C++ # library. # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) CLEANFILES = *.gc* MOSTLYCLEANFILES = *~ #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = *.h *.hpp #****************************************************************************** # Items from configure.ac #------------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ GIT_VERSION = @GIT_VERSION@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ prefix = @prefix@ includedir = @seq66includedir@ libdir = @seq66libdir@ datadir = @datadir@ datarootdir = @datarootdir@ #****************************************************************************** # Source files #---------------------------------------------------------------------------- pkginclude_HEADERS = \ mastermidibus_rm.hpp \ midibus_rm.hpp \ midi_alsa.hpp \ midi_alsa_info.hpp \ midi_api.hpp \ midi_info.hpp \ midi_jack.hpp \ midi_jack_data.hpp \ midi_jack_info.hpp \ midi_probe.hpp \ rterror.hpp \ rtmidi.hpp \ rtmidi_info.hpp \ seq66_rtmidi_features.h #****************************************************************************** # uninstall-hook #------------------------------------------------------------------------------ uninstall-hook: @echo "Note: you may want to remove $(pkgincludedir) manually" #****************************************************************************** # Makefile.am (seq_rtmidi) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: seq_rtmidi/include/mastermidibus_rm.hpp ================================================ #if ! defined SEQ66_MASTERMIDIBUS_RM_HPP #define SEQ66_MASTERMIDIBUS_RM_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file mastermidibus_rm.hpp * * This module declares/defines the base class for MIDI I/O for Linux, Mac, * and Windows, via the RtMidi API. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2024-04-30 * \license GNU GPLv2 or above * * This mastermidibus module is the Linux (and, soon, JACK) version of the * mastermidibus module using the completely refactored RtMidi library. */ #include "midi/mastermidibase.hpp" /* seq66::mastermidibase ABC */ #include "rtmidi_info.hpp" /* seq66::rtmidi_info, new class */ namespace seq66 { /** * The class that "supervises" all of the midibus objects. This * implementation uses the PortMidi library, which supports Linux and * Windows, but not JACK or Mac OSX. */ class mastermidibus final : public mastermidibase { friend class midi_alsa_info; friend class midi_jack_info; private: /** * Holds the basic MIDI input and output information for later re-use in * the construction of midibus objects. */ rtmidi_info m_midi_master; /** * Indicates we are running with JACK MIDI enabled, and need to use each * port's ability to poll for and get MIDI events, rather than use ALSA's * method of calling functions from the "MIDI master" object. */ bool m_use_jack_polling; public: mastermidibus () = delete; mastermidibus (int ppqn, midibpm bpm); virtual ~mastermidibus (); virtual bool activate () override; protected: virtual bool api_get_midi_event (event * in) override; virtual int api_poll_for_midi () override; virtual void api_init (int ppqn, midibpm bpm) override; /** * Provides MIDI API-specific functionality for the set_ppqn() function. */ virtual void api_set_ppqn (int p) override { midi_master().api_set_ppqn(p); } /** * Provides MIDI API-specific functionality for the * set_beats_per_minute() function. */ virtual void api_set_beats_per_minute (midibpm b) override { midi_master().api_set_beats_per_minute(b); } virtual void api_flush () override { midi_master().api_flush(); } /* virtual */ void api_port_start (mastermidibus & masterbus, int bus, int port) { midi_master().api_port_start(masterbus, bus, port); } private: /* * Would this function be useful? * * void port_list (const std::string & tag); */ midibus * make_virtual_bus (int bus, midibase::io iotype); midibus * make_normal_bus (int bus, midibase::io iotype); const rtmidi_info & midi_master () const { return m_midi_master; } rtmidi_info & midi_master () { return m_midi_master; } }; // class mastermidibus } // namespace seq66 #endif // SEQ66_MASTERMIDIBUS_RM_HPP /* * mastermidibus_rm.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/midi_alsa.hpp ================================================ #if ! defined SEQ66_MIDI_ALSA_HPP #define SEQ66_MIDI_ALSA_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_alsa.hpp * * This module declares/defines the base class for MIDI I/O under Linux. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-12-18 * \updates 2022-06-02 * \license GNU GPLv2 or above * * The midi_alsa module is the Linux version of the midi_alsa module. * There's almost enough commonality to be worth creating a base class * for both classes. */ #include "seq66-config.h" #include "midi_api.hpp" #if SEQ66_HAVE_LIBASOUND #include #include #else #error ALSA not supported in this build, fix the project configuration. #endif namespace seq66 { class event; class midibus; /** * This class implements the ALSA version of the midi_api. */ class midi_alsa : public midi_api { private: /** * ALSA sequencer client handle. */ snd_seq_t * const m_seq; /** * Destination address of client. Could potentially be replaced by * midibase::m_bus_id. */ const int m_dest_addr_client; /** * Destination port of client. Could potentially be replaced by * midibase::m_port_id. */ const int m_dest_addr_port; /** * Local address of client. */ const int m_local_addr_client; /** * Local port of client. */ int m_local_addr_port; /** * Holds the port name for the ALSA MIDI input port. It is derived from * the (optionally configured) official client name for the application * with the word "in" appended. */ const std::string m_port_name; public: /* * Normal port constructor. * This version is used when querying for existing input ports in the * ALSA system. It is also used when creating the "announce buss". * Does not yet directly include the concept of buss ID and port ID. * * Compare to the output midibus constructor called in seq_alsamidi's * mastermidibus module. Also note we'll need midi_info::midi_mode() * to set up for midi_alsa_in versus midi_alsa_out. */ midi_alsa (midibus & parentbus, midi_info & masterinfo); virtual ~midi_alsa (); virtual int get_client () const { return m_dest_addr_client; } virtual int get_port () const { return m_dest_addr_port; } protected: virtual bool api_init_out () override; virtual bool api_init_in () override; virtual bool api_init_out_sub () override; virtual bool api_init_in_sub () override; virtual bool api_deinit_out () override; virtual bool api_deinit_in () override; /** * ALSA get MIDI events via the midi_alsa_info object at present. */ virtual bool api_get_midi_event (event *) override { return false; } /* * The actual polling is handled by midi_alsa_info. What a mess! */ virtual int api_poll_for_midi () override { return 0; } virtual bool api_connect () override; virtual void api_play (const event * e24, midibyte channel) override; virtual void api_sysex (const event * e24) override; virtual void api_flush () override; virtual void api_continue_from (midipulse tick, midipulse beats) override; virtual void api_start () override; virtual void api_stop () override; virtual void api_clock (midipulse tick) override; virtual void api_set_ppqn (int ppqn) override; virtual void api_set_beats_per_minute (midibpm bpm) override; private: bool set_virtual_name (int portid, const std::string & portname); }; // class midi_alsa /** * This class implements the ALSA version of a MIDI input object. */ class midi_in_alsa final : public midi_alsa { public: midi_in_alsa (midibus & parentbus, midi_info & masterinfo); virtual int api_poll_for_midi () override; }; // class midi_in_alsa /** * This class implements the ALSA version of a MIDI output object. */ class midi_out_alsa final : public midi_alsa { public: midi_out_alsa (midibus & parentbus, midi_info & masterinfo); }; // class midi_out_alsa } // namespace seq66 #endif // SEQ66_MIDI_ALSA_HPP /* * midi_alsa.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/midi_alsa_info.hpp ================================================ #if ! defined SEQ66_MIDI_ALSA_INFO_HPP #define SEQ66_MIDI_ALSA_INFO_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_alsa_info.hpp * * A class for holding the current status of the ALSA system on the host. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-12-04 * \updates 2022-06-03 * \license See above. * * We need to have a way to get all of the ALSA information of * the midi_alsa objects...? */ #include #include "midi_info.hpp" /* seq66::midi_port_info etc. */ #include "midi/midibus.hpp" /* seq66::midibus */ namespace seq66 { class mastermidibus; /** * The class for handling ALSA MIDI input. */ class midi_alsa_info final : public midi_info { private: /** * Flags that denote queries for input (read) ports. */ static unsigned sm_input_caps; /** * Flags that denote queries for output (write) ports. */ static unsigned sm_output_caps; /** * Holds the ALSA sequencer client pointer so that it can be used * by the midibus objects. This is actually an opaque pointer; there is * no way to get the actual fields in this structure; they can only be * accessed through functions in the ALSA API. */ snd_seq_t * m_alsa_seq; /** * The number of descriptors for polling. */ int m_num_poll_descriptors; /** * Points to the list of descriptors for polling. */ struct pollfd * m_poll_descriptors; public: midi_alsa_info () = delete; midi_alsa_info (const std::string & appname, int ppqn, midibpm bpm); virtual ~midi_alsa_info (); /** * \getter m_alsa_seq * This is the platform-specific version of midi_handle(). */ snd_seq_t * seq () { return m_alsa_seq; } virtual bool api_get_midi_event (event * inev) override; virtual bool api_connect () override; virtual void api_set_ppqn (int p) override; virtual int api_poll_for_midi () override; virtual void api_set_beats_per_minute (midibpm b) override; virtual void api_port_start ( mastermidibus & masterbus, int bus, int port ) override; virtual void api_flush () override; private: virtual int get_all_port_info ( midi_port_info & inports, midi_port_info & outports ) override; void get_poll_descriptors (); void remove_poll_descriptors (); bool check_port_type (snd_seq_port_info_t * pinfo) const; bool show_event (snd_seq_event_t * ev, const char * tag); }; // class midi_alsa_info } // namespace seq66 #endif // SEQ66_MIDI_ALSA_INFO_HPP /* * midi_alsa_info.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/midi_api.hpp ================================================ #if ! defined SEQ66_MIDI_API_HPP #define SEQ66_MIDI_API_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_api.hpp * * An abstract base class for realtime MIDI input/output. * * \library seq66 application * \author Gary P. Scavone; modifications by Chris Ahlstrom * \date 2016-11-14 * \updates 2022-03-13 * \license See above. * * Declares the following classes: * * - seq66::midi_api * - seq66::midi_in_api * - seq66::midi_out_api */ #include "midibus_rm.hpp" /* seq66::midibus for rtmidi */ #include "rterror.hpp" /* seq66::rterror */ #include "rtmidi_types.hpp" /* seq66::rtmidi_in_data */ namespace seq66 { class event; class midi_info; /** * Subclasses of midi_in_api and midi_out_api contain all API- and * OS-specific code necessary to fully implement the rtmidi API. * * Note that midi_in_api and midi_out_api are abstract base classes and * cannot be explicitly instantiated. rtmidi_in and rtmidi_out will * create instances of a midi_in_api or midi_out_api subclass. */ class midi_api { private: /** * Contains information about the ports (system or client) enumerated by * the API. Currently has child classes midi_alsa_info and midi_jack_info. */ midi_info & m_master_info; /** * Contains a reference to the parent midibus/midibase object. This * object is needed to get parameters that are peculiar to the port as it * is actually set up, rather than information from the midi_info object. */ midibus & m_parent_bus; /** * Although this really is useful only for MIDI input objects, the split * of the midi_api is not as convenient for re-use as is the split for * derived classes like midi_in_jack/midi_out_jack. */ rtmidi_in_data m_input_data; /** * Set to true if the port was opened, activated, and connected without * issue. */ bool m_connected; protected: /** * Holds the last error message, if in force. This is an original RtMidi * concept. */ std::string m_error_string; /** * Holds the error callback function pointer, if any. This is an * original RtMidi concept. */ rterror_callback m_error_callback; /** * Indicates that the first error has happened. This is an original * RtMidi concept. I have to confess I am not sure how it is/should be * used, yet. */ bool m_first_error_occurred; /** * Holds data needed by the error-callback. This is an original RtMidi * concept. I have to confess I am not sure how it is/should be used, * yet. */ void * m_error_callback_user_data; public: midi_api (midibus & parentbus, midi_info & masterinfo); virtual ~midi_api (); public: /** * No code; only midi_jack overrides this function at present. */ virtual bool api_connect () { return true; } virtual int api_poll_for_midi () = 0; virtual bool api_init_out () = 0; virtual bool api_init_out_sub () = 0; virtual bool api_init_in () = 0; virtual bool api_init_in_sub () = 0; virtual bool api_deinit_out () = 0; virtual bool api_deinit_in () = 0; virtual bool api_get_midi_event (event *) = 0; virtual void api_play (const event * e24, midibyte channel) = 0; virtual void api_sysex (const event * e24) = 0; virtual void api_continue_from (midipulse tick, midipulse beats) = 0; virtual void api_start () = 0; virtual void api_stop () = 0; virtual void api_flush () = 0; virtual void api_clock (midipulse tick) = 0; virtual void api_set_ppqn (int ppqn) = 0; virtual void api_set_beats_per_minute (midibpm bpm) = 0; /* * The next two functions are provisional. Currently useful only in the * midi_jack module. */ virtual std::string api_get_bus_name () { std::string sm_empty; return sm_empty; } virtual std::string api_get_port_name () { std::string sm_empty; return sm_empty; } public: bool is_port_open () const { return m_connected; } midi_info & master_info () { return m_master_info; } const midi_info & master_info () const { return m_master_info; } midibus & parent_bus () { return m_parent_bus; } const midibus & parent_bus () const { return m_parent_bus; } void master_midi_mode (midibase::io iotype); /* * A basic error reporting function for rtmidi classes. */ void error (rterror::kind errtype, const std::string & errorstring); /* * Pass-alongs to the midibus representing this object's generic data. * * Ones not passed along at this time: * * display_name() * client_id() * set_clock() [useful in disabling a midi_jack/midi_alsa port?] */ bool is_input_port () const { return parent_bus().is_input_port(); } /** * A virtual port is what Seq24 called a "manual" port. It is a MIDI * port that an application can create as if it is a real ALSA or * JACK port. */ bool is_virtual_port () const { return parent_bus().is_virtual_port(); } /** * A system port is one that is independent of the devices and * applications that exist. In the ALSA subsystem, the only system port * is the "announce" port (and the unused "timer" port). */ bool is_system_port () const { return parent_bus().is_system_port(); } const std::string & bus_name () const { return parent_bus().bus_name(); } const std::string & port_name () const { return parent_bus().port_name(); } const std::string & port_alias () const { return parent_bus().port_alias(); } midibase::port port_type () const { return parent_bus().port_type(); } bool enabled () const { return parent_bus().port_enabled(); } std::string connect_name () const { return parent_bus().connect_name(); } int bus_index () const { return parent_bus().bus_index(); } /* * The next two functions are useful in debugging. */ int bus_id () const { return parent_bus().bus_id(); } int port_id () const { return parent_bus().port_id(); } int ppqn () const { return parent_bus().ppqn(); } midibpm bpm () const { return parent_bus().bpm(); } protected: /* * Pass-alongs to the midibus representing this object's generic data. */ void set_client_id (int id) { parent_bus().set_client_id(id); } void set_bus_id (int id) { parent_bus().set_bus_id(id); } void set_port_id (int id) { parent_bus().set_port_id(id); } void bus_name (const std::string & name) { parent_bus().bus_name(name); } void port_name (const std::string & name) { parent_bus().port_name(name); } /* * Is this ever called? */ void set_name ( const std::string & appname, const std::string & busname, const std::string & portname ) { parent_bus().set_name(appname, busname, portname); } void set_alt_name ( const std::string & appname, const std::string & busname ) { parent_bus().set_alt_name(appname, busname); } /* * API-related functions. */ void set_port_open () { m_connected = true; } rtmidi_in_data * input_data () { return &m_input_data; } }; // class midi_api } // namespace seq66 #endif // SEQ66_MIDI_API_HPP /* * midi_api.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/midi_info.hpp ================================================ #if ! defined SEQ66_MIDI_INFO_HPP #define SEQ66_MIDI_INFO_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_info.hpp * * A class for holding the current status of the MIDI system on the host. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-12-05 * \updates 2022-06-15 * \license See above. * * We need to have a way to get all of the API information from each * framework, without supporting the full API. The Seq66 masteridibus and * midibus classes require certain information to be known when they are * created: * * - Port counts. The number of input ports and output ports needs to * be known so that we can iterate properly over them to create * midibus objects. * - Port information. We want to assemble port names just once, and * never have to deal with it again (assuming that MIDI ports do not * come and go during the execution of Seq66. * - Client information. We want to assemble client names or numbers * just once. * * Note that, while the other midi_api-based classes access port via the port * numbers assigned by the MIDI subsystem, midi_info-based classes use the * concept of an "index", which ranges from 0 to one less than the number of * input or output ports. These values are indices into a vector of * port_info structures, and are easily looked up when mastermidibus creates * a midibus object. * * An alternate name for this class could be "midi_master". :-) */ #include "rterror.hpp" /* seq66::rterror exception class */ #include "rtmidi_types.hpp" /* seq66::rtmidi_api, midi_message */ /** * A potential future feature, macroed to avoid issues until it is perfected. * Meant to allow detecting changes in the set of MIDI ports, and * disconnecting or connecting as appropriate, if not in manual/virtual mode. * * It is now definable (currently for test purposes) in the configuration * process and in the rtmidi qmake configuration, so that it may be centrally * located, because it may have implication throughout Seq66. * * #undef SEQ66_MIDI_PORT_REFRESH */ namespace seq66 { class event; class mastermidibus; class midibus; /** * A structure for hold basic information about a single (MIDI) port. * Except for the virtual-vs-normal status, this information is obtained by * scanning the system at the startup time of the application. */ class port_info { friend class midi_port_info; private: int m_client_number; /**< The major buss number of the port. */ std::string m_client_name; /**< The system's name for the client. */ int m_port_number; /**< The minor port number of the port. */ std::string m_port_name; /**< The system's name for the port. */ int m_queue_number; /**< A number used in some APIs. */ midibase::io m_io_type; /**< Indicates input versus output. */ midibase::port m_port_type; /**< Indicates normal/virtual/system. */ std::string m_port_alias; /**< Can be non-empty in JACK setups. */ uint32_t m_internal_id; /**< Internal port number. */ public: port_info () = default; port_info ( int clientnumber, const std::string & clientname, int portnumber, const std::string & portname, midibase::io iotype, midibase::port porttype, int queuenumber, const std::string & alias ); port_info (const port_info &) = default; port_info & operator = (const port_info &) = default; ~port_info () = default; }; /** * A class for holding port information for a number of ports. */ class midi_port_info { private: /** * Holds the number of ports counted. */ int m_port_count; /** * Holds information on all of the ports that were "scanned". */ std::vector m_port_container; public: midi_port_info (); midi_port_info (const midi_port_info &) = default; midi_port_info & operator = (const midi_port_info &) = default; ~midi_port_info () = default; void add ( int clientnumber, // buss number/ID const std::string & clientname, // buss name int portnumber, const std::string & portname, midibase::io iotype, midibase::port porttype, int queuenumber = bad_id(), const std::string & alias = "" // not always available from JACK ); void add (const midibus * m); /** * This function is useful in replacing the discovered system ports with * the manual/virtual ports added in "manual" mode. */ void clear () { m_port_container.clear(); m_port_count = 0; } int get_port_count () const { return m_port_count; } bussbyte get_port_index (int client, int port); int get_bus_id (int index) const { if (index < get_port_count()) return m_port_container[index].m_client_number; else return bad_id(); } std::string get_bus_name (int index) const { if (index < get_port_count()) return m_port_container[index].m_client_name; else return std::string(""); } int get_port_id (int index) const { if (index < get_port_count()) return m_port_container[index].m_port_number; else return bad_id(); } std::string get_port_name (int index) const { if (index < get_port_count()) return m_port_container[index].m_port_name; else return std::string(""); } std::string get_port_alias (int index) const { if (index < get_port_count()) return m_port_container[index].m_port_alias; else return std::string(""); } bool get_input (int index) const { if (index < get_port_count()) return m_port_container[index].m_io_type == midibase::io::input; else return false; } bool get_virtual (int index) const { if (index < get_port_count()) return m_port_container[index].m_port_type == midibase::port::manual; else return false; } bool get_system (int index) const { if (index < get_port_count()) return m_port_container[index].m_port_type == midibase::port::system; else return false; } int get_queue_number (int index) const { if (index < get_port_count()) return m_port_container[index].m_queue_number; else return bad_id(); } /** * Provides the bus name and port name in canonical JACK format: * "busname:portname". This function is basically the same as * midibase::connect_name() function. If either the bus name or port * name are empty, then an empty string is returned. */ std::string connect_name (int index) const { std::string result = get_bus_name(index); if (! result.empty()) { std::string pname = get_port_name(index); if (! pname.empty()) { result += ":"; result += pname; } } return result; } }; // class midi_port_info /** * The class for holding basic information on the MIDI input and output ports * currently present in the system. */ class midi_info { friend class rtmidi_info; private: #if defined SEQ66_MIDI_PORT_REFRESH /** * Stores that last input configuration, used when port-registration or * port-unregistration is detected. */ static midi_port_info m_previous_input; /** * Stores that last output configuration, used when port-registration or * port-unregistration is detected. */ static midi_port_info m_previous_output; #endif // SEQ66_MIDI_PORT_REFRESH /** * Indicates which mode we're in, input or output. We have to pick the * mode we need to be in with the set_mode() function before we do a * series of operations. This clumsy two-step is needed in order to * preserve the midi_api interface. */ bool m_midi_mode_input; /** * Holds data on the ALSA/JACK/Core/WinMM inputs. */ midi_port_info m_input; /** * Holds data on the ALSA/JACK/Core/WinMM outputs. */ midi_port_info m_output; /** * Holds pointers to the ports that were created, so that, after * activation, we can call the connect_port() function on those that are * not virtual. See the add_bus() and bus_container() member functions. * * Currently this container is used ONLY in midi_jack_info :: * api_connect(). Can we give the JACK code access to the * busarrays that also contain these pointers? */ std::vector m_bus_container; /** * The ID of the ALSA MIDI queue. */ int m_global_queue; /** * Provides a handle to the main ALSA or JACK implementation object. * Created by the class derived from midi_info. */ void * m_midi_handle; /** * Holds this value for passing along, to reduce the number of arguments * needed. This value is the main application name as determined at * ./configure time. */ const std::string m_app_name; /** * Hold this value for passing along to some ports that get created. * Some APIs can use this value. */ int m_ppqn; /** * Hold this value for passing along to some ports that get created. * Some APIs can use this value. */ midibpm m_bpm; /** * Always false until this feature is complete. */ bool m_midi_port_refresh; protected: /** * Error string for the midi_info interface. */ std::string m_error_string; public: midi_info () = delete; midi_info /* similar to mastermidibus */ ( const std::string & appname, int ppqn, midibpm bpm ); virtual ~midi_info () { // Empty body } bool midi_mode () const { return m_midi_mode_input; } void midi_mode (bool flag) { m_midi_mode_input = flag; } void midi_mode (midibase::io iotype) { midi_mode(iotype == midibase::io::input); } void * midi_handle () { return m_midi_handle; } midi_port_info & input_ports () { return m_input; } midi_port_info & output_ports () { return m_output; } int full_port_count () const { return m_input.get_port_count() + m_output.get_port_count(); } void clear () { m_input.clear(); m_output.clear(); } const std::string & app_name () const { return m_app_name; } /** * \getter m_ppqn, simple version, also see api_set_ppqn(). */ int ppqn () const { return m_ppqn; } /** * \getter m_bpm, simple version, also see api_set_beats_per_minute(). */ midibpm bpm () const { return m_bpm; } bool midi_port_refresh () const { return m_midi_port_refresh; } /** * No need to override this one, though it is virtual. */ virtual int get_all_port_info () { return get_all_port_info(input_ports(), output_ports()); } virtual int get_all_port_info ( midi_port_info & inports, midi_port_info & outports ) = 0; /** * Special setter. */ virtual void api_set_ppqn (int p) { m_ppqn = p; } /** * Special setter. */ virtual void api_set_beats_per_minute (midibpm b) { m_bpm = b; } /** * An ALSA-specific function at the moment. */ virtual void api_port_start ( mastermidibus & /* masterbus */, int /* bus */, int /* port */ ) { // Empty body } virtual bool api_get_midi_event (event * inev) = 0; virtual int api_poll_for_midi () = 0; /* disposable??? */ virtual void api_flush () = 0; /** * Used only in the midi_jack_info class. */ virtual bool api_connect () { return true; } virtual int get_port_count () const { const midi_port_info & mpi = const_midi_port_info(); return mpi.get_port_count(); } virtual int get_bus_id (int index) const { const midi_port_info & mpi = const_midi_port_info(); return mpi.get_bus_id(index); } virtual std::string get_bus_name (int index) const { const midi_port_info & mpi = const_midi_port_info(); return mpi.get_bus_name(index); } virtual int get_port_id (int index) const { const midi_port_info & mpi = const_midi_port_info(); return mpi.get_port_id(index); } virtual std::string get_port_name (int index) const { const midi_port_info & mpi = const_midi_port_info(); return mpi.get_port_name(index); } virtual std::string get_port_alias (int index) const { const midi_port_info & mpi = const_midi_port_info(); return mpi.get_port_alias(index); } virtual bool get_input (int index) const { const midi_port_info & mpi = const_midi_port_info(); return mpi.get_input(index); } virtual bool get_virtual (int index) const { const midi_port_info & mpi = const_midi_port_info(); return mpi.get_virtual(index); } virtual bool get_system (int index) const { const midi_port_info & mpi = const_midi_port_info(); return mpi.get_system(index); } virtual int queue_number (int index) const { const midi_port_info & mpi = const_midi_port_info(); return mpi.get_queue_number(index); } std::string connect_name (int index) const { const midi_port_info & mpi = const_midi_port_info(); return mpi.connect_name(index); } std::string port_list () const; int global_queue () const { return m_global_queue; } /** * A basic error reporting function for midi_info classes. */ void error (rterror::kind errtype, const std::string & errorstring); protected: /** * Adds the midibus to a list of all ports for use in the api_connect() * call in mastermidibus. We could add the midibus pointer to the * midi_port_info structure, but that information is strictly for data * obtained by querying the system via the selected API. */ void add_bus (const midibus * m) { if (not_nullptr(m)) m_bus_container.push_back(const_cast(m)); } void global_queue (int q) { m_global_queue = q; } void midi_handle (void * h) { m_midi_handle = h; } std::vector & bus_container () { return m_bus_container; } private: /** * Used for retrieving values from the input or output containers. The * caller must insure the proper container by calling the midi_mode() * function with the value of true (midibase::c_input_port) or false * (midibase::c_output_port) first. Ugly stuff. I hate it. */ const midi_port_info & const_midi_port_info () const { return m_midi_mode_input ? m_input : m_output ; } }; // midi_info } // namespace seq66 #endif // SEQ66_MIDI_INFO_HPP /* * midi_info.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/midi_jack.hpp ================================================ #if ! defined SEQ66_MIDI_JACK_HPP #define SEQ66_MIDI_JACK_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_jack.hpp * * A class for realtime MIDI input/output via JACK. * * \library seq66 application * \author Gary P. Scavone; severe refactoring by Chris Ahlstrom * \date 2016-11-14 * \updates 2022-09-13 * \license See above. * * In this refactoring, we've stripped out most of the original RtMidi * functionality, leaving only the method for selecting the API to use for * MIDI. The method that Seq66's mastermidibus uses to initialize port has * been transplanted to this rtmidi library. The name "rtmidi" is now * somewhat misleading. */ #include #include "seq66_features.hpp" #include "midi_api.hpp" #if defined SEQ66_JACK_SUPPORT #include "midi_jack_data.hpp" /* seq66::midi_jack_data */ #include "midi_jack_info.hpp" /* seq66::midi_jack_info */ namespace seq66 { class midibus; /** * This class implements with JACK version of the midi_alsa object. */ class midi_jack : public midi_api { friend class midi_jack_info; private: /** * Preserves the original name of the remote port, so it can be used * later for connection and for analyzing port registration / * unregistration. */ std::string m_remote_port_name; protected: /** * This reference is needed in order for this midi_jack object to add * itself to the main midi_jack_info list. */ midi_jack_info & m_jack_info; /** * Holds the data needed for JACK processing. Please do not confuse this * item with the m_midi_handle of the midi_api base class. This object * holds a JACK-client pointer and a JACK-port pointer. */ midi_jack_data m_jack_data; private: midi_jack (); public: midi_jack (midibus & parentbus, midi_info & masterinfo); virtual ~midi_jack (); /** * This is the platform-specific version of midi_handle(). */ jack_client_t * client_handle () { return jack_data().jack_client(); } const midi_jack_data & jack_data () const { return m_jack_data; } midi_jack_data & jack_data () { return m_jack_data; } const std::string & remote_port_name () const { return m_remote_port_name; } void remote_port_name (const std::string & s) { m_remote_port_name = s; } jack_port_t * port_handle () { return jack_data().jack_port(); } protected: void client_handle (jack_client_t * handle) { jack_data().jack_client(handle); } void port_handle (jack_port_t * handle) { jack_data().jack_port(handle); } const midi_jack_info & jack_info () const { return m_jack_info; } midi_jack_info & jack_info () { return m_jack_info; } const midi_jack_info::portlist & jack_ports () const { return jack_info().jack_ports(); } midi_jack_info::portlist & jack_ports () { return jack_info().jack_ports(); } void close_client (); void close_port (); bool create_ringbuffer (size_t rbsize); bool connect_port ( midibase::io iotype, const std::string & sourceportname, const std::string & destportname ); bool register_port (midibase::io iotype, const std::string & portname); protected: virtual bool api_connect () override; virtual bool api_init_out () override; virtual bool api_init_in () override; virtual bool api_init_out_sub () override; virtual bool api_init_in_sub () override; virtual bool api_deinit_out () override; virtual bool api_deinit_in () override; /** * \return * Returns false, since this is an input function that is implemented * fully only by midi_in_jack. */ virtual bool api_get_midi_event (event *) override { return false; } virtual int api_poll_for_midi () override { return 0; } virtual void api_play (const event * e24, midibyte channel) override; virtual void api_sysex (const event * e24) override; virtual void api_flush () override; virtual void api_continue_from (midipulse tick, midipulse beats) override; virtual void api_start () override; virtual void api_stop () override; virtual void api_clock (midipulse tick) override; virtual void api_set_ppqn (int ppqn) override; virtual void api_set_beats_per_minute (midibpm bpm) override; virtual std::string api_get_port_name () override; private: void send_byte (midipulse tick, midibyte evbyte); /* * Not used! * * void send_realtime_message (midibyte evbyte); */ bool send_message (const midi_message & message); bool set_virtual_name (int portid, const std::string & portname); std::string details () const; }; // class midi_jack /** * The class for handling JACK MIDI input. */ class midi_in_jack final : public midi_jack { protected: std::string m_client_name; public: midi_in_jack (midibus & parentbus, midi_info & masterinfo); virtual ~midi_in_jack (); virtual int api_poll_for_midi () override; virtual bool api_get_midi_event (event *) override; private: }; // class midi_in_jack /** * The JACK MIDI output API class. */ class midi_out_jack final : public midi_jack { public: midi_out_jack (midibus & parentbus, midi_info & masterinfo); virtual ~midi_out_jack (); }; // class midi_out_jack } // namespace seq66 #endif // SEQ66_JACK_SUPPORT #endif // SEQ66_MIDI_JACK_HPP /* * midi_jack.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/midi_jack_data.hpp ================================================ #if ! defined SEQ66_MIDI_JACK_DATA_HPP #define SEQ66_MIDI_JACK_DATA_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_jack_data.hpp * * Object for holding the current status of JACK and JACK MIDI data. * * \library seq66 application * \author Chris Ahlstrom * \date 2017-01-02 * \updates 2022-11-22 * \license See above. * * GitHub issue #165: enabled a build and run with no JACK support. */ #include "util/basic_macros.h" /* nullptr and other macros */ #include "midi/midibytes.hpp" /* seq66::midibyte, other aliases */ #include "rtmidi_types.hpp" /* seq66::rtmidi_in_data class */ #if defined SEQ66_JACK_SUPPORT #include #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER #include "util/ring_buffer.hpp" /* seq66::ring_buffer<> template */ #else #include #endif namespace seq66 { /** * Contains the JACK MIDI API data as a kind of scratchpad for this object. * This guy needs a constructor taking parameters for an rtmidi_in_data * pointer. */ class midi_jack_data { /** * Holds data about JACK transport, to be used in midi_jack :: * jack_frame_offset(). These values are a subset of what appears in the * jack_position_t structure in jack/types.h. */ static jack_nframes_t sm_jack_frame_rate; /* e.g. 48000 or 96000 Hz */ static jack_nframes_t sm_jack_start_frame; /* 0 or large number? */ static jack_nframes_t sm_cycle_frame_count; /* progress callback param */ static jack_nframes_t sm_size_compensation; /* from ttymidi.c */ static jack_time_t sm_cycle_time_us; /* time between callbacks */ static jack_time_t sm_pulse_time_us; /* duration of a MIDI pulse */ static double sm_jack_ticks_per_beat; /* seems to be 10 * PPQN */ static double sm_jack_beats_per_minute; /* the BPM for the song */ static double sm_jack_frame_factor; /* frames per PPQN tick */ static bool sm_use_offset; /* requires JACK transport */ /** * Holds the JACK sequencer client pointer so that it can be used by the * midibus objects. This is actually an opaque pointer; there is no way * to get the actual fields in this structure; they can only be accessed * through functions in the JACK API. Note that it is also stored as a * void pointer in midi_info::m_midi_handle. This item is the single * JACK client created by the midi_jack_info object. */ jack_client_t * m_jack_client; /** * Holds the JACK port information of the JACK client. */ jack_port_t * m_jack_port; /** * Holds the size of data for communicating between the client * ring-buffer and the JACK port's internal buffer, plus the data * for communicating between the client ring-buffer and the JACK * port's internal buffer. * * We are in the process of replacing the character buffer with our * MIDI message ring buffer. (See issue #100). */ #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER ring_buffer * m_jack_buffer; #else jack_ringbuffer_t * m_jack_buffmessage; #endif /** * The last time-stamp obtained. Use for calculating the delta time, I * would imagine. */ jack_time_t m_jack_lasttime; #if defined SEQ66_MIDI_PORT_REFRESH /** * An unsigned 32-bit port ID that starts out as null_system_port_id(), * and, at least in JACK can be filled with actual internal port number * assigned during port registration. */ jack_port_id_t m_internal_port_id; #endif /** * Holds special data peculiar to the client and its MIDI input * processing. This data consists of the midi_queue message queue and a * few boolean flags. */ rtmidi_in_data * m_jack_rtmidiin; public: midi_jack_data (); ~midi_jack_data (); /* * Frame offset-related functions. */ static bool recalculate_frame_factor ( const jack_position_t & pos, jack_nframes_t F ); static jack_nframes_t frame_offset (jack_nframes_t F, midipulse p); static jack_nframes_t frame_offset ( jack_nframes_t cyclestart, jack_nframes_t F, midipulse p ); #if defined USE_JACK_TIME_OFFSET_FUNCTION static jack_nframes_t time_offset ( jack_nframes_t F, midipulse p, jack_time_t Tpop, jack_time_t Tpush ); #endif static jack_nframes_t frame_estimate (midipulse p); static void cycle_frame ( midipulse p, jack_nframes_t & cycle, jack_nframes_t & offset ); static double cycle (jack_nframes_t f, jack_nframes_t F); static double pulse_cycle (midipulse p, jack_nframes_t F); static double frame (midipulse p) { return double(p) * frame_factor(); } static jack_nframes_t frame_rate () { return sm_jack_frame_rate; } static jack_nframes_t start_frame () { return sm_jack_start_frame; } static double ticks_per_beat () { return sm_jack_ticks_per_beat; } static double beats_per_minute () { return sm_jack_beats_per_minute; } static double frame_factor () { return sm_jack_frame_factor; } static bool use_offset () { return sm_use_offset; } static jack_nframes_t cycle_frame_count () { return sm_cycle_frame_count; } static jack_nframes_t size_compensation () { return sm_size_compensation; } static jack_time_t cycle_time_us () { return sm_cycle_time_us; } static unsigned cycle_time_ms () { return sm_cycle_time_us / 1000; } static jack_time_t pulse_time_us () { return sm_pulse_time_us; } static unsigned pulse_time_ms () { return sm_pulse_time_us / 1000; } static unsigned delta_time_ms (midipulse p); static void frame_rate (jack_nframes_t nf) { sm_jack_frame_rate = nf; } static void start_frame (jack_nframes_t nf) { sm_jack_start_frame = nf; } static void ticks_per_beat (double tpb) { sm_jack_ticks_per_beat = tpb; } static void beats_per_minute (double bp) { sm_jack_beats_per_minute = bp; } static void frame_factor (double ff) { sm_jack_frame_factor = ff; } static void use_offset (bool flag) { sm_use_offset = flag; } static void cycle_frame_count (jack_nframes_t cfc) { sm_cycle_frame_count = cfc; } static void size_compensation (jack_nframes_t szc) { sm_size_compensation = szc; } static void cycle_time_us (jack_time_t jt) { sm_cycle_time_us = jt; } static void pulse_time_us (jack_time_t jt) { sm_pulse_time_us = jt; } /* * Basic member access. Getters and setters. */ jack_client_t * jack_client () { return m_jack_client; } void jack_client (jack_client_t * jc) { m_jack_client = jc; } jack_port_t * jack_port () { return m_jack_port; } void jack_port (jack_port_t * jp) { m_jack_port = jp; } rtmidi_in_data * jack_rtmidiin () const { return m_jack_rtmidiin; } void jack_rtmidiin (rtmidi_in_data * rid) { m_jack_rtmidiin = rid; } #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER bool valid_buffer () const { return not_nullptr(m_jack_buffer); } ring_buffer * jack_buffer () { return m_jack_buffer; } void jack_buffer (ring_buffer * rb) { m_jack_buffer = rb; } #else bool valid_buffer () const { return not_nullptr(m_jack_buffmessage); } jack_ringbuffer_t * jack_buffmessage () { return m_jack_buffmessage; } void jack_buffmessage (jack_ringbuffer_t * jrb) { m_jack_buffmessage = jrb; } #endif jack_time_t jack_lasttime () const { return m_jack_lasttime; } void jack_lasttime (jack_time_t jt) { m_jack_lasttime = jt; } #if defined SEQ66_MIDI_PORT_REFRESH jack_port_id_t internal_port_id () const { return m_internal_port_id; } void internal_port_id (uint32_t id) { m_internal_port_id = id; } #endif }; // class midi_jack_data } // namespace seq66 #endif // SEQ66_JACK_SUPPORT #endif // SEQ66_MIDI_JACK_DATA_HPP /* * midi_jack_data.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/midi_jack_info.hpp ================================================ #if ! defined SEQ66_MIDI_JACK_INFO_HPP #define SEQ66_MIDI_JACK_INFO_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_jack_info.hpp * * A class for holding the current status of the JACK system on the host. * * \library seq66 application * \author Chris Ahlstrom * \date 2017-01-01 * \updates 2023-12-08 * \license See above. * * We need to have a way to get all of the JACK information of * the midi_jack module. This module provides that information. * * GitHub issue #165: enabled a build and run with no JACK support. */ #include "seq66-config.h" #if defined SEQ66_JACK_SUPPORT #include /* JACK (2) API */ #include "midi_info.hpp" /* seq66::midi_port_info etc. */ #include "midi/midibus.hpp" /* seq66::midibus */ /* * This feature works, but if both the port-connect and port-registration * callbacks are defined, they interfere with each other. We really need to * know only that a new port has registered with the system. */ #undef SEQ66_JACK_PORT_CONNECT_CALLBACK namespace seq66 { class mastermidibus; class midi_jack; /** * The class for handling JACK MIDI port enumeration. */ class midi_jack_info final : public midi_info { friend class midi_jack; friend int jack_process_io (jack_nframes_t nframes, void * arg); friend void jack_port_register_callback ( jack_port_id_t portid, int regv, void * arg ); private: using portlist = std::vector; /** * Holds the port data. This list is iterated in the input and output * portions of the JACK process callback. This class does not own the * pointers. */ portlist m_jack_ports; /** * Holds the JACK sequencer client pointer so that it can be used * by the midibus objects. This is actually an opaque pointer; there is * no way to get the actual fields in this structure; they can only be * accessed through functions in the JACK API. Note that it is also * stored as a void pointer in midi_info::m_midi_handle. */ jack_client_t * m_jack_client; /** * jack_nframes_t jack_get_buffer_size(jack_client_t *) * * Returns the current maximum size that is passed to the process callback. * It should only be used before the client has been activated. * This size may change, clients that depend on it must register a * bufsize callback to be notified if it does. * * Part of issue #100. */ jack_nframes_t m_jack_buffer_size; /** * jack_nframes_t jack_get_sample_rate(jack_client_t *) * * Returns the sample rate of the JACK system as set by the user when jackd * started. * * Part of issue #100. */ jack_nframes_t m_jack_sample_rate; public: midi_jack_info () = delete; midi_jack_info (const std::string & appname, int ppqn, midibpm bpm); virtual ~midi_jack_info (); /** * This is the platform-specific version of midi_handle(). */ jack_client_t * client_handle () { return m_jack_client; } int jack_buffer_size () const { return int(m_jack_buffer_size); } int jack_sample_rate () const { return int(m_jack_sample_rate); } virtual bool api_get_midi_event (event * inev) override; virtual bool api_connect () override; virtual int api_poll_for_midi () override; virtual void api_set_ppqn (int p) override; virtual void api_set_beats_per_minute (midibpm b) override; virtual void api_port_start ( mastermidibus & masterbus, int bus, int port ) override; /** * Flushes our local queue events out into JACK. This is also a * midi_jack function that's never called. This function is called a * lot. */ virtual void api_flush () override { // no code } private: virtual int get_all_port_info ( midi_port_info & inports, midi_port_info & outports ) override; std::string get_port_alias_by_name (const std::string & name); /** * \getter m_jack_client * This is the platform-specific version of midi_handle(). */ void client_handle (jack_client_t * j) { m_jack_client = j; } jack_client_t * connect (); void disconnect (); void extract_names ( const std::string & fullname, std::string & clientname, std::string & portname ); private: void update_port_list ( bool is_my_port, int portid, bool registration, const std::string & shortname, const std::string & longname ); #if defined SEQ66_MIDI_PORT_REFRESH midi_jack * lookup_midi_jack ( const std::string & shortname, const std::string & longname = "" ); #endif void show_details () const; const portlist & jack_ports () const { return m_jack_ports; } portlist & jack_ports () { return m_jack_ports; } int count () const { return int(m_jack_ports.size()); } /** * Adds a pointer to a JACK port. */ bool add (midi_jack & mj) { jack_ports().push_back(&mj); return true; } }; // midi_jack_info /* * Free functions in the seq66 namespace */ extern void silence_jack_errors (bool silent = true); extern void silence_jack_info (bool silent = true); extern bool detect_jack (bool forcecheck = false); } // namespace seq66 #endif // SEQ66_JACK_SUPPORT #endif // SEQ66_MIDI_JACK_INFO_HPP /* * midi_jack_info.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/midi_probe.hpp ================================================ #if ! defined SEQ66_MIDI_PROBE_HPP #define SEQ66_MIDI_PROBE_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_probe.hpp * * Functions for testing and probing the MIDI support. * * \library seq66 application * \author Gary P. Scavone; refactoring by Chris Ahlstrom * \date 2016-11-19 * \updates 2017-01-11 * \license See above. * */ #include #include "rtmidi_info.hpp" namespace seq66 { extern std::string midi_api_name (int i); extern int midi_probe (); extern bool midi_input_test (rtmidi_info & info, int portindex); } // namespace seq66 #endif // SEQ66_MIDI_PROBE_HPP /* * midi_probe.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/midibus_rm.hpp ================================================ #if ! defined SEQ66_MIDIBUS_RM_HPP #define SEQ66_MIDIBUS_RM_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midibus_rm.hpp * * This module declares/defines the base class for MIDI I/O for Linux, Mac, * and Windows, using a refactored "RtMidi" library. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-11-21 * \updates 2025-04-30 * \license GNU GPLv2 or above * * This midibus module is the RtMidi version of the midibus * module. */ #include "midi/midibase.hpp" /* seq66::midibase class (new) */ #include "rtmidi_types.hpp" /* midibase::port::normal */ namespace seq66 { class event; class rtmidi; class rtmidi_info; /** * This class implements with rtmidi version of the midibus object. */ class midibus final : public midibase { /** * The master MIDI bus sets up the buss, so it gets access to private * details. */ friend class mastermidibus; private: /** * The rtmidi API interface input or output object this midibus will be * creating and then used. TODO: This needs to be an std::unique_ptr(). */ rtmidi * m_rt_midi; /** * For Seq66, the ALSA model used requires that all the midibus objects * use the same ALSA sequencer "handle". The rtmidi_info object used for * enumerating the ports is a good place to get this handle. It is an * extension of the legacy RtMidi interface. */ rtmidi_info & m_master_info; public: /* * Virtual-port and non-virtual-port constructor. */ midibus ( rtmidi_info & rt, int index, midibase::io iotype = io::output, midibase::port porttype = port::normal, int bussoverride = null_buss() ); virtual ~midibus (); /* virtual */ bool api_connect (); /* cannot be overridden */ bool good_api () const; private: const rtmidi_info & master_info () const { return m_master_info; } rtmidi_info & master_info () { return m_master_info; } protected: virtual bool api_init_in () override; virtual bool api_init_in_sub () override; virtual bool api_init_out () override; virtual bool api_init_out_sub () override; virtual bool api_deinit_out () override; virtual bool api_deinit_in () override; virtual bool api_get_midi_event (event * inev) override; virtual int api_poll_for_midi () override; virtual void api_continue_from (midipulse tick, midipulse beats) override; virtual void api_start () override; virtual void api_stop () override; virtual void api_clock (midipulse tick) override; virtual void api_play (const event * e24, midibyte channel) override; virtual void api_sysex (const event * e24) override; }; // class midibus (rtmidi version) } // namespace seq66 #endif // SEQ66_MIDIBUS_RM_HPP /* * midibus_rm.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/rterror.hpp ================================================ #if ! defined SEQ66_RTERROR_HPP #define SEQ66_RTERROR_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file rterror.hpp * * An abstract base class for MIDI error handling. * * \library seq66 application * \author Gary P. Scavone; refactoring by Chris Ahlstrom * \date 2016-11-14 * \updates 2022-05-14 * \license See above. * */ #include /* std::exception base class */ #include /* std::string */ #include "util/basic_macros.hpp" /* infoprint() */ namespace seq66 { /** * Exception handling class for our version of "rtmidi". The rterror class is * quite simple but it does allow errors to be "caught" by rterror::kind. * * Please note that, in this refactoring of rtmidi, we've done away with all * the exception specifications, on the advice of Herb Sutter. They may be * more relevent to C++11 and beyond, but this library is too small to worry * about them, for now. */ class rterror : public std::exception { public: enum class kind { warning, /**< A non-critical error. */ debug_warning, /**< Non-critical error 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, /**< Invalid parameter 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. */ max /**< An "illegal" value for range-checking */ }; private: /** * Holds the latest message information for the exception. */ std::string m_message; /** * Holds the type or severity of the exception. */ kind m_type; public: rterror (const std::string & message, kind errtype = kind::unspecified) : m_message (message), m_type (errtype) { // no code } virtual ~rterror () { // no code } /** * Prints thrown error message to stderr. */ virtual void print_message () const { infoprint(m_message); } /** * Returns the thrown error message type. */ virtual const kind & getType () const { return m_type; } /** * Returns the thrown error message string. */ virtual const std::string & get_message () const { return m_message; } /** * Returns the thrown error message as a c-style string. */ virtual const char * what () const noexcept override { return m_message.c_str(); } }; /** * rtmidi error callback function prototype. * * Note that class behaviour is undefined after a critical error (not * a warning) is reported. * * \param errtype * Type of error. * * \param errormsg * Error description. */ using rterror_callback = void (*) ( rterror::kind errtype, const std::string & errormsg, void * userdata ); } // namespace seq66 #endif // SEQ66_RTERROR_HPP /* * rterror.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/rtmidi.hpp ================================================ #if ! defined SEQ66_RTMIDI_HPP #define SEQ66_RTMIDI_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file rtmidi.hpp * * An abstract base class for realtime MIDI input/output. * * \library seq66 application * \author Gary P. Scavone; refactoring by Chris Ahlstrom * \date 2016-11-14 * \updates 2025-01-20 * \license See above. * * The big difference between this class (seq66::rtmidi) and * seq66::rtmidi_info is that it gets information via midi_api-derived * functions, while the latter gets if via midi_api_info-derived functions. */ #include #include "midi_api.hpp" /* seq66::midi[_in][_out]_api */ #include "rtmidi_types.hpp" /* seq66::rtmidi_api etc. */ #include "rtmidi_info.hpp" /* seq66::rtmidi_info */ namespace seq66 { /** * The main class of the rtmidi API. We moved the enum Api definition into * the new rtmidi_types.hpp module to make refactoring the code easier. */ class rtmidi : public midi_api { friend class midibus; private: /** * Holds a reference to the "global" midi_info wrapper object. * Unlike the original RtMidi library, this library separates the * port-enumeration code ("info") from the port-usage code ("api"). * * We might make it a static object at some point. */ rtmidi_info & m_rtmidi_info; /** * Points to the API I/O object (e.g. midi_alsa or midi_jack) for which * this class is a wrapper. */ midi_api * m_midi_api; protected: rtmidi (midibus & parentbus, rtmidi_info & info); virtual ~rtmidi (); public: virtual bool api_connect () override { return get_api()->api_connect(); } virtual void api_play (const event * e24, midibyte channel) override { get_api()->api_play(e24, channel); } virtual void api_continue_from (midipulse tick, midipulse beats) override { get_api()->api_continue_from(tick, beats); } virtual void api_start () override { get_api()->api_start(); } virtual void api_stop () override { get_api()->api_stop(); } virtual void api_clock (midipulse tick) override { get_api()->api_clock(tick); } virtual void api_set_ppqn (int ppqn) override { get_api()->api_set_ppqn(ppqn); } virtual void api_set_beats_per_minute (midibpm bpm) override { get_api()->api_set_beats_per_minute(bpm); } virtual bool api_init_out () override { return get_api()->api_init_out(); } virtual bool api_init_out_sub () override { return get_api()->api_init_out_sub(); } virtual bool api_init_in () override { return get_api()->api_init_in(); } virtual bool api_init_in_sub () override { return get_api()->api_init_in_sub(); } virtual bool api_deinit_out () override { return get_api()->api_deinit_out(); } virtual bool api_deinit_in () override { return get_api()->api_deinit_in(); } virtual bool api_get_midi_event (event * inev) override { return get_api()->api_get_midi_event(inev); } virtual int api_poll_for_midi () override { return get_api()->api_poll_for_midi(); } virtual void api_sysex (const event * e24) override { get_api()->api_sysex(e24); } virtual void api_flush () override { get_api()->api_flush(); } public: /** * Returns true if a port is open and false if not. */ virtual bool is_port_open () const { return get_api()->is_port_open(); } virtual std::string get_port_name () { return parent_bus().port_name(); /* get_api()->port_name() */ } virtual std::string get_port_alias () { return parent_bus().port_name(); /* get_api()->port_alias() */ } int get_port_count () { return m_rtmidi_info.get_port_count(); } /** * \return * Returns the sum of the number of input and output ports. */ int full_port_count () { return m_rtmidi_info.full_port_count(); } const midi_api * get_api () const { return m_midi_api; } midi_api * get_api () { return m_midi_api; } bool have_api () const { return not_nullptr(m_midi_api); } /* * Pass-alongs to the parent bus for this midi_api-derived object. * More are already defined above, as well. */ void set_bus_id (int id) { parent_bus().set_bus_id(id); } void set_port_id (int id) { parent_bus().set_port_id(id); } std::string connect_name () const { return parent_bus().connect_name(); } protected: void set_api (midi_api * ma) { if (not_nullptr(ma)) m_midi_api = ma; } void delete_api () { if (not_nullptr(m_midi_api)) { delete m_midi_api; m_midi_api = nullptr; } } }; // class rtmidi /** * 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 get_message() 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. */ class rtmidi_in : public rtmidi { public: rtmidi_in (midibus & parentbus, rtmidi_info & info); virtual ~rtmidi_in (); protected: void openmidi_api (rtmidi_api api, rtmidi_info & info); }; /** * 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. */ class rtmidi_out : public rtmidi { public: rtmidi_out (midibus & parentbus, rtmidi_info & info); /** * The destructor closes any open MIDI connections. */ virtual ~rtmidi_out (); protected: void openmidi_api (rtmidi_api api, rtmidi_info & info); }; // class rtmidi_out } // namespace seq66 #endif // SEQ66_RTMIDI_HPP /* * rtmidi.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/rtmidi_info.hpp ================================================ #if ! defined SEQ66_RTMIDI_INFO_HPP #define SEQ66_RTMIDI_INFO_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file rtmidi_info.hpp * * A base class for enumerating MIDI clients and ports. * * \library seq66 application * \author Refactoring by Chris Ahlstrom * \date 2016-12-08 * \updates 2022-03-26 * \license GNU GPLv2 or above * * This class is like the rtmidi_in and rtmidi_out classes, but cut down to * the interface functions needed to enumerate clients and ports. It is a * wrapper/selector for the new midi_info class and its children. */ #include "midi_api.hpp" /* seq66::midi[_in][_out]_api */ #include "midi_info.hpp" namespace seq66 { class mastermidibus; /** * A class for enumerating MIDI clients and ports. New, but ripe for * refactoring nonetheless. */ class rtmidi_info { friend class mastermidibus; friend class midibus; friend class rtmidi_in; friend class rtmidi_out; private: /** * Provides access to the selected API (currently only JACK or ALSA). */ midi_info * m_info_api; /** * To save repeated queries, we save this value. Its default value is * rtmidi_api::unspecified. The enum class rtmidi_api is defined in * rtmidi_types.hpp */ static rtmidi_api sm_selected_api; public: rtmidi_info ( rtmidi_api api, const std::string & appname, int ppqn, midibpm bpm ); virtual ~rtmidi_info (); /* * A static function to determine the current rtmidi version. */ static std::string get_version (); /* * 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 get_compiled_api (rtmidi_api_list & apis); /** * Sets the input or output mode for getting data. */ bool midi_mode () const { return get_api_info()->midi_mode(); } /** * Sets the input or output mode for getting data. The flag is true to * set up for input processing. */ void midi_mode (bool flag) { get_api_info()->midi_mode(flag); } void midi_mode (midibase::io iotype) { midi_mode(iotype == midibase::io::input); } /** * Clear the MIDI port container. */ void clear () { get_api_info()->clear(); } /** * Add midibus information to the input ports. Also adds the midibus to * a list of busses to connect in mastermidibus. This function is meant * for virtual ports. */ void add_input (const midibus * m) { get_api_info()->input_ports().add(m); add_bus(m); } /** * Add midibus information to the output ports. Also adds the midibus to * a list of busses to connect in mastermidibus. This function is meant * for virtual ports. */ void add_output (const midibus * m) { get_api_info()->output_ports().add(m); add_bus(m); } /** * Adds the bus to a list of busses to be connected by the API at the * right time (currently applies only to JACK). See the calls to this * function in mastermidibus. */ void add_bus (const midibus * m) { get_api_info()->add_bus(m); } /** * Gets the buss/client ID for a MIDI interfaces. This is the left-hand * side of a X:Y pair (such as 128:0). * * This function is a new part of the RtMidi interface. * * \param index * The ordinal index of the desired interface to look up. * * \return * Returns the buss/client value as provided by the selected API. */ int get_bus_id (int index) const { return get_api_info()->get_bus_id(index); } std::string get_bus_name (int index) const { return get_api_info()->get_bus_name(index); } int get_port_count () const { return get_api_info()->get_port_count(); } int full_port_count () const { return get_api_info()->full_port_count(); } int get_port_id (int index) const { return get_api_info()->get_port_id(index); } std::string get_port_name (int index) const { return get_api_info()->get_port_name(index); } std::string get_port_alias (int index) const { return get_api_info()->get_port_alias(index); } bool get_input (int index) const { return get_api_info()->get_input(index); } bool get_virtual (int index) const { return get_api_info()->get_virtual(index); } bool get_system (int index) const { return get_api_info()->get_system(index); } int get_all_port_info () { return get_api_info()->get_all_port_info(); } int get_all_port_info (midi_port_info & in, midi_port_info & out) { return get_api_info()->get_all_port_info(in, out); } int queue_number (int index) const { return get_api_info()->queue_number(index); } const std::string & app_name () const { return get_api_info()->app_name(); } int global_queue () const { return get_api_info()->global_queue(); } int ppqn () const { return get_api_info()->ppqn(); } void api_set_ppqn (int p) { get_api_info()->api_set_ppqn(p); } midibpm bpm () const { return get_api_info()->bpm(); } void api_set_beats_per_minute (midibpm b) { return get_api_info()->api_set_beats_per_minute(b); } void api_port_start (mastermidibus & masterbus, int bus, int port) { get_api_info()->api_port_start(masterbus, bus, port); } /* * There is no need for a corresponding port-exit function, because * the functionality in it is not API-specific. * * (Having second thoughts about this statement.) * * void api_port_exit (int client, int port) * { * get_api_info()->api_port_exit(client, port); * } */ bool api_get_midi_event (event * inev) { return get_api_info()->api_get_midi_event(inev); } void api_flush () { get_api_info()->api_flush(); } int api_poll_for_midi () { return get_api_info()->api_poll_for_midi(); } static rtmidi_api & selected_api () { return sm_selected_api; } const midi_info * get_api_info () const { return m_info_api; } midi_info * get_api_info () { return m_info_api; } protected: bool api_connect () { return get_api_info()->api_connect(); } static void selected_api (const rtmidi_api & api) { sm_selected_api = api; } /** * This function also checks the pointer and returns false if it is not * valid. This feature is important to allow a missing API (e.g. the * JACK server is not running) to be detected. */ bool set_api_info (midi_info * ma) { bool result = not_nullptr(ma); if (result) { result = not_nullptr(ma->midi_handle()); if (result) m_info_api = ma; } return result; } void delete_api () { if (not_nullptr(m_info_api)) { delete m_info_api; m_info_api = nullptr; } } protected: bool openmidi_api ( rtmidi_api api, const std::string & appname, int ppqn, midibpm bpm ); }; // class rtmidi_info } // namespace seq66 #endif // SEQ66_RTMIDI_INFO_HPP /* * rtmidi_info.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/rtmidi_types.hpp ================================================ #if ! defined SEQ66_RTMIDI_TYPES_HPP #define SEQ66_RTMIDI_TYPES_HPP /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file rtmidi_types.hpp * * Type definitions pulled out for the needs of the refactoring. * * \library seq66 application * \author Gary P. Scavone; severe refactoring by Chris Ahlstrom * \date 2016-11-20 * \updates 2023-12-03 * \license See above. * * The lack of hiding of these types within a class is a little to be * regretted. On the other hand, it does make the code much easier to * refactor and partition, and slightly easier to read. */ #include /* std::string */ #include /* std::vector container */ #include "midi/event.hpp" /* seq66::event namespace */ #include "midi/midibytes.hpp" /* seq66::midibyte alias */ /** * This was the version of the RtMidi library from which this reimplementation * was forked. However, the divergence from RtMidi by this library is now * very great... only the idea of selecting the MIDI API at runtime, and the * queuing and call-back mechanism, have been preserved. */ #define SEQ66_RTMIDI_VERSION "2.1.1" /* the revision at fork time */ /** * Define for checking JACK/rtmidi latency. */ #if defined SEQ66_PLATFORM_DEBUG_TMI #define SEQ66_SHOW_TIMING #endif namespace seq66 { /** * Default size of the MIDI queue. */ const int c_default_queue_size = 100; /** * MIDI API specifier arguments. These items used to be nested in * the rtmidi class, but that only worked when RtMidi.cpp/h were * large monolithic modules. */ enum class rtmidi_api { unspecified, /**< Search for a working compiled API. */ alsa, /**< Advanced Linux Sound Architecture API. */ jack, /**< JACK Low-Latency MIDI Server API. */ #if defined SEQ66_USE_RTMIDI_API_ALL /* * Not supported until we get a simplified seq66-friendly API worked out. * Windows (and presumably OSX) is currently supported by the Seq66 * portmidi library. */ macosx_core, /**< Macintosh OS-X Core Midi API. */ windows_mm, /**< Microsoft Multimedia MIDI API. */ dummy, /**< A compilable but non-functional API. */ #endif max /**< A count of APIs; an erroneous value. */ }; /** * Provides a container of API values. */ using rtmidi_api_list = std::vector; /* * Inline functions. */ inline rtmidi_api int_to_api (int index) { return index < static_cast(rtmidi_api::max) ? static_cast(index) : rtmidi_api::max ; } inline int api_to_int (rtmidi_api api) { return static_cast(api); } /** * Provides a handy capsule for a MIDI message, based on the * std::vector data type from the RtMidi project. * * For issue #100, we add the timestamp (in units of MIDI ticks, also * known as pulses) to the data. We then provide functions to * handle the array of data in two different ways: * * -# Buffer: Access the data buffer for all bytes, in order to put * them on the JACK ringbuffer for the process callback to use: * - MIDI timestamp bytes (4) * - Status byte * - Data bytes * -# Event: Access the status and data bytes as a unit to pass them * to the JACK engine for transmitting. * * Please note that the ALSA module in seq66's rtmidi infrastructure * uses the seq66::event rather than the seq66::midi_message object. * For the moment, we will translate between them until we have the * interactions between the old and new modules under control. */ class midi_message { public: /** * Holds the data of the MIDI message. Callers should use * midi_message::container rather than using the vector directly. * Bytes are added by the push() function, and are safely accessed * (with bounds-checking) by operator []. */ using container = std::vector; private: #if defined SEQ66_SHOW_TIMING /** * Provide a static counter to keep track of events. Currently needed for * trouble-shooting. We don't care about wraparound. */ static unsigned sm_msg_number; /** * Provides the message counter value when this event was created. */ unsigned m_msg_number; /** * Holds the "send time" of the message, for testing. In JACK, the * values is typed as jack_time_t, and is in microseconds. */ uint64_t m_msg_send_time; #endif /** * Holds the event status and data bytes. */ container m_bytes; /** * Holds the timestamp of the MIDI message. Non-zero only in the JACK * implementation at present. It can also hold a JACK frame number. The * caller can know this only by context at present. */ midipulse m_timestamp; /** * Holds the ID number of the input MIDI buss on which the message * was received. Note that this is an index number. Starts out * as a null-buss value. */ bussbyte m_input_buss; public: midi_message (midipulse ts = 0); midi_message (const midibyte * mbs, std::size_t sz); midi_message (const midi_message & rhs) = default; midi_message & operator = (const midi_message & rhs) = default; ~midi_message () = default; midibyte & operator [] (std::size_t i) { static midibyte s_zero = 0; return (i < m_bytes.size()) ? m_bytes[i] : s_zero ; } const midibyte & operator [] (std::size_t i) const { static midibyte s_zero = 0; return (i < m_bytes.size()) ? m_bytes[i] : s_zero ; } const char * buffer () const // was "array" { return reinterpret_cast(&m_bytes[0]); } const midibyte * event_bytes () const // bypasses timestamp { return m_bytes.data(); } #if defined SEQ66_SHOW_TIMING unsigned msg_number () const { return m_msg_number; } uint64_t msg_send_time () const { return m_msg_send_time; } void msg_send_time (uint64_t t) { m_msg_send_time = t; } #endif bool empty () const { return event_count() == 0; } int event_count () const // was "count" { return int(m_bytes.size()); } void push (midibyte b) { m_bytes.push_back(b); } midipulse timestamp () const { return m_timestamp; } void timestamp (midipulse t) { m_timestamp = t; } bussbyte input_buss () const { return m_input_buss; } void input_buss (bussbyte b) { m_input_buss = b; } midibyte status () const { return event_count() > 0 ? m_bytes[0] : 0 ; } bool is_sysex () const { return m_bytes.size() > 0 ? event::is_sysex_msg(m_bytes[0]) : false ; } std::string to_string () const; private: }; // class midi_message /** * MIDI caller callback function type definition. Used to be nested in the * rtmidi_in class. The timestamp parameter has been folded into the * midi_message class (a wrapper for std::vector), and the * pointer has been replaced by a reference. */ using rtmidi_callback_t = void (*) ( midi_message & message, /* includes the timestamp already */ void * userdata ); /** * Provides a queue of midi_message structures. This entity used to be a * plain structure nested in the midi_in_api class. We made it a class to * encapsulate some common operations to save a burden on the callers. */ class midi_queue { private: unsigned m_front; unsigned m_back; unsigned m_size; unsigned m_ring_size; midi_message * m_ring; public: midi_queue (); ~midi_queue (); bool empty () const { return m_size == 0; } int count () const { return int(m_size); } bool full () const { return m_size == m_ring_size; } const midi_message & front () const { return m_ring[m_front]; } bool add (const midi_message & mmsg); void pop (); midi_message pop_front (); void allocate (unsigned queuesize = c_default_queue_size); void deallocate (); }; // class midi_queue /** * The rtmidi_in_data structure is used to pass private class data to the * MIDI input handling function or thread. Used to be nested in the * rtmidi_in class. */ class rtmidi_in_data { private: /** * Provides a queue of MIDI messages. Used when not using a JACK callback * for MIDI input. */ midi_queue m_queue; /** * A one-time flag that starts out true and is falsified when the first * MIDI messages comes in to this port. It simply resets the delta JACK * time. */ bool m_first_message; /** * Indicates that SysEx is still coming in. */ bool m_continue_sysex; public: rtmidi_in_data (); const midi_queue & queue () const { return m_queue; } midi_queue & queue () { return m_queue; } bool first_message () const { return m_first_message; } void first_message (bool flag) { m_first_message = flag; } bool continue_sysex () const { return m_continue_sysex; } void continue_sysex (bool flag) { m_continue_sysex = flag; } }; // class rtmidi_in_data } // namespace seq66 #endif // SEQ66_RTMIDI_TYPES_HPP /* * rtmidi_types.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/include/seq66_rtmidi_features.h ================================================ #ifndef SEQ66_RTMIDI_FEATURES_H #define SEQ66_RTMIDI_FEATURES_H /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file seq66_rtmidi_features.h * * This module defines configure and build-time * options available for Seq66's RtMidi-derived implementation. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-11-19 * \updates 2024-01-10 * \license GNU GPLv2 or above * * For now, this header file enables only the JACK interface. That is our * main interest, adding native JACK support to Seq66. */ #include "seq66_platform_macros.h" #include "seq66-config.h" /** * Macros to enable the implementations that are supported under Linux. */ #if defined SEQ66_PLATFORM_LINUX || defined SEQ66_PLATFORM_FREEBSD #define SEQ66_BUILD_UNIX_JACK /* supported by Linux & FreeBSD */ #define SEQ66_BUILD_LINUX_ALSA /* also a wrapper for FreeBSD's OSS */ #define SEQ66_BUILD_RTMIDI_DUMMY /* an alternative for Linux, etc. */ #undef SEQ66_AVOID_TIMESTAMPING /* a feature of the ALSA rtmidi API */ #endif #if defined SEQ66_PLATFORM_MACOSX #define SEQ66_BUILD_MACOSX_CORE #define SEQ66_BUILD_UNIX_JACK #define SEQ66_BUILD_RTMIDI_DUMMY /* an alternative for OSX, etc. */ #endif #endif // SEQ66_RTMIDI_FEATURES_H /* * seq66_rtmidi_features.h * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/seq_rtmidi.pro ================================================ #****************************************************************************** # seq_rtmidi.pro (pseq66) #------------------------------------------------------------------------------ ## # \file seq_rtmidi.pro # \library qpseq66 application # \author Chris Ahlstrom # \date 2020-05-29 # \update 2022-09-13 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # Created by and for Qt Creator. This file was created for editing the project # sources only. You may attempt to use it for building too, by modifying this # file here. # # Important: # # This project file is designed only for Qt 5 (and above?). # #------------------------------------------------------------------------------ message($$_PRO_FILE_PWD_) TEMPLATE = lib CONFIG += staticlib config_prl qtc_runnable TARGET = seq_rtmidi # These are needed to set up seq66_platform_macros: CONFIG(debug, debug|release) { DEFINES += DEBUG } else { DEFINES += NDEBUG } DEFINES += "SEQ66_MIDILIB=rtmidi" DEFINES += "SEQ66_RTMIDI_SUPPORT=1" HEADERS += \ include/mastermidibus_rm.hpp \ include/midibus_rm.hpp \ include/midi_alsa.hpp \ include/midi_alsa_info.hpp \ include/midi_api.hpp \ include/midi_info.hpp \ include/midi_jack.hpp \ include/midi_jack_data.hpp \ include/midi_jack_info.hpp \ include/midi_probe.hpp \ include/rterror.hpp \ include/rtmidi.hpp \ include/rtmidi_info.hpp \ include/seq66_rtmidi_features.h # Mac OSX and Windows currently are not supported by the internal rtmidi # library. Use the portmidi build, not the rtmidi build. SOURCES += \ src/mastermidibus.cpp \ src/midibus.cpp \ src/midi_alsa.cpp \ src/midi_alsa_info.cpp \ src/midi_api.cpp \ src/midi_info.cpp \ src/midi_jack.cpp \ src/midi_jack_data.cpp \ src/midi_jack_info.cpp \ src/midi_probe.cpp \ src/rtmidi.cpp \ src/rtmidi_info.cpp \ src/rtmidi_types.cpp # Note that the seq66-config.h file in ../include/qt/rtmidi, though based on a # bootstrap of the release mode of the GNU Autotools-built version of # seq66-config.h, is hardwired and must be updated/edited manually. This is # the simplest way to provide a Qt-only rtmidi build for those who don't wish # to install autotools. INCLUDEPATH = ../include/qt/rtmidi ../libseq66/include include #****************************************************************************** # seq_rtmidi.pro (qpseq66) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: seq_rtmidi/src/Makefile.am ================================================ #****************************************************************************** # Makefile.am (seq_rtmidi/src) #------------------------------------------------------------------------------ ## # \file Makefile.am # \library seq_rtmidi library # \author Chris Ahlstrom # \date 2016-11-19 # \update 2022-09-13 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # # This module provides an Automake makefile for the seq_rtmidi C/C++ # library. # #------------------------------------------------------------------------------ #***************************************************************************** # Packing/cleaning targets #----------------------------------------------------------------------------- AUTOMAKE_OPTIONS = foreign dist-zip dist-bzip2 MAINTAINERCLEANFILES = Makefile.in Makefile $(AUX_DIST) #****************************************************************************** # CLEANFILES #------------------------------------------------------------------------------ CLEANFILES = *.gc* MOSTLYCLEANFILES = *~ #****************************************************************************** # EXTRA_DIST #------------------------------------------------------------------------------ EXTRA_DIST = #****************************************************************************** # Items from configure.ac #------------------------------------------------------------------------------- PACKAGE = @PACKAGE@ VERSION = @VERSION@ GIT_VERSION = @GIT_VERSION@ SEQ66_API_MAJOR = @SEQ66_API_MAJOR@ SEQ66_API_MINOR = @SEQ66_API_MINOR@ SEQ66_API_PATCH = @SEQ66_API_PATCH@ SEQ66_API_VERSION = @SEQ66_API_VERSION@ SEQ66_LT_CURRENT = @SEQ66_LT_CURRENT@ SEQ66_LT_REVISION = @SEQ66_LT_REVISION@ SEQ66_LT_AGE = @SEQ66_LT_AGE@ SEQ66_LIBTOOL_VERSION = @SEQ66_LIBTOOL_VERSION@ #****************************************************************************** # Install directories #------------------------------------------------------------------------------ prefix = @prefix@ includedir = @seq66includedir@ libdir = @seq66libdir@ datadir = @datadir@ datarootdir = @datarootdir@ seq66includedir = @seq66includedir@ seq66libdir = @seq66libdir@ #****************************************************************************** # localedir #------------------------------------------------------------------------------ # # 'localedir' is the normal system directory for installed localization # files. # #------------------------------------------------------------------------------ localedir = $(datadir)/locale DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ #****************************************************************************** # Local project directories #------------------------------------------------------------------------------ top_srcdir = @top_srcdir@ builddir = @abs_top_builddir@ #***************************************************************************** # libtool #----------------------------------------------------------------------------- version = $(SEQ66_LIBTOOL_VERSION) #***************************************************************************** # git_version #----------------------------------------------------------------------------- # git_version = $(shell git describe --abbrev=7 --always --tags) #----------------------------------------------------------------------------- git_version = $(shell git describe --tags --long) git_branch =$(shell git branch | grep -e ^*) git_info = "$(git_version) $(git_branch)" #****************************************************************************** # Compiler and linker flags # # $(GTKMM_CFLAGS) # #------------------------------------------------------------------------------ AM_CXXFLAGS = \ -I../include \ -I$(top_srcdir)/include \ -I$(top_srcdir)/libseq66/include \ -I$(top_srcdir)/seq_rtmidi/include \ $(ALSA_CFLAGS) \ $(JACK_CFLAGS) \ $(CALLFLAG) \ -DSEQ66_GIT_VERSION=\"$(git_info)\" #****************************************************************************** # The library to build, a libtool-based library #------------------------------------------------------------------------------ lib_LTLIBRARIES = libseq_rtmidi.la #****************************************************************************** # Source files # # We include only the JACK and ALSA support here. # # midi_jack.cpp # #---------------------------------------------------------------------------- libseq_rtmidi_la_SOURCES = \ mastermidibus.cpp \ midibus.cpp \ midi_alsa.cpp \ midi_alsa_info.cpp \ midi_api.cpp \ midi_info.cpp \ midi_jack.cpp \ midi_jack_data.cpp \ midi_jack_info.cpp \ midi_probe.cpp \ rtmidi.cpp \ rtmidi_info.cpp \ rtmidi_types.cpp libseq_rtmidi_la_LDFLAGS = -version-info $(version) libseq_rtmidi_la_LIBADD = $(ALSA_LIBS) $(JACK_LIBS) #****************************************************************************** # uninstall-hook #------------------------------------------------------------------------------ # # We'd like to remove /usr/local/include/seq66-1.0 if it is # empty. However, we don't have a good way to do it yet. # #------------------------------------------------------------------------------ uninstall-hook: @echo "Note: you may want to remove $(libdir) manually" #****************************************************************************** # Makefile.am (seq_rtmidi/src) #------------------------------------------------------------------------------ # vim: ts=3 sw=3 ft=automake #------------------------------------------------------------------------------ ================================================ FILE: seq_rtmidi/src/mastermidibus.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file mastermidibus.cpp * * This module declares/defines the base class for MIDI I/O under the * refactored RtMidi framework. * * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 * \updates 2024-06-04 * \license GNU GPLv2 or above * * This file provides a Windows-only implementation of the mastermidibus * class. There is a lot of common code between these two versions! */ #include "cfg/settings.hpp" /* seq66::rc() */ #include "midi/event.hpp" /* seq66::event */ #include "mastermidibus_rm.hpp" /* seq66::mastermidibus, rtmidi */ #include "midibus_rm.hpp" /* seq66::midibus, rtmidi */ #define SEQ66_USE_JACK_POLLING_FLAG /* until we reconcile ALSA/JACK */ namespace seq66 { /** * The base-class constructor fills the array for our busses. * * \param ppqn * Provides the PPQN value for this object. However, in most cases, the * default value, (-1) to use the default PPQN, should be specified. * * \param bpm * Provides the beats per minute value, which defaults to * c_beats_per_minute. */ mastermidibus::mastermidibus (int ppqn, midibpm bpm) : mastermidibase (ppqn, bpm), m_midi_master /* rtmidi_info */ ( rc().with_jack_midi() ? rtmidi_api::jack : rtmidi_api::alsa, rc().app_client_name(), ppqn, bpm ), m_use_jack_polling (rc().with_jack_midi()) { // Empty body } /** * The destructor deletes all of the output busses, and terminates the * Windows MIDI manager. */ mastermidibus::~mastermidibus () { // Empty body } /** * Initializes the RtMidi implementation. Two different styles are * supported. If the --manual-ports option is in force, then 16 virtual * output ports and one virtual input port are created. They are given names * that make it clear which application (seq66) has set them up. They are * not connected to anything. The user will have to use a connection GUI * (such as qjackctl) or a session manager to make the connections. * * Otherwise, the system MIDI input and output ports are scanned (via the * rtmidi_info member) and passed to the midibus constructor calls. For * every MIDI input port found on the system, this function creates a * corresponding output port, and connects to the system MIDI input. For * example, for an input port found called "qmidiarp:in 1", we want to create * a "shadow" output port called "seq66:qmidiarp in 1". * * Also, as a new feature for 0.98.0, we extract a port alias (JACK only) for * system port names that do not contain the name of the ALSA USB device in * them; the alias might contain that more human-readable name. * * For every MIDI output found on the system this function creates a * corresponding input port, and connects it to the system MIDI output. For * For example, for an output port found called "qmidiarp:out 1", we want to * create a "shadow" input port called "seq66:qmidiarp out 1". * * This code creates a midibus in the conventional manner. Then the * busarray::add() function makes a new businfo object with the desired * "output" and "isvirtual" parameters; the businfo object then decides * whether to call init_in(), init_out(), init_in_sub(), or init_out_sub(). * * Also, the midibus pointers created here are local, but the busarray::add() * function manages them, using the std::unique_ptr<> template. We could use * std::unique_ptr<> here, and even std::make_unique() if we wanted to * require C++14 at this time. * * Are these good conventions, or potentially confusing to users? They match * what the legacy seq66 and seq66 do for ALSA. * * \param ppqn * Provides the (possibly new) value of PPQN to set. ALSA has a function * that sets its idea of the PPQN. JACK, as far as we know, does not. * * \param bpm * Provides the (possibly new) value of BPM (beats per minute) to set. * ALSA has a function that sets its idea of the BPM. JACK, as far as we * know, does not. */ void mastermidibus::api_init (int ppqn, midibpm bpm) { midi_master().api_set_ppqn(ppqn); midi_master().api_set_beats_per_minute(bpm); if (rc().manual_ports()) /* virtual ports */ { bool enable = rc().manual_auto_enable(); int num_buses = rc().manual_port_count(); /* output count */ midi_master().clear(); for (int bus = 0; bus < num_buses; ++bus) /* output busses */ { midibus * m = make_virtual_bus(bus, midibase::io::output); if (not_nullptr(m)) { if (enable) m->set_io_status(enable); midi_master().add_output(m); /* must come 2nd */ } } num_buses = rc().manual_in_port_count(); /* input count */ for (int bus = 0; bus < num_buses; ++bus) /* input busses */ { midibus * m = make_virtual_bus(bus, midibase::io::input); if (not_nullptr(m)) { if (enable) m->set_io_status(enable); midi_master().add_input(m); /* must come 2nd */ } } } else { bool swap_io = midi_master().selected_api() == rtmidi_api::jack; unsigned nports = midi_master().full_port_count(); if (nports > 0) { midibase::io iodirection = swap_io ? midibase::io::output : midibase::io::input ; debug_message("Adding midibus port objects"); midi_master().midi_mode(midibase::io::input); /* ugh! mode! */ unsigned inports = midi_master().get_port_count(); for (unsigned bus = 0; bus < inports; ++bus) { midibus * m = make_normal_bus(bus, iodirection); if (not_nullptr(m)) { midi_master().add_bus(m); /* rtmidi_info */ } } iodirection = swap_io ? midibase::io::input : midibase::io::output ; midi_master().midi_mode(midibase::io::output); /* ugh! mode! */ unsigned outports = midi_master().get_port_count(); for (unsigned bus = 0; bus < outports; ++bus) { midibus * m = make_normal_bus(bus, iodirection); if (not_nullptr(m)) { midi_master().add_bus(m); /* rtmidi_info */ } } } } set_beats_per_minute(bpm); set_ppqn(ppqn); } midibus * mastermidibus::make_virtual_bus (int bus, midibase::io iotype) { midibus * m = new (std::nothrow) midibus ( midi_master(), bus, iotype, midibase::port::manual, bus ); if (not_nullptr(m)) { if (iotype == midibase::io::input) m_inbus_array.add(m, input(bus)); else m_outbus_array.add(m, clock(bus)); } return m; } midibus * mastermidibus::make_normal_bus (int bus, midibase::io iotype) { midibase::port porttype = midibase::port::normal; if (midi_master().get_virtual(bus)) porttype = midibase::port::manual; else if (midi_master().get_system(bus)) porttype = midibase::port::system; midibus * m = new (std::nothrow) midibus ( midi_master(), bus, iotype, porttype, null_buss() ); if (not_nullptr(m)) { set_midi_alias(bus, iotype, m->port_alias()); if (iotype == midibase::io::input) m_inbus_array.add(m, input(bus)); else m_outbus_array.add(m, clock(bus)); } return m; } /** * Activates the mastermidibase code and the rtmidi_info object via its * api_connect() function. */ bool mastermidibus::activate () { bool result = mastermidibase::activate(); if (result) result = midi_master().api_connect(); /* activates, too */ return result; } /** * Initiate a poll() on the existing poll descriptors. This is a * primitive poll, which exits when some data is obtained, or sleeps a * millisecond in note data is obtained. * * For JACK polling, call the base-class implementation: * * - mastermidibase::api_poll_for_midi() * - busarray::poll_for_midi() * - businfo::poll_for_midi() * - midibus::poll_for_midi() [midibase::poll_for_midi()] * - midibase::api_poll_for_midi(), a virtual function overridden * for JACK (and ALSA). * * Otherwise, the call sequence is: * * - rtmidi_info::api_poll_for_midi() * - rtmidi_info::get_api_info()->api_poll_for_midi() * - midi_alsa_info::api_poll_for_midi() * - poll() on the ALSA descriptors; a return > 0 means that number of * events are ready * * Because of some reasons long forgotten, the ALSA "rtmidi" framework here * handles MIDI via the midi_alsa_info object. * * \return * Returns the number of input MIDI events waiting. */ int mastermidibus::api_poll_for_midi () { #if defined SEQ66_USE_JACK_POLLING_FLAG if (m_use_jack_polling) /* --jack-midi set */ return mastermidibase::api_poll_for_midi(); /* inbus-array poll */ else return midi_master().api_poll_for_midi(); /* ALSA poll */ #else return mastermidibase::api_poll_for_midi(); /* inbus-array poll */ #endif } /** * Grab a MIDI event. For the ALSA implementation, this call is ...??? * * \threadsafe */ bool mastermidibus::api_get_midi_event (event * inev) { #if defined SEQ66_USE_JACK_POLLING_FLAG if (m_use_jack_polling) return m_inbus_array.get_midi_event(inev); else return midi_master().api_get_midi_event(inev); #else return m_inbus_array.get_midi_event(inev); #endif } } // namespace seq66 /* * mastermidibus.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/src/midi_alsa.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_alsa.cpp * * This module declares/defines the base class for handling MIDI I/O via * the ALSA system. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-12-18 * \updates 2025-08-10 * \license GNU GPLv2 or above * * This file provides a Linux-only implementation of ALSA MIDI support. * It is derived from the seq_alsamidi implementation in that midibus module. * Note that we are changing the MIDI API somewhat from the original RtMidi * midi_api class; that interface didn't really fit the seq66 model, * and it was getting very painful to warp RtMidi to fit. * * Examples of subscription: * * In ALSA library, subscription is done via snd_seq_subscribe_port() * function. It takes the argument of snd_seq_port_subscribe_t record * pointer. Suppose that you have a client which will receive data from a * MIDI input device. The source and destination addresses are like the * below: * * Capture from keyboard: * * Assume MIDI input port = 64:0, application port = 128:0, and queue for * timestamp = 1 with real-time stamp. The application port must have * capability SND_SEQ_PORT_CAP_WRITE. * \verbatim void capture_keyboard (snd_seq_t * seq) { snd_seq_addr_t sender, dest; snd_seq_port_subscribe_t *subs; sender.client = 64; sender.port = 0; dest.client = 128; dest.port = 0; snd_seq_port_subscribe_alloca(&subs); snd_seq_port_subscribe_set_sender(subs, &sender); snd_seq_port_subscribe_set_dest(subs, &dest); snd_seq_port_subscribe_set_queue(subs, 1); snd_seq_port_subscribe_set_time_update(subs, 1); snd_seq_port_subscribe_set_time_real(subs, 1); snd_seq_subscribe_port(seq, subs); } \endverbatim * * Question: can we simply replace the above with the following code, which * seems to work. * \verbatim * snd_seq_connect_from(seq, 0, 65, 1); // myport, srcclient, srcport \endverbatim * * Output to MIDI device: * * Assume MIDI output port = 65:1 and application port = 128:0. The * application port must have capability SND_SEQ_PORT_CAP_READ. * \verbatim void subscribe_output(snd_seq_t *seq) { snd_seq_addr_t sender, dest; snd_seq_port_subscribe_t *subs; sender.client = 128; sender.port = 0; dest.client = 65; dest.port = 1; snd_seq_port_subscribe_alloca(&subs); snd_seq_port_subscribe_set_sender(subs, &sender); snd_seq_port_subscribe_set_dest(subs, &dest); snd_seq_subscribe_port(seq, subs); } \endverbatim * * This example can be simplified by using the snd_seq_connect_to() * function (as done in the RtMidi library). * \verbatim void subscribe_output(snd_seq_t *seq) { snd_seq_connect_to(seq, 0, 65, 1); // myport, destclient, destport } \endverbatim * * See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html for a wealth of * information on ALSA sequencing. */ #include "cfg/settings.hpp" /* seq66::rc() */ #include "midi/event.hpp" /* seq66::event (MIDI event) */ #include "midibus_rm.hpp" /* seq66::midibus for rtmidi */ #include "midi_alsa.hpp" /* seq66::midi_alsa for ALSA */ #include "midi_info.hpp" /* seq66::midi_info */ namespace seq66 { /* * -------------------------------------------------------------------------- * midi_alsa * -------------------------------------------------------------------------- */ /** * Provides a constructor with client number, port number, ALSA sequencer * support, name of client, name of port, etc., mostly contained within an * already-initialized midi_info object. * * This constructor is the only one that is used for the MIDI input and * output busses, whether the [manual-ports] option is in force or not. * The actual setup of a normal or virtual port is done in the api_*_init_*() * routines. * * Also used for the announce buss, and in the mastermidi_alsa::port_start() * function. There's currently some overlap between local/dest client and * port numbers and the buss and port numbers of the midibase interface. * Also, note that the rcfile module uses the master buss to get the * buss names when it writes the file. * * We get the actual user-client ID from ALSA, then rebuild the descriptive * name for this port. Also have to do it for the parent midibus. We'd like * to use seq_client_name(), but it comes up unresolved by the damned GNU * linker! The obvious fixes don't work! * * ALSA returns "130" as the client ID. That is our ALSA ID, not the ID of * the client we are representing. Thus, we should not set the buss ID and * name of the parent-bus; these have already been determined. ALSA assigns * client IDs from the sequencer handle, and they range from 128 to 191. * * \param parentbus * Provides much of the infor about this ALSA buss. * * \param masterinfo * Provides the information about the desired port, and more. */ midi_alsa::midi_alsa (midibus & parentbus, midi_info & masterinfo) : midi_api (parentbus, masterinfo), m_seq ( reinterpret_cast(masterinfo.midi_handle()) ), m_dest_addr_client (parentbus.bus_id()), m_dest_addr_port (parentbus.port_id()), m_local_addr_client (snd_seq_client_id(m_seq)), /* our client ID */ m_local_addr_port (-1) { set_client_id(m_local_addr_client); set_name(SEQ66_CLIENT_NAME, bus_name(), port_name()); #if defined SEQ66_SHOW_BUS_VALUES parentbus.show_bus_values(); #endif } /** * A rote empty virtual destructor. */ midi_alsa::~midi_alsa () { // empty body } /** * Initialize the MIDI output port. This initialization is done when the * "manual-ports" option is not in force. * * This initialization is like the "open_port()" function of the RtMidi * library, with the addition of the snd_seq_connect_to() call involving * the local and destination ports. * * \tricky * One important thing to note is that this output port is initialized * with the SND_SEQ_PORT_CAP_READ flag, which means this is really an * input port. We connect this input port with a system output port that * was discovered. This is backwards of the way RtMidi does it. * * \return * Returns true unless setting up ALSA MIDI failed in some way. */ bool midi_alsa::api_init_out () { static unsigned s_port_caps = SND_SEQ_PORT_CAP_NO_EXPORT | SND_SEQ_PORT_CAP_READ ; static unsigned s_port_types = SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION ; std::string busname = parent_bus().bus_name(); int rcode = snd_seq_create_simple_port /* create ports */ ( m_seq, busname.c_str(), s_port_caps, s_port_types ); if (rcode < 0) { error_message("ALSA create output port failed"); return false; } else m_local_addr_port = rcode; rcode = snd_seq_connect_to ( m_seq, m_local_addr_port, m_dest_addr_client, m_dest_addr_port ); if (rcode < 0) { msgprintf ( msglevel::error, "ALSA connect to %d:%d error", m_dest_addr_client, m_dest_addr_port ); return false; } else set_port_open(); return true; } /** * Initialize the MIDI input port. * * SND_SEQ_PORT_CAP_SUBS_WRITE: Allow write subscription. * SND_SEQ_PORT_CAP_NO_EXPORT: Routing not allowed. * * \tricky * One important thing to note is that this input port is initialized * with the SND_SEQ_PORT_CAP_WRITE flag, which means this is really an * output port. We connect this output port with a system input port * that was discovered. This is backwards of the way RtMidi does it. * * \return * Returns true unless setting up ALSA MIDI failed in some way. */ bool midi_alsa::api_init_in () { static unsigned s_port_caps = SND_SEQ_PORT_CAP_NO_EXPORT | SND_SEQ_PORT_CAP_WRITE ; static unsigned s_port_types = SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION ; std::string portname = parent_bus().port_name(); int rcode = snd_seq_create_simple_port ( m_seq, portname.c_str(), s_port_caps, s_port_types ); if (rcode < 0) { error_message("ALSA create input port failed"); return false; } else { m_local_addr_port = rcode; } rcode = snd_seq_connect_from ( m_seq, m_local_addr_port, m_dest_addr_client, m_dest_addr_port ); if (rcode < 0) { msgprintf ( msglevel::error, "ALSA connect from %d:%d error", m_dest_addr_client, m_dest_addr_port ); return false; } else set_port_open(); return true; } /** * Gets information directly from ALSA. The problem this function solves is * that the midibus constructor for a virtual ALSA port doesn't not have all * of the information it needs at that point. Here, we can get this * information and get the actual data we need to rename the port to * something accurate. * * \return * Returns true if all of the information could be obtained. If false is * returned, then the caller should not use the side-effects. * * \sideeffect * Passes back the values found. */ bool midi_alsa::set_virtual_name (int portid, const std::string & portname) { bool result = not_nullptr(m_seq); if (result) { snd_seq_client_info_t * cinfo; /* client info */ snd_seq_client_info_alloca(&cinfo); /* a macro function */ int r = snd_seq_get_client_info(m_seq, cinfo); /* filled! */ result = r == 0; if (result) { int cid = snd_seq_client_info_get_client(cinfo); const char * cname = snd_seq_client_info_get_name(cinfo); result = not_nullptr(cname); if (result) { std::string clientname = cname; set_port_id(portid); port_name(portname); set_bus_id(cid); set_name(rc().app_client_name(), clientname, portname); /* * The functions above forward the calls to parent_bus(). * This calls midibase::set_name(); it supports aliases * for normal ports. * * parent_bus().set_name * ( * rc().app_client_name(), clientname, portname * ); */ } } else error_message("ALSA get client info failed"); } return result; } /** * Initialize the output in a different way. This version of initialization * is used by mastermidi_alsa in the "manual-ports" clause. This code * is also very similar to the same function in the * midibus::api_init_out_sub() function of midibus::api_init_out_sub(). * * \return * Returns true unless setting up the ALSA port failed in some way. */ bool midi_alsa::api_init_out_sub () { static unsigned s_port_caps = SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ ; static unsigned s_port_types = SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION ; std::string portname = port_name(); if (portname.empty()) portname = rc().app_client_name() + " out"; int result = snd_seq_create_simple_port /* create ports */ ( m_seq, portname.c_str(), s_port_caps, s_port_types ); m_local_addr_port = result; if (result < 0) { error_message("ALSA create output virtual port failed"); return false; } else { set_virtual_name(result, portname); set_port_open(); } return true; } /** * Initialize the output in a different way. This creates a "virtual" * (i.e. "manual") input port. The meanings of the CAP flags are: * * SND_SEQ_PORT_CAP_WRITE * * The (input) port can be written to by other clientsh. * * SND_SEQ_PORT_CAP_SUBS_WRITE * * Subscription to the input port is allowed. * * \return * Returns true unless setting up the ALSA port failed in some way. */ bool midi_alsa::api_init_in_sub () { static unsigned s_port_caps = SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE ; static unsigned s_port_types = SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION ; std::string portname = port_name(); if (portname.empty()) portname = rc().app_client_name() + " midi in"; int result = snd_seq_create_simple_port /* create ports */ ( m_seq, portname.c_str(), s_port_caps, s_port_types ); m_local_addr_port = result; if (result < 0) { error_message("ALSA create input virtual port failed"); return false; } else { set_virtual_name(result, portname); set_port_open(); } return true; } /** * Not used. */ bool midi_alsa::api_deinit_out () { return true; } /** * Deinitialize the MIDI input. Set the input and the output ports. * The destination port is actually our local port. * * \return * Returns true, unless an error occurs. */ bool midi_alsa::api_deinit_in () { snd_seq_port_subscribe_t * subs; snd_seq_port_subscribe_alloca(&subs); snd_seq_addr_t sender; /* output */ sender.client = m_dest_addr_client; sender.port = m_dest_addr_port; snd_seq_port_subscribe_set_sender(subs, &sender); snd_seq_addr_t dest; /* input */ dest.client = m_local_addr_client; dest.port = m_local_addr_port; snd_seq_port_subscribe_set_dest(subs, &dest); /* * This would seem to unsubscribe all ports. True? Danger? */ int queue = parent_bus().queue_number(); snd_seq_port_subscribe_set_queue(subs, queue); snd_seq_port_subscribe_set_time_update(subs, queue); /* get ticks */ int rc = snd_seq_unsubscribe_port(m_seq, subs); /* unsubscribe */ if (rc < 0) { msgprintf ( msglevel::error, "ALSA unsubscribe port %d:%d error", m_dest_addr_client, m_dest_addr_port ); return false; } return true; } /** * Trying to rearrange the ALSA code to be more like the JACK code. * However, currently midi_alsa_info::api_connect() is what is called, and * it currently doesn't do anything. */ bool midi_alsa::api_connect () { return true; } /** * Defines the size of the MIDI event buffer, which should be large enough to * accomodate the largest MIDI message to be encoded. A local define for * visibility. Also provided, but not yet used, is a size for SysEx events, * which we don't handle, but want to note here. Inspired by Qtractor code. * Also in Qtractor, the same snd_midi_event_t object is used over and over, * rather than being recreated/destroyed for every event-play by * snd_midi_event_new() and snd_midi_event_free(). * * Unused: * * static const size_t s_sysex_size_max = 512; // Hydrogen uses 32 * w/input */ static const size_t s_event_size_max = 12; /** * This play() function takes a native event, encodes it to an ALSA MIDI * sequencer event, sets the broadcasting to the subscribers, sets the * direct-passing mode to send the event without queueing, and puts it in the * queue. * * \threadsafe * * \param e24 * The event to be played on this bus. For speed, we don't bother to * check the pointer. * * \param channel * The channel of the playback. This channel is either the global MIDI * channel of the sequence, or the channel of the event. Either way, we * mask it into the event status. */ void midi_alsa::api_play (const event * e24, midibyte channel) { if (parent_bus().port_enabled()) { snd_midi_event_t * midi_ev; /* MIDI parser */ int rc = snd_midi_event_new(s_event_size_max, &midi_ev); if (rc == 0) { snd_seq_event_t ev; /* event memory */ midibyte buffer[4]; /* temp data */ buffer[0] = e24->get_status(channel); /* status+chan */ e24->get_data(buffer[1], buffer[2]); /* set the data */ snd_seq_ev_clear(&ev); /* clear event */ snd_midi_event_encode(midi_ev, buffer, 3, &ev); /* 3 raw bytes */ snd_midi_event_free(midi_ev); /* free parser */ snd_seq_ev_set_source(&ev, m_local_addr_port); /* set source */ snd_seq_ev_set_subs(&ev); /* subscriber */ snd_seq_ev_set_direct(&ev); /* immediate */ snd_seq_event_output(m_seq, &ev); /* pump to que */ } else { errprint("ALSA out-of-memory"); } } } /** * min() for long values. * * \param a * First operand. * * \param b * Second operand. * * \return * Returns the minimum value of a and b. */ inline long min (long a, long b) { return (a < b) ? a : b ; } /** * Defines the value used for sleeping, in microseconds. Defined locally * simply for visibility. Why 80000? */ static const int c_sysex_sleep_us = 80000; static const int c_sysex_chunk = 256; /** * Takes a native SYSEX event, encodes it to an ALSA event, and then * puts it in the queue. * * \param e24 * The event to be handled. */ void midi_alsa::api_sysex (const event * e24) { snd_seq_event_t ev; snd_seq_ev_clear(&ev); /* clear event */ snd_seq_ev_set_priority(&ev, 1); snd_seq_ev_set_subs(&ev); snd_seq_ev_set_direct(&ev); /* it's immediate */ snd_seq_ev_set_source(&ev, m_local_addr_port); /* set source */ /* * Replaced by a vector of midibytes: * * midibyte * data = e24->get_sysex(); * * This tack relies on the standard property of std::vector, where all n * elements of the vector are guaranteed to be stored contiguously (in * order to be accessible via random-access iterators). * * We send the sysex data in chunks, with a sleep, for slower devices. */ event::sysex & data = const_cast(e24->get_sysex()); int data_size = e24->sysex_size(); if (data_size < c_sysex_chunk) { snd_seq_ev_set_sysex(&ev, data_size, &data[0]); int rc = snd_seq_event_output_direct(m_seq, &ev); if (rc >= 0) { api_flush(); } else { errprint("Sending complete SysEx failed"); } } else { for (int offset = 0; offset < data_size; offset += c_sysex_chunk) { int data_left = data_size - offset; snd_seq_ev_set_sysex ( &ev, min(data_left, c_sysex_chunk), &data[offset] ); int rc = snd_seq_event_output_direct(m_seq, &ev); if (rc >= 0) { usleep(c_sysex_sleep_us); api_flush(); } else { errprint("Sending SysEx failed"); } } } } /** * Flushes our local queue events out into ALSA. This is also a * midi_alsa_info function. */ void midi_alsa::api_flush () { snd_seq_drain_output(m_seq); } /** * Continue from the given tick. * * Also defined in midi_alsa_info. * * \param tick * The continuing tick, unused in the ALSA implementation here. * The midibase::continue_from() function uses it. * * \param beats * The beats value calculated by midibase::continue_from(). */ void midi_alsa::api_continue_from (midipulse /* tick */, midipulse beats) { if (parent_bus().port_enabled()) { snd_seq_event_t ev; snd_seq_ev_clear(&ev); /* clear event */ ev.type = SND_SEQ_EVENT_CONTINUE; snd_seq_event_t evc; snd_seq_ev_clear(&evc); /* clear event */ evc.type = SND_SEQ_EVENT_SONGPOS; evc.data.control.value = beats; snd_seq_ev_set_fixed(&ev); snd_seq_ev_set_fixed(&evc); snd_seq_ev_set_priority(&ev, 1); snd_seq_ev_set_priority(&evc, 1); snd_seq_ev_set_source(&evc, m_local_addr_port); /* set the source */ snd_seq_ev_set_subs(&evc); snd_seq_ev_set_source(&ev, m_local_addr_port); snd_seq_ev_set_subs(&ev); snd_seq_ev_set_direct(&ev); /* it's immediate */ snd_seq_ev_set_direct(&evc); snd_seq_event_output(m_seq, &evc); /* pump into queue */ api_flush(); snd_seq_event_output(m_seq, &ev); } } /** * This function gets the MIDI clock a-runnin', if the clock type is not * e_clock::none. The clock-enabled status is checked in midibase::start(). */ void midi_alsa::api_start () { if (parent_bus().port_enabled()) { snd_seq_event_t ev; snd_seq_ev_clear(&ev); /* memsets it to 0 */ ev.type = SND_SEQ_EVENT_START; snd_seq_ev_set_fixed(&ev); snd_seq_ev_set_priority(&ev, 1); snd_seq_ev_set_source(&ev, m_local_addr_port); /* set the source */ snd_seq_ev_set_subs(&ev); snd_seq_ev_set_direct(&ev); /* it's immediate */ snd_seq_event_output(m_seq, &ev); /* pump into queue */ } } /** * Stop the MIDI buss. */ void midi_alsa::api_stop () { if (parent_bus().port_enabled()) { snd_seq_event_t ev; snd_seq_ev_clear(&ev); /* memsets it to 0 */ ev.type = SND_SEQ_EVENT_STOP; snd_seq_ev_set_fixed(&ev); snd_seq_ev_set_priority(&ev, 1); snd_seq_ev_set_source(&ev, m_local_addr_port); /* set the source */ snd_seq_ev_set_subs(&ev); snd_seq_ev_set_direct(&ev); /* it's immediate */ snd_seq_event_output(m_seq, &ev); /* pump into queue */ } } /** * Generates the MIDI clock, starting at the given tick value. * Also sets the event tag to 127 so the sequences won't remove it. * * \threadsafe * * \param tick * Provides the starting tick, unused in the ALSA implementation. */ void midi_alsa::api_clock (midipulse /*tick*/) { snd_seq_event_t ev; snd_seq_ev_clear(&ev); /* clear event */ ev.type = SND_SEQ_EVENT_CLOCK; ev.tag = 127; snd_seq_ev_set_fixed(&ev); snd_seq_ev_set_priority(&ev, 1); snd_seq_ev_set_source(&ev, m_local_addr_port); /* set source */ snd_seq_ev_set_subs(&ev); snd_seq_ev_set_direct(&ev); /* it's immediate */ snd_seq_event_output(m_seq, &ev); /* pump it into queue */ } /** * Currently, this code is implemented in the midi_alsa_info module, since * it is a mastermidibus function. Note the implementation here, though. * Which actually gets used? */ void midi_alsa::api_set_ppqn (int ppqn) { int queue = parent_bus().queue_number(); snd_seq_queue_tempo_t * tempo; snd_seq_queue_tempo_alloca(&tempo); int rc = snd_seq_get_queue_tempo(m_seq, queue, tempo); if (rc == 0) { snd_seq_queue_tempo_set_ppq(tempo, ppqn); snd_seq_set_queue_tempo(m_seq, queue, tempo); } } /** * Set the BPM value (beats per minute). This is done by creating * an ALSA tempo structure, adding tempo information to it, and then * setting the ALSA sequencer object with this information. * * We fill the ALSA tempo structure (snd_seq_queue_tempo_t) with the current * tempo information, set the BPM value, put it in the tempo structure, and * give the tempo value to the ALSA queue. * * \note * Consider using snd_seq_change_queue_tempo() here if the ALSA queue has * already been started. It's arguments would be m_alsa_seq, m_queue, * tempo (microseconds), and null. * * \threadsafe * * \param bpm * Provides the beats-per-minute value to set. */ void midi_alsa::api_set_beats_per_minute (midibpm bpm) { int queue = parent_bus().queue_number(); snd_seq_queue_tempo_t * tempo; snd_seq_queue_tempo_alloca(&tempo); /* allocate tempo struct */ int rc = snd_seq_get_queue_tempo(m_seq, queue, tempo); if (rc == 0) { snd_seq_queue_tempo_set_tempo(tempo, unsigned(tempo_us_from_bpm(bpm))); snd_seq_set_queue_tempo(m_seq, queue, tempo); } } #if defined REMOVE_QUEUED_ON_EVENTS_CODE /** * Deletes events in the queue. This function is not used anywhere, and * there was no comment about the intent/context of this function. */ void midi_alsa::remove_queued_on_events (int tag) { snd_seq_remove_events_t * remove_events; snd_seq_remove_events_malloc(&remove_events); snd_seq_remove_events_set_condition ( remove_events, SND_SEQ_REMOVE_OUTPUT | SND_SEQ_REMOVE_TAG_MATCH | SND_SEQ_REMOVE_IGNORE_OFF ); snd_seq_remove_events_set_tag(remove_events, tag); snd_seq_remove_events(m_seq, remove_events); snd_seq_remove_events_free(remove_events); } #endif // REMOVE_QUEUED_ON_EVENTS_CODE /* * -------------------------------------------------------------------------- * midi_in_alsa * -------------------------------------------------------------------------- */ /** * ALSA MIDI input normal port or virtual port constructor. The kind of port * is determine by which port-initialization function the mastermidibus * calls. */ midi_in_alsa::midi_in_alsa (midibus & parentbus, midi_info & masterinfo) : midi_alsa (parentbus, masterinfo) { // Empty body } int midi_in_alsa::api_poll_for_midi () { return 0; /* master_info().api_poll_for_midi(); */ } /* * -------------------------------------------------------------------------- * midi_out_alsa * -------------------------------------------------------------------------- */ /** * ALSA MIDI output normal port or virtual port constructor. The kind of * port is determine by which port-initialization function the mastermidibus * calls. */ midi_out_alsa::midi_out_alsa (midibus & parentbus, midi_info & masterinfo) : midi_alsa (parentbus, masterinfo) { // Empty body } } // namespace seq66 /* * midi_alsa.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/src/midi_alsa_info.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_alsa_info.cpp * * A class for obtaining ALSA information. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-11-14 * \updates 2025-05-20 * \license See above. * * API information found at: * * - http://www.alsa-project.org/documentation.php#Library * * This class is meant to collect a whole bunch of ALSA information about * client number, port numbers, and port names, and hold them for usage when * creating ALSA midibus objects and midi_alsa API objects. * * This was to be a function to create an ALSA "announce" bus. But it turned * out to be feasible and simpler to add it as a special input port in the * get_all_port_info() function. Still, the discussion here is useful. * * A sequencer core has two pre-defined system ports on the system client * SND_SEQ_CLIENT_SYSTEM: SND_SEQ_PORT_SYSTEM_TIMER and * SND_SEQ_PORT_SYSTEM_ANNOUNCE. The SND_SEQ_PORT_SYSTEM_TIMER is the system * timer port, and SND_SEQ_PORT_SYSTEM_ANNOUNCE is the system announce port. * * Timer: * * In order to control a queue from a client, client should send a * queue-control event like start, stop and continue queue, change tempo, * etc. to the system timer port. Then the sequencer system handles the queue * according to the received event. This port supports subscription. The * received timer events are broadcasted to all subscribed clients. From * SND_SEQ_PORT_SYSTEM_TIMER, one may receive SND_SEQ_EVENT_START events. * * Announce: * * The SND_SEQ_PORT_SYSTEM_ANNOUNCE port does not receive messages, but * supports subscription. When each client or port is attached, detached or * modified, an announcement is sent to subscribers from this port. From * SND_SEQ_PORT_SYSTEM_ANNOUNCE, one may receive * SND_SEQ_EVENT_PORT_SUBSCRIBED events. * * Capability bits (FYI): * \verbatim SND_SEQ_PORT_CAP_READ 0x01 SND_SEQ_PORT_CAP_WRITE 0x02 SND_SEQ_PORT_CAP_SYNC_READ 0x04 SND_SEQ_PORT_CAP_SYNC_WRITE 0x08 SND_SEQ_PORT_CAP_DUPLEX 0x10 SND_SEQ_PORT_CAP_SUBS_READ 0x20 SND_SEQ_PORT_CAP_SUBS_WRITE 0x40 SND_SEQ_PORT_CAP_NO_EXPORT 0x80 \endverbatim */ #include "cfg/settings.hpp" /* seq66::rc() configuration object */ #include "midi/event.hpp" /* seq66::event and other tokens */ #include "midi/midibus_common.hpp" /* from the libseq66 sub-project */ #include "midi_alsa_info.hpp" /* seq66::midi_alsa_info */ #include "util/basic_macros.hpp" /* C++ version of easy macros */ namespace seq66 { /** * We tried opening the ALSA port in non-blocking mode. Didn't seem to * offer any benefit. * * - 0 Blocking mode. * - SND_SEQ_NONBLOCK Non-blocking mode. * * We did reduce the polling timeout from 1000 milliseconds (in Seq24) to 100 * milliseconds, and now, after testing, 10 milliseconds, and removed the * additional 100 microsecond wait. */ static const int c_poll_wait_ms = 10; static const int c_open_block_mode = SND_SEQ_NONBLOCK; /* * Initialization of static members. */ unsigned midi_alsa_info::sm_input_caps = SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; unsigned midi_alsa_info::sm_output_caps = SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE; /** * Principal constructor. * * \param appname * Provides the name of the application. * * \param ppqn * Provides the PPQN value needed by this object. * * \param bpm * Provides the beats/minute value needed by this object. */ midi_alsa_info::midi_alsa_info ( const std::string & appname, int ppqn, midibpm bpm ) : midi_info (appname, ppqn, bpm), m_alsa_seq (nullptr), m_num_poll_descriptors (0), /* from ALSA mastermidibus */ m_poll_descriptors (nullptr) /* ditto */ { snd_seq_t * seq; /* point to member */ int rcode = snd_seq_open /* set up ALSA sequencer client */ ( &seq, "default", SND_SEQ_OPEN_DUPLEX, c_open_block_mode /* vs 0 */ ); if (rcode < 0) { const char * errmsg = snd_strerror(rcode); m_error_string = "ALSA seq open error: "; m_error_string += errmsg; error(rterror::kind::driver_error, m_error_string); } else { /* * Save the ALSA "handle". Set the client's name for ALSA. Then set up * the ALSA client queue. rc().app_client_name() gets a name that is not * necessarily on the executable name. */ m_alsa_seq = seq; midi_handle(seq); snd_seq_set_client_name(m_alsa_seq, rc().app_client_name().c_str()); global_queue(snd_seq_alloc_queue(m_alsa_seq)); get_poll_descriptors(); } } /** * Destructor. Closes a connection if it exists, shuts down the input * thread, and then cleans up any API resources in use. */ midi_alsa_info::~midi_alsa_info () { if (not_nullptr(m_alsa_seq)) { snd_seq_event_t ev; snd_seq_ev_clear(&ev); /* memset it to 0 */ snd_seq_stop_queue(m_alsa_seq, global_queue(), &ev); snd_seq_free_queue(m_alsa_seq, global_queue()); snd_seq_close(m_alsa_seq); /* close client */ (void) snd_config_update_free_global(); /* more cleanup */ m_alsa_seq = nullptr; remove_poll_descriptors(); } } /** * Get the number of MIDI input poll file descriptors. Allocate the * poll-descriptors array. Then get the input poll-descriptors into the * array. Finally, set the input and output buffer sizes. Can we do this * before creating all the MIDI busses? If not, we'll put them in a separate * function to call later. * * This function is called in the constructor and in api_port_start(). * * According to https://users.suse.com/~mana/alsa090_howto.html#sect04 * snd_seq_poll_descriptors_count(alsa_seq, POLLIN) always returns 1. * */ void midi_alsa_info::get_poll_descriptors () { m_num_poll_descriptors = snd_seq_poll_descriptors_count(m_alsa_seq, POLLIN); if (m_num_poll_descriptors > 0) { m_poll_descriptors = new (std::nothrow) pollfd[m_num_poll_descriptors]; if (not_nullptr(m_poll_descriptors)) { snd_seq_poll_descriptors /* get input descriptors */ ( m_alsa_seq, m_poll_descriptors, m_num_poll_descriptors, POLLIN ); snd_seq_set_output_buffer_size(m_alsa_seq, c_midibus_output_size); snd_seq_set_input_buffer_size(m_alsa_seq, c_midibus_input_size); } } else { errprint("No ALSA poll descriptors found"); } } /** * Removes the poll descriptors. */ void midi_alsa_info::remove_poll_descriptors () { if (not_nullptr(m_poll_descriptors)) { struct pollfd * pds = m_poll_descriptors; m_poll_descriptors = nullptr; m_num_poll_descriptors = 0; delete [] pds; } } /** * Checks the port type for not being the "generic" types * SND_SEQ_PORT_TYPE_MIDI_GENERIC and SND_SEQ_PORT_TYPE_SYNTH. * * We might need to add this check!!! * * ((alsatype & SND_SEQ_PORT_TYPE_APPLICATION) == 0) */ bool midi_alsa_info::check_port_type (snd_seq_port_info_t * pinfo) const { unsigned alsatype = snd_seq_port_info_get_type(pinfo); return ( ((alsatype & SND_SEQ_PORT_TYPE_MIDI_GENERIC) == 0) && ((alsatype & SND_SEQ_PORT_TYPE_SYNTH) == 0) ); } /** * Gets information on ALL ports, putting input data into one midi_info * container, and putting output data into another container. For ALSA * input, the first item added is the ALSA MIDI system "announce" buss. * It has the client:port value of "0:1", denoted by the ALSA macros * SND_SEQ_CLIENT_SYSTEM:SND_SEQ_PORT_SYSTEM_ANNOUNCE. * The information obtained is: * * - Client name * - Port number * - Port name * - Port capabilities * * \return * Returns the total number of ports found. For an ALSA setup, finding * no ALSA ports can be considered an error. However, finding no ports * for other APIS may be fine. So, we set the result to -1 to flag a * true error. */ int midi_alsa_info::get_all_port_info ( midi_port_info & inputports, midi_port_info & outputports ) { int result = 0; if (not_nullptr(m_alsa_seq)) { snd_seq_port_info_t * pinfo; /* point to member */ snd_seq_client_info_t * cinfo; snd_seq_client_info_alloca(&cinfo); snd_seq_client_info_set_client(cinfo, -1); inputports.clear(); outputports.clear(); inputports.add ( SND_SEQ_CLIENT_SYSTEM, "system", SND_SEQ_PORT_SYSTEM_ANNOUNCE, "ALSA Announce", midibase::io::input, midibase::port::system, global_queue() ); ++result; while (snd_seq_query_next_client(m_alsa_seq, cinfo) >= 0) { int client = snd_seq_client_info_get_client(cinfo); if (client == SND_SEQ_CLIENT_SYSTEM) /* i.e. 0 in alsa/seq.h */ { /* * Client 0 won't have ports (timer and announce) that match * the MIDI-generic and Synth types checked below. */ continue; } snd_seq_port_info_alloca(&pinfo); snd_seq_port_info_set_client(pinfo, client); /* reset query info */ snd_seq_port_info_set_port(pinfo, -1); while (snd_seq_query_next_port(m_alsa_seq, pinfo) >= 0) { if (check_port_type(pinfo)) continue; unsigned caps = snd_seq_port_info_get_capability(pinfo); std::string clientname = snd_seq_client_info_get_name(cinfo); std::string portname = snd_seq_port_info_get_name(pinfo); int portnumber = snd_seq_port_info_get_port(pinfo); if ((caps & sm_input_caps) == sm_input_caps) { inputports.add ( client, clientname, portnumber, portname, midibase::io::input, midibase::port::normal, global_queue() ); ++result; } if ((caps & sm_output_caps) == sm_output_caps) { /* * ca 2023-05-07 Big bug? Was using the * midibase::io::input value! */ outputports.add ( client, clientname, portnumber, portname, midibase::io::output, midibase::port::normal ); ++result; } else { /* * When VMPK is running, we get this message for a * client-name of 'VMPK Output'. We also get this * as some kind of weird leftover from NSM with * clientnames of seq66.nOIJQ and seq66.nYAUV. * The capabilities include SND_SEQ_PORT_CAP_NO_EXPORT: * Subscription management from 3rd client is disallowed. */ warnprintf("Non-I/O port '%s'", clientname.c_str()); } } } } if (result == 0) result = (-1); return result; } /** * Flushes our local queue events out into ALSA. This is also a midi_alsa * function. */ void midi_alsa_info::api_flush () { snd_seq_drain_output(m_alsa_seq); } bool midi_alsa_info::api_connect () { return true; } /** * Sets the PPQN numeric value, then makes ALSA calls to set up the PPQN * tempo. The steps are: * * - Allocate tempo structure. * - Fill tempo structure with current tempo info. * - Set the PPQN. * - Give tempo structure to the queue. * * \param p * The desired new PPQN value to set. */ void midi_alsa_info::api_set_ppqn (int p) { int queue = global_queue(); midi_info::api_set_ppqn(p); snd_seq_queue_tempo_t * tempo; snd_seq_queue_tempo_alloca(&tempo); /* allocate struct */ int rc = snd_seq_get_queue_tempo(m_alsa_seq, queue, tempo); if (rc == 0) { snd_seq_queue_tempo_set_ppq(tempo, p); snd_seq_set_queue_tempo(m_alsa_seq, queue, tempo); } } /** * Sets the BPM numeric value, then makes ALSA calls to set up the BPM * tempo. The steps are: * * - Allocate tempo structure. * - Fill tempo structure with current tempo info. * - Set the tempo. * - Give tempo structure to the queue. * * \param b * The desired new BPM value to set. */ void midi_alsa_info::api_set_beats_per_minute (midibpm b) { int queue = global_queue(); midi_info::api_set_beats_per_minute(b); snd_seq_queue_tempo_t * tempo; snd_seq_queue_tempo_alloca(&tempo); /* allocate tempo struct */ int rc = snd_seq_get_queue_tempo(m_alsa_seq, queue, tempo); if (rc == 0) { snd_seq_queue_tempo_set_tempo(tempo, unsigned(tempo_us_from_bpm(b))); snd_seq_set_queue_tempo(m_alsa_seq, queue, tempo); } } /** * Polls for any ALSA MIDI information using a timeout value of 10 * milliseconds (c_poll_wait_ms). Currently there is only 1 poll * descriptor.. * * \return * Returns the result of the call to poll() on the global ALSA poll * descriptors. */ int midi_alsa_info::api_poll_for_midi () { int result = poll ( m_poll_descriptors, m_num_poll_descriptors, c_poll_wait_ms ); return result; } /* * Definitions copped from the seq_alsamidi/src/mastermidibus.cpp module. */ /** * Macros to make capabilities-checking more readable. */ #define CAP_READ(cap) (((cap) & SND_SEQ_PORT_CAP_SUBS_READ) != 0) #define CAP_WRITE(cap) (((cap) & SND_SEQ_PORT_CAP_SUBS_WRITE) != 0) /** * These checks need both bits to be set. Intermediate macros used for * readability. */ #define CAP_R_BITS (SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_READ) #define CAP_W_BITS (SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_WRITE) #define CAP_FULL_READ(cap) (((cap) & CAP_R_BITS) == CAP_R_BITS) #define CAP_FULL_WRITE(cap) (((cap) & CAP_W_BITS) == CAP_W_BITS) #define ALSA_CLIENT_CHECK(pinfo) \ (snd_seq_client_id(m_alsa_seq) != snd_seq_port_info_get_client(pinfo)) /** * Start the given ALSA MIDI port. This function is called by * api_get_midi_event() when an ALSA event SND_SEQ_EVENT_PORT_START is * received. * * - Get the API's client and port information. * - Do some capability checks. * - Find the client/port combination among the set of input/output busses. * If it exists and is not active, then mark it as a replacement. If it * is not a replacement, it will increment the number of input/output * busses. * * We can simplify this code a bit by using elements already present in * midi_alsa_info. * * Also, the midibus pointers created here are local, but the busarray::add() * function manages them, using the std::unique_ptr<> template. We could use * std::unique_ptr<> here, and even std::make_unique() if we wanted to require * C++14 at this time. * * \param masterbus * Provides the object that is need to get access to the busses that need * to be started. * * \param bus * Provides the ALSA bus/client number. * * \param port * Provides the ALSA client port. */ void midi_alsa_info::api_port_start (mastermidibus & masterbus, int bus, int port) { snd_seq_client_info_t * cinfo; /* get bus info */ snd_seq_client_info_alloca(&cinfo); snd_seq_get_any_client_info(m_alsa_seq, bus, cinfo); snd_seq_port_info_t * pinfo; /* get port info */ snd_seq_port_info_alloca(&pinfo); snd_seq_get_any_port_info(m_alsa_seq, bus, port, pinfo); int cap = snd_seq_port_info_get_capability(pinfo); /* get caps */ if (ALSA_CLIENT_CHECK(pinfo)) { if (CAP_FULL_WRITE(cap) && ALSA_CLIENT_CHECK(pinfo)) /* outputs */ { int bus_slot = masterbus.m_outbus_array.count(); int test = masterbus.m_outbus_array.replacement_port(bus, port); if (test >= 0) bus_slot = test; midibus * m = new (std::nothrow) midibus ( masterbus.m_midi_master, bus_slot ); if (not_nullptr(m)) { m->is_virtual_port(false); m->is_input_port(false); masterbus.m_outbus_array.add(m, e_clock::none); } } if (CAP_FULL_READ(cap) && ALSA_CLIENT_CHECK(pinfo)) /* inputs */ { int bus_slot = masterbus.m_inbus_array.count(); int test = masterbus.m_inbus_array.replacement_port(bus, port); if (test >= 0) bus_slot = test; midibus * m = new (std::nothrow) midibus ( masterbus.m_midi_master, bus_slot ); if (not_nullptr(m)) { m->is_virtual_port(false); m->is_input_port(true); masterbus.m_inbus_array.add(m, false); } } } /* end loop for clients */ /* * Get the number of MIDI input poll file descriptors. This is done in the * constructor, too! */ remove_poll_descriptors(); get_poll_descriptors(); } /** * For debugging, we may expose the following static function for use for * normal (and usually copious) incoming MIDI events. For less common * events, like port/client subscription, the debugging can be enabled by the * "--investigate" command-line option. */ bool midi_alsa_info::show_event (snd_seq_event_t * ev, const char * tag) { if (rc().investigate()) { int c = int(ev->source.client); int p = int(ev->source.port); int b = int(input_ports().get_port_index(c, p)); char tmp[80]; snprintf ( tmp, sizeof tmp, "[%s event[%d] = 0x%x: client %d port %d]", tag, b, unsigned(ev->type), c, p ); info_message(tmp); } return true; } /** * Grab a MIDI event. First, a rather large buffer is allocated on the stack * to hold the MIDI event data. Next, if the --alsa-manual-ports option is * not in force, then we check to see if the event is a port-start, * port-exit, or port-change event, and we prcess it, and are done. * * Otherwise, we create a "MIDI event parser" and decode the MIDI event. * * We've beefed up the error-checking in this function due to crashes we got * when connected to VMPK and suddenly getting a rush of ghost notes, then a * seqfault. This also occurs in legacy seq66. To reproduce, run VMPK and * make it the input source. Open a new pattern, turn on recording, and * start the ALSA transport. Record one note. Then activate the button for * "dump input to MIDI bus". You will here the note through VMPK, then ghost * notes start appearing and seq66/seq66 eventually crash. A bug in VMPK, or * our processing? At any rate, we catch the bug now, and don't crash, but * eventually processing gets swamped until we kill VMPK. And we now have a * note sounding even though neither app is running. Really screws up ALSA! * * ALSA events: * * The ALSA events are listed in the snd_seq_event_type enumeration in * /usr/lib/alsa/seq_event.h, where the "normal" MIDI events (from Note * On to Key Signature) have values ranging from 5 to almost 30. But * there are some special ALSA events we need to handle in a different * manner (currently by ignoring them): * * - 0x3c: SND_SEQ_EVENT_CLIENT_START * - 0x3d: SND_SEQ_EVENT_CLIENT_EXIT * - 0x3e: SND_SEQ_EVENT_CLIENT_CHANGE * - 0x3f: SND_SEQ_EVENT_PORT_START * - 0x40: SND_SEQ_EVENT_PORT_EXIT * - 0x41: SND_SEQ_EVENT_PORT_CHANGE * - 0x42: SND_SEQ_EVENT_PORT_SUBSCRIBED * - 0x43: SND_SEQ_EVENT_PORT_UNSUBSCRIBED * * We will add more special events here as we find them. * * VMPK: * * This ALSA-based application is weird and causes weird behavior. * Running it, letting Seq66 auto-connect, then hitting a piano key in * VMPK, causes a Seq66 message "input FIFO overrun". Later, VMPK * crashes. * * \todo * Also, we need to consider using the new remcount return code to loop * on receiving events as long as we are getting them. * * \param inev * The event to be set based on the found input event. It is the * destination for the incoming event. * * \return * This function returns false if we are not using virtual/manual ports * and the event is an ALSA port-start, port-exit, or port-change event. * It also returns false if there is no event to decode. Otherwise, it * returns true. */ bool midi_alsa_info::api_get_midi_event (event * inev) { bool result = false; snd_seq_event_t * ev; int remcount = snd_seq_event_input(m_alsa_seq, &ev); if (remcount < 0 || is_nullptr(ev)) { if (remcount == -EAGAIN) { // no input in non-blocking mode warnprint("input EAGAIN status"); /* ca 2025-05-20 */ } else if (remcount == -ENOSPC) { errprint("input FIFO overrun"); /* see VMPK note */ } else errprint("snd_seq_event_input() failure"); return false; } if (! rc().manual_ports()) { switch (ev->type) { case SND_SEQ_EVENT_CLIENT_START: result = show_event(ev, "Client start"); break; case SND_SEQ_EVENT_CLIENT_EXIT: result = show_event(ev, "Client exit"); break; case SND_SEQ_EVENT_CLIENT_CHANGE: result = show_event(ev, "Client change"); break; case SND_SEQ_EVENT_PORT_START: { /* * Figure out how to best do this. It has way too many parameters * now, and is currently meant to be called from mastermidibus. * See mastermidibase::port_start(). * * port_start(masterbus, ev->data.addr.client, ev->data.addr.port); * api_port_start (mastermidibus & masterbus, int bus, int port) */ result = show_event(ev, "Port start"); break; } case SND_SEQ_EVENT_PORT_EXIT: { /* * The port_exit() function is defined in mastermidibase and in * businfo. They seem to cover this functionality. * * port_exit(masterbus, ev->data.addr.client, ev->data.addr.port); */ result = show_event(ev, "Port exit"); break; } case SND_SEQ_EVENT_PORT_CHANGE: { result = show_event(ev, "Port change"); break; } case SND_SEQ_EVENT_PORT_SUBSCRIBED: result = show_event(ev, "Port subscribed"); break; case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: result = show_event(ev, "Port unsubscribed"); break; default: #if defined SEQ66_PLATFORM_DEBUG_TMI result = show_event(ev, "Port other"); #endif break; } } if (result) return false; midibyte buffer[0x1000]; /* 4096 buffer for data */ snd_midi_event_t * midi_ev; /* make ALSA MIDI parser */ int rc = snd_midi_event_new(sizeof buffer, &midi_ev); if (rc < 0 || is_nullptr(midi_ev)) { errprint("snd_midi_event_new() failed"); return false; } /* * Note that ev->time.tick is always 0. (Same in Seq32). Not sure about * this handling of SysEx data. Apparently one can get only up to ALSA * buffer size (4096) of data. Also, the snd_seq_event_input() function * is said to block! */ #if defined USE_EXPERIMENTAL_RESET_DECODE // undefined, not a fix snd_midi_event_reset_decode(midi_ev); #endif long bytes = snd_midi_event_decode(midi_ev, buffer, sizeof buffer, ev); if (bytes > 0) { result = inev->set_midi_event(ev->time.tick, buffer, bytes); if (result) { bussbyte b = input_ports().get_port_index ( int(ev->source.client), int(ev->source.port) ); bool sysex = inev->is_sysex(); inev->set_input_bus(b); #if defined SEQ66_PLATFORM_DEBUG_TMI printf("[seq66] input event on ALSA bus %d\n", int(b)); #endif while (sysex) /* sysex might be more than one message */ { int remcount = snd_seq_event_input(m_alsa_seq, &ev); long bytes = snd_midi_event_decode ( midi_ev, buffer, sizeof buffer, ev ); if (bytes > 0) { sysex = inev->append_sysex(buffer, bytes); if (remcount == 0) sysex = false; } else sysex = false; } } snd_midi_event_free(midi_ev); return true; } else { /* * This happens even at startup, before anything is really happening. */ snd_midi_event_free(midi_ev); if (bytes < 0) { if (bytes == -EINVAL) { errprint("Invalid seq event"); } else if (bytes == -ENOENT) { errprint("Not a MIDI message"); } else if (bytes == -ENOMEM) { errprint("MIDI message too big"); } } #if defined SEQ66_PLATFORM_DEBUG_TMI errprintf("snd_midi_event_decode() returned %ld", bytes); #endif return false; } } } // namespace seq66 /* * midi_alsa_info.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/src/midi_api.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_api.cpp * * A class for a generic MIDI API. * * \library seq66 application * \author Gary P. Scavone; refactoring by Chris Ahlstrom * \date 2016-11-14 * \updates 2022-05-16 * \license See above. * * In this refactoring, we had to adapt the existing Seq66 * infrastructure to how the "RtMidi" library works. We also had to * refactor the RtMidi library significantly to fit it within the working * mode of the Seq66 application and libraries. */ #include "midi_api.hpp" #include "midi_info.hpp" #include "midibus_rm.hpp" #include "util/basic_macros.hpp" /* errprint() etc. */ namespace seq66 { /* * midi_api section */ /** * Principle constructor. */ midi_api::midi_api (midibus & parentbus, midi_info & masterinfo) : m_master_info (masterinfo), m_parent_bus (parentbus), m_input_data (), m_connected (false), m_error_string (), m_error_callback (0), m_first_error_occurred (false), m_error_callback_user_data (0) { // no code } /** * Destructor, needed because it is virtual. */ midi_api::~midi_api () { // no code } /** * Provides an error handler that can support an error callback. * * \throw * If the error is not just a warning, then an rterror object is thrown. * * \param type * The type of the error. * * \param errorstring * The error message, which gets copied if this is the first error. */ void midi_api::error (rterror::kind errtype, const std::string & errorstring) { if (m_error_callback) { if (m_first_error_occurred) return; m_first_error_occurred = true; const std::string errorMessage = errorstring; m_error_callback(errtype, errorMessage, m_error_callback_user_data); m_first_error_occurred = false; return; } else { /* * Not a big fan of throwing errors, especially since we currently log * errors in rtmidi to the console. Might make this a build option. * * throw rterror(errorstring, type); */ errprint(errorstring); } } /** * This function makes it a bit simpler on the caller. */ void midi_api::master_midi_mode (midibase::io iotype) { master_info().midi_mode(iotype); } } // namespace seq66 /* * midi_api.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/src/midi_info.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_info.cpp * * A class for obtaining system MIDI information * * \library seq66 application * \author Chris Ahlstrom * \date 2016-12-06 * \updates 2025-04-25 * \license See above. * * Classes defined: * * - port_info. A structure class holding data about a port. * - midi_port_info. Holds multiple port_infos and provides accessors. * - midi_info. Holds input and output midi_portinfos. * * These classes are meant to collect a whole bunch of system MIDI information * about client/buss number, port numbers, and port names, and hold it * for usage when creating midibus objects and midi_api objects. * * Port Refresh Idea: * * -# When midi_info :: get_all_port_info() is called, copy midi_info :: * input_ports() and output_ports() midi_info :: m_previous_input and * midi_info :: m_previous_output. * -# Detect when a MIDI port registers or unregisters. * -# Compare the old set of ports to the new set found by * get_all_port_info() to find new ports or missing ports. */ #include /* std::ostringstream */ #include "cfg/settings.hpp" /* access to rc() configuration */ #include "midi/midibus.hpp" /* select portmidi/rtmidi headers */ #include "midi_info.hpp" /* seq66::midi_info etc. */ #if defined SEQ66_MIDI_PORT_REFRESH_BROKEN #include "midi_ports.hpp" /* seq66::midi_ports classes */ #endif namespace seq66 { /* * class midi_port_info */ port_info::port_info ( int clientnumber, const std::string & clientname, int portnumber, const std::string & portname, midibase::io iotype, midibase::port porttype, int queuenumber, const std::string & alias ) : m_client_number (clientnumber), m_client_name (clientname), m_port_number (portnumber), m_port_name (portname), m_queue_number (queuenumber), m_io_type (iotype), m_port_type (porttype), m_port_alias (alias), m_internal_id (null_system_port_id()) { // No other code } /* * class midi_port_info */ /** * Default constructor. Instantiated just for visibility. */ midi_port_info::midi_port_info () : m_port_count (0), m_port_container () { // Empty body } /** * Adds a set of port information to the port container. * * \param clientnumber * Provides the client or buss number for the port. This is a value like * * \param clientname * Provides the system or user-supplied name for the client or buss. * * \param portnumber * Provides the port number, usually re 0. * * \param portname * Provides the system or user-supplied name for the port. * * \param iotype * Indicates if the port is an input port or an output port. * * \param porttype * If the system currently has no input or output port available, then we * want to create a virtual port so that the application has something to * work with. In some systems, we need to create and activate a system * port, such as a timer port or an ALSA announce port. For all other * ports, this value is note used. * * \param queuenumber * Provides the optional queue number, if applicable. For example, the * seq66 application grabs the client number (normally valued at 1) * from the ALSA subsystem. * * \param alias * In some JACK configurations an alias is available. This lets one see * the device's model name without running the a2jmidid daemon. */ void midi_port_info::add ( int clientnumber, const std::string & clientname, int portnumber, const std::string & portname, midibase::io iotype, midibase::port porttype, int queuenumber, const std::string & alias ) { port_info temp ( clientnumber, clientname, portnumber, portname, iotype, porttype, queuenumber, alias ); m_port_container.push_back(temp); m_port_count = int(m_port_container.size()); if (rc().verbose() && ! rc().investigate()) { bool makevirtual = porttype == midibase::port::manual; bool makesystem = porttype == midibase::port::system; bool makeinput = iotype == midibase::io::input; const char * vport = makevirtual ? "virtual" : "auto" ; const char * iport = makeinput ? "input" : "output" ; const char * sport = makesystem ? "system" : "device" ; char tmp[128]; snprintf ( tmp, sizeof tmp, "\"%s:%s\" %s (%s %s %s)", clientname.c_str(), portname.c_str(), alias.c_str(), vport, iport, sport ); info_message(tmp); } } /** * Adds values from a midibus (actually a midibase-derived class). */ void midi_port_info::add (const midibus * m) { add ( m->bus_id(), m->bus_name(), m->port_id(), m->port_name(), m->io_type(), m->port_type() ); } /** * Retrieve the index of a client:port combination (e.g. in ALSA, the output * of the "aplaymidi -l" or "arecordmidi -l" commands) in the port-container. * * \param client * Provides the client number. * * \param port * Provides the port number, the number of a sub-port of the client. * * \return * Returns the index of the pair in the port container, which will match * up with the listing one sees in the "MIDI Input" or "MIDI Clocks" * pages in the "Preferences" dialog. If not found, a -1 is returned. */ bussbyte midi_port_info::get_port_index (int client, int port) { bussbyte result = null_buss(); for (int i = 0; i < m_port_count; ++i) { if (m_port_container[i].m_client_number != client) continue; if (m_port_container[i].m_port_number == port) { result = bussbyte(i); break; } } return result; } /* * class midi_info */ #if defined SEQ66_MIDI_PORT_REFRESH_BROKEN midi_ports midi_info::m_previous_input; midi_ports midi_info::m_previous_output; #endif /** * Principal constructor. */ midi_info::midi_info (const std::string & appname, int ppqn, midibpm bpm) : m_midi_mode_input (true), m_input (), /* midi_port_info for inputs */ m_output (), /* midi_port_info for outputs */ m_bus_container (), /* holds all I/O buss pointers */ m_global_queue (c_bad_id), /* a la mastermidibase; created */ m_midi_handle (nullptr), /* usually looked up or created */ m_app_name (appname), m_ppqn (ppqn), m_bpm (bpm), m_midi_port_refresh (false), m_error_string () { // No code } /** * Provides an error handler. Unlike the midi_api version, it cannot support * an error callback. * * \throw * If the error is not just a warning, then an rterror object is thrown. * * \param type * The type of the error. * * \param errorstring * The error message, which gets copied if this is the first error. */ void midi_info::error (rterror::kind errtype, const std::string & errorstring) { /* * Not a big fan of throwing errors, especially since we currently log * errors in rtmidi to the console. Might make this a build option. * * throw rterror(errorstring, type); */ if (errtype != rterror::kind::max) errprint(errorstring); } /** * Generates a string listing all of the ports present in the port container. * Useful for debugging and probing. * * \return * Returns a multi-line ASCII string enumerating all of the ports. */ std::string midi_info::port_list () const { int inportcount = m_input.get_port_count(); int outportcount = m_output.get_port_count(); std::ostringstream os; midi_info * nc_this = const_cast(this); nc_this->midi_mode(midibase::io::input); os << "Input ports (" << inportcount << "):" << std::endl; for (int i = 0; i < inportcount; ++i) { std::string annotation; if (nc_this->get_virtual(i)) annotation = "virtual"; else if (nc_this->get_system(i)) annotation = "system"; os << " [" << i << "] " << nc_this->get_bus_id(i) << ":" << nc_this->get_port_id(i) << " " << nc_this->get_bus_name(i) << ":" << nc_this->get_port_name(i) ; if (! annotation.empty()) os << " (" << annotation << ")"; os << std::endl; } nc_this->midi_mode(midibase::io::input); os << "Output ports (" << outportcount << "):" << std::endl; for (int o = 0; o < outportcount; ++o) { os << " [" << o << "] " << nc_this->get_bus_id(o) << ":" << nc_this->get_port_id(o) << " " << nc_this->get_bus_name(o) << ":" << nc_this->get_port_name(o) << (nc_this->get_virtual(o) ? " (virtual)" : " ") << std::endl ; } return os.str(); } } // namespace seq66 /* * midi_info.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/src/midi_jack.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_jack.cpp * * A class for realtime MIDI input/output via JACK. * * \library seq66 application * \author Gary P. Scavone; severe refactoring by Chris Ahlstrom * \date 2016-11-14 * \updates 2025-09-22 * \license See above. * * Written primarily by Alexander Svetalkin, with updates for delta time by * Gary Scavone, April 2011. * * In this Seq66 refactoring of RtMidi, we have to warp the RtMidi * model, where ports are opened directly by the application, to the * Seq66 midibus model, where the port object is created, but * initialized after creation. This proved very challenging -- it took a * long time to get the midi_alsa implementation working, and still more time * to get the midi_jack implementation solid. So to call this code "rtmidi" * code is slightly misleading. * * There is an additional issue with JACK ports. First, think of our ALSA * implementation. We have two modes: manual (virtual) and real (normal) * ports. In ALSA, the manual mode exposes Seq66 ports (1 input port, * 16 output ports) to which other applications can connect. The real/normal * mode, via a midi_alsa_info object, determines the list of existing ALSA * ports in the system, and then Seq66 ports are created (via the * midibus) that are local, but connected to these "destination" system * ports. * * In JACK, we can do something similar. Use the manual/virtual mode to * allow Sequencer64 (seq66) to be connected manually via something like * QJackCtl or a session manager. Use the real/normal mode to connect * automatically to whatever is already present. Currently, though, new * devices that appear in the system won't be accessible until a restart. * (Or perhaps a reconnection using a JACK manager like QJackCtl.) * Also, there is now an option to avoid the automatic connection of real * (non-virtual) ports. * * Random JACK notes: * * jack_activate() tells the JACK server that the program is ready to * process JACK events. jack_deactivate() removes the JACK client from * the process graph and disconnects all ports belonging to it. * * Callbacks: * * The input JACK callback can call an rtmidi input callback of the form * * void callback (midi_message & message, void * userdata) * * This callback is wired in by calling rtmidi_in_data :: * user_callback(). Unlike RtMidi, the delta time is stored as part of * the message. * * JackPortFlags: * \verbatim JackPortIsInput = 0x01 JackPortIsOutput = 0x02 JackPortIsPhysical = 0x04 JackPortCanMonitor = 0x08 JackPortIsTerminal = 0x10 \endverbatim * * I/O Issues: * * The nomenclature for JACK input/output ports seems to be backwards of * that for ALSA. Note the confusing (but necessary) orientation of the * driver backend ports: playback ports are "input" to the backend, and * capture ports are "output" from the backend. Here are the properties * we have gleaned for JACK I/O ports: * * -# JACK Input Port. * -# A writable client. * -# Accepts input from an application or device. * -# We create an "output" port and connect it to this input port, * so that we can send data to the port. * -# We set up an "output" JACK process callback that hands off the * data to JACK, so that it can be played. * -# JACK Output Port. * -# A readable client. * -# Provides output to an application or device. * -# We create an "input" port and connect it to this output port, * so that we can receive data from the port. * -# We set up an "input" JACK process callback that provides us * with the data collected by JACK, so that we can record the * data. * * jack_port_get_buffer() returns a pointer to the memory area associated with * the specified port. For an output port, it will be a memory area that can be * written to; for an input port, it will be an area containing the data from * the port's connection(s), or zero-filled. If there are multiple inbound * connections, the data will be mixed appropriately. Do not cache the * returned address across process() callbacks. Port buffers have to be * retrieved in each callback for proper functionning. * * jack_midi_clear_buffer() clears the buffer, which must be an output buffer. * It must be called before calling jack_midi_event_reserve() or * jack_midi_event_write(). This function may not be called on an input * port's buffer! The function jack_midi_reset_buffer() is deprecated. * * jack_midi_event_reserve() allocates space for an event to be written to an * event port buffer. Clients must write the event data to the pointer * returned by this function. Clients must not write more than data_size * bytes into this buffer. Clients must write normalized MIDI data to the * port - no running status and no (1-byte) realtime messages interspersed * with other messages (realtime messages are fine when they occur on their * own). The events must be written in order, sorted by their offsets. JACK * will not sort the events, and will refuse to store out-of-order events. * The offset ranges from 0 to nframes, where nframes is a parameter passed * to the callback. * * jack_ringbuffer_read() reads data from the ring-buffer and advances the data * pointer. The first parameter is the pointer to the ring-buffer. The second * parameter is the destination for the data that is read from the ring-buffer. * The third parameter is the number of bytes to read. It returns the number * of bytes actually read. * * jack_midi_event_get() gets a MIDI event from an event port buffer. JACK * MIDI is normalized; the MIDI event returned by this function is guaranteed * to be a complete MIDI event (the status byte is always present, and no * realtime events are interspersed with the event). It returns 0 on * success, or ENODATA if buffer is empty. * * jack_nframes_t frame = jack_last_frame_time(jack_client) gets the precise * time at the start of the current process cycle. It may only be used from * the process callback, and can be used to interpret timestamps generated by * `frame_time` in other threads with respect to the current process cycle. * This is the only JACK time function that returns exact time: when used * during the process callback it always returns the same value (until the * next process callback, where it will return that value + 'nframes', etc). * The return value is guaranteed to be monotonic and linear in this fashion * unless an XRUN occurs. If an XRUN occurs, clients must check this value * again, as time may have advanced in a non-linear way (e.g. cycles may have * been skipped). The jack_nframes_t type is uint32_t. * * MIDI clock: * * We need to study the source code to the jack_midi_clock application to * make sure we're doing this correctly. * * GitHub issue #165: enabled a build and run with no JACK support. Weird is * that removing or moving the calculations.hpp header into the support macro * section causes midi_jack member functions to be unresolved! */ #include "seq66-config.h" /* SEQ66_JACK_SUPPORT */ #if defined SEQ66_JACK_SUPPORT #include /* std::strcpy(), std::strcat() */ #include #include #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER #include "util/ring_buffer.hpp" /* seq66::ring_buffer<> template */ #else #include #endif #if defined SEQ66_JACK_METADATA_TEST #include #endif #include "cfg/settings.hpp" /* seq66::rc() accessor function */ #include "midi/event.hpp" /* seq66::event from main library */ #include "midi/jack_assistant.hpp" /* seq66::jack_status_pair_t */ #include "midibus_rm.hpp" /* seq66::midibus for rtmidi */ #include "midi_jack.hpp" /* seq66::midi_jack */ #include "os/timing.hpp" /* seq66::microsleep() */ /** * Delimits the size of the JACK ringbuffer. Related to issue #100, when * we play the Sequencer64 MIDI tune "b4uacuse-stress.midi", we can fail to * write to the ringbuffer. So we've doubled the size. Without timestamps, * that allows about 10K events. With time-stamps, about 4.7K events. * * For the new midi_message ringbuffer, running the stress file from the * Sequencer64 project, the maximum number of events in the ring buffer * is about 200 at 192 PPQN. At 960 PPQN, thousands of events are dropped * and the buffer maxes out (1024 events). Let's try 4096 instead. * Weird, now that tune yields the max of 196! Let's try 2048. Might be a * useful configuration option. */ #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER static const size_t c_jack_ringbuffer_size = 2048; /* tentative */ #else static const size_t c_jack_ringbuffer_size = 32768; /* was 16384 */ #endif namespace seq66 { /* ---------------------------------------------------------------------------- Static items and JACK callback functions. ---------------------------------------------------------------------------- */ /** * The buffer size for basic MIDI messages. Probably excludes SysEx * messages. */ static const size_t s_message_buffer_size = 256; #if defined SEQ66_PLATFORM_DEBUG_TMI /** * For trouble-shooting. */ static void show_jack_port_status ( const std::string & title, jack_client_t * clientptr, jack_port_t * portptr ) { bool mine { ::jack_port_is_mine(clientptr, portptr) != 0 }; std::string whose { mine ? "mine" : "not mine" }; std::string longname { "unknown" }; std::string shortname { "unknown" }; std::string porttype { ::jack_port_type(portptr) }; const char * ln { ::jack_port_name(portptr) }; const char * sn { ::jack_port_short_name(portptr) }; if (not_nullptr(ln)) longname = std::string(ln); if (not_nullptr(sn)) shortname = std::string(sn); int flags = ::jack_port_flags(portptr); std::string portflags { "flags" }; if (flags & JackPortIsInput) portflags += " Input"; if (flags & JackPortIsOutput) portflags += " Output"; if (flags & JackPortIsPhysical) portflags += " Physical"; if (flags & JackPortCanMonitor) portflags += " CanMonitor"; if (flags & JackPortIsTerminal) portflags += " Terminal"; printf ( "%s Port: %s (%s) is %s\n" "%s Type: %s, %s\n" , title.c_str(), shortname.c_str(), longname.c_str(), whose.c_str(), title.c_str(), porttype.c_str(), portflags.c_str() ); } #endif // defined SEQ66_PLATFORM_DEBUG_TMI /** * Checks a frame offset for validity. */ inline bool valid_frame_offset (jack_nframes_t offset) { return offset != UINT32_MAX; } /** * Defines the JACK input process callback. It is the JACK process callback * for a MIDI output port (e.g. "system:midi_capture_1", which gives us the * output of the Korg nanoKEY2 MIDI controller), also known as a "Readable * Client" by qjackctl. This callback receives data from JACK and gives it * to our application's input port: * * -# Get the JACK port buffer and the MIDI event-count in this buffer. * -# For each MIDI event, get the event from JACK and push it into a * local midi_message object. * -# Get the event time, converting it to a delta time if possible. * -# If it is not a SysEx continuation, then: * -# If we're using a callback, pass the data to that callback. Do * we need this callback to interface with the midibus-based * code? * -# Otherwise, add the midi_message container to the rtmidi input * queue. One can then grab this data in a midibase :: * poll_for_midi() call. We still ought to check the add * success. * * The ALSA code polls for events, and that model is also available here. * We're still working exactly how it will work best. * * Since this is an input port, buff is the area that contains data from the * "remote" (i.e. outside our application) port. We do not check * midi_jack_data::jack_port() here, it should be good, or else. * * This function used to be static, but now we make it available to * midi_jack_info. Also note the s_null_detected flag. * * jack_time_t t = jack_get_time() returns JACK's current system time * in microseconds, using the JACK clock source. Guaranteed to be monotonic, * but not linear. The jack_time_t type is uint64_t. * * Question: * * When MIDI input comes in, does it accumulate if we don't call * jack_midi_get_event()? Should we get it even if the port is * disabled, and then just not use it? * * Error codes: * * ENODATA = 61: No data available. * ENOBUFS = 105: No buffer space available can happen. * * \param framect * The number of frames to be processes * * \param arg * A pointer to the midi_jack_data structure to be processed. * * \return * Returns 0 unless overflow occurs, then -1 is returned. */ int jack_process_rtmidi_input (jack_nframes_t framect, void * arg) { midi_jack_data * jackdata = reinterpret_cast(arg); rtmidi_in_data * rtindata = jackdata->jack_rtmidiin(); void * buf = ::jack_port_get_buffer(jackdata->jack_port(), framect); int evcount = ::jack_midi_get_event_count(buf); bool overflow = false; for (int j = 0; j < evcount; ++j) { jack_midi_event_t jmevent; int rc = ::jack_midi_event_get(&jmevent, buf, j); if (rc == 0) /* ENODATA if buf empty */ { jack_time_t jtime = ::jack_get_time(); /* time in microsec (!) */ jack_time_t delta_jtime; /* uint64_t */ if (rtindata->first_message()) { rtindata->first_message(false); delta_jtime = 0; } else { jtime -= jackdata->jack_lasttime(); delta_jtime = jack_time_t(jtime * 0.000001); /* secs ??? */ } jackdata->jack_lasttime(jtime); size_t eventsize = jmevent.size; midi_message message(delta_jtime); /* issue #100 ?????? */ for (size_t i = 0; i < eventsize; ++i) message.push(jmevent.buffer[i]); if (! rtindata->continue_sysex()) { if (! rtindata->queue().add(message)) { async_safe_strprint("~"); overflow = true; break; } } } else { const char * errmsg = "rtmidi input error"; if (rc == ENODATA) errmsg = "rtmidi input: ENODATA"; else if (rc == ENOBUFS) errmsg = "rtmidi input: ENOBUFS"; async_safe_errprint(errmsg); } } if (overflow) { async_safe_errprint(" Message overflow "); return (-1); } return 0; } #if defined SEQ66_SHOW_TIMING /** * We should use the async print methods. */ static void message_time (const midi_message & msg, uint64_t t = 0) { const char * tag = t == 0 ? "Sent" : "Delta" ; unsigned jtime = unsigned(msg.msg_send_time()); if (t > 0) jtime = t - jtime; printf ( "%s #%u (0x%2x) %u us\n", tag, msg.msg_number(), msg.status(), jtime ); } #endif #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER /** * Handles peeking and reading data from our replacement for the JACK * ringbuffer. * * \param jackdata * The source of JACK information for this port. * * \param framect * The size of the JACK buffer in "samples". Needed to calculate the * offset, it also provides the limit for the offset value. * * \param lastvalue * Provides the last frame offset. * * \param [out] dest * The bytes from the ringbuffer that are to be written to MIDI are * stored here. * * \param [inout] destsz * Provides the size of the destination buffer. It is modified to return * the number of bytes actually written. * * \return * Returns the calculated frame offset. If UINT32_MAX, then there is * no data, or the frame offset of the event is earlier than the last * offset, and so the data are not read, put off to the next process * callback cycle. */ static jack_nframes_t jack_get_event_data ( midi_jack_data * jackdata, jack_nframes_t framect, jack_nframes_t cycle_start, jack_nframes_t & lastvalue, char * dest, size_t & destsz ) { jack_nframes_t result = UINT32_MAX; ring_buffer * buffmsg = jackdata->jack_buffer(); int count = int(buffmsg->read_space()); bool process = count > 0; if (process) { static bool s_use_offset = midi_jack_data::use_offset(); const midi_message & msg = buffmsg->front(); #if defined SEQ66_SHOW_TIMING static unsigned s_last_msg_number = 0; unsigned msgno = msg.msg_number(); if (msgno < s_last_msg_number) async_safe_errprint("ring_buffer wraparound?"); s_last_msg_number = msgno; message_time(msg, uint64_t(::jack_get_time())); #endif midipulse ts = msg.timestamp(); if (s_use_offset) { #if defined USE_FULL_TTYMIDI_METHOD /* * Handling the "lastvalue" doesn't seem to help at all. */ jack_nframes_t frame = midi_jack_data::frame_estimate(ts); frame += framect - midi_jack_data::size_compensation(); if (lastvalue > frame) frame = lastvalue; else lastvalue = frame; if (frame > cycle_start) { result = frame - cycle_start; if (result >= framect) result = framect - 1; } else result = 0; #else (void) lastvalue; result = midi_jack_data::frame_offset(cycle_start, framect, ts); #endif } else result = 0; if (process) { size_t datasz = size_t(msg.event_count()); if (datasz <= destsz) { memcpy(dest, msg.event_bytes(), datasz); destsz = datasz; } buffmsg->pop_front(); } else { #if defined SEQ66_SHOW_TIMING char value[c_async_safe_utoa_size]; char text[c_async_safe_utoa_size + 32]; std::strcpy(text, "Event "); async_safe_utoa(value, msg.msg_number()); std::strcat(text, value); std::strcat(text, " belayed"); async_safe_errprint(text); #endif result = UINT32_MAX; } } return result; } #endif // defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER /** * Defines the JACK output process callback for a MIDI output port (a * midi_out_jack object associated with, for example, * "system:midi_playback_1", representing, for example, a Korg nanoKEY2 to * which we can send information), also known as a "Writable Client" by * qjackctl. Here's how it works: * * -# Get the JACK port buffer, for our local jack port. Clear it. * -# Loop while the number of bytes available for reading [via * jack_ringbuffer_read_space()] is non-zero. Note that the second * parameter is where the data is copied. * NOTE: This byte buffer has been replaced by a midi_message * ring-buffer. * -# Get the size of each event, and allocate space for an event to be * written to an event port buffer (JACK reserve/write functions). * -# Read the data from the ringbuffer into this port buffer. JACK * will then send it to the remote port. * * Since this is an output port, "buff" is the area to which we can write * data, to send it to the "remote" (i.e. outside our application) port. The * data is written to the ringbuffer in api_init_out(), and here we read the * ring buffer and pass it to the output buffer. * * We were wondering if, like the JACK midiseq example program, we need to * wrap the out-process in a for-loop over the number of frames. In our * tests, we are getting 1024 frames, and the code seems to work without that * loop. A for-loop over the number of frames? See discussion above. * * Could consider using JACK callbacks to detect server-setting changes. * That would be more robust. * * \param framect * The number of frames to be processed. * * \param arg * A pointer to the midi_jack_data object, which holds basic information * about the JACK client. * * \return * Returns 0. */ #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER int jack_process_rtmidi_output (jack_nframes_t framect, void * arg) { char mbuffer[s_message_buffer_size]; char * mbuf = &mbuffer[0]; midi_jack_data * jackdata = reinterpret_cast(arg); jack_port_t * jackport = jackdata->jack_port(); const jack_nframes_t cycle_start = ::jack_last_frame_time(jackdata->jack_client()); jack_nframes_t lastvalue = 0; void * buf = ::jack_port_get_buffer(jackport, framect); jack_position_t pos = jack_assistant::get_jack_parameters().position; if (midi_jack_data::recalculate_frame_factor(pos, framect)) async_safe_errprint("JACK settings changed"); ::jack_midi_clear_buffer(buf); for (;;) { size_t destsz = s_message_buffer_size; jack_nframes_t offset = jack_get_event_data ( jackdata, framect, cycle_start, lastvalue, mbuf, destsz ); if (destsz > 0 && valid_frame_offset(offset)) { const jack_midi_data_t * data = reinterpret_cast(mbuf); int rc = ::jack_midi_event_write(buf, offset, data, destsz); if (rc != 0) { async_safe_errprint("JACK MIDI write error"); break; } lastvalue = offset; /* tricky code */ } else break; } return 0; } #else int jack_process_rtmidi_output (jack_nframes_t framect, void * arg) { midi_jack_data * jackdata = reinterpret_cast(arg); jack_port_t * jackport = jackdata->jack_port(); jack_ringbuffer_t * buffmsg = jackdata->jack_buffmessage(); short space = 0; const size_t sz = sizeof(space); char * sp = reinterpret_cast(&space); void * buf = ::jack_port_get_buffer(jackport, framect); ::jack_midi_clear_buffer(buf); /* no nullptr test */ for (;;) { size_t msgsz = ::jack_ringbuffer_read_space(buffmsg); if (msgsz >= c_jack_ringbuffer_size) { async_safe_errprint("impossible event size"); break; } else if (msgsz > 0) { (void) ::jack_ringbuffer_read(buffmsg, sp, sz); jack_midi_data_t * md = ::jack_midi_event_reserve(buf, 0, space); if (not_nullptr(md)) { char * mididata = reinterpret_cast(md); (void) ::jack_ringbuffer_read /* ignore byte count */ ( buffmsg, mididata, size_t(space) ); } else { async_safe_errprint("JACK event reserve null pointer"); break; } } else break; } return 0; } #endif // defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER /** * This callback is to shut down JACK by clearing the jack_assistant :: * m_jack_running flag. * * \param arg * Points to the jack_assistant in charge of JACK support for the * performer object. */ void jack_shutdown_callback (void * arg) { midi_jack_info * jack = reinterpret_cast(arg); if (not_nullptr(jack)) async_safe_strprint("JACK Shutdown"); else async_safe_errprint("JACK Shutdown null pointer"); } #if defined SEQ66_JACK_PORT_CONNECT_CALLBACK /** * Port-connect and port-register callbacks seem to get interleaved, making * for confusing output. We currently want to know only about registration, * anwyay. * * \param a * One of two ports connected/disconnected. * * \param b * One of two ports connected/disconnected. * * \param connect * Non-zero if ports were connected, zero if ports were disconnected. * * \param arg * Pointer to a client supplied data (a midi_jack_info pointer). */ void jack_port_connect_callback ( jack_port_id_t port_a, jack_port_id_t port_b, int connect, void * arg ) { midi_jack_info * jack = reinterpret_cast(arg); if (not_nullptr(jack)) { if (rc().investigate()) { char value[c_async_safe_utoa_size]; char temp[2 * c_async_safe_utoa_size + 48]; std::strcpy(temp, "port-connect("); async_safe_utoa(value, unsigned(port_a)); std::strcat(temp, value); async_safe_utoa(value, unsigned(port_b)); std::strcat(temp, value); async_safe_utoa(value, unsigned(connect)); std::strcat(temp, connect != 0 ? " connect" : " disconnect"); std::strcat(temp, value); std::strcat(temp, not_nullptr(arg) ? " arg)" : " nullptr)"); async_safe_strprint(temp); } } } #endif /** * Provides a callback for registering a port, so that we can detect when a * port appears or disappears in the system. We use jack_port_by_id() to * get the port, then jack_port_is_mine() so that we can avoid processing our * own port. * * The original port handle is set via the port_handle() setter called in the * member function register_port(). If this port_handle can be found in the * list of ports, that should mean that it is a Seq66 port, and nothing should * be done. Otherwise, the I/O port containers should be updated. * * Also see the api_get_port_name() function and the midi_jack_data class. * * Notes: * * - The documentation for jack_set_port_registration_callback() says that * all notifications are received in a separate non-real-time thread, so * the code doesn't need to be suitable for real-time execution. What about * re-entrant? Async-safe? Based on tests, we are pretty sure that * a series of calls don't interfere with each other. * - For a2j ports, the long and short names are like this: * - Long: 'a2j:nanoKEY2 [20] (playback): nanoKEY2 nanoKEY2 _ CTRL' * - Short: 'nanoKEY2 [20] (playback): nanoKEY2 nanoKEY2 _ CTRL' * - For Seq66's connections to a2j ports, the long and short names are * like this: * - Long: 'seq66:a2j nanoKEY2 [20] (playback): nanoKEY2 nanoKEY2 _ CTRL' * - Short: 'a2j nanoKEY2 [20] (playback): nanoKEY2 nanoKEY2 _ CTRL' * - FluidSynth (on Ubuntu 20): * - Long: 'fluidsynth-midi:midi_00' * - Short: 'midi_00'. * * Desired activity steps: * * -# Startup. * -# Enable port-refresh (callback, processing) only if * port-mapping is enabled. * -# As done now, gather the input and output ports into these * containers (midi_port_info). * -# Also make copies of these containers. * -# MORE * * Rules for handling port registration and unregistration: * * -# Port change detection. * -# Changes should only be detected in non-Seq66 ("not mine") ports. * -# This occurs when registration or un-registration causes the * callback to occur. * -# No special processing unless the user has enabled port-mapping. * Otherwise, Seq66 port numbers, which are simply indexes into the * port vectors, would change, breaking the song. * -# Ports are never removed from the port map. * -# Unregistered ports are simply disabled. * -# New ports are appended to the port map. * -# Ports accumulate. The user must (currently) edit the 'rc' file * to remove ports from the port-map, and restart the application. * -# Port removal. * -# Detection. Examine the active ports and the port map. * -# If the port is not in the active port list, do nothing. * -# If the port is not in the port map, do nothing. * -# Disable or de-initialize the port in: * -# In midi_jack_info/midi_info. * -# The port map. * -# The mastermidibus. ??? * -# The busarray. * -# Port addition. * -# Detection. Examine the active ports and the port map. * -# If the port is in the active port list, do nothing. * -# If the port is in the port map, enable it. * -# If the port is new: * -# Append it to the portmap. * -# Append it to midi_jack_info/midi_info. * -# Add it to the mastermidis. ??? * -# Add it to the busarray and initialize it. * -# User interface. It must be updated to reflect the new port map. * -# Edit / Preferences MIDI Clock and MIDI Input. * -# Main window port list. * # The pattern editor(s) (if output). * * \param portid * The ID of the port. It is a 32-bit unsigned integer. * * \param regv * The registration value. It is non-zero if the port is being registered, * and zero if the port is being unregistered. * * \param arg * Pointer to a client supplied data, the midi_jack_info object. */ void jack_port_register_callback (jack_port_id_t portid, int regv, void * arg) { midi_jack_info * jackinfo = reinterpret_cast(arg); if (not_nullptr(jackinfo)) { jack_client_t * handle = jackinfo->client_handle(); jack_port_t * portptr = nullptr; if (not_nullptr(handle)) { bool mine = false; int flags = 0; midibase::io iotype = midibase::io::indeterminate; std::string longname; std::string shortname; std::string porttype; portptr = ::jack_port_by_id(handle, portid); if (not_nullptr(portptr)) { const char * ln = ::jack_port_name(portptr); const char * sn = ::jack_port_short_name(portptr); if (not_nullptr(ln)) longname = std::string(ln); if (not_nullptr(sn)) shortname = std::string(sn); mine = ::jack_port_is_mine(handle, portptr) != 0; flags = ::jack_port_flags(portptr); porttype = ::jack_port_type(portptr); if (flags & JackPortIsInput) iotype = midibase::io::input; else if (flags & JackPortIsOutput) iotype = midibase::io::output; } else return; /* fatal error, bug out */ if (rc().investigate()) { /* * JACK docs say this function doesn't need to be suitable for * real-time execution. But what about async-safety? It seems * to be necessary; debug_message() yields intermixed output. */ const char * iot = "TBD"; char value[c_async_safe_utoa_size]; char temp[128]; if (iotype == midibase::io::input) iot = " In"; else if (iotype == midibase::io::output) iot = "Out"; async_safe_utoa(value, unsigned(portid), false); std::strcpy(temp, "Port "); std::strcat(temp, value); std::strcat(temp, ": "); std::strcat(temp, regv != 0 ? "Reg" : "Unreg"); std::strcat(temp, " "); std::strcat(temp, iot); std::strcat(temp, " "); std::strncat(temp, shortname.c_str(), 30); /* truncate it */ std::strcat(temp, "/ "); if (is_nullptr(arg)) std::strcat(temp, "nullptr! "); std::strcat(temp, porttype.c_str()); if (mine) std::strcat(temp, " seq66"); async_safe_strprint(temp); #if defined SEQ66_PLATFORM_DEBUG_TMI show_details(); #endif } jackinfo->update_port_list ( mine, int(portid), regv, shortname, longname ); } } } /* ---------------------------------------------------------------------------- MIDI JACK base class ---------------------------------------------------------------------------- */ /** * Note that this constructor also adds its object to the midi_jack_info port * list, so that the JACK callback functions can iterate through all of the * JACK ports in use by this application, performing work on them. * * In midi_jack::api_init_[in/out](), which occurs in midibus::api_init_out() * and results in a call to midi_jack::api_init_out(), the call to * set_alt_name() changes the port name from something like "midi" to * "fluidsynth-midi:midi". That is, it makes the name out of the client name * and (minimal) port name. * * This is the short name we can look up. * * \param parentbus * Provides a reference to the midibus that represents this object. * * \param masterinfo * Provides a reference to the midi_jack_info object that may provide * extra informatino that is needed by this port. Too many entities! */ midi_jack::midi_jack (midibus & parentbus, midi_info & masterinfo) : midi_api (parentbus, masterinfo), m_remote_port_name (), m_jack_info (dynamic_cast(masterinfo)), m_jack_data () { client_handle(reinterpret_cast(masterinfo.midi_handle())); (void) jack_info().add(*this); /* * New for issue #100. These are only tentative values, and are replaced * once transport is active. */ jack_data().ticks_per_beat(ppqn() * 10.0); jack_data().beats_per_minute(bpm()); } /** * This could be a rote empty destructor if we offload this destruction to the * midi_jack_data structure. However, other than the initialization, that * structure is currently "dumb". */ midi_jack::~midi_jack () { #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER if (not_nullptr(jack_data().jack_buffer())) { ring_buffer * rb = jack_data().jack_buffer(); if (rb->dropped() > 0 || rb->count_max() > (rb->buffer_size() / 2)) { char tmp[64]; snprintf ( tmp, sizeof tmp, "%d events dropped, %d max/%d", rb->dropped(), rb->count_max(), rb->buffer_size() ); (void) warn_message("ring-buffer", tmp); } delete jack_data().jack_buffer(); } #else if (not_nullptr(jack_data().jack_buffmessage())) ::jack_ringbuffer_free(jack_data().jack_buffmessage()); #endif } /** * Initialize the MIDI output port. This initialization is done when the * "manual ports" option is not in force. This code is basically what was * done by midi_out_jack::open_port() in RtMidi. * * For jack_connect(), the first port-name is the source port, and the source * port must include the JackPortIsOutput flag. The second port-name is the * destination port, and the destination port must include the * JackPortIsInput flag. For this function, the source/output port is this * port, and the destination/input is.... * * Note that connect_port() [which calls jack_connect()] cannot usefully be * called until jack_activate() has been called. We cannot connect ports * until we are activated, and we cannot activate until all ports are * properly set up. * * \return * Returns true unless setting up JACK MIDI failed in some way. */ bool midi_jack::api_init_out () { bool result = true; std::string remoteportname = connect_name(); /* "bus:port" */ remote_port_name(remoteportname); result = create_ringbuffer(c_jack_ringbuffer_size); if (result) { set_alt_name(rc().application_name(), rc().app_client_name()); result = register_port(midibase::io::output, port_name()); } return result; } /** * This function is called when we are processing the list of system input * ports. We want to create an output port of a similar name, but with the * application as client, and connect it to this sytem input port. We want to * follow the model we got from seq24, rather than the RtMidi model, so that we * do not have to rework (and probably break) the seq24 model. * * We can't use the API port name here at this time, because it comes up * empty. It comes up empty because we haven't yet registered the ports, * including the source ports. So we register it first; connect_port() will * not try to register it again. * * Based on the comments in the jack.txt note file, here is what we need to do: * * -# open_client_impl(INPUT). * -# Get port name via master_info().connect_name(). * -# Call jack_client_open() with or without a UUID. * -# Call jack_set_process_callback() for input/output. * -# Call jack_activate(). * -# register_port(INPUT...). The flag is JackPortIsInput. * -# connect_port(INPUT...). Call jack_connect(srcport, destport). * * Unlike the corresponding virtual port, this input port is actually an * output port. * * Note that we cannot connect ports until we are activated, and we cannot be * activated until all ports are properly set up. We also need to fill in * the jack_data() member here. * * \return * Returns true if the function was successful, and sets the flag * indicating that the port is open. */ bool midi_jack::api_init_in () { std::string remoteportname = connect_name(); /* "bus:port" */ remote_port_name(remoteportname); set_alt_name(rc().application_name(), rc().app_client_name()); return register_port(midibase::io::input, port_name()); } /** * Assumes that the port has already been registered, and that JACK * activation has already occurred. * * \return * Returns true if all steps of the connection succeeded. */ bool midi_jack::api_connect () { std::string remotename = remote_port_name(); std::string localname = connect_name(); /* modified! */ bool result; if (is_input_port()) result = connect_port(midibase::io::input, remotename, localname); else result = connect_port(midibase::io::output, localname, remotename); if (result) set_port_open(); return result; } /** * Gets information directly from JACK. The problem this function solves is * that the midibus constructor for a virtual JACK port doesn't not have all * of the information it needs at that point. Here, we can get this * information and get the actual data we need to rename the port to * something accurate. * * const char * pname = jack_port_name(const jack_port_t *); * * \return * Returns true if all of the information could be obtained. If false is * returned, then the caller should not use the side-effects. * * \sideeffect * Passes back the values found. */ bool midi_jack::set_virtual_name (int portid, const std::string & portname) { bool result = not_nullptr(client_handle()); if (result) { char * cname = ::jack_get_client_name(client_handle()); result = not_nullptr(cname); if (result) { std::string clientname = cname; set_port_id(portid); port_name(portname); set_name(rc().app_client_name(), clientname, portname); } } return result; } /** * Useful for trouble-shooting and exploration. We find that the port name (of * client:port) is, as expected, the JACK short name. For example, * "fluidsynth-midi midi_00". The remote name obtained from midibase :: * connect_name() is just the constructed "bus:port" name: * "fulidsynth-midi:midi_00". For a2jmidi-supplied ports, though, the * port/short and remote names are the same. */ std::string midi_jack::details () const { /* * TMI: * std::string result = parent_bus().display_name(); * result += ", "; * result += parent_bus().bus_name(); */ std::string result = parent_bus().bus_name(); result += ":"; result += parent_bus().port_name(); result += "-->"; result += m_remote_port_name; return result; } /** * This initialization is like the "open_virtual_port()" function of the * RtMidi library. * * \return * Returns true if all steps of the initialization succeeded. */ bool midi_jack::api_init_out_sub () { master_midi_mode(midibase::io::output); /* this is necessary */ int portid = parent_bus().port_id(); bool result = portid >= 0; if (! result) { portid = bus_index(); result = portid >= 0; } if (result) result = create_ringbuffer(c_jack_ringbuffer_size); if (result) { std::string portname = parent_bus().port_name(); if (portname.empty()) { portname = "midi out"; portname += " "; portname += std::to_string(portid); } result = register_port(midibase::io::output, portname); if (result) { set_virtual_name(portid, portname); set_port_open(); } } return result; } /** * Initializes a virtual/manual input port. * * \return * Returns true if all steps of the initialization succeeded. */ bool midi_jack::api_init_in_sub () { master_midi_mode(midibase::io::input); int portid = parent_bus().port_id(); bool result = portid >= 0; if (! result) { portid = bus_index(); result = portid >= 0; } if (result) { std::string portname = master_info().get_port_name(bus_index()); std::string portname2 = parent_bus().port_name(); if (portname.empty()) { portname = "midi in"; portname += " "; portname += std::to_string(portid); } result = register_port(midibase::io::input, portname); if (result) { set_virtual_name(portid, portname); set_port_open(); } } return result; } /** * Commented out. See the discussion at api_deinit_in(). */ bool midi_jack::api_deinit_out () { /* * Seems, in retrospect, to be ill-advised. * * set_port_suspended(true); */ return true; } /** * We could define these in the opposite order. * * This function is mis-named at this time; it actually leaves the port open * now, and simply disables the reading of MIDI data. We need to improve on * the life-cycle. Also, this suspension seems to cause an issue where * the application hangs on exit. */ bool midi_jack::api_deinit_in () { /* * Do not close. We still have callbacks responding, and they bitch about * exercising a closed port. * * close_port(); * * Seems to cause an application hang upon exit. * * set_port_suspended(true); */ return true; } /** * We could push the bytes of the event into a midibyte vector, as done in * send_message(). The ALSA code (seq_alsamidi/src/midibus.cpp) sticks the * event bytes in an array, which might be a little faster than using * push_back(), but let's try the vector first. The rtmidi code here is from * midi_out_jack :: send_message(). */ void midi_jack::api_play (const event * e24, midibyte channel) { midi_message message(e24->timestamp()); /* issue #100 */ midibyte status = e24->get_status(channel); midibyte d0, d1; e24->get_data(d0, d1); message.push(status); message.push(d0); if (e24->is_two_bytes()) message.push(d1); #if defined SEQ66_SHOW_TIMING message.msg_send_time(uint64_t(::jack_get_time())); #endif if (jack_data().valid_buffer()) { if (send_message(message)) { #if defined SEQ66_SHOW_TIMING_DISABLED_HERE message_time(message); #endif } else async_safe_errprint("JACK send event failed"); } } /** * Sends a JACK MIDI output message. It writes the full message size and * the message itself to the JACK ring buffer (actually our new ring_buffer * template. The timestamp was not provided by the original "rtmidi" * implementation. This new data and the custom ringbuffer are * added for issue #100. * * Let's try the frame count instead of the timestamp. See the ttymidi.c * module. * * jack_nframes_t t = jack_frame_time() returns the estimated current time in * frames. This function is intended for use in other threads (not the * process callback). The return value can be compared with the value of * jack_last_frame_time to relate time in other threads to JACK time. * * \param message * Provides the MIDI message object, which contains the bytes to send. * * \return * Returns true if the buffer message and buffer size seem to be written * correctly. */ bool midi_jack::send_message (const midi_message & message) { #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER ring_buffer * rb = jack_data().jack_buffer(); #if defined SEQ66_ENCODE_JACK_FRAME_TIME midi_message & ncmessage = const_cast(message); ncmessage.timestamp(midipulse(::jack_frame_time(jack_data().jack_client()))); #endif #if defined SEQ66_PLATFORM_DEBUG bool result = rb->push_back(message); if (result) { size_t space = size_t(rb->read_space()); result = space > 0 && space < c_jack_ringbuffer_size; } /* * Redundant. We get "seq66] JACK send event failed" anyway. * * if (! result) * printf("send_message() failed\n"); */ return result; #else return rb->push_back(message); #endif #else // ! defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER int nbytes = message.event_count(); bool result = nbytes > 0 && nbytes < int(s_message_buffer_size); if (result) { short n = short(nbytes); int count1 = ::jack_ringbuffer_write /* write raw message size ! */ ( jack_data().jack_buffmessage(), reinterpret_cast(&n), sizeof n ); int count2 = ::jack_ringbuffer_write /* write the message bytes */ ( jack_data().jack_buffmessage(), message.buffer(), size_t(n) ); result = (count1 > 0) && (count2 > 0); } return result; #endif } /** * Work on this routine now in progress. Unlike the ALSA implementation, we * do not try to send large messages (greater than 255 bytes) in chunks. * * The event::sysex data type is a vector of midibytes. Also note that both * Meta and Sysex messages are covered by the event :: is_ex_data() function * and via the sysex() function to send data. * * This SysEx capability is also used in the midicontrolout class to create * an event that contains the MIDI bytes of a control macro, which can then * be sent out via mastermidibus :: sysex(). * * \param e24 * Provides a pointer to the event. Currently we do not use the normal * event time-stamp, which is in units of ticks, but instead insert * the current JACK time in microseconds. */ void midi_jack::api_sysex (const event * e24) { midi_message message(e24->timestamp()); /* issue #100 */ const event::sysex & data = e24->get_sysex(); int data_size = e24->sysex_size(); for (int offset = 0; offset < data_size; ++offset) message.push(data[offset]); if (jack_data().valid_buffer()) { if (! send_message(message)) printf("JACK send sysex failed"); } } /** * It seems like JACK doesn't have the concept of flushing events. * The actual function called right now is midi_jack_info::api_flush(), via * rtmidi_info::api_flush(), and it is also an empty function. */ void midi_jack::api_flush () { // No code needed } /** * jack_transport_locate(), jack_transport_reposition(), or something else? * What is used by jack_assistant? * * \param tick * Provides the tick value to continue from. * * \param beats * The parameter "beats" is currently unused. */ void midi_jack::api_continue_from (midipulse tick, midipulse /*beats*/) { int beat_width = 4; // no m_beat_width !!! int ticks_per_beat = ppqn() * 10; // why 10 !!! midibpm beats_per_minute = bpm(); uint64_t tick_rate = ( uint64_t(::jack_get_sample_rate(client_handle())) * tick * 60.0 ); long tpb_bpm = long(ticks_per_beat * beats_per_minute * 4.0 / beat_width); uint64_t jack_frame = tick_rate / tpb_bpm; if (::jack_transport_locate(client_handle(), jack_frame) != 0) (void) info_message("JACK Continue failed"); /* * New code to work like the ALSA version, needs testing. Related to * issue #67. However, the ALSA version sends Continue, flushes, and * then sends Song Position, so we will match that here. */ send_byte(tick, EVENT_MIDI_CONTINUE); api_flush(); /* currently does nothing */ send_byte(tick, EVENT_MIDI_SONG_POS); } /** * Starts this JACK client and sends MIDI start. Note that the * jack_assistant code (which implements JACK transport) checks if JACK is * running, but a check of the JACK client handle here should be enough. */ void midi_jack::api_start () { ::jack_transport_start(client_handle()); send_byte(0, EVENT_MIDI_START); /* is tick 0 always good */ } /** * Stops this JACK client and sends MIDI stop. Note that the jack_assistant * code (which implements JACK transport) checks if JACK is running, but a * check of the JACK client handle here should be enough. */ void midi_jack::api_stop () { ::jack_transport_stop(client_handle()); send_byte(0, EVENT_MIDI_STOP); /* do we need real tick? */ } /** * Sends a MIDI clock event. * * \param tick * The value of the tick to use. */ void midi_jack::api_clock (midipulse tick) { if (tick >= 0) { #if defined SEQ66_PLATFORM_DEBUG_TMI midibase::show_clock("JACK", tick); #endif send_byte(tick, EVENT_MIDI_CLOCK); } } /** * An internal helper function for sending MIDI clock bytes. This function * ought to be called "send_realtime_message()". * * Based on the GitHub project "jack_midi_clock", we could try to bypass the * ringbuffer used here and convert the ticks to jack_nframes_t, and use the * following code: * * uint8_t * buffer = jack_midi_event_reserve(port_buf, time, 1); * if (buffer) * buffer[0] = rt_msg; * * We generally need to send the (realtime) MIDI clock messages Start, Stop, * and Continue if the JACK transport state changed. * * \param evbyte * Provides one of the following values (though any byte can be sent): * * - EVENT_MIDI_SONG_POS * - EVENT_MIDI_CLOCK. The tick value is not needed. * - EVENT_MIDI_START. The tick value is not needed. * - EVENT_MIDI_CONTINUE. The tick value is needed if... * - EVENT_MIDI_STOP. The tick value is not needed. */ void midi_jack::send_byte (midipulse tick, midibyte evbyte) { midi_message message(tick); message.push(evbyte); if (jack_data().valid_buffer()) { if (! send_message(message)) printf("JACK send byte failed"); } } /** * Empty body for setting PPQN. */ void midi_jack::api_set_ppqn (int /*ppqn*/) { // No code needed for JACK } /** * Empty body for setting BPM. */ void midi_jack::api_set_beats_per_minute (midibpm /* bp */) { // No code needed for JACK } /** * Gets the name of the current port via jack_port_name(). This is different * from get_port_name(index), which simply looks up the port-name in the * attached midi_info object. * * This function is never called! * * \return * Returns the full port name ("clientname:portname") if the port has * already been opened/registered; otherwise an empty string is returned. */ std::string midi_jack::api_get_port_name () { std::string result; if (not_nullptr(port_handle())) result = std::string(::jack_port_name(port_handle())); return result; } /** * Closes the JACK client handle. * * This function is currently not called! */ void midi_jack::close_client () { if (not_nullptr(client_handle())) { int rc = ::jack_client_close(client_handle()); client_handle(nullptr); if (rc != 0) { int index = bus_index(); int id = parent_bus().port_id(); m_error_string = "JACK Close port "; m_error_string += std::to_string(index); m_error_string += " (id "; m_error_string += std::to_string(id); m_error_string += ")"; error(rterror::kind::driver_error, m_error_string); } } } /** * Connects two named JACK ports, but only if they are not virtual/manual * ports. First, we register the local port. If this is nominally a local * input port, it is really doing output, and this is the source-port name. * If this is nominally a local output port, it is really accepting input, * and this is the destination-port name. * * \param input * Indicates true if the port to register and connect is an input port, * and false if the port is an output port. Useful macros for * readability: midibase::io::input and midibase::io::output. * * \param srcportname * Provides the destination port-name for the connection. For input, * this should be the name associated with the JACK client handle; it is * the port that gets registered. For output, this should be the full * name of the port that was enumerated at start-up. The JackPortFlags * of the source port must include JackPortIsOutput. * * \param destportname * For input, this should be full name of port that was enumerated at * start-up. For output, this should be the name associated with the * JACK client handle; it is the port that gets registered. The * JackPortFlags of the destination port must include JackPortIsInput. * Now, if this name is empty, that basically means that there is no such * port, and we create a virtual port in this case. So jack_connect() is * not called in this case. We do not treat this case as an error. * * \return * If the jack_connect() call succeeds, true is returned. If the port is * a virtual (manual) port, then it is not connected, and true is * returned without any action. */ bool midi_jack::connect_port ( midibase::io iotype, const std::string & srcportname, const std::string & destportname ) { bool result = true; if (! is_virtual_port()) { result = ! srcportname.empty() && ! destportname.empty(); if (result) { #if defined SEQ66_PLATFORM_DEBUG_TMI /* * The port types must be identical. The JackPortFlags * of the source must include JackPortIsOutput; the * destination must include JackPortIsInput. */ jack_port_t * srcptr = ::jack_port_by_name(client_handle(), srcportname.c_str()); jack_port_t * destptr = ::jack_port_by_name(client_handle(), destportname.c_str()); show_jack_port_status("Source", client_handle(), srcptr); show_jack_port_status("Destination", client_handle(), destptr); #endif int rc = ::jack_connect ( client_handle(), srcportname.c_str(), destportname.c_str() ); result = rc == 0; if (! result) { if (rc == EEXIST) { /* * Probably not worth emitting a warning to the console. */ } else { bool input = iotype == midibase::io::input; m_error_string = "JACK Connect error"; m_error_string += input ? "input '" : "output '"; m_error_string += srcportname; m_error_string += "' to '"; m_error_string += destportname; m_error_string += "'"; error(rterror::kind::driver_error, m_error_string); } } } } return result; } /** * Registers a named JACK port. We made this function to encapsulate some * otherwise cut-and-paste functionality. * * For jack_port_register(), the port-name must be the actual port-name, not * the full-port name ("busname:portname"). * * Note that the buffer size of non-built-in port type is 0, and so it is * ignored. * * JackPortIsTerminal: * * For an input port, the data received by the port will not be passed on * or made available at any other port. For an output port, the data * available at the port does not originate from any other port. Audio * synthesizers, I/O hardware interface clients, HDR systems are examples * of clients that would set this flag for their ports. * * \tricky * If we are registering an input port, this means that we got the input * port from the system. In order to connect to that port, we have * to register as an output port, even though the application calls it in * input port (midi_in_jack). Confusing, but the same thing was implicit * in the ALSA implementation, and so we have to apply that same setup * here. * * \param input * Indicates if the port to register is an input or output port * * \param portname * Provides the local name of the port. This is the full name * ("clientname:portname"), but the "portname" part is extracted to fit * the requirements of the jack_port_register() function. There is an * issue here when a2jmidid is running. We may see a client name of * "seq66", but a port name of "a2j Midi Through [1] capture: Midi * Through Port-0", which has a colon in it. What to do? Just not * extract the port name from the portname parameter. If we have an * issue here, we'll have to fix it in the caller. */ bool midi_jack::register_port (midibase::io iotype, const std::string & portname) { bool result = not_nullptr(port_handle()); if (! result) { bool isinput = iotype == midibase::io::input; unsigned long buffsize = 0; unsigned long flag = isinput ? JackPortIsInput : JackPortIsOutput ; const char * porttype = JACK_DEFAULT_MIDI_TYPE; /* * At least in the version of JACK on our developer laptop, * using our own port type string fails. This is contrary * to the current JACK documentation for jack_port_register(). * * if (port_type() == midibase::port::manual) * porttype = "virtual midi"; */ jack_port_t * p = ::jack_port_register ( client_handle(), portname.c_str(), porttype, flag, buffsize ); if (not_nullptr(p)) { #if defined SEQ66_PLATFORM_DEBUG_TMI std::string title = isinput ? "Input" : "Output" ; show_jack_port_status(title, client_handle(), p); #endif port_handle(p); result = true; #if defined SEQ66_JACK_METADATA_TEST /* undefined */ (void) set_jack_port_property /* it works! */ ( client_handle(), p, JACK_METADATA_PRETTY_NAME, "Pretty Name" ); #endif if (rc().investigate()) { std::string longname = portname; std::string shortname = ::jack_port_short_name(p); if (shortname != portname) { longname += " \""; longname += shortname; longname += "\""; } debug_message("Registered", longname); } } else { m_error_string = "JACK Register error"; m_error_string += " "; m_error_string += portname; error(rterror::kind::driver_error, m_error_string); } } return result; } /** * Closes the MIDI port by calling jack_port_unregister() and * nullifying the port pointer. * * This function is not called at application exit! */ void midi_jack::close_port () { if (not_nullptr(client_handle()) && not_nullptr(port_handle())) { ::jack_port_unregister(client_handle(), port_handle()); port_handle(nullptr); } } /** * Creates the JACK output ring-buffer. */ bool midi_jack::create_ringbuffer (size_t rbsize) { bool result = rbsize > 0; if (result) { #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER ring_buffer * rb = new (std::nothrow) ring_buffer(rbsize); result = not_nullptr(rb); if (result) jack_data().jack_buffer(rb); #else jack_ringbuffer_t * rb = ::jack_ringbuffer_create(rbsize); result = not_nullptr(rb); if (result) jack_data().jack_buffmessage(rb); #endif if (! result) { m_error_string = "JACK ringbuffer create error"; error(rterror::kind::warning, m_error_string); } } return result; } /* * MIDI JACK input class. */ /** * Principal constructor. For Seq66, we don't current need to create * a midi_in_jack object; all that is needed is created via the * api_init_in*() functions. Also, this constructor still needs to do * something with queue size. * * \param parentbus * Provides the buss object that determines buss-specific parameters of * this class. * * \param masterinfo * Provides information about the JACK system as found on this machine. */ midi_in_jack::midi_in_jack (midibus & parentbus, midi_info & masterinfo) : midi_jack (parentbus, masterinfo), m_client_name () { /* * Currently, we cannot initialize here because the clientname is empty. * It is retrieved in api_init_in(). * * Hook in the input data. The JACK port pointer will get set in * api_init_in() or api_init_out() when the port is registered. */ jack_data().jack_rtmidiin(input_data()); } /** * Checks the rtmidi_in_data queue for the number of items in the queue. * * \return * Returns the value of rtindata->queue().count(), unless the caller is * using an rtmidi callback function, in which case 0 is always returned. */ int midi_in_jack::api_poll_for_midi () { rtmidi_in_data * rtindata = jack_data().jack_rtmidiin(); (void) microsleep(std_sleep_us()); /* 10 us */ return rtindata->queue().count(); } /** * Gets a MIDI event. This implementation gets a midi_message off the front * of the queue and converts it to a Seq66 event. * * \change ca 2017-11-04 * Issue #4 "Bug with Yamaha PSR in JACK native mode" in the * seq66-packages project has been fixed. For now, we ignore * system messages. Yamaha keyboards like my PSS-790 constantly emit * active sensing messages (0xfe) which are not logged, and the previous * event (typically pitch wheel 0xe0 0x0 0x40) is continually emitted. * One result (we think) is odd artifacts in the seqroll when recording * and passing through. * * \param inev * Provides the destination for the MIDI event. * * \return * Returns true if a MIDI event was obtained, indicating that the return * parameter can be used. Note that we force a value of false for all * system messages at this time; they cannot yet be handled gracefully in * the native JACK implementation. */ bool midi_in_jack::api_get_midi_event (event * inev) { rtmidi_in_data * rtindata = jack_data().jack_rtmidiin(); bool result = ! rtindata->queue().empty(); if (result) { midi_message mm = rtindata->queue().pop_front(); result = inev->set_midi_event ( mm.timestamp(), mm.event_bytes(), mm.event_count() ); inev->set_input_bus(mm.input_buss()); // but busarry::get_midi_event()! if (result) { /* * For now, ignore certain messages; they're not handled by the * perform object. Could be handled there, but saves some * processing time if done here. Also could move the output code * to perform so it is available for frameworks beside JACK. */ midibyte st = mm[0]; if (st >= EVENT_MIDI_REALTIME && rc().verbose()) { static int s_count = 0; char c; switch (st) { case EVENT_MIDI_CLOCK: c = 'C'; break; case EVENT_MIDI_ACTIVE_SENSE: c = 'S'; break; case EVENT_MIDI_RESET: c = 'R'; break; case EVENT_MIDI_START: c = '>'; break; case EVENT_MIDI_CONTINUE: c = '|'; break; case EVENT_MIDI_STOP: c = '<'; break; case EVENT_MIDI_SYSEX: c = 'X'; break; default: c = '.'; break; } (void) putchar(c); if (++s_count == 80) { s_count = 0; (void) putchar('\n'); } fflush(stdout); } if (event::is_sense_or_reset(st)) result = false; /* * Issue #55 (ignoring the channel). The status is already set, * with channel nybble, above, no need to set it here. */ } } return result; } /** * Destructor. Currently the base class closes the port, closes the JACK * client, and cleans up the API data structure. */ midi_in_jack::~midi_in_jack() { // No code yet } /* * API: JACK Class Definitions: midi_out_jack */ /** * Principal constructor. For Seq66, we don't current need to create * a midi_out_jack object; all that is needed is created via the * api_init_out*() functions. * * \param parentbus * Provides the buss object that determines buss-specific parameters of * this class. * * \param masterinfo * Provides information about the JACK system as found on this machine. */ midi_out_jack::midi_out_jack (midibus & parentbus, midi_info & masterinfo) : midi_jack (parentbus, masterinfo) { /* * Currently, we cannot initialize here because the clientname is empty. * It is retrieved in api_init_out(). */ } /** * Destructor. Currently the base class closes the port, closes the JACK * client, and cleans up the API data structure. */ midi_out_jack::~midi_out_jack () { // No code yet } } // namespace seq66 #endif // SEQ66_JACK_SUPPORT /* * midi_jack.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/src/midi_jack_data.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_jack_data.cpp * * Object for holding the current status of JACK and JACK MIDI data. * * \library seq66 application * \author Chris Ahlstrom * \date 2022-09-13 * \updates 2022-11-22 * \license See above. * * GitHub issue #165: enabled a build and run with no JACK support. */ #include /* std::trunc(double) functions */ #include "midi_jack_data.hpp" /* seq66::midi_jack_data class */ #include "cfg/settings.hpp" /* seq66::rc() config accessor */ #if defined SEQ66_JACK_SUPPORT namespace seq66 { /** * Static members used for calculating frame offsets in the Seq66 JACK * output callback. */ jack_nframes_t midi_jack_data::sm_jack_frame_rate = 1; /* impossible */ jack_nframes_t midi_jack_data::sm_jack_start_frame = 0; jack_nframes_t midi_jack_data::sm_cycle_frame_count = 0; jack_nframes_t midi_jack_data::sm_size_compensation = 0; jack_time_t midi_jack_data::sm_cycle_time_us = 0; jack_time_t midi_jack_data::sm_pulse_time_us = 0; double midi_jack_data::sm_jack_ticks_per_beat = 1920.0; double midi_jack_data::sm_jack_beats_per_minute = 1.0; /* 120.0 */ double midi_jack_data::sm_jack_frame_factor = 0.0; bool midi_jack_data::sm_use_offset = false; /** * \ctor midi_jack_data */ midi_jack_data::midi_jack_data () : m_jack_client (nullptr), m_jack_port (nullptr), #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER m_jack_buffer (nullptr), /* ring_buffer */ #else m_jack_buffmessage (nullptr), #endif m_jack_lasttime (0), #if defined SEQ66_MIDI_PORT_REFRESH m_internal_port_id (null_system_port_id()), #endif m_jack_rtmidiin (nullptr) { // Empty body } /** * This destructor currently does nothing, as it owns nothing. */ midi_jack_data::~midi_jack_data () { // Empty body } /** * Embodies the calculation of the pulse factor reasoned out in the * frame_offset() function below. * * TODO: * * - The jack_position_t's ticks_per_beat and beats_per_minute are * currently zero if seq66 is not transport (slave or master). Then * we need to make sure the beats-per-minute is set from the * tune settings, and the ticks-per-beat is set to PPQN * 10. * - It would be good to call this function during a settings change as * indicated by calls to the jack_set_buffer_size(frames, arg) or * jack_set_sample_rate(frames, arg) callbacks. * * Adjustment number: * * The lower this number, the faster the calculated offsets vary. * Not sure why, but using the "adjustment" factor makes playback * better behaved at 2048 and 4096 buffer sizes. A really low number * (0.1) causes "belays", with glitches at each one. */ bool midi_jack_data::recalculate_frame_factor ( const jack_position_t & pos, jack_nframes_t F ) { bool changed = false; if ( (pos.ticks_per_beat > 1.0) && /* sanity check */ (ticks_per_beat() != pos.ticks_per_beat) ) { ticks_per_beat(pos.ticks_per_beat); changed = true; } if ( (pos.beats_per_minute > 1.0) && /* sanity check */ (beats_per_minute() != pos.beats_per_minute) ) { beats_per_minute(pos.beats_per_minute); changed = true; } if (frame_rate() != pos.frame_rate) { frame_rate(pos.frame_rate); changed = true; } if (changed) { /* * A value of 2 or 3 would represent the ALSA nperiod, which does * not affect (purportedly) the calculations. */ const double adjustment = 1.0; /* nperiod? */ double factor = 600.0 / /* seconds/pulse */ ( adjustment * ticks_per_beat() * beats_per_minute() ); double cycletime = double(F) / frame_rate(); double compensation = double(F) * 0.10 + 0.5; bool useoffset = rc().with_jack_transport() && rc().jack_use_offset(); cycle_frame_count(F); cycle_time_us(1000000.0 * cycletime); /* microsec/cycle */ pulse_time_us(1000000.0 * factor); /* microsec/pulse */ frame_factor(frame_rate() * factor); /* frames/pulse */ size_compensation(jack_nframes_t(compensation)); use_offset(useoffset); #if defined SEQ66_PLATFORM_DEBUG_TMI printf ( "cycle = %u ms, pulse = %u ms\n", unsigned(cycle_time_us() / 1000), unsigned(pulse_time_us() / 1000) ); #endif } return changed; } /** * Attempts to convert ticks to a frame offset (0 to framect). * * The problem (issue #100): * * Our RtMidi-based process handles output by first pushing the current * playback tick (pulse) and the message bytes to the JACK ringubffer. This * happens at frame f0 and time t1. In the JACK output callback, the message * data is plucked from the ringbuffer at later frame f1 and time t1. There, * we need an offset from the current frame to the actual frame, * so that the messages don't jitter to much with large frame sizes. * * The solution: * * -# We assume that the tick position p is in the same relative location * relative to the current frame when placed into the ringbuffer and when * retrieved from the ring buffer. Actually, this is unlikely, but * all event spacing should still roughly be preserved. * -# We can use the calculations modules pulse_length_us() function to * get the length of a tick in seconds: 60 / ppqn / bpm. Therefore the * tick time t(p) = p * 60 / ppqn / bpm. * -# What is the number of frames at at given time? The duration of each * frame is D = 1 / R. So the time at the end of frame Fn is * T(n) = n / R. * * The latency L of a frame is given by the number of frames, F, the sampling * rate R (e.g. 48000), and the number of periods P: L = P * F / R. Here is a * brief table for 48000, 2 periods: * \verbatim Frames Latency Qjackctl P=2 Qjackctl P-3 32 0.667 ms (doubles them) (triples them) 64 1.333 ms 128 2.667 ms 256 5.333 ms 512 10.667 ms 1024 21.333 ms 2048 42.667 ms 4096 85.333 ms \endverbatim * * The jackd arguments from its man apge: * * - --rate: The sample rate, R. Defaults 48000. * - --period: The number of frames between the process callback * calls, F. Must be a power of 2, defaults to 1024. Set * as low as possible to avoid xruns. * - --nperiods: The number of periods of playback latency, P. Defaults * to the minimum, 2. For USB audio, 3 is recommended. * This an ALSA engine parameter, but it might contribute * to the duration of a process callback cycle [proved * using probe code to calculate microseconds.] * * So, given a tick position p, what is the frame offset needed? * \verbatim t(p) = 60 * p / ppqn / bpm, but ppqn = ticks_per_beat / 10, so t(p) = 60 * p / (Tpb / 10) / bpm T(n) = n * P * F / R 60 * 10 * p / (Tpb * bpm) = n * P * F / R n = 60 * 10 * p * R / (Tpb * bpm * P * F) seconds 1 beats minutes frame = pulse * 10 * 60 ------- * ---- * ------ * ------- minute Tpb ticks beat \endverbatim * * Typical values: * \verbatim frame_rate = 48000 beats_per_bar = 4 beat_type = 4 ticks_per_beat = 1920 beats_per_minute = 120 \endverbatim * * \param F * The current frame count (the jack_nframes framect) parameter to the * callback. * * \param p * The running timestamp, in pulses, of the event being emitted. * * \return * Returns a putative offset value to use in reserving the event. */ jack_nframes_t midi_jack_data::frame_offset (jack_nframes_t F, midipulse p) { jack_nframes_t result = frame_estimate(p) + start_frame(); if (F > 1) result = result % F; return result; } /** * This method is an adaptation of the ttymidi.c module's method. * How it works: * * 1. Get the cycle_start frame-number, fc. * 2. bufsize_compensation, b = jack_get_buffer_size() / 10.0 + 0.5) * 3. Read the ringbuffer data into bufc in the old rtmidi way. New * wrinkle format: [data, data_size, frame]. The frame is set this way: * a. Clock messages. The frame = jack_frame_time(). * b. Other MIDI. Same. * 4. We have the frame, f, from the data, and the frame-count, F. * f += F - b. * 5. If last_buf_frame > f, f = last_buf_frame else last_buf_frame = f. * 6. If f >= fc offset = f - fc else 0. If offset > F, offset = F-1 * * The implementation here currently leaves out Step 5, but seems to work * well anyway. It seems to avoid the delay of an event to a later cyle * at random times. */ jack_nframes_t midi_jack_data::frame_offset ( jack_nframes_t fc, /* Step 1, cycle-start */ jack_nframes_t F, midipulse p /* Step 3, sort of */ ) { jack_nframes_t result = 0; jack_nframes_t b = size_compensation(); /* Step 2. */ jack_nframes_t f = frame_estimate(p) + F - b; /* Step 4. */ if (f > fc) result = f - fc; if (result > F) result = F - 1; return result; } /** * Calculates pulses * frames / pulse to estimate the frame value. * This value is rounded and truncated. */ jack_nframes_t midi_jack_data::frame_estimate (midipulse p) { double temp = double(p) * frame_factor() + 0.5; return jack_nframes_t(temp); } void midi_jack_data::cycle_frame ( midipulse p, jack_nframes_t & cycle, jack_nframes_t & offset ) { double f = double(p) * frame_factor() + 0.5; /* frame estimate */ double c = f / double(cycle_frame_count()); /* cycle + fraction */ double fullc = std::trunc(c); double fraction = c - fullc; cycle = jack_nframes_t(c); /* cycle number */ offset = jack_nframes_t(fraction * cycle_frame_count()); } unsigned midi_jack_data::delta_time_ms (midipulse p) { unsigned result = 0; double tpb = ticks_per_beat(); double bpm = beats_per_minute(); if (tpb > 10.0 && bpm > 1.0) { double ms = 0.001 * ticks_to_delta_time_us(p, tpb / 10.0, bpm); result = unsigned(ms + 0.5); } return result; } #if defined USE_JACK_TIME_OFFSET_FUNCTION /** * Function to adjust a MIDI event timestamp for lag when calculating what * frame offset it needs. * * Do we need the base time in microseconds when playback starts? * * -# Get the microseconds at push time (the time just before pushing * the message onto the ringbuffer. This is Tpush. * -# Set this time in the MIDI message, then push the message. * -# In the process callback, get the message, and get the microseconds * at pop (front) time, Tpop. * -# Compute the lag as Lpushpop = Tpop - Tpush. * -# Convert the event timestamp p into microseconds, Tp. * -# Add the lag to this time: Treal = Tp + Lpushpop. * -# Get the duration of a single callback cycle, Tcycle. * -# Convert the Treal time to a frame offset. Calculate a floating- * point frame number f = Treal / Tcycle. * -# Truncate it to get an integer number of cycles. * -# Deduct that from f. * -# Use the fractional remainder to calculate the offset: * result = Foffset = F * fraction. * * However: * * This function yields very bad playback at 4096 frames/cycle. It acts like * the events are piling up in the ringbuffer. Removing the lag calculation * make it play normally (but badly). */ jack_nframes_t midi_jack_data::time_offset ( jack_nframes_t F, midipulse p, jack_time_t Tpop, jack_time_t Tpush ) { double Lpushpop = double(Tpop - Tpush); double Tcycle = 1000000.0 * double(F) / frame_rate(); double Tp = p * 1000000.0 * 600.0 / (ticks_per_beat() * beats_per_minute()); double Treal = Tp + Lpushpop; /* double Treal = Tp; (see banner) */ double f = Treal / Tcycle; double truncation = std::trunc(f); double fraction = f - truncation; jack_nframes_t result = jack_nframes_t(F * fraction); if (result >= F) { // todo? } return result; } #endif // defined USE_JACK_TIME_OFFSET_FUNCTION /** * Calculates the putative cycle number, without truncation, because we want * the fractional part. * * \param f * The current frame number. * * \param F * The number of frames in a cycle. (A power of 2.) * * \return * The cycle number with fractional portion. */ double midi_jack_data::cycle (jack_nframes_t f, jack_nframes_t F) { return double(f) / double(F); } double midi_jack_data::pulse_cycle (midipulse p, jack_nframes_t F) { return p * frame_factor() / double(F); } } // namespace seq66 #endif // SEQ66_JACK_SUPPORT /* * midi_jack_data.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/src/midi_jack_info.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_jack_info.cpp * * A class for obtaining JACK port information. * * \library seq66 application * \author Chris Ahlstrom * \date 2017-01-01 * \updates 2025-09-19 * \license See above. * * This class is meant to collect a whole bunch of JACK information about * client number, port numbers, and port names, and hold them for usage when * creating JACK midibus objects and midi_jack API objects. */ #include /* std::strcpy(), std::strcat() */ #include "seq66-config.h" #if defined SEQ66_JACK_SUPPORT #if defined SEQ66_JACK_METADATA #include #include "base64_images.hpp" #endif #include "cfg/settings.hpp" /* seq66::rc() configuration object */ #include "midi/event.hpp" /* seq66::event and other tokens */ #include "midi/jack_assistant.hpp" /* seq66::create_jack_client() */ #include "midi/midibus_common.hpp" /* from the libseq66 sub-project */ #include "midi_jack.hpp" /* seq66::midi_jack_info */ #include "midi_jack_data.hpp" /* seq66::midi_jack_data */ #include "midi_jack_info.hpp" /* seq66::midi_jack_info */ #include "os/timing.hpp" /* seq66::microsleep() */ #include "util/basic_macros.hpp" /* C++ version of easy macros */ #include "util/strfunctions.hpp" /* seq66::contains() */ namespace seq66 { /* * Defined in midi_jack.cpp; used to be static. */ extern int jack_process_rtmidi_input (jack_nframes_t nframes, void * arg); extern int jack_process_rtmidi_output (jack_nframes_t nframes, void * arg); extern void jack_shutdown_callback (void * arg); #if defined SEQ66_JACK_PORT_CONNECT_CALLBACK extern void jack_port_connect_callback ( jack_port_id_t a, jack_port_id_t b, int connect, void * arg ); #endif extern void jack_port_register_callback ( jack_port_id_t port, int ev_value, void * arg ); /** * Provides a JACK callback function that uses the callbacks defined in the * midi_jack module. This function calls both the input callback and * the output callback, depending on the port type. This may lead to * delays, depending on the size of the JACK MIDI buffer. * * \param nframes * The frame number from the JACK API. * * \param arg * The putative pointer to the midi_jack_info structure. * * \return * Always returns 0. */ int jack_process_io (jack_nframes_t nframes, void * arg) { midi_jack_info * self = reinterpret_cast(arg); if (not_nullptr(self)) { /* * Go through the I/O ports and route the data appropriately. */ for (auto mj : self->jack_ports()) /* midi_jack pointers */ { if (mj->enabled()) { midi_jack_data * mjp = &mj->jack_data(); if (mj->parent_bus().is_input_port()) (void) jack_process_rtmidi_input(nframes, mjp); else (void) jack_process_rtmidi_output(nframes, mjp); } } } return 0; } /** * Principal constructor. * * \param appname * Provides the name of the application. * * \param ppqn * Provides the desired value of the PPQN (pulses per quarter note). * * \param bpm * Provides the desired value of the BPM (beats per minute). */ midi_jack_info::midi_jack_info ( const std::string & appname, int ppqn, midibpm bpm ) : midi_info (appname, ppqn, bpm), m_jack_ports (), m_jack_client (nullptr), /* inited for connect() */ m_jack_buffer_size (0), m_jack_sample_rate (0) { silence_jack_info(); m_jack_client = connect(); if (not_nullptr(m_jack_client)) /* created by connect() */ { midi_handle(m_jack_client); /* void version */ client_handle(m_jack_client); /* jack version */ } } /** * Destructor. Deactivates (disconnects and closes) any ports maintained by * the JACK client, then closes the JACK client, shuts down the input * thread, and then cleans up any API resources in use. */ midi_jack_info::~midi_jack_info () { disconnect(); } /** * Local JACK connection for enumerating the ports. Note that this name will * be used for normal ports, so we make sure it reflects the application * name. * * Note that this function does not call jack_connect(). * * We need to add a call to jack_on_shutdown() to set up a shutdown callback. * We also need to wait on the activation call until we have registered all * the ports. Then we (actually the mastermidibus) can call the * api_connect() function to activate this JACK client and connect all the * ports. */ jack_client_t * midi_jack_info::connect () { jack_client_t * result = m_jack_client; if (is_nullptr(result)) /* do not connect again */ { const char * clientname = seq_client_name().c_str(); result = create_jack_client(clientname); /* see jack_assistant */ if (not_nullptr(result)) { int r = ::jack_set_process_callback(result, jack_process_io, this); m_jack_client = result; if (r == 0) { std::string uuid = rc().jack_session(); if (uuid.empty()) uuid = get_jack_client_uuid(result); if (! uuid.empty()) rc().jack_session(uuid); ::jack_on_shutdown ( m_jack_client, jack_shutdown_callback, (void *) this ); #if defined SEQ66_JACK_PORT_CONNECT_CALLBACK r = ::jack_set_port_connect_callback ( m_jack_client, jack_port_connect_callback, (void *) this ); if (r != 0) { m_error_string = "JACK cannot set port-connect callback"; error(rterror::kind::warning, m_error_string); } #endif r = ::jack_set_port_registration_callback ( m_jack_client, jack_port_register_callback, (void *) this ); if (r != 0) { m_error_string = "JACK cannot set port-register callback"; error(rterror::kind::warning, m_error_string); } #if defined SEQ66_JACK_METADATA std::string n = seq_icon_name(); bool ok = set_jack_client_property ( m_jack_client, JACK_METADATA_ICON_NAME, n ); if (ok) { debug_message("Set 32x32 icon", n); ok = set_jack_client_property ( m_jack_client, JACK_METADATA_ICON_SMALL, qseq66_32x32, "image/png;base64" ); if (! ok) error_message("Failed to set 32x32 icon"); } else error_message("Failed to set client icon", n); if (ok) { debug_message("Set 128x128 icon", n); ok = set_jack_client_property ( m_jack_client, JACK_METADATA_ICON_LARGE, qseq66_128x128, "image/png;base64" ); if (! ok) error_message("Failed to set 128x128 icon"); } #endif } else { m_error_string = "JACK cannot set I/O callback"; error(rterror::kind::warning, m_error_string); } /* * TODO: set the position to 0 */ } else { m_error_string = "JACK server not running"; error(rterror::kind::warning, m_error_string); } } return result; } /** * The opposite of connect(). */ void midi_jack_info::disconnect () { if (not_nullptr(m_jack_client)) { ::jack_deactivate(m_jack_client); ::jack_client_close(m_jack_client); m_jack_client = nullptr; } } /** * Extracts the two names from the JACK port-name format, * "clientname:portname". */ void midi_jack_info::extract_names ( const std::string & fullname, std::string & clientname, std::string & portname ) { (void) extract_port_names(fullname, clientname, portname); } /** * Gets information on ALL ports, putting input data into one midi_info * container, and putting output data into another midi_info container. * * \tricky * When we want to connect to a system input port, we want to use an * output port to do that. When we want to connect to a system output * port, we want to use an input port to do that. Therefore, we search * for the opposite kind of port. * * If there is no system input port, or no system output port, then we add a * virtual port of that type so that the application has something to work * with. * * Note that, at some pointer, we ought to consider how to deal with * transitory system JACK clients and ports, and adjust for it. A kind of * miniature form of session management. Also, don't forget about the * usefulness of jack_get_port_by_id() and jack_get_port_by_name(). * * Error handling: * * Not having any JACK input ports present isn't necessarily an error. There * may not be any, and there may still be at least one output port. Also, if * there are none, we try to make a virtual port so that the application has * something to work with. The only issue is the client number. Currently * all virtual ports we create have a client number of 0. * * JackPortIsPhysical: * * If this flag is added, then only ports corresponding to a physical device * are get detected and connected. This might be a useful option to add at a * later date. * * \return * Returns the total number of ports found. Note that 0 ports is not * necessarily an error; there may be no JACK apps running with exposed * ports. If there is no JACK client, then -1 is returned. */ int midi_jack_info::get_all_port_info ( midi_port_info & inputports, midi_port_info & outputports ) { int result = (-1); if (not_nullptr(m_jack_client)) { const char ** inports = ::jack_get_ports /* list of the JACK ports */ ( m_jack_client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput /* tricky JACK code */ ); inputports.clear(); result = 0; if (is_nullptr(inports)) /* check port validity */ { #if defined SEQ66_MISSING_JACK_VIRTUAL_PORTS warnprint("No JACK in-ports; making a virtual port"); int clientnumber = 0; int portnumber = 0; std::string clientname = seq_client_name(); std::string portname = "midi in 0"; inputports.add ( clientnumber, clientname, portnumber, portname, midibase::io::input, midibase::port::manual ); ++result; #endif } else { tokenization client_name_list; int client = -1; int count = 0; while (not_nullptr(inports[count])) { std::string fullname = inports[count]; std::string clientname; std::string portname; std::string alias = get_port_alias_by_name(fullname); if (alias == fullname) alias.clear(); /* * TODO: somehow get the 32-bit ID of the port and add it as a * system port ID to use for lookup when detecting newly * registered or unregistered ports. */ extract_names(fullname, clientname, portname); if (client == -1 || clientname != client_name_list.back()) { client_name_list.push_back(clientname); ++client; } inputports.add ( client, clientname, count, portname, midibase::io::input, midibase::port::normal, 0, alias ); ++count; } ::jack_free(inports); result += count; } const char ** outports = ::jack_get_ports /* list of JACK ports */ ( m_jack_client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput /* tricky */ ); outputports.clear(); if (is_nullptr(outports)) /* check port validity */ { #if defined SEQ66_MISSING_JACK_VIRTUAL_PORTS /* * Not really an error, though perhaps we want to warn about it. * As with the input port, we create a virtual port. */ warnprint("No JACK out-ports, making a virtual port"); int client = 0; std::string clientname = seq_client_name(); std::string portname = "midi out 0"; outputports.add ( client, clientname, 0, portname, midibase::io::output, midibase::port::manual ); ++result; #endif } else { tokenization client_name_list; int client = -1; int count = 0; while (not_nullptr(outports[count])) { std::string fullname = outports[count]; std::string clientname; std::string portname; std::string alias = get_port_alias_by_name(fullname); if (alias == fullname) alias.clear(); extract_names(fullname, clientname, portname); if (client == -1 || clientname != client_name_list.back()) { client_name_list.push_back(clientname); ++client; } outputports.add ( client, clientname, count, portname, midibase::io::output, midibase::port::normal, 0, alias ); ++count; } ::jack_free(outports); result += count; } } return result; } /** * If the name includes "system:", try getting an alias instead via * jack_port_by_name() and jack_port_get_aliases(). * * The cast of the result of malloc() is needed in C++, but not C. * * The jack_port_t pointer type is actually an opaque value equivalent to an * integer greater than 1, which is a port index. * * Examples of aliases retrieved: * \verbatim Out-port: "system:midi_capture_2": alsa_pcm:Launchpad-Mini/midi_capture_1 Launchpad-Mini:midi/capture_1 \endverbatim * * and * \verbatim In-port "system:midi_playback_2": alsa_pcm:Launchpad-Mini/midi_playback_1 Launchpad-Mini:midi/playback_1 \endverbatim * * Ports created by "a2jmidid --export-hw" do not have JACK aliases. Ports * created by Seq66 do not have JACK aliases. Ports created by qsynth do not * have JACK aliases. * * \param name * Provides the name of the port as retrieved by jack_get_ports(). This * name is used to look up the JACK port handle. * * \return * Returns the alias, if it exists, for a system port. That is, one with * "system:" in its name. Brittle. And some JACK systems do not provide * an alias. */ std::string midi_jack_info::get_port_alias_by_name (const std::string & name) { bool is_system_port = contains(name, "system:"); /* brittle code */ std::string result; if (is_system_port) { jack_port_t * p = ::jack_port_by_name(m_jack_client, name.c_str()); if (not_NULL(p)) { char * aliases[2]; const int sz = ::jack_port_name_size(); aliases[0] = reinterpret_cast(malloc(sz)); /* alsa_pcm */ aliases[1] = reinterpret_cast(malloc(sz)); /* dev name */ if (is_nullptr_2(aliases[0], aliases[1])) return result; /* bug out */ aliases[0][0] = aliases[1][0] = 0; /* 0 length */ int rc = ::jack_port_get_aliases(p, aliases); if (rc > 1) { std::string nick = std::string(aliases[1]); /* brittle */ auto colonpos = nick.find_first_of(":"); /* brittle */ if (colonpos != std::string::npos) result = nick.substr(0, colonpos); /* * Another bit of brittleness: the name generated via the * a2jmidid program uses spaces, but the system alias returned * by JACK uses a hyphen. Convert them to spaces. */ auto hyphenpos = result.find_first_of("-"); /* brittle */ while (hyphenpos != std::string::npos) { result[hyphenpos] = ' '; hyphenpos = result.find_first_of("-", hyphenpos); } } else { if (rc < 0) errprint("JACK port aliases error"); else infoprint("JACK aliases unavailable"); } free(aliases[0]); free(aliases[1]); } } return result; } /** * Handle port registration and unregistration. A feature for the future! * * \param is_my_port * Provides the result of the call to the jack_port_is_mine() function. * * \param portid * Provides the port number provided to the port-registration callback. * * \param registration * True if the port is being registered, and false if it is being * unregistered with JACK. * * \param portname * Provides the name obtained by calling jack_port_short_name(). */ void midi_jack_info::update_port_list ( bool is_my_port, int portid, bool registration, const std::string & shortname, const std::string & longname ) { #if defined USE_PORTSMAP_ACTIVE_STATUS bool permitted = rc().portmaps_active(); #else bool permitted = true; #endif if (permitted) { #if defined SEQ66_MIDI_PORT_REFRESH midi_jack * mj = lookup_midi_jack(shortname, longname); bool is_new = is_nullptr(mj); if (is_new) { /* * Add the port to this list and to the appropriate busarray. * This begs the question, how to detect input versus output? * If all is established, we can query the midibus associated * this midi_jack_info. */ if (is_my_port) { /* * Should not do anything. This port will be added to the * useful ports for Seq66 in the normal course of handling. */ } else { /* * A new external port. Add it to the busarray, midi_jack_info * list, and to the portmap as "disabled". Then it can be * checked for later registrations and unregistrations. */ } } else { if (is_my_port) { /* * The port exists. If unregistered, simply disable the port in * this list and in the busarray. If registered, make sure the * port status is unchanged. */ } else { /* * Same as above? */ } } #endif // defined SEQ66_MIDI_PORT_REFRESH } else { if (! rc().investigate() && ! is_my_port) { std::string pinfo = shortname; pinfo += " #"; pinfo += std::to_string(portid); if (! longname.empty()) { pinfo += "("; pinfo += longname; pinfo += ")"; } pinfo += " "; pinfo += registration ? "registered" : "unregistered"; pinfo += "; ignored"; info_message("External port", pinfo); } } } #if defined SEQ66_MIDI_PORT_REFRESH /** * By this time, the midi_jack::port_name() returns the short name of the * port as registered, so we can look up port_name() here. * * Important: * * At present, the list of midi_jacks contains only the ones that are * enabled! */ midi_jack * midi_jack_info::lookup_midi_jack ( const std::string & shortname, const std::string & longname ) { midi_jack * result = nullptr; if (! shortname.empty()) { const portlist & ports = jack_ports(); /* midi_jack pointers */ #if defined SEQ66_PLATFORM_DEBUG if (rc().investigate()) { char value[c_async_safe_utoa_size]; char temp[512]; async_safe_utoa(value, unsigned(count())); std::strcpy(temp, "!! Port lookup: "); std::strcat(temp, value); std::strcat(temp, " jack ports\n short: "); std::strcat(temp, shortname.c_str()); std::strcat(temp, "\n long: "); std::strcat(temp, longname.c_str()); async_safe_strprint(temp, false); } #endif for (const auto mj : ports) { #if defined SEQ66_PLATFORM_DEBUG if (rc().investigate()) { char temp[512]; temp[0] = 0; std::strcpy(temp, "jack port: "); std::strcat(temp, mj->port_name().c_str()); std::strcat(temp, "\n jack remote: "); std::strcat(temp, mj->remote_port_name().c_str()); async_safe_strprint(temp); } #endif bool match = mj->port_name() == shortname; if (! match && ! longname.empty()) match = mj->port_name() == longname; if (match) { if (rc().investigate()) async_safe_strprint(mj->port_name().c_str()); result = mj; break; } } } return result; } #endif // defined SEQ66_MIDI_PORT_REFRESH /** * Useful for trouble-shooting and exploration. */ void midi_jack_info::show_details () const { int count = 0; const portlist & ports = jack_ports(); /* midi_jack pointers */ for (const auto mj : ports) { std::string d = "Index "; d += std::to_string(count); d += ": "; d += mj->details(); printf("%s\n", d.c_str()); ++count; } } /** * Sets up all of the I/O ports, represented by midibus objects, that have * been created. * * The main JACK client is activated, and then all non-virtual ports are * simply connected. * * Each JACK port's midi_jack::api_connect() function decides whether or not * to activate before making the connection. * * Issue #60: GUI option to disable auto midi port creation/connection. * * \return * Returns true if activation succeeds. */ bool midi_jack_info::api_connect () { bool result = not_nullptr(client_handle()); if (result) { m_jack_buffer_size = ::jack_get_buffer_size(client_handle()); int rcode = ::jack_activate(client_handle()); result = rcode == 0; if (result) { int bsize = rc().jack_buffer_size(); if (bsize > 0) { jack_nframes_t sz = jack_nframes_t(bsize); rcode = ::jack_set_buffer_size(client_handle(), sz); result = rcode == 0; if (result) status_message("JACK buffer size", std::to_string(bsize)); else error_message("JACK set buffer size failed"); } m_jack_sample_rate = jack_get_sample_rate(client_handle()); } } if (result && rc().jack_auto_connect()) /* issue #60 */ { for (auto m : bus_container()) /* midibus pointers */ { if (m->is_port_connectable()) { result = m->api_connect(); if (! result) break; } } } if (! result) { m_error_string = "JACK cannot activate/connect I/O"; error(rterror::kind::warning, m_error_string); } return result; } /** * Sets the PPQN numeric value, then makes JACK calls to set up the PPQ * tempo. * * \param p * The desired new PPQN value to set. */ void midi_jack_info::api_set_ppqn (int p) { midi_info::api_set_ppqn(p); } /** * Sets the BPM numeric value, then makes JACK calls to set up the BPM * tempo. These calls might need to be done in a JACK callback. * Why is this called twice? * * \param b * The desired new BPM value to set. */ void midi_jack_info::api_set_beats_per_minute (midibpm b) { midi_info::api_set_beats_per_minute(b); // Need JACK specific tempo-setting here if applicable. } /** * Start the given JACK MIDI port. This function is called by * api_get_midi_event() when an JACK event SND_SEQ_EVENT_PORT_START is * received. * * - Get the API's client and port information. * - Do some capability checks. * - Find the client/port combination among the set of input/output busses. * If it exists and is not active, then mark it as a replacement. If it * is not a replacement, it will increment the number of input/output * busses. * * We can simplify this code a bit by using elements already present in * midi_jack_info. * * \param masterbus * Provides the object needed to get access to the array of input and * output buss objects. * * \param bus * Provides the JACK bus/client number. * * \param port * Provides the JACK client port. */ void midi_jack_info::api_port_start ( mastermidibus & /*masterbus*/, int /*bus*/, int /*port*/ ) { // no code } /** * We might be able to eliminate this function. */ int midi_jack_info::api_poll_for_midi () { (void) microsleep(std_sleep_us()); return 0; } /** * Grab a MIDI event. * * \todo * Return a bussbyte or c_bussbyte_max value instead of a boolean. * * \param inev * The event to be set based on the found input event. We should make * this value a reference someday. Not used here. * * \return * Always returns false. Will eventually delete this function. */ bool midi_jack_info::api_get_midi_event (event * /*inev*/) { return false; } /** * This function merely eats the string passed as a parameter. */ static void jack_message_bit_bucket (const char *) { // Into the bit-bucket with ye ya scalliwag! } /** * This function silences JACK error output to the console. Probably not * good to silence this output, but let's provide the option, for the sake of * symmetry, consistency, what have you. */ void silence_jack_errors (bool silent) { if (silent) ::jack_set_error_function(jack_message_bit_bucket); } /** * This function silences JACK info output to the console. We were getting * way too many informational message, to the point of obscuring the debug * and error output. */ void silence_jack_info (bool silent) { if (silent) ::jack_set_info_function(jack_message_bit_bucket); } /** * JACK detection function. Just opens a client without activating it, then * closes it. * * However, if only jackdbus is running, this function will detect JACK, * but later attempts to activate JACK and open ports will fail. * * falkTX wrote: * * Both jackd and jackdbus implement a JACK server. * * - Jackd is started via command-line and having it running means the * jack server is also running. * - Jackdbus is a user service that can stop/start the JACK server * dynamically. jackd != JACK server. * * Jackdbus can be always running because it doesn't imply the JACK * server is also running. It's only there to listen for requests on the * dbus service. If jackdbus is running the JACK server can be started * with: * * $ jack_control start * * If jackdbus is running both jack_client_open() and jack_activate() work. * We need a better test. * * To do: Use this function to log the JACK version information: * * const char * version = ::jack_get_version_string(); * * Needs testing under various conditions. */ bool detect_jack (bool forcecheck) { static bool s_already_checked = false; static bool s_jack_was_detected = false; bool result = false; if (forcecheck) { s_already_checked = s_jack_was_detected = false; } if (s_already_checked) { result = s_jack_was_detected; } else { const char * cname = "jack_detector"; jack_status_t status; jack_status_t * ps = &status; jack_options_t jopts = JackNoStartServer; jack_client_t * jackman = ::jack_client_open(cname, jopts, ps); if (not_nullptr(jackman)) { int rc = ::jack_activate(jackman); if (rc == 0) { /* * Rather than using JackPortIsInput or JackPortIsOutput, * we use 0 to avoid port-selection based on port flags. */ const char ** ports = ::jack_get_ports ( jackman, NULL, JACK_DEFAULT_MIDI_TYPE, 0 ); result = not_nullptr(ports); if (result) { int count = 0; while (not_nullptr(ports[count])) ++count; result = count > 0; ::jack_free(ports); } ::jack_deactivate(jackman); } (void) ::jack_client_close(jackman); } s_already_checked = true; if (result) s_jack_was_detected = true; } if (! result) { warnprint("JACK not detected"); } return result; } } // namespace seq66 #endif // SEQ66_JACK_SUPPORT /* * midi_jack_info.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/src/midi_probe.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midi_probe.cpp * * Functions to check MIDI inputs and outputs, based on the RtMidi test * programs. * * \library seq66 application * \author Gary P. Scavone, 2003-2012; refactoring by Chris Ahlstrom * \date 2016-11-19 * \updates 2022-03-13 * \license See above. * * We include this test code in our library, rather than in a separate * application, because we want to include some diagnostic code in the * application. */ #include #include #include #include "midi_probe.hpp" #include "midibus_rm.hpp" #include "rtmidi.hpp" /* rtmidi_in and rt_midi_out */ #include "util/basic_macros.hpp" namespace seq66 { /** * Function to get RtMidi API names in a reusable manner. * * \param i * The integer value code for the desired API. Must range from * int(rtmidi_api::unspecified) to int(rtmidi_api::max) minus one. * * \return * Returns a human-readable name for the API. */ std::string midi_api_name (int i) { static std::map s_api_map; static bool s_map_is_initialized = false; if (! s_map_is_initialized) { s_api_map[rtmidi_api::unspecified] = "Unspecified"; s_api_map[rtmidi_api::alsa] = "ALSA"; s_api_map[rtmidi_api::jack] = "Jack"; #if defined SEQ66_USE_RTMIDI_API_ALL /* * We're not supporting these until we get a simplified seq66-friendly * API worked out. May take years to get to this. Currently, Windows * is supported by our portmidi implementation. So is OSX, but we * have no way to test it right now. */ s_api_map[rtmidi_api::macosx_core] = "OSX CoreMidi"; s_api_map[rtmidi_api::windows_mm] = "Win MultiMedia"; s_api_map[rtmidi_api::dummy] = "Dummy"; #endif s_map_is_initialized = true; } std::string result = "Unknown MIDI API"; if (i >= int(rtmidi_api::unspecified) && i < int(rtmidi_api::max)) result = s_api_map[rtmidi_api(i)]; return result; } /** * Formerly the main program of the RtMidi test program midiprobe. * We will upgrade this function for some better testing eventually. * It uses the functionality of the midi_info/rtmidi_info objects, plus * its own version of some of that functionality. * * \return * Currently always returns 0. */ int midi_probe () { static rtmidi_info s_rtmidi_info_dummy ( rtmidi_api::unspecified, "probe", 192, 120 ); static midibus s_midibus_dummy(s_rtmidi_info_dummy, 0); rtmidi_api_list apis; rtmidi_info::get_compiled_api(apis); std::cout << "\nCompiled APIs:\n"; for (unsigned i = 0; i < apis.size(); ++i) { std::cout << " " << midi_api_name(i) << std::endl; } try /* rtmidi constructors; exceptions possible */ { rtmidi_info dummyinfo ( rtmidi_api::unspecified, "probe", 192, 120 ); rtmidi_in midiin(s_midibus_dummy, dummyinfo); int index = api_to_int(rtmidi_info::selected_api()); std::string name = midi_api_name(index); std::cout << "MIDI Input/Output API: " << name << std::endl ; int nports = midiin.get_port_count(); std::cout << nports << " MIDI input sources:" << std::endl; for (int i = 0; i < nports; ++i) { std::string portname = midiin.get_port_name(); std::cout << " Input Port #" << i+1 << ": " << portname << std::endl ; } /* * We actually need to get this object in the loop! */ rtmidi_out midiout(s_midibus_dummy, dummyinfo); std::cout << std::endl; nports = midiout.get_port_count(); std::cout << nports << " MIDI output ports:" << std::endl; for (int i = 0; i < nports; ++i) { std::string portname = midiout.get_port_name(); std::cout << " Output Port #" << i+1 << ": " << portname << std::endl ; } std::cout << std::endl; } catch (const rterror & error) { error.print_message(); } return 0; } } // namespace seq66 /* * midi_probe.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/src/midibus.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file midibus.cpp (rtmidi) * * This module declares/defines the base class for MIDI I/O under one of * the rtmidi frameworks. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-11-21 * \updates 2025-01-20 * \license GNU GPLv2 or above * * This file provides a cross-platform implementation of the midibus class. * Based on our super-heavily refactored version of the RtMidi project * included in this library. Currently only ALSA and JACK are supported. */ #include "cfg/settings.hpp" /* seq66::rc() */ #include "midi/event.hpp" /* seq66::event and macros */ #include "midibus_rm.hpp" /* seq66::midibus for rtmidi */ #include "rtmidi.hpp" /* RtMidi updated API header file */ #include "rtmidi_info.hpp" /* seq66::rtmidi_info (new) */ namespace seq66 { /** * Normal-port and virtual-port constructor. * * \param rt * Provides the rtmidi_info object to use to obtain the * client ID (buss ID), port ID, and port name, as obtained via calls to * the ALSA, JACK, Core MIDI, or Windows MM subsystems. * We need it to provide the single ALSA "handle" needed in the in * Seq66 buss model, where the master MIDI buss provides it to be * used by all the MIDI buss objects. * * \param index * This is the index into the rtmidi object, and is used to get the * desired client and port information. It is an index into the * info container held by the rtmidi object. * * \param iotype * Indicates that the port is an input port, as opposed to an output port. * However, in JACK we need to swap the I/O semantics. For now we must * let the caller (mastermidibus) do this. * * \param porttype * Indicates that the port is also a system port (i.e. always present) or * indicates that the port is virtual, as opposed to normal. * * \param bussoverride * Optional buss ID, if not equal to the index parameter. */ midibus::midibus ( rtmidi_info & rt, int index, midibase::io iotype, midibase::port porttype, int bussoverride ) : midibase ( rt.app_name(), rt.get_bus_name(index), rt.get_port_name(index), index, is_good_buss(bussoverride) ? bussoverride : rt.get_bus_id(index), rt.get_port_id(index), /* ca 2023-05-07 was "index" */ rt.global_queue(), rt.ppqn(), rt.bpm(), iotype, /* perhaps I/O is swapped (JACK) */ porttype, rt.get_port_alias(index) ), m_rt_midi (nullptr), m_master_info (rt) /* master_info() accessor */ { if (porttype == port::manual) { /* * Set the buss ID for virtual ports to 0. We might consider another * number. */ bus_name(rc().app_client_name()); if (is_null_buss(bus_id())) set_bus_id(0); bool isinput = iotype == midibase::io::input; std::string pname = "midi "; pname += isinput ? "in" : "out"; if (index >= 0) { pname += " "; pname += std::to_string(index); port_name(pname); set_port_id(index); set_bus_id(index); set_name(rt.app_name(), bus_name(), port_name()); } } else { int portcount = rt.get_port_count(); if (index < portcount) { int id = rt.get_port_id(index); if (id >= 0) set_port_id(id); id = rt.get_bus_id(index); if (id >= 0) set_bus_id(id); /* * This is called with identical parameters in the midibase class. * * set_name * ( * rt.app_name(), rt.get_bus_name(index), * rt.get_port_name(index) * ); */ } } } /** * The destructor closes out the RtMidi MIDI infrastructure. */ midibus::~midibus () { if (not_nullptr(m_rt_midi)) { delete m_rt_midi; m_rt_midi = nullptr; } } /** * We want to see if we can get by without this full check: * * return not_nullptr(m_rt_midi) && m_rt_midi->have_api(); */ bool midibus::good_api () const { return not_nullptr(m_rt_midi); } /** * Connects to another port. If the port is an input port, but is not * configured (by the user or the "rc" configuration file), then it is not * connected, and this is not an error. Output ports are always connected. * * WHY IS THE ABOVE THE CASE? * * Note that the api_connect() function will errprint is own errors. But if * we expected to be able to connect, and have a null rt_midi pointer, then * this is a reported error. * * \return * Returns true if the connection was made successfully. */ bool midibus::api_connect () { bool result = good_api(); if (result) { result = m_rt_midi->api_connect(); } else { char temp[80]; snprintf ( temp, sizeof temp, "null pointer port '%s'", display_name().c_str() ); errprintfunc(temp); } return result; } /** * Polls for MIDI events. This is the API implementation for RtMidi. * * This should work only for input busses, so we need to insure this at some * point. Currently, this is the domain of the master bus. We also should * make this routine just check the input queue size and then read the queue. * Note that the ALSA handle checks incoming MIDI events and either passes * them to the callback function or pushes them onto the input queue. * * \return * Returns 0 if the polling succeeded, and 1 if it failed. If the buss * hasn't been initialized, it has a null m_rt_midi pointer, and will * return 0. This can happen normally when a MIDI input port is * configured to be disabled. */ int midibus::api_poll_for_midi () { if (port_enabled()) return good_api() ? m_rt_midi->api_poll_for_midi() : 0 ; else return 0; } /** * Gets a MIDI event. * * \param inev * The location to deposit the MIDI event data. * * \return * Returns true if a MIDI event was obtained. */ bool midibus::api_get_midi_event (event * inev) { if (port_enabled()) { return good_api() ? m_rt_midi->api_get_midi_event(inev) : false ; } else return false; } /** * Initializes the MIDI output port. * * Currently, we use the default values for the rtmidi API, the queue number, * and the queue size. * * \return * Returns true if the output port was successfully opened. */ bool midibus::api_init_out () { bool result = false; try { m_rt_midi = new rtmidi_out(*this, master_info()); result = m_rt_midi->api_init_out(); } catch (const rterror & err) { err.print_message(); } return result; } /** * Initializes the MIDI virtual output port. * * \return * Returns true if the output port was successfully opened. */ bool midibus::api_init_out_sub () { bool result = false; try { m_rt_midi = new rtmidi_out(*this, master_info()); result = m_rt_midi->api_init_out_sub(); } catch (const rterror & err) { err.print_message(); } return result; } /** * Initializes the MIDI input port. Note that this port already exists if * we're coming back from suspending the port. * * \return * Returns true if the input port was successfully opened. */ bool midibus::api_init_in () { bool result = false; try { if (is_nullptr(m_rt_midi)) m_rt_midi = new rtmidi_in(*this, master_info()); result = good_api(); if (result) result = m_rt_midi->api_init_in(); } catch (const rterror & err) { err.print_message(); } return result; } /** * Initializes the MIDI virtual input port. * * \return * Returns true if the input port was successfully opened. */ bool midibus::api_init_in_sub () { bool result = false; try { m_rt_midi = new rtmidi_in(*this, master_info()); result = good_api(); if (result) result = m_rt_midi->api_init_in_sub(); } catch (const rterror & err) { err.print_message(); } return result; } /** * Seems to cause no ill effects. :-D */ bool midibus::api_deinit_out () { return good_api() ? m_rt_midi->api_deinit_out() : false ; } /** * Forwards the de-initialization call to the API object that implements * it. We don't bother checking the m_rt_midi pointer. If it is null, * it is the programmer's fault. * * \return * Returns the result of the m_rt_midi->api_deinit_in() call. */ bool midibus::api_deinit_in () { return good_api() ? m_rt_midi->api_deinit_in() : false ; } /** * Takes a native event, and encodes to a Windows message, and writes it to * the queue. It fills a small byte buffer, sets the MIDI channel, make a * message of it, and writes the message. * * Again, DO WE NEED to distinguish between input and output here? * * Note that we're doing a double-forwarding here, which may lower * throughput. * * \param e24 * The MIDI event to play. * * \param channel * The channel on which to play the event. */ void midibus::api_play (const event * e24, midibyte channel) { if (good_api()) m_rt_midi->api_play(e24, channel); } void midibus::api_sysex (const event * e24) { if (good_api()) m_rt_midi->api_sysex(e24); } /** * Continue from the given tick. This function implements only the * RtMidi-specific code. * * Note that, unlike in PortMidi, here we do not deal with zeroing the event * timestamp. * * \param tick * The tick to continue from; unused in the RtMidi API implementation. * * \param beats * The calculated beats. This calculation is made in the * midibase::continue_from() function. */ void midibus::api_continue_from (midipulse tick, midipulse beats) { if (good_api()) m_rt_midi->api_continue_from(tick, beats); } /** * Sets the MIDI clock a-runnin', if the clock type is not e_clock::none. * This function is called by midibase::start(). No timestamp handling. */ void midibus::api_start () { if (good_api()) m_rt_midi->api_start(); } /** * Stops the MIDI clock, if the clock-type is not e_clock::none. * This function is called by midibase::stop(). No timestamp handling. */ void midibus::api_stop () { if (good_api()) m_rt_midi->api_stop(); } /** * Generates MIDI clock. This function is called by midibase::clock(). No * timestamp handling. * * \param tick * The clock tick value. */ void midibus::api_clock (midipulse tick) { if (good_api()) m_rt_midi->api_clock(tick); } } // namespace seq66 /* * midibus.cpp (rtmidi) * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/src/rtmidi.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file rtmidi.cpp * * A class for managing various MIDI APIs. * * \library seq66 application * \author Gary P. Scavone; refactoring by Chris Ahlstrom * \date 2016-11-14 * \updates 2025-02-02 * \license See above. * * An abstract base class for realtime MIDI input/output. * * This class implements some common functionality for the realtime * MIDI input/output subclasses rtmidi_in and rtmidi_out. * * RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/ * * RtMidi: realtime MIDI i/o C++ classes * * GitHub issue #165: enabled a build and run with no JACK support. */ #include "cfg/settings.hpp" /* seq66::rc().with_jack_...() */ #include "rtmidi.hpp" /* seq66::rtmidi, etc. */ #include "rtmidi_info.hpp" /* seq66::rtmidi_info, etc. */ #include "seq66_rtmidi_features.h" /* selects the usable APIs */ #include "util/basic_macros.hpp" /* C++ version of easy macros */ #if defined SEQ66_JACK_SUPPORT #include /* jack_get_version_string() */ #endif #if defined SEQ66_BUILD_UNIX_JACK #include "midi_jack.hpp" #endif #if defined SEQ66_BUILD_LINUX_ALSA #include "midi_alsa.hpp" #endif namespace seq66 { /* * class rtmidi */ /** * Default constructor. We have commented out the call to * jack_get_version_string() because it is not present in the original * version of JACK. * * \param parentbus * This is the midibus that the rtmidi object is going to implement, by * forwarding calls to the selected MIDI subsystem (e.g. ALSA or JACK). * * \param info * This object provides the system's enumerated busses/ports as found by * the selected MIDI subsystem. */ rtmidi::rtmidi (midibus & parentbus, rtmidi_info & info) : midi_api (parentbus, *(info.get_api_info())), m_rtmidi_info (info), m_midi_api (nullptr) /* get_api() pointer accessor */ { #if defined SEQ66_JACK_GET_VERSION_STRING const char * jv = jack_get_version_string(); if (not_nullptr(jv) && strlen(jv) > 0) set_jack_version(std::string(jv)); #else std::string jv{"JACK < v. 1"}; set_jack_version(jv); #endif #if defined SND_LIB_VERSION_STR /* * Since this is build info, we do not use the run-time version of ALSA * that can be obtained from snd_asoundlib_version(). */ std::string ver{std::string(SND_LIB_VERSION_STR)}; std::string runtime{snd_asoundlib_version()}; ver += " (runtime "; ver += runtime; ver += ")"; set_alsa_version(ver); #endif } /** * Destructor. */ rtmidi::~rtmidi () { delete_api(); } /* * class rtmidi_in */ /** * Constructs the desired MIDI API. * * If no system support for the specified API value is found, we issue a * warning and continue as if no API was specified. In this case, we 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. * * \param parentbus * This is the midibus that the rtmidi object is going to implement, by * forwarding calls to the selected MIDI subsystem (e.g. ALSA or JACK). * * \param info * Contains information about the existing ports and the selected MIDI * API. Actually, the selected MIDI API is a static value of the * rtmidi_info class. * * \throw * This function will throw an rterror object if it cannot find a MIDI * API to use. */ rtmidi_in::rtmidi_in (midibus & parentbus, rtmidi_info & info) : rtmidi (parentbus, info) { if (rtmidi_info::selected_api() != rtmidi_api::unspecified) { openmidi_api(rtmidi_info::selected_api(), info); if (is_nullptr(get_api())) { errprintfunc("no system support for specified API"); } } else { /* * Generally, we should have already selected the API at this point, * so this is fallback code. */ rtmidi_api_list apis; rtmidi_info::get_compiled_api(apis); for (unsigned i = 0; i < apis.size(); ++i) { openmidi_api(apis[i], info); if (info.get_api_info()->get_port_count() > 0) { rtmidi_info::selected_api(apis[i]); /* log API that worked */ break; } } if (is_nullptr(get_api())) { /* * It should not be possible to get here because the preprocessor * definition SEQ66_BUILD_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 = "no rtmidi API support found"; throw(rterror(errortext, rterror::kind::unspecified)); } } } /** * A do-nothing virtual destructor. */ rtmidi_in::~rtmidi_in() { // no code } /** * Opens the desired MIDI API. * * \param api * The enum value for the desired MIDI API. If not specified, first JACK * is tried, then ALSA. * * \param info * Holds information about the API's setup (e.g. a list of the ALSA ports * found on the system, the main ALSA "handle" pointer, plus the PPQN, * BPM, and other information). */ void rtmidi_in::openmidi_api (rtmidi_api api, rtmidi_info & info) { bool got_an_api = false; if (not_nullptr(info.get_api_info())) { midi_info & midiinfo = *(info.get_api_info()); delete_api(); if (api == rtmidi_api::unspecified) { if (rc().with_jack_midi()) { if (api == rtmidi_api::jack) { #if defined SEQ66_BUILD_UNIX_JACK && defined SEQ66_JACK_SUPPORT bool ok = detect_jack(); if (ok) { midi_in_jack * mijp = new (std::nothrow) midi_in_jack ( parent_bus(), midiinfo ); if (not_nullptr(mijp)) { set_api(mijp); got_an_api = true; } } #endif } } if (! got_an_api) { if (api == rtmidi_api::alsa) { #if defined SEQ66_BUILD_LINUX_ALSA midi_in_alsa * miap = new (std::nothrow) midi_in_alsa ( parent_bus(), midiinfo ); if (not_nullptr(miap)) { set_api(miap); got_an_api = true; } #endif } } } else if (api == rtmidi_api::jack) { #if defined SEQ66_BUILD_UNIX_JACK && defined SEQ66_JACK_SUPPORT bool ok = detect_jack(); if (ok) { midi_in_jack * mijp = new (std::nothrow) midi_in_jack ( parent_bus(), midiinfo ); if (not_nullptr(mijp)) { set_api(mijp); got_an_api = true; } } #endif } else if (api == rtmidi_api::alsa) { #if defined SEQ66_BUILD_LINUX_ALSA midi_in_alsa * miap = new (std::nothrow) midi_in_alsa ( parent_bus(), midiinfo ); if (not_nullptr(miap)) { set_api(miap); got_an_api = true; } #endif } } if (! got_an_api) { errprintfunc("could not create an input API"); } } /* * class rtmidi_out */ /** * Principal constructor. Attempt to open the specified API. If there is no * system support for the specified API value, then issue a warning and * continue as if no API was specified. In that case, we 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. * * \param parentbus * This is the midibus that the rtmidi object is going to implement, by * forwarding calls to the selected MIDI subsystem (e.g. ALSA or JACK). * * \param info * Contains information about the existing ports and the selected MIDI * API. Actually, the selected MIDI API is a static value of the * rtmidi_info class. * * \throw * This function will throw an rterror object if it cannot find a MIDI * API to use. */ rtmidi_out::rtmidi_out (midibus & parentbus, rtmidi_info & info) : rtmidi (parentbus, info) { if (rtmidi_info::selected_api() != rtmidi_api::unspecified) { openmidi_api(rtmidi_info::selected_api(), info); if (not_nullptr(get_api())) { return; } else { errprintfunc("no system support for specified API"); } } /* * Generally, we should have already selected the API at this point, * so this is fallback code. */ rtmidi_api_list apis; rtmidi_info::get_compiled_api(apis); for (unsigned i = 0; i < apis.size(); ++i) { openmidi_api(apis[i], info); if (info.get_api_info()->get_port_count() > 0) { rtmidi_info::selected_api(apis[i]); /* log the API that worked */ break; } } if (is_nullptr(get_api())) { /* * It should not be possible to get here because the preprocessor * definition SEQ66_BUILD_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 = "no rtmidi API support found"; throw(rterror(errorText, rterror::kind::unspecified)); } } /** * A do-nothing virtual destructor. */ rtmidi_out::~rtmidi_out() { // no code } /*** * Opens the desired MIDI API. * * \param api * Provides the API to be constructed. * * \param info * Provides information about the port. * * \throw * This function will throw an rterror object if it cannot find a MIDI * API to use. */ void rtmidi_out::openmidi_api (rtmidi_api api, rtmidi_info & info) { bool got_an_api = false; if (not_nullptr(info.get_api_info())) { midi_info & midiinfo = *(info.get_api_info()); delete_api(); if (api == rtmidi_api::unspecified) { if (rc().with_jack_midi()) { if (api == rtmidi_api::jack) { #if defined SEQ66_BUILD_UNIX_JACK && defined SEQ66_JACK_SUPPORT bool ok = detect_jack(); if (ok) { midi_out_jack * mojp = new (std::nothrow) midi_out_jack ( parent_bus(), midiinfo ); if (not_nullptr(mojp)) { set_api(mojp); got_an_api = true; } } #endif } } if (! got_an_api) { if (api == rtmidi_api::alsa) { #if defined SEQ66_BUILD_LINUX_ALSA midi_out_alsa * moap = new (std::nothrow) midi_out_alsa ( parent_bus(), midiinfo ); if (not_nullptr(moap)) { set_api(moap); got_an_api = true; } #endif } } } else if (api == rtmidi_api::jack) { #if defined SEQ66_BUILD_UNIX_JACK && defined SEQ66_JACK_SUPPORT bool ok = detect_jack(); if (ok) { midi_out_jack * mojp = new (std::nothrow) midi_out_jack ( parent_bus(), midiinfo ); if (not_nullptr(mojp)) { set_api(mojp); got_an_api = true; } } #endif } else if (api == rtmidi_api::alsa) { #if defined SEQ66_BUILD_LINUX_ALSA midi_out_alsa * moap = new (std::nothrow) midi_out_alsa ( parent_bus(), midiinfo ); if (not_nullptr(moap)) { set_api(moap); got_an_api = true; } #endif } } if (! got_an_api) { errprintfunc("could not create an output API"); } } } // namespace seq66 /* * rtmidi.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/src/rtmidi_info.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file rtmidi_info.cpp * * A class for managing various MIDI APIs. * * \library seq66 application * \author Chris Ahlstrom * \date 2016-12-08 * \updates 2025-01-20 * \license See above. * * An abstract base class for realtime MIDI input/output. This class * implements some common functionality enumerating MIDI clients and ports. * * GitHub issue #165: enabled a build and run with no JACK support. */ #include "cfg/settings.hpp" /* seq66::rc().with_jack_...() */ #include "rtmidi_info.hpp" /* seq66::rtmidi_info */ #include "seq66_rtmidi_features.h" /* selects the usable APIs */ #include "util/basic_macros.hpp" /* C++ version of easy macros */ #if defined SEQ66_BUILD_LINUX_ALSA #include "midi_alsa_info.hpp" #endif #if defined SEQ66_BUILD_UNIX_JACK #include "midi_jack_info.hpp" #endif namespace seq66 { /** * Holds the selected API code. */ rtmidi_api rtmidi_info::sm_selected_api = rtmidi_api::unspecified; /** * This is a static function to replace the midi_api version. */ std::string rtmidi_info::get_version () { return std::string(SEQ66_RTMIDI_VERSION); } /** * Gets the list of APIs compiled into the application. Note that we make * ALSA versus JACK a runtime option as it is in the legacy Sequencer64 * application. * * This is a static function to replace the midi_api version. * * \param apis * The API structure. */ void rtmidi_info::get_compiled_api (rtmidi_api_list & apis) { apis.clear(); /* * The order here will control the order of rtmidi's API search in the * constructor. For Linux, we will try JACK first, then fall back to * ALSA, and then to the dummy implementation. We were checking * rc().with_jack_transport(), but the "rc" configuration file has not yet * been read by the time we get to here. On the other hand, we can make * it default to "true" and see what happens. * * However, it seems silly to not provide the API categorically. */ #if defined SEQ66_BUILD_UNIX_JACK apis.push_back(rtmidi_api::jack); #endif #if defined SEQ66_BUILD_LINUX_ALSA apis.push_back(rtmidi_api::alsa); #endif if (apis.empty()) { std::string errortext = "no rtmidi API found"; throw(rterror(errortext, rterror::kind::unspecified)); } } /** * Default constructor. Code basically cut-and-paste from rtmidi_in or * rtmidi_out. Common code! */ rtmidi_info::rtmidi_info ( rtmidi_api api, const std::string & appname, int ppqn, midibpm bpm ) : m_info_api (nullptr) { if (api != rtmidi_api::unspecified) { bool ok = openmidi_api(api, appname, ppqn, bpm); if (ok) { if (not_nullptr(get_api_info())) /* TODO: encapsulate */ { if (get_all_port_info() >= 0) { selected_api(api); /* log API that worked */ return; } } } else { errprintfunc("No support for default MIDI API"); } } rtmidi_api_list apis; get_compiled_api(apis); for (unsigned i = 0; i < apis.size(); ++i) { if (openmidi_api(apis[i], appname, ppqn, bpm)) // get_api_info() { /* * For JACK, or any other API, there may be no ports (from other * applications) yet in place. */ if (not_nullptr(get_api_info())) /* TODO: encapsulate */ { if (get_all_port_info() >= 0) { selected_api(apis[i]); /* log API that worked */ break; } } } else { continue; } } if (is_nullptr(get_api_info())) { std::string errortext = "No rtmidi API found"; throw(rterror(errortext, rterror::kind::unspecified)); } } /** * Destructor. Gets rid of m_info_api and nullifies it. */ rtmidi_info::~rtmidi_info () { delete_api(); } /** * Opens the desired MIDI API. * * If the JACK API is tried, and found missing, we turn off all of the other * JACK flags found in the "rc" configuration file. Also, the loop in the * constructor will come back here to try the other compiled-in APIs * (currently just ALSA). * * \param api * The desired MIDI API. * * \param appname * The name of the application, to be passed to the midi_info-derived * constructor. * * \param ppqn * The PPQN value to pass along to the midi_info_derived constructor. * * \param bpm * The BPM (beats per minute) value to pass along to the * midi_info_derived constructor. * * \return * Returns true if a valid API is found. A valid API is on that is both * compiled into the application and is found existing on the host * computer (system). */ bool rtmidi_info::openmidi_api ( rtmidi_api api, const std::string & appname, int ppqn, midibpm bpm ) { bool result = false; delete_api(); #if defined SEQ66_BUILD_UNIX_JACK if (api == rtmidi_api::jack) { if (rc().with_jack_midi()) { #if defined SEQ66_BUILD_UNIX_JACK && defined SEQ66_JACK_SUPPORT bool ok = detect_jack(); if (ok) { midi_jack_info * mjip = new (std::nothrow) midi_jack_info ( appname, ppqn, bpm ); result = not_nullptr(mjip); if (result) result = set_api_info(mjip); } #else result = false; #endif if (! result) { /** * Disables the usage of JACK MIDI for the rest of the program * run. This includes JACK Transport, which also obviously * needs JACK to work. */ rc().with_jack_transport(false); rc().with_jack_master(false); rc().with_jack_master_cond(false); rc().with_jack_midi(false); } } } #endif #if defined SEQ66_BUILD_LINUX_ALSA if (api == rtmidi_api::alsa) { /* * ca 2023-04-15 * Encountered a weird "No such device" error (even though MPD could * play music through pulseaudio. So now we check the handle. */ midi_alsa_info * maip = new (std::nothrow) midi_alsa_info ( appname, ppqn, bpm ); result = not_nullptr_2(maip, maip->midi_handle()); if (result) { result = set_api_info(maip); if (result) rc().with_alsa_midi(true); } } #endif return result; } } // namespace seq66 /* * rtmidi_info.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ ================================================ FILE: seq_rtmidi/src/rtmidi_types.cpp ================================================ /* * This file is part of seq66. * * seq66 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. * * seq66 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 seq66; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ /** * \file rtmidi_types.cpp * * Classes that used to be structs. * * \library seq66 application * \author Gary P. Scavone; severe refactoring by Chris Ahlstrom * \date 2016-12-01 * \updates 2023-12-03 * \license See above. * * Provides some basic types for the (heavily-factored) rtmidi library, very * loosely based on Gary Scavone's RtMidi library. */ #include /* std::memcpy() */ #include "rtmidi_types.hpp" /* seq66::rtmidi, etc. */ #include "util/basic_macros.hpp" /* errprintfunc() macro, etc. */ namespace seq66 { /* * class midi_message */ #if defined SEQ66_SHOW_TIMING unsigned midi_message::sm_msg_number = 0; #endif /** * Constructs an empty MIDI message. Well, empty except for the timestamp * bytes. The caller will then push the data bytes for the MIDI event. * * \param ts * The timestamp of the event in ticks (pulses). */ midi_message::midi_message (midipulse ts) : #if defined SEQ66_SHOW_TIMING m_msg_number (sm_msg_number++), m_msg_send_time (0), #endif m_bytes (), m_timestamp (ts), m_input_buss (null_buss()) { // No code } /** * Constructs a midi_message from an array of bytes. * * \param mbs * Provides the data, which should start with the timestamp bytes, and * optionally followed by event bytes. * * \param sz * The putative size of the byte array. */ midi_message::midi_message (const midibyte * mbs, std::size_t sz) : #if defined SEQ66_SHOW_TIMING m_msg_number (sm_msg_number++), #endif m_bytes (), m_timestamp (0), m_input_buss (null_buss()) { for (std::size_t i = 0; i < sz; ++i) m_bytes.push_back(*mbs++); } /** * Shows the bytes in a string, for trouble-shooting. It includes only the * timestamp and the first few bytes. */ std::string midi_message::to_string () const { unsigned long ts = (unsigned long)(timestamp()); /* pulse or frame */ #if defined SEQ66_SHOW_TIMING std::string result = "Event #"; result += std::to_string(msg_number()); result += " @ "; #else std::string result = "Event @ "; #endif result += std::to_string(ts); result += ":"; int counter = 8; if (event_count() < counter) counter = event_count(); for (int i = 0; i < counter; ++i) { result += " "; if (i == 0) { char temp[8]; snprintf(temp, sizeof temp, "0x%2x", m_bytes[i]); result += temp; } else result += std::to_string(m_bytes[i]); } return result; } /* * class midi_queue */ /** * Default constructor. */ midi_queue::midi_queue () : m_front (0), m_back (0), m_size (0), m_ring_size (0), m_ring (nullptr) { allocate(); } /** * Destructor. */ midi_queue::~midi_queue () { deallocate(); } /** * * This would be better off as a constructor operation. But one step at a * time. */ void midi_queue::allocate (unsigned queuesize) { deallocate(); if (queuesize > 0 && is_nullptr(m_ring)) { m_ring = new(std::nothrow) midi_message[queuesize]; if (not_nullptr(m_ring)) m_ring_size = queuesize; } } /** * * This would be better off as a destructor operation. But one step at a * time. */ void midi_queue::deallocate () { if (not_nullptr(m_ring)) { delete [] m_ring; m_ring = nullptr; } } /** * * As long as we haven't reached our queue size limit, push the message. */ bool midi_queue::add (const midi_message & mmsg) { bool result = ! full(); if (result) { m_ring[m_back++] = mmsg; if (m_back == m_ring_size) m_back = 0; ++m_size; } else { /* * errprintfunc("message queue limit reached"); */ } return result; } /** * Pops, so to speak, the front message out of the queue, effectively * throwing it away. One useful call sequence is: * \verbatim midi_message latest = queue.front(); queue.pop(); \endverbatim * * An alternative is to use the pop_front() function instead. */ void midi_queue::pop () { --m_size; ++m_front; if (m_front == m_ring_size) m_front = 0; } /** * Pops a copy of the front message. Could be a little inefficient, since a * couple of copies are made, and we cannot use return-code optimization. * * Perhaps at some point we could use move semantics? * * \return * Returns a copy of the message that was in front before the popping. * If the queue is empty, an empty (all zeros) message is returned. * Can be checked with the midi_message::empty() function. */ midi_message midi_queue::pop_front () { midi_message result; if (m_size != 0) { result = m_ring[m_front]; pop(); } return result; } /* * Class rtmidi_in_data is used to hold the data needed by some of the JACK * callback functions. */ rtmidi_in_data::rtmidi_in_data () : m_queue (), m_first_message (true), m_continue_sysex (false) { // no body } } // namespace seq66 /* * rtmidi_types.cpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */